Home | History | Annotate | Download | only in chromedriver
      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 
     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
    798