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