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