capabilities.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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["args"] = base::Bind(&ParseSwitches); 266 } else if (is_existing) { 267 parser_map["debuggerAddress"] = base::Bind(&ParseUseExistingBrowser); 268 } else { 269 parser_map["args"] = base::Bind(&ParseSwitches); 270 parser_map["binary"] = base::Bind(&ParseFilePath, &capabilities->binary); 271 parser_map["detach"] = base::Bind(&ParseBoolean, &capabilities->detach); 272 parser_map["excludeSwitches"] = base::Bind(&ParseExcludeSwitches); 273 parser_map["extensions"] = base::Bind(&ParseExtensions); 274 parser_map["forceDevToolsScreenshot"] = base::Bind( 275 &ParseBoolean, &capabilities->force_devtools_screenshot); 276 parser_map["loadAsync"] = base::Bind(&IgnoreDeprecatedOption, "loadAsync"); 277 parser_map["localState"] = 278 base::Bind(&ParseDict, &capabilities->local_state); 279 parser_map["logPath"] = base::Bind(&ParseLogPath); 280 parser_map["prefs"] = base::Bind(&ParseDict, &capabilities->prefs); 281 } 282 283 for (base::DictionaryValue::Iterator it(*chrome_options); !it.IsAtEnd(); 284 it.Advance()) { 285 if (parser_map.find(it.key()) == parser_map.end()) { 286 return Status(kUnknownError, 287 "unrecognized chrome option: " + it.key()); 288 } 289 Status status = parser_map[it.key()].Run(it.value(), capabilities); 290 if (status.IsError()) 291 return Status(kUnknownError, "cannot parse " + it.key(), status); 292 } 293 return Status(kOk); 294} 295 296} // namespace 297 298Switches::Switches() {} 299 300Switches::~Switches() {} 301 302void Switches::SetSwitch(const std::string& name) { 303 SetSwitch(name, NativeString()); 304} 305 306void Switches::SetSwitch(const std::string& name, const std::string& value) { 307#if defined(OS_WIN) 308 SetSwitch(name, UTF8ToUTF16(value)); 309#else 310 switch_map_[name] = value; 311#endif 312} 313 314void Switches::SetSwitch(const std::string& name, const string16& value) { 315#if defined(OS_WIN) 316 switch_map_[name] = value; 317#else 318 SetSwitch(name, UTF16ToUTF8(value)); 319#endif 320} 321 322void Switches::SetSwitch(const std::string& name, const base::FilePath& value) { 323 SetSwitch(name, value.value()); 324} 325 326void Switches::SetFromSwitches(const Switches& switches) { 327 for (SwitchMap::const_iterator iter = switches.switch_map_.begin(); 328 iter != switches.switch_map_.end(); 329 ++iter) { 330 switch_map_[iter->first] = iter->second; 331 } 332} 333 334void Switches::SetUnparsedSwitch(const std::string& unparsed_switch) { 335 std::string value; 336 size_t equals_index = unparsed_switch.find('='); 337 if (equals_index != std::string::npos) 338 value = unparsed_switch.substr(equals_index + 1); 339 340 std::string name; 341 size_t start_index = 0; 342 if (unparsed_switch.substr(0, 2) == "--") 343 start_index = 2; 344 name = unparsed_switch.substr(start_index, equals_index - start_index); 345 346 SetSwitch(name, value); 347} 348 349void Switches::RemoveSwitch(const std::string& name) { 350 switch_map_.erase(name); 351} 352 353bool Switches::HasSwitch(const std::string& name) const { 354 return switch_map_.count(name) > 0; 355} 356 357std::string Switches::GetSwitchValue(const std::string& name) const { 358 NativeString value = GetSwitchValueNative(name); 359#if defined(OS_WIN) 360 return UTF16ToUTF8(value); 361#else 362 return value; 363#endif 364} 365 366Switches::NativeString Switches::GetSwitchValueNative( 367 const std::string& name) const { 368 SwitchMap::const_iterator iter = switch_map_.find(name); 369 if (iter == switch_map_.end()) 370 return NativeString(); 371 return iter->second; 372} 373 374size_t Switches::GetSize() const { 375 return switch_map_.size(); 376} 377 378void Switches::AppendToCommandLine(CommandLine* command) const { 379 for (SwitchMap::const_iterator iter = switch_map_.begin(); 380 iter != switch_map_.end(); 381 ++iter) { 382 command->AppendSwitchNative(iter->first, iter->second); 383 } 384} 385 386std::string Switches::ToString() const { 387 std::string str; 388 SwitchMap::const_iterator iter = switch_map_.begin(); 389 while (iter != switch_map_.end()) { 390 str += "--" + iter->first; 391 std::string value = GetSwitchValue(iter->first); 392 if (value.length()) { 393 if (value.find(' ') != std::string::npos) 394 value = base::GetDoubleQuotedJson(value); 395 str += "=" + value; 396 } 397 ++iter; 398 if (iter == switch_map_.end()) 399 break; 400 str += " "; 401 } 402 return str; 403} 404 405Capabilities::Capabilities() 406 : detach(false), 407 force_devtools_screenshot(false) {} 408 409Capabilities::~Capabilities() {} 410 411bool Capabilities::IsAndroid() const { 412 return !android_package.empty(); 413} 414 415bool Capabilities::IsExistingBrowser() const { 416 return debugger_address.IsValid(); 417} 418 419Status Capabilities::Parse(const base::DictionaryValue& desired_caps) { 420 std::map<std::string, Parser> parser_map; 421 parser_map["chromeOptions"] = base::Bind(&ParseChromeOptions); 422 parser_map["loggingPrefs"] = base::Bind(&ParseLoggingPrefs); 423 parser_map["proxy"] = base::Bind(&ParseProxy); 424 for (std::map<std::string, Parser>::iterator it = parser_map.begin(); 425 it != parser_map.end(); ++it) { 426 const base::Value* capability = NULL; 427 if (desired_caps.Get(it->first, &capability)) { 428 Status status = it->second.Run(*capability, this); 429 if (status.IsError()) { 430 return Status( 431 kUnknownError, "cannot parse capability: " + it->first, status); 432 } 433 } 434 } 435 return Status(kOk); 436} 437