capabilities.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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/capabilities.h"
6
7#include <map>
8
9#include "base/bind.h"
10#include "base/callback.h"
11#include "base/strings/string_util.h"
12#include "base/strings/stringprintf.h"
13#include "base/values.h"
14#include "chrome/test/chromedriver/chrome/status.h"
15
16namespace {
17
18typedef base::Callback<Status(const base::Value&, Capabilities*)> Parser;
19
20Status ParseDetach(
21    const base::Value& option,
22    Capabilities* capabilities) {
23  if (!option.GetAsBoolean(&capabilities->detach))
24    return Status(kUnknownError, "'detach' must be a boolean");
25  return Status(kOk);
26}
27
28Status ParseChromeBinary(
29    const base::Value& option,
30    Capabilities* capabilities) {
31  base::FilePath::StringType path_str;
32  if (!option.GetAsString(&path_str))
33    return Status(kUnknownError, "'binary' must be a string");
34  base::FilePath chrome_exe(path_str);
35  capabilities->command.SetProgram(chrome_exe);
36  return Status(kOk);
37}
38
39Status ParseLogPath(const base::Value& option, Capabilities* capabilities) {
40  if (!option.GetAsString(&capabilities->log_path))
41    return Status(kUnknownError, "'logPath' must be a string");
42  return Status(kOk);
43}
44
45Status ParseArgs(bool is_android,
46                 const base::Value& option,
47                 Capabilities* capabilities) {
48  const base::ListValue* args_list = NULL;
49  if (!option.GetAsList(&args_list))
50    return Status(kUnknownError, "'args' must be a list");
51  for (size_t i = 0; i < args_list->GetSize(); ++i) {
52    std::string arg_string;
53    if (!args_list->GetString(i, &arg_string))
54      return Status(kUnknownError, "each argument must be a string");
55    if (is_android) {
56      capabilities->android_args += "--" + arg_string + " ";
57    } else {
58      size_t separator_index = arg_string.find("=");
59      if (separator_index != std::string::npos) {
60        CommandLine::StringType arg_string_native;
61        if (!args_list->GetString(i, &arg_string_native))
62          return Status(kUnknownError, "each argument must be a string");
63        capabilities->command.AppendSwitchNative(
64            arg_string.substr(0, separator_index),
65            arg_string_native.substr(separator_index + 1));
66      } else {
67        capabilities->command.AppendSwitch(arg_string);
68      }
69    }
70  }
71  return Status(kOk);
72}
73
74Status ParsePrefs(const base::Value& option, Capabilities* capabilities) {
75  const base::DictionaryValue* prefs = NULL;
76  if (!option.GetAsDictionary(&prefs))
77    return Status(kUnknownError, "'prefs' must be a dictionary");
78  capabilities->prefs.reset(prefs->DeepCopy());
79  return Status(kOk);
80}
81
82Status ParseLocalState(const base::Value& option, Capabilities* capabilities) {
83  const base::DictionaryValue* local_state = NULL;
84  if (!option.GetAsDictionary(&local_state))
85    return Status(kUnknownError, "'localState' must be a dictionary");
86  capabilities->local_state.reset(local_state->DeepCopy());
87  return Status(kOk);
88}
89
90Status ParseExtensions(const base::Value& option, Capabilities* capabilities) {
91  const base::ListValue* extensions = NULL;
92  if (!option.GetAsList(&extensions))
93    return Status(kUnknownError, "'extensions' must be a list");
94  for (size_t i = 0; i < extensions->GetSize(); ++i) {
95    std::string extension;
96    if (!extensions->GetString(i, &extension)) {
97      return Status(kUnknownError,
98                    "each extension must be a base64 encoded string");
99    }
100    capabilities->extensions.push_back(extension);
101  }
102  return Status(kOk);
103}
104
105Status ParseProxy(const base::Value& option, Capabilities* capabilities) {
106  const base::DictionaryValue* proxy_dict;
107  if (!option.GetAsDictionary(&proxy_dict))
108    return Status(kUnknownError, "'proxy' must be a dictionary");
109  std::string proxy_type;
110  if (!proxy_dict->GetString("proxyType", &proxy_type))
111    return Status(kUnknownError, "'proxyType' must be a string");
112  proxy_type = StringToLowerASCII(proxy_type);
113  if (proxy_type == "direct") {
114    capabilities->command.AppendSwitch("no-proxy-server");
115  } else if (proxy_type == "system") {
116    // Chrome default.
117  } else if (proxy_type == "pac") {
118    CommandLine::StringType proxy_pac_url;
119    if (!proxy_dict->GetString("proxyAutoconfigUrl", &proxy_pac_url))
120      return Status(kUnknownError, "'proxyAutoconfigUrl' must be a string");
121    capabilities->command.AppendSwitchNative("proxy-pac-url", proxy_pac_url);
122  } else if (proxy_type == "autodetect") {
123    capabilities->command.AppendSwitch("proxy-auto-detect");
124  } else if (proxy_type == "manual") {
125    const char* proxy_servers_options[][2] = {
126        {"ftpProxy", "ftp"}, {"httpProxy", "http"}, {"sslProxy", "https"}};
127    const base::Value* option_value = NULL;
128    std::string proxy_servers;
129    for (size_t i = 0; i < arraysize(proxy_servers_options); ++i) {
130      if (!proxy_dict->Get(proxy_servers_options[i][0], &option_value) ||
131          option_value->IsType(base::Value::TYPE_NULL)) {
132        continue;
133      }
134      std::string value;
135      if (!option_value->GetAsString(&value)) {
136        return Status(
137            kUnknownError,
138            base::StringPrintf("'%s' must be a string",
139                               proxy_servers_options[i][0]));
140      }
141      // Converts into Chrome proxy scheme.
142      // Example: "http=localhost:9000;ftp=localhost:8000".
143      if (!proxy_servers.empty())
144        proxy_servers += ";";
145      proxy_servers += base::StringPrintf(
146          "%s=%s", proxy_servers_options[i][1], value.c_str());
147    }
148
149    std::string proxy_bypass_list;
150    if (proxy_dict->Get("noProxy", &option_value) &&
151        !option_value->IsType(base::Value::TYPE_NULL)) {
152      if (!option_value->GetAsString(&proxy_bypass_list))
153        return Status(kUnknownError, "'noProxy' must be a string");
154    }
155
156    if (proxy_servers.empty() && proxy_bypass_list.empty()) {
157      return Status(kUnknownError,
158                    "proxyType is 'manual' but no manual "
159                    "proxy capabilities were found");
160    }
161    if (!proxy_servers.empty())
162      capabilities->command.AppendSwitchASCII("proxy-server", proxy_servers);
163    if (!proxy_bypass_list.empty()) {
164      capabilities->command.AppendSwitchASCII("proxy-bypass-list",
165                                              proxy_bypass_list);
166    }
167  } else {
168    return Status(kUnknownError, "unrecognized proxy type:" + proxy_type);
169  }
170  return Status(kOk);
171}
172
173Status ParseDesktopChromeCapabilities(
174    const base::Value& capability,
175    Capabilities* capabilities) {
176  const base::DictionaryValue* chrome_options = NULL;
177  if (!capability.GetAsDictionary(&chrome_options))
178    return Status(kUnknownError, "'chromeOptions' must be a dictionary");
179
180  std::map<std::string, Parser> parser_map;
181
182  parser_map["detach"] = base::Bind(&ParseDetach);
183  parser_map["binary"] = base::Bind(&ParseChromeBinary);
184  parser_map["logPath"] = base::Bind(&ParseLogPath);
185  parser_map["args"] = base::Bind(&ParseArgs, false);
186  parser_map["prefs"] = base::Bind(&ParsePrefs);
187  parser_map["localState"] = base::Bind(&ParseLocalState);
188  parser_map["extensions"] = base::Bind(&ParseExtensions);
189
190  for (base::DictionaryValue::Iterator it(*chrome_options); !it.IsAtEnd();
191       it.Advance()) {
192    if (parser_map.find(it.key()) == parser_map.end()) {
193      return Status(kUnknownError,
194                    "unrecognized chrome option: " + it.key());
195    }
196    Status status = parser_map[it.key()].Run(it.value(), capabilities);
197    if (status.IsError())
198      return status;
199  }
200  return Status(kOk);
201}
202
203Status ParseAndroidChromeCapabilities(const base::DictionaryValue& desired_caps,
204                                      Capabilities* capabilities) {
205  const base::Value* chrome_options = NULL;
206  if (desired_caps.Get("chromeOptions", &chrome_options)) {
207    const base::DictionaryValue* chrome_options_dict = NULL;
208    if (!chrome_options->GetAsDictionary(&chrome_options_dict))
209      return Status(kUnknownError, "'chromeOptions' must be a dictionary");
210
211    const base::Value* android_package_value;
212    if (chrome_options_dict->Get("androidPackage", &android_package_value)) {
213      if (!android_package_value->GetAsString(&capabilities->android_package) ||
214          capabilities->android_package.empty()) {
215        return Status(kUnknownError,
216                      "'androidPackage' must be a non-empty string");
217      }
218
219      const base::Value* device_serial_value;
220      if (chrome_options_dict->Get("androidDeviceSerial",
221                                   &device_serial_value)) {
222        if (!device_serial_value->GetAsString(&capabilities->device_serial) ||
223            capabilities->device_serial.empty()) {
224          return Status(kUnknownError,
225                        "'androidDeviceSerial' must be a non-empty string");
226        }
227      }
228
229      const base::Value* args_value;
230      if (chrome_options_dict->Get("args", &args_value))
231        return ParseArgs(true, *args_value, capabilities);
232    }
233  }
234  return Status(kOk);
235}
236
237Status ParseLoggingPrefs(const base::DictionaryValue& desired_caps,
238                         Capabilities* capabilities) {
239  const base::Value* logging_prefs = NULL;
240  if (desired_caps.Get("loggingPrefs", &logging_prefs)) {
241    const base::DictionaryValue* logging_prefs_dict = NULL;
242    if (!logging_prefs->GetAsDictionary(&logging_prefs_dict)) {
243      return Status(kUnknownError, "'loggingPrefs' must be a dictionary");
244    }
245    // TODO(klm): verify log types.
246    // TODO(klm): verify log levels.
247    capabilities->logging_prefs.reset(logging_prefs_dict->DeepCopy());
248  }
249  return Status(kOk);
250}
251
252}  // namespace
253
254Capabilities::Capabilities()
255    : detach(false),
256      command(CommandLine::NO_PROGRAM) {}
257
258Capabilities::~Capabilities() {}
259
260bool Capabilities::IsAndroid() const {
261  return !android_package.empty();
262}
263
264Status Capabilities::Parse(const base::DictionaryValue& desired_caps) {
265  Status status = ParseLoggingPrefs(desired_caps, this);
266  if (status.IsError())
267    return status;
268  status = ParseAndroidChromeCapabilities(desired_caps, this);
269  if (status.IsError())
270    return status;
271  if (IsAndroid())
272    return Status(kOk);
273
274  std::map<std::string, Parser> parser_map;
275  parser_map["proxy"] = base::Bind(&ParseProxy);
276  parser_map["chromeOptions"] = base::Bind(&ParseDesktopChromeCapabilities);
277  for (std::map<std::string, Parser>::iterator it = parser_map.begin();
278       it != parser_map.end(); ++it) {
279    const base::Value* capability = NULL;
280    if (desired_caps.Get(it->first, &capability)) {
281      status = it->second.Run(*capability, this);
282      if (status.IsError())
283        return status;
284    }
285  }
286  return Status(kOk);
287}
288