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/file_util.h"
10 #include "base/files/file_enumerator.h"
11 #include "base/location.h"
12 #include "base/strings/string_util.h"
13 #include "base/values.h"
14 #include "base/version.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/extensions/crx_installer.h"
17 #include "chrome/browser/extensions/external_provider_impl.h"
18 #include "chrome/browser/extensions/updater/extension_downloader.h"
19 #include "chrome/common/extensions/extension.h"
20 #include "chrome/common/extensions/extension_constants.h"
21 #include "content/public/browser/browser_thread.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"
30 // File name extension for CRX files (not case sensitive).
31 const char kCRXFileExtension
[] = ".crx";
33 // Name of flag file that indicates that cache is ready (import finished).
34 const char kCacheReadyFlagFileName
[] = ".initialized";
36 // Delay between checking cache ready flag file.
37 const int64_t kCacheReadyDelayMs
= 1000;
41 ExternalCache::ExternalCache(const std::string
& cache_dir
,
42 net::URLRequestContextGetter
* request_context
,
44 bool always_check_updates
,
45 bool wait_cache_initialization
)
46 : cache_dir_(cache_dir
),
47 request_context_(request_context
),
49 always_check_updates_(always_check_updates
),
50 wait_cache_initialization_(wait_cache_initialization
),
51 cached_extensions_(new base::DictionaryValue()),
52 weak_ptr_factory_(this),
54 content::BrowserThread::GetBlockingPool()->GetSequenceToken()) {
55 notification_registrar_
.Add(
57 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR
,
58 content::NotificationService::AllBrowserContextsAndSources());
61 ExternalCache::~ExternalCache() {
64 void ExternalCache::UpdateExtensionsList(
65 scoped_ptr
<base::DictionaryValue
> prefs
) {
66 extensions_
= prefs
.Pass();
67 if (extensions_
->empty()) {
68 // Don't check cache and clear it if there are no extensions in the list.
69 // It is important case because login to supervised user shouldn't clear
70 // cache for normal users.
71 // TODO(dpolukhin): introduce reference counting to preserve cache elements
72 // when they are not needed for current user but needed for other users.
73 cached_extensions_
->Clear();
74 UpdateExtensionLoader();
80 void 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();
90 std::string external_crx
;
91 if (entry
->GetString(extensions::ExternalProviderImpl::kExternalCrx
,
93 external_crx
== path
.value()) {
95 LOG(ERROR
) << "ExternalCache extension at " << path
.value()
96 << " failed to install, deleting it.";
97 cached_extensions_
->Remove(it
.key(), NULL
);
98 UpdateExtensionLoader();
100 // The file will be downloaded again on the next restart.
101 if (base::FilePath(cache_dir_
).IsParent(path
)) {
102 // Don't delete files out of cache_dir_.
103 content::BrowserThread::PostTask(
104 content::BrowserThread::FILE, FROM_HERE
,
105 base::Bind(base::IgnoreResult(base::DeleteFile
), path
, true));
108 // Don't try to DownloadMissingExtensions() from here,
109 // since it can cause a fail/retry loop.
113 LOG(ERROR
) << "ExternalCache cannot find external_crx " << path
.value();
116 void ExternalCache::Observe(int type
,
117 const content::NotificationSource
& source
,
118 const content::NotificationDetails
& details
) {
120 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR
: {
121 extensions::CrxInstaller
* installer
=
122 content::Source
<extensions::CrxInstaller
>(source
).ptr();
123 OnDamagedFileDetected(installer
->source_file());
132 void ExternalCache::OnExtensionDownloadFailed(
133 const std::string
& id
,
134 extensions::ExtensionDownloaderDelegate::Error error
,
135 const extensions::ExtensionDownloaderDelegate::PingResult
& ping_result
,
136 const std::set
<int>& request_ids
) {
137 if (error
== NO_UPDATE_AVAILABLE
) {
138 if (!cached_extensions_
->HasKey(id
)) {
139 LOG(ERROR
) << "ExternalCache extension " << id
140 << " not found on update server";
143 LOG(ERROR
) << "ExternalCache failed to download extension " << id
144 << ", error " << error
;
148 void ExternalCache::OnExtensionDownloadFinished(
149 const std::string
& id
,
150 const base::FilePath
& path
,
151 const GURL
& download_url
,
152 const std::string
& version
,
153 const extensions::ExtensionDownloaderDelegate::PingResult
& ping_result
,
154 const std::set
<int>& request_ids
) {
155 // The explicit copy ctors are to make sure that Bind() binds a copy and not
156 // a reference to the arguments.
157 PostBlockingTask(FROM_HERE
,
158 base::Bind(&ExternalCache::BlockingInstallCacheEntry
,
159 weak_ptr_factory_
.GetWeakPtr(),
160 std::string(cache_dir_
),
162 base::FilePath(path
),
163 std::string(version
)));
166 void ExternalCache::OnBlacklistDownloadFinished(
167 const std::string
& data
,
168 const std::string
& package_hash
,
169 const std::string
& version
,
170 const extensions::ExtensionDownloaderDelegate::PingResult
& ping_result
,
171 const std::set
<int>& request_ids
) {
175 bool ExternalCache::IsExtensionPending(const std::string
& id
) {
176 // Pending means that there is no installed version yet.
177 return extensions_
->HasKey(id
) && !cached_extensions_
->HasKey(id
);
180 bool ExternalCache::GetExtensionExistingVersion(const std::string
& id
,
181 std::string
* version
) {
182 DictionaryValue
* extension_dictionary
= NULL
;
183 if (cached_extensions_
->GetDictionary(id
, &extension_dictionary
)) {
184 return extension_dictionary
->GetString(
185 extensions::ExternalProviderImpl::kExternalVersion
,
191 void ExternalCache::CheckCacheNow() {
192 scoped_ptr
<DictionaryValue
> prefs(extensions_
->DeepCopy());
193 PostBlockingTask(FROM_HERE
,
194 base::Bind(&ExternalCache::BlockingCheckCache
,
195 weak_ptr_factory_
.GetWeakPtr(),
197 std::string(cache_dir_
),
198 base::Passed(&prefs
),
199 wait_cache_initialization_
));
202 void ExternalCache::UpdateExtensionLoader() {
203 VLOG(1) << "Notify ExternalCache delegate about cache update";
205 delegate_
->OnExtensionListsUpdated(cached_extensions_
.get());
209 void ExternalCache::BlockingCheckCache(
210 base::WeakPtr
<ExternalCache
> external_cache
,
211 base::SequencedWorkerPool::SequenceToken sequence_token
,
212 const std::string
& cache_dir
,
213 scoped_ptr
<base::DictionaryValue
> prefs
,
214 bool wait_cache_initialization
) {
216 base::FilePath
dir(cache_dir
);
217 if (wait_cache_initialization
&&
218 !base::PathExists(dir
.AppendASCII(kCacheReadyFlagFileName
))) {
219 content::BrowserThread::GetBlockingPool()->PostDelayedSequencedWorkerTask(
222 base::Bind(&ExternalCache::BlockingCheckCache
,
225 std::string(cache_dir
),
226 base::Passed(&prefs
),
227 wait_cache_initialization
),
228 base::TimeDelta::FromMilliseconds(kCacheReadyDelayMs
));
232 BlockingCheckCacheInternal(cache_dir
, prefs
.get());
233 content::BrowserThread::PostTask(content::BrowserThread::UI
, FROM_HERE
,
234 base::Bind(&ExternalCache::OnCacheUpdated
,
236 base::Passed(&prefs
)));
240 void ExternalCache::BlockingCheckCacheInternal(const std::string
& cache_dir
,
241 base::DictionaryValue
* prefs
) {
242 // Start by verifying that the cache dir exists.
243 base::FilePath
dir(cache_dir
);
244 if (!base::DirectoryExists(dir
)) {
246 if (!file_util::CreateDirectory(dir
)) {
247 LOG(ERROR
) << "Failed to create ExternalCache directory at "
251 // Nothing else to do. Cache won't be used.
255 // Enumerate all the files in the cache |dir|, including directories
256 // and symlinks. Each unrecognized file will be erased.
257 int types
= base::FileEnumerator::FILES
| base::FileEnumerator::DIRECTORIES
|
258 base::FileEnumerator::SHOW_SYM_LINKS
;
259 base::FileEnumerator
enumerator(dir
, false /* recursive */, types
);
260 for (base::FilePath path
= enumerator
.Next();
261 !path
.empty(); path
= enumerator
.Next()) {
262 base::FileEnumerator::FileInfo info
= enumerator
.GetInfo();
263 std::string basename
= path
.BaseName().value();
265 if (info
.IsDirectory() || file_util::IsLink(info
.GetName())) {
266 LOG(ERROR
) << "Erasing bad file in ExternalCache directory: " << basename
;
267 base::DeleteFile(path
, true /* recursive */);
271 // Skip flag file that indicates that cache is ready.
272 if (basename
== kCacheReadyFlagFileName
)
275 // crx files in the cache are named <extension-id>-<version>.crx.
278 if (EndsWith(basename
, kCRXFileExtension
, false /* case-sensitive */)) {
279 size_t n
= basename
.find('-');
280 if (n
!= std::string::npos
&& n
+ 1 < basename
.size() - 4) {
281 id
= basename
.substr(0, n
);
282 // Size of |version| = total size - "<id>" - "-" - ".crx"
283 version
= basename
.substr(n
+ 1, basename
.size() - 5 - id
.size());
287 base::DictionaryValue
* entry
= NULL
;
288 if (!extensions::Extension::IdIsValid(id
)) {
289 LOG(ERROR
) << "Bad extension id in ExternalCache: " << id
;
291 } else if (!prefs
->GetDictionary(id
, &entry
)) {
292 LOG(WARNING
) << basename
<< " is in the cache but is not configured by "
293 << "the ExternalCache source, and will be erased.";
297 if (!Version(version
).IsValid()) {
298 LOG(ERROR
) << "Bad extension version in ExternalCache: " << version
;
302 if (id
.empty() || version
.empty()) {
303 LOG(ERROR
) << "Invalid file in ExternalCache, erasing: " << basename
;
304 base::DeleteFile(path
, true /* recursive */);
308 // Enforce a lower-case id.
309 id
= StringToLowerASCII(id
);
311 std::string update_url
;
312 std::string prev_version_string
;
313 std::string prev_crx
;
314 if (entry
->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl
,
316 VLOG(1) << "ExternalCache found cached version " << version
317 << " for extension id: " << id
;
318 entry
->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl
, NULL
);
319 entry
->SetString(extensions::ExternalProviderImpl::kExternalVersion
,
321 entry
->SetString(extensions::ExternalProviderImpl::kExternalCrx
,
323 if (extension_urls::IsWebstoreUpdateUrl(GURL(update_url
))) {
324 entry
->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore
,
328 entry
->GetString(extensions::ExternalProviderImpl::kExternalVersion
,
329 &prev_version_string
) &&
330 entry
->GetString(extensions::ExternalProviderImpl::kExternalCrx
,
332 Version
prev_version(prev_version_string
);
333 Version
curr_version(version
);
334 DCHECK(prev_version
.IsValid());
335 DCHECK(curr_version
.IsValid());
336 if (prev_version
.CompareTo(curr_version
) <= 0) {
337 VLOG(1) << "ExternalCache found old cached version "
338 << prev_version_string
<< " path: " << prev_crx
;
339 base::FilePath
prev_crx_file(prev_crx
);
340 if (dir
.IsParent(prev_crx_file
)) {
341 // Only delete old cached files under cache_dir_ folder.
342 base::DeleteFile(base::FilePath(prev_crx
), true /* recursive */);
344 entry
->SetString(extensions::ExternalProviderImpl::kExternalVersion
,
346 entry
->SetString(extensions::ExternalProviderImpl::kExternalCrx
,
349 VLOG(1) << "ExternalCache found old cached version "
350 << version
<< " path: " << path
.value();
351 base::DeleteFile(path
, true /* recursive */);
354 NOTREACHED() << "ExternalCache found bad entry for extension id: " << id
355 << " file path: " << path
.value();
360 void ExternalCache::OnCacheUpdated(scoped_ptr
<base::DictionaryValue
> prefs
) {
361 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
363 // If request_context_ is missing we can't download anything.
364 if (!downloader_
&& request_context_
) {
366 new extensions::ExtensionDownloader(this, request_context_
));
369 cached_extensions_
->Clear();
370 for (base::DictionaryValue::Iterator
it(*extensions_
.get());
371 !it
.IsAtEnd(); it
.Advance()) {
372 const base::DictionaryValue
* entry
= NULL
;
373 if (!it
.value().GetAsDictionary(&entry
)) {
374 LOG(ERROR
) << "ExternalCache found bad entry with type "
375 << it
.value().GetType();
379 // Check for updates for all extensions configured except for extensions
380 // marked as keep_if_present.
382 !entry
->HasKey(extensions::ExternalProviderImpl::kKeepIfPresent
)) {
384 std::string external_update_url
;
385 if (entry
->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl
,
386 &external_update_url
)) {
387 update_url
= GURL(external_update_url
);
388 } else if (always_check_updates_
) {
389 update_url
= extension_urls::GetWebstoreUpdateUrl();
391 if (update_url
.is_valid())
392 downloader_
->AddPendingExtension(it
.key(), update_url
, 0);
395 base::DictionaryValue
* cached_entry
= NULL
;
396 if (prefs
->GetDictionary(it
.key(), &cached_entry
)) {
397 std::string crx_path
;
399 cached_entry
->GetString(
400 extensions::ExternalProviderImpl::kExternalCrx
, &crx_path
) ||
401 cached_entry
->HasKey(
402 extensions::ExternalProviderImpl::kKeepIfPresent
)) {
403 scoped_ptr
<base::Value
> value
;
404 prefs
->Remove(it
.key(), &value
);
405 cached_extensions_
->Set(it
.key(), value
.release());
410 downloader_
->StartAllPending();
412 VLOG(1) << "Updated ExternalCache, there are "
413 << cached_extensions_
->size() << " extensions cached";
415 UpdateExtensionLoader();
419 void ExternalCache::BlockingInstallCacheEntry(
420 base::WeakPtr
<ExternalCache
> external_cache
,
421 const std::string
& app_cache_dir
,
422 const std::string
& id
,
423 const base::FilePath
& path
,
424 const std::string
& version
) {
425 Version
version_validator(version
);
426 if (!version_validator
.IsValid()) {
427 LOG(ERROR
) << "ExternalCache downloaded extension " << id
<< " but got bad "
428 << "version: " << version
;
429 base::DeleteFile(path
, true /* recursive */);
433 std::string basename
= id
+ "-" + version
+ kCRXFileExtension
;
434 base::FilePath
cache_dir(app_cache_dir
);
435 base::FilePath cached_crx_path
= cache_dir
.Append(basename
);
437 if (base::PathExists(cached_crx_path
)) {
438 LOG(WARNING
) << "AppPack downloaded a crx whose filename will overwrite "
439 << "an existing cached crx.";
440 base::DeleteFile(cached_crx_path
, true /* recursive */);
443 if (!base::DirectoryExists(cache_dir
)) {
444 LOG(ERROR
) << "AppPack cache directory does not exist, creating now: "
445 << cache_dir
.value();
446 if (!file_util::CreateDirectory(cache_dir
)) {
447 LOG(ERROR
) << "Failed to create the AppPack cache dir!";
448 base::DeleteFile(path
, true /* recursive */);
453 if (!base::Move(path
, cached_crx_path
)) {
454 LOG(ERROR
) << "Failed to move AppPack crx from " << path
.value()
455 << " to " << cached_crx_path
.value();
456 base::DeleteFile(path
, true /* recursive */);
460 content::BrowserThread::PostTask(content::BrowserThread::UI
, FROM_HERE
,
461 base::Bind(&ExternalCache::OnCacheEntryInstalled
,
464 cached_crx_path
.value(),
465 std::string(version
)));
468 void ExternalCache::OnCacheEntryInstalled(const std::string
& id
,
469 const std::string
& path
,
470 const std::string
& version
) {
471 VLOG(1) << "AppPack installed a new extension in the cache: " << path
;
473 base::DictionaryValue
* entry
= NULL
;
474 std::string update_url
;
475 if (!extensions_
->GetDictionary(id
, &entry
)) {
476 LOG(ERROR
) << "ExternalCache cannot find entry for extension " << id
;
480 // Copy entry to don't modify it inside extensions_.
481 entry
= entry
->DeepCopy();
482 entry
->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl
, NULL
);
483 entry
->SetString(extensions::ExternalProviderImpl::kExternalVersion
, version
);
484 entry
->SetString(extensions::ExternalProviderImpl::kExternalCrx
, path
);
485 if (extension_urls::IsWebstoreUpdateUrl(GURL(update_url
)))
486 entry
->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore
, true);
488 cached_extensions_
->Set(id
, entry
);
489 UpdateExtensionLoader();
492 void ExternalCache::PostBlockingTask(const tracked_objects::Location
& location
,
493 const base::Closure
& task
) {
494 content::BrowserThread::GetBlockingPool()->
495 PostSequencedWorkerTaskWithShutdownBehavior(
496 worker_pool_token_
, location
, task
,
497 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
500 } // namespace chromeos