1 // Copyright (c) 2013 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/chromeos/extensions/external_cache.h"
8 #include "base/bind_helpers.h"
9 #include "base/callback.h"
10 #include "base/file_util.h"
11 #include "base/files/file_enumerator.h"
12 #include "base/location.h"
13 #include "base/logging.h"
14 #include "base/strings/string_util.h"
15 #include "base/values.h"
16 #include "base/version.h"
17 #include "chrome/browser/chrome_notification_types.h"
18 #include "chrome/browser/extensions/crx_installer.h"
19 #include "chrome/browser/extensions/external_provider_impl.h"
20 #include "chrome/browser/extensions/updater/extension_downloader.h"
21 #include "chrome/common/extensions/extension.h"
22 #include "chrome/common/extensions/extension_constants.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/notification_details.h"
25 #include "content/public/browser/notification_service.h"
26 #include "content/public/browser/notification_source.h"
32 // File name extension for CRX files (not case sensitive).
33 const char kCRXFileExtension[] = ".crx";
35 // Delay between checks for flag file presence when waiting for the cache to
37 const int64_t kCacheStatusPollingDelayMs = 1000;
41 const char ExternalCache::kCacheReadyFlagFileName[] = ".initialized";
43 ExternalCache::ExternalCache(const base::FilePath& cache_dir,
44 net::URLRequestContextGetter* request_context,
45 const scoped_refptr<base::SequencedTaskRunner>&
48 bool always_check_updates,
49 bool wait_for_cache_initialization)
50 : cache_dir_(cache_dir),
51 request_context_(request_context),
54 always_check_updates_(always_check_updates),
55 wait_for_cache_initialization_(wait_for_cache_initialization),
56 cached_extensions_(new base::DictionaryValue()),
57 backend_task_runner_(backend_task_runner),
58 weak_ptr_factory_(this) {
59 notification_registrar_.Add(
61 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
62 content::NotificationService::AllBrowserContextsAndSources());
65 ExternalCache::~ExternalCache() {
68 void ExternalCache::Shutdown(const base::Closure& callback) {
71 backend_task_runner_->PostTask(FROM_HERE,
72 base::Bind(&ExternalCache::BackendShudown,
76 void ExternalCache::UpdateExtensionsList(
77 scoped_ptr<base::DictionaryValue> prefs) {
81 extensions_ = prefs.Pass();
82 if (extensions_->empty()) {
83 // Don't check cache and clear it if there are no extensions in the list.
84 // It is important case because login to supervised user shouldn't clear
85 // cache for normal users.
86 // TODO(dpolukhin): introduce reference counting to preserve cache elements
87 // when they are not needed for current user but needed for other users.
88 cached_extensions_->Clear();
89 UpdateExtensionLoader();
95 void ExternalCache::OnDamagedFileDetected(const base::FilePath& path) {
99 for (base::DictionaryValue::Iterator it(*cached_extensions_.get());
100 !it.IsAtEnd(); it.Advance()) {
101 const base::DictionaryValue* entry = NULL;
102 if (!it.value().GetAsDictionary(&entry)) {
103 NOTREACHED() << "ExternalCache found bad entry with type "
104 << it.value().GetType();
108 std::string external_crx;
109 if (entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
111 external_crx == path.value()) {
113 LOG(ERROR) << "ExternalCache extension at " << path.value()
114 << " failed to install, deleting it.";
115 cached_extensions_->Remove(it.key(), NULL);
116 UpdateExtensionLoader();
118 // The file will be downloaded again on the next restart.
119 if (cache_dir_.IsParent(path)) {
120 backend_task_runner_->PostTask(
122 base::Bind(base::IgnoreResult(base::DeleteFile),
127 // Don't try to DownloadMissingExtensions() from here,
128 // since it can cause a fail/retry loop.
132 LOG(ERROR) << "ExternalCache cannot find external_crx " << path.value();
135 void ExternalCache::Observe(int type,
136 const content::NotificationSource& source,
137 const content::NotificationDetails& details) {
142 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
143 extensions::CrxInstaller* installer =
144 content::Source<extensions::CrxInstaller>(source).ptr();
145 OnDamagedFileDetected(installer->source_file());
154 void ExternalCache::OnExtensionDownloadFailed(
155 const std::string& id,
156 extensions::ExtensionDownloaderDelegate::Error error,
157 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
158 const std::set<int>& request_ids) {
162 if (error == NO_UPDATE_AVAILABLE) {
163 if (!cached_extensions_->HasKey(id)) {
164 LOG(ERROR) << "ExternalCache extension " << id
165 << " not found on update server";
168 LOG(ERROR) << "ExternalCache failed to download extension " << id
169 << ", error " << error;
173 void ExternalCache::OnExtensionDownloadFinished(
174 const std::string& id,
175 const base::FilePath& path,
176 const GURL& download_url,
177 const std::string& version,
178 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
179 const std::set<int>& request_ids) {
183 backend_task_runner_->PostTask(
185 base::Bind(&ExternalCache::BackendInstallCacheEntry,
186 weak_ptr_factory_.GetWeakPtr(),
193 bool ExternalCache::IsExtensionPending(const std::string& id) {
194 // Pending means that there is no installed version yet.
195 return extensions_->HasKey(id) && !cached_extensions_->HasKey(id);
198 bool ExternalCache::GetExtensionExistingVersion(const std::string& id,
199 std::string* version) {
200 DictionaryValue* extension_dictionary = NULL;
201 if (cached_extensions_->GetDictionary(id, &extension_dictionary)) {
202 if (extension_dictionary->GetString(
203 extensions::ExternalProviderImpl::kExternalVersion, version)) {
206 *version = delegate_->GetInstalledExtensionVersion(id);
207 return !version->empty();
212 void ExternalCache::UpdateExtensionLoader() {
216 VLOG(1) << "Notify ExternalCache delegate about cache update";
218 delegate_->OnExtensionListsUpdated(cached_extensions_.get());
221 void ExternalCache::CheckCache() {
222 if (wait_for_cache_initialization_) {
223 backend_task_runner_->PostTask(
225 base::Bind(&ExternalCache::BackendCheckCacheStatus,
226 weak_ptr_factory_.GetWeakPtr(),
229 CheckCacheContents();
234 void ExternalCache::BackendCheckCacheStatus(
235 base::WeakPtr<ExternalCache> external_cache,
236 const base::FilePath& cache_dir) {
237 content::BrowserThread::PostTask(
238 content::BrowserThread::UI,
240 base::Bind(&ExternalCache::OnCacheStatusChecked,
242 base::PathExists(cache_dir.AppendASCII(kCacheReadyFlagFileName))));
245 void ExternalCache::OnCacheStatusChecked(bool ready) {
250 CheckCacheContents();
252 content::BrowserThread::PostDelayedTask(
253 content::BrowserThread::UI,
255 base::Bind(&ExternalCache::CheckCache,
256 weak_ptr_factory_.GetWeakPtr()),
257 base::TimeDelta::FromMilliseconds(kCacheStatusPollingDelayMs));
261 void ExternalCache::CheckCacheContents() {
265 backend_task_runner_->PostTask(
267 base::Bind(&ExternalCache::BackendCheckCacheContents,
268 weak_ptr_factory_.GetWeakPtr(),
270 base::Passed(make_scoped_ptr(extensions_->DeepCopy()))));
274 void ExternalCache::BackendCheckCacheContents(
275 base::WeakPtr<ExternalCache> external_cache,
276 const base::FilePath& cache_dir,
277 scoped_ptr<base::DictionaryValue> prefs) {
278 BackendCheckCacheContentsInternal(cache_dir, prefs.get());
279 content::BrowserThread::PostTask(content::BrowserThread::UI,
281 base::Bind(&ExternalCache::OnCacheUpdated,
283 base::Passed(&prefs)));
287 void ExternalCache::BackendCheckCacheContentsInternal(
288 const base::FilePath& cache_dir,
289 base::DictionaryValue* prefs) {
290 // Start by verifying that the cache_dir exists.
291 if (!base::DirectoryExists(cache_dir)) {
293 if (!file_util::CreateDirectory(cache_dir)) {
294 LOG(ERROR) << "Failed to create ExternalCache directory at "
295 << cache_dir.value();
298 // Nothing else to do. Cache won't be used.
302 // Enumerate all the files in the cache |cache_dir|, including directories
303 // and symlinks. Each unrecognized file will be erased.
304 int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES |
305 base::FileEnumerator::SHOW_SYM_LINKS;
306 base::FileEnumerator enumerator(cache_dir, false /* recursive */, types);
307 for (base::FilePath path = enumerator.Next();
308 !path.empty(); path = enumerator.Next()) {
309 base::FileEnumerator::FileInfo info = enumerator.GetInfo();
310 std::string basename = path.BaseName().value();
312 if (info.IsDirectory() || file_util::IsLink(info.GetName())) {
313 LOG(ERROR) << "Erasing bad file in ExternalCache directory: " << basename;
314 base::DeleteFile(path, true /* recursive */);
318 // Skip flag file that indicates that cache is ready.
319 if (basename == kCacheReadyFlagFileName)
322 // crx files in the cache are named <extension-id>-<version>.crx.
325 if (EndsWith(basename, kCRXFileExtension, false /* case-sensitive */)) {
326 size_t n = basename.find('-');
327 if (n != std::string::npos && n + 1 < basename.size() - 4) {
328 id = basename.substr(0, n);
329 // Size of |version| = total size - "<id>" - "-" - ".crx"
330 version = basename.substr(n + 1, basename.size() - 5 - id.size());
334 base::DictionaryValue* entry = NULL;
335 if (!extensions::Extension::IdIsValid(id)) {
336 LOG(ERROR) << "Bad extension id in ExternalCache: " << id;
338 } else if (!prefs->GetDictionary(id, &entry)) {
339 LOG(WARNING) << basename << " is in the cache but is not configured by "
340 << "the ExternalCache source, and will be erased.";
344 if (!Version(version).IsValid()) {
345 LOG(ERROR) << "Bad extension version in ExternalCache: " << version;
349 if (id.empty() || version.empty()) {
350 LOG(ERROR) << "Invalid file in ExternalCache, erasing: " << basename;
351 base::DeleteFile(path, true /* recursive */);
355 // Enforce a lower-case id.
356 id = StringToLowerASCII(id);
358 std::string update_url;
359 std::string prev_version_string;
360 std::string prev_crx;
361 if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
363 VLOG(1) << "ExternalCache found cached version " << version
364 << " for extension id: " << id;
365 entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
366 entry->SetString(extensions::ExternalProviderImpl::kExternalVersion,
368 entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
370 if (extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
371 entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore,
375 entry->GetString(extensions::ExternalProviderImpl::kExternalVersion,
376 &prev_version_string) &&
377 entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
379 Version prev_version(prev_version_string);
380 Version curr_version(version);
381 DCHECK(prev_version.IsValid());
382 DCHECK(curr_version.IsValid());
383 if (prev_version.CompareTo(curr_version) <= 0) {
384 VLOG(1) << "ExternalCache found old cached version "
385 << prev_version_string << " path: " << prev_crx;
386 base::FilePath prev_crx_file(prev_crx);
387 if (cache_dir.IsParent(prev_crx_file)) {
388 // Only delete old cached files under cache_dir_ folder.
389 base::DeleteFile(base::FilePath(prev_crx), true /* recursive */);
391 entry->SetString(extensions::ExternalProviderImpl::kExternalVersion,
393 entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
396 VLOG(1) << "ExternalCache found old cached version "
397 << version << " path: " << path.value();
398 base::DeleteFile(path, true /* recursive */);
401 NOTREACHED() << "ExternalCache found bad entry for extension id: " << id
402 << " file path: " << path.value();
407 void ExternalCache::OnCacheUpdated(scoped_ptr<base::DictionaryValue> prefs) {
408 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
412 // If request_context_ is missing we can't download anything.
413 if (!downloader_ && request_context_) {
415 new extensions::ExtensionDownloader(this, request_context_));
418 cached_extensions_->Clear();
419 for (base::DictionaryValue::Iterator it(*extensions_.get());
420 !it.IsAtEnd(); it.Advance()) {
421 const base::DictionaryValue* entry = NULL;
422 if (!it.value().GetAsDictionary(&entry)) {
423 LOG(ERROR) << "ExternalCache found bad entry with type "
424 << it.value().GetType();
428 bool keep_if_present =
429 entry->HasKey(extensions::ExternalProviderImpl::kKeepIfPresent);
430 // Check for updates for all extensions configured except for extensions
431 // marked as keep_if_present.
432 if (downloader_ && !keep_if_present) {
434 std::string external_update_url;
435 if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
436 &external_update_url)) {
437 update_url = GURL(external_update_url);
438 } else if (always_check_updates_) {
439 update_url = extension_urls::GetWebstoreUpdateUrl();
441 if (update_url.is_valid())
442 downloader_->AddPendingExtension(it.key(), update_url, 0);
445 base::DictionaryValue* cached_entry = NULL;
446 if (prefs->GetDictionary(it.key(), &cached_entry)) {
447 bool has_external_crx = cached_entry->HasKey(
448 extensions::ExternalProviderImpl::kExternalCrx);
449 bool is_already_installed =
450 !delegate_->GetInstalledExtensionVersion(it.key()).empty();
451 if (!downloader_ || keep_if_present || has_external_crx ||
452 is_already_installed) {
453 scoped_ptr<base::Value> value;
454 prefs->Remove(it.key(), &value);
455 cached_extensions_->Set(it.key(), value.release());
460 downloader_->StartAllPending();
462 VLOG(1) << "Updated ExternalCache, there are "
463 << cached_extensions_->size() << " extensions cached";
465 UpdateExtensionLoader();
469 void ExternalCache::BackendInstallCacheEntry(
470 base::WeakPtr<ExternalCache> external_cache,
471 const base::FilePath& cache_dir,
472 const std::string& id,
473 const base::FilePath& path,
474 const std::string& version) {
475 Version version_validator(version);
476 if (!version_validator.IsValid()) {
477 LOG(ERROR) << "ExternalCache downloaded extension " << id << " but got bad "
478 << "version: " << version;
479 base::DeleteFile(path, true /* recursive */);
483 std::string basename = id + "-" + version + kCRXFileExtension;
484 base::FilePath cached_crx_path = cache_dir.Append(basename);
486 if (base::PathExists(cached_crx_path)) {
487 LOG(WARNING) << "AppPack downloaded a crx whose filename will overwrite "
488 << "an existing cached crx.";
489 base::DeleteFile(cached_crx_path, true /* recursive */);
492 if (!base::DirectoryExists(cache_dir)) {
493 LOG(ERROR) << "AppPack cache directory does not exist, creating now: "
494 << cache_dir.value();
495 if (!file_util::CreateDirectory(cache_dir)) {
496 LOG(ERROR) << "Failed to create the AppPack cache dir!";
497 base::DeleteFile(path, true /* recursive */);
502 if (!base::Move(path, cached_crx_path)) {
503 LOG(ERROR) << "Failed to move AppPack crx from " << path.value()
504 << " to " << cached_crx_path.value();
505 base::DeleteFile(path, true /* recursive */);
509 content::BrowserThread::PostTask(
510 content::BrowserThread::UI,
512 base::Bind(&ExternalCache::OnCacheEntryInstalled,
519 void ExternalCache::OnCacheEntryInstalled(const std::string& id,
520 const base::FilePath& path,
521 const std::string& version) {
525 VLOG(1) << "AppPack installed a new extension in the cache: " << path.value();
527 base::DictionaryValue* entry = NULL;
528 if (!extensions_->GetDictionary(id, &entry)) {
529 LOG(ERROR) << "ExternalCache cannot find entry for extension " << id;
533 // Copy entry to don't modify it inside extensions_.
534 entry = entry->DeepCopy();
536 std::string update_url;
537 if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
539 extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
540 entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true);
542 entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
543 entry->SetString(extensions::ExternalProviderImpl::kExternalVersion, version);
544 entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
547 cached_extensions_->Set(id, entry);
548 UpdateExtensionLoader();
552 void ExternalCache::BackendShudown(const base::Closure& callback) {
553 content::BrowserThread::PostTask(content::BrowserThread::UI,
558 std::string ExternalCache::Delegate::GetInstalledExtensionVersion(
559 const std::string& id) {
560 return std::string();
563 } // namespace chromeos