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 <stddef.h>
8 #include <stdint.h>
9 #include <limits>
10 #include <set>
11 #include <utility>
12 #include <vector>
13
14 #include "base/bind.h"
15 #include "base/command_line.h"
16 #include "base/files/file_util.h"
17 #include "base/metrics/field_trial.h"
18 #include "base/metrics/histogram_functions.h"
19 #include "base/metrics/histogram_macros.h"
20 #include "base/path_service.h"
21 #include "base/rand_util.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/string_piece.h"
24 #include "base/strings/string_util.h"
25 #include "base/strings/stringprintf.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/task/post_task.h"
28 #include "base/time/time.h"
29 #include "build/build_config.h"
30 #include "chrome/browser/chrome_notification_types.h"
31 #include "chrome/browser/download/download_crx_util.h"
32 #include "chrome/browser/download/download_prefs.h"
33 #include "chrome/browser/download/download_stats.h"
34 #include "chrome/browser/extensions/crx_installer.h"
35 #include "chrome/browser/extensions/install_tracker.h"
36 #include "chrome/browser/extensions/install_tracker_factory.h"
37 #include "chrome/browser/extensions/install_verifier.h"
38 #include "chrome/browser/extensions/shared_module_service.h"
39 #include "chrome/browser/profiles/profile.h"
40 #include "chrome/common/chrome_paths.h"
41 #include "chrome/common/chrome_switches.h"
42 #include "chrome/grit/generated_resources.h"
43 #include "components/crx_file/id_util.h"
44 #include "components/download/public/common/download_url_parameters.h"
45 #include "components/update_client/update_query_params.h"
46 #include "content/public/browser/browser_thread.h"
47 #include "content/public/browser/download_manager.h"
48 #include "content/public/browser/navigation_controller.h"
49 #include "content/public/browser/navigation_entry.h"
50 #include "content/public/browser/notification_details.h"
51 #include "content/public/browser/notification_service.h"
52 #include "content/public/browser/notification_source.h"
53 #include "content/public/browser/render_frame_host.h"
54 #include "content/public/browser/render_process_host.h"
55 #include "content/public/browser/render_view_host.h"
56 #include "content/public/browser/web_contents.h"
57 #include "extensions/browser/extension_file_task_runner.h"
58 #include "extensions/browser/extension_system.h"
59 #include "extensions/browser/install/crx_install_error.h"
60 #include "extensions/common/extension.h"
61 #include "extensions/common/extension_urls.h"
62 #include "extensions/common/manifest_constants.h"
63 #include "extensions/common/manifest_handlers/shared_module_info.h"
64 #include "net/base/escape.h"
65 #include "net/traffic_annotation/network_traffic_annotation.h"
66 #include "ui/base/l10n/l10n_util.h"
67 #include "url/gurl.h"
68
69 using content::BrowserContext;
70 using content::BrowserThread;
71 using content::DownloadManager;
72 using content::NavigationController;
73 using download::DownloadItem;
74 using download::DownloadUrlParameters;
75
76 namespace {
77
78 // Key used to attach the Approval to the DownloadItem.
79 const char kApprovalKey[] = "extensions.webstore_installer";
80
81 const char kInvalidIdError[] = "Invalid id";
82 const char kDownloadDirectoryError[] = "Could not create download directory";
83 const char kDownloadCanceledError[] = "Download canceled";
84 const char kDownloadInterruptedError[] = "Download interrupted";
85 const char kInvalidDownloadError[] =
86 "Download was not a valid extension or user script";
87 const char kDependencyNotFoundError[] = "Dependency not found";
88 const char kDependencyNotSharedModuleError[] =
89 "Dependency is not shared module";
90 const char kInlineInstallSource[] = "inline";
91 const char kDefaultInstallSource[] = "ondemand";
92 const char kAppLauncherInstallSource[] = "applauncher";
93
94 // TODO(rockot): Share this duplicated constant with the extension updater.
95 // See http://crbug.com/371398.
96 const char kAuthUserQueryKey[] = "authuser";
97
98 constexpr base::TimeDelta kTimeRemainingThreshold =
99 base::TimeDelta::FromSeconds(1);
100
101 // Folder for downloading crx files from the webstore. This is used so that the
102 // crx files don't go via the usual downloads folder.
103 const base::FilePath::CharType kWebstoreDownloadFolder[] =
104 FILE_PATH_LITERAL("Webstore Downloads");
105
106 base::FilePath* g_download_directory_for_tests = nullptr;
107
GetDownloadFilePath(const base::FilePath & download_directory,const std::string & id)108 base::FilePath GetDownloadFilePath(const base::FilePath& download_directory,
109 const std::string& id) {
110 // Ensure the download directory exists. TODO(asargent) - make this use
111 // common code from the downloads system.
112 if (!base::DirectoryExists(download_directory) &&
113 !base::CreateDirectory(download_directory)) {
114 return base::FilePath();
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 = base::NumberToString(
122 base::RandGenerator(std::numeric_limits<uint16_t>::max()));
123
124 base::FilePath file =
125 download_directory.AppendASCII(id + "_" + random_number + ".crx");
126
127 return base::GetUniquePath(file);
128 }
129
MaybeAppendAuthUserParameter(const std::string & authuser,GURL * url)130 void MaybeAppendAuthUserParameter(const std::string& authuser, GURL* url) {
131 if (authuser.empty())
132 return;
133 std::string old_query = url->query();
134 url::Component query(0, old_query.length());
135 url::Component key, value;
136 // Ensure that the URL doesn't already specify an authuser parameter.
137 while (url::ExtractQueryKeyValue(
138 old_query.c_str(), &query, &key, &value)) {
139 std::string key_string = old_query.substr(key.begin, key.len);
140 if (key_string == kAuthUserQueryKey) {
141 return;
142 }
143 }
144 if (!old_query.empty()) {
145 old_query += "&";
146 }
147 std::string authuser_param = base::StringPrintf(
148 "%s=%s",
149 kAuthUserQueryKey,
150 authuser.c_str());
151
152 // TODO(rockot): Share this duplicated code with the extension updater.
153 // See http://crbug.com/371398.
154 std::string new_query_string = old_query + authuser_param;
155 url::Component new_query(0, new_query_string.length());
156 url::Replacements<char> replacements;
157 replacements.SetQuery(new_query_string.c_str(), new_query);
158 *url = url->ReplaceComponents(replacements);
159 }
160
GetErrorMessageForDownloadInterrupt(download::DownloadInterruptReason reason)161 std::string GetErrorMessageForDownloadInterrupt(
162 download::DownloadInterruptReason reason) {
163 switch (reason) {
164 case download::DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED:
165 case download::DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN:
166 return l10n_util::GetStringUTF8(IDS_WEBSTORE_DOWNLOAD_ACCESS_DENIED);
167 default:
168 break;
169 }
170 return kDownloadInterruptedError;
171 }
172
173 } // namespace
174
175 namespace extensions {
176
177 // static
GetWebstoreInstallURL(const std::string & extension_id,InstallSource source)178 GURL WebstoreInstaller::GetWebstoreInstallURL(
179 const std::string& extension_id,
180 InstallSource source) {
181 std::string install_source;
182 switch (source) {
183 case INSTALL_SOURCE_INLINE:
184 install_source = kInlineInstallSource;
185 break;
186 case INSTALL_SOURCE_APP_LAUNCHER:
187 install_source = kAppLauncherInstallSource;
188 break;
189 case INSTALL_SOURCE_OTHER:
190 install_source = kDefaultInstallSource;
191 }
192
193 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
194 if (cmd_line->HasSwitch(::switches::kAppsGalleryDownloadURL)) {
195 std::string download_url =
196 cmd_line->GetSwitchValueASCII(::switches::kAppsGalleryDownloadURL);
197 return GURL(base::StringPrintf(download_url.c_str(),
198 extension_id.c_str()));
199 }
200 std::vector<base::StringPiece> params;
201 std::string extension_param = "id=" + extension_id;
202 std::string installsource_param = "installsource=" + install_source;
203 params.push_back(extension_param);
204 if (!install_source.empty())
205 params.push_back(installsource_param);
206 params.push_back("uc");
207 std::string url_string = extension_urls::GetWebstoreUpdateUrl().spec();
208
209 GURL url(url_string + "?response=redirect&" +
210 update_client::UpdateQueryParams::Get(
211 update_client::UpdateQueryParams::CRX) +
212 "&x=" + net::EscapeQueryParamValue(base::JoinString(params, "&"),
213 true));
214 DCHECK(url.is_valid());
215
216 return url;
217 }
218
OnExtensionDownloadStarted(const std::string & id,download::DownloadItem * item)219 void WebstoreInstaller::Delegate::OnExtensionDownloadStarted(
220 const std::string& id,
221 download::DownloadItem* item) {}
222
OnExtensionDownloadProgress(const std::string & id,download::DownloadItem * item)223 void WebstoreInstaller::Delegate::OnExtensionDownloadProgress(
224 const std::string& id,
225 download::DownloadItem* item) {}
226
227 WebstoreInstaller::Approval::Approval() = default;
228
229 std::unique_ptr<WebstoreInstaller::Approval>
CreateWithInstallPrompt(Profile * profile)230 WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile* profile) {
231 std::unique_ptr<Approval> result(new Approval());
232 result->profile = profile;
233 return result;
234 }
235
236 std::unique_ptr<WebstoreInstaller::Approval>
CreateForSharedModule(Profile * profile)237 WebstoreInstaller::Approval::CreateForSharedModule(Profile* profile) {
238 std::unique_ptr<Approval> result(new Approval());
239 result->profile = profile;
240 result->skip_install_dialog = true;
241 result->skip_post_install_ui = true;
242 result->manifest_check_level = MANIFEST_CHECK_LEVEL_NONE;
243 return result;
244 }
245
246 std::unique_ptr<WebstoreInstaller::Approval>
CreateWithNoInstallPrompt(Profile * profile,const std::string & extension_id,std::unique_ptr<base::DictionaryValue> parsed_manifest,bool strict_manifest_check)247 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
248 Profile* profile,
249 const std::string& extension_id,
250 std::unique_ptr<base::DictionaryValue> parsed_manifest,
251 bool strict_manifest_check) {
252 std::unique_ptr<Approval> result(new Approval());
253 result->extension_id = extension_id;
254 result->profile = profile;
255 result->manifest = std::unique_ptr<Manifest>(new Manifest(
256 Manifest::INVALID_LOCATION,
257 std::unique_ptr<base::DictionaryValue>(parsed_manifest->DeepCopy())));
258 result->skip_install_dialog = true;
259 result->manifest_check_level = strict_manifest_check ?
260 MANIFEST_CHECK_LEVEL_STRICT : MANIFEST_CHECK_LEVEL_LOOSE;
261 return result;
262 }
263
~Approval()264 WebstoreInstaller::Approval::~Approval() {}
265
GetAssociatedApproval(const DownloadItem & download)266 const WebstoreInstaller::Approval* WebstoreInstaller::GetAssociatedApproval(
267 const DownloadItem& download) {
268 return static_cast<const Approval*>(download.GetUserData(kApprovalKey));
269 }
270
WebstoreInstaller(Profile * profile,Delegate * delegate,content::WebContents * web_contents,const std::string & id,std::unique_ptr<Approval> approval,InstallSource source)271 WebstoreInstaller::WebstoreInstaller(Profile* profile,
272 Delegate* delegate,
273 content::WebContents* web_contents,
274 const std::string& id,
275 std::unique_ptr<Approval> approval,
276 InstallSource source)
277 : content::WebContentsObserver(web_contents),
278 profile_(profile),
279 delegate_(delegate),
280 id_(id),
281 install_source_(source),
282 approval_(approval.release()) {
283 DCHECK_CURRENTLY_ON(BrowserThread::UI);
284 DCHECK(web_contents);
285
286 registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR,
287 content::Source<CrxInstaller>(nullptr));
288 extension_registry_observer_.Add(ExtensionRegistry::Get(profile));
289 }
290
Start()291 void WebstoreInstaller::Start() {
292 DCHECK_CURRENTLY_ON(BrowserThread::UI);
293 AddRef(); // Balanced in ReportSuccess and ReportFailure.
294
295 if (!crx_file::id_util::IdIsValid(id_)) {
296 ReportFailure(kInvalidIdError, FAILURE_REASON_OTHER);
297 return;
298 }
299
300 ExtensionService* extension_service =
301 ExtensionSystem::Get(profile_)->extension_service();
302 if (approval_.get() && approval_->dummy_extension.get()) {
303 extension_service->shared_module_service()->CheckImports(
304 approval_->dummy_extension.get(), &pending_modules_, &pending_modules_);
305 // Do not check the return value of CheckImports, the CRX installer
306 // will report appropriate error messages and fail to install if there
307 // is an import error.
308 }
309
310 // Add the extension main module into the list.
311 SharedModuleInfo::ImportInfo info;
312 info.extension_id = id_;
313 pending_modules_.push_back(info);
314
315 total_modules_ = pending_modules_.size();
316
317 std::set<std::string> ids;
318 std::list<SharedModuleInfo::ImportInfo>::const_iterator i;
319 for (i = pending_modules_.begin(); i != pending_modules_.end(); ++i) {
320 ids.insert(i->extension_id);
321 }
322 InstallVerifier::Get(profile_)->AddProvisional(ids);
323
324 std::string name;
325 if (!approval_->manifest->value()->GetString(manifest_keys::kName, &name)) {
326 NOTREACHED();
327 }
328 extensions::InstallTracker* tracker =
329 extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
330 extensions::InstallObserver::ExtensionInstallParams params(
331 id_,
332 name,
333 approval_->installing_icon,
334 approval_->manifest->is_app(),
335 approval_->manifest->is_platform_app());
336 tracker->OnBeginExtensionInstall(params);
337
338 tracker->OnBeginExtensionDownload(id_);
339
340 // TODO(crbug.com/305343): Query manifest of dependencies before
341 // downloading & installing those dependencies.
342 DownloadNextPendingModule();
343 }
344
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)345 void WebstoreInstaller::Observe(int type,
346 const content::NotificationSource& source,
347 const content::NotificationDetails& details) {
348 DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR, type);
349
350 CrxInstaller* crx_installer = content::Source<CrxInstaller>(source).ptr();
351 CHECK(crx_installer);
352 if (crx_installer != crx_installer_.get())
353 return;
354
355 // TODO(rdevlin.cronin): Continue removing std::string errors and
356 // replacing with base::string16. See crbug.com/71980.
357 const extensions::CrxInstallError* error =
358 content::Details<const extensions::CrxInstallError>(details).ptr();
359 const std::string utf8_error = base::UTF16ToUTF8(error->message());
360 crx_installer_ = nullptr;
361 // ReportFailure releases a reference to this object so it must be the
362 // last operation in this method.
363 ReportFailure(utf8_error, FAILURE_REASON_OTHER);
364 }
365
OnExtensionInstalled(content::BrowserContext * browser_context,const Extension * extension,bool is_update)366 void WebstoreInstaller::OnExtensionInstalled(
367 content::BrowserContext* browser_context,
368 const Extension* extension,
369 bool is_update) {
370 CHECK(profile_->IsSameOrParent(Profile::FromBrowserContext(browser_context)));
371 if (pending_modules_.empty())
372 return;
373 SharedModuleInfo::ImportInfo info = pending_modules_.front();
374 if (extension->id() != info.extension_id)
375 return;
376 pending_modules_.pop_front();
377
378 // Clean up local state from the current download.
379 if (download_item_) {
380 download_item_->RemoveObserver(this);
381 download_item_->Remove();
382 download_item_ = nullptr;
383 }
384 crx_installer_.reset();
385
386 if (pending_modules_.empty()) {
387 CHECK_EQ(extension->id(), id_);
388 ReportSuccess();
389 } else {
390 const base::Version version_required(info.minimum_version);
391 if (version_required.IsValid() &&
392 extension->version().CompareTo(version_required) < 0) {
393 // It should not happen, CrxInstaller will make sure the version is
394 // equal or newer than version_required.
395 ReportFailure(kDependencyNotFoundError,
396 FAILURE_REASON_DEPENDENCY_NOT_FOUND);
397 } else if (!SharedModuleInfo::IsSharedModule(extension)) {
398 // It should not happen, CrxInstaller will make sure it is a shared
399 // module.
400 ReportFailure(kDependencyNotSharedModuleError,
401 FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE);
402 } else {
403 DownloadNextPendingModule();
404 }
405 }
406 }
407
InvalidateDelegate()408 void WebstoreInstaller::InvalidateDelegate() {
409 delegate_ = nullptr;
410 }
411
SetDownloadDirectoryForTests(base::FilePath * directory)412 void WebstoreInstaller::SetDownloadDirectoryForTests(
413 base::FilePath* directory) {
414 g_download_directory_for_tests = directory;
415 }
416
~WebstoreInstaller()417 WebstoreInstaller::~WebstoreInstaller() {
418 if (download_item_) {
419 download_item_->RemoveObserver(this);
420 download_item_ = nullptr;
421 }
422 }
423
OnDownloadStarted(const std::string & extension_id,DownloadItem * item,download::DownloadInterruptReason interrupt_reason)424 void WebstoreInstaller::OnDownloadStarted(
425 const std::string& extension_id,
426 DownloadItem* item,
427 download::DownloadInterruptReason interrupt_reason) {
428 if (!item || interrupt_reason != download::DOWNLOAD_INTERRUPT_REASON_NONE) {
429 if (item)
430 item->Remove();
431 ReportFailure(download::DownloadInterruptReasonToString(interrupt_reason),
432 FAILURE_REASON_OTHER);
433 return;
434 }
435
436 bool found = false;
437 for (const auto& module : pending_modules_) {
438 if (extension_id == module.extension_id) {
439 found = true;
440 break;
441 }
442 }
443 if (!found) {
444 // If this extension is not pending, it means another installer has
445 // installed this extension and triggered OnExtensionInstalled(). In this
446 // case, either it was the main module and success has already been
447 // reported, or it was a dependency and either failed (ie. wrong version) or
448 // the next download was triggered. In any case, the only thing that needs
449 // to be done is to stop this download.
450 item->Remove();
451 return;
452 }
453
454 DCHECK_EQ(download::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
455 DCHECK(!pending_modules_.empty());
456 download_item_ = item;
457 download_item_->AddObserver(this);
458 if (pending_modules_.size() > 1) {
459 // We are downloading a shared module. We need create an approval for it.
460 std::unique_ptr<Approval> approval =
461 Approval::CreateForSharedModule(profile_);
462 const SharedModuleInfo::ImportInfo& info = pending_modules_.front();
463 approval->extension_id = info.extension_id;
464 const base::Version version_required(info.minimum_version);
465
466 if (version_required.IsValid()) {
467 approval->minimum_version.reset(new base::Version(version_required));
468 }
469 download_item_->SetUserData(kApprovalKey, std::move(approval));
470 } else {
471 // It is for the main module of the extension. We should use the provided
472 // |approval_|.
473 if (approval_)
474 download_item_->SetUserData(kApprovalKey, std::move(approval_));
475 }
476
477 if (!download_started_) {
478 if (delegate_)
479 delegate_->OnExtensionDownloadStarted(id_, download_item_);
480 download_started_ = true;
481 }
482 }
483
OnDownloadUpdated(DownloadItem * download)484 void WebstoreInstaller::OnDownloadUpdated(DownloadItem* download) {
485 CHECK_EQ(download_item_, download);
486
487 switch (download->GetState()) {
488 case DownloadItem::CANCELLED:
489 ReportFailure(kDownloadCanceledError, FAILURE_REASON_CANCELLED);
490 break;
491 case DownloadItem::INTERRUPTED:
492 RecordInterrupt(download);
493 ReportFailure(
494 GetErrorMessageForDownloadInterrupt(download->GetLastReason()),
495 FAILURE_REASON_OTHER);
496 break;
497 case DownloadItem::COMPLETE:
498 // Stop the progress timer if it's running.
499 download_progress_timer_.Stop();
500
501 // Only wait for other notifications if the download is really
502 // an extension.
503 if (!download_crx_util::IsExtensionDownload(*download)) {
504 ReportFailure(kInvalidDownloadError, FAILURE_REASON_OTHER);
505 return;
506 }
507
508 if (crx_installer_.get())
509 return; // DownloadItemImpl calls the observer twice, ignore it.
510
511 StartCrxInstaller(*download);
512
513 if (pending_modules_.size() == 1) {
514 // The download is the last module - the extension main module.
515 if (delegate_)
516 delegate_->OnExtensionDownloadProgress(id_, download);
517 extensions::InstallTracker* tracker =
518 extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
519 tracker->OnDownloadProgress(id_, 100);
520 }
521 break;
522 case DownloadItem::IN_PROGRESS: {
523 if (delegate_ && pending_modules_.size() == 1) {
524 // Only report download progress for the main module to |delegrate_|.
525 delegate_->OnExtensionDownloadProgress(id_, download);
526 }
527 UpdateDownloadProgress();
528 break;
529 }
530 default:
531 // Continue listening if the download is not in one of the above states.
532 break;
533 }
534 }
535
OnDownloadDestroyed(DownloadItem * download)536 void WebstoreInstaller::OnDownloadDestroyed(DownloadItem* download) {
537 CHECK_EQ(download_item_, download);
538 download_item_->RemoveObserver(this);
539 download_item_ = nullptr;
540 }
541
DownloadNextPendingModule()542 void WebstoreInstaller::DownloadNextPendingModule() {
543 CHECK(!pending_modules_.empty());
544 if (pending_modules_.size() == 1) {
545 DCHECK_EQ(id_, pending_modules_.front().extension_id);
546 DownloadCrx(id_, install_source_);
547 } else {
548 DownloadCrx(pending_modules_.front().extension_id, INSTALL_SOURCE_OTHER);
549 }
550 }
551
DownloadCrx(const std::string & extension_id,InstallSource source)552 void WebstoreInstaller::DownloadCrx(
553 const std::string& extension_id,
554 InstallSource source) {
555 download_url_ = GetWebstoreInstallURL(extension_id, source);
556 MaybeAppendAuthUserParameter(approval_->authuser, &download_url_);
557
558 base::FilePath user_data_dir;
559 base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
560 base::FilePath download_path = user_data_dir.Append(kWebstoreDownloadFolder);
561
562 base::FilePath download_directory(g_download_directory_for_tests ?
563 *g_download_directory_for_tests : download_path);
564
565 base::PostTaskAndReplyWithResult(
566 GetExtensionFileTaskRunner().get(), FROM_HERE,
567 base::BindOnce(&GetDownloadFilePath, download_directory, extension_id),
568 base::BindOnce(&WebstoreInstaller::StartDownload, this, extension_id));
569 }
570
571 // http://crbug.com/165634
572 // http://crbug.com/126013
573 // The current working theory is that one of the many pointers dereferenced in
574 // here is occasionally deleted before all of its referers are nullified,
575 // probably in a callback race. After this comment is released, the crash
576 // reports should narrow down exactly which pointer it is. Collapsing all the
577 // early-returns into a single branch makes it hard to see exactly which pointer
578 // it is.
StartDownload(const std::string & extension_id,const base::FilePath & file)579 void WebstoreInstaller::StartDownload(const std::string& extension_id,
580 const base::FilePath& file) {
581 DCHECK_CURRENTLY_ON(BrowserThread::UI);
582
583 if (file.empty()) {
584 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
585 return;
586 }
587
588 DownloadManager* download_manager =
589 BrowserContext::GetDownloadManager(profile_);
590 if (!download_manager) {
591 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
592 return;
593 }
594
595 content::WebContents* contents = web_contents();
596 if (!contents) {
597 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
598 return;
599 }
600 if (!contents->GetMainFrame()->GetRenderViewHost()) {
601 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
602 return;
603 }
604 if (!contents->GetMainFrame()->GetRenderViewHost()->GetProcess()) {
605 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
606 return;
607 }
608
609 content::NavigationController& controller = contents->GetController();
610 if (!controller.GetBrowserContext()) {
611 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
612 return;
613 }
614 if (!controller.GetBrowserContext()->GetResourceContext()) {
615 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
616 return;
617 }
618
619 // The download url for the given extension is contained in |download_url_|.
620 // We will navigate the current tab to this url to start the download. The
621 // download system will then pass the crx to the CrxInstaller.
622 int render_process_host_id =
623 contents->GetMainFrame()->GetRenderViewHost()->GetProcess()->GetID();
624
625 content::RenderFrameHost* render_frame_host = contents->GetMainFrame();
626 net::NetworkTrafficAnnotationTag traffic_annotation =
627 net::DefineNetworkTrafficAnnotation("webstore_installer", R"(
628 semantics {
629 sender: "Webstore Installer"
630 description: "Downloads an extension for installation."
631 trigger:
632 "User initiates a webstore extension installation flow, including "
633 "installing from the webstore, inline installation from a site, "
634 "re-installing a corrupted extension, and others."
635 data:
636 "The id of the extension to be installed and information about the "
637 "user's installation, including version, language, distribution "
638 "(Chrome vs Chromium), NaCl architecture, installation source (as "
639 "an enum), and accepted crx formats."
640 destination: GOOGLE_OWNED_SERVICE
641 }
642 policy {
643 cookies_allowed: YES
644 cookies_store: "user"
645 setting:
646 "This feature cannot be disabled. It is only activated if the user "
647 "triggers an extension installation."
648 chrome_policy {
649 ExtensionInstallBlocklist {
650 ExtensionInstallBlocklist: {
651 entries: '*'
652 }
653 }
654 }
655 })");
656 std::unique_ptr<DownloadUrlParameters> params(new DownloadUrlParameters(
657 download_url_, render_process_host_id, render_frame_host->GetRoutingID(),
658 traffic_annotation));
659 params->set_file_path(file);
660 if (controller.GetVisibleEntry()) {
661 content::Referrer referrer = content::Referrer::SanitizeForRequest(
662 download_url_,
663 content::Referrer(controller.GetVisibleEntry()->GetURL(),
664 network::mojom::ReferrerPolicy::kDefault));
665 params->set_referrer(referrer.url);
666 params->set_referrer_policy(
667 content::Referrer::ReferrerPolicyForUrlRequest(referrer.policy));
668 }
669 params->set_callback(base::BindOnce(&WebstoreInstaller::OnDownloadStarted,
670 this, extension_id));
671 params->set_download_source(download::DownloadSource::EXTENSION_INSTALLER);
672 download_manager->DownloadUrl(std::move(params));
673 }
674
675 void WebstoreInstaller::UpdateDownloadProgress() {
676 // If the download has gone away, or isn't in progress (in which case we can't
677 // give a good progress estimate), stop any running timers and return.
678 if (!download_item_ ||
679 download_item_->GetState() != DownloadItem::IN_PROGRESS) {
680 download_progress_timer_.Stop();
681 return;
682 }
683
684 int percent = download_item_->PercentComplete();
685 // Only report progress if percent is more than 0 or we have finished
686 // downloading at least one of the pending modules.
687 int finished_modules = total_modules_ - pending_modules_.size();
688 if (finished_modules > 0 && percent < 0)
689 percent = 0;
690 if (percent >= 0) {
691 percent = (percent + (finished_modules * 100)) / total_modules_;
692 extensions::InstallTracker* tracker =
693 extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
694 tracker->OnDownloadProgress(id_, percent);
695 }
696
697 // If there's enough time remaining on the download to warrant an update,
698 // set the timer (overwriting any current timers). Otherwise, stop the
699 // timer.
700 base::TimeDelta time_remaining;
701 if (download_item_->TimeRemaining(&time_remaining) &&
702 time_remaining > kTimeRemainingThreshold) {
703 download_progress_timer_.Start(FROM_HERE, kTimeRemainingThreshold, this,
704 &WebstoreInstaller::UpdateDownloadProgress);
705 } else {
706 download_progress_timer_.Stop();
707 }
708 }
709
StartCrxInstaller(const DownloadItem & download)710 void WebstoreInstaller::StartCrxInstaller(const DownloadItem& download) {
711 DCHECK_CURRENTLY_ON(BrowserThread::UI);
712 DCHECK(!crx_installer_.get());
713
714 ExtensionService* service = ExtensionSystem::Get(profile_)->
715 extension_service();
716 CHECK(service);
717
718 const Approval* approval = GetAssociatedApproval(download);
719 DCHECK(approval);
720
721 crx_installer_ = download_crx_util::CreateCrxInstaller(profile_, download);
722
723 crx_installer_->set_expected_id(approval->extension_id);
724 crx_installer_->set_is_gallery_install(true);
725 crx_installer_->set_allow_silent_install(true);
726
727 crx_installer_->InstallCrx(download.GetFullPath());
728 }
729
ReportFailure(const std::string & error,FailureReason reason)730 void WebstoreInstaller::ReportFailure(const std::string& error,
731 FailureReason reason) {
732 if (delegate_) {
733 delegate_->OnExtensionInstallFailure(id_, error, reason);
734 delegate_ = nullptr;
735 }
736
737 extensions::InstallTracker* tracker =
738 extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
739 tracker->OnInstallFailure(id_);
740
741 Release(); // Balanced in Start().
742 }
743
ReportSuccess()744 void WebstoreInstaller::ReportSuccess() {
745 if (delegate_) {
746 delegate_->OnExtensionInstallSuccess(id_);
747 delegate_ = nullptr;
748 }
749
750 Release(); // Balanced in Start().
751 }
752
RecordInterrupt(const DownloadItem * download) const753 void WebstoreInstaller::RecordInterrupt(const DownloadItem* download) const {
754 base::UmaHistogramSparse("Extensions.WebstoreDownload.InterruptReason",
755 download->GetLastReason());
756
757 // Use logarithmic bin sizes up to 1 TB.
758 const int kNumBuckets = 30;
759 const int64_t kMaxSizeKb = 1 << kNumBuckets;
760 UMA_HISTOGRAM_CUSTOM_COUNTS(
761 "Extensions.WebstoreDownload.InterruptReceivedKBytes",
762 download->GetReceivedBytes() / 1024,
763 1,
764 kMaxSizeKb,
765 kNumBuckets);
766 int64_t total_bytes = download->GetTotalBytes();
767 if (total_bytes >= 0) {
768 UMA_HISTOGRAM_CUSTOM_COUNTS(
769 "Extensions.WebstoreDownload.InterruptTotalKBytes",
770 total_bytes / 1024,
771 1,
772 kMaxSizeKb,
773 kNumBuckets);
774 }
775 UMA_HISTOGRAM_BOOLEAN(
776 "Extensions.WebstoreDownload.InterruptTotalSizeUnknown",
777 total_bytes <= 0);
778 }
779
780 } // namespace extensions
781