Rename GetIconID to GetIconId
[chromium-blink-merge.git] / chrome / browser / extensions / updater / local_extension_cache.cc
blobe265b8ca631ac0e83d74251b49a9d0dcbaad3eb5
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"
7 #include "base/bind.h"
8 #include "base/files/file_enumerator.h"
9 #include "base/files/file_util.h"
10 #include "base/sequenced_task_runner.h"
11 #include "base/strings/string_util.h"
12 #include "base/sys_info.h"
13 #include "base/version.h"
14 #include "components/crx_file/id_util.h"
15 #include "content/public/browser/browser_thread.h"
17 namespace extensions {
18 namespace {
20 // File name extension for CRX files (not case sensitive).
21 const char kCRXFileExtension[] = ".crx";
23 // Delay between checks for flag file presence when waiting for the cache to
24 // become ready.
25 const int64_t kCacheStatusPollingDelayMs = 1000;
27 } // namespace
29 const char LocalExtensionCache::kCacheReadyFlagFileName[] = ".initialized";
31 LocalExtensionCache::LocalExtensionCache(
32 const base::FilePath& cache_dir,
33 uint64 max_cache_size,
34 const base::TimeDelta& max_cache_age,
35 const scoped_refptr<base::SequencedTaskRunner>& backend_task_runner)
36 : cache_dir_(cache_dir),
37 max_cache_size_(max_cache_size),
38 min_cache_age_(base::Time::Now() - max_cache_age),
39 backend_task_runner_(backend_task_runner),
40 state_(kUninitialized),
41 cache_status_polling_delay_(
42 base::TimeDelta::FromMilliseconds(kCacheStatusPollingDelayMs)),
43 weak_ptr_factory_(this) {
46 LocalExtensionCache::~LocalExtensionCache() {
47 if (state_ == kReady)
48 CleanUp();
51 void LocalExtensionCache::Init(bool wait_for_cache_initialization,
52 const base::Closure& callback) {
53 DCHECK_EQ(state_, kUninitialized);
55 state_ = kWaitInitialization;
56 if (wait_for_cache_initialization)
57 CheckCacheStatus(callback);
58 else
59 CheckCacheContents(callback);
62 void LocalExtensionCache::Shutdown(const base::Closure& callback) {
63 DCHECK_NE(state_, kShutdown);
64 if (state_ == kReady)
65 CleanUp();
66 state_ = kShutdown;
67 backend_task_runner_->PostTaskAndReply(FROM_HERE,
68 base::Bind(&base::DoNothing), callback);
71 // static
72 LocalExtensionCache::CacheMap::iterator LocalExtensionCache::FindExtension(
73 CacheMap& cache,
74 const std::string& id,
75 const std::string& expected_hash) {
76 CacheHit hit = cache.equal_range(id);
77 CacheMap::iterator empty_hash = cache.end();
78 std::string hash = base::ToLowerASCII(expected_hash);
79 for (CacheMap::iterator it = hit.first; it != hit.second; ++it) {
80 if (expected_hash.empty() || it->second.expected_hash == hash) {
81 return it;
83 if (it->second.expected_hash.empty()) {
84 empty_hash = it;
87 return empty_hash;
90 bool LocalExtensionCache::GetExtension(const std::string& id,
91 const std::string& expected_hash,
92 base::FilePath* file_path,
93 std::string* version) {
94 if (state_ != kReady)
95 return false;
97 CacheMap::iterator it = FindExtension(cached_extensions_, id, expected_hash);
98 if (it == cached_extensions_.end())
99 return false;
101 if (file_path) {
102 *file_path = it->second.file_path;
104 // If caller is not interested in file_path, extension is not used.
105 base::Time now = base::Time::Now();
106 backend_task_runner_->PostTask(FROM_HERE,
107 base::Bind(&LocalExtensionCache::BackendMarkFileUsed,
108 it->second.file_path, now));
109 it->second.last_used = now;
112 if (version)
113 *version = it->second.version;
115 return true;
118 bool LocalExtensionCache::ShouldRetryDownload(
119 const std::string& id,
120 const std::string& expected_hash) {
121 if (state_ != kReady)
122 return false;
124 CacheMap::iterator it = FindExtension(cached_extensions_, id, expected_hash);
125 if (it == cached_extensions_.end())
126 return false;
128 return (!expected_hash.empty() && it->second.expected_hash.empty());
131 // static
132 bool LocalExtensionCache::NewerOrSame(const CacheMap::iterator& entry,
133 const std::string& version,
134 const std::string& expected_hash,
135 int* compare) {
136 Version new_version(version);
137 Version prev_version(entry->second.version);
138 int cmp = new_version.CompareTo(prev_version);
140 if (compare)
141 *compare = cmp;
143 // Cache entry is newer if its version is greater or same, and in the latter
144 // case we will prefer the existing one if we are trying to add an
145 // unhashed file, or we already have a hashed file in cache.
146 return (cmp < 0 || (cmp == 0 && (expected_hash.empty() ||
147 !entry->second.expected_hash.empty())));
150 void LocalExtensionCache::PutExtension(const std::string& id,
151 const std::string& expected_hash,
152 const base::FilePath& file_path,
153 const std::string& version,
154 const PutExtensionCallback& callback) {
155 if (state_ != kReady) {
156 callback.Run(file_path, true);
157 return;
160 Version version_validator(version);
161 if (!version_validator.IsValid()) {
162 LOG(ERROR) << "Extension " << id << " has bad version " << version;
163 callback.Run(file_path, true);
164 return;
167 CacheMap::iterator it = FindExtension(cached_extensions_, id, expected_hash);
168 if (it != cached_extensions_.end() &&
169 NewerOrSame(it, version, expected_hash, NULL)) {
170 LOG(WARNING) << "Cache contains newer or the same version "
171 << it->second.version << " for extension " << id << " version "
172 << version;
173 callback.Run(file_path, true);
174 return;
177 backend_task_runner_->PostTask(
178 FROM_HERE, base::Bind(&LocalExtensionCache::BackendInstallCacheEntry,
179 weak_ptr_factory_.GetWeakPtr(), cache_dir_, id,
180 expected_hash, file_path, version, callback));
183 bool LocalExtensionCache::RemoveExtensionAt(const CacheMap::iterator& it,
184 bool match_hash) {
185 if (state_ != kReady || it == cached_extensions_.end())
186 return false;
187 std::string hash = match_hash ? it->second.expected_hash : std::string();
188 backend_task_runner_->PostTask(
189 FROM_HERE, base::Bind(&LocalExtensionCache::BackendRemoveCacheEntry,
190 cache_dir_, it->first, hash));
191 cached_extensions_.erase(it);
192 return true;
195 bool LocalExtensionCache::RemoveExtension(const std::string& id,
196 const std::string& expected_hash) {
197 if (state_ != kReady)
198 return false;
200 CacheMap::iterator it = FindExtension(cached_extensions_, id, expected_hash);
201 if (it == cached_extensions_.end())
202 return false;
204 while (it != cached_extensions_.end()) {
205 RemoveExtensionAt(it, !expected_hash.empty());
207 // For empty |expected_hash| this will iteratively return any cached file.
208 // For any specific |expected_hash| this will only be able to find the
209 // matching entry once.
210 it = FindExtension(cached_extensions_, id, expected_hash);
213 return true;
216 bool LocalExtensionCache::GetStatistics(uint64* cache_size,
217 size_t* extensions_count) {
218 if (state_ != kReady)
219 return false;
221 *cache_size = 0;
222 for (CacheMap::iterator it = cached_extensions_.begin();
223 it != cached_extensions_.end(); ++it) {
224 *cache_size += it->second.size;
226 *extensions_count = cached_extensions_.size();
228 return true;
231 void LocalExtensionCache::SetCacheStatusPollingDelayForTests(
232 const base::TimeDelta& delay) {
233 cache_status_polling_delay_ = delay;
236 void LocalExtensionCache::CheckCacheStatus(const base::Closure& callback) {
237 if (state_ == kShutdown) {
238 callback.Run();
239 return;
242 backend_task_runner_->PostTask(
243 FROM_HERE,
244 base::Bind(&LocalExtensionCache::BackendCheckCacheStatus,
245 weak_ptr_factory_.GetWeakPtr(),
246 cache_dir_,
247 callback));
250 // static
251 void LocalExtensionCache::BackendCheckCacheStatus(
252 base::WeakPtr<LocalExtensionCache> local_cache,
253 const base::FilePath& cache_dir,
254 const base::Closure& callback) {
255 const bool exists =
256 base::PathExists(cache_dir.AppendASCII(kCacheReadyFlagFileName));
258 static bool first_check = true;
259 if (first_check && !exists && !base::SysInfo::IsRunningOnChromeOS()) {
260 LOG(WARNING) << "Extensions will not be installed from update URLs until "
261 << cache_dir.AppendASCII(kCacheReadyFlagFileName).value()
262 << " exists.";
264 first_check = false;
266 content::BrowserThread::PostTask(
267 content::BrowserThread::UI,
268 FROM_HERE,
269 base::Bind(&LocalExtensionCache::OnCacheStatusChecked,
270 local_cache,
271 exists,
272 callback));
275 void LocalExtensionCache::OnCacheStatusChecked(bool ready,
276 const base::Closure& callback) {
277 if (state_ == kShutdown) {
278 callback.Run();
279 return;
282 if (ready) {
283 CheckCacheContents(callback);
284 } else {
285 content::BrowserThread::PostDelayedTask(
286 content::BrowserThread::UI,
287 FROM_HERE,
288 base::Bind(&LocalExtensionCache::CheckCacheStatus,
289 weak_ptr_factory_.GetWeakPtr(),
290 callback),
291 cache_status_polling_delay_);
295 void LocalExtensionCache::CheckCacheContents(const base::Closure& callback) {
296 DCHECK_EQ(state_, kWaitInitialization);
297 backend_task_runner_->PostTask(
298 FROM_HERE,
299 base::Bind(&LocalExtensionCache::BackendCheckCacheContents,
300 weak_ptr_factory_.GetWeakPtr(),
301 cache_dir_,
302 callback));
305 // static
306 void LocalExtensionCache::BackendCheckCacheContents(
307 base::WeakPtr<LocalExtensionCache> local_cache,
308 const base::FilePath& cache_dir,
309 const base::Closure& callback) {
310 scoped_ptr<CacheMap> cache_content(new CacheMap);
311 BackendCheckCacheContentsInternal(cache_dir, cache_content.get());
312 content::BrowserThread::PostTask(
313 content::BrowserThread::UI,
314 FROM_HERE,
315 base::Bind(&LocalExtensionCache::OnCacheContentsChecked,
316 local_cache,
317 base::Passed(&cache_content),
318 callback));
321 // static
322 LocalExtensionCache::CacheMap::iterator LocalExtensionCache::InsertCacheEntry(
323 CacheMap& cache,
324 const std::string& id,
325 const CacheItemInfo& info,
326 const bool delete_files) {
327 bool keep = true;
328 std::string any_hash;
329 // FindExtension with empty hash will always return the first one
330 CacheMap::iterator it = FindExtension(cache, id, any_hash);
331 if (it != cache.end()) {
332 // |cache_content| already has some version for this ID. Remove older ones.
333 // If we loook at the first cache entry, it may be:
334 // 1. an older version (in which case we should remove all its instances)
335 // 2. a newer version (in which case we should skip current file)
336 // 3. the same version without hash (skip if our hash is empty,
337 // 4. remove if our hash in not empty),
338 // 5. the same version with hash (skip if our hash is empty,
339 // 6. skip if there is already an entry with the same hash,
340 // otherwise add a new entry).
342 int cmp = 0;
343 if (!NewerOrSame(it, info.version, info.expected_hash, &cmp)) {
344 // Case #1 or #4, remove all instances from cache.
345 while ((it != cache.end()) && (it->first == id)) {
346 if (delete_files) {
347 base::DeleteFile(base::FilePath(it->second.file_path),
348 true /* recursive */);
349 VLOG(1) << "Remove older version " << it->second.version
350 << " for extension id " << id;
352 it = cache.erase(it);
354 } else if ((cmp < 0) || (cmp == 0 && info.expected_hash.empty())) {
355 // Case #2, #3 or #5
356 keep = false;
357 } else if (cmp == 0) {
358 // Same version, both hashes are not empty, try to find the same hash.
359 while (keep && (it != cache.end()) && (it->first == id)) {
360 if (it->second.expected_hash == info.expected_hash) {
361 // Case #6
362 keep = false;
364 ++it;
369 if (keep) {
370 it = cache.insert(std::make_pair(id, info));
371 } else {
372 if (delete_files) {
373 base::DeleteFile(info.file_path, true /* recursive */);
374 VLOG(1) << "Remove older version " << info.version << " for extension id "
375 << id;
377 it = cache.end();
380 return it;
383 // static
384 void LocalExtensionCache::BackendCheckCacheContentsInternal(
385 const base::FilePath& cache_dir,
386 CacheMap* cache_content) {
387 // Start by verifying that the cache_dir exists.
388 if (!base::DirectoryExists(cache_dir)) {
389 // Create it now.
390 if (!base::CreateDirectory(cache_dir)) {
391 LOG(ERROR) << "Failed to create cache directory at "
392 << cache_dir.value();
395 // Nothing else to do. Cache is empty.
396 return;
399 // Enumerate all the files in the cache |cache_dir|, including directories
400 // and symlinks. Each unrecognized file will be erased.
401 int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES;
402 base::FileEnumerator enumerator(cache_dir, false /* recursive */, types);
403 for (base::FilePath path = enumerator.Next();
404 !path.empty(); path = enumerator.Next()) {
405 base::FileEnumerator::FileInfo info = enumerator.GetInfo();
406 std::string basename = path.BaseName().value();
408 if (info.IsDirectory() || base::IsLink(info.GetName())) {
409 LOG(ERROR) << "Erasing bad file in cache directory: " << basename;
410 base::DeleteFile(path, true /* recursive */);
411 continue;
414 // Skip flag file that indicates that cache is ready.
415 if (basename == kCacheReadyFlagFileName)
416 continue;
418 // crx files in the cache are named
419 // <extension-id>-<version>[-<expected_hash>].crx.
420 std::string id;
421 std::string version;
422 std::string expected_hash;
423 if (base::EndsWith(basename, kCRXFileExtension,
424 base::CompareCase::INSENSITIVE_ASCII)) {
425 size_t n = basename.find('-');
426 if (n != std::string::npos && n + 1 < basename.size() - 4) {
427 id = basename.substr(0, n);
428 // Size of |version| = total size - "<id>" - "-" - ".crx"
429 version = basename.substr(n + 1, basename.size() - 5 - id.size());
431 n = version.find('-');
432 if (n != std::string::npos && n + 1 < version.size()) {
433 expected_hash = version.substr(n + 1, version.size() - n - 1);
434 version.resize(n);
439 // Enforce a lower-case id.
440 id = base::ToLowerASCII(id);
441 if (!crx_file::id_util::IdIsValid(id)) {
442 LOG(ERROR) << "Bad extension id in cache: " << id;
443 id.clear();
446 if (!Version(version).IsValid()) {
447 LOG(ERROR) << "Bad extension version in cache: " << version;
448 version.clear();
451 if (id.empty() || version.empty()) {
452 LOG(ERROR) << "Invalid file in cache, erasing: " << basename;
453 base::DeleteFile(path, true /* recursive */);
454 continue;
457 VLOG(1) << "Found cached version " << version
458 << " for extension id " << id;
460 InsertCacheEntry(
461 *cache_content, id,
462 CacheItemInfo(version, expected_hash, info.GetLastModifiedTime(),
463 info.GetSize(), path),
464 true);
468 void LocalExtensionCache::OnCacheContentsChecked(
469 scoped_ptr<CacheMap> cache_content,
470 const base::Closure& callback) {
471 cache_content->swap(cached_extensions_);
472 state_ = kReady;
473 callback.Run();
476 // static
477 void LocalExtensionCache::BackendMarkFileUsed(const base::FilePath& file_path,
478 const base::Time& time) {
479 base::TouchFile(file_path, time, time);
482 // static
483 std::string LocalExtensionCache::ExtensionFileName(
484 const std::string& id,
485 const std::string& version,
486 const std::string& expected_hash) {
487 std::string filename = id + "-" + version;
488 if (!expected_hash.empty())
489 filename += "-" + base::ToLowerASCII(expected_hash);
490 filename += kCRXFileExtension;
491 return filename;
494 // static
495 void LocalExtensionCache::BackendInstallCacheEntry(
496 base::WeakPtr<LocalExtensionCache> local_cache,
497 const base::FilePath& cache_dir,
498 const std::string& id,
499 const std::string& expected_hash,
500 const base::FilePath& file_path,
501 const std::string& version,
502 const PutExtensionCallback& callback) {
503 std::string basename = ExtensionFileName(id, version, expected_hash);
504 base::FilePath cached_crx_path = cache_dir.AppendASCII(basename);
506 bool was_error = false;
507 if (base::PathExists(cached_crx_path)) {
508 LOG(ERROR) << "File already exists " << file_path.value();
509 cached_crx_path = file_path;
510 was_error = true;
513 base::File::Info info;
514 if (!was_error) {
515 if (!base::Move(file_path, cached_crx_path)) {
516 LOG(ERROR) << "Failed to copy from " << file_path.value()
517 << " to " << cached_crx_path.value();
518 cached_crx_path = file_path;
519 was_error = true;
520 } else {
521 was_error = !base::GetFileInfo(cached_crx_path, &info);
522 VLOG(1) << "Cache entry installed for extension id " << id
523 << " version " << version;
527 content::BrowserThread::PostTask(
528 content::BrowserThread::UI, FROM_HERE,
529 base::Bind(&LocalExtensionCache::OnCacheEntryInstalled, local_cache, id,
530 CacheItemInfo(version, expected_hash, info.last_modified,
531 info.size, cached_crx_path),
532 was_error, callback));
535 void LocalExtensionCache::OnCacheEntryInstalled(
536 const std::string& id,
537 const CacheItemInfo& info,
538 bool was_error,
539 const PutExtensionCallback& callback) {
540 if (state_ == kShutdown || was_error) {
541 // If |was_error| is true, it means that the |info.file_path| refers to the
542 // original downloaded file, otherwise it refers to a file in cache, which
543 // should not be deleted by CrxInstaller.
544 callback.Run(info.file_path, was_error);
545 return;
548 CacheMap::iterator it = InsertCacheEntry(cached_extensions_, id, info, false);
549 if (it == cached_extensions_.end()) {
550 DCHECK(0) << "Cache contains newer or the same version";
551 callback.Run(info.file_path, true);
552 return;
555 // Time from file system can have lower precision so use precise "now".
556 it->second.last_used = base::Time::Now();
558 callback.Run(info.file_path, false);
561 // static
562 void LocalExtensionCache::BackendRemoveCacheEntry(
563 const base::FilePath& cache_dir,
564 const std::string& id,
565 const std::string& expected_hash) {
566 std::string file_pattern = ExtensionFileName(id, "*", expected_hash);
567 base::FileEnumerator enumerator(cache_dir,
568 false /* not recursive */,
569 base::FileEnumerator::FILES,
570 file_pattern);
571 for (base::FilePath path = enumerator.Next(); !path.empty();
572 path = enumerator.Next()) {
573 base::DeleteFile(path, false);
574 VLOG(1) << "Removed cached file " << path.value();
578 // static
579 bool LocalExtensionCache::CompareCacheItemsAge(const CacheMap::iterator& lhs,
580 const CacheMap::iterator& rhs) {
581 return lhs->second.last_used < rhs->second.last_used;
584 void LocalExtensionCache::CleanUp() {
585 DCHECK_EQ(state_, kReady);
587 std::vector<CacheMap::iterator> items;
588 items.reserve(cached_extensions_.size());
589 uint64_t total_size = 0;
590 for (CacheMap::iterator it = cached_extensions_.begin();
591 it != cached_extensions_.end(); ++it) {
592 items.push_back(it);
593 total_size += it->second.size;
595 std::sort(items.begin(), items.end(), CompareCacheItemsAge);
597 for (std::vector<CacheMap::iterator>::iterator it = items.begin();
598 it != items.end(); ++it) {
599 if ((*it)->second.last_used < min_cache_age_ ||
600 (max_cache_size_ && total_size > max_cache_size_)) {
601 total_size -= (*it)->second.size;
602 VLOG(1) << "Clean up cached extension id " << (*it)->first;
603 RemoveExtensionAt(*it, true);
608 LocalExtensionCache::CacheItemInfo::CacheItemInfo(
609 const std::string& version,
610 const std::string& expected_hash,
611 const base::Time& last_used,
612 uint64 size,
613 const base::FilePath& file_path)
614 : version(version),
615 expected_hash(base::ToLowerASCII(expected_hash)),
616 last_used(last_used),
617 size(size),
618 file_path(file_path) {
621 LocalExtensionCache::CacheItemInfo::~CacheItemInfo() {
624 } // namespace extensions