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