• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/crx_installer.h"
6 
7 #include <map>
8 #include <set>
9 
10 #include "base/file_util.h"
11 #include "base/lazy_instance.h"
12 #include "base/memory/scoped_temp_dir.h"
13 #include "base/metrics/histogram.h"
14 #include "base/path_service.h"
15 #include "base/stl_util-inl.h"
16 #include "base/stringprintf.h"
17 #include "base/task.h"
18 #include "base/threading/thread_restrictions.h"
19 #include "base/time.h"
20 #include "base/utf_string_conversions.h"
21 #include "base/version.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/extensions/convert_user_script.h"
24 #include "chrome/browser/extensions/convert_web_app.h"
25 #include "chrome/browser/extensions/extension_error_reporter.h"
26 #include "chrome/browser/extensions/extension_service.h"
27 #include "chrome/browser/shell_integration.h"
28 #include "chrome/browser/web_applications/web_app.h"
29 #include "chrome/common/chrome_paths.h"
30 #include "chrome/common/extensions/extension_constants.h"
31 #include "chrome/common/extensions/extension_file_util.h"
32 #include "content/browser/browser_thread.h"
33 #include "content/common/notification_service.h"
34 #include "content/common/notification_type.h"
35 #include "grit/chromium_strings.h"
36 #include "grit/generated_resources.h"
37 #include "grit/theme_resources.h"
38 #include "third_party/skia/include/core/SkBitmap.h"
39 #include "ui/base/l10n/l10n_util.h"
40 #include "ui/base/resource/resource_bundle.h"
41 
42 namespace {
43 
44 struct WhitelistedInstallData {
WhitelistedInstallData__anone89c7e550111::WhitelistedInstallData45   WhitelistedInstallData() {}
46   std::set<std::string> ids;
47   std::map<std::string, linked_ptr<DictionaryValue> > manifests;
48 };
49 
50 static base::LazyInstance<WhitelistedInstallData>
51     g_whitelisted_install_data(base::LINKER_INITIALIZED);
52 
53 }  // namespace
54 
55 // static
SetWhitelistedInstallId(const std::string & id)56 void CrxInstaller::SetWhitelistedInstallId(const std::string& id) {
57   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
58   g_whitelisted_install_data.Get().ids.insert(id);
59 }
60 
61 // static
SetWhitelistedManifest(const std::string & id,DictionaryValue * parsed_manifest)62 void CrxInstaller::SetWhitelistedManifest(const std::string& id,
63                                           DictionaryValue* parsed_manifest) {
64   CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
65   WhitelistedInstallData& data = g_whitelisted_install_data.Get();
66   data.manifests[id] = linked_ptr<DictionaryValue>(parsed_manifest);
67 }
68 
69 // static
GetWhitelistedManifest(const std::string & id)70 const DictionaryValue* CrxInstaller::GetWhitelistedManifest(
71     const std::string& id) {
72   WhitelistedInstallData& data = g_whitelisted_install_data.Get();
73   if (ContainsKey(data.manifests, id))
74     return data.manifests[id].get();
75   else
76     return NULL;
77 }
78 
79 // static
RemoveWhitelistedManifest(const std::string & id)80 DictionaryValue* CrxInstaller::RemoveWhitelistedManifest(
81     const std::string& id) {
82   WhitelistedInstallData& data = g_whitelisted_install_data.Get();
83   if (ContainsKey(data.manifests, id)) {
84     DictionaryValue* manifest = data.manifests[id].release();
85     data.manifests.erase(id);
86     return manifest;
87   }
88   return NULL;
89 }
90 
91 // static
IsIdWhitelisted(const std::string & id)92 bool CrxInstaller::IsIdWhitelisted(const std::string& id) {
93   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
94   std::set<std::string>& ids = g_whitelisted_install_data.Get().ids;
95   return ContainsKey(ids, id);
96 }
97 
98 // static
ClearWhitelistedInstallId(const std::string & id)99 bool CrxInstaller::ClearWhitelistedInstallId(const std::string& id) {
100   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
101   std::set<std::string>& ids = g_whitelisted_install_data.Get().ids;
102   if (ContainsKey(ids, id)) {
103     ids.erase(id);
104     return true;
105   }
106   return false;
107 }
108 
CrxInstaller(ExtensionService * frontend,ExtensionInstallUI * client)109 CrxInstaller::CrxInstaller(ExtensionService* frontend,
110                            ExtensionInstallUI* client)
111     : install_directory_(frontend->install_directory()),
112       install_source_(Extension::INTERNAL),
113       extensions_enabled_(frontend->extensions_enabled()),
114       delete_source_(false),
115       is_gallery_install_(false),
116       create_app_shortcut_(false),
117       frontend_(frontend),
118       client_(client),
119       apps_require_extension_mime_type_(false),
120       allow_silent_install_(false) {
121 }
122 
~CrxInstaller()123 CrxInstaller::~CrxInstaller() {
124   // Delete the temp directory and crx file as necessary. Note that the
125   // destructor might be called on any thread, so we post a task to the file
126   // thread to make sure the delete happens there.
127   if (!temp_dir_.value().empty()) {
128     BrowserThread::PostTask(
129         BrowserThread::FILE, FROM_HERE,
130         NewRunnableFunction(
131             &extension_file_util::DeleteFile, temp_dir_, true));
132   }
133 
134   if (delete_source_) {
135     BrowserThread::PostTask(
136         BrowserThread::FILE, FROM_HERE,
137         NewRunnableFunction(
138             &extension_file_util::DeleteFile, source_file_, false));
139   }
140 
141   // Make sure the UI is deleted on the ui thread.
142   BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, client_);
143   client_ = NULL;
144 }
145 
InstallCrx(const FilePath & source_file)146 void CrxInstaller::InstallCrx(const FilePath& source_file) {
147   source_file_ = source_file;
148 
149   scoped_refptr<SandboxedExtensionUnpacker> unpacker(
150       new SandboxedExtensionUnpacker(
151           source_file,
152           g_browser_process->resource_dispatcher_host(),
153           this));
154 
155   BrowserThread::PostTask(
156       BrowserThread::FILE, FROM_HERE,
157       NewRunnableMethod(
158           unpacker.get(), &SandboxedExtensionUnpacker::Start));
159 }
160 
InstallUserScript(const FilePath & source_file,const GURL & original_url)161 void CrxInstaller::InstallUserScript(const FilePath& source_file,
162                                      const GURL& original_url) {
163   DCHECK(!original_url.is_empty());
164 
165   source_file_ = source_file;
166   original_url_ = original_url;
167 
168   BrowserThread::PostTask(
169       BrowserThread::FILE, FROM_HERE,
170       NewRunnableMethod(this, &CrxInstaller::ConvertUserScriptOnFileThread));
171 }
172 
ConvertUserScriptOnFileThread()173 void CrxInstaller::ConvertUserScriptOnFileThread() {
174   std::string error;
175   scoped_refptr<Extension> extension =
176       ConvertUserScriptToExtension(source_file_, original_url_, &error);
177   if (!extension) {
178     ReportFailureFromFileThread(error);
179     return;
180   }
181 
182   OnUnpackSuccess(extension->path(), extension->path(), extension);
183 }
184 
InstallWebApp(const WebApplicationInfo & web_app)185 void CrxInstaller::InstallWebApp(const WebApplicationInfo& web_app) {
186   BrowserThread::PostTask(
187       BrowserThread::FILE, FROM_HERE,
188       NewRunnableMethod(this, &CrxInstaller::ConvertWebAppOnFileThread,
189                         web_app));
190 }
191 
ConvertWebAppOnFileThread(const WebApplicationInfo & web_app)192 void CrxInstaller::ConvertWebAppOnFileThread(
193     const WebApplicationInfo& web_app) {
194   std::string error;
195   scoped_refptr<Extension> extension(
196       ConvertWebAppToExtension(web_app, base::Time::Now()));
197   if (!extension) {
198     // Validation should have stopped any potential errors before getting here.
199     NOTREACHED() << "Could not convert web app to extension.";
200     return;
201   }
202 
203   // TODO(aa): conversion data gets lost here :(
204 
205   OnUnpackSuccess(extension->path(), extension->path(), extension);
206 }
207 
AllowInstall(const Extension * extension,std::string * error)208 bool CrxInstaller::AllowInstall(const Extension* extension,
209                                 std::string* error) {
210   DCHECK(error);
211 
212   // Make sure the expected id matches.
213   if (!expected_id_.empty() && expected_id_ != extension->id()) {
214     *error = base::StringPrintf(
215         "ID in new CRX manifest (%s) does not match expected id (%s)",
216         extension->id().c_str(),
217         expected_id_.c_str());
218     return false;
219   }
220 
221   if (expected_version_.get() &&
222       !expected_version_->Equals(*extension->version())) {
223     *error = base::StringPrintf(
224         "Version in new CRX %s manifest (%s) does not match expected "
225         "version (%s)",
226         extension->id().c_str(),
227         expected_version_->GetString().c_str(),
228         extension->version()->GetString().c_str());
229     return false;
230   }
231 
232   // The checks below are skipped for themes and external installs.
233   if (extension->is_theme() || Extension::IsExternalLocation(install_source_))
234     return true;
235 
236   if (!extensions_enabled_) {
237     *error = "Extensions are not enabled.";
238     return false;
239   }
240 
241   if (extension_->is_app()) {
242     // If the app was downloaded, apps_require_extension_mime_type_
243     // will be set.  In this case, check that it was served with the
244     // right mime type.  Make an exception for file URLs, which come
245     // from the users computer and have no headers.
246     if (!original_url_.SchemeIsFile() &&
247         apps_require_extension_mime_type_ &&
248         original_mime_type_ != Extension::kMimeType) {
249       *error = base::StringPrintf(
250           "Apps must be served with content type %s.",
251           Extension::kMimeType);
252       return false;
253     }
254 
255     // If the client_ is NULL, then the app is either being installed via
256     // an internal mechanism like sync, external_extensions, or default apps.
257     // In that case, we don't want to enforce things like the install origin.
258     if (!is_gallery_install_ && client_) {
259       // For apps with a gallery update URL, require that they be installed
260       // from the gallery.
261       // TODO(erikkay) Apply this rule for paid extensions and themes as well.
262       if (extension->UpdatesFromGallery()) {
263         *error = l10n_util::GetStringFUTF8(
264             IDS_EXTENSION_DISALLOW_NON_DOWNLOADED_GALLERY_INSTALLS,
265             l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE));
266         return false;
267       }
268 
269       // For self-hosted apps, verify that the entire extent is on the same
270       // host (or a subdomain of the host) the download happened from.  There's
271       // no way for us to verify that the app controls any other hosts.
272       URLPattern pattern(UserScript::kValidUserScriptSchemes);
273       pattern.set_host(original_url_.host());
274       pattern.set_match_subdomains(true);
275 
276       ExtensionExtent::PatternList patterns =
277           extension_->web_extent().patterns();
278       for (size_t i = 0; i < patterns.size(); ++i) {
279         if (!pattern.MatchesHost(patterns[i].host())) {
280           *error = base::StringPrintf(
281               "Apps must be served from the host that they affect.");
282           return false;
283         }
284       }
285     }
286   }
287 
288   return true;
289 }
290 
OnUnpackFailure(const std::string & error_message)291 void CrxInstaller::OnUnpackFailure(const std::string& error_message) {
292   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
293   ReportFailureFromFileThread(error_message);
294 }
295 
OnUnpackSuccess(const FilePath & temp_dir,const FilePath & extension_dir,const Extension * extension)296 void CrxInstaller::OnUnpackSuccess(const FilePath& temp_dir,
297                                    const FilePath& extension_dir,
298                                    const Extension* extension) {
299   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
300 
301   // Note: We take ownership of |extension| and |temp_dir|.
302   extension_ = extension;
303   temp_dir_ = temp_dir;
304 
305   // We don't have to delete the unpack dir explicity since it is a child of
306   // the temp dir.
307   unpacked_extension_root_ = extension_dir;
308 
309   std::string error;
310   if (!AllowInstall(extension, &error)) {
311     ReportFailureFromFileThread(error);
312     return;
313   }
314 
315   if (client_) {
316     Extension::DecodeIcon(extension_.get(), Extension::EXTENSION_ICON_LARGE,
317                           &install_icon_);
318   }
319 
320   BrowserThread::PostTask(
321       BrowserThread::UI, FROM_HERE,
322       NewRunnableMethod(this, &CrxInstaller::ConfirmInstall));
323 }
324 
325 // Helper method to let us compare a whitelisted manifest with the actual
326 // downloaded extension's manifest, but ignoring the kPublicKey since the
327 // whitelisted manifest doesn't have that value.
EqualsIgnoringPublicKey(const DictionaryValue & extension_manifest,const DictionaryValue & whitelisted_manifest)328 static bool EqualsIgnoringPublicKey(
329     const DictionaryValue& extension_manifest,
330     const DictionaryValue& whitelisted_manifest) {
331   scoped_ptr<DictionaryValue> manifest_copy(extension_manifest.DeepCopy());
332   manifest_copy->Remove(extension_manifest_keys::kPublicKey, NULL);
333   return manifest_copy->Equals(&whitelisted_manifest);
334 }
335 
ConfirmInstall()336 void CrxInstaller::ConfirmInstall() {
337   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
338   if (frontend_->extension_prefs()->IsExtensionBlacklisted(extension_->id())) {
339     VLOG(1) << "This extension: " << extension_->id()
340             << " is blacklisted. Install failed.";
341     ReportFailureFromUIThread("This extension is blacklisted.");
342     return;
343   }
344 
345   if (!frontend_->extension_prefs()->IsExtensionAllowedByPolicy(
346       extension_->id())) {
347     ReportFailureFromUIThread("This extension is blacklisted by admin policy.");
348     return;
349   }
350 
351   GURL overlapping_url;
352   const Extension* overlapping_extension =
353       frontend_->GetExtensionByOverlappingWebExtent(extension_->web_extent());
354   if (overlapping_extension &&
355       overlapping_extension->id() != extension_->id()) {
356     ReportFailureFromUIThread(l10n_util::GetStringFUTF8(
357         IDS_EXTENSION_OVERLAPPING_WEB_EXTENT,
358         UTF8ToUTF16(overlapping_extension->name())));
359     return;
360   }
361 
362   current_version_ =
363       frontend_->extension_prefs()->GetVersionString(extension_->id());
364 
365   // First see if it's whitelisted by id (the old mechanism).
366   bool whitelisted = ClearWhitelistedInstallId(extension_->id()) &&
367       extension_->plugins().empty() && is_gallery_install_;
368 
369   // Now check if it's whitelisted by manifest.
370   scoped_ptr<DictionaryValue> whitelisted_manifest(
371       RemoveWhitelistedManifest(extension_->id()));
372   if (is_gallery_install_ && whitelisted_manifest.get()) {
373     if (!EqualsIgnoringPublicKey(*extension_->manifest_value(),
374                                  *whitelisted_manifest)) {
375       ReportFailureFromUIThread(
376           l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID));
377       return;
378     }
379     whitelisted = true;
380   }
381 
382   if (client_ &&
383       (!allow_silent_install_ || !whitelisted)) {
384     AddRef();  // Balanced in Proceed() and Abort().
385     client_->ConfirmInstall(this, extension_.get());
386   } else {
387     BrowserThread::PostTask(
388         BrowserThread::FILE, FROM_HERE,
389         NewRunnableMethod(this, &CrxInstaller::CompleteInstall));
390   }
391   return;
392 }
393 
InstallUIProceed()394 void CrxInstaller::InstallUIProceed() {
395   BrowserThread::PostTask(
396         BrowserThread::FILE, FROM_HERE,
397         NewRunnableMethod(this, &CrxInstaller::CompleteInstall));
398 
399   Release();  // balanced in ConfirmInstall().
400 }
401 
InstallUIAbort()402 void CrxInstaller::InstallUIAbort() {
403   // Technically, this can be called for other reasons than the user hitting
404   // cancel, but they're rare.
405   ExtensionService::RecordPermissionMessagesHistogram(
406       extension_, "Extensions.Permissions_InstallCancel");
407 
408   // Kill the theme loading bubble.
409   NotificationService* service = NotificationService::current();
410   service->Notify(NotificationType::NO_THEME_DETECTED,
411                   Source<CrxInstaller>(this),
412                   NotificationService::NoDetails());
413   Release();  // balanced in ConfirmInstall().
414 
415   // We're done. Since we don't post any more tasks to ourself, our ref count
416   // should go to zero and we die. The destructor will clean up the temp dir.
417 }
418 
CompleteInstall()419 void CrxInstaller::CompleteInstall() {
420   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
421 
422   if (!current_version_.empty()) {
423     scoped_ptr<Version> current_version(
424         Version::GetVersionFromString(current_version_));
425     if (current_version->CompareTo(*(extension_->version())) > 0) {
426       ReportFailureFromFileThread("Attempted to downgrade extension.");
427       return;
428     }
429   }
430 
431   // See how long extension install paths are.  This is important on
432   // windows, because file operations may fail if the path to a file
433   // exceeds a small constant.  See crbug.com/69693 .
434   UMA_HISTOGRAM_CUSTOM_COUNTS(
435     "Extensions.CrxInstallDirPathLength",
436         install_directory_.value().length(), 0, 500, 100);
437 
438   FilePath version_dir = extension_file_util::InstallExtension(
439       unpacked_extension_root_,
440       extension_->id(),
441       extension_->VersionString(),
442       install_directory_);
443   if (version_dir.empty()) {
444     ReportFailureFromFileThread(
445         l10n_util::GetStringUTF8(
446             IDS_EXTENSION_MOVE_DIRECTORY_TO_PROFILE_FAILED));
447     return;
448   }
449 
450   // This is lame, but we must reload the extension because absolute paths
451   // inside the content scripts are established inside InitFromValue() and we
452   // just moved the extension.
453   // TODO(aa): All paths to resources inside extensions should be created
454   // lazily and based on the Extension's root path at that moment.
455   std::string error;
456   extension_ = extension_file_util::LoadExtension(
457       version_dir,
458       install_source_,
459       Extension::REQUIRE_KEY,
460       &error);
461   CHECK(error.empty()) << error;
462 
463   ReportSuccessFromFileThread();
464 }
465 
ReportFailureFromFileThread(const std::string & error)466 void CrxInstaller::ReportFailureFromFileThread(const std::string& error) {
467   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
468   BrowserThread::PostTask(
469       BrowserThread::UI, FROM_HERE,
470       NewRunnableMethod(this, &CrxInstaller::ReportFailureFromUIThread, error));
471 }
472 
ReportFailureFromUIThread(const std::string & error)473 void CrxInstaller::ReportFailureFromUIThread(const std::string& error) {
474   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
475 
476   NotificationService* service = NotificationService::current();
477   service->Notify(NotificationType::EXTENSION_INSTALL_ERROR,
478                   Source<CrxInstaller>(this),
479                   Details<const std::string>(&error));
480 
481   // This isn't really necessary, it is only used because unit tests expect to
482   // see errors get reported via this interface.
483   //
484   // TODO(aa): Need to go through unit tests and clean them up too, probably get
485   // rid of this line.
486   ExtensionErrorReporter::GetInstance()->ReportError(error, false);  // quiet
487 
488   if (client_)
489     client_->OnInstallFailure(error);
490 }
491 
ReportSuccessFromFileThread()492 void CrxInstaller::ReportSuccessFromFileThread() {
493   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
494   BrowserThread::PostTask(
495       BrowserThread::UI, FROM_HERE,
496       NewRunnableMethod(this, &CrxInstaller::ReportSuccessFromUIThread));
497 }
498 
ReportSuccessFromUIThread()499 void CrxInstaller::ReportSuccessFromUIThread() {
500   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
501 
502   // If there is a client, tell the client about installation.
503   if (client_)
504     client_->OnInstallSuccess(extension_.get(), install_icon_.get());
505 
506   // Tell the frontend about the installation and hand off ownership of
507   // extension_ to it.
508   frontend_->OnExtensionInstalled(extension_);
509   extension_ = NULL;
510 
511   // We're done. We don't post any more tasks to ourselves so we are deleted
512   // soon.
513 }
514