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