• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/files/file_enumerator.h"
11 #include "base/files/file_util.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/extensions/crx_installer.h"
18 #include "chrome/browser/extensions/external_provider_impl.h"
19 #include "chrome/browser/extensions/updater/chrome_extension_downloader_factory.h"
20 #include "chrome/browser/extensions/updater/extension_downloader.h"
21 #include "content/public/browser/notification_details.h"
22 #include "content/public/browser/notification_service.h"
23 #include "content/public/browser/notification_source.h"
24 #include "extensions/browser/notification_types.h"
25 #include "extensions/common/extension.h"
26 #include "extensions/common/extension_urls.h"
27 #include "net/url_request/url_request_context_getter.h"
28 
29 namespace chromeos {
30 
ExternalCache(const base::FilePath & cache_dir,net::URLRequestContextGetter * request_context,const scoped_refptr<base::SequencedTaskRunner> & backend_task_runner,Delegate * delegate,bool always_check_updates,bool wait_for_cache_initialization)31 ExternalCache::ExternalCache(const base::FilePath& cache_dir,
32                              net::URLRequestContextGetter* request_context,
33                              const scoped_refptr<base::SequencedTaskRunner>&
34                                  backend_task_runner,
35                              Delegate* delegate,
36                              bool always_check_updates,
37                              bool wait_for_cache_initialization)
38     : local_cache_(cache_dir, 0, base::TimeDelta(), backend_task_runner),
39       request_context_(request_context),
40       backend_task_runner_(backend_task_runner),
41       delegate_(delegate),
42       always_check_updates_(always_check_updates),
43       wait_for_cache_initialization_(wait_for_cache_initialization),
44       cached_extensions_(new base::DictionaryValue()),
45       weak_ptr_factory_(this) {
46   notification_registrar_.Add(
47       this,
48       extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR,
49       content::NotificationService::AllBrowserContextsAndSources());
50 }
51 
~ExternalCache()52 ExternalCache::~ExternalCache() {
53 }
54 
Shutdown(const base::Closure & callback)55 void ExternalCache::Shutdown(const base::Closure& callback) {
56   local_cache_.Shutdown(callback);
57 }
58 
UpdateExtensionsList(scoped_ptr<base::DictionaryValue> prefs)59 void ExternalCache::UpdateExtensionsList(
60     scoped_ptr<base::DictionaryValue> prefs) {
61   extensions_ = prefs.Pass();
62 
63   if (extensions_->empty()) {
64     // If list of know extensions is empty, don't init cache on disk. It is
65     // important shortcut for test to don't wait forever for cache dir
66     // initialization that should happen outside of Chrome on real device.
67     cached_extensions_->Clear();
68     UpdateExtensionLoader();
69     return;
70   }
71 
72   if (local_cache_.is_uninitialized()) {
73     local_cache_.Init(wait_for_cache_initialization_,
74                       base::Bind(&ExternalCache::CheckCache,
75                                  weak_ptr_factory_.GetWeakPtr()));
76   } else {
77     CheckCache();
78   }
79 }
80 
OnDamagedFileDetected(const base::FilePath & path)81 void ExternalCache::OnDamagedFileDetected(const base::FilePath& path) {
82   for (base::DictionaryValue::Iterator it(*cached_extensions_.get());
83        !it.IsAtEnd(); it.Advance()) {
84     const base::DictionaryValue* entry = NULL;
85     if (!it.value().GetAsDictionary(&entry)) {
86       NOTREACHED() << "ExternalCache found bad entry with type "
87                    << it.value().GetType();
88       continue;
89     }
90 
91     std::string external_crx;
92     if (entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
93                          &external_crx) &&
94         external_crx == path.value()) {
95       std::string id = it.key();
96       LOG(ERROR) << "ExternalCache extension at " << path.value()
97                  << " failed to install, deleting it.";
98       cached_extensions_->Remove(id, NULL);
99       extensions_->Remove(id, NULL);
100 
101       local_cache_.RemoveExtension(id);
102       UpdateExtensionLoader();
103 
104       // Don't try to DownloadMissingExtensions() from here,
105       // since it can cause a fail/retry loop.
106       return;
107     }
108   }
109   LOG(ERROR) << "ExternalCache cannot find external_crx " << path.value();
110 }
111 
RemoveExtensions(const std::vector<std::string> & ids)112 void ExternalCache::RemoveExtensions(const std::vector<std::string>& ids) {
113   if (ids.empty())
114     return;
115 
116   for (size_t i = 0; i < ids.size(); ++i) {
117     cached_extensions_->Remove(ids[i], NULL);
118     extensions_->Remove(ids[i], NULL);
119     local_cache_.RemoveExtension(ids[i]);
120   }
121   UpdateExtensionLoader();
122 }
123 
GetExtension(const std::string & id,base::FilePath * file_path,std::string * version)124 bool ExternalCache::GetExtension(const std::string& id,
125                                  base::FilePath* file_path,
126                                  std::string* version) {
127   return local_cache_.GetExtension(id, file_path, version);
128 }
129 
PutExternalExtension(const std::string & id,const base::FilePath & crx_file_path,const std::string & version,const PutExternalExtensionCallback & callback)130 void ExternalCache::PutExternalExtension(
131     const std::string& id,
132     const base::FilePath& crx_file_path,
133     const std::string& version,
134     const PutExternalExtensionCallback& callback) {
135   local_cache_.PutExtension(id,
136                             crx_file_path,
137                             version,
138                             base::Bind(&ExternalCache::OnPutExternalExtension,
139                                        weak_ptr_factory_.GetWeakPtr(),
140                                        id,
141                                        callback));
142 }
143 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)144 void ExternalCache::Observe(int type,
145                             const content::NotificationSource& source,
146                             const content::NotificationDetails& details) {
147   switch (type) {
148     case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
149       extensions::CrxInstaller* installer =
150           content::Source<extensions::CrxInstaller>(source).ptr();
151       OnDamagedFileDetected(installer->source_file());
152       break;
153     }
154 
155     default:
156       NOTREACHED();
157   }
158 }
159 
OnExtensionDownloadFailed(const std::string & id,extensions::ExtensionDownloaderDelegate::Error error,const extensions::ExtensionDownloaderDelegate::PingResult & ping_result,const std::set<int> & request_ids)160 void ExternalCache::OnExtensionDownloadFailed(
161     const std::string& id,
162     extensions::ExtensionDownloaderDelegate::Error error,
163     const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
164     const std::set<int>& request_ids) {
165   if (error == NO_UPDATE_AVAILABLE) {
166     if (!cached_extensions_->HasKey(id)) {
167       LOG(ERROR) << "ExternalCache extension " << id
168                  << " not found on update server";
169       delegate_->OnExtensionDownloadFailed(id, error);
170     } else {
171       // No version update for an already cached extension.
172       delegate_->OnExtensionLoadedInCache(id);
173     }
174   } else {
175     LOG(ERROR) << "ExternalCache failed to download extension " << id
176                << ", error " << error;
177     delegate_->OnExtensionDownloadFailed(id, error);
178   }
179 }
180 
OnExtensionDownloadFinished(const std::string & id,const base::FilePath & path,bool file_ownership_passed,const GURL & download_url,const std::string & version,const extensions::ExtensionDownloaderDelegate::PingResult & ping_result,const std::set<int> & request_ids)181 void ExternalCache::OnExtensionDownloadFinished(
182     const std::string& id,
183     const base::FilePath& path,
184     bool file_ownership_passed,
185     const GURL& download_url,
186     const std::string& version,
187     const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
188     const std::set<int>& request_ids) {
189   DCHECK(file_ownership_passed);
190   local_cache_.PutExtension(id, path, version,
191                             base::Bind(&ExternalCache::OnPutExtension,
192                                        weak_ptr_factory_.GetWeakPtr(),
193                                        id));
194 }
195 
IsExtensionPending(const std::string & id)196 bool ExternalCache::IsExtensionPending(const std::string& id) {
197   // Pending means that there is no installed version yet.
198   return extensions_->HasKey(id) && !cached_extensions_->HasKey(id);
199 }
200 
GetExtensionExistingVersion(const std::string & id,std::string * version)201 bool ExternalCache::GetExtensionExistingVersion(const std::string& id,
202                                                 std::string* version) {
203   base::DictionaryValue* extension_dictionary = NULL;
204   if (cached_extensions_->GetDictionary(id, &extension_dictionary)) {
205     if (extension_dictionary->GetString(
206             extensions::ExternalProviderImpl::kExternalVersion, version)) {
207       return true;
208     }
209     *version = delegate_->GetInstalledExtensionVersion(id);
210     return !version->empty();
211   }
212   return false;
213 }
214 
UpdateExtensionLoader()215 void ExternalCache::UpdateExtensionLoader() {
216   VLOG(1) << "Notify ExternalCache delegate about cache update";
217   if (delegate_)
218     delegate_->OnExtensionListsUpdated(cached_extensions_.get());
219 }
220 
CheckCache()221 void ExternalCache::CheckCache() {
222   if (local_cache_.is_shutdown())
223     return;
224 
225   // If request_context_ is missing we can't download anything.
226   if (!downloader_ && request_context_.get()) {
227     downloader_ = ChromeExtensionDownloaderFactory::CreateForRequestContext(
228         request_context_.get(), this);
229   }
230 
231   cached_extensions_->Clear();
232   for (base::DictionaryValue::Iterator it(*extensions_.get());
233        !it.IsAtEnd(); it.Advance()) {
234     const base::DictionaryValue* entry = NULL;
235     if (!it.value().GetAsDictionary(&entry)) {
236       LOG(ERROR) << "ExternalCache found bad entry with type "
237                  << it.value().GetType();
238       continue;
239     }
240 
241     bool keep_if_present =
242         entry->HasKey(extensions::ExternalProviderImpl::kKeepIfPresent);
243     std::string external_update_url;
244     entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
245                      &external_update_url);
246     if (downloader_ && !keep_if_present) {
247       GURL update_url;
248       if (!external_update_url.empty())
249         update_url = GURL(external_update_url);
250       else if (always_check_updates_)
251         update_url = extension_urls::GetWebstoreUpdateUrl();
252 
253       if (update_url.is_valid())
254         downloader_->AddPendingExtension(it.key(), update_url, 0);
255     }
256 
257     base::FilePath file_path;
258     std::string version;
259     if (local_cache_.GetExtension(it.key(), &file_path, &version)) {
260       // Copy entry to don't modify it inside extensions_.
261       base::DictionaryValue* entry_copy = entry->DeepCopy();
262 
263       if (extension_urls::IsWebstoreUpdateUrl(GURL(external_update_url))) {
264         entry_copy->SetBoolean(
265             extensions::ExternalProviderImpl::kIsFromWebstore, true);
266       }
267       entry_copy->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl,
268                          NULL);
269       entry_copy->SetString(extensions::ExternalProviderImpl::kExternalVersion,
270                             version);
271       entry_copy->SetString(extensions::ExternalProviderImpl::kExternalCrx,
272                             file_path.value());
273       cached_extensions_->Set(it.key(), entry_copy);
274     } else {
275       bool has_external_crx = entry->HasKey(
276           extensions::ExternalProviderImpl::kExternalCrx);
277       bool is_already_installed =
278           !delegate_->GetInstalledExtensionVersion(it.key()).empty();
279       if (keep_if_present || has_external_crx || is_already_installed) {
280         // Copy entry to don't modify it inside extensions_.
281         cached_extensions_->Set(it.key(), entry->DeepCopy());
282       }
283     }
284   }
285 
286   if (downloader_)
287     downloader_->StartAllPending(NULL);
288 
289   VLOG(1) << "Updated ExternalCache, there are "
290           << cached_extensions_->size() << " extensions cached";
291 
292   UpdateExtensionLoader();
293 }
294 
OnPutExtension(const std::string & id,const base::FilePath & file_path,bool file_ownership_passed)295 void ExternalCache::OnPutExtension(const std::string& id,
296                                    const base::FilePath& file_path,
297                                    bool file_ownership_passed) {
298   if (local_cache_.is_shutdown() || file_ownership_passed) {
299     backend_task_runner_->PostTask(FROM_HERE,
300         base::Bind(base::IgnoreResult(&base::DeleteFile), file_path, true));
301     return;
302   }
303 
304   VLOG(1) << "ExternalCache installed a new extension in the cache " << id;
305 
306   base::DictionaryValue* entry = NULL;
307   if (!extensions_->GetDictionary(id, &entry)) {
308     LOG(ERROR) << "ExternalCache cannot find entry for extension " << id;
309     return;
310   }
311 
312   // Copy entry to don't modify it inside extensions_.
313   entry = entry->DeepCopy();
314 
315   std::string version;
316   if (!local_cache_.GetExtension(id, NULL, &version)) {
317     // Copy entry to don't modify it inside extensions_.
318     LOG(ERROR) << "Can't find installed extension in cache " << id;
319     return;
320   }
321 
322   std::string update_url;
323   if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
324                        &update_url) &&
325       extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
326     entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true);
327   }
328   entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
329   entry->SetString(extensions::ExternalProviderImpl::kExternalVersion, version);
330   entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
331                    file_path.value());
332 
333   cached_extensions_->Set(id, entry);
334   if (delegate_)
335     delegate_->OnExtensionLoadedInCache(id);
336   UpdateExtensionLoader();
337 }
338 
OnPutExternalExtension(const std::string & id,const PutExternalExtensionCallback & callback,const base::FilePath & file_path,bool file_ownership_passed)339 void ExternalCache::OnPutExternalExtension(
340     const std::string& id,
341     const PutExternalExtensionCallback& callback,
342     const base::FilePath& file_path,
343     bool file_ownership_passed) {
344   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
345   OnPutExtension(id, file_path, file_ownership_passed);
346   callback.Run(id, !file_ownership_passed);
347 }
348 
GetInstalledExtensionVersion(const std::string & id)349 std::string ExternalCache::Delegate::GetInstalledExtensionVersion(
350     const std::string& id) {
351   return std::string();
352 }
353 
354 }  // namespace chromeos
355