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