1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/updater/local_extension_cache.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/run_loop.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_util.h"
14 #include "base/test/sequenced_worker_pool_owner.h"
15 #include "base/values.h"
16 #include "chrome/common/extensions/extension_constants.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/test/test_browser_thread_bundle.h"
19 #include "content/public/test/test_utils.h"
20 #include "crypto/secure_hash.h"
21 #include "crypto/sha2.h"
22 #include "testing/gtest/include/gtest/gtest.h"
26 const char kTestExtensionId1
[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
27 const char kTestExtensionId2
[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
28 const char kTestExtensionId3
[] = "cccccccccccccccccccccccccccccccc";
32 namespace extensions
{
34 class LocalExtensionCacheTest
: public testing::Test
{
36 LocalExtensionCacheTest() {}
37 ~LocalExtensionCacheTest() override
{}
39 scoped_refptr
<base::SequencedTaskRunner
> background_task_runner() {
40 return background_task_runner_
;
43 // testing::Test overrides:
44 void SetUp() override
{
46 new base::SequencedWorkerPoolOwner(3, "Background Pool"));
47 background_task_runner_
= pool_owner_
->pool()->GetSequencedTaskRunner(
48 pool_owner_
->pool()->GetNamedSequenceToken("background"));
51 void TearDown() override
{
52 pool_owner_
->pool()->Shutdown();
53 base::RunLoop().RunUntilIdle();
56 base::FilePath
CreateCacheDir(bool initialized
) {
57 EXPECT_TRUE(cache_dir_
.CreateUniqueTempDir());
59 CreateFlagFile(cache_dir_
.path());
60 return cache_dir_
.path();
63 base::FilePath
CreateTempDir() {
64 EXPECT_TRUE(temp_dir_
.CreateUniqueTempDir());
65 return temp_dir_
.path();
68 void CreateFlagFile(const base::FilePath
& dir
) {
69 CreateFile(dir
.Append(
70 extensions::LocalExtensionCache::kCacheReadyFlagFileName
),
71 0, base::Time::Now());
74 void CreateExtensionFile(const base::FilePath
& dir
,
75 const std::string
& id
,
76 const std::string
& version
,
78 const base::Time
& timestamp
,
79 base::FilePath
* filename
) {
80 const base::FilePath file
= GetExtensionFileName(dir
, id
, version
, "");
83 CreateFile(file
, size
, timestamp
);
86 void CreateFile(const base::FilePath
& file
,
88 const base::Time
& timestamp
) {
89 std::string
data(size
, 0);
90 EXPECT_EQ(base::WriteFile(file
, data
.data(), data
.size()), int(size
));
91 EXPECT_TRUE(base::TouchFile(file
, timestamp
, timestamp
));
94 std::string
CreateSignedExtensionFile(const base::FilePath
& dir
,
95 const std::string
& id
,
96 const std::string
& version
,
98 const base::Time
& timestamp
,
99 base::FilePath
* filename
) {
100 std::string
data(size
, 0);
102 crypto::SecureHash
* hash
=
103 crypto::SecureHash::Create(crypto::SecureHash::SHA256
);
104 hash
->Update(data
.c_str(), size
);
105 uint8 output
[crypto::kSHA256Length
];
106 hash
->Finish(output
, sizeof(output
));
107 const std::string hex_hash
=
108 base::ToLowerASCII(base::HexEncode(output
, sizeof(output
)));
111 const base::FilePath file
=
112 GetExtensionFileName(dir
, id
, version
, hex_hash
);
115 EXPECT_EQ(base::WriteFile(file
, data
.data(), data
.size()), int(size
));
116 EXPECT_TRUE(base::TouchFile(file
, timestamp
, timestamp
));
121 base::FilePath
GetExtensionFileName(const base::FilePath
& dir
,
122 const std::string
& id
,
123 const std::string
& version
,
124 const std::string
& hash
) {
126 extensions::LocalExtensionCache::ExtensionFileName(id
, version
, hash
));
129 void WaitForCompletion() {
130 // In the worst case you need to repeat this up to 3 times to make sure that
131 // all pending tasks we sent from UI thread to task runner and back to UI.
132 for (int i
= 0; i
< 3; i
++) {
133 // Wait for background task completion that sends replay to UI thread.
134 pool_owner_
->pool()->FlushForTesting();
135 // Wait for UI thread task completion.
136 base::RunLoop().RunUntilIdle();
141 content::TestBrowserThreadBundle thread_bundle_
;
143 scoped_ptr
<base::SequencedWorkerPoolOwner
> pool_owner_
;
144 scoped_refptr
<base::SequencedTaskRunner
> background_task_runner_
;
146 base::ScopedTempDir cache_dir_
;
147 base::ScopedTempDir temp_dir_
;
149 DISALLOW_COPY_AND_ASSIGN(LocalExtensionCacheTest
);
152 static void SimpleCallback(bool* ptr
) {
156 TEST_F(LocalExtensionCacheTest
, Basic
) {
157 base::FilePath
cache_dir(CreateCacheDir(false));
159 LocalExtensionCache
cache(cache_dir
,
161 base::TimeDelta::FromDays(30),
162 background_task_runner());
163 cache
.SetCacheStatusPollingDelayForTests(base::TimeDelta());
165 bool initialized
= false;
166 cache
.Init(true, base::Bind(&SimpleCallback
, &initialized
));
169 EXPECT_FALSE(initialized
);
171 base::FilePath file10
, file01
, file20
, file30
;
172 CreateExtensionFile(cache_dir
, kTestExtensionId1
, "1.0", 100,
173 base::Time::Now() - base::TimeDelta::FromDays(1),
175 CreateExtensionFile(cache_dir
, kTestExtensionId1
, "0.1", 100,
176 base::Time::Now() - base::TimeDelta::FromDays(10),
178 CreateExtensionFile(cache_dir
, kTestExtensionId2
, "2.0", 100,
179 base::Time::Now() - base::TimeDelta::FromDays(40),
181 CreateExtensionFile(cache_dir
, kTestExtensionId3
, "3.0", 900,
182 base::Time::Now() - base::TimeDelta::FromDays(41),
185 CreateFlagFile(cache_dir
);
188 ASSERT_TRUE(initialized
);
190 // Older version should be removed on cache initialization.
191 EXPECT_FALSE(base::PathExists(file01
));
193 // All extensions should be there because cleanup happens on shutdown to
194 // support use case when device was not used to more than 30 days and cache
195 // shouldn't be cleaned before someone will have a chance to use it.
196 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, "", NULL
, NULL
));
197 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId2
, "", NULL
, NULL
));
198 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId3
, "", NULL
, NULL
));
200 bool did_shutdown
= false;
201 cache
.Shutdown(base::Bind(&SimpleCallback
, &did_shutdown
));
203 ASSERT_TRUE(did_shutdown
);
205 EXPECT_TRUE(base::PathExists(file10
));
206 EXPECT_FALSE(base::PathExists(file20
));
207 EXPECT_FALSE(base::PathExists(file30
));
210 TEST_F(LocalExtensionCacheTest
, KeepHashed
) {
211 base::FilePath
cache_dir(CreateCacheDir(false));
213 LocalExtensionCache
cache(cache_dir
, 1000, base::TimeDelta::FromDays(30),
214 background_task_runner());
215 cache
.SetCacheStatusPollingDelayForTests(base::TimeDelta());
217 bool initialized
= false;
218 cache
.Init(true, base::Bind(&SimpleCallback
, &initialized
));
221 EXPECT_FALSE(initialized
);
223 // Add three identical extensions with different hash sums
224 const base::Time time
= base::Time::Now() - base::TimeDelta::FromDays(1);
225 base::FilePath file
, file1
, file2
;
226 CreateExtensionFile(cache_dir
, kTestExtensionId1
, "1.0", 100, time
, &file
);
227 const std::string hash1
= CreateSignedExtensionFile(
228 cache_dir
, kTestExtensionId1
, "1.0", 100, time
, &file1
);
229 const std::string hash2
= CreateSignedExtensionFile(
230 cache_dir
, kTestExtensionId1
, "1.0", 123, time
, &file2
);
232 CreateFlagFile(cache_dir
);
235 ASSERT_TRUE(initialized
);
237 // Unhashed version should be removed on cache initialization.
238 EXPECT_FALSE(base::PathExists(file
));
239 // Both hashed versions should stay
240 EXPECT_TRUE(base::PathExists(file1
));
241 EXPECT_TRUE(base::PathExists(file2
));
243 // We should be able to lookup all three extension queries
245 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, "", NULL
, &version
));
246 EXPECT_EQ(version
, "1.0");
247 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, hash1
, NULL
, NULL
));
248 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, hash2
, NULL
, NULL
));
251 TEST_F(LocalExtensionCacheTest
, KeepLatest
) {
252 base::FilePath
cache_dir(CreateCacheDir(false));
254 LocalExtensionCache
cache(cache_dir
, 1000, base::TimeDelta::FromDays(30),
255 background_task_runner());
256 cache
.SetCacheStatusPollingDelayForTests(base::TimeDelta());
258 bool initialized
= false;
259 cache
.Init(true, base::Bind(&SimpleCallback
, &initialized
));
262 EXPECT_FALSE(initialized
);
264 // All extension files are hashed, but have different versions
265 const base::Time time
= base::Time::Now() - base::TimeDelta::FromDays(1);
266 base::FilePath file1
, file21
, file22
;
267 const std::string hash1
= CreateSignedExtensionFile(
268 cache_dir
, kTestExtensionId1
, "1.0", 100, time
, &file1
);
269 const std::string hash21
= CreateSignedExtensionFile(
270 cache_dir
, kTestExtensionId1
, "2.0", 101, time
, &file21
);
271 const std::string hash22
= CreateSignedExtensionFile(
272 cache_dir
, kTestExtensionId1
, "2.0", 123, time
, &file22
);
274 CreateFlagFile(cache_dir
);
277 ASSERT_TRUE(initialized
);
279 // Older version should be removed
280 EXPECT_FALSE(base::PathExists(file1
));
281 // Both newer hashed versions should stay
282 EXPECT_TRUE(base::PathExists(file21
));
283 EXPECT_TRUE(base::PathExists(file22
));
285 // We should be able to lookup only the latest version queries
286 EXPECT_FALSE(cache
.GetExtension(kTestExtensionId1
, hash1
, NULL
, NULL
));
287 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, hash21
, NULL
, NULL
));
288 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, hash22
, NULL
, NULL
));
291 TEST_F(LocalExtensionCacheTest
, Complex
) {
292 base::FilePath
cache_dir(CreateCacheDir(false));
294 LocalExtensionCache
cache(cache_dir
, 1000, base::TimeDelta::FromDays(30),
295 background_task_runner());
296 cache
.SetCacheStatusPollingDelayForTests(base::TimeDelta());
298 bool initialized
= false;
299 cache
.Init(true, base::Bind(&SimpleCallback
, &initialized
));
302 EXPECT_FALSE(initialized
);
304 // Like in KeepHashed test, but with two different versions
305 const base::Time time
= base::Time::Now() - base::TimeDelta::FromDays(1);
306 base::FilePath file1
, file11
, file12
, file2
, file21
, file22
;
307 CreateExtensionFile(cache_dir
, kTestExtensionId1
, "1.0", 100, time
, &file1
);
308 const std::string hash11
= CreateSignedExtensionFile(
309 cache_dir
, kTestExtensionId1
, "1.0", 101, time
, &file11
);
310 const std::string hash12
= CreateSignedExtensionFile(
311 cache_dir
, kTestExtensionId1
, "1.0", 102, time
, &file12
);
312 CreateExtensionFile(cache_dir
, kTestExtensionId1
, "2.0", 103, time
, &file2
);
313 const std::string hash21
= CreateSignedExtensionFile(
314 cache_dir
, kTestExtensionId1
, "2.0", 104, time
, &file21
);
315 const std::string hash22
= CreateSignedExtensionFile(
316 cache_dir
, kTestExtensionId1
, "2.0", 105, time
, &file22
);
318 CreateFlagFile(cache_dir
);
321 ASSERT_TRUE(initialized
);
323 // Older and unhashed versions should be removed
324 EXPECT_FALSE(base::PathExists(file1
));
325 EXPECT_FALSE(base::PathExists(file11
));
326 EXPECT_FALSE(base::PathExists(file12
));
327 EXPECT_FALSE(base::PathExists(file2
));
328 // Newest hashed versions should stay
329 EXPECT_TRUE(base::PathExists(file21
));
330 EXPECT_TRUE(base::PathExists(file22
));
332 // We should be able to lookup only the latest version queries, both with and
335 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, "", NULL
, &version
));
336 EXPECT_EQ(version
, "2.0");
337 EXPECT_FALSE(cache
.GetExtension(kTestExtensionId1
, hash11
, NULL
, NULL
));
338 EXPECT_FALSE(cache
.GetExtension(kTestExtensionId1
, hash12
, NULL
, NULL
));
339 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, hash21
, NULL
, NULL
));
340 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, hash22
, NULL
, NULL
));
343 static void OnPutExtension(scoped_ptr
<base::RunLoop
>* run_loop
,
344 const base::FilePath
& file_path
,
345 bool file_ownership_passed
) {
346 ASSERT_TRUE(*run_loop
);
350 static void PutExtensionAndWait(LocalExtensionCache
& cache
,
351 const std::string
& id
,
352 const std::string
& expected_hash
,
353 const base::FilePath
& path
,
354 const std::string
& version
) {
355 scoped_ptr
<base::RunLoop
> run_loop
;
356 run_loop
.reset(new base::RunLoop
);
357 cache
.PutExtension(id
, expected_hash
, path
, version
,
358 base::Bind(&OnPutExtension
, &run_loop
));
362 TEST_F(LocalExtensionCacheTest
, PutExtensionCases
) {
363 base::FilePath
cache_dir(CreateCacheDir(false));
365 LocalExtensionCache
cache(cache_dir
, 1000, base::TimeDelta::FromDays(30),
366 background_task_runner());
367 cache
.SetCacheStatusPollingDelayForTests(base::TimeDelta());
369 bool initialized
= false;
370 cache
.Init(true, base::Bind(&SimpleCallback
, &initialized
));
373 EXPECT_FALSE(initialized
);
375 // Initialize cache with several different files
376 const base::Time time
= base::Time::Now() - base::TimeDelta::FromDays(1);
377 base::FilePath file11
, file12
, file2
, file3
;
378 const std::string hash11
= CreateSignedExtensionFile(
379 cache_dir
, kTestExtensionId1
, "1.0", 101, time
, &file11
);
380 const std::string hash12
= CreateSignedExtensionFile(
381 cache_dir
, kTestExtensionId1
, "1.0", 102, time
, &file12
);
382 CreateSignedExtensionFile(cache_dir
, kTestExtensionId2
, "0.2", 200, time
,
384 CreateExtensionFile(cache_dir
, kTestExtensionId3
, "0.3", 300, time
, &file3
);
386 CreateFlagFile(cache_dir
);
389 ASSERT_TRUE(initialized
);
391 // Create and initialize installation source directory.
392 base::ScopedTempDir temp_dir
;
393 EXPECT_TRUE(temp_dir
.CreateUniqueTempDir());
394 const base::FilePath temp_path
= temp_dir
.path();
397 // Right now we have two files for the first extension
398 EXPECT_TRUE(base::PathExists(file11
));
399 EXPECT_TRUE(base::PathExists(file12
));
400 EXPECT_TRUE(base::PathExists(file2
));
401 EXPECT_TRUE(base::PathExists(file3
));
403 // 1. Cache contains an older version
404 base::FilePath temp1
;
405 CreateExtensionFile(temp_path
, kTestExtensionId1
, "3.0", 110, time
, &temp1
);
406 PutExtensionAndWait(cache
, kTestExtensionId1
, "", temp1
, "3.0");
408 const base::FilePath unhashed
=
409 GetExtensionFileName(cache_dir
, kTestExtensionId1
, "3.0", "");
410 EXPECT_TRUE(base::PathExists(unhashed
));
411 // Old files removed from cache (kept in the directory though)
412 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, hash11
, NULL
, &version
));
413 EXPECT_EQ(version
, "3.0");
414 EXPECT_TRUE(base::DeleteFile(temp1
, false));
416 // 2. Cache contains a newer version
417 base::FilePath temp2
;
418 CreateExtensionFile(temp_path
, kTestExtensionId1
, "2.0", 120, time
, &temp2
);
419 PutExtensionAndWait(cache
, kTestExtensionId1
, "", temp2
, "2.0");
421 EXPECT_FALSE(base::PathExists(
422 GetExtensionFileName(cache_dir
, kTestExtensionId1
, "2.0", "")));
424 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, "", NULL
, &version
));
425 EXPECT_EQ(version
, "3.0");
426 EXPECT_TRUE(base::DeleteFile(temp2
, false));
428 // 3. Cache contains the same version without hash, our file is unhashed
429 base::FilePath temp3
;
430 CreateExtensionFile(temp_path
, kTestExtensionId1
, "3.0", 130, time
, &temp3
);
431 PutExtensionAndWait(cache
, kTestExtensionId1
, "", temp3
, "3.0");
432 // New file skipped, old file kept
433 EXPECT_EQ(base::File(unhashed
, base::File::FLAG_READ
| base::File::FLAG_OPEN
)
436 EXPECT_TRUE(base::DeleteFile(temp3
, false));
438 // 4. Cache contains the same version without hash, our file is hashed
439 base::FilePath temp4
;
440 const std::string hash3
= CreateSignedExtensionFile(
441 temp_path
, kTestExtensionId1
, "3.0", 140, time
, &temp4
);
442 PutExtensionAndWait(cache
, kTestExtensionId1
, hash3
, temp4
, "3.0");
444 const base::FilePath hashed
=
445 GetExtensionFileName(cache_dir
, kTestExtensionId1
, "3.0", hash3
);
446 EXPECT_TRUE(base::PathExists(hashed
));
447 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, hash3
, NULL
, NULL
));
448 // Old file removed (queries return hashed version)
449 base::FilePath unhashed_path
;
450 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, "", &unhashed_path
, NULL
));
451 EXPECT_EQ(unhashed_path
, hashed
);
452 EXPECT_TRUE(base::DeleteFile(temp4
, false));
453 EXPECT_TRUE(base::DeleteFile(unhashed
, false));
455 // 5. Cache contains the same version with hash, our file is unhashed
456 base::FilePath temp5
;
457 CreateExtensionFile(temp_path
, kTestExtensionId1
, "3.0", 150, time
, &temp5
);
458 PutExtensionAndWait(cache
, kTestExtensionId1
, "", temp5
, "3.0");
460 EXPECT_FALSE(base::PathExists(unhashed
));
462 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, hash3
, NULL
, NULL
));
463 EXPECT_TRUE(base::DeleteFile(temp5
, false));
465 // 6. Cache contains the same version with hash, our file has the "same" hash
466 base::FilePath temp6
;
467 CreateExtensionFile(temp_path
, kTestExtensionId1
, "3.0", 160, time
, &temp6
);
468 PutExtensionAndWait(cache
, kTestExtensionId1
, hash3
, temp6
, "3.0");
469 // New file skipped, old file kept
470 EXPECT_EQ(base::File(hashed
, base::File::FLAG_READ
| base::File::FLAG_OPEN
)
473 EXPECT_TRUE(base::DeleteFile(temp6
, false));
475 // 7. Cache contains the same version with hash, our file is different
476 base::FilePath temp7
;
477 const std::string hash4
= CreateSignedExtensionFile(
478 temp_path
, kTestExtensionId1
, "3.0", 170, time
, &temp7
);
479 PutExtensionAndWait(cache
, kTestExtensionId1
, hash4
, temp7
, "3.0");
481 const base::FilePath hashed2
=
482 GetExtensionFileName(cache_dir
, kTestExtensionId1
, "3.0", hash4
);
483 EXPECT_TRUE(base::PathExists(hashed2
));
484 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, hash4
, NULL
, NULL
));
486 EXPECT_TRUE(cache
.GetExtension(kTestExtensionId1
, hash3
, NULL
, NULL
));
487 EXPECT_TRUE(base::DeleteFile(temp7
, false));
490 } // namespace extensions