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