Home | History | Annotate | Download | only in extensions
      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 {
     45   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
     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
     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
     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
     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
     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
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    291 void CrxInstaller::OnUnpackFailure(const std::string& error_message) {
    292   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    293   ReportFailureFromFileThread(error_message);
    294 }
    295 
    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.
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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