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