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