Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2012 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/webstore_installer.h"
      6 
      7 #include "base/basictypes.h"
      8 #include "base/bind.h"
      9 #include "base/command_line.h"
     10 #include "base/file_util.h"
     11 #include "base/rand_util.h"
     12 #include "base/strings/string_number_conversions.h"
     13 #include "base/strings/string_util.h"
     14 #include "base/strings/stringprintf.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "chrome/browser/browser_process.h"
     17 #include "chrome/browser/chrome_notification_types.h"
     18 #include "chrome/browser/download/download_crx_util.h"
     19 #include "chrome/browser/download/download_prefs.h"
     20 #include "chrome/browser/download/download_stats.h"
     21 #include "chrome/browser/download/download_util.h"
     22 #include "chrome/browser/extensions/crx_installer.h"
     23 #include "chrome/browser/extensions/install_tracker.h"
     24 #include "chrome/browser/extensions/install_tracker_factory.h"
     25 #include "chrome/browser/profiles/profile.h"
     26 #include "chrome/browser/ui/browser_list.h"
     27 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     28 #include "chrome/common/chrome_switches.h"
     29 #include "chrome/common/extensions/extension.h"
     30 #include "chrome/common/extensions/extension_constants.h"
     31 #include "chrome/common/extensions/extension_manifest_constants.h"
     32 #include "chrome/common/omaha_query_params/omaha_query_params.h"
     33 #include "content/public/browser/browser_thread.h"
     34 #include "content/public/browser/download_manager.h"
     35 #include "content/public/browser/download_save_info.h"
     36 #include "content/public/browser/download_url_parameters.h"
     37 #include "content/public/browser/navigation_controller.h"
     38 #include "content/public/browser/navigation_entry.h"
     39 #include "content/public/browser/notification_details.h"
     40 #include "content/public/browser/notification_service.h"
     41 #include "content/public/browser/notification_source.h"
     42 #include "content/public/browser/render_process_host.h"
     43 #include "content/public/browser/render_view_host.h"
     44 #include "content/public/browser/web_contents.h"
     45 #include "net/base/escape.h"
     46 #include "url/gurl.h"
     47 
     48 #if defined(OS_CHROMEOS)
     49 #include "chrome/browser/chromeos/drive/file_system_util.h"
     50 #endif
     51 
     52 using chrome::OmahaQueryParams;
     53 using content::BrowserContext;
     54 using content::BrowserThread;
     55 using content::DownloadItem;
     56 using content::DownloadManager;
     57 using content::NavigationController;
     58 using content::DownloadUrlParameters;
     59 
     60 namespace {
     61 
     62 // Key used to attach the Approval to the DownloadItem.
     63 const char kApprovalKey[] = "extensions.webstore_installer";
     64 
     65 const char kInvalidIdError[] = "Invalid id";
     66 const char kNoBrowserError[] = "No browser found";
     67 const char kDownloadDirectoryError[] = "Could not create download directory";
     68 const char kDownloadCanceledError[] = "Download canceled";
     69 const char kInstallCanceledError[] = "Install canceled";
     70 const char kDownloadInterruptedError[] = "Download interrupted";
     71 const char kInvalidDownloadError[] =
     72     "Download was not a valid extension or user script";
     73 const char kInlineInstallSource[] = "inline";
     74 const char kDefaultInstallSource[] = "ondemand";
     75 
     76 base::FilePath* g_download_directory_for_tests = NULL;
     77 
     78 // Must be executed on the FILE thread.
     79 void GetDownloadFilePath(
     80     const base::FilePath& download_directory, const std::string& id,
     81     const base::Callback<void(const base::FilePath&)>& callback) {
     82   base::FilePath directory(g_download_directory_for_tests ?
     83                      *g_download_directory_for_tests : download_directory);
     84 
     85 #if defined(OS_CHROMEOS)
     86   // Do not use drive for extension downloads.
     87   if (drive::util::IsUnderDriveMountPoint(directory))
     88     directory = download_util::GetDefaultDownloadDirectory();
     89 #endif
     90 
     91   // Ensure the download directory exists. TODO(asargent) - make this use
     92   // common code from the downloads system.
     93   if (!base::DirectoryExists(directory)) {
     94     if (!file_util::CreateDirectory(directory)) {
     95       BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
     96                               base::Bind(callback, base::FilePath()));
     97       return;
     98     }
     99   }
    100 
    101   // This is to help avoid a race condition between when we generate this
    102   // filename and when the download starts writing to it (think concurrently
    103   // running sharded browser tests installing the same test file, for
    104   // instance).
    105   std::string random_number =
    106       base::Uint64ToString(base::RandGenerator(kuint16max));
    107 
    108   base::FilePath file =
    109       directory.AppendASCII(id + "_" + random_number + ".crx");
    110 
    111   int uniquifier =
    112       file_util::GetUniquePathNumber(file, base::FilePath::StringType());
    113   if (uniquifier > 0) {
    114     file = file.InsertBeforeExtensionASCII(
    115         base::StringPrintf(" (%d)", uniquifier));
    116   }
    117 
    118   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    119                           base::Bind(callback, file));
    120 }
    121 
    122 }  // namespace
    123 
    124 namespace extensions {
    125 
    126 // static
    127 GURL WebstoreInstaller::GetWebstoreInstallURL(
    128     const std::string& extension_id, const std::string& install_source) {
    129   CommandLine* cmd_line = CommandLine::ForCurrentProcess();
    130   if (cmd_line->HasSwitch(switches::kAppsGalleryDownloadURL)) {
    131     std::string download_url =
    132         cmd_line->GetSwitchValueASCII(switches::kAppsGalleryDownloadURL);
    133     return GURL(base::StringPrintf(download_url.c_str(),
    134                                    extension_id.c_str()));
    135   }
    136   std::vector<std::string> params;
    137   params.push_back("id=" + extension_id);
    138   if (!install_source.empty())
    139     params.push_back("installsource=" + install_source);
    140   params.push_back("lang=" + g_browser_process->GetApplicationLocale());
    141   params.push_back("uc");
    142   std::string url_string = extension_urls::GetWebstoreUpdateUrl().spec();
    143 
    144   GURL url(url_string + "?response=redirect&" +
    145            OmahaQueryParams::Get(OmahaQueryParams::CRX) + "&x=" +
    146            net::EscapeQueryParamValue(JoinString(params, '&'), true));
    147   DCHECK(url.is_valid());
    148 
    149   return url;
    150 }
    151 
    152 void WebstoreInstaller::Delegate::OnExtensionDownloadStarted(
    153     const std::string& id,
    154     content::DownloadItem* item) {
    155 }
    156 
    157 void WebstoreInstaller::Delegate::OnExtensionDownloadProgress(
    158     const std::string& id,
    159     content::DownloadItem* item) {
    160 }
    161 
    162 WebstoreInstaller::Approval::Approval()
    163     : profile(NULL),
    164       use_app_installed_bubble(false),
    165       skip_post_install_ui(false),
    166       skip_install_dialog(false),
    167       enable_launcher(false) {
    168 }
    169 
    170 scoped_ptr<WebstoreInstaller::Approval>
    171 WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile* profile) {
    172   scoped_ptr<Approval> result(new Approval());
    173   result->profile = profile;
    174   return result.Pass();
    175 }
    176 
    177 scoped_ptr<WebstoreInstaller::Approval>
    178 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
    179     Profile* profile,
    180     const std::string& extension_id,
    181     scoped_ptr<base::DictionaryValue> parsed_manifest) {
    182   scoped_ptr<Approval> result(new Approval());
    183   result->extension_id = extension_id;
    184   result->profile = profile;
    185   result->manifest = scoped_ptr<Manifest>(
    186       new Manifest(Manifest::INVALID_LOCATION,
    187                    scoped_ptr<DictionaryValue>(parsed_manifest->DeepCopy())));
    188   result->skip_install_dialog = true;
    189   return result.Pass();
    190 }
    191 
    192 WebstoreInstaller::Approval::~Approval() {}
    193 
    194 const WebstoreInstaller::Approval* WebstoreInstaller::GetAssociatedApproval(
    195     const DownloadItem& download) {
    196   return static_cast<const Approval*>(download.GetUserData(kApprovalKey));
    197 }
    198 
    199 WebstoreInstaller::WebstoreInstaller(Profile* profile,
    200                                      Delegate* delegate,
    201                                      NavigationController* controller,
    202                                      const std::string& id,
    203                                      scoped_ptr<Approval> approval,
    204                                      int flags)
    205     : profile_(profile),
    206       delegate_(delegate),
    207       controller_(controller),
    208       id_(id),
    209       download_item_(NULL),
    210       approval_(approval.release()) {
    211   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    212   DCHECK(controller_);
    213   download_url_ = GetWebstoreInstallURL(id, flags & FLAG_INLINE_INSTALL ?
    214       kInlineInstallSource : kDefaultInstallSource);
    215 
    216   registrar_.Add(this, chrome::NOTIFICATION_CRX_INSTALLER_DONE,
    217                  content::NotificationService::AllSources());
    218   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
    219                  content::Source<Profile>(profile->GetOriginalProfile()));
    220   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
    221                  content::Source<CrxInstaller>(NULL));
    222 }
    223 
    224 void WebstoreInstaller::Start() {
    225   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    226   AddRef();  // Balanced in ReportSuccess and ReportFailure.
    227 
    228   if (!Extension::IdIsValid(id_)) {
    229     ReportFailure(kInvalidIdError, FAILURE_REASON_OTHER);
    230     return;
    231   }
    232 
    233   base::FilePath download_path = DownloadPrefs::FromDownloadManager(
    234       BrowserContext::GetDownloadManager(profile_))->DownloadPath();
    235   BrowserThread::PostTask(
    236       BrowserThread::FILE, FROM_HERE,
    237       base::Bind(&GetDownloadFilePath, download_path, id_,
    238                  base::Bind(&WebstoreInstaller::StartDownload, this)));
    239 
    240   std::string name;
    241   if (!approval_->manifest->value()->GetString(extension_manifest_keys::kName,
    242                                                &name)) {
    243     NOTREACHED();
    244   }
    245   extensions::InstallTracker* tracker =
    246       extensions::InstallTrackerFactory::GetForProfile(profile_);
    247   tracker->OnBeginExtensionInstall(
    248       id_, name, approval_->installing_icon, approval_->manifest->is_app(),
    249       approval_->manifest->is_platform_app());
    250 }
    251 
    252 void WebstoreInstaller::Observe(int type,
    253                                 const content::NotificationSource& source,
    254                                 const content::NotificationDetails& details) {
    255   switch (type) {
    256     case chrome::NOTIFICATION_CRX_INSTALLER_DONE: {
    257       const Extension* extension =
    258           content::Details<const Extension>(details).ptr();
    259       CrxInstaller* installer = content::Source<CrxInstaller>(source).ptr();
    260       if (extension == NULL && download_item_ != NULL &&
    261           installer->download_url() == download_item_->GetURL() &&
    262           installer->profile()->IsSameProfile(profile_)) {
    263         ReportFailure(kInstallCanceledError, FAILURE_REASON_CANCELLED);
    264       }
    265       break;
    266     }
    267 
    268     case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
    269       CHECK(profile_->IsSameProfile(content::Source<Profile>(source).ptr()));
    270       const Extension* extension =
    271           content::Details<const InstalledExtensionInfo>(details)->extension;
    272       if (id_ == extension->id())
    273         ReportSuccess();
    274       break;
    275     }
    276 
    277     case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
    278       CrxInstaller* crx_installer = content::Source<CrxInstaller>(source).ptr();
    279       CHECK(crx_installer);
    280       if (!profile_->IsSameProfile(crx_installer->profile()))
    281         return;
    282 
    283       // TODO(rdevlin.cronin): Continue removing std::string errors and
    284       // replacing with string16. See crbug.com/71980.
    285       const string16* error = content::Details<const string16>(details).ptr();
    286       const std::string utf8_error = UTF16ToUTF8(*error);
    287       if (download_url_ == crx_installer->original_download_url())
    288         ReportFailure(utf8_error, FAILURE_REASON_OTHER);
    289       break;
    290     }
    291 
    292     default:
    293       NOTREACHED();
    294   }
    295 }
    296 
    297 void WebstoreInstaller::InvalidateDelegate() {
    298   delegate_ = NULL;
    299 }
    300 
    301 void WebstoreInstaller::SetDownloadDirectoryForTests(
    302     base::FilePath* directory) {
    303   g_download_directory_for_tests = directory;
    304 }
    305 
    306 WebstoreInstaller::~WebstoreInstaller() {
    307   controller_ = NULL;
    308   if (download_item_) {
    309     download_item_->RemoveObserver(this);
    310     download_item_ = NULL;
    311   }
    312 }
    313 
    314 void WebstoreInstaller::OnDownloadStarted(
    315     DownloadItem* item, net::Error error) {
    316   if (!item) {
    317     DCHECK_NE(net::OK, error);
    318     ReportFailure(net::ErrorToString(error), FAILURE_REASON_OTHER);
    319     return;
    320   }
    321 
    322   DCHECK_EQ(net::OK, error);
    323   download_item_ = item;
    324   download_item_->AddObserver(this);
    325   if (approval_)
    326     download_item_->SetUserData(kApprovalKey, approval_.release());
    327   if (delegate_)
    328     delegate_->OnExtensionDownloadStarted(id_, download_item_);
    329 }
    330 
    331 void WebstoreInstaller::OnDownloadUpdated(DownloadItem* download) {
    332   CHECK_EQ(download_item_, download);
    333 
    334   switch (download->GetState()) {
    335     case DownloadItem::CANCELLED:
    336       ReportFailure(kDownloadCanceledError, FAILURE_REASON_CANCELLED);
    337       break;
    338     case DownloadItem::INTERRUPTED:
    339       ReportFailure(kDownloadInterruptedError, FAILURE_REASON_OTHER);
    340       break;
    341     case DownloadItem::COMPLETE:
    342       // Wait for other notifications if the download is really an extension.
    343       if (!download_crx_util::IsExtensionDownload(*download)) {
    344         ReportFailure(kInvalidDownloadError, FAILURE_REASON_OTHER);
    345       } else {
    346         if (delegate_)
    347           delegate_->OnExtensionDownloadProgress(id_, download);
    348 
    349         extensions::InstallTracker* tracker =
    350             extensions::InstallTrackerFactory::GetForProfile(profile_);
    351         tracker->OnDownloadProgress(id_, 100);
    352       }
    353       break;
    354     case DownloadItem::IN_PROGRESS: {
    355       if (delegate_)
    356         delegate_->OnExtensionDownloadProgress(id_, download);
    357 
    358       extensions::InstallTracker* tracker =
    359           extensions::InstallTrackerFactory::GetForProfile(profile_);
    360       tracker->OnDownloadProgress(id_, download->PercentComplete());
    361 
    362       break;
    363     }
    364     default:
    365       // Continue listening if the download is not in one of the above states.
    366       break;
    367   }
    368 }
    369 
    370 void WebstoreInstaller::OnDownloadDestroyed(DownloadItem* download) {
    371   CHECK_EQ(download_item_, download);
    372   download_item_->RemoveObserver(this);
    373   download_item_ = NULL;
    374 }
    375 
    376 // http://crbug.com/165634
    377 // http://crbug.com/126013
    378 // The current working theory is that one of the many pointers dereferenced in
    379 // here is occasionally deleted before all of its referers are nullified,
    380 // probably in a callback race. After this comment is released, the crash
    381 // reports should narrow down exactly which pointer it is.  Collapsing all the
    382 // early-returns into a single branch makes it hard to see exactly which pointer
    383 // it is.
    384 void WebstoreInstaller::StartDownload(const base::FilePath& file) {
    385   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    386 
    387   DownloadManager* download_manager =
    388     BrowserContext::GetDownloadManager(profile_);
    389   if (file.empty()) {
    390     ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
    391     return;
    392   }
    393   if (!download_manager) {
    394     ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
    395     return;
    396   }
    397   if (!controller_) {
    398     ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
    399     return;
    400   }
    401   if (!controller_->GetWebContents()) {
    402     ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
    403     return;
    404   }
    405   if (!controller_->GetWebContents()->GetRenderProcessHost()) {
    406     ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
    407     return;
    408   }
    409   if (!controller_->GetWebContents()->GetRenderViewHost()) {
    410     ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
    411     return;
    412   }
    413   if (!controller_->GetBrowserContext()) {
    414     ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
    415     return;
    416   }
    417   if (!controller_->GetBrowserContext()->GetResourceContext()) {
    418     ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
    419     return;
    420   }
    421 
    422   // The download url for the given extension is contained in |download_url_|.
    423   // We will navigate the current tab to this url to start the download. The
    424   // download system will then pass the crx to the CrxInstaller.
    425   RecordDownloadSource(DOWNLOAD_INITIATED_BY_WEBSTORE_INSTALLER);
    426   int render_process_host_id =
    427     controller_->GetWebContents()->GetRenderProcessHost()->GetID();
    428   int render_view_host_routing_id =
    429     controller_->GetWebContents()->GetRenderViewHost()->GetRoutingID();
    430   content::ResourceContext* resource_context =
    431     controller_->GetBrowserContext()->GetResourceContext();
    432   scoped_ptr<DownloadUrlParameters> params(new DownloadUrlParameters(
    433       download_url_,
    434       render_process_host_id,
    435       render_view_host_routing_id ,
    436       resource_context));
    437   params->set_file_path(file);
    438   if (controller_->GetActiveEntry())
    439     params->set_referrer(
    440         content::Referrer(controller_->GetActiveEntry()->GetURL(),
    441                           WebKit::WebReferrerPolicyDefault));
    442   params->set_callback(base::Bind(&WebstoreInstaller::OnDownloadStarted, this));
    443   download_manager->DownloadUrl(params.Pass());
    444 }
    445 
    446 void WebstoreInstaller::ReportFailure(const std::string& error,
    447                                       FailureReason reason) {
    448   if (delegate_) {
    449     delegate_->OnExtensionInstallFailure(id_, error, reason);
    450     delegate_ = NULL;
    451   }
    452 
    453   extensions::InstallTracker* tracker =
    454       extensions::InstallTrackerFactory::GetForProfile(profile_);
    455   tracker->OnInstallFailure(id_);
    456 
    457   Release();  // Balanced in Start().
    458 }
    459 
    460 void WebstoreInstaller::ReportSuccess() {
    461   if (delegate_) {
    462     delegate_->OnExtensionInstallSuccess(id_);
    463     delegate_ = NULL;
    464   }
    465 
    466   Release();  // Balanced in Start().
    467 }
    468 
    469 }  // namespace extensions
    470