Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / updater / local_extension_cache.cc
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.
4
5 #include "chrome/browser/extensions/updater/local_extension_cache.h"
6
7 #include "base/bind.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"
15
16 namespace extensions {
17 namespace {
18
19 // File name extension for CRX files (not case sensitive).
20 const char kCRXFileExtension[] = ".crx";
21
22 // Delay between checks for flag file presence when waiting for the cache to
23 // become ready.
24 const int64_t kCacheStatusPollingDelayMs = 1000;
25
26 }  // namespace
27
28 const char LocalExtensionCache::kCacheReadyFlagFileName[] = ".initialized";
29
30 LocalExtensionCache::LocalExtensionCache(
31     const base::FilePath& cache_dir,
32     uint64 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)) {
43 }
44
45 LocalExtensionCache::~LocalExtensionCache() {
46   if (state_ == kReady)
47     CleanUp();
48 }
49
50 void LocalExtensionCache::Init(bool wait_for_cache_initialization,
51                                const base::Closure& callback) {
52   DCHECK_EQ(state_, kUninitialized);
53
54   state_ = kWaitInitialization;
55   if (wait_for_cache_initialization)
56     CheckCacheStatus(callback);
57   else
58     CheckCacheContents(callback);
59 }
60
61 void LocalExtensionCache::Shutdown(const base::Closure& callback) {
62   DCHECK_NE(state_, kShutdown);
63   if (state_ == kReady)
64     CleanUp();
65   state_ = kShutdown;
66   backend_task_runner_->PostTaskAndReply(FROM_HERE,
67       base::Bind(&base::DoNothing), callback);
68 }
69
70 bool LocalExtensionCache::GetExtension(const std::string& id,
71                                        base::FilePath* file_path,
72                                        std::string* version) {
73   if (state_ != kReady)
74     return false;
75
76   CacheMap::iterator it = cached_extensions_.find(id);
77   if (it == cached_extensions_.end())
78     return false;
79
80   if (file_path) {
81     *file_path = it->second.file_path;
82
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;
89   }
90
91   if (version)
92     *version = it->second.version;
93
94   return true;
95 }
96
97 void LocalExtensionCache::PutExtension(const std::string& id,
98                                        const base::FilePath& file_path,
99                                        const std::string& version,
100                                        const PutExtensionCallback& callback) {
101   if (state_ != kReady) {
102     callback.Run(file_path, true);
103     return;
104   }
105
106   Version version_validator(version);
107   if (!version_validator.IsValid()) {
108     LOG(ERROR) << "Extension " << id << " has bad version " << version;
109     callback.Run(file_path, true);
110     return;
111   }
112
113   CacheMap::iterator it = cached_extensions_.find(id);
114   if (it != cached_extensions_.end()) {
115     Version new_version(version);
116     Version prev_version(it->second.version);
117     if (new_version.CompareTo(prev_version) <= 0) {
118       LOG(WARNING) << "Cache contains newer or the same version "
119                    << prev_version.GetString() << " for extension "
120                    << id << " version " << version;
121       callback.Run(file_path, true);
122       return;
123     }
124   }
125
126   backend_task_runner_->PostTask(
127       FROM_HERE,
128       base::Bind(&LocalExtensionCache::BackendInstallCacheEntry,
129                   weak_ptr_factory_.GetWeakPtr(),
130                   cache_dir_,
131                   id,
132                   file_path,
133                   version,
134                   callback));
135 }
136
137 bool LocalExtensionCache::RemoveExtension(const std::string& id) {
138   if (state_ != kReady)
139     return false;
140
141   CacheMap::iterator it = cached_extensions_.find(id);
142   if (it == cached_extensions_.end())
143     return false;
144
145   backend_task_runner_->PostTask(
146       FROM_HERE,
147       base::Bind(&LocalExtensionCache::BackendRemoveCacheEntry,
148                  it->second.file_path));
149
150   cached_extensions_.erase(it);
151   return true;
152 }
153
154 bool LocalExtensionCache::GetStatistics(uint64* cache_size,
155                                         size_t* extensions_count) {
156   if (state_ != kReady)
157     return false;
158
159   *cache_size = 0;
160   for (CacheMap::iterator it = cached_extensions_.begin();
161        it != cached_extensions_.end(); ++it) {
162     *cache_size += it->second.size;
163   }
164   *extensions_count = cached_extensions_.size();
165
166   return true;
167 }
168
169 void LocalExtensionCache::SetCacheStatusPollingDelayForTests(
170     const base::TimeDelta& delay) {
171   cache_status_polling_delay_ = delay;
172 }
173
174 void LocalExtensionCache::CheckCacheStatus(const base::Closure& callback) {
175   if (state_ == kShutdown) {
176     callback.Run();
177     return;
178   }
179
180   backend_task_runner_->PostTask(
181       FROM_HERE,
182       base::Bind(&LocalExtensionCache::BackendCheckCacheStatus,
183                   weak_ptr_factory_.GetWeakPtr(),
184                   cache_dir_,
185                   callback));
186 }
187
188 // static
189 void LocalExtensionCache::BackendCheckCacheStatus(
190     base::WeakPtr<LocalExtensionCache> local_cache,
191     const base::FilePath& cache_dir,
192     const base::Closure& callback) {
193   content::BrowserThread::PostTask(
194       content::BrowserThread::UI,
195       FROM_HERE,
196       base::Bind(&LocalExtensionCache::OnCacheStatusChecked,
197           local_cache,
198           base::PathExists(cache_dir.AppendASCII(kCacheReadyFlagFileName)),
199           callback));
200 }
201
202 void LocalExtensionCache::OnCacheStatusChecked(bool ready,
203                                                const base::Closure& callback) {
204   if (state_ == kShutdown) {
205     callback.Run();
206     return;
207   }
208
209   if (ready) {
210     CheckCacheContents(callback);
211   } else {
212     content::BrowserThread::PostDelayedTask(
213         content::BrowserThread::UI,
214         FROM_HERE,
215         base::Bind(&LocalExtensionCache::CheckCacheStatus,
216                    weak_ptr_factory_.GetWeakPtr(),
217                    callback),
218         cache_status_polling_delay_);
219   }
220 }
221
222 void LocalExtensionCache::CheckCacheContents(const base::Closure& callback) {
223   DCHECK_EQ(state_, kWaitInitialization);
224   backend_task_runner_->PostTask(
225       FROM_HERE,
226       base::Bind(&LocalExtensionCache::BackendCheckCacheContents,
227                  weak_ptr_factory_.GetWeakPtr(),
228                  cache_dir_,
229                  callback));
230 }
231
232 // static
233 void LocalExtensionCache::BackendCheckCacheContents(
234     base::WeakPtr<LocalExtensionCache> local_cache,
235     const base::FilePath& cache_dir,
236     const base::Closure& callback) {
237   scoped_ptr<CacheMap> cache_content(new CacheMap);
238   BackendCheckCacheContentsInternal(cache_dir, cache_content.get());
239   content::BrowserThread::PostTask(
240       content::BrowserThread::UI,
241       FROM_HERE,
242       base::Bind(&LocalExtensionCache::OnCacheContentsChecked,
243                  local_cache,
244                  base::Passed(&cache_content),
245                  callback));
246 }
247
248 // static
249 void LocalExtensionCache::BackendCheckCacheContentsInternal(
250     const base::FilePath& cache_dir,
251     CacheMap* cache_content) {
252   // Start by verifying that the cache_dir exists.
253   if (!base::DirectoryExists(cache_dir)) {
254     // Create it now.
255     if (!base::CreateDirectory(cache_dir)) {
256       LOG(ERROR) << "Failed to create cache directory at "
257                  << cache_dir.value();
258     }
259
260     // Nothing else to do. Cache is empty.
261     return;
262   }
263
264   // Enumerate all the files in the cache |cache_dir|, including directories
265   // and symlinks. Each unrecognized file will be erased.
266   int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES;
267   base::FileEnumerator enumerator(cache_dir, false /* recursive */, types);
268   for (base::FilePath path = enumerator.Next();
269        !path.empty(); path = enumerator.Next()) {
270     base::FileEnumerator::FileInfo info = enumerator.GetInfo();
271     std::string basename = path.BaseName().value();
272
273     if (info.IsDirectory() || base::IsLink(info.GetName())) {
274       LOG(ERROR) << "Erasing bad file in cache directory: " << basename;
275       base::DeleteFile(path, true /* recursive */);
276       continue;
277     }
278
279     // Skip flag file that indicates that cache is ready.
280     if (basename == kCacheReadyFlagFileName)
281       continue;
282
283     // crx files in the cache are named <extension-id>-<version>.crx.
284     std::string id;
285     std::string version;
286     if (EndsWith(basename, kCRXFileExtension, false /* case-sensitive */)) {
287       size_t n = basename.find('-');
288       if (n != std::string::npos && n + 1 < basename.size() - 4) {
289         id = basename.substr(0, n);
290         // Size of |version| = total size - "<id>" - "-" - ".crx"
291         version = basename.substr(n + 1, basename.size() - 5 - id.size());
292       }
293     }
294
295     // Enforce a lower-case id.
296     id = StringToLowerASCII(id);
297     if (!extensions::Extension::IdIsValid(id)) {
298       LOG(ERROR) << "Bad extension id in cache: " << id;
299       id.clear();
300     }
301
302     if (!Version(version).IsValid()) {
303       LOG(ERROR) << "Bad extension version in cache: " << version;
304       version.clear();
305     }
306
307     if (id.empty() || version.empty()) {
308       LOG(ERROR) << "Invalid file in cache, erasing: " << basename;
309       base::DeleteFile(path, true /* recursive */);
310       continue;
311     }
312
313     VLOG(1) << "Found cached version " << version
314             << " for extension id " << id;
315
316     CacheMap::iterator it = cache_content->find(id);
317     if (it != cache_content->end()) {
318       // |cache_content| already has version for this ID. Removed older one.
319       Version curr_version(version);
320       Version prev_version(it->second.version);
321       if (prev_version.CompareTo(curr_version) <= 0) {
322         base::DeleteFile(base::FilePath(it->second.file_path),
323                          true /* recursive */);
324         cache_content->erase(id);
325         VLOG(1) << "Remove older version " << it->second.version
326                 << " for extension id " << id;
327       } else {
328         base::DeleteFile(path, true /* recursive */);
329         VLOG(1) << "Remove older version " << version
330                 << " for extension id " << id;
331         continue;
332       }
333     }
334
335     cache_content->insert(std::make_pair(id, CacheItemInfo(
336         version, info.GetLastModifiedTime(), info.GetSize(), path)));
337   }
338 }
339
340 void LocalExtensionCache::OnCacheContentsChecked(
341     scoped_ptr<CacheMap> cache_content,
342     const base::Closure& callback) {
343   cache_content->swap(cached_extensions_);
344   state_ = kReady;
345   callback.Run();
346 }
347
348 // static
349 void LocalExtensionCache::BackendMarkFileUsed(const base::FilePath& file_path,
350                                               const base::Time& time) {
351   base::TouchFile(file_path, time, time);
352 }
353
354 // static
355 void LocalExtensionCache::BackendInstallCacheEntry(
356     base::WeakPtr<LocalExtensionCache> local_cache,
357     const base::FilePath& cache_dir,
358     const std::string& id,
359     const base::FilePath& file_path,
360     const std::string& version,
361     const PutExtensionCallback& callback) {
362   std::string basename = id + "-" + version + kCRXFileExtension;
363   base::FilePath cached_crx_path = cache_dir.AppendASCII(basename);
364
365   bool was_error = false;
366   if (base::PathExists(cached_crx_path)) {
367     LOG(ERROR) << "File already exists " << file_path.value();
368     cached_crx_path = file_path;
369     was_error = true;
370   }
371
372   base::File::Info info;
373   if (!was_error) {
374     if (!base::Move(file_path, cached_crx_path)) {
375       LOG(ERROR) << "Failed to copy from " << file_path.value()
376                  << " to " << cached_crx_path.value();
377       cached_crx_path = file_path;
378       was_error = true;
379     } else {
380       was_error = !base::GetFileInfo(cached_crx_path, &info);
381       VLOG(1) << "Cache entry installed for extension id " << id
382               << " version " << version;
383     }
384   }
385
386   content::BrowserThread::PostTask(
387       content::BrowserThread::UI,
388       FROM_HERE,
389       base::Bind(&LocalExtensionCache::OnCacheEntryInstalled,
390                  local_cache,
391                  id,
392                  CacheItemInfo(version, info.last_modified,
393                                info.size, cached_crx_path),
394                  was_error,
395                  callback));
396 }
397
398 void LocalExtensionCache::OnCacheEntryInstalled(
399     const std::string& id,
400     const CacheItemInfo& info,
401     bool was_error,
402     const PutExtensionCallback& callback) {
403   if (state_ == kShutdown || was_error) {
404     callback.Run(info.file_path, true);
405     return;
406   }
407
408   CacheMap::iterator it = cached_extensions_.find(id);
409   if (it != cached_extensions_.end()) {
410     Version new_version(info.version);
411     Version prev_version(it->second.version);
412     if (new_version.CompareTo(prev_version) <= 0) {
413       DCHECK(0) << "Cache contains newer or the same version";
414       callback.Run(info.file_path, true);
415       return;
416     }
417   }
418   it = cached_extensions_.insert(std::make_pair(id, info)).first;
419   // Time from file system can have lower precision so use precise "now".
420   it->second.last_used = base::Time::Now();
421
422   callback.Run(info.file_path, false);
423 }
424
425 // static
426 void LocalExtensionCache::BackendRemoveCacheEntry(
427     const base::FilePath& file_path) {
428   base::DeleteFile(file_path, true /* recursive */);
429   VLOG(1) << "Removed cached file " << file_path.value();
430 }
431
432 // static
433 bool LocalExtensionCache::CompareCacheItemsAge(const CacheMap::iterator& lhs,
434                                                const CacheMap::iterator& rhs) {
435   return lhs->second.last_used < rhs->second.last_used;
436 }
437
438 void LocalExtensionCache::CleanUp() {
439   DCHECK_EQ(state_, kReady);
440
441   std::vector<CacheMap::iterator> items;
442   items.reserve(cached_extensions_.size());
443   uint64_t total_size = 0;
444   for (CacheMap::iterator it = cached_extensions_.begin();
445        it != cached_extensions_.end(); ++it) {
446     items.push_back(it);
447     total_size += it->second.size;
448   }
449   std::sort(items.begin(), items.end(), CompareCacheItemsAge);
450
451   for (std::vector<CacheMap::iterator>::iterator it = items.begin();
452        it != items.end(); ++it) {
453     if ((*it)->second.last_used < min_cache_age_ ||
454         (max_cache_size_ && total_size > max_cache_size_)) {
455       total_size -= (*it)->second.size;
456       VLOG(1) << "Clean up cached extension id " << (*it)->first;
457       RemoveExtension((*it)->first);
458     }
459   }
460 }
461
462 LocalExtensionCache::CacheItemInfo::CacheItemInfo(
463     const std::string& version,
464     const base::Time& last_used,
465     uint64 size,
466     const base::FilePath& file_path)
467     : version(version), last_used(last_used), size(size), file_path(file_path) {
468 }
469
470 }  // namespace extensions