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. 4 5 #include "chrome/browser/chromeos/extensions/external_cache.h" 6 7 #include "base/bind.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_constants.h" 22 #include "content/public/browser/browser_thread.h" 23 #include "content/public/browser/notification_details.h" 24 #include "content/public/browser/notification_service.h" 25 #include "content/public/browser/notification_source.h" 26 #include "extensions/common/extension.h" 27 28 namespace chromeos { 29 30 namespace { 31 32 // File name extension for CRX files (not case sensitive). 33 const char kCRXFileExtension[] = ".crx"; 34 35 // Delay between checks for flag file presence when waiting for the cache to 36 // become ready. 37 const int64_t kCacheStatusPollingDelayMs = 1000; 38 39 } // namespace 40 41 const char ExternalCache::kCacheReadyFlagFileName[] = ".initialized"; 42 43 ExternalCache::ExternalCache(const base::FilePath& cache_dir, 44 net::URLRequestContextGetter* request_context, 45 const scoped_refptr<base::SequencedTaskRunner>& 46 backend_task_runner, 47 Delegate* delegate, 48 bool always_check_updates, 49 bool wait_for_cache_initialization) 50 : cache_dir_(cache_dir), 51 request_context_(request_context), 52 delegate_(delegate), 53 shutdown_(false), 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( 60 this, 61 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR, 62 content::NotificationService::AllBrowserContextsAndSources()); 63 } 64 65 ExternalCache::~ExternalCache() { 66 } 67 68 void ExternalCache::Shutdown(const base::Closure& callback) { 69 DCHECK(!shutdown_); 70 shutdown_ = true; 71 backend_task_runner_->PostTask(FROM_HERE, 72 base::Bind(&ExternalCache::BackendShudown, 73 callback)); 74 } 75 76 void ExternalCache::UpdateExtensionsList( 77 scoped_ptr<base::DictionaryValue> prefs) { 78 if (shutdown_) 79 return; 80 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(); 90 } else { 91 CheckCache(); 92 } 93 } 94 95 void ExternalCache::OnDamagedFileDetected(const base::FilePath& path) { 96 if (shutdown_) 97 return; 98 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(); 105 continue; 106 } 107 108 std::string external_crx; 109 if (entry->GetString(extensions::ExternalProviderImpl::kExternalCrx, 110 &external_crx) && 111 external_crx == path.value()) { 112 113 LOG(ERROR) << "ExternalCache extension at " << path.value() 114 << " failed to install, deleting it."; 115 cached_extensions_->Remove(it.key(), NULL); 116 UpdateExtensionLoader(); 117 118 // The file will be downloaded again on the next restart. 119 if (cache_dir_.IsParent(path)) { 120 backend_task_runner_->PostTask( 121 FROM_HERE, 122 base::Bind(base::IgnoreResult(base::DeleteFile), 123 path, 124 true)); 125 } 126 127 // Don't try to DownloadMissingExtensions() from here, 128 // since it can cause a fail/retry loop. 129 return; 130 } 131 } 132 LOG(ERROR) << "ExternalCache cannot find external_crx " << path.value(); 133 } 134 135 void ExternalCache::Observe(int type, 136 const content::NotificationSource& source, 137 const content::NotificationDetails& details) { 138 if (shutdown_) 139 return; 140 141 switch (type) { 142 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: { 143 extensions::CrxInstaller* installer = 144 content::Source<extensions::CrxInstaller>(source).ptr(); 145 OnDamagedFileDetected(installer->source_file()); 146 break; 147 } 148 149 default: 150 NOTREACHED(); 151 } 152 } 153 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) { 159 if (shutdown_) 160 return; 161 162 if (error == NO_UPDATE_AVAILABLE) { 163 if (!cached_extensions_->HasKey(id)) { 164 LOG(ERROR) << "ExternalCache extension " << id 165 << " not found on update server"; 166 } 167 } else { 168 LOG(ERROR) << "ExternalCache failed to download extension " << id 169 << ", error " << error; 170 } 171 } 172 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) { 180 if (shutdown_) 181 return; 182 183 backend_task_runner_->PostTask( 184 FROM_HERE, 185 base::Bind(&ExternalCache::BackendInstallCacheEntry, 186 weak_ptr_factory_.GetWeakPtr(), 187 cache_dir_, 188 id, 189 path, 190 version)); 191 } 192 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); 196 } 197 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)) { 204 return true; 205 } 206 *version = delegate_->GetInstalledExtensionVersion(id); 207 return !version->empty(); 208 } 209 return false; 210 } 211 212 void ExternalCache::UpdateExtensionLoader() { 213 if (shutdown_) 214 return; 215 216 VLOG(1) << "Notify ExternalCache delegate about cache update"; 217 if (delegate_) 218 delegate_->OnExtensionListsUpdated(cached_extensions_.get()); 219 } 220 221 void ExternalCache::CheckCache() { 222 if (wait_for_cache_initialization_) { 223 backend_task_runner_->PostTask( 224 FROM_HERE, 225 base::Bind(&ExternalCache::BackendCheckCacheStatus, 226 weak_ptr_factory_.GetWeakPtr(), 227 cache_dir_)); 228 } else { 229 CheckCacheContents(); 230 } 231 } 232 233 // static 234 void ExternalCache::BackendCheckCacheStatus( 235 base::WeakPtr<ExternalCache> external_cache, 236 const base::FilePath& cache_dir) { 237 content::BrowserThread::PostTask( 238 content::BrowserThread::UI, 239 FROM_HERE, 240 base::Bind(&ExternalCache::OnCacheStatusChecked, 241 external_cache, 242 base::PathExists(cache_dir.AppendASCII(kCacheReadyFlagFileName)))); 243 } 244 245 void ExternalCache::OnCacheStatusChecked(bool ready) { 246 if (shutdown_) 247 return; 248 249 if (ready) { 250 CheckCacheContents(); 251 } else { 252 content::BrowserThread::PostDelayedTask( 253 content::BrowserThread::UI, 254 FROM_HERE, 255 base::Bind(&ExternalCache::CheckCache, 256 weak_ptr_factory_.GetWeakPtr()), 257 base::TimeDelta::FromMilliseconds(kCacheStatusPollingDelayMs)); 258 } 259 } 260 261 void ExternalCache::CheckCacheContents() { 262 if (shutdown_) 263 return; 264 265 backend_task_runner_->PostTask( 266 FROM_HERE, 267 base::Bind(&ExternalCache::BackendCheckCacheContents, 268 weak_ptr_factory_.GetWeakPtr(), 269 cache_dir_, 270 base::Passed(make_scoped_ptr(extensions_->DeepCopy())))); 271 } 272 273 // static 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, 280 FROM_HERE, 281 base::Bind(&ExternalCache::OnCacheUpdated, 282 external_cache, 283 base::Passed(&prefs))); 284 } 285 286 // static 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)) { 292 // Create it now. 293 if (!base::CreateDirectory(cache_dir)) { 294 LOG(ERROR) << "Failed to create ExternalCache directory at " 295 << cache_dir.value(); 296 } 297 298 // Nothing else to do. Cache won't be used. 299 return; 300 } 301 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(); 311 312 if (info.IsDirectory() || base::IsLink(info.GetName())) { 313 LOG(ERROR) << "Erasing bad file in ExternalCache directory: " << basename; 314 base::DeleteFile(path, true /* recursive */); 315 continue; 316 } 317 318 // Skip flag file that indicates that cache is ready. 319 if (basename == kCacheReadyFlagFileName) 320 continue; 321 322 // crx files in the cache are named <extension-id>-<version>.crx. 323 std::string id; 324 std::string version; 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()); 331 } 332 } 333 334 base::DictionaryValue* entry = NULL; 335 if (!extensions::Extension::IdIsValid(id)) { 336 LOG(ERROR) << "Bad extension id in ExternalCache: " << id; 337 id.clear(); 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."; 341 id.clear(); 342 } 343 344 if (!Version(version).IsValid()) { 345 LOG(ERROR) << "Bad extension version in ExternalCache: " << version; 346 version.clear(); 347 } 348 349 if (id.empty() || version.empty()) { 350 LOG(ERROR) << "Invalid file in ExternalCache, erasing: " << basename; 351 base::DeleteFile(path, true /* recursive */); 352 continue; 353 } 354 355 // Enforce a lower-case id. 356 id = StringToLowerASCII(id); 357 358 std::string update_url; 359 std::string prev_version_string; 360 std::string prev_crx; 361 if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl, 362 &update_url)) { 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, 367 version); 368 entry->SetString(extensions::ExternalProviderImpl::kExternalCrx, 369 path.value()); 370 if (extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) { 371 entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, 372 true); 373 } 374 } else if ( 375 entry->GetString(extensions::ExternalProviderImpl::kExternalVersion, 376 &prev_version_string) && 377 entry->GetString(extensions::ExternalProviderImpl::kExternalCrx, 378 &prev_crx)) { 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 */); 390 } 391 entry->SetString(extensions::ExternalProviderImpl::kExternalVersion, 392 version); 393 entry->SetString(extensions::ExternalProviderImpl::kExternalCrx, 394 path.value()); 395 } else { 396 VLOG(1) << "ExternalCache found old cached version " 397 << version << " path: " << path.value(); 398 base::DeleteFile(path, true /* recursive */); 399 } 400 } else { 401 NOTREACHED() << "ExternalCache found bad entry for extension id: " << id 402 << " file path: " << path.value(); 403 } 404 } 405 } 406 407 void ExternalCache::OnCacheUpdated(scoped_ptr<base::DictionaryValue> prefs) { 408 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 409 if (shutdown_) 410 return; 411 412 // If request_context_ is missing we can't download anything. 413 if (!downloader_ && request_context_) { 414 downloader_.reset( 415 new extensions::ExtensionDownloader(this, request_context_)); 416 } 417 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(); 425 continue; 426 } 427 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) { 433 GURL update_url; 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(); 440 } 441 if (update_url.is_valid()) 442 downloader_->AddPendingExtension(it.key(), update_url, 0); 443 } 444 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()); 456 } 457 } 458 } 459 if (downloader_) 460 downloader_->StartAllPending(); 461 462 VLOG(1) << "Updated ExternalCache, there are " 463 << cached_extensions_->size() << " extensions cached"; 464 465 UpdateExtensionLoader(); 466 } 467 468 // static 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 */); 480 return; 481 } 482 483 std::string basename = id + "-" + version + kCRXFileExtension; 484 base::FilePath cached_crx_path = cache_dir.Append(basename); 485 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 */); 490 } 491 492 if (!base::DirectoryExists(cache_dir)) { 493 LOG(ERROR) << "AppPack cache directory does not exist, creating now: " 494 << cache_dir.value(); 495 if (!base::CreateDirectory(cache_dir)) { 496 LOG(ERROR) << "Failed to create the AppPack cache dir!"; 497 base::DeleteFile(path, true /* recursive */); 498 return; 499 } 500 } 501 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 */); 506 return; 507 } 508 509 content::BrowserThread::PostTask( 510 content::BrowserThread::UI, 511 FROM_HERE, 512 base::Bind(&ExternalCache::OnCacheEntryInstalled, 513 external_cache, 514 id, 515 cached_crx_path, 516 version)); 517 } 518 519 void ExternalCache::OnCacheEntryInstalled(const std::string& id, 520 const base::FilePath& path, 521 const std::string& version) { 522 if (shutdown_) 523 return; 524 525 VLOG(1) << "AppPack installed a new extension in the cache: " << path.value(); 526 527 base::DictionaryValue* entry = NULL; 528 if (!extensions_->GetDictionary(id, &entry)) { 529 LOG(ERROR) << "ExternalCache cannot find entry for extension " << id; 530 return; 531 } 532 533 // Copy entry to don't modify it inside extensions_. 534 entry = entry->DeepCopy(); 535 536 std::string update_url; 537 if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl, 538 &update_url) && 539 extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) { 540 entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true); 541 } 542 entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL); 543 entry->SetString(extensions::ExternalProviderImpl::kExternalVersion, version); 544 entry->SetString(extensions::ExternalProviderImpl::kExternalCrx, 545 path.value()); 546 547 cached_extensions_->Set(id, entry); 548 UpdateExtensionLoader(); 549 } 550 551 // static 552 void ExternalCache::BackendShudown(const base::Closure& callback) { 553 content::BrowserThread::PostTask(content::BrowserThread::UI, 554 FROM_HERE, 555 callback); 556 } 557 558 std::string ExternalCache::Delegate::GetInstalledExtensionVersion( 559 const std::string& id) { 560 return std::string(); 561 } 562 563 } // namespace chromeos 564