crx_installer.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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/crx_installer.h"
6
7#include <map>
8#include <set>
9
10#include "base/bind.h"
11#include "base/file_util.h"
12#include "base/files/scoped_temp_dir.h"
13#include "base/lazy_instance.h"
14#include "base/metrics/histogram.h"
15#include "base/path_service.h"
16#include "base/sequenced_task_runner.h"
17#include "base/string_util.h"
18#include "base/stringprintf.h"
19#include "base/threading/sequenced_worker_pool.h"
20#include "base/threading/thread_restrictions.h"
21#include "base/time.h"
22#include "base/utf_string_conversions.h"
23#include "base/version.h"
24#include "chrome/browser/extensions/convert_user_script.h"
25#include "chrome/browser/extensions/convert_web_app.h"
26#include "chrome/browser/extensions/crx_installer_error.h"
27#include "chrome/browser/extensions/extension_error_reporter.h"
28#include "chrome/browser/extensions/extension_install_ui.h"
29#include "chrome/browser/extensions/extension_service.h"
30#include "chrome/browser/extensions/extension_system.h"
31#include "chrome/browser/extensions/permissions_updater.h"
32#include "chrome/browser/extensions/webstore_installer.h"
33#include "chrome/browser/profiles/profile.h"
34#include "chrome/browser/web_applications/web_app.h"
35#include "chrome/common/chrome_notification_types.h"
36#include "chrome/common/chrome_paths.h"
37#include "chrome/common/extensions/extension_constants.h"
38#include "chrome/common/extensions/extension_file_util.h"
39#include "chrome/common/extensions/extension_icon_set.h"
40#include "chrome/common/extensions/feature_switch.h"
41#include "chrome/common/extensions/manifest.h"
42#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
43#include "chrome/common/extensions/manifest_handlers/shared_module_info.h"
44#include "content/public/browser/browser_thread.h"
45#include "content/public/browser/notification_service.h"
46#include "content/public/browser/resource_dispatcher_host.h"
47#include "content/public/browser/user_metrics.h"
48#include "grit/chromium_strings.h"
49#include "grit/generated_resources.h"
50#include "grit/theme_resources.h"
51#include "third_party/skia/include/core/SkBitmap.h"
52#include "ui/base/l10n/l10n_util.h"
53#include "ui/base/resource/resource_bundle.h"
54
55using content::BrowserThread;
56using content::UserMetricsAction;
57using extensions::SharedModuleInfo;
58
59namespace extensions {
60
61namespace {
62
63// Used in histograms; do not change order.
64enum OffStoreInstallDecision {
65  OnStoreInstall,
66  OffStoreInstallAllowed,
67  OffStoreInstallDisallowed,
68  NumOffStoreInstallDecision
69};
70
71}  // namespace
72
73// static
74scoped_refptr<CrxInstaller> CrxInstaller::Create(
75    ExtensionService* frontend,
76    ExtensionInstallPrompt* client) {
77  return new CrxInstaller(frontend->AsWeakPtr(), client, NULL);
78}
79
80// static
81scoped_refptr<CrxInstaller> CrxInstaller::Create(
82    ExtensionService* service,
83    ExtensionInstallPrompt* client,
84    const WebstoreInstaller::Approval* approval) {
85  return new CrxInstaller(service->AsWeakPtr(), client, approval);
86}
87
88CrxInstaller::CrxInstaller(
89    base::WeakPtr<ExtensionService> service_weak,
90    ExtensionInstallPrompt* client,
91    const WebstoreInstaller::Approval* approval)
92    : install_directory_(service_weak->install_directory()),
93      install_source_(Manifest::INTERNAL),
94      approved_(false),
95      extensions_enabled_(service_weak->extensions_enabled()),
96      delete_source_(false),
97      create_app_shortcut_(false),
98      service_weak_(service_weak),
99      client_(client),
100      apps_require_extension_mime_type_(false),
101      allow_silent_install_(false),
102      bypass_blacklist_for_test_(false),
103      install_cause_(extension_misc::INSTALL_CAUSE_UNSET),
104      creation_flags_(Extension::NO_FLAGS),
105      off_store_install_allow_reason_(OffStoreInstallDisallowed),
106      did_handle_successfully_(true),
107      error_on_unsupported_requirements_(false),
108      has_requirement_errors_(false),
109      install_wait_for_idle_(true),
110      update_from_settings_page_(false),
111      installer_(service_weak->profile()) {
112  installer_task_runner_ = service_weak->GetFileTaskRunner();
113  if (!approval)
114    return;
115
116  CHECK(profile()->IsSameProfile(approval->profile));
117  if (client_) {
118    client_->install_ui()->SetUseAppInstalledBubble(
119        approval->use_app_installed_bubble);
120    client_->install_ui()->SetSkipPostInstallUI(approval->skip_post_install_ui);
121  }
122
123  if (approval->skip_install_dialog) {
124    // Mark the extension as approved, but save the expected manifest and ID
125    // so we can check that they match the CRX's.
126    approved_ = true;
127    expected_manifest_.reset(approval->manifest->DeepCopy());
128    expected_id_ = approval->extension_id;
129  }
130
131  show_dialog_callback_ = approval->show_dialog_callback;
132}
133
134CrxInstaller::~CrxInstaller() {
135  // Make sure the UI is deleted on the ui thread.
136  if (client_) {
137    BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, client_);
138    client_ = NULL;
139  }
140}
141
142void CrxInstaller::InstallCrx(const base::FilePath& source_file) {
143  source_file_ = source_file;
144
145  scoped_refptr<SandboxedUnpacker> unpacker(
146      new SandboxedUnpacker(
147          source_file,
148          content::ResourceDispatcherHost::Get() != NULL,
149          install_source_,
150          creation_flags_,
151          install_directory_,
152          installer_task_runner_,
153          this));
154
155  if (!installer_task_runner_->PostTask(
156          FROM_HERE,
157          base::Bind(&SandboxedUnpacker::Start, unpacker.get())))
158    NOTREACHED();
159}
160
161void CrxInstaller::InstallUserScript(const base::FilePath& source_file,
162                                     const GURL& download_url) {
163  DCHECK(!download_url.is_empty());
164
165  source_file_ = source_file;
166  download_url_ = download_url;
167
168  if (!installer_task_runner_->PostTask(
169          FROM_HERE,
170          base::Bind(&CrxInstaller::ConvertUserScriptOnFileThread, this)))
171    NOTREACHED();
172}
173
174void CrxInstaller::ConvertUserScriptOnFileThread() {
175  string16 error;
176  scoped_refptr<Extension> extension = ConvertUserScriptToExtension(
177      source_file_, download_url_, install_directory_, &error);
178  if (!extension) {
179    ReportFailureFromFileThread(CrxInstallerError(error));
180    return;
181  }
182
183  OnUnpackSuccess(extension->path(), extension->path(), NULL, extension);
184}
185
186void CrxInstaller::InstallWebApp(const WebApplicationInfo& web_app) {
187  if (!installer_task_runner_->PostTask(
188          FROM_HERE,
189          base::Bind(&CrxInstaller::ConvertWebAppOnFileThread,
190                     this,
191                     web_app,
192                     install_directory_)))
193    NOTREACHED();
194}
195
196void CrxInstaller::ConvertWebAppOnFileThread(
197    const WebApplicationInfo& web_app,
198    const base::FilePath& install_directory) {
199  string16 error;
200  scoped_refptr<Extension> extension(
201      ConvertWebAppToExtension(web_app, base::Time::Now(), install_directory));
202  if (!extension) {
203    // Validation should have stopped any potential errors before getting here.
204    NOTREACHED() << "Could not convert web app to extension.";
205    return;
206  }
207
208  // TODO(aa): conversion data gets lost here :(
209
210  OnUnpackSuccess(extension->path(), extension->path(), NULL, extension);
211}
212
213CrxInstallerError CrxInstaller::AllowInstall(const Extension* extension) {
214  DCHECK(installer_task_runner_->RunsTasksOnCurrentThread());
215
216  // Make sure the expected ID matches if one was supplied or if we want to
217  // bypass the prompt.
218  if ((approved_ || !expected_id_.empty()) &&
219      expected_id_ != extension->id()) {
220    return CrxInstallerError(
221        l10n_util::GetStringFUTF16(IDS_EXTENSION_INSTALL_UNEXPECTED_ID,
222                                   ASCIIToUTF16(expected_id_),
223                                   ASCIIToUTF16(extension->id())));
224  }
225
226  if (expected_version_.get() &&
227      !expected_version_->Equals(*extension->version())) {
228    return CrxInstallerError(
229        l10n_util::GetStringFUTF16(
230            IDS_EXTENSION_INSTALL_UNEXPECTED_VERSION,
231            ASCIIToUTF16(expected_version_->GetString()),
232            ASCIIToUTF16(extension->version()->GetString())));
233  }
234
235  // Make sure the manifests match if we want to bypass the prompt.
236  if (approved_ &&
237      (!expected_manifest_.get() ||
238       !expected_manifest_->Equals(original_manifest_.get()))) {
239    return CrxInstallerError(
240        l10n_util::GetStringUTF16(IDS_EXTENSION_MANIFEST_INVALID));
241  }
242
243  // The checks below are skipped for themes and external installs.
244  // TODO(pamg): After ManagementPolicy refactoring is complete, remove this
245  // and other uses of install_source_ that are no longer needed now that the
246  // SandboxedUnpacker sets extension->location.
247  if (extension->is_theme() || Manifest::IsExternalLocation(install_source_))
248    return CrxInstallerError();
249
250  if (!extensions_enabled_) {
251    return CrxInstallerError(
252        l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALL_NOT_ENABLED));
253  }
254
255  if (install_cause_ == extension_misc::INSTALL_CAUSE_USER_DOWNLOAD) {
256    if (FeatureSwitch::easy_off_store_install()->IsEnabled()) {
257      const char* kHistogramName = "Extensions.OffStoreInstallDecisionEasy";
258      if (is_gallery_install()) {
259        UMA_HISTOGRAM_ENUMERATION(kHistogramName, OnStoreInstall,
260                                  NumOffStoreInstallDecision);
261      } else {
262        UMA_HISTOGRAM_ENUMERATION(kHistogramName, OffStoreInstallAllowed,
263                                  NumOffStoreInstallDecision);
264      }
265    } else {
266      const char* kHistogramName = "Extensions.OffStoreInstallDecisionHard";
267      if (is_gallery_install()) {
268        UMA_HISTOGRAM_ENUMERATION(kHistogramName, OnStoreInstall,
269                                  NumOffStoreInstallDecision);
270      } else if (off_store_install_allow_reason_ != OffStoreInstallDisallowed) {
271        UMA_HISTOGRAM_ENUMERATION(kHistogramName, OffStoreInstallAllowed,
272                                  NumOffStoreInstallDecision);
273        UMA_HISTOGRAM_ENUMERATION("Extensions.OffStoreInstallAllowReason",
274                                  off_store_install_allow_reason_,
275                                  NumOffStoreInstallAllowReasons);
276      } else {
277        UMA_HISTOGRAM_ENUMERATION(kHistogramName, OffStoreInstallDisallowed,
278                                  NumOffStoreInstallDecision);
279        // Don't delete source in this case so that the user can install
280        // manually if they want.
281        delete_source_ = false;
282        did_handle_successfully_ = false;
283
284        return CrxInstallerError(
285            CrxInstallerError::ERROR_OFF_STORE,
286            l10n_util::GetStringUTF16(
287                IDS_EXTENSION_INSTALL_DISALLOWED_ON_SITE));
288      }
289    }
290  }
291
292  if (installer_.extension()->is_app()) {
293    // If the app was downloaded, apps_require_extension_mime_type_
294    // will be set.  In this case, check that it was served with the
295    // right mime type.  Make an exception for file URLs, which come
296    // from the users computer and have no headers.
297    if (!download_url_.SchemeIsFile() &&
298        apps_require_extension_mime_type_ &&
299        original_mime_type_ != Extension::kMimeType) {
300      return CrxInstallerError(
301          l10n_util::GetStringFUTF16(
302              IDS_EXTENSION_INSTALL_INCORRECT_APP_CONTENT_TYPE,
303              ASCIIToUTF16(Extension::kMimeType)));
304    }
305
306    // If the client_ is NULL, then the app is either being installed via
307    // an internal mechanism like sync, external_extensions, or default apps.
308    // In that case, we don't want to enforce things like the install origin.
309    if (!is_gallery_install() && client_) {
310      // For apps with a gallery update URL, require that they be installed
311      // from the gallery.
312      // TODO(erikkay) Apply this rule for paid extensions and themes as well.
313      if (extension->UpdatesFromGallery()) {
314        return CrxInstallerError(
315            l10n_util::GetStringFUTF16(
316                IDS_EXTENSION_DISALLOW_NON_DOWNLOADED_GALLERY_INSTALLS,
317                l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE)));
318      }
319
320      // For self-hosted apps, verify that the entire extent is on the same
321      // host (or a subdomain of the host) the download happened from.  There's
322      // no way for us to verify that the app controls any other hosts.
323      URLPattern pattern(UserScript::ValidUserScriptSchemes());
324      pattern.SetHost(download_url_.host());
325      pattern.SetMatchSubdomains(true);
326
327      URLPatternSet patterns = installer_.extension()->web_extent();
328      for (URLPatternSet::const_iterator i = patterns.begin();
329           i != patterns.end(); ++i) {
330        if (!pattern.MatchesHost(i->host())) {
331          return CrxInstallerError(
332              l10n_util::GetStringUTF16(
333                  IDS_EXTENSION_INSTALL_INCORRECT_INSTALL_HOST));
334        }
335      }
336    }
337  }
338
339  return CrxInstallerError();
340}
341
342void CrxInstaller::OnUnpackFailure(const string16& error_message) {
343  DCHECK(installer_task_runner_->RunsTasksOnCurrentThread());
344
345  UMA_HISTOGRAM_ENUMERATION("Extensions.UnpackFailureInstallSource",
346                            install_source(), Manifest::NUM_LOCATIONS);
347
348  UMA_HISTOGRAM_ENUMERATION("Extensions.UnpackFailureInstallCause",
349                            install_cause(),
350                            extension_misc::NUM_INSTALL_CAUSES);
351
352  ReportFailureFromFileThread(CrxInstallerError(error_message));
353}
354
355void CrxInstaller::OnUnpackSuccess(const base::FilePath& temp_dir,
356                                   const base::FilePath& extension_dir,
357                                   const DictionaryValue* original_manifest,
358                                   const Extension* extension) {
359  DCHECK(installer_task_runner_->RunsTasksOnCurrentThread());
360
361  UMA_HISTOGRAM_ENUMERATION("Extensions.UnpackSuccessInstallSource",
362                            install_source(), Manifest::NUM_LOCATIONS);
363
364
365  UMA_HISTOGRAM_ENUMERATION("Extensions.UnpackSuccessInstallCause",
366                            install_cause(),
367                            extension_misc::NUM_INSTALL_CAUSES);
368
369  installer_.set_extension(extension);
370  temp_dir_ = temp_dir;
371
372  if (original_manifest)
373    original_manifest_.reset(new Manifest(
374        Manifest::INVALID_LOCATION,
375        scoped_ptr<DictionaryValue>(original_manifest->DeepCopy())));
376
377  // We don't have to delete the unpack dir explicity since it is a child of
378  // the temp dir.
379  unpacked_extension_root_ = extension_dir;
380
381  CrxInstallerError error = AllowInstall(extension);
382  if (error.type() != CrxInstallerError::ERROR_NONE) {
383    ReportFailureFromFileThread(error);
384    return;
385  }
386
387  if (client_) {
388    IconsInfo::DecodeIcon(installer_.extension(),
389                          extension_misc::EXTENSION_ICON_LARGE,
390                          ExtensionIconSet::MATCH_BIGGER,
391                          &install_icon_);
392  }
393
394  if (!BrowserThread::PostTask(
395        BrowserThread::UI, FROM_HERE,
396        base::Bind(&CrxInstaller::CheckImportsAndRequirements, this)))
397    NOTREACHED();
398}
399
400void CrxInstaller::CheckImportsAndRequirements() {
401  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
402  ExtensionService* service = service_weak_;
403  if (!service || service->browser_terminating())
404    return;
405
406  if (SharedModuleInfo::ImportsModules(extension())) {
407    const std::vector<SharedModuleInfo::ImportInfo>& imports =
408        SharedModuleInfo::GetImports(extension());
409    std::vector<SharedModuleInfo::ImportInfo>::const_iterator i;
410    for (i = imports.begin(); i != imports.end(); ++i) {
411      Version version_required(i->minimum_version);
412      const Extension* imported_module =
413          service->GetExtensionById(i->extension_id, true);
414      if (!imported_module ||
415          (version_required.IsValid() &&
416           imported_module->version()->CompareTo(version_required) < 0)) {
417        ReportFailureFromUIThread(
418            CrxInstallerError(l10n_util::GetStringFUTF16(
419                IDS_EXTENSION_INSTALL_DEPENDENCY_NOT_FOUND,
420                ASCIIToUTF16(i->extension_id),
421                ASCIIToUTF16(i->minimum_version))));
422        return;
423      }
424      if (!SharedModuleInfo::IsSharedModule(imported_module)) {
425        ReportFailureFromUIThread(
426            CrxInstallerError(l10n_util::GetStringFUTF16(
427                IDS_EXTENSION_INSTALL_DEPENDENCY_NOT_SHARED_MODULE,
428                ASCIIToUTF16(i->extension_id))));
429        return;
430      }
431    }
432  }
433  installer_.CheckRequirements(base::Bind(&CrxInstaller::OnRequirementsChecked,
434                                          this));
435}
436
437void CrxInstaller::OnRequirementsChecked(
438    std::vector<std::string> requirement_errors) {
439  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
440  if (!requirement_errors.empty()) {
441    if (error_on_unsupported_requirements_) {
442      ReportFailureFromUIThread(CrxInstallerError(
443          UTF8ToUTF16(JoinString(requirement_errors, ' '))));
444      return;
445    }
446    has_requirement_errors_ = true;
447  }
448
449#if defined(ENABLE_MANAGED_USERS) && !defined(OS_CHROMEOS)
450  // Extensions which should be installed by policy are installed without
451  // using the ExtensionInstallPrompt. In that case, |client_| is NULL.
452  if (!client_) {
453    // Automatically set authorization
454    installer_.OnAuthorizationResult(
455        base::Bind(&CrxInstaller::ConfirmInstall, this),
456        true);
457    return;
458  }
459  // |parent_web_contents| could be NULL when the client is instantiated from
460  // ExtensionEnableFlow, but that code path does not lead to here. Also they
461  // are NULL in some tests.
462  installer_.ShowPassphraseDialog(
463      client_->parent_web_contents(),
464      base::Bind(&CrxInstaller::ConfirmInstall, this));
465#else
466  ConfirmInstall();
467#endif
468}
469
470void CrxInstaller::ConfirmInstall() {
471  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
472  ExtensionService* service = service_weak_;
473  if (!service || service->browser_terminating())
474    return;
475
476  string16 error = installer_.CheckManagementPolicy();
477  if (!error.empty()) {
478    ReportFailureFromUIThread(CrxInstallerError(error));
479    return;
480  }
481
482  // Check whether this install is initiated from the settings page to
483  // update an existing extension or app.
484  CheckUpdateFromSettingsPage();
485
486  GURL overlapping_url;
487  const Extension* overlapping_extension =
488      service->extensions()->GetHostedAppByOverlappingWebExtent(
489          extension()->web_extent());
490  if (overlapping_extension &&
491      overlapping_extension->id() != extension()->id()) {
492    ReportFailureFromUIThread(
493        CrxInstallerError(
494            l10n_util::GetStringFUTF16(
495                IDS_EXTENSION_OVERLAPPING_WEB_EXTENT,
496                UTF8ToUTF16(overlapping_extension->name()))));
497    return;
498  }
499
500  current_version_ =
501      service->extension_prefs()->GetVersionString(extension()->id());
502
503  if (client_ &&
504      (!allow_silent_install_ || !approved_) &&
505      !update_from_settings_page_) {
506    AddRef();  // Balanced in InstallUIProceed() and InstallUIAbort().
507    client_->ConfirmInstall(this, extension(), show_dialog_callback_);
508  } else {
509    if (!installer_task_runner_->PostTask(
510            FROM_HERE,
511            base::Bind(&CrxInstaller::CompleteInstall, this)))
512      NOTREACHED();
513  }
514  return;
515}
516
517void CrxInstaller::InstallUIProceed() {
518  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
519
520  ExtensionService* service = service_weak_;
521  if (!service || service->browser_terminating())
522    return;
523
524  // If update_from_settings_page_ boolean is true, this functions is
525  // getting called in response to ExtensionInstallPrompt::ConfirmReEnable()
526  // and if it is false, this function is called in response to
527  // ExtensionInstallPrompt::ConfirmInstall().
528  if (update_from_settings_page_) {
529    service->GrantPermissionsAndEnableExtension(extension());
530  } else {
531    if (!installer_task_runner_->PostTask(
532            FROM_HERE,
533            base::Bind(&CrxInstaller::CompleteInstall, this)))
534      NOTREACHED();
535  }
536
537  Release();  // balanced in ConfirmInstall() or ConfirmReEnable().
538}
539
540void CrxInstaller::InstallUIAbort(bool user_initiated) {
541  // If update_from_settings_page_ boolean is true, this functions is
542  // getting called in response to ExtensionInstallPrompt::ConfirmReEnable()
543  // and if it is false, this function is called in response to
544  // ExtensionInstallPrompt::ConfirmInstall().
545  if (!update_from_settings_page_) {
546    std::string histogram_name = user_initiated ?
547        "Extensions.Permissions_InstallCancel" :
548        "Extensions.Permissions_InstallAbort";
549    ExtensionService::RecordPermissionMessagesHistogram(
550        extension(), histogram_name.c_str());
551
552    // Kill the theme loading bubble.
553    content::NotificationService* service =
554        content::NotificationService::current();
555    service->Notify(chrome::NOTIFICATION_NO_THEME_DETECTED,
556                    content::Source<CrxInstaller>(this),
557                    content::NotificationService::NoDetails());
558
559    NotifyCrxInstallComplete(false);
560  }
561
562  Release();  // balanced in ConfirmInstall() or ConfirmReEnable().
563
564  // We're done. Since we don't post any more tasks to ourself, our ref count
565  // should go to zero and we die. The destructor will clean up the temp dir.
566}
567
568void CrxInstaller::CompleteInstall() {
569  DCHECK(installer_task_runner_->RunsTasksOnCurrentThread());
570
571  if (!current_version_.empty()) {
572    Version current_version(current_version_);
573    if (current_version.CompareTo(*(extension()->version())) > 0) {
574      ReportFailureFromFileThread(
575          CrxInstallerError(
576              l10n_util::GetStringUTF16(extension()->is_app() ?
577                  IDS_APP_CANT_DOWNGRADE_VERSION :
578                  IDS_EXTENSION_CANT_DOWNGRADE_VERSION)));
579      return;
580    }
581  }
582
583  // See how long extension install paths are.  This is important on
584  // windows, because file operations may fail if the path to a file
585  // exceeds a small constant.  See crbug.com/69693 .
586  UMA_HISTOGRAM_CUSTOM_COUNTS(
587    "Extensions.CrxInstallDirPathLength",
588        install_directory_.value().length(), 0, 500, 100);
589
590  base::FilePath version_dir = extension_file_util::InstallExtension(
591      unpacked_extension_root_,
592      extension()->id(),
593      extension()->VersionString(),
594      install_directory_);
595  if (version_dir.empty()) {
596    ReportFailureFromFileThread(
597        CrxInstallerError(
598            l10n_util::GetStringUTF16(
599                IDS_EXTENSION_MOVE_DIRECTORY_TO_PROFILE_FAILED)));
600    return;
601  }
602
603  // This is lame, but we must reload the extension because absolute paths
604  // inside the content scripts are established inside InitFromValue() and we
605  // just moved the extension.
606  // TODO(aa): All paths to resources inside extensions should be created
607  // lazily and based on the Extension's root path at that moment.
608  // TODO(rdevlin.cronin): Continue removing std::string errors and replacing
609  // with string16
610  std::string extension_id = extension()->id();
611  std::string error;
612  installer_.set_extension(extension_file_util::LoadExtension(
613      version_dir,
614      install_source_,
615      extension()->creation_flags() | Extension::REQUIRE_KEY,
616      &error));
617
618  if (extension()) {
619    ReportSuccessFromFileThread();
620  } else {
621    LOG(ERROR) << error << " " << extension_id << " " << download_url_;
622    ReportFailureFromFileThread(CrxInstallerError(UTF8ToUTF16(error)));
623  }
624
625}
626
627void CrxInstaller::ReportFailureFromFileThread(const CrxInstallerError& error) {
628  DCHECK(installer_task_runner_->RunsTasksOnCurrentThread());
629  if (!BrowserThread::PostTask(
630          BrowserThread::UI, FROM_HERE,
631          base::Bind(&CrxInstaller::ReportFailureFromUIThread, this, error))) {
632    NOTREACHED();
633  }
634}
635
636void CrxInstaller::ReportFailureFromUIThread(const CrxInstallerError& error) {
637  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
638
639  content::NotificationService* service =
640      content::NotificationService::current();
641  service->Notify(chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
642                  content::Source<CrxInstaller>(this),
643                  content::Details<const string16>(&error.message()));
644
645  // This isn't really necessary, it is only used because unit tests expect to
646  // see errors get reported via this interface.
647  //
648  // TODO(aa): Need to go through unit tests and clean them up too, probably get
649  // rid of this line.
650  ExtensionErrorReporter::GetInstance()->ReportError(
651      error.message(), false);  // quiet
652
653  if (client_)
654    client_->OnInstallFailure(error);
655
656  NotifyCrxInstallComplete(false);
657
658  // Delete temporary files.
659  CleanupTempFiles();
660}
661
662void CrxInstaller::ReportSuccessFromFileThread() {
663  DCHECK(installer_task_runner_->RunsTasksOnCurrentThread());
664
665  // Tracking number of extensions installed by users
666  if (install_cause() == extension_misc::INSTALL_CAUSE_USER_DOWNLOAD)
667    UMA_HISTOGRAM_ENUMERATION("Extensions.ExtensionInstalled", 1, 2);
668
669  if (!BrowserThread::PostTask(
670          BrowserThread::UI, FROM_HERE,
671          base::Bind(&CrxInstaller::ReportSuccessFromUIThread, this)))
672    NOTREACHED();
673
674  // Delete temporary files.
675  CleanupTempFiles();
676}
677
678void CrxInstaller::ReportSuccessFromUIThread() {
679  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
680
681  if (!service_weak_ || service_weak_->browser_terminating())
682    return;
683
684  if (!update_from_settings_page_) {
685    // If there is a client, tell the client about installation.
686    if (client_)
687      client_->OnInstallSuccess(extension(), install_icon_.get());
688
689    // We update the extension's granted permissions if the user already
690    // approved the install (client_ is non NULL), or we are allowed to install
691    // this silently.
692    if (client_ || allow_silent_install_) {
693      PermissionsUpdater perms_updater(profile());
694      perms_updater.GrantActivePermissions(extension());
695    }
696  }
697
698  // Install the extension if it's not blacklisted, but notify either way.
699  base::Closure on_success =
700      base::Bind(&ExtensionService::OnExtensionInstalled,
701                 service_weak_,
702                 extension(),
703                 page_ordinal_,
704                 has_requirement_errors_,
705                 install_wait_for_idle_);
706  if (bypass_blacklist_for_test_) {
707    HandleIsBlacklistedResponse(on_success, false);
708  } else {
709    ExtensionSystem::Get(profile())->blacklist()->IsBlacklisted(
710        extension()->id(),
711        base::Bind(&CrxInstaller::HandleIsBlacklistedResponse,
712                   this,
713                   on_success));
714  }
715}
716
717void CrxInstaller::HandleIsBlacklistedResponse(
718    const base::Closure& on_success,
719    bool is_blacklisted) {
720  if (is_blacklisted) {
721    string16 error = l10n_util::GetStringFUTF16(
722        IDS_EXTENSION_IS_BLACKLISTED,
723        UTF8ToUTF16(extension()->name()));
724    make_scoped_ptr(ExtensionInstallUI::Create(profile()))->OnInstallFailure(
725        extensions::CrxInstallerError(error));
726    // Show error via reporter to make tests happy.
727    ExtensionErrorReporter::GetInstance()->ReportError(error, false);  // quiet
728    UMA_HISTOGRAM_ENUMERATION("ExtensionBlacklist.BlockCRX",
729                              extension()->location(),
730                              Manifest::NUM_LOCATIONS);
731  } else {
732    on_success.Run();
733  }
734  NotifyCrxInstallComplete(!is_blacklisted);
735}
736
737void CrxInstaller::NotifyCrxInstallComplete(bool success) {
738  // Some users (such as the download shelf) need to know when a
739  // CRXInstaller is done.  Listening for the EXTENSION_* events
740  // is problematic because they don't know anything about the
741  // extension before it is unpacked, so they cannot filter based
742  // on the extension.
743  content::NotificationService::current()->Notify(
744      chrome::NOTIFICATION_CRX_INSTALLER_DONE,
745      content::Source<CrxInstaller>(this),
746      content::Details<const Extension>(
747          success ? extension() : NULL));
748
749  if (success)
750    ConfirmReEnable();
751
752}
753
754void CrxInstaller::CleanupTempFiles() {
755  if (!installer_task_runner_->RunsTasksOnCurrentThread()) {
756    if (!installer_task_runner_->PostTask(
757            FROM_HERE,
758            base::Bind(&CrxInstaller::CleanupTempFiles, this))) {
759      NOTREACHED();
760    }
761    return;
762  }
763
764  // Delete the temp directory and crx file as necessary.
765  if (!temp_dir_.value().empty()) {
766    extension_file_util::DeleteFile(temp_dir_, true);
767    temp_dir_ = base::FilePath();
768  }
769
770  if (delete_source_ && !source_file_.value().empty()) {
771    extension_file_util::DeleteFile(source_file_, false);
772    source_file_ = base::FilePath();
773  }
774}
775
776void CrxInstaller::CheckUpdateFromSettingsPage() {
777  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
778
779  ExtensionService* service = service_weak_;
780  if (!service || service->browser_terminating())
781    return;
782
783  if (off_store_install_allow_reason_ != OffStoreInstallAllowedFromSettingsPage)
784    return;
785
786  const Extension* installed_extension =
787      service->GetInstalledExtension(extension()->id());
788  if (installed_extension) {
789    // Previous version of the extension exists.
790    update_from_settings_page_ = true;
791    expected_id_ = installed_extension->id();
792    install_source_ = installed_extension->location();
793    install_cause_ = extension_misc::INSTALL_CAUSE_UPDATE;
794  }
795}
796
797void CrxInstaller::ConfirmReEnable() {
798  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
799
800  ExtensionService* service = service_weak_;
801  if (!service || service->browser_terminating())
802    return;
803
804  if (!update_from_settings_page_)
805    return;
806
807  ExtensionPrefs* prefs = service->extension_prefs();
808  if (!prefs->DidExtensionEscalatePermissions(extension()->id()))
809    return;
810
811  if (client_) {
812    AddRef();  // Balanced in InstallUIProceed() and InstallUIAbort().
813    client_->ConfirmReEnable(this, extension());
814  }
815}
816
817}  // namespace extensions
818