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/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 
     24 namespace {
     25 
     26 typedef base::Callback<Status(const base::Value&, Capabilities*)> Parser;
     27 
     28 Status 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 
     37 Status 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 
     49 Status 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 
     59 Status 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 
     69 Status 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 
     77 Status IgnoreCapability(const base::Value& option, Capabilities* capabilities) {
     78   return Status(kOk);
     79 }
     80 
     81 Status 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 
     87 Status 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 
    101 Status 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 
    116 Status 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 
    184 Status 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 
    200 Status 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 
    220 Status 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 
    240 Status 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 
    302 Switches::Switches() {}
    303 
    304 Switches::~Switches() {}
    305 
    306 void Switches::SetSwitch(const std::string& name) {
    307   SetSwitch(name, NativeString());
    308 }
    309 
    310 void 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 
    318 void 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 
    326 void Switches::SetSwitch(const std::string& name, const base::FilePath& value) {
    327   SetSwitch(name, value.value());
    328 }
    329 
    330 void 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 
    338 void 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 
    353 void Switches::RemoveSwitch(const std::string& name) {
    354   switch_map_.erase(name);
    355 }
    356 
    357 bool Switches::HasSwitch(const std::string& name) const {
    358   return switch_map_.count(name) > 0;
    359 }
    360 
    361 std::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 
    370 Switches::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 
    378 size_t Switches::GetSize() const {
    379   return switch_map_.size();
    380 }
    381 
    382 void 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 
    390 std::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::GetQuotedJSONString(value);
    399       str += "=" + value;
    400     }
    401     ++iter;
    402     if (iter == switch_map_.end())
    403       break;
    404     str += " ";
    405   }
    406   return str;
    407 }
    408 
    409 Capabilities::Capabilities()
    410     : android_use_running_app(false),
    411       detach(false),
    412       force_devtools_screenshot(false) {}
    413 
    414 Capabilities::~Capabilities() {}
    415 
    416 bool Capabilities::IsAndroid() const {
    417   return !android_package.empty();
    418 }
    419 
    420 bool Capabilities::IsExistingBrowser() const {
    421   return debugger_address.IsValid();
    422 }
    423 
    424 Status 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