webstore_installer.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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/string_number_conversions.h"
13#include "base/string_util.h"
14#include "base/stringprintf.h"
15#include "base/utf_string_conversions.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/download/download_crx_util.h"
18#include "chrome/browser/download/download_prefs.h"
19#include "chrome/browser/download/download_util.h"
20#include "chrome/browser/extensions/crx_installer.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/ui/browser_list.h"
23#include "chrome/browser/ui/tab_contents/tab_contents.h"
24#include "chrome/browser/ui/tabs/tab_strip_model.h"
25#include "chrome/common/chrome_notification_types.h"
26#include "chrome/common/chrome_switches.h"
27#include "chrome/common/extensions/extension.h"
28#include "chrome/common/extensions/extension_constants.h"
29#include "content/public/browser/browser_thread.h"
30#include "content/public/browser/download_manager.h"
31#include "content/public/browser/download_save_info.h"
32#include "content/public/browser/download_url_parameters.h"
33#include "content/public/browser/navigation_controller.h"
34#include "content/public/browser/navigation_entry.h"
35#include "content/public/browser/notification_details.h"
36#include "content/public/browser/notification_service.h"
37#include "content/public/browser/notification_source.h"
38#include "content/public/browser/web_contents.h"
39#include "googleurl/src/gurl.h"
40#include "net/base/escape.h"
41
42#if defined(OS_CHROMEOS)
43#include "chrome/browser/chromeos/drive/drive_file_system_util.h"
44#endif
45
46using content::BrowserContext;
47using content::BrowserThread;
48using content::DownloadItem;
49using content::DownloadManager;
50using content::NavigationController;
51using content::DownloadUrlParameters;
52
53namespace {
54
55// Key used to attach the Approval to the DownloadItem.
56const char kApprovalKey[] = "extensions.webstore_installer";
57
58const char kInvalidIdError[] = "Invalid id";
59const char kNoBrowserError[] = "No browser found";
60const char kDownloadDirectoryError[] = "Could not create download directory";
61const char kDownloadCanceledError[] = "Download canceled";
62const char kInstallCanceledError[] = "Install canceled";
63const char kDownloadInterruptedError[] = "Download interrupted";
64const char kInvalidDownloadError[] = "Download was not a CRX";
65const char kInlineInstallSource[] = "inline";
66const char kDefaultInstallSource[] = "";
67
68FilePath* g_download_directory_for_tests = NULL;
69
70GURL GetWebstoreInstallURL(
71    const std::string& extension_id, const std::string& install_source) {
72  CommandLine* cmd_line = CommandLine::ForCurrentProcess();
73  if (cmd_line->HasSwitch(switches::kAppsGalleryDownloadURL)) {
74    std::string download_url =
75        cmd_line->GetSwitchValueASCII(switches::kAppsGalleryDownloadURL);
76    return GURL(base::StringPrintf(download_url.c_str(),
77                                   extension_id.c_str()));
78  }
79  std::vector<std::string> params;
80  params.push_back("id=" + extension_id);
81  if (!install_source.empty())
82    params.push_back("installsource=" + install_source);
83  params.push_back("lang=" + g_browser_process->GetApplicationLocale());
84  params.push_back("uc");
85  std::string url_string = extension_urls::GetWebstoreUpdateUrl().spec();
86
87  GURL url(url_string + "?response=redirect&x=" +
88      net::EscapeQueryParamValue(JoinString(params, '&'), true));
89  DCHECK(url.is_valid());
90
91  return url;
92}
93
94// Must be executed on the FILE thread.
95void GetDownloadFilePath(
96    const FilePath& download_directory, const std::string& id,
97    const base::Callback<void(const FilePath&)>& callback) {
98  FilePath directory(g_download_directory_for_tests ?
99                     *g_download_directory_for_tests : download_directory);
100
101#if defined (OS_CHROMEOS)
102  // Do not use drive for extension downloads.
103  if (drive::util::IsUnderDriveMountPoint(directory))
104    directory = download_util::GetDefaultDownloadDirectory();
105#endif
106
107  // Ensure the download directory exists. TODO(asargent) - make this use
108  // common code from the downloads system.
109  if (!file_util::DirectoryExists(directory)) {
110    if (!file_util::CreateDirectory(directory)) {
111      BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
112                              base::Bind(callback, FilePath()));
113      return;
114    }
115  }
116
117  // This is to help avoid a race condition between when we generate this
118  // filename and when the download starts writing to it (think concurrently
119  // running sharded browser tests installing the same test file, for
120  // instance).
121  std::string random_number =
122      base::Uint64ToString(base::RandGenerator(kuint16max));
123
124  FilePath file = directory.AppendASCII(id + "_" + random_number + ".crx");
125
126  int uniquifier = file_util::GetUniquePathNumber(file, FILE_PATH_LITERAL(""));
127  if (uniquifier > 0)
128    file = file.InsertBeforeExtensionASCII(StringPrintf(" (%d)", uniquifier));
129
130  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
131                          base::Bind(callback, file));
132}
133
134}  // namespace
135
136namespace extensions {
137
138void WebstoreInstaller::Delegate::OnExtensionDownloadStarted(
139    const std::string& id,
140    content::DownloadItem* item) {
141}
142
143void WebstoreInstaller::Delegate::OnExtensionDownloadProgress(
144    const std::string& id,
145    content::DownloadItem* item) {
146}
147
148WebstoreInstaller::Approval::Approval()
149    : profile(NULL),
150      use_app_installed_bubble(false),
151      skip_post_install_ui(false),
152      skip_install_dialog(false),
153      record_oauth2_grant(false) {
154}
155
156scoped_ptr<WebstoreInstaller::Approval>
157WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile* profile) {
158  scoped_ptr<Approval> result(new Approval());
159  result->profile = profile;
160  return result.Pass();
161}
162
163scoped_ptr<WebstoreInstaller::Approval>
164WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
165    Profile* profile,
166    const std::string& extension_id,
167    scoped_ptr<base::DictionaryValue> parsed_manifest) {
168  scoped_ptr<Approval> result(new Approval());
169  result->extension_id = extension_id;
170  result->profile = profile;
171  result->parsed_manifest = parsed_manifest.Pass();
172  result->skip_install_dialog = true;
173  return result.Pass();
174}
175
176WebstoreInstaller::Approval::~Approval() {}
177
178const WebstoreInstaller::Approval* WebstoreInstaller::GetAssociatedApproval(
179    const DownloadItem& download) {
180  return static_cast<const Approval*>(download.GetUserData(kApprovalKey));
181}
182
183WebstoreInstaller::WebstoreInstaller(Profile* profile,
184                                     Delegate* delegate,
185                                     NavigationController* controller,
186                                     const std::string& id,
187                                     scoped_ptr<Approval> approval,
188                                     int flags)
189    : profile_(profile),
190      delegate_(delegate),
191      controller_(controller),
192      id_(id),
193      download_item_(NULL),
194      approval_(approval.release()) {
195  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
196  DCHECK(controller_);
197  download_url_ = GetWebstoreInstallURL(id, flags & FLAG_INLINE_INSTALL ?
198      kInlineInstallSource : kDefaultInstallSource);
199
200  registrar_.Add(this, chrome::NOTIFICATION_CRX_INSTALLER_DONE,
201                 content::NotificationService::AllSources());
202  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
203                 content::Source<Profile>(profile->GetOriginalProfile()));
204  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
205                 content::Source<CrxInstaller>(NULL));
206}
207
208void WebstoreInstaller::Start() {
209  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
210  AddRef();  // Balanced in ReportSuccess and ReportFailure.
211
212  if (!Extension::IdIsValid(id_)) {
213    ReportFailure(kInvalidIdError, FAILURE_REASON_OTHER);
214    return;
215  }
216
217  FilePath download_path = DownloadPrefs::FromDownloadManager(
218      BrowserContext::GetDownloadManager(profile_))->DownloadPath();
219  BrowserThread::PostTask(
220      BrowserThread::FILE, FROM_HERE,
221      base::Bind(&GetDownloadFilePath, download_path, id_,
222                 base::Bind(&WebstoreInstaller::StartDownload, this)));
223}
224
225void WebstoreInstaller::Observe(int type,
226                                const content::NotificationSource& source,
227                                const content::NotificationDetails& details) {
228  switch (type) {
229    case chrome::NOTIFICATION_CRX_INSTALLER_DONE: {
230      const Extension* extension =
231          content::Details<const Extension>(details).ptr();
232      CrxInstaller* installer = content::Source<CrxInstaller>(source).ptr();
233      if (extension == NULL && download_item_ != NULL &&
234          installer->download_url() == download_item_->GetURL() &&
235          installer->profile()->IsSameProfile(profile_)) {
236        ReportFailure(kInstallCanceledError, FAILURE_REASON_CANCELLED);
237      }
238      break;
239    }
240
241    case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
242      CHECK(profile_->IsSameProfile(content::Source<Profile>(source).ptr()));
243      const Extension* extension =
244          content::Details<const Extension>(details).ptr();
245      if (id_ == extension->id())
246        ReportSuccess();
247      break;
248    }
249
250    case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
251      CrxInstaller* crx_installer = content::Source<CrxInstaller>(source).ptr();
252      CHECK(crx_installer);
253      if (!profile_->IsSameProfile(crx_installer->profile()))
254        return;
255
256      // TODO(rdevlin.cronin): Continue removing std::string errors and
257      // replacing with string16
258      const string16* error = content::Details<const string16>(details).ptr();
259      const std::string utf8_error = UTF16ToUTF8(*error);
260      if (download_url_ == crx_installer->original_download_url())
261        ReportFailure(utf8_error, FAILURE_REASON_OTHER);
262      break;
263    }
264
265    default:
266      NOTREACHED();
267  }
268}
269
270void WebstoreInstaller::InvalidateDelegate() {
271  delegate_ = NULL;
272}
273
274void WebstoreInstaller::SetDownloadDirectoryForTests(FilePath* directory) {
275  g_download_directory_for_tests = directory;
276}
277
278WebstoreInstaller::~WebstoreInstaller() {
279  if (download_item_) {
280    download_item_->RemoveObserver(this);
281    download_item_ = NULL;
282  }
283}
284
285void WebstoreInstaller::OnDownloadStarted(
286    DownloadItem* item, net::Error error) {
287  if (!item) {
288    DCHECK_NE(net::OK, error);
289    ReportFailure(net::ErrorToString(error), FAILURE_REASON_OTHER);
290    return;
291  }
292
293  DCHECK_EQ(net::OK, error);
294  download_item_ = item;
295  download_item_->AddObserver(this);
296  if (approval_.get())
297    download_item_->SetUserData(kApprovalKey, approval_.release());
298  if (delegate_)
299    delegate_->OnExtensionDownloadStarted(id_, download_item_);
300}
301
302void WebstoreInstaller::OnDownloadUpdated(DownloadItem* download) {
303  CHECK_EQ(download_item_, download);
304
305  switch (download->GetState()) {
306    case DownloadItem::CANCELLED:
307      ReportFailure(kDownloadCanceledError, FAILURE_REASON_CANCELLED);
308      break;
309    case DownloadItem::INTERRUPTED:
310      ReportFailure(kDownloadInterruptedError, FAILURE_REASON_OTHER);
311      break;
312    case DownloadItem::COMPLETE:
313      // Wait for other notifications if the download is really an extension.
314      if (!download_crx_util::IsExtensionDownload(*download))
315        ReportFailure(kInvalidDownloadError, FAILURE_REASON_OTHER);
316      else if (delegate_)
317        delegate_->OnExtensionDownloadProgress(id_, download);
318      break;
319    case DownloadItem::IN_PROGRESS:
320      if (delegate_)
321        delegate_->OnExtensionDownloadProgress(id_, download);
322      break;
323    default:
324      // Continue listening if the download is not in one of the above states.
325      break;
326  }
327}
328
329void WebstoreInstaller::OnDownloadDestroyed(DownloadItem* download) {
330  CHECK_EQ(download_item_, download);
331  download_item_->RemoveObserver(this);
332  download_item_ = NULL;
333}
334
335void WebstoreInstaller::StartDownload(const FilePath& file) {
336  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
337
338  DownloadManager* download_manager =
339    BrowserContext::GetDownloadManager(profile_);
340  if (file.empty() ||
341      !download_manager ||
342      !controller_->GetWebContents() ||
343      !controller_->GetWebContents()->GetRenderProcessHost() ||
344      !controller_->GetWebContents()->GetRenderViewHost() ||
345      !controller_->GetWebContents()->GetBrowserContext() ||
346      !controller_->GetWebContents()->GetBrowserContext()
347        ->GetResourceContext()) {
348    ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
349    return;
350  }
351
352  // The download url for the given extension is contained in |download_url_|.
353  // We will navigate the current tab to this url to start the download. The
354  // download system will then pass the crx to the CrxInstaller.
355  download_util::RecordDownloadSource(
356      download_util::INITIATED_BY_WEBSTORE_INSTALLER);
357  scoped_ptr<DownloadUrlParameters> params(
358      DownloadUrlParameters::FromWebContents(
359          controller_->GetWebContents(), download_url_));
360  params->set_file_path(file);
361  if (controller_->GetActiveEntry())
362    params->set_referrer(
363        content::Referrer(controller_->GetActiveEntry()->GetURL(),
364                          WebKit::WebReferrerPolicyDefault));
365  params->set_callback(base::Bind(&WebstoreInstaller::OnDownloadStarted, this));
366  download_manager->DownloadUrl(params.Pass());
367}
368
369void WebstoreInstaller::ReportFailure(const std::string& error,
370                                      FailureReason reason) {
371  if (delegate_) {
372    delegate_->OnExtensionInstallFailure(id_, error, reason);
373    delegate_ = NULL;
374  }
375
376  Release();  // Balanced in Start().
377}
378
379void WebstoreInstaller::ReportSuccess() {
380  if (delegate_) {
381    delegate_->OnExtensionInstallSuccess(id_);
382    delegate_ = NULL;
383  }
384
385  Release();  // Balanced in Start().
386}
387
388}  // namespace extensions
389