blob: a662982adb0d7d592bcfe0ae896b9f599eb514b3 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/startup_helper.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/sandboxed_unpacker.h"
#include "chrome/browser/extensions/webstore_startup_installer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/chrome_extensions_client.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/extension.h"
#include "ipc/ipc_message.h"
#if defined(OS_WIN)
#include "apps/app_window.h"
#include "apps/app_window_registry.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#endif
using content::BrowserThread;
namespace {
void PrintPackExtensionMessage(const std::string& message) {
VLOG(1) << message;
}
// On Windows, the jumplist action for installing an ephemeral app has to use
// the --install-from-webstore command line arg to initiate an install.
scoped_refptr<extensions::WebstoreStandaloneInstaller>
CreateEphemeralAppInstaller(
Profile* profile,
const std::string& app_id,
extensions::WebstoreStandaloneInstaller::Callback callback) {
scoped_refptr<extensions::WebstoreStandaloneInstaller> installer;
#if defined(OS_WIN)
using extensions::ExtensionRegistry;
ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
DCHECK(registry);
if (!registry->GetExtensionById(app_id, ExtensionRegistry::EVERYTHING) ||
!extensions::util::IsEphemeralApp(app_id, profile)) {
return installer;
}
apps::AppWindowRegistry* app_window_registry =
apps::AppWindowRegistry::Get(profile);
DCHECK(app_window_registry);
apps::AppWindow* app_window =
app_window_registry->GetCurrentAppWindowForApp(app_id);
if (!app_window)
return installer;
installer = new extensions::WebstoreInstallWithPrompt(
app_id, profile, app_window->GetNativeWindow(), callback);
#endif
return installer;
}
} // namespace
namespace extensions {
StartupHelper::StartupHelper() : pack_job_succeeded_(false) {
ExtensionsClient::Set(ChromeExtensionsClient::GetInstance());
}
void StartupHelper::OnPackSuccess(
const base::FilePath& crx_path,
const base::FilePath& output_private_key_path) {
pack_job_succeeded_ = true;
PrintPackExtensionMessage(
base::UTF16ToUTF8(
PackExtensionJob::StandardSuccessMessage(crx_path,
output_private_key_path)));
}
void StartupHelper::OnPackFailure(const std::string& error_message,
ExtensionCreator::ErrorType type) {
PrintPackExtensionMessage(error_message);
}
bool StartupHelper::PackExtension(const CommandLine& cmd_line) {
if (!cmd_line.HasSwitch(switches::kPackExtension))
return false;
// Input Paths.
base::FilePath src_dir =
cmd_line.GetSwitchValuePath(switches::kPackExtension);
base::FilePath private_key_path;
if (cmd_line.HasSwitch(switches::kPackExtensionKey)) {
private_key_path = cmd_line.GetSwitchValuePath(switches::kPackExtensionKey);
}
// Launch a job to perform the packing on the file thread. Ignore warnings
// from the packing process. (e.g. Overwrite any existing crx file.)
pack_job_ = new PackExtensionJob(this, src_dir, private_key_path,
ExtensionCreator::kOverwriteCRX);
pack_job_->set_asynchronous(false);
pack_job_->Start();
return pack_job_succeeded_;
}
namespace {
class ValidateCrxHelper : public SandboxedUnpackerClient {
public:
ValidateCrxHelper(const base::FilePath& crx_file,
const base::FilePath& temp_dir,
base::RunLoop* run_loop)
: crx_file_(crx_file), temp_dir_(temp_dir), run_loop_(run_loop),
finished_(false), success_(false) {}
bool finished() { return finished_; }
bool success() { return success_; }
const base::string16& error() { return error_; }
void Start() {
BrowserThread::PostTask(BrowserThread::FILE,
FROM_HERE,
base::Bind(&ValidateCrxHelper::StartOnFileThread,
this));
}
protected:
virtual ~ValidateCrxHelper() {}
virtual void OnUnpackSuccess(const base::FilePath& temp_dir,
const base::FilePath& extension_root,
const base::DictionaryValue* original_manifest,
const Extension* extension,
const SkBitmap& install_icon) OVERRIDE {
finished_ = true;
success_ = true;
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&ValidateCrxHelper::FinishOnUIThread,
this));
}
virtual void OnUnpackFailure(const base::string16& error) OVERRIDE {
finished_ = true;
success_ = false;
error_ = error;
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&ValidateCrxHelper::FinishOnUIThread,
this));
}
void FinishOnUIThread() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (run_loop_->running())
run_loop_->Quit();
}
void StartOnFileThread() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
scoped_refptr<base::MessageLoopProxy> file_thread_proxy =
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE);
scoped_refptr<SandboxedUnpacker> unpacker(
new SandboxedUnpacker(crx_file_,
Manifest::INTERNAL,
0, /* no special creation flags */
temp_dir_,
file_thread_proxy.get(),
this));
unpacker->Start();
}
// The file being validated.
const base::FilePath& crx_file_;
// The temporary directory where the sandboxed unpacker will do work.
const base::FilePath& temp_dir_;
// Unowned pointer to a runloop, so our consumer can wait for us to finish.
base::RunLoop* run_loop_;
// Whether we're finished unpacking;
bool finished_;
// Whether the unpacking was successful.
bool success_;
// If the unpacking wasn't successful, this contains an error message.
base::string16 error_;
};
} // namespace
bool StartupHelper::ValidateCrx(const CommandLine& cmd_line,
std::string* error) {
CHECK(error);
base::FilePath path = cmd_line.GetSwitchValuePath(switches::kValidateCrx);
if (path.empty()) {
*error = base::StringPrintf("Empty path passed for %s",
switches::kValidateCrx);
return false;
}
base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDir()) {
*error = std::string("Failed to create temp dir");
return false;
}
base::RunLoop run_loop;
scoped_refptr<ValidateCrxHelper> helper(
new ValidateCrxHelper(path, temp_dir.path(), &run_loop));
helper->Start();
if (!helper->finished())
run_loop.Run();
bool success = helper->success();
if (!success)
*error = base::UTF16ToUTF8(helper->error());
return success;
}
namespace {
class AppInstallHelper {
public:
// A callback for when the install process is done.
typedef base::Callback<void()> DoneCallback;
AppInstallHelper();
virtual ~AppInstallHelper();
bool success() { return success_; }
const std::string& error() { return error_; }
void BeginInstall(Profile* profile,
const std::string& id,
bool show_prompt,
DoneCallback callback);
private:
WebstoreStandaloneInstaller::Callback Callback();
void OnAppInstallComplete(bool success,
const std::string& error,
webstore_install::Result result);
DoneCallback done_callback_;
// These hold on to the result of the app install when it is complete.
bool success_;
std::string error_;
scoped_refptr<WebstoreStandaloneInstaller> installer_;
};
AppInstallHelper::AppInstallHelper() : success_(false) {}
AppInstallHelper::~AppInstallHelper() {}
WebstoreStandaloneInstaller::Callback AppInstallHelper::Callback() {
return base::Bind(&AppInstallHelper::OnAppInstallComplete,
base::Unretained(this));
}
void AppInstallHelper::BeginInstall(
Profile* profile,
const std::string& id,
bool show_prompt,
DoneCallback done_callback) {
done_callback_ = done_callback;
WebstoreStandaloneInstaller::Callback callback =
base::Bind(&AppInstallHelper::OnAppInstallComplete,
base::Unretained(this));
installer_ = CreateEphemeralAppInstaller(profile, id, callback);
if (!installer_.get()) {
installer_ =
new WebstoreStartupInstaller(id, profile, show_prompt, callback);
}
installer_->BeginInstall();
}
void AppInstallHelper::OnAppInstallComplete(bool success,
const std::string& error,
webstore_install::Result result) {
success_ = success;
error_= error;
done_callback_.Run();
}
void DeleteHelperAndRunCallback(AppInstallHelper* helper,
base::Callback<void()> callback) {
delete helper;
callback.Run();
}
} // namespace
bool StartupHelper::InstallFromWebstore(const CommandLine& cmd_line,
Profile* profile) {
std::string id = cmd_line.GetSwitchValueASCII(switches::kInstallFromWebstore);
if (!Extension::IdIsValid(id)) {
LOG(ERROR) << "Invalid id for " << switches::kInstallFromWebstore
<< " : '" << id << "'";
return false;
}
AppInstallHelper helper;
base::RunLoop run_loop;
helper.BeginInstall(profile, id, true, run_loop.QuitClosure());
run_loop.Run();
if (!helper.success())
LOG(ERROR) << "InstallFromWebstore failed with error: " << helper.error();
return helper.success();
}
void StartupHelper::LimitedInstallFromWebstore(
const CommandLine& cmd_line,
Profile* profile,
base::Callback<void()> done_callback) {
std::string id = WebStoreIdFromLimitedInstallCmdLine(cmd_line);
if (!Extension::IdIsValid(id)) {
LOG(ERROR) << "Invalid index for " << switches::kLimitedInstallFromWebstore;
done_callback.Run();
return;
}
AppInstallHelper* helper = new AppInstallHelper();
helper->BeginInstall(profile, id, false /*show_prompt*/,
base::Bind(&DeleteHelperAndRunCallback,
helper, done_callback));
}
std::string StartupHelper::WebStoreIdFromLimitedInstallCmdLine(
const CommandLine& cmd_line) {
std::string index = cmd_line.GetSwitchValueASCII(
switches::kLimitedInstallFromWebstore);
std::string id;
if (index == "1") {
id = "nckgahadagoaajjgafhacjanaoiihapd";
} else if (index == "2") {
id = "ecglahbcnmdpdciemllbhojghbkagdje";
}
return id;
}
StartupHelper::~StartupHelper() {
if (pack_job_.get())
pack_job_->ClearClient();
}
} // namespace extensions