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