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