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