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