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