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