webstore_installer.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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/string_number_conversions.h" 13#include "base/string_util.h" 14#include "base/stringprintf.h" 15#include "base/utf_string_conversions.h" 16#include "chrome/browser/browser_process.h" 17#include "chrome/browser/download/download_crx_util.h" 18#include "chrome/browser/download/download_prefs.h" 19#include "chrome/browser/download/download_util.h" 20#include "chrome/browser/extensions/crx_installer.h" 21#include "chrome/browser/profiles/profile.h" 22#include "chrome/browser/ui/browser_list.h" 23#include "chrome/browser/ui/tab_contents/tab_contents.h" 24#include "chrome/browser/ui/tabs/tab_strip_model.h" 25#include "chrome/common/chrome_notification_types.h" 26#include "chrome/common/chrome_switches.h" 27#include "chrome/common/extensions/extension.h" 28#include "chrome/common/extensions/extension_constants.h" 29#include "content/public/browser/browser_thread.h" 30#include "content/public/browser/download_manager.h" 31#include "content/public/browser/download_save_info.h" 32#include "content/public/browser/download_url_parameters.h" 33#include "content/public/browser/navigation_controller.h" 34#include "content/public/browser/navigation_entry.h" 35#include "content/public/browser/notification_details.h" 36#include "content/public/browser/notification_service.h" 37#include "content/public/browser/notification_source.h" 38#include "content/public/browser/web_contents.h" 39#include "googleurl/src/gurl.h" 40#include "net/base/escape.h" 41 42#if defined(OS_CHROMEOS) 43#include "chrome/browser/chromeos/drive/drive_file_system_util.h" 44#endif 45 46using content::BrowserContext; 47using content::BrowserThread; 48using content::DownloadItem; 49using content::DownloadManager; 50using content::NavigationController; 51using content::DownloadUrlParameters; 52 53namespace { 54 55// Key used to attach the Approval to the DownloadItem. 56const char kApprovalKey[] = "extensions.webstore_installer"; 57 58const char kInvalidIdError[] = "Invalid id"; 59const char kNoBrowserError[] = "No browser found"; 60const char kDownloadDirectoryError[] = "Could not create download directory"; 61const char kDownloadCanceledError[] = "Download canceled"; 62const char kInstallCanceledError[] = "Install canceled"; 63const char kDownloadInterruptedError[] = "Download interrupted"; 64const char kInvalidDownloadError[] = "Download was not a CRX"; 65const char kInlineInstallSource[] = "inline"; 66const char kDefaultInstallSource[] = ""; 67 68FilePath* g_download_directory_for_tests = NULL; 69 70GURL GetWebstoreInstallURL( 71 const std::string& extension_id, const std::string& install_source) { 72 CommandLine* cmd_line = CommandLine::ForCurrentProcess(); 73 if (cmd_line->HasSwitch(switches::kAppsGalleryDownloadURL)) { 74 std::string download_url = 75 cmd_line->GetSwitchValueASCII(switches::kAppsGalleryDownloadURL); 76 return GURL(base::StringPrintf(download_url.c_str(), 77 extension_id.c_str())); 78 } 79 std::vector<std::string> params; 80 params.push_back("id=" + extension_id); 81 if (!install_source.empty()) 82 params.push_back("installsource=" + install_source); 83 params.push_back("lang=" + g_browser_process->GetApplicationLocale()); 84 params.push_back("uc"); 85 std::string url_string = extension_urls::GetWebstoreUpdateUrl().spec(); 86 87 GURL url(url_string + "?response=redirect&x=" + 88 net::EscapeQueryParamValue(JoinString(params, '&'), true)); 89 DCHECK(url.is_valid()); 90 91 return url; 92} 93 94// Must be executed on the FILE thread. 95void GetDownloadFilePath( 96 const FilePath& download_directory, const std::string& id, 97 const base::Callback<void(const FilePath&)>& callback) { 98 FilePath directory(g_download_directory_for_tests ? 99 *g_download_directory_for_tests : download_directory); 100 101#if defined (OS_CHROMEOS) 102 // Do not use drive for extension downloads. 103 if (drive::util::IsUnderDriveMountPoint(directory)) 104 directory = download_util::GetDefaultDownloadDirectory(); 105#endif 106 107 // Ensure the download directory exists. TODO(asargent) - make this use 108 // common code from the downloads system. 109 if (!file_util::DirectoryExists(directory)) { 110 if (!file_util::CreateDirectory(directory)) { 111 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 112 base::Bind(callback, FilePath())); 113 return; 114 } 115 } 116 117 // This is to help avoid a race condition between when we generate this 118 // filename and when the download starts writing to it (think concurrently 119 // running sharded browser tests installing the same test file, for 120 // instance). 121 std::string random_number = 122 base::Uint64ToString(base::RandGenerator(kuint16max)); 123 124 FilePath file = directory.AppendASCII(id + "_" + random_number + ".crx"); 125 126 int uniquifier = file_util::GetUniquePathNumber(file, FILE_PATH_LITERAL("")); 127 if (uniquifier > 0) 128 file = file.InsertBeforeExtensionASCII(StringPrintf(" (%d)", uniquifier)); 129 130 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 131 base::Bind(callback, file)); 132} 133 134} // namespace 135 136namespace extensions { 137 138void WebstoreInstaller::Delegate::OnExtensionDownloadStarted( 139 const std::string& id, 140 content::DownloadItem* item) { 141} 142 143void WebstoreInstaller::Delegate::OnExtensionDownloadProgress( 144 const std::string& id, 145 content::DownloadItem* item) { 146} 147 148WebstoreInstaller::Approval::Approval() 149 : profile(NULL), 150 use_app_installed_bubble(false), 151 skip_post_install_ui(false), 152 skip_install_dialog(false), 153 record_oauth2_grant(false) { 154} 155 156scoped_ptr<WebstoreInstaller::Approval> 157WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile* profile) { 158 scoped_ptr<Approval> result(new Approval()); 159 result->profile = profile; 160 return result.Pass(); 161} 162 163scoped_ptr<WebstoreInstaller::Approval> 164WebstoreInstaller::Approval::CreateWithNoInstallPrompt( 165 Profile* profile, 166 const std::string& extension_id, 167 scoped_ptr<base::DictionaryValue> parsed_manifest) { 168 scoped_ptr<Approval> result(new Approval()); 169 result->extension_id = extension_id; 170 result->profile = profile; 171 result->parsed_manifest = parsed_manifest.Pass(); 172 result->skip_install_dialog = true; 173 return result.Pass(); 174} 175 176WebstoreInstaller::Approval::~Approval() {} 177 178const WebstoreInstaller::Approval* WebstoreInstaller::GetAssociatedApproval( 179 const DownloadItem& download) { 180 return static_cast<const Approval*>(download.GetUserData(kApprovalKey)); 181} 182 183WebstoreInstaller::WebstoreInstaller(Profile* profile, 184 Delegate* delegate, 185 NavigationController* controller, 186 const std::string& id, 187 scoped_ptr<Approval> approval, 188 int flags) 189 : profile_(profile), 190 delegate_(delegate), 191 controller_(controller), 192 id_(id), 193 download_item_(NULL), 194 approval_(approval.release()) { 195 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 196 DCHECK(controller_); 197 download_url_ = GetWebstoreInstallURL(id, flags & FLAG_INLINE_INSTALL ? 198 kInlineInstallSource : kDefaultInstallSource); 199 200 registrar_.Add(this, chrome::NOTIFICATION_CRX_INSTALLER_DONE, 201 content::NotificationService::AllSources()); 202 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, 203 content::Source<Profile>(profile->GetOriginalProfile())); 204 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR, 205 content::Source<CrxInstaller>(NULL)); 206} 207 208void WebstoreInstaller::Start() { 209 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 210 AddRef(); // Balanced in ReportSuccess and ReportFailure. 211 212 if (!Extension::IdIsValid(id_)) { 213 ReportFailure(kInvalidIdError, FAILURE_REASON_OTHER); 214 return; 215 } 216 217 FilePath download_path = DownloadPrefs::FromDownloadManager( 218 BrowserContext::GetDownloadManager(profile_))->DownloadPath(); 219 BrowserThread::PostTask( 220 BrowserThread::FILE, FROM_HERE, 221 base::Bind(&GetDownloadFilePath, download_path, id_, 222 base::Bind(&WebstoreInstaller::StartDownload, this))); 223} 224 225void WebstoreInstaller::Observe(int type, 226 const content::NotificationSource& source, 227 const content::NotificationDetails& details) { 228 switch (type) { 229 case chrome::NOTIFICATION_CRX_INSTALLER_DONE: { 230 const Extension* extension = 231 content::Details<const Extension>(details).ptr(); 232 CrxInstaller* installer = content::Source<CrxInstaller>(source).ptr(); 233 if (extension == NULL && download_item_ != NULL && 234 installer->download_url() == download_item_->GetURL() && 235 installer->profile()->IsSameProfile(profile_)) { 236 ReportFailure(kInstallCanceledError, FAILURE_REASON_CANCELLED); 237 } 238 break; 239 } 240 241 case chrome::NOTIFICATION_EXTENSION_INSTALLED: { 242 CHECK(profile_->IsSameProfile(content::Source<Profile>(source).ptr())); 243 const Extension* extension = 244 content::Details<const Extension>(details).ptr(); 245 if (id_ == extension->id()) 246 ReportSuccess(); 247 break; 248 } 249 250 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: { 251 CrxInstaller* crx_installer = content::Source<CrxInstaller>(source).ptr(); 252 CHECK(crx_installer); 253 if (!profile_->IsSameProfile(crx_installer->profile())) 254 return; 255 256 // TODO(rdevlin.cronin): Continue removing std::string errors and 257 // replacing with string16 258 const string16* error = content::Details<const string16>(details).ptr(); 259 const std::string utf8_error = UTF16ToUTF8(*error); 260 if (download_url_ == crx_installer->original_download_url()) 261 ReportFailure(utf8_error, FAILURE_REASON_OTHER); 262 break; 263 } 264 265 default: 266 NOTREACHED(); 267 } 268} 269 270void WebstoreInstaller::InvalidateDelegate() { 271 delegate_ = NULL; 272} 273 274void WebstoreInstaller::SetDownloadDirectoryForTests(FilePath* directory) { 275 g_download_directory_for_tests = directory; 276} 277 278WebstoreInstaller::~WebstoreInstaller() { 279 if (download_item_) { 280 download_item_->RemoveObserver(this); 281 download_item_ = NULL; 282 } 283} 284 285void WebstoreInstaller::OnDownloadStarted( 286 DownloadItem* item, net::Error error) { 287 if (!item) { 288 DCHECK_NE(net::OK, error); 289 ReportFailure(net::ErrorToString(error), FAILURE_REASON_OTHER); 290 return; 291 } 292 293 DCHECK_EQ(net::OK, error); 294 download_item_ = item; 295 download_item_->AddObserver(this); 296 if (approval_.get()) 297 download_item_->SetUserData(kApprovalKey, approval_.release()); 298 if (delegate_) 299 delegate_->OnExtensionDownloadStarted(id_, download_item_); 300} 301 302void WebstoreInstaller::OnDownloadUpdated(DownloadItem* download) { 303 CHECK_EQ(download_item_, download); 304 305 switch (download->GetState()) { 306 case DownloadItem::CANCELLED: 307 ReportFailure(kDownloadCanceledError, FAILURE_REASON_CANCELLED); 308 break; 309 case DownloadItem::INTERRUPTED: 310 ReportFailure(kDownloadInterruptedError, FAILURE_REASON_OTHER); 311 break; 312 case DownloadItem::COMPLETE: 313 // Wait for other notifications if the download is really an extension. 314 if (!download_crx_util::IsExtensionDownload(*download)) 315 ReportFailure(kInvalidDownloadError, FAILURE_REASON_OTHER); 316 else if (delegate_) 317 delegate_->OnExtensionDownloadProgress(id_, download); 318 break; 319 case DownloadItem::IN_PROGRESS: 320 if (delegate_) 321 delegate_->OnExtensionDownloadProgress(id_, download); 322 break; 323 default: 324 // Continue listening if the download is not in one of the above states. 325 break; 326 } 327} 328 329void WebstoreInstaller::OnDownloadDestroyed(DownloadItem* download) { 330 CHECK_EQ(download_item_, download); 331 download_item_->RemoveObserver(this); 332 download_item_ = NULL; 333} 334 335void WebstoreInstaller::StartDownload(const FilePath& file) { 336 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 337 338 DownloadManager* download_manager = 339 BrowserContext::GetDownloadManager(profile_); 340 if (file.empty() || 341 !download_manager || 342 !controller_->GetWebContents() || 343 !controller_->GetWebContents()->GetRenderProcessHost() || 344 !controller_->GetWebContents()->GetRenderViewHost() || 345 !controller_->GetWebContents()->GetBrowserContext() || 346 !controller_->GetWebContents()->GetBrowserContext() 347 ->GetResourceContext()) { 348 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER); 349 return; 350 } 351 352 // The download url for the given extension is contained in |download_url_|. 353 // We will navigate the current tab to this url to start the download. The 354 // download system will then pass the crx to the CrxInstaller. 355 download_util::RecordDownloadSource( 356 download_util::INITIATED_BY_WEBSTORE_INSTALLER); 357 scoped_ptr<DownloadUrlParameters> params( 358 DownloadUrlParameters::FromWebContents( 359 controller_->GetWebContents(), download_url_)); 360 params->set_file_path(file); 361 if (controller_->GetActiveEntry()) 362 params->set_referrer( 363 content::Referrer(controller_->GetActiveEntry()->GetURL(), 364 WebKit::WebReferrerPolicyDefault)); 365 params->set_callback(base::Bind(&WebstoreInstaller::OnDownloadStarted, this)); 366 download_manager->DownloadUrl(params.Pass()); 367} 368 369void WebstoreInstaller::ReportFailure(const std::string& error, 370 FailureReason reason) { 371 if (delegate_) { 372 delegate_->OnExtensionInstallFailure(id_, error, reason); 373 delegate_ = NULL; 374 } 375 376 Release(); // Balanced in Start(). 377} 378 379void WebstoreInstaller::ReportSuccess() { 380 if (delegate_) { 381 delegate_->OnExtensionInstallSuccess(id_); 382 delegate_ = NULL; 383 } 384 385 Release(); // Balanced in Start(). 386} 387 388} // namespace extensions 389