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/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"
31 ExternalCache::ExternalCache(const base::FilePath& cache_dir,
32 net::URLRequestContextGetter* request_context,
33 const scoped_refptr<base::SequencedTaskRunner>&
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),
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(
48 extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR,
49 content::NotificationService::AllBrowserContextsAndSources());
52 ExternalCache::~ExternalCache() {
55 void ExternalCache::Shutdown(const base::Closure& callback) {
56 local_cache_.Shutdown(callback);
59 void ExternalCache::UpdateExtensionsList(
60 scoped_ptr<base::DictionaryValue> prefs) {
61 extensions_ = prefs.Pass();
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();
72 if (local_cache_.is_uninitialized()) {
73 local_cache_.Init(wait_for_cache_initialization_,
74 base::Bind(&ExternalCache::CheckCache,
75 weak_ptr_factory_.GetWeakPtr()));
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();
91 std::string external_crx;
92 if (entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
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);
101 local_cache_.RemoveExtension(id);
102 UpdateExtensionLoader();
104 // Don't try to DownloadMissingExtensions() from here,
105 // since it can cause a fail/retry loop.
109 LOG(ERROR) << "ExternalCache cannot find external_crx " << path.value();
112 void ExternalCache::RemoveExtensions(const std::vector<std::string>& ids) {
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]);
121 UpdateExtensionLoader();
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);
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,
138 base::Bind(&ExternalCache::OnPutExternalExtension,
139 weak_ptr_factory_.GetWeakPtr(),
144 void ExternalCache::Observe(int type,
145 const content::NotificationSource& source,
146 const content::NotificationDetails& details) {
148 case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
149 extensions::CrxInstaller* installer =
150 content::Source<extensions::CrxInstaller>(source).ptr();
151 OnDamagedFileDetected(installer->source_file());
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);
171 // No version update for an already cached extension.
172 delegate_->OnExtensionLoadedInCache(id);
175 LOG(ERROR) << "ExternalCache failed to download extension " << id
176 << ", error " << error;
177 delegate_->OnExtensionDownloadFailed(id, error);
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(),
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);
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)) {
209 *version = delegate_->GetInstalledExtensionVersion(id);
210 return !version->empty();
215 void ExternalCache::UpdateExtensionLoader() {
216 VLOG(1) << "Notify ExternalCache delegate about cache update";
218 delegate_->OnExtensionListsUpdated(cached_extensions_.get());
221 void ExternalCache::CheckCache() {
222 if (local_cache_.is_shutdown())
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);
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();
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) {
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();
253 if (update_url.is_valid())
254 downloader_->AddPendingExtension(it.key(), update_url, 0);
257 base::FilePath file_path;
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();
263 if (extension_urls::IsWebstoreUpdateUrl(GURL(external_update_url))) {
264 entry_copy->SetBoolean(
265 extensions::ExternalProviderImpl::kIsFromWebstore, true);
267 entry_copy->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl,
269 entry_copy->SetString(extensions::ExternalProviderImpl::kExternalVersion,
271 entry_copy->SetString(extensions::ExternalProviderImpl::kExternalCrx,
273 cached_extensions_->Set(it.key(), entry_copy);
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());
287 downloader_->StartAllPending(NULL);
289 VLOG(1) << "Updated ExternalCache, there are "
290 << cached_extensions_->size() << " extensions cached";
292 UpdateExtensionLoader();
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));
304 VLOG(1) << "ExternalCache installed a new extension in the cache " << id;
306 base::DictionaryValue* entry = NULL;
307 if (!extensions_->GetDictionary(id, &entry)) {
308 LOG(ERROR) << "ExternalCache cannot find entry for extension " << id;
312 // Copy entry to don't modify it inside extensions_.
313 entry = entry->DeepCopy();
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;
322 std::string update_url;
323 if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
325 extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
326 entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true);
328 entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
329 entry->SetString(extensions::ExternalProviderImpl::kExternalVersion, version);
330 entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
333 cached_extensions_->Set(id, entry);
335 delegate_->OnExtensionLoadedInCache(id);
336 UpdateExtensionLoader();
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);
349 std::string ExternalCache::Delegate::GetInstalledExtensionVersion(
350 const std::string& id) {
351 return std::string();
354 } // namespace chromeos