1 // Copyright 2014 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/extensions/updater/local_extension_cache.h"
8 #include "base/file_util.h"
9 #include "base/files/file_enumerator.h"
10 #include "base/sequenced_task_runner.h"
11 #include "base/strings/string_util.h"
12 #include "base/version.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "extensions/common/extension.h"
16 namespace extensions
{
19 // File name extension for CRX files (not case sensitive).
20 const char kCRXFileExtension
[] = ".crx";
22 // Delay between checks for flag file presence when waiting for the cache to
24 const int64_t kCacheStatusPollingDelayMs
= 1000;
28 const char LocalExtensionCache::kCacheReadyFlagFileName
[] = ".initialized";
30 LocalExtensionCache::LocalExtensionCache(
31 const base::FilePath
& cache_dir
,
32 size_t max_cache_size
,
33 const base::TimeDelta
& max_cache_age
,
34 const scoped_refptr
<base::SequencedTaskRunner
>& backend_task_runner
)
35 : cache_dir_(cache_dir
),
36 max_cache_size_(max_cache_size
),
37 min_cache_age_(base::Time::Now() - max_cache_age
),
38 backend_task_runner_(backend_task_runner
),
39 state_(kUninitialized
),
40 weak_ptr_factory_(this),
41 cache_status_polling_delay_(
42 base::TimeDelta::FromMilliseconds(kCacheStatusPollingDelayMs
)) {
45 LocalExtensionCache::~LocalExtensionCache() {
50 void LocalExtensionCache::Init(bool wait_for_cache_initialization
,
51 const base::Closure
& callback
) {
52 DCHECK_EQ(state_
, kUninitialized
);
54 state_
= kWaitInitialization
;
55 if (wait_for_cache_initialization
)
56 CheckCacheStatus(callback
);
58 CheckCacheContents(callback
);
61 void LocalExtensionCache::Shutdown(const base::Closure
& callback
) {
62 DCHECK_NE(state_
, kShutdown
);
66 backend_task_runner_
->PostTaskAndReply(FROM_HERE
,
67 base::Bind(&base::DoNothing
), callback
);
70 bool LocalExtensionCache::GetExtension(const std::string
& id
,
71 base::FilePath
* file_path
,
72 std::string
* version
) {
76 CacheMap::iterator it
= cached_extensions_
.find(id
);
77 if (it
== cached_extensions_
.end())
81 *file_path
= it
->second
.file_path
;
83 // If caller is not interesting in file_path, extension is not used.
84 base::Time now
= base::Time::Now();
85 backend_task_runner_
->PostTask(FROM_HERE
,
86 base::Bind(&LocalExtensionCache::BackendMarkFileUsed
,
87 it
->second
.file_path
, now
));
88 it
->second
.last_used
= now
;
92 *version
= it
->second
.version
;
97 void LocalExtensionCache::PutExtension(const std::string
& id
,
98 const base::FilePath
& file_path
,
99 const std::string
& version
,
100 const base::Closure
& callback
) {
101 if (state_
!= kReady
)
104 Version
version_validator(version
);
105 if (!version_validator
.IsValid()) {
106 LOG(ERROR
) << "Extension " << id
<< " has bad version " << version
;
110 CacheMap::iterator it
= cached_extensions_
.find(id
);
111 if (it
!= cached_extensions_
.end()) {
112 Version
new_version(version
);
113 Version
prev_version(it
->second
.version
);
114 if (new_version
.CompareTo(prev_version
) <= 0) {
115 LOG(WARNING
) << "Cache contains newer or the same version "
116 << prev_version
.GetString() << " for extension "
117 << id
<< " version " << version
;
122 backend_task_runner_
->PostTask(
124 base::Bind(&LocalExtensionCache::BackendInstallCacheEntry
,
125 weak_ptr_factory_
.GetWeakPtr(),
133 bool LocalExtensionCache::RemoveExtension(const std::string
& id
) {
134 if (state_
!= kReady
)
137 CacheMap::iterator it
= cached_extensions_
.find(id
);
138 if (it
== cached_extensions_
.end())
141 backend_task_runner_
->PostTask(
143 base::Bind(&LocalExtensionCache::BackendRemoveCacheEntry
,
144 it
->second
.file_path
));
146 cached_extensions_
.erase(it
);
150 void LocalExtensionCache::SetCacheStatusPollingDelayForTests(
151 const base::TimeDelta
& delay
) {
152 cache_status_polling_delay_
= delay
;
155 void LocalExtensionCache::CheckCacheStatus(const base::Closure
& callback
) {
156 if (state_
== kShutdown
) {
161 backend_task_runner_
->PostTask(
163 base::Bind(&LocalExtensionCache::BackendCheckCacheStatus
,
164 weak_ptr_factory_
.GetWeakPtr(),
170 void LocalExtensionCache::BackendCheckCacheStatus(
171 base::WeakPtr
<LocalExtensionCache
> local_cache
,
172 const base::FilePath
& cache_dir
,
173 const base::Closure
& callback
) {
174 content::BrowserThread::PostTask(
175 content::BrowserThread::UI
,
177 base::Bind(&LocalExtensionCache::OnCacheStatusChecked
,
179 base::PathExists(cache_dir
.AppendASCII(kCacheReadyFlagFileName
)),
183 void LocalExtensionCache::OnCacheStatusChecked(bool ready
,
184 const base::Closure
& callback
) {
185 if (state_
== kShutdown
) {
191 CheckCacheContents(callback
);
193 content::BrowserThread::PostDelayedTask(
194 content::BrowserThread::UI
,
196 base::Bind(&LocalExtensionCache::CheckCacheStatus
,
197 weak_ptr_factory_
.GetWeakPtr(),
199 cache_status_polling_delay_
);
203 void LocalExtensionCache::CheckCacheContents(const base::Closure
& callback
) {
204 DCHECK_EQ(state_
, kWaitInitialization
);
205 backend_task_runner_
->PostTask(
207 base::Bind(&LocalExtensionCache::BackendCheckCacheContents
,
208 weak_ptr_factory_
.GetWeakPtr(),
214 void LocalExtensionCache::BackendCheckCacheContents(
215 base::WeakPtr
<LocalExtensionCache
> local_cache
,
216 const base::FilePath
& cache_dir
,
217 const base::Closure
& callback
) {
218 scoped_ptr
<CacheMap
> cache_content(new CacheMap
);
219 BackendCheckCacheContentsInternal(cache_dir
, cache_content
.get());
220 content::BrowserThread::PostTask(
221 content::BrowserThread::UI
,
223 base::Bind(&LocalExtensionCache::OnCacheContentsChecked
,
225 base::Passed(&cache_content
),
230 void LocalExtensionCache::BackendCheckCacheContentsInternal(
231 const base::FilePath
& cache_dir
,
232 CacheMap
* cache_content
) {
233 // Start by verifying that the cache_dir exists.
234 if (!base::DirectoryExists(cache_dir
)) {
236 if (!base::CreateDirectory(cache_dir
)) {
237 LOG(ERROR
) << "Failed to create cache directory at "
238 << cache_dir
.value();
241 // Nothing else to do. Cache is empty.
245 // Enumerate all the files in the cache |cache_dir|, including directories
246 // and symlinks. Each unrecognized file will be erased.
247 int types
= base::FileEnumerator::FILES
| base::FileEnumerator::DIRECTORIES
;
248 base::FileEnumerator
enumerator(cache_dir
, false /* recursive */, types
);
249 for (base::FilePath path
= enumerator
.Next();
250 !path
.empty(); path
= enumerator
.Next()) {
251 base::FileEnumerator::FileInfo info
= enumerator
.GetInfo();
252 std::string basename
= path
.BaseName().value();
254 if (info
.IsDirectory() || base::IsLink(info
.GetName())) {
255 LOG(ERROR
) << "Erasing bad file in cache directory: " << basename
;
256 base::DeleteFile(path
, true /* recursive */);
260 // Skip flag file that indicates that cache is ready.
261 if (basename
== kCacheReadyFlagFileName
)
264 // crx files in the cache are named <extension-id>-<version>.crx.
267 if (EndsWith(basename
, kCRXFileExtension
, false /* case-sensitive */)) {
268 size_t n
= basename
.find('-');
269 if (n
!= std::string::npos
&& n
+ 1 < basename
.size() - 4) {
270 id
= basename
.substr(0, n
);
271 // Size of |version| = total size - "<id>" - "-" - ".crx"
272 version
= basename
.substr(n
+ 1, basename
.size() - 5 - id
.size());
276 // Enforce a lower-case id.
277 id
= StringToLowerASCII(id
);
278 if (!extensions::Extension::IdIsValid(id
)) {
279 LOG(ERROR
) << "Bad extension id in cache: " << id
;
283 if (!Version(version
).IsValid()) {
284 LOG(ERROR
) << "Bad extension version in cache: " << version
;
288 if (id
.empty() || version
.empty()) {
289 LOG(ERROR
) << "Invalid file in cache, erasing: " << basename
;
290 base::DeleteFile(path
, true /* recursive */);
294 VLOG(1) << "Found cached version " << version
295 << " for extension id " << id
;
297 CacheMap::iterator it
= cache_content
->find(id
);
298 if (it
!= cache_content
->end()) {
299 // |cache_content| already has version for this ID. Removed older one.
300 Version
curr_version(version
);
301 Version
prev_version(it
->second
.version
);
302 if (prev_version
.CompareTo(curr_version
) <= 0) {
303 base::DeleteFile(base::FilePath(it
->second
.file_path
),
304 true /* recursive */);
305 cache_content
->erase(id
);
306 VLOG(1) << "Remove older version " << it
->second
.version
307 << " for extension id " << id
;
309 base::DeleteFile(path
, true /* recursive */);
310 VLOG(1) << "Remove older version " << version
311 << " for extension id " << id
;
316 cache_content
->insert(std::make_pair(id
, CacheItemInfo(
317 version
, info
.GetLastModifiedTime(), info
.GetSize(), path
)));
321 void LocalExtensionCache::OnCacheContentsChecked(
322 scoped_ptr
<CacheMap
> cache_content
,
323 const base::Closure
& callback
) {
324 cache_content
->swap(cached_extensions_
);
330 void LocalExtensionCache::BackendMarkFileUsed(const base::FilePath
& file_path
,
331 const base::Time
& time
) {
332 base::TouchFile(file_path
, time
, time
);
336 void LocalExtensionCache::BackendInstallCacheEntry(
337 base::WeakPtr
<LocalExtensionCache
> local_cache
,
338 const base::FilePath
& cache_dir
,
339 const std::string
& id
,
340 const base::FilePath
& file_path
,
341 const std::string
& version
,
342 const base::Closure
& callback
) {
343 std::string basename
= id
+ "-" + version
+ kCRXFileExtension
;
344 base::FilePath cached_crx_path
= cache_dir
.AppendASCII(basename
);
346 if (base::PathExists(cached_crx_path
)) {
347 LOG(ERROR
) << "File already exists " << file_path
.value();
351 if (!base::CopyFile(file_path
, cached_crx_path
)) {
352 LOG(ERROR
) << "Failed to copy from " << file_path
.value()
353 << " to " << cached_crx_path
.value();
357 base::File::Info info
;
358 if (!base::GetFileInfo(cached_crx_path
, &info
)) {
359 LOG(ERROR
) << "Failed to stat file " << cached_crx_path
.value();
363 content::BrowserThread::PostTask(
364 content::BrowserThread::UI
,
366 base::Bind(&LocalExtensionCache::OnCacheEntryInstalled
,
369 CacheItemInfo(version
, info
.last_modified
,
370 info
.size
, cached_crx_path
),
374 void LocalExtensionCache::OnCacheEntryInstalled(const std::string
& id
,
375 const CacheItemInfo
& info
,
376 const base::Closure
& callback
) {
377 if (state_
== kShutdown
)
380 CacheMap::iterator it
= cached_extensions_
.find(id
);
381 if (it
!= cached_extensions_
.end()) {
382 Version
new_version(info
.version
);
383 Version
prev_version(it
->second
.version
);
384 if (new_version
.CompareTo(prev_version
) <= 0) {
385 // Cache contains newer or the same version.
389 cached_extensions_
.insert(std::make_pair(id
, info
));
394 void LocalExtensionCache::BackendRemoveCacheEntry(
395 const base::FilePath
& file_path
) {
396 base::DeleteFile(file_path
, true /* recursive */);
397 VLOG(1) << "Removed cached file " << file_path
.value();
401 bool LocalExtensionCache::CompareCacheItemsAge(const CacheMap::iterator
& lhs
,
402 const CacheMap::iterator
& rhs
) {
403 return lhs
->second
.last_used
< rhs
->second
.last_used
;
406 void LocalExtensionCache::CleanUp() {
407 DCHECK_EQ(state_
, kReady
);
409 std::vector
<CacheMap::iterator
> items
;
410 items
.reserve(cached_extensions_
.size());
411 size_t total_size
= 0;
412 for (CacheMap::iterator it
= cached_extensions_
.begin();
413 it
!= cached_extensions_
.end(); ++it
) {
415 total_size
+= it
->second
.size
;
417 std::sort(items
.begin(), items
.end(), CompareCacheItemsAge
);
419 for (std::vector
<CacheMap::iterator
>::iterator it
= items
.begin();
420 it
!= items
.end(); ++it
) {
421 if ((*it
)->second
.last_used
< min_cache_age_
||
422 (max_cache_size_
&& total_size
> max_cache_size_
)) {
423 total_size
-= (*it
)->second
.size
;
424 RemoveExtension((*it
)->first
);
429 LocalExtensionCache::CacheItemInfo::CacheItemInfo(
430 const std::string
& version
,
431 const base::Time
& last_used
,
433 const base::FilePath
& file_path
)
434 : version(version
), last_used(last_used
), size(size
), file_path(file_path
) {
437 } // namespace extensions