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/startup_helper.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/command_line.h"
10#include "base/files/file_path.h"
11#include "base/message_loop/message_loop.h"
12#include "base/run_loop.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/extensions/extension_service.h"
17#include "chrome/browser/extensions/sandboxed_unpacker.h"
18#include "chrome/browser/extensions/webstore_startup_installer.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/common/chrome_switches.h"
21#include "chrome/common/extensions/chrome_extensions_client.h"
22#include "components/crx_file/id_util.h"
23#include "content/public/browser/browser_thread.h"
24#include "content/public/browser/web_contents.h"
25#include "extensions/common/extension.h"
26#include "ipc/ipc_message.h"
27
28#if defined(OS_WIN)
29#include "extensions/browser/app_window/app_window.h"
30#include "extensions/browser/app_window/app_window_registry.h"
31#include "extensions/browser/extension_registry.h"
32#include "extensions/browser/extension_util.h"
33#endif
34
35using content::BrowserThread;
36
37namespace extensions {
38
39namespace {
40
41void PrintPackExtensionMessage(const std::string& message) {
42  VLOG(1) << message;
43}
44
45// On Windows, the jumplist action for installing an ephemeral app has to use
46// the --install-ephemeral-app-from-webstore command line arg to initiate an
47// install.
48scoped_refptr<WebstoreStandaloneInstaller> CreateEphemeralAppInstaller(
49    Profile* profile,
50    const std::string& app_id,
51    WebstoreStandaloneInstaller::Callback callback) {
52  scoped_refptr<WebstoreStandaloneInstaller> installer;
53
54#if defined(OS_WIN)
55  ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
56  DCHECK(registry);
57  if (!registry->GetExtensionById(app_id, ExtensionRegistry::EVERYTHING) ||
58      !util::IsEphemeralApp(app_id, profile)) {
59    return installer;
60  }
61
62  AppWindowRegistry* app_window_registry = AppWindowRegistry::Get(profile);
63  DCHECK(app_window_registry);
64  AppWindow* app_window =
65      app_window_registry->GetCurrentAppWindowForApp(app_id);
66  if (!app_window)
67    return installer;
68
69  installer = new WebstoreInstallWithPrompt(
70      app_id, profile, app_window->GetNativeWindow(), callback);
71#endif
72
73  return installer;
74}
75
76}  // namespace
77
78StartupHelper::StartupHelper() : pack_job_succeeded_(false) {
79  ExtensionsClient::Set(ChromeExtensionsClient::GetInstance());
80}
81
82void StartupHelper::OnPackSuccess(
83    const base::FilePath& crx_path,
84    const base::FilePath& output_private_key_path) {
85  pack_job_succeeded_ = true;
86  PrintPackExtensionMessage(
87      base::UTF16ToUTF8(
88          PackExtensionJob::StandardSuccessMessage(crx_path,
89                                                   output_private_key_path)));
90}
91
92void StartupHelper::OnPackFailure(const std::string& error_message,
93                                  ExtensionCreator::ErrorType type) {
94  PrintPackExtensionMessage(error_message);
95}
96
97bool StartupHelper::PackExtension(const CommandLine& cmd_line) {
98  if (!cmd_line.HasSwitch(switches::kPackExtension))
99    return false;
100
101  // Input Paths.
102  base::FilePath src_dir =
103      cmd_line.GetSwitchValuePath(switches::kPackExtension);
104  base::FilePath private_key_path;
105  if (cmd_line.HasSwitch(switches::kPackExtensionKey)) {
106    private_key_path = cmd_line.GetSwitchValuePath(switches::kPackExtensionKey);
107  }
108
109  // Launch a job to perform the packing on the file thread.  Ignore warnings
110  // from the packing process. (e.g. Overwrite any existing crx file.)
111  pack_job_ = new PackExtensionJob(this, src_dir, private_key_path,
112                                   ExtensionCreator::kOverwriteCRX);
113  pack_job_->set_asynchronous(false);
114  pack_job_->Start();
115
116  return pack_job_succeeded_;
117}
118
119namespace {
120
121class ValidateCrxHelper : public SandboxedUnpackerClient {
122 public:
123  ValidateCrxHelper(const base::FilePath& crx_file,
124                    const base::FilePath& temp_dir,
125                    base::RunLoop* run_loop)
126      : crx_file_(crx_file), temp_dir_(temp_dir), run_loop_(run_loop),
127        finished_(false), success_(false) {}
128
129  bool finished() { return finished_; }
130  bool success() { return success_; }
131  const base::string16& error() { return error_; }
132
133  void Start() {
134    BrowserThread::PostTask(BrowserThread::FILE,
135                            FROM_HERE,
136                            base::Bind(&ValidateCrxHelper::StartOnFileThread,
137                                       this));
138  }
139
140 protected:
141  virtual ~ValidateCrxHelper() {}
142
143  virtual void OnUnpackSuccess(const base::FilePath& temp_dir,
144                               const base::FilePath& extension_root,
145                               const base::DictionaryValue* original_manifest,
146                               const Extension* extension,
147                               const SkBitmap& install_icon) OVERRIDE {
148    finished_ = true;
149    success_ = true;
150    BrowserThread::PostTask(BrowserThread::UI,
151                            FROM_HERE,
152                            base::Bind(&ValidateCrxHelper::FinishOnUIThread,
153                                       this));
154  }
155
156  virtual void OnUnpackFailure(const base::string16& error) OVERRIDE {
157    finished_ = true;
158    success_ = false;
159    error_ = error;
160    BrowserThread::PostTask(BrowserThread::UI,
161                            FROM_HERE,
162                            base::Bind(&ValidateCrxHelper::FinishOnUIThread,
163                                       this));
164  }
165
166  void FinishOnUIThread() {
167    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
168    if (run_loop_->running())
169      run_loop_->Quit();
170  }
171
172  void StartOnFileThread() {
173    CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
174    scoped_refptr<base::MessageLoopProxy> file_thread_proxy =
175        BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE);
176
177    scoped_refptr<SandboxedUnpacker> unpacker(
178        new SandboxedUnpacker(crx_file_,
179                              Manifest::INTERNAL,
180                              0, /* no special creation flags */
181                              temp_dir_,
182                              file_thread_proxy.get(),
183                              this));
184    unpacker->Start();
185  }
186
187  // The file being validated.
188  const base::FilePath& crx_file_;
189
190  // The temporary directory where the sandboxed unpacker will do work.
191  const base::FilePath& temp_dir_;
192
193  // Unowned pointer to a runloop, so our consumer can wait for us to finish.
194  base::RunLoop* run_loop_;
195
196  // Whether we're finished unpacking;
197  bool finished_;
198
199  // Whether the unpacking was successful.
200  bool success_;
201
202  // If the unpacking wasn't successful, this contains an error message.
203  base::string16 error_;
204};
205
206}  // namespace
207
208bool StartupHelper::ValidateCrx(const CommandLine& cmd_line,
209                                std::string* error) {
210  CHECK(error);
211  base::FilePath path = cmd_line.GetSwitchValuePath(switches::kValidateCrx);
212  if (path.empty()) {
213    *error = base::StringPrintf("Empty path passed for %s",
214                                switches::kValidateCrx);
215    return false;
216  }
217  base::ScopedTempDir temp_dir;
218
219  if (!temp_dir.CreateUniqueTempDir()) {
220    *error = std::string("Failed to create temp dir");
221    return false;
222  }
223
224  base::RunLoop run_loop;
225  scoped_refptr<ValidateCrxHelper> helper(
226      new ValidateCrxHelper(path, temp_dir.path(), &run_loop));
227  helper->Start();
228  if (!helper->finished())
229    run_loop.Run();
230
231  bool success = helper->success();
232  if (!success)
233    *error = base::UTF16ToUTF8(helper->error());
234  return success;
235}
236
237namespace {
238
239class AppInstallHelper {
240 public:
241  // A callback for when the install process is done.
242  typedef base::Callback<void()> DoneCallback;
243
244  AppInstallHelper();
245  virtual ~AppInstallHelper();
246  bool success() { return success_; }
247  const std::string& error() { return error_; }
248  void BeginInstall(Profile* profile,
249                    const std::string& id,
250                    bool show_prompt,
251                    DoneCallback callback);
252
253 private:
254  WebstoreStandaloneInstaller::Callback Callback();
255  void OnAppInstallComplete(bool success,
256                            const std::string& error,
257                            webstore_install::Result result);
258
259  DoneCallback done_callback_;
260
261  // These hold on to the result of the app install when it is complete.
262  bool success_;
263  std::string error_;
264
265  scoped_refptr<WebstoreStandaloneInstaller> installer_;
266};
267
268AppInstallHelper::AppInstallHelper() : success_(false) {}
269
270AppInstallHelper::~AppInstallHelper() {}
271
272WebstoreStandaloneInstaller::Callback AppInstallHelper::Callback() {
273  return base::Bind(&AppInstallHelper::OnAppInstallComplete,
274                    base::Unretained(this));
275}
276
277void AppInstallHelper::BeginInstall(
278    Profile* profile,
279    const std::string& id,
280    bool show_prompt,
281    DoneCallback done_callback) {
282  done_callback_ = done_callback;
283
284  WebstoreStandaloneInstaller::Callback callback =
285      base::Bind(&AppInstallHelper::OnAppInstallComplete,
286                 base::Unretained(this));
287
288  installer_ = CreateEphemeralAppInstaller(profile, id, callback);
289  if (installer_.get()) {
290    installer_->BeginInstall();
291  } else {
292    error_ = "Not a supported ephemeral app installation.";
293    done_callback_.Run();
294  }
295}
296
297void AppInstallHelper::OnAppInstallComplete(bool success,
298                                            const std::string& error,
299                                            webstore_install::Result result) {
300  success_ = success;
301  error_= error;
302  done_callback_.Run();
303}
304
305}  // namespace
306
307bool StartupHelper::InstallEphemeralApp(const CommandLine& cmd_line,
308                                        Profile* profile) {
309  std::string id =
310      cmd_line.GetSwitchValueASCII(switches::kInstallEphemeralAppFromWebstore);
311  if (!crx_file::id_util::IdIsValid(id)) {
312    LOG(ERROR) << "Invalid id for "
313        << switches::kInstallEphemeralAppFromWebstore << " : '" << id << "'";
314    return false;
315  }
316
317  AppInstallHelper helper;
318  base::RunLoop run_loop;
319  helper.BeginInstall(profile, id, true, run_loop.QuitClosure());
320  run_loop.Run();
321
322  if (!helper.success())
323    LOG(ERROR) << "InstallFromWebstore failed with error: " << helper.error();
324  return helper.success();
325}
326
327StartupHelper::~StartupHelper() {
328  if (pack_job_.get())
329    pack_job_->ClearClient();
330}
331
332}  // namespace extensions
333