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.
5 #include "chrome/browser/extensions/webstore_installer.h"
9 #include "base/basictypes.h"
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/file_util.h"
13 #include "base/rand_util.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/chrome_notification_types.h"
20 #include "chrome/browser/download/download_crx_util.h"
21 #include "chrome/browser/download/download_prefs.h"
22 #include "chrome/browser/download/download_stats.h"
23 #include "chrome/browser/extensions/crx_installer.h"
24 #include "chrome/browser/extensions/extension_system.h"
25 #include "chrome/browser/extensions/install_tracker.h"
26 #include "chrome/browser/extensions/install_tracker_factory.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/ui/browser_list.h"
29 #include "chrome/browser/ui/tabs/tab_strip_model.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chrome/common/extensions/extension.h"
32 #include "chrome/common/extensions/extension_constants.h"
33 #include "chrome/common/extensions/manifest_handlers/shared_module_info.h"
34 #include "chrome/common/omaha_query_params/omaha_query_params.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/browser/download_manager.h"
37 #include "content/public/browser/download_save_info.h"
38 #include "content/public/browser/download_url_parameters.h"
39 #include "content/public/browser/navigation_controller.h"
40 #include "content/public/browser/navigation_entry.h"
41 #include "content/public/browser/notification_details.h"
42 #include "content/public/browser/notification_service.h"
43 #include "content/public/browser/notification_source.h"
44 #include "content/public/browser/render_process_host.h"
45 #include "content/public/browser/render_view_host.h"
46 #include "content/public/browser/web_contents.h"
47 #include "extensions/common/manifest_constants.h"
48 #include "net/base/escape.h"
51 #if defined(OS_CHROMEOS)
52 #include "chrome/browser/chromeos/drive/file_system_util.h"
55 using chrome::OmahaQueryParams;
56 using content::BrowserContext;
57 using content::BrowserThread;
58 using content::DownloadItem;
59 using content::DownloadManager;
60 using content::NavigationController;
61 using content::DownloadUrlParameters;
65 // Key used to attach the Approval to the DownloadItem.
66 const char kApprovalKey[] = "extensions.webstore_installer";
68 const char kInvalidIdError[] = "Invalid id";
69 const char kDownloadDirectoryError[] = "Could not create download directory";
70 const char kDownloadCanceledError[] = "Download canceled";
71 const char kInstallCanceledError[] = "Install canceled";
72 const char kDownloadInterruptedError[] = "Download interrupted";
73 const char kInvalidDownloadError[] =
74 "Download was not a valid extension or user script";
75 const char kDependencyNotFoundError[] = "Dependency not found";
76 const char kDependencyNotSharedModuleError[] =
77 "Dependency is not shared module";
78 const char kInlineInstallSource[] = "inline";
79 const char kDefaultInstallSource[] = "ondemand";
80 const char kAppLauncherInstallSource[] = "applauncher";
82 base::FilePath* g_download_directory_for_tests = NULL;
84 // Must be executed on the FILE thread.
85 void GetDownloadFilePath(
86 const base::FilePath& download_directory, const std::string& id,
87 const base::Callback<void(const base::FilePath&)>& callback) {
88 base::FilePath directory(g_download_directory_for_tests ?
89 *g_download_directory_for_tests : download_directory);
91 #if defined(OS_CHROMEOS)
92 // Do not use drive for extension downloads.
93 if (drive::util::IsUnderDriveMountPoint(directory))
94 directory = DownloadPrefs::GetDefaultDownloadDirectory();
97 // Ensure the download directory exists. TODO(asargent) - make this use
98 // common code from the downloads system.
99 if (!base::DirectoryExists(directory)) {
100 if (!file_util::CreateDirectory(directory)) {
101 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
102 base::Bind(callback, base::FilePath()));
107 // This is to help avoid a race condition between when we generate this
108 // filename and when the download starts writing to it (think concurrently
109 // running sharded browser tests installing the same test file, for
111 std::string random_number =
112 base::Uint64ToString(base::RandGenerator(kuint16max));
114 base::FilePath file =
115 directory.AppendASCII(id + "_" + random_number + ".crx");
118 file_util::GetUniquePathNumber(file, base::FilePath::StringType());
119 if (uniquifier > 0) {
120 file = file.InsertBeforeExtensionASCII(
121 base::StringPrintf(" (%d)", uniquifier));
124 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
125 base::Bind(callback, file));
130 namespace extensions {
133 GURL WebstoreInstaller::GetWebstoreInstallURL(
134 const std::string& extension_id, InstallSource source) {
135 std::string install_source;
137 case INSTALL_SOURCE_INLINE:
138 install_source = kInlineInstallSource;
140 case INSTALL_SOURCE_APP_LAUNCHER:
141 install_source = kAppLauncherInstallSource;
143 case INSTALL_SOURCE_OTHER:
144 install_source = kDefaultInstallSource;
147 CommandLine* cmd_line = CommandLine::ForCurrentProcess();
148 if (cmd_line->HasSwitch(switches::kAppsGalleryDownloadURL)) {
149 std::string download_url =
150 cmd_line->GetSwitchValueASCII(switches::kAppsGalleryDownloadURL);
151 return GURL(base::StringPrintf(download_url.c_str(),
152 extension_id.c_str()));
154 std::vector<std::string> params;
155 params.push_back("id=" + extension_id);
156 if (!install_source.empty())
157 params.push_back("installsource=" + install_source);
158 params.push_back("lang=" + g_browser_process->GetApplicationLocale());
159 params.push_back("uc");
160 std::string url_string = extension_urls::GetWebstoreUpdateUrl().spec();
162 GURL url(url_string + "?response=redirect&" +
163 OmahaQueryParams::Get(OmahaQueryParams::CRX) + "&x=" +
164 net::EscapeQueryParamValue(JoinString(params, '&'), true));
165 DCHECK(url.is_valid());
170 void WebstoreInstaller::Delegate::OnExtensionDownloadStarted(
171 const std::string& id,
172 content::DownloadItem* item) {
175 void WebstoreInstaller::Delegate::OnExtensionDownloadProgress(
176 const std::string& id,
177 content::DownloadItem* item) {
180 WebstoreInstaller::Approval::Approval()
182 use_app_installed_bubble(false),
183 skip_post_install_ui(false),
184 skip_install_dialog(false),
185 enable_launcher(false),
186 manifest_check_level(MANIFEST_CHECK_LEVEL_STRICT) {
189 scoped_ptr<WebstoreInstaller::Approval>
190 WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile* profile) {
191 scoped_ptr<Approval> result(new Approval());
192 result->profile = profile;
193 return result.Pass();
196 scoped_ptr<WebstoreInstaller::Approval>
197 WebstoreInstaller::Approval::CreateForSharedModule(Profile* profile) {
198 scoped_ptr<Approval> result(new Approval());
199 result->profile = profile;
200 result->skip_install_dialog = true;
201 result->manifest_check_level = MANIFEST_CHECK_LEVEL_NONE;
202 return result.Pass();
205 scoped_ptr<WebstoreInstaller::Approval>
206 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
208 const std::string& extension_id,
209 scoped_ptr<base::DictionaryValue> parsed_manifest,
210 bool strict_manifest_check) {
211 scoped_ptr<Approval> result(new Approval());
212 result->extension_id = extension_id;
213 result->profile = profile;
214 result->manifest = scoped_ptr<Manifest>(
215 new Manifest(Manifest::INVALID_LOCATION,
216 scoped_ptr<DictionaryValue>(parsed_manifest->DeepCopy())));
217 result->skip_install_dialog = true;
218 result->manifest_check_level = strict_manifest_check ?
219 MANIFEST_CHECK_LEVEL_STRICT : MANIFEST_CHECK_LEVEL_LOOSE;
220 return result.Pass();
223 WebstoreInstaller::Approval::~Approval() {}
225 const WebstoreInstaller::Approval* WebstoreInstaller::GetAssociatedApproval(
226 const DownloadItem& download) {
227 return static_cast<const Approval*>(download.GetUserData(kApprovalKey));
230 WebstoreInstaller::WebstoreInstaller(Profile* profile,
232 NavigationController* controller,
233 const std::string& id,
234 scoped_ptr<Approval> approval,
235 InstallSource source)
238 controller_(controller),
240 install_source_(source),
241 download_item_(NULL),
242 approval_(approval.release()),
244 download_started_(false) {
245 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
248 registrar_.Add(this, chrome::NOTIFICATION_CRX_INSTALLER_DONE,
249 content::NotificationService::AllSources());
250 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
251 content::Source<Profile>(profile->GetOriginalProfile()));
252 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
253 content::Source<CrxInstaller>(NULL));
256 void WebstoreInstaller::Start() {
257 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
258 AddRef(); // Balanced in ReportSuccess and ReportFailure.
260 if (!Extension::IdIsValid(id_)) {
261 ReportFailure(kInvalidIdError, FAILURE_REASON_OTHER);
265 ExtensionService* extension_service =
266 ExtensionSystem::Get(profile_)->extension_service();
267 if (approval_.get() && approval_->dummy_extension) {
268 ExtensionService::ImportStatus status =
269 extension_service->CheckImports(approval_->dummy_extension,
270 &pending_modules_, &pending_modules_);
271 // For this case, it is because some imports are not shared modules.
272 if (status == ExtensionService::IMPORT_STATUS_UNRECOVERABLE) {
273 ReportFailure(kDependencyNotSharedModuleError,
274 FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE);
279 // Add the extension main module into the list.
280 SharedModuleInfo::ImportInfo info;
281 info.extension_id = id_;
282 pending_modules_.push_back(info);
284 total_modules_ = pending_modules_.size();
286 // TODO(crbug.com/305343): Query manifest of dependencises before
287 // downloading & installing those dependencies.
288 DownloadNextPendingModule();
291 if (!approval_->manifest->value()->GetString(manifest_keys::kName, &name)) {
294 extensions::InstallTracker* tracker =
295 extensions::InstallTrackerFactory::GetForProfile(profile_);
296 tracker->OnBeginExtensionInstall(
297 id_, name, approval_->installing_icon, approval_->manifest->is_app(),
298 approval_->manifest->is_platform_app());
301 void WebstoreInstaller::Observe(int type,
302 const content::NotificationSource& source,
303 const content::NotificationDetails& details) {
305 case chrome::NOTIFICATION_CRX_INSTALLER_DONE: {
306 const Extension* extension =
307 content::Details<const Extension>(details).ptr();
308 CrxInstaller* installer = content::Source<CrxInstaller>(source).ptr();
309 if (extension == NULL && download_item_ != NULL &&
310 installer->download_url() == download_item_->GetURL() &&
311 installer->profile()->IsSameProfile(profile_)) {
312 ReportFailure(kInstallCanceledError, FAILURE_REASON_CANCELLED);
317 case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
318 CHECK(profile_->IsSameProfile(content::Source<Profile>(source).ptr()));
319 const Extension* extension =
320 content::Details<const InstalledExtensionInfo>(details)->extension;
321 if (pending_modules_.empty())
323 SharedModuleInfo::ImportInfo info = pending_modules_.front();
324 if (extension->id() != info.extension_id)
326 pending_modules_.pop_front();
328 if (pending_modules_.empty()) {
329 CHECK_EQ(extension->id(), id_);
332 const Version version_required(info.minimum_version);
333 if (version_required.IsValid() &&
334 extension->version()->CompareTo(version_required) < 0) {
335 // It should not happen, CrxInstaller will make sure the version is
336 // equal or newer than version_required.
337 ReportFailure(kDependencyNotFoundError,
338 FAILURE_REASON_DEPENDENCY_NOT_FOUND);
339 } else if (!SharedModuleInfo::IsSharedModule(extension)) {
340 // It should not happen, CrxInstaller will make sure it is a shared
342 ReportFailure(kDependencyNotSharedModuleError,
343 FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE);
345 DownloadNextPendingModule();
351 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
352 CrxInstaller* crx_installer = content::Source<CrxInstaller>(source).ptr();
353 CHECK(crx_installer);
354 if (!profile_->IsSameProfile(crx_installer->profile()))
357 // TODO(rdevlin.cronin): Continue removing std::string errors and
358 // replacing with string16. See crbug.com/71980.
359 const string16* error = content::Details<const string16>(details).ptr();
360 const std::string utf8_error = UTF16ToUTF8(*error);
361 if (download_url_ == crx_installer->original_download_url())
362 ReportFailure(utf8_error, FAILURE_REASON_OTHER);
371 void WebstoreInstaller::InvalidateDelegate() {
375 void WebstoreInstaller::SetDownloadDirectoryForTests(
376 base::FilePath* directory) {
377 g_download_directory_for_tests = directory;
380 WebstoreInstaller::~WebstoreInstaller() {
382 if (download_item_) {
383 download_item_->RemoveObserver(this);
384 download_item_ = NULL;
388 void WebstoreInstaller::OnDownloadStarted(
389 DownloadItem* item, net::Error error) {
391 DCHECK_NE(net::OK, error);
392 ReportFailure(net::ErrorToString(error), FAILURE_REASON_OTHER);
396 DCHECK_EQ(net::OK, error);
397 DCHECK(!pending_modules_.empty());
398 download_item_ = item;
399 download_item_->AddObserver(this);
400 if (pending_modules_.size() > 1) {
401 // We are downloading a shared module. We need create an approval for it.
402 scoped_ptr<Approval> approval = Approval::CreateForSharedModule(profile_);
403 const SharedModuleInfo::ImportInfo& info = pending_modules_.front();
404 approval->extension_id = info.extension_id;
405 const Version version_required(info.minimum_version);
407 if (version_required.IsValid()) {
408 approval->minimum_version.reset(
409 new Version(version_required));
411 download_item_->SetUserData(kApprovalKey, approval.release());
413 // It is for the main module of the extension. We should use the provided
416 download_item_->SetUserData(kApprovalKey, approval_.release());
419 if (!download_started_) {
421 delegate_->OnExtensionDownloadStarted(id_, download_item_);
422 download_started_ = true;
426 void WebstoreInstaller::OnDownloadUpdated(DownloadItem* download) {
427 CHECK_EQ(download_item_, download);
429 switch (download->GetState()) {
430 case DownloadItem::CANCELLED:
431 ReportFailure(kDownloadCanceledError, FAILURE_REASON_CANCELLED);
433 case DownloadItem::INTERRUPTED:
434 ReportFailure(kDownloadInterruptedError, FAILURE_REASON_OTHER);
436 case DownloadItem::COMPLETE:
437 // Wait for other notifications if the download is really an extension.
438 if (!download_crx_util::IsExtensionDownload(*download)) {
439 ReportFailure(kInvalidDownloadError, FAILURE_REASON_OTHER);
440 } else if (pending_modules_.empty()) {
441 // The download is the last module - the extension main module.
443 delegate_->OnExtensionDownloadProgress(id_, download);
444 extensions::InstallTracker* tracker =
445 extensions::InstallTrackerFactory::GetForProfile(profile_);
446 tracker->OnDownloadProgress(id_, 100);
449 case DownloadItem::IN_PROGRESS: {
450 if (delegate_ && pending_modules_.size() == 1) {
451 // Only report download progress for the main module to |delegrate_|.
452 delegate_->OnExtensionDownloadProgress(id_, download);
454 int percent = download->PercentComplete();
455 // Only report progress if precent is more than 0
457 int finished_modules = total_modules_ - pending_modules_.size();
458 percent = (percent + finished_modules * 100) / total_modules_;
459 extensions::InstallTracker* tracker =
460 extensions::InstallTrackerFactory::GetForProfile(profile_);
461 tracker->OnDownloadProgress(id_, percent);
466 // Continue listening if the download is not in one of the above states.
471 void WebstoreInstaller::OnDownloadDestroyed(DownloadItem* download) {
472 CHECK_EQ(download_item_, download);
473 download_item_->RemoveObserver(this);
474 download_item_ = NULL;
477 void WebstoreInstaller::DownloadNextPendingModule() {
478 CHECK(!pending_modules_.empty());
479 if (pending_modules_.size() == 1) {
480 DCHECK_EQ(id_, pending_modules_.front().extension_id);
481 DownloadCrx(id_, install_source_);
483 DownloadCrx(pending_modules_.front().extension_id, INSTALL_SOURCE_OTHER);
487 void WebstoreInstaller::DownloadCrx(
488 const std::string& extension_id, InstallSource source) {
489 download_url_ = GetWebstoreInstallURL(extension_id, source);
490 base::FilePath download_path = DownloadPrefs::FromDownloadManager(
491 BrowserContext::GetDownloadManager(profile_))->DownloadPath();
492 BrowserThread::PostTask(
493 BrowserThread::FILE, FROM_HERE,
494 base::Bind(&GetDownloadFilePath, download_path, id_,
495 base::Bind(&WebstoreInstaller::StartDownload, this)));
498 // http://crbug.com/165634
499 // http://crbug.com/126013
500 // The current working theory is that one of the many pointers dereferenced in
501 // here is occasionally deleted before all of its referers are nullified,
502 // probably in a callback race. After this comment is released, the crash
503 // reports should narrow down exactly which pointer it is. Collapsing all the
504 // early-returns into a single branch makes it hard to see exactly which pointer
506 void WebstoreInstaller::StartDownload(const base::FilePath& file) {
507 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
509 DownloadManager* download_manager =
510 BrowserContext::GetDownloadManager(profile_);
512 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
515 if (!download_manager) {
516 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
520 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
523 if (!controller_->GetWebContents()) {
524 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
527 if (!controller_->GetWebContents()->GetRenderProcessHost()) {
528 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
531 if (!controller_->GetWebContents()->GetRenderViewHost()) {
532 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
535 if (!controller_->GetBrowserContext()) {
536 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
539 if (!controller_->GetBrowserContext()->GetResourceContext()) {
540 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
544 // The download url for the given extension is contained in |download_url_|.
545 // We will navigate the current tab to this url to start the download. The
546 // download system will then pass the crx to the CrxInstaller.
547 RecordDownloadSource(DOWNLOAD_INITIATED_BY_WEBSTORE_INSTALLER);
548 int render_process_host_id =
549 controller_->GetWebContents()->GetRenderProcessHost()->GetID();
550 int render_view_host_routing_id =
551 controller_->GetWebContents()->GetRenderViewHost()->GetRoutingID();
552 content::ResourceContext* resource_context =
553 controller_->GetBrowserContext()->GetResourceContext();
554 scoped_ptr<DownloadUrlParameters> params(new DownloadUrlParameters(
556 render_process_host_id,
557 render_view_host_routing_id ,
559 params->set_file_path(file);
560 if (controller_->GetVisibleEntry())
561 params->set_referrer(
562 content::Referrer(controller_->GetVisibleEntry()->GetURL(),
563 WebKit::WebReferrerPolicyDefault));
564 params->set_callback(base::Bind(&WebstoreInstaller::OnDownloadStarted, this));
565 download_manager->DownloadUrl(params.Pass());
568 void WebstoreInstaller::ReportFailure(const std::string& error,
569 FailureReason reason) {
571 delegate_->OnExtensionInstallFailure(id_, error, reason);
575 extensions::InstallTracker* tracker =
576 extensions::InstallTrackerFactory::GetForProfile(profile_);
577 tracker->OnInstallFailure(id_);
579 Release(); // Balanced in Start().
582 void WebstoreInstaller::ReportSuccess() {
584 delegate_->OnExtensionInstallSuccess(id_);
588 Release(); // Balanced in Start().
591 } // namespace extensions