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