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/files/file_enumerator.h" 11 #include "base/files/file_util.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/extensions/crx_installer.h" 18 #include "chrome/browser/extensions/external_provider_impl.h" 19 #include "chrome/browser/extensions/updater/chrome_extension_downloader_factory.h" 20 #include "chrome/browser/extensions/updater/extension_downloader.h" 21 #include "content/public/browser/notification_details.h" 22 #include "content/public/browser/notification_service.h" 23 #include "content/public/browser/notification_source.h" 24 #include "extensions/browser/notification_types.h" 25 #include "extensions/common/extension.h" 26 #include "extensions/common/extension_urls.h" 27 #include "net/url_request/url_request_context_getter.h" 28 29 namespace chromeos { 30 31 ExternalCache::ExternalCache(const base::FilePath& cache_dir, 32 net::URLRequestContextGetter* request_context, 33 const scoped_refptr<base::SequencedTaskRunner>& 34 backend_task_runner, 35 Delegate* delegate, 36 bool always_check_updates, 37 bool wait_for_cache_initialization) 38 : local_cache_(cache_dir, 0, base::TimeDelta(), backend_task_runner), 39 request_context_(request_context), 40 backend_task_runner_(backend_task_runner), 41 delegate_(delegate), 42 always_check_updates_(always_check_updates), 43 wait_for_cache_initialization_(wait_for_cache_initialization), 44 cached_extensions_(new base::DictionaryValue()), 45 weak_ptr_factory_(this) { 46 notification_registrar_.Add( 47 this, 48 extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR, 49 content::NotificationService::AllBrowserContextsAndSources()); 50 } 51 52 ExternalCache::~ExternalCache() { 53 } 54 55 void ExternalCache::Shutdown(const base::Closure& callback) { 56 local_cache_.Shutdown(callback); 57 } 58 59 void ExternalCache::UpdateExtensionsList( 60 scoped_ptr<base::DictionaryValue> prefs) { 61 extensions_ = prefs.Pass(); 62 63 if (extensions_->empty()) { 64 // If list of know extensions is empty, don't init cache on disk. It is 65 // important shortcut for test to don't wait forever for cache dir 66 // initialization that should happen outside of Chrome on real device. 67 cached_extensions_->Clear(); 68 UpdateExtensionLoader(); 69 return; 70 } 71 72 if (local_cache_.is_uninitialized()) { 73 local_cache_.Init(wait_for_cache_initialization_, 74 base::Bind(&ExternalCache::CheckCache, 75 weak_ptr_factory_.GetWeakPtr())); 76 } else { 77 CheckCache(); 78 } 79 } 80 81 void ExternalCache::OnDamagedFileDetected(const base::FilePath& path) { 82 for (base::DictionaryValue::Iterator it(*cached_extensions_.get()); 83 !it.IsAtEnd(); it.Advance()) { 84 const base::DictionaryValue* entry = NULL; 85 if (!it.value().GetAsDictionary(&entry)) { 86 NOTREACHED() << "ExternalCache found bad entry with type " 87 << it.value().GetType(); 88 continue; 89 } 90 91 std::string external_crx; 92 if (entry->GetString(extensions::ExternalProviderImpl::kExternalCrx, 93 &external_crx) && 94 external_crx == path.value()) { 95 std::string id = it.key(); 96 LOG(ERROR) << "ExternalCache extension at " << path.value() 97 << " failed to install, deleting it."; 98 cached_extensions_->Remove(id, NULL); 99 extensions_->Remove(id, NULL); 100 101 local_cache_.RemoveExtension(id); 102 UpdateExtensionLoader(); 103 104 // Don't try to DownloadMissingExtensions() from here, 105 // since it can cause a fail/retry loop. 106 return; 107 } 108 } 109 LOG(ERROR) << "ExternalCache cannot find external_crx " << path.value(); 110 } 111 112 void ExternalCache::RemoveExtensions(const std::vector<std::string>& ids) { 113 if (ids.empty()) 114 return; 115 116 for (size_t i = 0; i < ids.size(); ++i) { 117 cached_extensions_->Remove(ids[i], NULL); 118 extensions_->Remove(ids[i], NULL); 119 local_cache_.RemoveExtension(ids[i]); 120 } 121 UpdateExtensionLoader(); 122 } 123 124 bool ExternalCache::GetExtension(const std::string& id, 125 base::FilePath* file_path, 126 std::string* version) { 127 return local_cache_.GetExtension(id, file_path, version); 128 } 129 130 void ExternalCache::PutExternalExtension( 131 const std::string& id, 132 const base::FilePath& crx_file_path, 133 const std::string& version, 134 const PutExternalExtensionCallback& callback) { 135 local_cache_.PutExtension(id, 136 crx_file_path, 137 version, 138 base::Bind(&ExternalCache::OnPutExternalExtension, 139 weak_ptr_factory_.GetWeakPtr(), 140 id, 141 callback)); 142 } 143 144 void ExternalCache::Observe(int type, 145 const content::NotificationSource& source, 146 const content::NotificationDetails& details) { 147 switch (type) { 148 case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR: { 149 extensions::CrxInstaller* installer = 150 content::Source<extensions::CrxInstaller>(source).ptr(); 151 OnDamagedFileDetected(installer->source_file()); 152 break; 153 } 154 155 default: 156 NOTREACHED(); 157 } 158 } 159 160 void ExternalCache::OnExtensionDownloadFailed( 161 const std::string& id, 162 extensions::ExtensionDownloaderDelegate::Error error, 163 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result, 164 const std::set<int>& request_ids) { 165 if (error == NO_UPDATE_AVAILABLE) { 166 if (!cached_extensions_->HasKey(id)) { 167 LOG(ERROR) << "ExternalCache extension " << id 168 << " not found on update server"; 169 delegate_->OnExtensionDownloadFailed(id, error); 170 } else { 171 // No version update for an already cached extension. 172 delegate_->OnExtensionLoadedInCache(id); 173 } 174 } else { 175 LOG(ERROR) << "ExternalCache failed to download extension " << id 176 << ", error " << error; 177 delegate_->OnExtensionDownloadFailed(id, error); 178 } 179 } 180 181 void ExternalCache::OnExtensionDownloadFinished( 182 const std::string& id, 183 const base::FilePath& path, 184 bool file_ownership_passed, 185 const GURL& download_url, 186 const std::string& version, 187 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result, 188 const std::set<int>& request_ids) { 189 DCHECK(file_ownership_passed); 190 local_cache_.PutExtension(id, path, version, 191 base::Bind(&ExternalCache::OnPutExtension, 192 weak_ptr_factory_.GetWeakPtr(), 193 id)); 194 } 195 196 bool ExternalCache::IsExtensionPending(const std::string& id) { 197 // Pending means that there is no installed version yet. 198 return extensions_->HasKey(id) && !cached_extensions_->HasKey(id); 199 } 200 201 bool ExternalCache::GetExtensionExistingVersion(const std::string& id, 202 std::string* version) { 203 base::DictionaryValue* extension_dictionary = NULL; 204 if (cached_extensions_->GetDictionary(id, &extension_dictionary)) { 205 if (extension_dictionary->GetString( 206 extensions::ExternalProviderImpl::kExternalVersion, version)) { 207 return true; 208 } 209 *version = delegate_->GetInstalledExtensionVersion(id); 210 return !version->empty(); 211 } 212 return false; 213 } 214 215 void ExternalCache::UpdateExtensionLoader() { 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 (local_cache_.is_shutdown()) 223 return; 224 225 // If request_context_ is missing we can't download anything. 226 if (!downloader_ && request_context_.get()) { 227 downloader_ = ChromeExtensionDownloaderFactory::CreateForRequestContext( 228 request_context_.get(), this); 229 } 230 231 cached_extensions_->Clear(); 232 for (base::DictionaryValue::Iterator it(*extensions_.get()); 233 !it.IsAtEnd(); it.Advance()) { 234 const base::DictionaryValue* entry = NULL; 235 if (!it.value().GetAsDictionary(&entry)) { 236 LOG(ERROR) << "ExternalCache found bad entry with type " 237 << it.value().GetType(); 238 continue; 239 } 240 241 bool keep_if_present = 242 entry->HasKey(extensions::ExternalProviderImpl::kKeepIfPresent); 243 std::string external_update_url; 244 entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl, 245 &external_update_url); 246 if (downloader_ && !keep_if_present) { 247 GURL update_url; 248 if (!external_update_url.empty()) 249 update_url = GURL(external_update_url); 250 else if (always_check_updates_) 251 update_url = extension_urls::GetWebstoreUpdateUrl(); 252 253 if (update_url.is_valid()) 254 downloader_->AddPendingExtension(it.key(), update_url, 0); 255 } 256 257 base::FilePath file_path; 258 std::string version; 259 if (local_cache_.GetExtension(it.key(), &file_path, &version)) { 260 // Copy entry to don't modify it inside extensions_. 261 base::DictionaryValue* entry_copy = entry->DeepCopy(); 262 263 if (extension_urls::IsWebstoreUpdateUrl(GURL(external_update_url))) { 264 entry_copy->SetBoolean( 265 extensions::ExternalProviderImpl::kIsFromWebstore, true); 266 } 267 entry_copy->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, 268 NULL); 269 entry_copy->SetString(extensions::ExternalProviderImpl::kExternalVersion, 270 version); 271 entry_copy->SetString(extensions::ExternalProviderImpl::kExternalCrx, 272 file_path.value()); 273 cached_extensions_->Set(it.key(), entry_copy); 274 } else { 275 bool has_external_crx = entry->HasKey( 276 extensions::ExternalProviderImpl::kExternalCrx); 277 bool is_already_installed = 278 !delegate_->GetInstalledExtensionVersion(it.key()).empty(); 279 if (keep_if_present || has_external_crx || is_already_installed) { 280 // Copy entry to don't modify it inside extensions_. 281 cached_extensions_->Set(it.key(), entry->DeepCopy()); 282 } 283 } 284 } 285 286 if (downloader_) 287 downloader_->StartAllPending(NULL); 288 289 VLOG(1) << "Updated ExternalCache, there are " 290 << cached_extensions_->size() << " extensions cached"; 291 292 UpdateExtensionLoader(); 293 } 294 295 void ExternalCache::OnPutExtension(const std::string& id, 296 const base::FilePath& file_path, 297 bool file_ownership_passed) { 298 if (local_cache_.is_shutdown() || file_ownership_passed) { 299 backend_task_runner_->PostTask(FROM_HERE, 300 base::Bind(base::IgnoreResult(&base::DeleteFile), file_path, true)); 301 return; 302 } 303 304 VLOG(1) << "ExternalCache installed a new extension in the cache " << id; 305 306 base::DictionaryValue* entry = NULL; 307 if (!extensions_->GetDictionary(id, &entry)) { 308 LOG(ERROR) << "ExternalCache cannot find entry for extension " << id; 309 return; 310 } 311 312 // Copy entry to don't modify it inside extensions_. 313 entry = entry->DeepCopy(); 314 315 std::string version; 316 if (!local_cache_.GetExtension(id, NULL, &version)) { 317 // Copy entry to don't modify it inside extensions_. 318 LOG(ERROR) << "Can't find installed extension in cache " << id; 319 return; 320 } 321 322 std::string update_url; 323 if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl, 324 &update_url) && 325 extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) { 326 entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true); 327 } 328 entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL); 329 entry->SetString(extensions::ExternalProviderImpl::kExternalVersion, version); 330 entry->SetString(extensions::ExternalProviderImpl::kExternalCrx, 331 file_path.value()); 332 333 cached_extensions_->Set(id, entry); 334 if (delegate_) 335 delegate_->OnExtensionLoadedInCache(id); 336 UpdateExtensionLoader(); 337 } 338 339 void ExternalCache::OnPutExternalExtension( 340 const std::string& id, 341 const PutExternalExtensionCallback& callback, 342 const base::FilePath& file_path, 343 bool file_ownership_passed) { 344 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 345 OnPutExtension(id, file_path, file_ownership_passed); 346 callback.Run(id, !file_ownership_passed); 347 } 348 349 std::string ExternalCache::Delegate::GetInstalledExtensionVersion( 350 const std::string& id) { 351 return std::string(); 352 } 353 354 } // namespace chromeos 355