1 // Copyright (c) 2013 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/test/chromedriver/chrome_launcher.h" 6 7 #include <algorithm> 8 #include <vector> 9 10 #include "base/base64.h" 11 #include "base/basictypes.h" 12 #include "base/bind.h" 13 #include "base/command_line.h" 14 #include "base/files/file_path.h" 15 #include "base/files/file_util.h" 16 #include "base/files/scoped_file.h" 17 #include "base/format_macros.h" 18 #include "base/json/json_reader.h" 19 #include "base/json/json_writer.h" 20 #include "base/logging.h" 21 #include "base/process/kill.h" 22 #include "base/process/launch.h" 23 #include "base/strings/string_number_conversions.h" 24 #include "base/strings/string_util.h" 25 #include "base/strings/stringprintf.h" 26 #include "base/strings/utf_string_conversions.h" 27 #include "base/threading/platform_thread.h" 28 #include "base/time/time.h" 29 #include "base/values.h" 30 #include "chrome/common/chrome_constants.h" 31 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h" 32 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h" 33 #include "chrome/test/chromedriver/chrome/chrome_finder.h" 34 #include "chrome/test/chromedriver/chrome/chrome_remote_impl.h" 35 #include "chrome/test/chromedriver/chrome/device_manager.h" 36 #include "chrome/test/chromedriver/chrome/devtools_client_impl.h" 37 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h" 38 #include "chrome/test/chromedriver/chrome/devtools_http_client.h" 39 #include "chrome/test/chromedriver/chrome/embedded_automation_extension.h" 40 #include "chrome/test/chromedriver/chrome/status.h" 41 #include "chrome/test/chromedriver/chrome/user_data_dir.h" 42 #include "chrome/test/chromedriver/chrome/version.h" 43 #include "chrome/test/chromedriver/chrome/web_view.h" 44 #include "chrome/test/chromedriver/net/port_server.h" 45 #include "chrome/test/chromedriver/net/url_request_context_getter.h" 46 #include "crypto/rsa_private_key.h" 47 #include "crypto/sha2.h" 48 #include "third_party/zlib/google/zip.h" 49 50 #if defined(OS_POSIX) 51 #include <fcntl.h> 52 #include <sys/stat.h> 53 #include <sys/types.h> 54 #endif 55 56 namespace { 57 58 const char* kCommonSwitches[] = { 59 "ignore-certificate-errors", "metrics-recording-only"}; 60 61 #if defined(OS_LINUX) 62 const char* kEnableCrashReport = "enable-crash-reporter-for-testing"; 63 #endif 64 65 Status UnpackAutomationExtension(const base::FilePath& temp_dir, 66 base::FilePath* automation_extension) { 67 std::string decoded_extension; 68 if (!base::Base64Decode(kAutomationExtension, &decoded_extension)) 69 return Status(kUnknownError, "failed to base64decode automation extension"); 70 71 base::FilePath extension_zip = temp_dir.AppendASCII("internal.zip"); 72 int size = static_cast<int>(decoded_extension.length()); 73 if (base::WriteFile(extension_zip, decoded_extension.c_str(), size) 74 != size) { 75 return Status(kUnknownError, "failed to write automation extension zip"); 76 } 77 78 base::FilePath extension_dir = temp_dir.AppendASCII("internal"); 79 if (!zip::Unzip(extension_zip, extension_dir)) 80 return Status(kUnknownError, "failed to unzip automation extension"); 81 82 *automation_extension = extension_dir; 83 return Status(kOk); 84 } 85 86 Status PrepareCommandLine(int port, 87 const Capabilities& capabilities, 88 CommandLine* prepared_command, 89 base::ScopedTempDir* user_data_dir, 90 base::ScopedTempDir* extension_dir, 91 std::vector<std::string>* extension_bg_pages) { 92 base::FilePath program = capabilities.binary; 93 if (program.empty()) { 94 if (!FindChrome(&program)) 95 return Status(kUnknownError, "cannot find Chrome binary"); 96 } else if (!base::PathExists(program)) { 97 return Status(kUnknownError, 98 base::StringPrintf("no chrome binary at %" PRFilePath, 99 program.value().c_str())); 100 } 101 CommandLine command(program); 102 Switches switches; 103 104 for (size_t i = 0; i < arraysize(kCommonSwitches); ++i) 105 switches.SetSwitch(kCommonSwitches[i]); 106 switches.SetSwitch("disable-hang-monitor"); 107 switches.SetSwitch("disable-prompt-on-repost"); 108 switches.SetSwitch("disable-sync"); 109 switches.SetSwitch("no-first-run"); 110 switches.SetSwitch("disable-background-networking"); 111 switches.SetSwitch("disable-web-resources"); 112 switches.SetSwitch("safebrowsing-disable-auto-update"); 113 switches.SetSwitch("safebrowsing-disable-download-protection"); 114 switches.SetSwitch("disable-client-side-phishing-detection"); 115 switches.SetSwitch("disable-component-update"); 116 switches.SetSwitch("disable-default-apps"); 117 switches.SetSwitch("enable-logging"); 118 switches.SetSwitch("log-level", "0"); 119 switches.SetSwitch("password-store", "basic"); 120 switches.SetSwitch("use-mock-keychain"); 121 switches.SetSwitch("remote-debugging-port", base::IntToString(port)); 122 switches.SetSwitch("test-type", "webdriver"); 123 124 for (std::set<std::string>::const_iterator iter = 125 capabilities.exclude_switches.begin(); 126 iter != capabilities.exclude_switches.end(); 127 ++iter) { 128 switches.RemoveSwitch(*iter); 129 } 130 switches.SetFromSwitches(capabilities.switches); 131 132 if (!switches.HasSwitch("user-data-dir")) { 133 command.AppendArg("data:,"); 134 if (!user_data_dir->CreateUniqueTempDir()) 135 return Status(kUnknownError, "cannot create temp dir for user data dir"); 136 switches.SetSwitch("user-data-dir", user_data_dir->path().value()); 137 Status status = internal::PrepareUserDataDir( 138 user_data_dir->path(), capabilities.prefs.get(), 139 capabilities.local_state.get()); 140 if (status.IsError()) 141 return status; 142 } 143 144 if (!extension_dir->CreateUniqueTempDir()) { 145 return Status(kUnknownError, 146 "cannot create temp dir for unpacking extensions"); 147 } 148 Status status = internal::ProcessExtensions(capabilities.extensions, 149 extension_dir->path(), 150 true, 151 &switches, 152 extension_bg_pages); 153 if (status.IsError()) 154 return status; 155 switches.AppendToCommandLine(&command); 156 *prepared_command = command; 157 return Status(kOk); 158 } 159 160 Status WaitForDevToolsAndCheckVersion( 161 const NetAddress& address, 162 URLRequestContextGetter* context_getter, 163 const SyncWebSocketFactory& socket_factory, 164 const Capabilities* capabilities, 165 scoped_ptr<DevToolsHttpClient>* user_client) { 166 scoped_ptr<DeviceMetrics> device_metrics; 167 if (capabilities && capabilities->device_metrics) 168 device_metrics.reset(new DeviceMetrics(*capabilities->device_metrics)); 169 170 scoped_ptr<DevToolsHttpClient> client(new DevToolsHttpClient( 171 address, context_getter, socket_factory, device_metrics.Pass())); 172 base::TimeTicks deadline = 173 base::TimeTicks::Now() + base::TimeDelta::FromSeconds(60); 174 Status status = client->Init(deadline - base::TimeTicks::Now()); 175 if (status.IsError()) 176 return status; 177 if (client->browser_info()->build_no < kMinimumSupportedChromeBuildNo) { 178 return Status(kUnknownError, "Chrome version must be >= " + 179 GetMinimumSupportedChromeVersion()); 180 } 181 182 while (base::TimeTicks::Now() < deadline) { 183 WebViewsInfo views_info; 184 client->GetWebViewsInfo(&views_info); 185 for (size_t i = 0; i < views_info.GetSize(); ++i) { 186 if (views_info.Get(i).type == WebViewInfo::kPage) { 187 *user_client = client.Pass(); 188 return Status(kOk); 189 } 190 } 191 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); 192 } 193 return Status(kUnknownError, "unable to discover open pages"); 194 } 195 196 Status CreateBrowserwideDevToolsClientAndConnect( 197 const NetAddress& address, 198 const PerfLoggingPrefs& perf_logging_prefs, 199 const SyncWebSocketFactory& socket_factory, 200 ScopedVector<DevToolsEventListener>& devtools_event_listeners, 201 scoped_ptr<DevToolsClient>* browser_client) { 202 scoped_ptr<DevToolsClient> client(new DevToolsClientImpl( 203 socket_factory, base::StringPrintf("ws://%s/devtools/browser/", 204 address.ToString().c_str()), 205 DevToolsClientImpl::kBrowserwideDevToolsClientId)); 206 for (ScopedVector<DevToolsEventListener>::const_iterator it = 207 devtools_event_listeners.begin(); 208 it != devtools_event_listeners.end(); 209 ++it) { 210 // Only add listeners that subscribe to the browser-wide |DevToolsClient|. 211 // Otherwise, listeners will think this client is associated with a webview, 212 // and will send unrecognized commands to it. 213 if ((*it)->subscribes_to_browser()) 214 client->AddListener(*it); 215 } 216 // Provide the client regardless of whether it connects, so that Chrome always 217 // has a valid |devtools_websocket_client_|. If not connected, no listeners 218 // will be notified, and client will just return kDisconnected errors if used. 219 *browser_client = client.Pass(); 220 // To avoid unnecessary overhead, only connect if tracing is enabled, since 221 // the browser-wide client is currently only used for tracing. 222 if (!perf_logging_prefs.trace_categories.empty()) { 223 Status status = (*browser_client)->ConnectIfNecessary(); 224 if (status.IsError()) 225 return status; 226 } 227 return Status(kOk); 228 } 229 230 Status LaunchRemoteChromeSession( 231 URLRequestContextGetter* context_getter, 232 const SyncWebSocketFactory& socket_factory, 233 const Capabilities& capabilities, 234 ScopedVector<DevToolsEventListener>& devtools_event_listeners, 235 scoped_ptr<Chrome>* chrome) { 236 Status status(kOk); 237 scoped_ptr<DevToolsHttpClient> devtools_http_client; 238 status = WaitForDevToolsAndCheckVersion( 239 capabilities.debugger_address, context_getter, socket_factory, 240 NULL, &devtools_http_client); 241 if (status.IsError()) { 242 return Status(kUnknownError, "cannot connect to chrome at " + 243 capabilities.debugger_address.ToString(), 244 status); 245 } 246 247 scoped_ptr<DevToolsClient> devtools_websocket_client; 248 status = CreateBrowserwideDevToolsClientAndConnect( 249 capabilities.debugger_address, capabilities.perf_logging_prefs, 250 socket_factory, devtools_event_listeners, &devtools_websocket_client); 251 if (status.IsError()) { 252 LOG(WARNING) << "Browser-wide DevTools client failed to connect: " 253 << status.message(); 254 } 255 256 chrome->reset(new ChromeRemoteImpl(devtools_http_client.Pass(), 257 devtools_websocket_client.Pass(), 258 devtools_event_listeners)); 259 return Status(kOk); 260 } 261 262 Status LaunchDesktopChrome( 263 URLRequestContextGetter* context_getter, 264 int port, 265 scoped_ptr<PortReservation> port_reservation, 266 const SyncWebSocketFactory& socket_factory, 267 const Capabilities& capabilities, 268 ScopedVector<DevToolsEventListener>& devtools_event_listeners, 269 scoped_ptr<Chrome>* chrome) { 270 CommandLine command(CommandLine::NO_PROGRAM); 271 base::ScopedTempDir user_data_dir; 272 base::ScopedTempDir extension_dir; 273 std::vector<std::string> extension_bg_pages; 274 Status status = PrepareCommandLine(port, 275 capabilities, 276 &command, 277 &user_data_dir, 278 &extension_dir, 279 &extension_bg_pages); 280 if (status.IsError()) 281 return status; 282 283 base::LaunchOptions options; 284 285 #if defined(OS_LINUX) 286 // If minidump path is set in the capability, enable minidump for crashes. 287 if (!capabilities.minidump_path.empty()) { 288 VLOG(0) << "Minidump generation specified. Will save dumps to: " 289 << capabilities.minidump_path; 290 291 options.environ["CHROME_HEADLESS"] = 1; 292 options.environ["BREAKPAD_DUMP_LOCATION"] = capabilities.minidump_path; 293 294 if (!command.HasSwitch(kEnableCrashReport)) 295 command.AppendSwitch(kEnableCrashReport); 296 } 297 298 // We need to allow new privileges so that chrome's setuid sandbox can run. 299 options.allow_new_privs = true; 300 #endif 301 302 #if !defined(OS_WIN) 303 if (!capabilities.log_path.empty()) 304 options.environ["CHROME_LOG_FILE"] = capabilities.log_path; 305 if (capabilities.detach) 306 options.new_process_group = true; 307 #endif 308 309 #if defined(OS_POSIX) 310 base::FileHandleMappingVector no_stderr; 311 base::ScopedFD devnull; 312 if (!CommandLine::ForCurrentProcess()->HasSwitch("verbose")) { 313 // Redirect stderr to /dev/null, so that Chrome log spew doesn't confuse 314 // users. 315 devnull.reset(HANDLE_EINTR(open("/dev/null", O_WRONLY))); 316 if (!devnull.is_valid()) 317 return Status(kUnknownError, "couldn't open /dev/null"); 318 no_stderr.push_back(std::make_pair(devnull.get(), STDERR_FILENO)); 319 options.fds_to_remap = &no_stderr; 320 } 321 #endif 322 323 #if defined(OS_WIN) 324 std::string command_string = base::WideToUTF8(command.GetCommandLineString()); 325 #else 326 std::string command_string = command.GetCommandLineString(); 327 #endif 328 VLOG(0) << "Launching chrome: " << command_string; 329 base::ProcessHandle process; 330 if (!base::LaunchProcess(command, options, &process)) 331 return Status(kUnknownError, "chrome failed to start"); 332 333 scoped_ptr<DevToolsHttpClient> devtools_http_client; 334 status = WaitForDevToolsAndCheckVersion( 335 NetAddress(port), context_getter, socket_factory, &capabilities, 336 &devtools_http_client); 337 338 if (status.IsError()) { 339 int exit_code; 340 base::TerminationStatus chrome_status = 341 base::GetTerminationStatus(process, &exit_code); 342 if (chrome_status != base::TERMINATION_STATUS_STILL_RUNNING) { 343 std::string termination_reason; 344 switch (chrome_status) { 345 case base::TERMINATION_STATUS_NORMAL_TERMINATION: 346 termination_reason = "exited normally"; 347 break; 348 case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: 349 termination_reason = "exited abnormally"; 350 break; 351 case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: 352 termination_reason = "was killed"; 353 break; 354 case base::TERMINATION_STATUS_PROCESS_CRASHED: 355 termination_reason = "crashed"; 356 break; 357 default: 358 termination_reason = "unknown"; 359 break; 360 } 361 return Status(kUnknownError, 362 "Chrome failed to start: " + termination_reason); 363 } 364 if (!base::KillProcess(process, 0, true)) { 365 int exit_code; 366 if (base::GetTerminationStatus(process, &exit_code) == 367 base::TERMINATION_STATUS_STILL_RUNNING) 368 return Status(kUnknownError, "cannot kill Chrome", status); 369 } 370 return status; 371 } 372 373 scoped_ptr<DevToolsClient> devtools_websocket_client; 374 status = CreateBrowserwideDevToolsClientAndConnect( 375 NetAddress(port), capabilities.perf_logging_prefs, socket_factory, 376 devtools_event_listeners, &devtools_websocket_client); 377 if (status.IsError()) { 378 LOG(WARNING) << "Browser-wide DevTools client failed to connect: " 379 << status.message(); 380 } 381 382 scoped_ptr<ChromeDesktopImpl> chrome_desktop( 383 new ChromeDesktopImpl(devtools_http_client.Pass(), 384 devtools_websocket_client.Pass(), 385 devtools_event_listeners, 386 port_reservation.Pass(), 387 process, 388 command, 389 &user_data_dir, 390 &extension_dir)); 391 for (size_t i = 0; i < extension_bg_pages.size(); ++i) { 392 VLOG(0) << "Waiting for extension bg page load: " << extension_bg_pages[i]; 393 scoped_ptr<WebView> web_view; 394 Status status = chrome_desktop->WaitForPageToLoad( 395 extension_bg_pages[i], base::TimeDelta::FromSeconds(10), &web_view); 396 if (status.IsError()) { 397 return Status(kUnknownError, 398 "failed to wait for extension background page to load: " + 399 extension_bg_pages[i], 400 status); 401 } 402 } 403 *chrome = chrome_desktop.Pass(); 404 return Status(kOk); 405 } 406 407 Status LaunchAndroidChrome( 408 URLRequestContextGetter* context_getter, 409 int port, 410 scoped_ptr<PortReservation> port_reservation, 411 const SyncWebSocketFactory& socket_factory, 412 const Capabilities& capabilities, 413 ScopedVector<DevToolsEventListener>& devtools_event_listeners, 414 DeviceManager* device_manager, 415 scoped_ptr<Chrome>* chrome) { 416 Status status(kOk); 417 scoped_ptr<Device> device; 418 if (capabilities.android_device_serial.empty()) { 419 status = device_manager->AcquireDevice(&device); 420 } else { 421 status = device_manager->AcquireSpecificDevice( 422 capabilities.android_device_serial, &device); 423 } 424 if (status.IsError()) 425 return status; 426 427 Switches switches(capabilities.switches); 428 for (size_t i = 0; i < arraysize(kCommonSwitches); ++i) 429 switches.SetSwitch(kCommonSwitches[i]); 430 switches.SetSwitch("disable-fre"); 431 switches.SetSwitch("enable-remote-debugging"); 432 status = device->SetUp(capabilities.android_package, 433 capabilities.android_activity, 434 capabilities.android_process, 435 switches.ToString(), 436 capabilities.android_use_running_app, 437 port); 438 if (status.IsError()) { 439 device->TearDown(); 440 return status; 441 } 442 443 scoped_ptr<DevToolsHttpClient> devtools_http_client; 444 status = WaitForDevToolsAndCheckVersion(NetAddress(port), 445 context_getter, 446 socket_factory, 447 &capabilities, 448 &devtools_http_client); 449 if (status.IsError()) { 450 device->TearDown(); 451 return status; 452 } 453 454 scoped_ptr<DevToolsClient> devtools_websocket_client; 455 status = CreateBrowserwideDevToolsClientAndConnect( 456 NetAddress(port), capabilities.perf_logging_prefs, socket_factory, 457 devtools_event_listeners, &devtools_websocket_client); 458 if (status.IsError()) { 459 LOG(WARNING) << "Browser-wide DevTools client failed to connect: " 460 << status.message(); 461 } 462 463 chrome->reset(new ChromeAndroidImpl(devtools_http_client.Pass(), 464 devtools_websocket_client.Pass(), 465 devtools_event_listeners, 466 port_reservation.Pass(), 467 device.Pass())); 468 return Status(kOk); 469 } 470 471 } // namespace 472 473 Status LaunchChrome( 474 URLRequestContextGetter* context_getter, 475 const SyncWebSocketFactory& socket_factory, 476 DeviceManager* device_manager, 477 PortServer* port_server, 478 PortManager* port_manager, 479 const Capabilities& capabilities, 480 ScopedVector<DevToolsEventListener>& devtools_event_listeners, 481 scoped_ptr<Chrome>* chrome) { 482 if (capabilities.IsRemoteBrowser()) { 483 return LaunchRemoteChromeSession( 484 context_getter, socket_factory, 485 capabilities, devtools_event_listeners, chrome); 486 } 487 488 int port = 0; 489 scoped_ptr<PortReservation> port_reservation; 490 Status port_status(kOk); 491 492 if (capabilities.IsAndroid()) { 493 port_status = port_manager->ReservePortFromPool(&port, &port_reservation); 494 if (port_status.IsError()) 495 return Status(kUnknownError, "cannot reserve port for Chrome", 496 port_status); 497 return LaunchAndroidChrome(context_getter, 498 port, 499 port_reservation.Pass(), 500 socket_factory, 501 capabilities, 502 devtools_event_listeners, 503 device_manager, 504 chrome); 505 } else { 506 if (port_server) 507 port_status = port_server->ReservePort(&port, &port_reservation); 508 else 509 port_status = port_manager->ReservePort(&port, &port_reservation); 510 if (port_status.IsError()) 511 return Status(kUnknownError, "cannot reserve port for Chrome", 512 port_status); 513 return LaunchDesktopChrome(context_getter, 514 port, 515 port_reservation.Pass(), 516 socket_factory, 517 capabilities, 518 devtools_event_listeners, 519 chrome); 520 } 521 } 522 523 namespace internal { 524 525 void ConvertHexadecimalToIDAlphabet(std::string* id) { 526 for (size_t i = 0; i < id->size(); ++i) { 527 int val; 528 if (base::HexStringToInt(base::StringPiece(id->begin() + i, 529 id->begin() + i + 1), 530 &val)) { 531 (*id)[i] = val + 'a'; 532 } else { 533 (*id)[i] = 'a'; 534 } 535 } 536 } 537 538 std::string GenerateExtensionId(const std::string& input) { 539 uint8 hash[16]; 540 crypto::SHA256HashString(input, hash, sizeof(hash)); 541 std::string output = 542 base::StringToLowerASCII(base::HexEncode(hash, sizeof(hash))); 543 ConvertHexadecimalToIDAlphabet(&output); 544 return output; 545 } 546 547 Status GetExtensionBackgroundPage(const base::DictionaryValue* manifest, 548 const std::string& id, 549 std::string* bg_page) { 550 std::string bg_page_name; 551 bool persistent = true; 552 manifest->GetBoolean("background.persistent", &persistent); 553 const base::Value* unused_value; 554 if (manifest->Get("background.scripts", &unused_value)) 555 bg_page_name = "_generated_background_page.html"; 556 manifest->GetString("background.page", &bg_page_name); 557 manifest->GetString("background_page", &bg_page_name); 558 if (bg_page_name.empty() || !persistent) 559 return Status(kOk); 560 *bg_page = "chrome-extension://" + id + "/" + bg_page_name; 561 return Status(kOk); 562 } 563 564 Status ProcessExtension(const std::string& extension, 565 const base::FilePath& temp_dir, 566 base::FilePath* path, 567 std::string* bg_page) { 568 // Decodes extension string. 569 // Some WebDriver client base64 encoders follow RFC 1521, which require that 570 // 'encoded lines be no more than 76 characters long'. Just remove any 571 // newlines. 572 std::string extension_base64; 573 base::RemoveChars(extension, "\n", &extension_base64); 574 std::string decoded_extension; 575 if (!base::Base64Decode(extension_base64, &decoded_extension)) 576 return Status(kUnknownError, "cannot base64 decode"); 577 578 // If the file is a crx file, extract the extension's ID from its public key. 579 // Otherwise generate a random public key and use its derived extension ID. 580 std::string public_key; 581 std::string magic_header = decoded_extension.substr(0, 4); 582 if (magic_header.size() != 4) 583 return Status(kUnknownError, "cannot extract magic number"); 584 585 const bool is_crx_file = magic_header == "Cr24"; 586 587 if (is_crx_file) { 588 // Assume a CRX v2 file - see https://developer.chrome.com/extensions/crx. 589 std::string key_len_str = decoded_extension.substr(8, 4); 590 if (key_len_str.size() != 4) 591 return Status(kUnknownError, "cannot extract public key length"); 592 uint32 key_len = *reinterpret_cast<const uint32*>(key_len_str.c_str()); 593 public_key = decoded_extension.substr(16, key_len); 594 if (key_len != public_key.size()) 595 return Status(kUnknownError, "invalid public key length"); 596 } else { 597 // Not a CRX file. Generate RSA keypair to get a valid extension id. 598 scoped_ptr<crypto::RSAPrivateKey> key_pair( 599 crypto::RSAPrivateKey::Create(2048)); 600 if (!key_pair) 601 return Status(kUnknownError, "cannot generate RSA key pair"); 602 std::vector<uint8> public_key_vector; 603 if (!key_pair->ExportPublicKey(&public_key_vector)) 604 return Status(kUnknownError, "cannot extract public key"); 605 public_key = 606 std::string(reinterpret_cast<char*>(&public_key_vector.front()), 607 public_key_vector.size()); 608 } 609 std::string public_key_base64; 610 base::Base64Encode(public_key, &public_key_base64); 611 std::string id = GenerateExtensionId(public_key); 612 613 // Unzip the crx file. 614 base::ScopedTempDir temp_crx_dir; 615 if (!temp_crx_dir.CreateUniqueTempDir()) 616 return Status(kUnknownError, "cannot create temp dir"); 617 base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx"); 618 int size = static_cast<int>(decoded_extension.length()); 619 if (base::WriteFile(extension_crx, decoded_extension.c_str(), size) != 620 size) { 621 return Status(kUnknownError, "cannot write file"); 622 } 623 base::FilePath extension_dir = temp_dir.AppendASCII("extension_" + id); 624 if (!zip::Unzip(extension_crx, extension_dir)) 625 return Status(kUnknownError, "cannot unzip"); 626 627 // Parse the manifest and set the 'key' if not already present. 628 base::FilePath manifest_path(extension_dir.AppendASCII("manifest.json")); 629 std::string manifest_data; 630 if (!base::ReadFileToString(manifest_path, &manifest_data)) 631 return Status(kUnknownError, "cannot read manifest"); 632 scoped_ptr<base::Value> manifest_value(base::JSONReader::Read(manifest_data)); 633 base::DictionaryValue* manifest; 634 if (!manifest_value || !manifest_value->GetAsDictionary(&manifest)) 635 return Status(kUnknownError, "invalid manifest"); 636 637 std::string manifest_key_base64; 638 if (manifest->GetString("key", &manifest_key_base64)) { 639 // If there is a key in both the header and the manifest, use the key in the 640 // manifest. This allows chromedriver users users who generate dummy crxs 641 // to set the manifest key and have a consistent ID. 642 std::string manifest_key; 643 if (!base::Base64Decode(manifest_key_base64, &manifest_key)) 644 return Status(kUnknownError, "'key' in manifest is not base64 encoded"); 645 std::string manifest_id = GenerateExtensionId(manifest_key); 646 if (id != manifest_id) { 647 if (is_crx_file) { 648 LOG(WARNING) 649 << "Public key in crx header is different from key in manifest" 650 << std::endl << "key from header: " << public_key_base64 651 << std::endl << "key from manifest: " << manifest_key_base64 652 << std::endl << "generated extension id from header key: " << id 653 << std::endl << "generated extension id from manifest key: " 654 << manifest_id; 655 } 656 id = manifest_id; 657 } 658 } else { 659 manifest->SetString("key", public_key_base64); 660 base::JSONWriter::Write(manifest, &manifest_data); 661 if (base::WriteFile( 662 manifest_path, manifest_data.c_str(), manifest_data.size()) != 663 static_cast<int>(manifest_data.size())) { 664 return Status(kUnknownError, "cannot add 'key' to manifest"); 665 } 666 } 667 668 // Get extension's background page URL, if there is one. 669 std::string bg_page_tmp; 670 Status status = GetExtensionBackgroundPage(manifest, id, &bg_page_tmp); 671 if (status.IsError()) 672 return status; 673 674 *path = extension_dir; 675 if (bg_page_tmp.size()) 676 *bg_page = bg_page_tmp; 677 return Status(kOk); 678 } 679 680 void UpdateExtensionSwitch(Switches* switches, 681 const char name[], 682 const base::FilePath::StringType& extension) { 683 base::FilePath::StringType value = switches->GetSwitchValueNative(name); 684 if (value.length()) 685 value += FILE_PATH_LITERAL(","); 686 value += extension; 687 switches->SetSwitch(name, value); 688 } 689 690 Status ProcessExtensions(const std::vector<std::string>& extensions, 691 const base::FilePath& temp_dir, 692 bool include_automation_extension, 693 Switches* switches, 694 std::vector<std::string>* bg_pages) { 695 std::vector<std::string> bg_pages_tmp; 696 std::vector<base::FilePath::StringType> extension_paths; 697 for (size_t i = 0; i < extensions.size(); ++i) { 698 base::FilePath path; 699 std::string bg_page; 700 Status status = ProcessExtension(extensions[i], temp_dir, &path, &bg_page); 701 if (status.IsError()) { 702 return Status( 703 kUnknownError, 704 base::StringPrintf("cannot process extension #%" PRIuS, i + 1), 705 status); 706 } 707 extension_paths.push_back(path.value()); 708 if (bg_page.length()) 709 bg_pages_tmp.push_back(bg_page); 710 } 711 712 if (include_automation_extension) { 713 base::FilePath automation_extension; 714 Status status = UnpackAutomationExtension(temp_dir, &automation_extension); 715 if (status.IsError()) 716 return status; 717 if (switches->HasSwitch("disable-extensions")) { 718 UpdateExtensionSwitch(switches, "load-component-extension", 719 automation_extension.value()); 720 } else { 721 extension_paths.push_back(automation_extension.value()); 722 } 723 } 724 725 if (extension_paths.size()) { 726 base::FilePath::StringType extension_paths_value = JoinString( 727 extension_paths, FILE_PATH_LITERAL(',')); 728 UpdateExtensionSwitch(switches, "load-extension", extension_paths_value); 729 } 730 bg_pages->swap(bg_pages_tmp); 731 return Status(kOk); 732 } 733 734 Status WritePrefsFile( 735 const std::string& template_string, 736 const base::DictionaryValue* custom_prefs, 737 const base::FilePath& path) { 738 int code; 739 std::string error_msg; 740 scoped_ptr<base::Value> template_value(base::JSONReader::ReadAndReturnError( 741 template_string, 0, &code, &error_msg)); 742 base::DictionaryValue* prefs; 743 if (!template_value || !template_value->GetAsDictionary(&prefs)) { 744 return Status(kUnknownError, 745 "cannot parse internal JSON template: " + error_msg); 746 } 747 748 if (custom_prefs) { 749 for (base::DictionaryValue::Iterator it(*custom_prefs); !it.IsAtEnd(); 750 it.Advance()) { 751 prefs->Set(it.key(), it.value().DeepCopy()); 752 } 753 } 754 755 std::string prefs_str; 756 base::JSONWriter::Write(prefs, &prefs_str); 757 VLOG(0) << "Populating " << path.BaseName().value() 758 << " file: " << PrettyPrintValue(*prefs); 759 if (static_cast<int>(prefs_str.length()) != base::WriteFile( 760 path, prefs_str.c_str(), prefs_str.length())) { 761 return Status(kUnknownError, "failed to write prefs file"); 762 } 763 return Status(kOk); 764 } 765 766 Status PrepareUserDataDir( 767 const base::FilePath& user_data_dir, 768 const base::DictionaryValue* custom_prefs, 769 const base::DictionaryValue* custom_local_state) { 770 base::FilePath default_dir = 771 user_data_dir.AppendASCII(chrome::kInitialProfile); 772 if (!base::CreateDirectory(default_dir)) 773 return Status(kUnknownError, "cannot create default profile directory"); 774 775 Status status = 776 WritePrefsFile(kPreferences, 777 custom_prefs, 778 default_dir.Append(chrome::kPreferencesFilename)); 779 if (status.IsError()) 780 return status; 781 782 status = WritePrefsFile(kLocalState, 783 custom_local_state, 784 user_data_dir.Append(chrome::kLocalStateFilename)); 785 if (status.IsError()) 786 return status; 787 788 // Write empty "First Run" file, otherwise Chrome will wipe the default 789 // profile that was written. 790 if (base::WriteFile( 791 user_data_dir.Append(chrome::kFirstRunSentinel), "", 0) != 0) { 792 return Status(kUnknownError, "failed to write first run file"); 793 } 794 return Status(kOk); 795 } 796 797 } // namespace internal 798