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