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