capabilities.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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/json/string_escape.h"
12#include "base/logging.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/strings/string_split.h"
15#include "base/strings/string_tokenizer.h"
16#include "base/strings/string_util.h"
17#include "base/strings/stringprintf.h"
18#include "base/strings/utf_string_conversions.h"
19#include "base/values.h"
20#include "chrome/test/chromedriver/chrome/status.h"
21#include "chrome/test/chromedriver/logging.h"
22#include "net/base/net_util.h"
23
24namespace {
25
26typedef base::Callback<Status(const base::Value&, Capabilities*)> Parser;
27
28Status ParseBoolean(
29    bool* to_set,
30    const base::Value& option,
31    Capabilities* capabilities) {
32  if (!option.GetAsBoolean(to_set))
33    return Status(kUnknownError, "must be a boolean");
34  return Status(kOk);
35}
36
37Status ParseString(std::string* to_set,
38                   const base::Value& option,
39                   Capabilities* capabilities) {
40  std::string str;
41  if (!option.GetAsString(&str))
42    return Status(kUnknownError, "must be a string");
43  if (str.empty())
44    return Status(kUnknownError, "cannot be empty");
45  *to_set = str;
46  return Status(kOk);
47}
48
49Status ParseFilePath(base::FilePath* to_set,
50                     const base::Value& option,
51                     Capabilities* capabilities) {
52  base::FilePath::StringType str;
53  if (!option.GetAsString(&str))
54    return Status(kUnknownError, "must be a string");
55  *to_set = base::FilePath(str);
56  return Status(kOk);
57}
58
59Status ParseDict(scoped_ptr<base::DictionaryValue>* to_set,
60                 const base::Value& option,
61                 Capabilities* capabilities) {
62  const base::DictionaryValue* dict = NULL;
63  if (!option.GetAsDictionary(&dict))
64    return Status(kUnknownError, "must be a dictionary");
65  to_set->reset(dict->DeepCopy());
66  return Status(kOk);
67}
68
69Status IgnoreDeprecatedOption(
70    const char* option_name,
71    const base::Value& option,
72    Capabilities* capabilities) {
73  LOG(WARNING) << "Deprecated chrome option is ignored: " << option_name;
74  return Status(kOk);
75}
76
77Status IgnoreCapability(const base::Value& option, Capabilities* capabilities) {
78  return Status(kOk);
79}
80
81Status ParseLogPath(const base::Value& option, Capabilities* capabilities) {
82  if (!option.GetAsString(&capabilities->log_path))
83    return Status(kUnknownError, "must be a string");
84  return Status(kOk);
85}
86
87Status ParseSwitches(const base::Value& option,
88                     Capabilities* capabilities) {
89  const base::ListValue* switches_list = NULL;
90  if (!option.GetAsList(&switches_list))
91    return Status(kUnknownError, "must be a list");
92  for (size_t i = 0; i < switches_list->GetSize(); ++i) {
93    std::string arg_string;
94    if (!switches_list->GetString(i, &arg_string))
95      return Status(kUnknownError, "each argument must be a string");
96    capabilities->switches.SetUnparsedSwitch(arg_string);
97  }
98  return Status(kOk);
99}
100
101Status ParseExtensions(const base::Value& option, Capabilities* capabilities) {
102  const base::ListValue* extensions = NULL;
103  if (!option.GetAsList(&extensions))
104    return Status(kUnknownError, "must be a list");
105  for (size_t i = 0; i < extensions->GetSize(); ++i) {
106    std::string extension;
107    if (!extensions->GetString(i, &extension)) {
108      return Status(kUnknownError,
109                    "each extension must be a base64 encoded string");
110    }
111    capabilities->extensions.push_back(extension);
112  }
113  return Status(kOk);
114}
115
116Status ParseProxy(const base::Value& option, Capabilities* capabilities) {
117  const base::DictionaryValue* proxy_dict;
118  if (!option.GetAsDictionary(&proxy_dict))
119    return Status(kUnknownError, "must be a dictionary");
120  std::string proxy_type;
121  if (!proxy_dict->GetString("proxyType", &proxy_type))
122    return Status(kUnknownError, "'proxyType' must be a string");
123  proxy_type = StringToLowerASCII(proxy_type);
124  if (proxy_type == "direct") {
125    capabilities->switches.SetSwitch("no-proxy-server");
126  } else if (proxy_type == "system") {
127    // Chrome default.
128  } else if (proxy_type == "pac") {
129    CommandLine::StringType proxy_pac_url;
130    if (!proxy_dict->GetString("proxyAutoconfigUrl", &proxy_pac_url))
131      return Status(kUnknownError, "'proxyAutoconfigUrl' must be a string");
132    capabilities->switches.SetSwitch("proxy-pac-url", proxy_pac_url);
133  } else if (proxy_type == "autodetect") {
134    capabilities->switches.SetSwitch("proxy-auto-detect");
135  } else if (proxy_type == "manual") {
136    const char* proxy_servers_options[][2] = {
137        {"ftpProxy", "ftp"}, {"httpProxy", "http"}, {"sslProxy", "https"}};
138    const base::Value* option_value = NULL;
139    std::string proxy_servers;
140    for (size_t i = 0; i < arraysize(proxy_servers_options); ++i) {
141      if (!proxy_dict->Get(proxy_servers_options[i][0], &option_value) ||
142          option_value->IsType(base::Value::TYPE_NULL)) {
143        continue;
144      }
145      std::string value;
146      if (!option_value->GetAsString(&value)) {
147        return Status(
148            kUnknownError,
149            base::StringPrintf("'%s' must be a string",
150                               proxy_servers_options[i][0]));
151      }
152      // Converts into Chrome proxy scheme.
153      // Example: "http=localhost:9000;ftp=localhost:8000".
154      if (!proxy_servers.empty())
155        proxy_servers += ";";
156      proxy_servers += base::StringPrintf(
157          "%s=%s", proxy_servers_options[i][1], value.c_str());
158    }
159
160    std::string proxy_bypass_list;
161    if (proxy_dict->Get("noProxy", &option_value) &&
162        !option_value->IsType(base::Value::TYPE_NULL)) {
163      if (!option_value->GetAsString(&proxy_bypass_list))
164        return Status(kUnknownError, "'noProxy' must be a string");
165    }
166
167    if (proxy_servers.empty() && proxy_bypass_list.empty()) {
168      return Status(kUnknownError,
169                    "proxyType is 'manual' but no manual "
170                    "proxy capabilities were found");
171    }
172    if (!proxy_servers.empty())
173      capabilities->switches.SetSwitch("proxy-server", proxy_servers);
174    if (!proxy_bypass_list.empty()) {
175      capabilities->switches.SetSwitch("proxy-bypass-list",
176                                       proxy_bypass_list);
177    }
178  } else {
179    return Status(kUnknownError, "unrecognized proxy type:" + proxy_type);
180  }
181  return Status(kOk);
182}
183
184Status ParseExcludeSwitches(const base::Value& option,
185                            Capabilities* capabilities) {
186  const base::ListValue* switches = NULL;
187  if (!option.GetAsList(&switches))
188    return Status(kUnknownError, "must be a list");
189  for (size_t i = 0; i < switches->GetSize(); ++i) {
190    std::string switch_name;
191    if (!switches->GetString(i, &switch_name)) {
192      return Status(kUnknownError,
193                    "each switch to be removed must be a string");
194    }
195    capabilities->exclude_switches.insert(switch_name);
196  }
197  return Status(kOk);
198}
199
200Status ParseUseExistingBrowser(const base::Value& option,
201                               Capabilities* capabilities) {
202  std::string server_addr;
203  if (!option.GetAsString(&server_addr))
204    return Status(kUnknownError, "must be 'host:port'");
205
206  std::vector<std::string> values;
207  base::SplitString(server_addr, ':', &values);
208  if (values.size() != 2)
209    return Status(kUnknownError, "must be 'host:port'");
210
211  int port = 0;
212  base::StringToInt(values[1], &port);
213  if (port <= 0)
214    return Status(kUnknownError, "port must be > 0");
215
216  capabilities->debugger_address = NetAddress(values[0], port);
217  return Status(kOk);
218}
219
220Status ParseLoggingPrefs(const base::Value& option,
221                         Capabilities* capabilities) {
222  const base::DictionaryValue* logging_prefs = NULL;
223  if (!option.GetAsDictionary(&logging_prefs))
224    return Status(kUnknownError, "must be a dictionary");
225
226  for (base::DictionaryValue::Iterator pref(*logging_prefs);
227       !pref.IsAtEnd(); pref.Advance()) {
228    std::string type = pref.key();
229    Log::Level level;
230    std::string level_name;
231    if (!pref.value().GetAsString(&level_name) ||
232        !WebDriverLog::NameToLevel(level_name, &level)) {
233      return Status(kUnknownError, "invalid log level for '" + type + "' log");
234    }
235    capabilities->logging_prefs.insert(std::make_pair(type, level));
236  }
237  return Status(kOk);
238}
239
240Status ParseChromeOptions(
241    const base::Value& capability,
242    Capabilities* capabilities) {
243  const base::DictionaryValue* chrome_options = NULL;
244  if (!capability.GetAsDictionary(&chrome_options))
245    return Status(kUnknownError, "must be a dictionary");
246
247  bool is_android = chrome_options->HasKey("androidPackage");
248  bool is_existing = chrome_options->HasKey("debuggerAddress");
249
250  std::map<std::string, Parser> parser_map;
251  // Ignore 'args', 'binary' and 'extensions' capabilities by default, since the
252  // Java client always passes them.
253  parser_map["args"] = base::Bind(&IgnoreCapability);
254  parser_map["binary"] = base::Bind(&IgnoreCapability);
255  parser_map["extensions"] = base::Bind(&IgnoreCapability);
256  if (is_android) {
257    parser_map["androidActivity"] =
258        base::Bind(&ParseString, &capabilities->android_activity);
259    parser_map["androidDeviceSerial"] =
260        base::Bind(&ParseString, &capabilities->android_device_serial);
261    parser_map["androidPackage"] =
262        base::Bind(&ParseString, &capabilities->android_package);
263    parser_map["androidProcess"] =
264        base::Bind(&ParseString, &capabilities->android_process);
265    parser_map["androidUseRunningApp"] =
266        base::Bind(&ParseBoolean, &capabilities->android_use_running_app);
267    parser_map["args"] = base::Bind(&ParseSwitches);
268  } else if (is_existing) {
269    parser_map["debuggerAddress"] = base::Bind(&ParseUseExistingBrowser);
270  } else {
271    parser_map["args"] = base::Bind(&ParseSwitches);
272    parser_map["binary"] = base::Bind(&ParseFilePath, &capabilities->binary);
273    parser_map["detach"] = base::Bind(&ParseBoolean, &capabilities->detach);
274    parser_map["excludeSwitches"] = base::Bind(&ParseExcludeSwitches);
275    parser_map["extensions"] = base::Bind(&ParseExtensions);
276    parser_map["forceDevToolsScreenshot"] = base::Bind(
277        &ParseBoolean, &capabilities->force_devtools_screenshot);
278    parser_map["loadAsync"] = base::Bind(&IgnoreDeprecatedOption, "loadAsync");
279    parser_map["localState"] =
280        base::Bind(&ParseDict, &capabilities->local_state);
281    parser_map["logPath"] = base::Bind(&ParseLogPath);
282    parser_map["minidumpPath"] =
283        base::Bind(&ParseString, &capabilities->minidump_path);
284    parser_map["prefs"] = base::Bind(&ParseDict, &capabilities->prefs);
285  }
286
287  for (base::DictionaryValue::Iterator it(*chrome_options); !it.IsAtEnd();
288       it.Advance()) {
289    if (parser_map.find(it.key()) == parser_map.end()) {
290      return Status(kUnknownError,
291                    "unrecognized chrome option: " + it.key());
292    }
293    Status status = parser_map[it.key()].Run(it.value(), capabilities);
294    if (status.IsError())
295      return Status(kUnknownError, "cannot parse " + it.key(), status);
296  }
297  return Status(kOk);
298}
299
300}  // namespace
301
302Switches::Switches() {}
303
304Switches::~Switches() {}
305
306void Switches::SetSwitch(const std::string& name) {
307  SetSwitch(name, NativeString());
308}
309
310void Switches::SetSwitch(const std::string& name, const std::string& value) {
311#if defined(OS_WIN)
312  SetSwitch(name, UTF8ToUTF16(value));
313#else
314  switch_map_[name] = value;
315#endif
316}
317
318void Switches::SetSwitch(const std::string& name, const string16& value) {
319#if defined(OS_WIN)
320  switch_map_[name] = value;
321#else
322  SetSwitch(name, UTF16ToUTF8(value));
323#endif
324}
325
326void Switches::SetSwitch(const std::string& name, const base::FilePath& value) {
327  SetSwitch(name, value.value());
328}
329
330void Switches::SetFromSwitches(const Switches& switches) {
331  for (SwitchMap::const_iterator iter = switches.switch_map_.begin();
332       iter != switches.switch_map_.end();
333       ++iter) {
334    switch_map_[iter->first] = iter->second;
335  }
336}
337
338void Switches::SetUnparsedSwitch(const std::string& unparsed_switch) {
339  std::string value;
340  size_t equals_index = unparsed_switch.find('=');
341  if (equals_index != std::string::npos)
342    value = unparsed_switch.substr(equals_index + 1);
343
344  std::string name;
345  size_t start_index = 0;
346  if (unparsed_switch.substr(0, 2) == "--")
347    start_index = 2;
348  name = unparsed_switch.substr(start_index, equals_index - start_index);
349
350  SetSwitch(name, value);
351}
352
353void Switches::RemoveSwitch(const std::string& name) {
354  switch_map_.erase(name);
355}
356
357bool Switches::HasSwitch(const std::string& name) const {
358  return switch_map_.count(name) > 0;
359}
360
361std::string Switches::GetSwitchValue(const std::string& name) const {
362  NativeString value = GetSwitchValueNative(name);
363#if defined(OS_WIN)
364  return UTF16ToUTF8(value);
365#else
366  return value;
367#endif
368}
369
370Switches::NativeString Switches::GetSwitchValueNative(
371    const std::string& name) const {
372  SwitchMap::const_iterator iter = switch_map_.find(name);
373  if (iter == switch_map_.end())
374    return NativeString();
375  return iter->second;
376}
377
378size_t Switches::GetSize() const {
379  return switch_map_.size();
380}
381
382void Switches::AppendToCommandLine(CommandLine* command) const {
383  for (SwitchMap::const_iterator iter = switch_map_.begin();
384       iter != switch_map_.end();
385       ++iter) {
386    command->AppendSwitchNative(iter->first, iter->second);
387  }
388}
389
390std::string Switches::ToString() const {
391  std::string str;
392  SwitchMap::const_iterator iter = switch_map_.begin();
393  while (iter != switch_map_.end()) {
394    str += "--" + iter->first;
395    std::string value = GetSwitchValue(iter->first);
396    if (value.length()) {
397      if (value.find(' ') != std::string::npos)
398        value = base::GetDoubleQuotedJson(value);
399      str += "=" + value;
400    }
401    ++iter;
402    if (iter == switch_map_.end())
403      break;
404    str += " ";
405  }
406  return str;
407}
408
409Capabilities::Capabilities()
410    : android_use_running_app(false),
411      detach(false),
412      force_devtools_screenshot(false) {}
413
414Capabilities::~Capabilities() {}
415
416bool Capabilities::IsAndroid() const {
417  return !android_package.empty();
418}
419
420bool Capabilities::IsExistingBrowser() const {
421  return debugger_address.IsValid();
422}
423
424Status Capabilities::Parse(const base::DictionaryValue& desired_caps) {
425  std::map<std::string, Parser> parser_map;
426  parser_map["chromeOptions"] = base::Bind(&ParseChromeOptions);
427  parser_map["loggingPrefs"] = base::Bind(&ParseLoggingPrefs);
428  parser_map["proxy"] = base::Bind(&ParseProxy);
429  for (std::map<std::string, Parser>::iterator it = parser_map.begin();
430       it != parser_map.end(); ++it) {
431    const base::Value* capability = NULL;
432    if (desired_caps.Get(it->first, &capability)) {
433      Status status = it->second.Run(*capability, this);
434      if (status.IsError()) {
435        return Status(
436            kUnknownError, "cannot parse capability: " + it->first, status);
437      }
438    }
439  }
440  return Status(kOk);
441}
442