Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / test / chromedriver / chrome_launcher.cc
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/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"
49
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
57
58 namespace {
59
60 const char* const kCommonSwitches[] = {
61     "ignore-certificate-errors", "metrics-recording-only"};
62
63 #if defined(OS_LINUX)
64 const char kEnableCrashReport[] = "enable-crash-reporter-for-testing";
65 #endif
66
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");
72
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");
78   }
79
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");
83
84   *automation_extension = extension_dir;
85   return Status(kOk);
86 }
87
88 Status PrepareCommandLine(int port,
89                           const Capabilities& capabilities,
90                           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()));
102   }
103   CommandLine command(program);
104   Switches switches;
105
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");
125
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);
131   }
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"));
143   }
144
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;
150
151   if (!extension_dir->CreateUniqueTempDir()) {
152     return Status(kUnknownError,
153                   "cannot create temp dir for unpacking extensions");
154   }
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);
165 }
166
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));
176
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());
187   }
188
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);
196       }
197     }
198     base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
199   }
200   return Status(kUnknownError, "unable to discover open pages");
201 }
202
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);
222   }
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;
233   }
234   return Status(kOk);
235 }
236
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);
252   }
253
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();
261   }
262
263   chrome->reset(new ChromeRemoteImpl(devtools_http_client.Pass(),
264                                      devtools_websocket_client.Pass(),
265                                      *devtools_event_listeners));
266   return Status(kOk);
267 }
268
269 Status LaunchDesktopChrome(
270     URLRequestContextGetter* context_getter,
271     int 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   CommandLine command(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;
289
290   base::LaunchOptions options;
291
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;
297
298     options.environ["CHROME_HEADLESS"] = 1;
299     options.environ["BREAKPAD_DUMP_LOCATION"] = capabilities.minidump_path;
300
301     if (!command.HasSwitch(kEnableCrashReport))
302       command.AppendSwitch(kEnableCrashReport);
303   }
304
305   // We need to allow new privileges so that chrome's setuid sandbox can run.
306   options.allow_new_privs = true;
307 #endif
308
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
315
316 #if defined(OS_POSIX)
317   base::FileHandleMappingVector no_stderr;
318   base::ScopedFD devnull;
319   if (!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;
327   }
328 #elif defined(OS_WIN)
329   // Silence chrome error message.
330   HANDLE out_read;
331   HANDLE out_write;
332   SECURITY_ATTRIBUTES sa_attr;
333
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);
342
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
348
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::ProcessHandle process;
356   if (!base::LaunchProcess(command, options, &process))
357     return Status(kUnknownError, "chrome failed to start");
358
359   scoped_ptr<DevToolsHttpClient> devtools_http_client;
360   status = WaitForDevToolsAndCheckVersion(
361       NetAddress(port), context_getter, socket_factory, &capabilities,
362       &devtools_http_client);
363
364   if (status.IsError()) {
365     int exit_code;
366     base::TerminationStatus chrome_status =
367         base::GetTerminationStatus(process, &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;
386       }
387       return Status(kUnknownError,
388                     "Chrome failed to start: " + termination_reason);
389     }
390     if (!base::KillProcess(process, 0, true)) {
391       int exit_code;
392       if (base::GetTerminationStatus(process, &exit_code) ==
393           base::TERMINATION_STATUS_STILL_RUNNING)
394         return Status(kUnknownError, "cannot kill Chrome", status);
395     }
396     return status;
397   }
398
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();
406   }
407
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,
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);
427     }
428   }
429   *chrome = chrome_desktop.Pass();
430   return Status(kOk);
431 }
432
433 Status LaunchAndroidChrome(
434     URLRequestContextGetter* context_getter,
435     int 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);
449   }
450   if (status.IsError())
451     return status;
452
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;
467   }
468
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;
478   }
479
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();
487   }
488
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);
495 }
496
497 }  // namespace
498
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);
512   }
513
514   int port = 0;
515   scoped_ptr<PortReservation> port_reservation;
516   Status port_status(kOk);
517
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);
549   }
550 }
551
552 namespace internal {
553
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';
563     }
564   }
565 }
566
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;
574 }
575
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);
591 }
592
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");
606
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");
613
614   const bool is_crx_file = magic_header == "Cr24";
615
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());
637   }
638   std::string public_key_base64;
639   base::Base64Encode(public_key, &public_key_base64);
640   std::string id = GenerateExtensionId(public_key);
641
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");
651   }
652   base::FilePath extension_dir = temp_dir.AppendASCII("extension_" + id);
653   if (!zip::Unzip(extension_crx, extension_dir))
654     return Status(kUnknownError, "cannot unzip");
655
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");
665
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;
684       }
685       id = manifest_id;
686     }
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");
694     }
695   }
696
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;
702
703   *path = extension_dir;
704   if (bg_page_tmp.size())
705     *bg_page = bg_page_tmp;
706   return Status(kOk);
707 }
708
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);
717 }
718
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);
735     }
736     extension_paths.push_back(path.value());
737     if (bg_page.length())
738       bg_pages_tmp.push_back(bg_page);
739   }
740
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());
751     }
752   }
753
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);
758   }
759   bg_pages->swap(bg_pages_tmp);
760   return Status(kOk);
761 }
762
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);
775   }
776
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());
781     }
782   }
783
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");
791   }
792   return Status(kOk);
793 }
794
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");
803
804   std::string preferences;
805   base::FilePath preferences_path =
806       default_dir.Append(chrome::kPreferencesFilename);
807
808   if (base::PathExists(preferences_path))
809     base::ReadFileToString(preferences_path, &preferences);
810   else
811     preferences = kPreferences;
812
813   Status status =
814       WritePrefsFile(preferences,
815                      custom_prefs,
816                      default_dir.Append(chrome::kPreferencesFilename));
817   if (status.IsError())
818     return status;
819
820   std::string local_state;
821   base::FilePath local_state_path =
822       user_data_dir.Append(chrome::kLocalStateFilename);
823
824   if (base::PathExists(local_state_path))
825     base::ReadFileToString(local_state_path, &local_state);
826   else
827     local_state = kLocalState;
828
829   status = WritePrefsFile(local_state,
830                           custom_local_state,
831                           user_data_dir.Append(chrome::kLocalStateFilename));
832   if (status.IsError())
833     return status;
834
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");
840   }
841   return Status(kOk);
842 }
843
844 }  // namespace internal