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#endif
55
56namespace {
57
58const char* kCommonSwitches[] = {
59    "ignore-certificate-errors", "metrics-recording-only"};
60
61#if defined(OS_LINUX)
62const char* kEnableCrashReport = "enable-crash-reporter-for-testing";
63#endif
64
65Status 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
86Status 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
160Status 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
196Status 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
230Status 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
262Status 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
407Status 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
473Status 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
523namespace internal {
524
525void 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
538std::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
547Status 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
564Status 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
680void 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
690Status 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
734Status 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
766Status 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
798