- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / extensions / external_cache.cc
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.
4
5 #include "chrome/browser/chromeos/extensions/external_cache.h"
6
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/callback.h"
10 #include "base/file_util.h"
11 #include "base/files/file_enumerator.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/chrome_notification_types.h"
18 #include "chrome/browser/extensions/crx_installer.h"
19 #include "chrome/browser/extensions/external_provider_impl.h"
20 #include "chrome/browser/extensions/updater/extension_downloader.h"
21 #include "chrome/common/extensions/extension.h"
22 #include "chrome/common/extensions/extension_constants.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/notification_details.h"
25 #include "content/public/browser/notification_service.h"
26 #include "content/public/browser/notification_source.h"
27
28 namespace chromeos {
29
30 namespace {
31
32 // File name extension for CRX files (not case sensitive).
33 const char kCRXFileExtension[] = ".crx";
34
35 // Delay between checks for flag file presence when waiting for the cache to
36 // become ready.
37 const int64_t kCacheStatusPollingDelayMs = 1000;
38
39 }  // namespace
40
41 const char ExternalCache::kCacheReadyFlagFileName[] = ".initialized";
42
43 ExternalCache::ExternalCache(const base::FilePath& cache_dir,
44                              net::URLRequestContextGetter* request_context,
45                              const scoped_refptr<base::SequencedTaskRunner>&
46                                  backend_task_runner,
47                              Delegate* delegate,
48                              bool always_check_updates,
49                              bool wait_for_cache_initialization)
50     : cache_dir_(cache_dir),
51       request_context_(request_context),
52       delegate_(delegate),
53       shutdown_(false),
54       always_check_updates_(always_check_updates),
55       wait_for_cache_initialization_(wait_for_cache_initialization),
56       cached_extensions_(new base::DictionaryValue()),
57       backend_task_runner_(backend_task_runner),
58       weak_ptr_factory_(this) {
59   notification_registrar_.Add(
60       this,
61       chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
62       content::NotificationService::AllBrowserContextsAndSources());
63 }
64
65 ExternalCache::~ExternalCache() {
66 }
67
68 void ExternalCache::Shutdown(const base::Closure& callback) {
69   DCHECK(!shutdown_);
70   shutdown_ = true;
71   backend_task_runner_->PostTask(FROM_HERE,
72                                  base::Bind(&ExternalCache::BackendShudown,
73                                             callback));
74 }
75
76 void ExternalCache::UpdateExtensionsList(
77     scoped_ptr<base::DictionaryValue> prefs) {
78   if (shutdown_)
79     return;
80
81   extensions_ = prefs.Pass();
82   if (extensions_->empty()) {
83     // Don't check cache and clear it if there are no extensions in the list.
84     // It is important case because login to supervised user shouldn't clear
85     // cache for normal users.
86     // TODO(dpolukhin): introduce reference counting to preserve cache elements
87     // when they are not needed for current user but needed for other users.
88     cached_extensions_->Clear();
89     UpdateExtensionLoader();
90   } else {
91     CheckCache();
92   }
93 }
94
95 void ExternalCache::OnDamagedFileDetected(const base::FilePath& path) {
96   if (shutdown_)
97     return;
98
99   for (base::DictionaryValue::Iterator it(*cached_extensions_.get());
100        !it.IsAtEnd(); it.Advance()) {
101     const base::DictionaryValue* entry = NULL;
102     if (!it.value().GetAsDictionary(&entry)) {
103       NOTREACHED() << "ExternalCache found bad entry with type "
104                    << it.value().GetType();
105       continue;
106     }
107
108     std::string external_crx;
109     if (entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
110                          &external_crx) &&
111         external_crx == path.value()) {
112
113       LOG(ERROR) << "ExternalCache extension at " << path.value()
114                  << " failed to install, deleting it.";
115       cached_extensions_->Remove(it.key(), NULL);
116       UpdateExtensionLoader();
117
118       // The file will be downloaded again on the next restart.
119       if (cache_dir_.IsParent(path)) {
120         backend_task_runner_->PostTask(
121             FROM_HERE,
122             base::Bind(base::IgnoreResult(base::DeleteFile),
123             path,
124             true));
125       }
126
127       // Don't try to DownloadMissingExtensions() from here,
128       // since it can cause a fail/retry loop.
129       return;
130     }
131   }
132   LOG(ERROR) << "ExternalCache cannot find external_crx " << path.value();
133 }
134
135 void ExternalCache::Observe(int type,
136                             const content::NotificationSource& source,
137                             const content::NotificationDetails& details) {
138   if (shutdown_)
139     return;
140
141   switch (type) {
142     case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
143       extensions::CrxInstaller* installer =
144           content::Source<extensions::CrxInstaller>(source).ptr();
145       OnDamagedFileDetected(installer->source_file());
146       break;
147     }
148
149     default:
150       NOTREACHED();
151   }
152 }
153
154 void ExternalCache::OnExtensionDownloadFailed(
155     const std::string& id,
156     extensions::ExtensionDownloaderDelegate::Error error,
157     const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
158     const std::set<int>& request_ids) {
159   if (shutdown_)
160     return;
161
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     }
167   } else {
168     LOG(ERROR) << "ExternalCache failed to download extension " << id
169                << ", error " << error;
170   }
171 }
172
173 void ExternalCache::OnExtensionDownloadFinished(
174     const std::string& id,
175     const base::FilePath& path,
176     const GURL& download_url,
177     const std::string& version,
178     const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
179     const std::set<int>& request_ids) {
180   if (shutdown_)
181     return;
182
183   backend_task_runner_->PostTask(
184       FROM_HERE,
185       base::Bind(&ExternalCache::BackendInstallCacheEntry,
186                  weak_ptr_factory_.GetWeakPtr(),
187                  cache_dir_,
188                  id,
189                  path,
190                  version));
191 }
192
193 bool ExternalCache::IsExtensionPending(const std::string& id) {
194   // Pending means that there is no installed version yet.
195   return extensions_->HasKey(id) && !cached_extensions_->HasKey(id);
196 }
197
198 bool ExternalCache::GetExtensionExistingVersion(const std::string& id,
199                                                 std::string* version) {
200   DictionaryValue* extension_dictionary = NULL;
201   if (cached_extensions_->GetDictionary(id, &extension_dictionary)) {
202     if (extension_dictionary->GetString(
203             extensions::ExternalProviderImpl::kExternalVersion, version)) {
204       return true;
205     }
206     *version = delegate_->GetInstalledExtensionVersion(id);
207     return !version->empty();
208   }
209   return false;
210 }
211
212 void ExternalCache::UpdateExtensionLoader() {
213   if (shutdown_)
214     return;
215
216   VLOG(1) << "Notify ExternalCache delegate about cache update";
217   if (delegate_)
218     delegate_->OnExtensionListsUpdated(cached_extensions_.get());
219 }
220
221 void ExternalCache::CheckCache() {
222   if (wait_for_cache_initialization_) {
223     backend_task_runner_->PostTask(
224         FROM_HERE,
225         base::Bind(&ExternalCache::BackendCheckCacheStatus,
226                    weak_ptr_factory_.GetWeakPtr(),
227                    cache_dir_));
228   } else {
229     CheckCacheContents();
230   }
231 }
232
233 // static
234 void ExternalCache::BackendCheckCacheStatus(
235     base::WeakPtr<ExternalCache> external_cache,
236     const base::FilePath& cache_dir) {
237   content::BrowserThread::PostTask(
238       content::BrowserThread::UI,
239       FROM_HERE,
240       base::Bind(&ExternalCache::OnCacheStatusChecked,
241           external_cache,
242           base::PathExists(cache_dir.AppendASCII(kCacheReadyFlagFileName))));
243 }
244
245 void ExternalCache::OnCacheStatusChecked(bool ready) {
246   if (shutdown_)
247     return;
248
249   if (ready) {
250     CheckCacheContents();
251   } else {
252     content::BrowserThread::PostDelayedTask(
253         content::BrowserThread::UI,
254         FROM_HERE,
255         base::Bind(&ExternalCache::CheckCache,
256                    weak_ptr_factory_.GetWeakPtr()),
257         base::TimeDelta::FromMilliseconds(kCacheStatusPollingDelayMs));
258   }
259 }
260
261 void ExternalCache::CheckCacheContents() {
262   if (shutdown_)
263     return;
264
265   backend_task_runner_->PostTask(
266       FROM_HERE,
267       base::Bind(&ExternalCache::BackendCheckCacheContents,
268                  weak_ptr_factory_.GetWeakPtr(),
269                  cache_dir_,
270                  base::Passed(make_scoped_ptr(extensions_->DeepCopy()))));
271 }
272
273 // static
274 void ExternalCache::BackendCheckCacheContents(
275     base::WeakPtr<ExternalCache> external_cache,
276     const base::FilePath& cache_dir,
277     scoped_ptr<base::DictionaryValue> prefs) {
278   BackendCheckCacheContentsInternal(cache_dir, prefs.get());
279   content::BrowserThread::PostTask(content::BrowserThread::UI,
280                                    FROM_HERE,
281                                    base::Bind(&ExternalCache::OnCacheUpdated,
282                                               external_cache,
283                                               base::Passed(&prefs)));
284 }
285
286 // static
287 void ExternalCache::BackendCheckCacheContentsInternal(
288     const base::FilePath& cache_dir,
289     base::DictionaryValue* prefs) {
290   // Start by verifying that the cache_dir exists.
291   if (!base::DirectoryExists(cache_dir)) {
292     // Create it now.
293     if (!file_util::CreateDirectory(cache_dir)) {
294       LOG(ERROR) << "Failed to create ExternalCache directory at "
295                  << cache_dir.value();
296     }
297
298     // Nothing else to do. Cache won't be used.
299     return;
300   }
301
302   // Enumerate all the files in the cache |cache_dir|, including directories
303   // and symlinks. Each unrecognized file will be erased.
304   int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES |
305       base::FileEnumerator::SHOW_SYM_LINKS;
306   base::FileEnumerator enumerator(cache_dir, false /* recursive */, types);
307   for (base::FilePath path = enumerator.Next();
308        !path.empty(); path = enumerator.Next()) {
309     base::FileEnumerator::FileInfo info = enumerator.GetInfo();
310     std::string basename = path.BaseName().value();
311
312     if (info.IsDirectory() || file_util::IsLink(info.GetName())) {
313       LOG(ERROR) << "Erasing bad file in ExternalCache directory: " << basename;
314       base::DeleteFile(path, true /* recursive */);
315       continue;
316     }
317
318     // Skip flag file that indicates that cache is ready.
319     if (basename == kCacheReadyFlagFileName)
320       continue;
321
322     // crx files in the cache are named <extension-id>-<version>.crx.
323     std::string id;
324     std::string version;
325     if (EndsWith(basename, kCRXFileExtension, false /* case-sensitive */)) {
326       size_t n = basename.find('-');
327       if (n != std::string::npos && n + 1 < basename.size() - 4) {
328         id = basename.substr(0, n);
329         // Size of |version| = total size - "<id>" - "-" - ".crx"
330         version = basename.substr(n + 1, basename.size() - 5 - id.size());
331       }
332     }
333
334     base::DictionaryValue* entry = NULL;
335     if (!extensions::Extension::IdIsValid(id)) {
336       LOG(ERROR) << "Bad extension id in ExternalCache: " << id;
337       id.clear();
338     } else if (!prefs->GetDictionary(id, &entry)) {
339       LOG(WARNING) << basename << " is in the cache but is not configured by "
340                    << "the ExternalCache source, and will be erased.";
341       id.clear();
342     }
343
344     if (!Version(version).IsValid()) {
345       LOG(ERROR) << "Bad extension version in ExternalCache: " << version;
346       version.clear();
347     }
348
349     if (id.empty() || version.empty()) {
350       LOG(ERROR) << "Invalid file in ExternalCache, erasing: " << basename;
351       base::DeleteFile(path, true /* recursive */);
352       continue;
353     }
354
355     // Enforce a lower-case id.
356     id = StringToLowerASCII(id);
357
358     std::string update_url;
359     std::string prev_version_string;
360     std::string prev_crx;
361     if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
362                          &update_url)) {
363       VLOG(1) << "ExternalCache found cached version " << version
364               << " for extension id: " << id;
365       entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
366       entry->SetString(extensions::ExternalProviderImpl::kExternalVersion,
367                        version);
368       entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
369                        path.value());
370       if (extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
371         entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore,
372                           true);
373       }
374     } else if (
375         entry->GetString(extensions::ExternalProviderImpl::kExternalVersion,
376                          &prev_version_string) &&
377         entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
378                          &prev_crx)) {
379       Version prev_version(prev_version_string);
380       Version curr_version(version);
381       DCHECK(prev_version.IsValid());
382       DCHECK(curr_version.IsValid());
383       if (prev_version.CompareTo(curr_version) <= 0) {
384         VLOG(1) << "ExternalCache found old cached version "
385                 << prev_version_string << " path: " << prev_crx;
386         base::FilePath prev_crx_file(prev_crx);
387         if (cache_dir.IsParent(prev_crx_file)) {
388           // Only delete old cached files under cache_dir_ folder.
389           base::DeleteFile(base::FilePath(prev_crx), true /* recursive */);
390         }
391         entry->SetString(extensions::ExternalProviderImpl::kExternalVersion,
392                          version);
393         entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
394                          path.value());
395       } else {
396         VLOG(1) << "ExternalCache found old cached version "
397                 << version << " path: " << path.value();
398         base::DeleteFile(path, true /* recursive */);
399       }
400     } else {
401       NOTREACHED() << "ExternalCache found bad entry for extension id: " << id
402                    << " file path: " << path.value();
403     }
404   }
405 }
406
407 void ExternalCache::OnCacheUpdated(scoped_ptr<base::DictionaryValue> prefs) {
408   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
409   if (shutdown_)
410     return;
411
412   // If request_context_ is missing we can't download anything.
413   if (!downloader_ && request_context_) {
414     downloader_.reset(
415         new extensions::ExtensionDownloader(this, request_context_));
416   }
417
418   cached_extensions_->Clear();
419   for (base::DictionaryValue::Iterator it(*extensions_.get());
420        !it.IsAtEnd(); it.Advance()) {
421     const base::DictionaryValue* entry = NULL;
422     if (!it.value().GetAsDictionary(&entry)) {
423       LOG(ERROR) << "ExternalCache found bad entry with type "
424                  << it.value().GetType();
425       continue;
426     }
427
428     bool keep_if_present =
429         entry->HasKey(extensions::ExternalProviderImpl::kKeepIfPresent);
430     // Check for updates for all extensions configured except for extensions
431     // marked as keep_if_present.
432     if (downloader_ && !keep_if_present) {
433       GURL update_url;
434       std::string external_update_url;
435       if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
436                            &external_update_url)) {
437         update_url = GURL(external_update_url);
438       } else if (always_check_updates_) {
439         update_url = extension_urls::GetWebstoreUpdateUrl();
440       }
441       if (update_url.is_valid())
442         downloader_->AddPendingExtension(it.key(), update_url, 0);
443     }
444
445     base::DictionaryValue* cached_entry = NULL;
446     if (prefs->GetDictionary(it.key(), &cached_entry)) {
447       bool has_external_crx = cached_entry->HasKey(
448           extensions::ExternalProviderImpl::kExternalCrx);
449       bool is_already_installed =
450           !delegate_->GetInstalledExtensionVersion(it.key()).empty();
451       if (!downloader_ || keep_if_present || has_external_crx ||
452           is_already_installed) {
453         scoped_ptr<base::Value> value;
454         prefs->Remove(it.key(), &value);
455         cached_extensions_->Set(it.key(), value.release());
456       }
457     }
458   }
459   if (downloader_)
460     downloader_->StartAllPending();
461
462   VLOG(1) << "Updated ExternalCache, there are "
463           << cached_extensions_->size() << " extensions cached";
464
465   UpdateExtensionLoader();
466 }
467
468 // static
469 void ExternalCache::BackendInstallCacheEntry(
470     base::WeakPtr<ExternalCache> external_cache,
471     const base::FilePath& cache_dir,
472     const std::string& id,
473     const base::FilePath& path,
474     const std::string& version) {
475   Version version_validator(version);
476   if (!version_validator.IsValid()) {
477     LOG(ERROR) << "ExternalCache downloaded extension " << id << " but got bad "
478                << "version: " << version;
479     base::DeleteFile(path, true /* recursive */);
480     return;
481   }
482
483   std::string basename = id + "-" + version + kCRXFileExtension;
484   base::FilePath cached_crx_path = cache_dir.Append(basename);
485
486   if (base::PathExists(cached_crx_path)) {
487     LOG(WARNING) << "AppPack downloaded a crx whose filename will overwrite "
488                  << "an existing cached crx.";
489     base::DeleteFile(cached_crx_path, true /* recursive */);
490   }
491
492   if (!base::DirectoryExists(cache_dir)) {
493     LOG(ERROR) << "AppPack cache directory does not exist, creating now: "
494                << cache_dir.value();
495     if (!file_util::CreateDirectory(cache_dir)) {
496       LOG(ERROR) << "Failed to create the AppPack cache dir!";
497       base::DeleteFile(path, true /* recursive */);
498       return;
499     }
500   }
501
502   if (!base::Move(path, cached_crx_path)) {
503     LOG(ERROR) << "Failed to move AppPack crx from " << path.value()
504                << " to " << cached_crx_path.value();
505     base::DeleteFile(path, true /* recursive */);
506     return;
507   }
508
509   content::BrowserThread::PostTask(
510       content::BrowserThread::UI,
511       FROM_HERE,
512       base::Bind(&ExternalCache::OnCacheEntryInstalled,
513                  external_cache,
514                  id,
515                  cached_crx_path,
516                  version));
517 }
518
519 void ExternalCache::OnCacheEntryInstalled(const std::string& id,
520                                           const base::FilePath& path,
521                                           const std::string& version) {
522   if (shutdown_)
523     return;
524
525   VLOG(1) << "AppPack installed a new extension in the cache: " << path.value();
526
527   base::DictionaryValue* entry = NULL;
528   if (!extensions_->GetDictionary(id, &entry)) {
529     LOG(ERROR) << "ExternalCache cannot find entry for extension " << id;
530     return;
531   }
532
533   // Copy entry to don't modify it inside extensions_.
534   entry = entry->DeepCopy();
535
536   std::string update_url;
537   if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
538                        &update_url) &&
539       extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
540     entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true);
541   }
542   entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
543   entry->SetString(extensions::ExternalProviderImpl::kExternalVersion, version);
544   entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
545                    path.value());
546
547   cached_extensions_->Set(id, entry);
548   UpdateExtensionLoader();
549 }
550
551 // static
552 void ExternalCache::BackendShudown(const base::Closure& callback) {
553   content::BrowserThread::PostTask(content::BrowserThread::UI,
554                                    FROM_HERE,
555                                    callback);
556 }
557
558 std::string ExternalCache::Delegate::GetInstalledExtensionVersion(
559     const std::string& id) {
560   return std::string();
561 }
562
563 }  // namespace chromeos