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