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 35 using content::BrowserThread; 36 37 namespace extensions { 38 39 namespace { 40 41 void 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. 48 scoped_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 78 StartupHelper::StartupHelper() : pack_job_succeeded_(false) { 79 ExtensionsClient::Set(ChromeExtensionsClient::GetInstance()); 80 } 81 82 void 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 92 void StartupHelper::OnPackFailure(const std::string& error_message, 93 ExtensionCreator::ErrorType type) { 94 PrintPackExtensionMessage(error_message); 95 } 96 97 bool 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 119 namespace { 120 121 class 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 208 bool 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 237 namespace { 238 239 class 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 268 AppInstallHelper::AppInstallHelper() : success_(false) {} 269 270 AppInstallHelper::~AppInstallHelper() {} 271 272 WebstoreStandaloneInstaller::Callback AppInstallHelper::Callback() { 273 return base::Bind(&AppInstallHelper::OnAppInstallComplete, 274 base::Unretained(this)); 275 } 276 277 void 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 297 void 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 307 bool 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 327 StartupHelper::~StartupHelper() { 328 if (pack_job_.get()) 329 pack_job_->ClearClient(); 330 } 331 332 } // namespace extensions 333