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 "content/public/browser/notification_details.h"
21 #include "content/public/browser/notification_service.h"
22 #include "content/public/browser/notification_source.h"
23 #include "extensions/browser/notification_types.h"
24 #include "extensions/browser/updater/extension_downloader.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
, std::string());
102 UpdateExtensionLoader();
104 // Don't try to DownloadMissingExtensions() from here,
105 // since it can cause a fail/retry loop.
109 DLOG(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
], std::string());
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
, std::string(), 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(
136 id
, std::string(), crx_file_path
, version
,
137 base::Bind(&ExternalCache::OnPutExternalExtension
,
138 weak_ptr_factory_
.GetWeakPtr(), id
, callback
));
141 void ExternalCache::Observe(int type
,
142 const content::NotificationSource
& source
,
143 const content::NotificationDetails
& details
) {
145 case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR
: {
146 extensions::CrxInstaller
* installer
=
147 content::Source
<extensions::CrxInstaller
>(source
).ptr();
148 OnDamagedFileDetected(installer
->source_file());
157 void ExternalCache::OnExtensionDownloadFailed(
158 const std::string
& id
,
159 extensions::ExtensionDownloaderDelegate::Error error
,
160 const extensions::ExtensionDownloaderDelegate::PingResult
& ping_result
,
161 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";
166 delegate_
->OnExtensionDownloadFailed(id
, error
);
168 // No version update for an already cached extension.
169 delegate_
->OnExtensionLoadedInCache(id
);
172 LOG(ERROR
) << "ExternalCache failed to download extension " << id
173 << ", error " << error
;
174 delegate_
->OnExtensionDownloadFailed(id
, error
);
178 void ExternalCache::OnExtensionDownloadFinished(
179 const extensions::CRXFileInfo
& file
,
180 bool file_ownership_passed
,
181 const GURL
& download_url
,
182 const std::string
& version
,
183 const extensions::ExtensionDownloaderDelegate::PingResult
& ping_result
,
184 const std::set
<int>& request_ids
,
185 const InstallCallback
& callback
) {
186 DCHECK(file_ownership_passed
);
187 local_cache_
.PutExtension(
188 file
.extension_id
, file
.expected_hash
, file
.path
, version
,
189 base::Bind(&ExternalCache::OnPutExtension
, weak_ptr_factory_
.GetWeakPtr(),
191 if (!callback
.is_null())
195 bool ExternalCache::IsExtensionPending(const std::string
& id
) {
196 // Pending means that there is no installed version yet.
197 return extensions_
->HasKey(id
) && !cached_extensions_
->HasKey(id
);
200 bool ExternalCache::GetExtensionExistingVersion(const std::string
& id
,
201 std::string
* version
) {
202 base::DictionaryValue
* extension_dictionary
= NULL
;
203 if (cached_extensions_
->GetDictionary(id
, &extension_dictionary
)) {
204 if (extension_dictionary
->GetString(
205 extensions::ExternalProviderImpl::kExternalVersion
, version
)) {
208 *version
= delegate_
->GetInstalledExtensionVersion(id
);
209 return !version
->empty();
214 void ExternalCache::UpdateExtensionLoader() {
215 VLOG(1) << "Notify ExternalCache delegate about cache update";
217 delegate_
->OnExtensionListsUpdated(cached_extensions_
.get());
220 void ExternalCache::CheckCache() {
221 if (local_cache_
.is_shutdown())
224 // If request_context_ is missing we can't download anything.
225 if (!downloader_
&& request_context_
.get()) {
226 downloader_
= ChromeExtensionDownloaderFactory::CreateForRequestContext(
227 request_context_
.get(), this);
230 cached_extensions_
->Clear();
231 for (base::DictionaryValue::Iterator
it(*extensions_
.get());
232 !it
.IsAtEnd(); it
.Advance()) {
233 const base::DictionaryValue
* entry
= NULL
;
234 if (!it
.value().GetAsDictionary(&entry
)) {
235 LOG(ERROR
) << "ExternalCache found bad entry with type "
236 << it
.value().GetType();
240 bool keep_if_present
=
241 entry
->HasKey(extensions::ExternalProviderImpl::kKeepIfPresent
);
242 std::string external_update_url
;
243 entry
->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl
,
244 &external_update_url
);
245 if (downloader_
&& !keep_if_present
) {
247 if (!external_update_url
.empty())
248 update_url
= GURL(external_update_url
);
249 else if (always_check_updates_
)
250 update_url
= extension_urls::GetWebstoreUpdateUrl();
252 if (update_url
.is_valid())
253 downloader_
->AddPendingExtension(it
.key(), update_url
, 0);
256 base::FilePath file_path
;
259 if (local_cache_
.GetExtension(it
.key(), hash
, &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();
317 if (!local_cache_
.GetExtension(id
, hash
, NULL
, &version
)) {
318 // Copy entry to don't modify it inside extensions_.
319 LOG(ERROR
) << "Can't find installed extension in cache " << id
;
323 std::string update_url
;
324 if (entry
->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl
,
326 extension_urls::IsWebstoreUpdateUrl(GURL(update_url
))) {
327 entry
->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore
, true);
329 entry
->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl
, NULL
);
330 entry
->SetString(extensions::ExternalProviderImpl::kExternalVersion
, version
);
331 entry
->SetString(extensions::ExternalProviderImpl::kExternalCrx
,
334 cached_extensions_
->Set(id
, entry
);
336 delegate_
->OnExtensionLoadedInCache(id
);
337 UpdateExtensionLoader();
340 void ExternalCache::OnPutExternalExtension(
341 const std::string
& id
,
342 const PutExternalExtensionCallback
& callback
,
343 const base::FilePath
& file_path
,
344 bool file_ownership_passed
) {
345 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
346 OnPutExtension(id
, file_path
, file_ownership_passed
);
347 callback
.Run(id
, !file_ownership_passed
);
350 std::string
ExternalCache::Delegate::GetInstalledExtensionVersion(
351 const std::string
& id
) {
352 return std::string();
355 } // namespace chromeos