OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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/webdriver/webdriver_capabilities_parser.h" | |
6 | |
7 #include "base/file_util.h" | |
8 #include "base/format_macros.h" | |
9 #include "base/strings/string_util.h" | |
10 #include "base/strings/stringprintf.h" | |
11 #include "base/values.h" | |
12 #include "chrome/common/chrome_switches.h" | |
13 #include "chrome/test/webdriver/webdriver_error.h" | |
14 #include "chrome/test/webdriver/webdriver_logging.h" | |
15 #include "chrome/test/webdriver/webdriver_util.h" | |
16 | |
17 using base::DictionaryValue; | |
18 using base::Value; | |
19 | |
20 namespace webdriver { | |
21 | |
22 namespace { | |
23 | |
24 Error* CreateBadInputError(const std::string& name, | |
25 Value::Type type, | |
26 const Value* option) { | |
27 return new Error(kBadRequest, base::StringPrintf( | |
28 "%s must be of type %s, not %s. Received: %s", | |
29 name.c_str(), GetJsonTypeName(type), | |
30 GetJsonTypeName(option->GetType()), | |
31 JsonStringifyForDisplay(option).c_str())); | |
32 } | |
33 | |
34 } // namespace | |
35 | |
36 Capabilities::Capabilities() | |
37 : command(CommandLine::NO_PROGRAM), | |
38 detach(false), | |
39 load_async(false), | |
40 local_state(new DictionaryValue()), | |
41 no_website_testing_defaults(false), | |
42 prefs(new DictionaryValue()) { | |
43 log_levels[LogType::kDriver] = kAllLogLevel; | |
44 } | |
45 | |
46 Capabilities::~Capabilities() { } | |
47 | |
48 CapabilitiesParser::CapabilitiesParser( | |
49 const DictionaryValue* capabilities_dict, | |
50 const base::FilePath& root_path, | |
51 const Logger& logger, | |
52 Capabilities* capabilities) | |
53 : dict_(capabilities_dict), | |
54 root_(root_path), | |
55 logger_(logger), | |
56 caps_(capabilities) { | |
57 } | |
58 | |
59 CapabilitiesParser::~CapabilitiesParser() { } | |
60 | |
61 Error* CapabilitiesParser::Parse() { | |
62 // Parse WebDriver standard capabilities. | |
63 typedef Error* (CapabilitiesParser::*Parser)(const Value*); | |
64 | |
65 struct NameAndParser { | |
66 const char* name; | |
67 Parser parser; | |
68 }; | |
69 NameAndParser name_and_parser[] = { | |
70 { "proxy", &CapabilitiesParser::ParseProxy }, | |
71 { "loggingPrefs", &CapabilitiesParser::ParseLoggingPrefs } | |
72 }; | |
73 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(name_and_parser); ++i) { | |
74 const Value* value; | |
75 if (dict_->Get(name_and_parser[i].name, &value)) { | |
76 Error* error = (this->*name_and_parser[i].parser)(value); | |
77 if (error) | |
78 return error; | |
79 } | |
80 } | |
81 | |
82 // Parse Chrome custom capabilities (a.k.a., ChromeOptions). | |
83 const char kOptionsKey[] = "chromeOptions"; | |
84 const DictionaryValue* options = dict_; | |
85 bool legacy_options = true; | |
86 const Value* options_value; | |
87 if (dict_->Get(kOptionsKey, &options_value)) { | |
88 legacy_options = false; | |
89 if (options_value->IsType(Value::TYPE_DICTIONARY)) { | |
90 options = static_cast<const DictionaryValue*>(options_value); | |
91 } else { | |
92 return CreateBadInputError( | |
93 kOptionsKey, Value::TYPE_DICTIONARY, options_value); | |
94 } | |
95 } | |
96 | |
97 std::map<std::string, Parser> parser_map; | |
98 if (legacy_options) { | |
99 parser_map["chrome.binary"] = &CapabilitiesParser::ParseBinary; | |
100 parser_map["chrome.channel"] = &CapabilitiesParser::ParseChannel; | |
101 parser_map["chrome.detach"] = &CapabilitiesParser::ParseDetach; | |
102 parser_map["chrome.extensions"] = &CapabilitiesParser::ParseExtensions; | |
103 parser_map["chrome.loadAsync"] = &CapabilitiesParser::ParseLoadAsync; | |
104 parser_map["chrome.localState"] = &CapabilitiesParser::ParseLocalState; | |
105 parser_map["chrome.nativeEvents"] = &CapabilitiesParser::ParseNativeEvents; | |
106 parser_map["chrome.prefs"] = &CapabilitiesParser::ParsePrefs; | |
107 parser_map["chrome.profile"] = &CapabilitiesParser::ParseProfile; | |
108 parser_map["chrome.switches"] = &CapabilitiesParser::ParseArgs; | |
109 parser_map["chrome.noWebsiteTestingDefaults"] = | |
110 &CapabilitiesParser::ParseNoWebsiteTestingDefaults; | |
111 parser_map["chrome.excludeSwitches"] = | |
112 &CapabilitiesParser::ParseExcludeSwitches; | |
113 } else { | |
114 parser_map["args"] = &CapabilitiesParser::ParseArgs; | |
115 parser_map["binary"] = &CapabilitiesParser::ParseBinary; | |
116 parser_map["channel"] = &CapabilitiesParser::ParseChannel; | |
117 parser_map["detach"] = &CapabilitiesParser::ParseDetach; | |
118 parser_map["extensions"] = &CapabilitiesParser::ParseExtensions; | |
119 parser_map["loadAsync"] = &CapabilitiesParser::ParseLoadAsync; | |
120 parser_map["localState"] = &CapabilitiesParser::ParseLocalState; | |
121 parser_map["nativeEvents"] = &CapabilitiesParser::ParseNativeEvents; | |
122 parser_map["prefs"] = &CapabilitiesParser::ParsePrefs; | |
123 parser_map["profile"] = &CapabilitiesParser::ParseProfile; | |
124 parser_map["noWebsiteTestingDefaults"] = | |
125 &CapabilitiesParser::ParseNoWebsiteTestingDefaults; | |
126 parser_map["excludeSwitches"] = | |
127 &CapabilitiesParser::ParseExcludeSwitches; | |
128 } | |
129 | |
130 for (DictionaryValue::Iterator iter(*options); !iter.IsAtEnd(); | |
131 iter.Advance()) { | |
132 if (parser_map.find(iter.key()) == parser_map.end()) { | |
133 if (!legacy_options) | |
134 return new Error(kBadRequest, | |
135 "Unrecognized chrome capability: " + iter.key()); | |
136 continue; | |
137 } | |
138 const Value* option = &iter.value(); | |
139 Error* error = (this->*parser_map[iter.key()])(option); | |
140 if (error) { | |
141 error->AddDetails(base::StringPrintf( | |
142 "Error occurred while processing capability '%s'", | |
143 iter.key().c_str())); | |
144 return error; | |
145 } | |
146 } | |
147 return NULL; | |
148 } | |
149 | |
150 Error* CapabilitiesParser::ParseArgs(const Value* option) { | |
151 const base::ListValue* args; | |
152 if (!option->GetAsList(&args)) | |
153 return CreateBadInputError("arguments", Value::TYPE_LIST, option); | |
154 for (size_t i = 0; i < args->GetSize(); ++i) { | |
155 std::string arg_string; | |
156 if (!args->GetString(i, &arg_string)) | |
157 return CreateBadInputError("argument", Value::TYPE_STRING, option); | |
158 size_t separator_index = arg_string.find("="); | |
159 if (separator_index != std::string::npos) { | |
160 CommandLine::StringType arg_string_native; | |
161 if (!args->GetString(i, &arg_string_native)) | |
162 return CreateBadInputError("argument", Value::TYPE_STRING, option); | |
163 caps_->command.AppendSwitchNative( | |
164 arg_string.substr(0, separator_index), | |
165 arg_string_native.substr(separator_index + 1)); | |
166 } else { | |
167 caps_->command.AppendSwitch(arg_string); | |
168 } | |
169 } | |
170 return NULL; | |
171 } | |
172 | |
173 Error* CapabilitiesParser::ParseBinary(const Value* option) { | |
174 base::FilePath::StringType path; | |
175 if (!option->GetAsString(&path)) { | |
176 return CreateBadInputError("binary path", Value::TYPE_STRING, option); | |
177 } | |
178 caps_->command.SetProgram(base::FilePath(path)); | |
179 return NULL; | |
180 } | |
181 | |
182 Error* CapabilitiesParser::ParseChannel(const Value* option) { | |
183 if (!option->GetAsString(&caps_->channel)) | |
184 return CreateBadInputError("channel", Value::TYPE_STRING, option); | |
185 return NULL; | |
186 } | |
187 | |
188 Error* CapabilitiesParser::ParseDetach(const Value* option) { | |
189 if (!option->GetAsBoolean(&caps_->detach)) | |
190 return CreateBadInputError("detach", Value::TYPE_BOOLEAN, option); | |
191 return NULL; | |
192 } | |
193 | |
194 Error* CapabilitiesParser::ParseExtensions(const Value* option) { | |
195 const base::ListValue* extensions; | |
196 if (!option->GetAsList(&extensions)) | |
197 return CreateBadInputError("extensions", Value::TYPE_LIST, option); | |
198 for (size_t i = 0; i < extensions->GetSize(); ++i) { | |
199 std::string extension_base64; | |
200 if (!extensions->GetString(i, &extension_base64)) { | |
201 return new Error(kBadRequest, | |
202 "Each extension must be a base64 encoded string"); | |
203 } | |
204 base::FilePath extension = root_.AppendASCII( | |
205 base::StringPrintf("extension%" PRIuS ".crx", i)); | |
206 std::string decoded_extension; | |
207 if (!Base64Decode(extension_base64, &decoded_extension)) | |
208 return new Error(kUnknownError, "Failed to base64 decode extension"); | |
209 int size = static_cast<int>(decoded_extension.length()); | |
210 if (file_util::WriteFile( | |
211 extension, decoded_extension.c_str(), size) != size) | |
212 return new Error(kUnknownError, "Failed to write extension file"); | |
213 caps_->extensions.push_back(extension); | |
214 } | |
215 return NULL; | |
216 } | |
217 | |
218 Error* CapabilitiesParser::ParseLoadAsync(const Value* option) { | |
219 if (!option->GetAsBoolean(&caps_->load_async)) | |
220 return CreateBadInputError("loadAsync", Value::TYPE_BOOLEAN, option); | |
221 return NULL; | |
222 } | |
223 | |
224 Error* CapabilitiesParser::ParseLocalState(const Value* option) { | |
225 const base::DictionaryValue* local_state; | |
226 if (!option->GetAsDictionary(&local_state)) | |
227 return CreateBadInputError("localState", Value::TYPE_DICTIONARY, option); | |
228 caps_->local_state.reset(local_state->DeepCopy()); | |
229 return NULL; | |
230 } | |
231 | |
232 Error* CapabilitiesParser::ParseLoggingPrefs(const base::Value* option) { | |
233 const DictionaryValue* logging_prefs; | |
234 if (!option->GetAsDictionary(&logging_prefs)) | |
235 return CreateBadInputError("loggingPrefs", Value::TYPE_DICTIONARY, option); | |
236 | |
237 for (DictionaryValue::Iterator iter(*logging_prefs); !iter.IsAtEnd(); | |
238 iter.Advance()) { | |
239 LogType log_type; | |
240 if (!LogType::FromString(iter.key(), &log_type)) | |
241 continue; | |
242 | |
243 std::string level_name; | |
244 if (!iter.value().GetAsString(&level_name)) { | |
245 return CreateBadInputError( | |
246 std::string("loggingPrefs.") + iter.key(), | |
247 Value::TYPE_STRING, | |
248 &iter.value()); | |
249 } | |
250 caps_->log_levels[log_type.type()] = LogLevelFromString(level_name); | |
251 } | |
252 return NULL; | |
253 } | |
254 | |
255 Error* CapabilitiesParser::ParseNativeEvents(const Value* option) { | |
256 bool native_events; | |
257 if (!option->GetAsBoolean(&native_events)) | |
258 return CreateBadInputError("nativeEvents", Value::TYPE_BOOLEAN, option); | |
259 if (native_events) | |
260 return new Error(kUnknownError, "OS-level events are not supported"); | |
261 return NULL; | |
262 } | |
263 | |
264 Error* CapabilitiesParser::ParsePrefs(const Value* option) { | |
265 const base::DictionaryValue* prefs; | |
266 if (!option->GetAsDictionary(&prefs)) | |
267 return CreateBadInputError("prefs", Value::TYPE_DICTIONARY, option); | |
268 caps_->prefs.reset(prefs->DeepCopy()); | |
269 return NULL; | |
270 } | |
271 | |
272 Error* CapabilitiesParser::ParseProfile(const Value* option) { | |
273 std::string profile_base64; | |
274 if (!option->GetAsString(&profile_base64)) | |
275 return CreateBadInputError("profile", Value::TYPE_STRING, option); | |
276 std::string error_msg; | |
277 caps_->profile = root_.AppendASCII("profile"); | |
278 if (!Base64DecodeAndUnzip(caps_->profile, profile_base64, &error_msg)) | |
279 return new Error(kUnknownError, "unable to unpack profile: " + error_msg); | |
280 return NULL; | |
281 } | |
282 | |
283 Error* CapabilitiesParser::ParseProxy(const base::Value* option) { | |
284 const DictionaryValue* options; | |
285 if (!option->GetAsDictionary(&options)) | |
286 return CreateBadInputError("proxy", Value::TYPE_DICTIONARY, option); | |
287 | |
288 // Quick check of proxy capabilities. | |
289 std::set<std::string> proxy_options; | |
290 proxy_options.insert("autodetect"); | |
291 proxy_options.insert("ftpProxy"); | |
292 proxy_options.insert("httpProxy"); | |
293 proxy_options.insert("noProxy"); | |
294 proxy_options.insert("proxyType"); | |
295 proxy_options.insert("proxyAutoconfigUrl"); | |
296 proxy_options.insert("sslProxy"); | |
297 proxy_options.insert("class"); // Created by BeanToJSONConverter. | |
298 | |
299 for (DictionaryValue::Iterator iter(*options); !iter.IsAtEnd(); | |
300 iter.Advance()) { | |
301 if (proxy_options.find(iter.key()) == proxy_options.end()) { | |
302 logger_.Log(kInfoLogLevel, | |
303 "Unrecognized proxy capability: " + iter.key()); | |
304 } | |
305 } | |
306 | |
307 typedef Error* (CapabilitiesParser::*Parser)(const DictionaryValue*); | |
308 std::map<std::string, Parser> proxy_type_parser_map; | |
309 proxy_type_parser_map["autodetect"] = | |
310 &CapabilitiesParser::ParseProxyAutoDetect; | |
311 proxy_type_parser_map["pac"] = | |
312 &CapabilitiesParser::ParseProxyAutoconfigUrl; | |
313 proxy_type_parser_map["manual"] = &CapabilitiesParser::ParseProxyServers; | |
314 proxy_type_parser_map["direct"] = NULL; | |
315 proxy_type_parser_map["system"] = NULL; | |
316 | |
317 const Value* proxy_type_value; | |
318 if (!options->Get("proxyType", &proxy_type_value)) | |
319 return new Error(kBadRequest, "Missing 'proxyType' capability."); | |
320 | |
321 std::string proxy_type; | |
322 if (!proxy_type_value->GetAsString(&proxy_type)) | |
323 return CreateBadInputError("proxyType", Value::TYPE_STRING, | |
324 proxy_type_value); | |
325 | |
326 proxy_type = StringToLowerASCII(proxy_type); | |
327 if (proxy_type_parser_map.find(proxy_type) == proxy_type_parser_map.end()) | |
328 return new Error(kBadRequest, "Unrecognized 'proxyType': " + proxy_type); | |
329 | |
330 if (proxy_type == "direct") { | |
331 caps_->command.AppendSwitch(switches::kNoProxyServer); | |
332 } else if (proxy_type == "system") { | |
333 // Chrome default. | |
334 } else { | |
335 Error* error = (this->*proxy_type_parser_map[proxy_type])(options); | |
336 if (error) { | |
337 error->AddDetails("Error occurred while processing 'proxyType': " + | |
338 proxy_type); | |
339 return error; | |
340 } | |
341 } | |
342 return NULL; | |
343 } | |
344 | |
345 Error* CapabilitiesParser::ParseProxyAutoDetect( | |
346 const DictionaryValue* options) { | |
347 const char kProxyAutoDetectKey[] = "autodetect"; | |
348 bool proxy_auto_detect = false; | |
349 if (!options->GetBoolean(kProxyAutoDetectKey, &proxy_auto_detect)) | |
350 return CreateBadInputError(kProxyAutoDetectKey, | |
351 Value::TYPE_BOOLEAN, options); | |
352 if (proxy_auto_detect) | |
353 caps_->command.AppendSwitch(switches::kProxyAutoDetect); | |
354 return NULL; | |
355 } | |
356 | |
357 Error* CapabilitiesParser::ParseProxyAutoconfigUrl( | |
358 const DictionaryValue* options){ | |
359 const char kProxyAutoconfigUrlKey[] = "proxyAutoconfigUrl"; | |
360 CommandLine::StringType proxy_pac_url; | |
361 if (!options->GetString(kProxyAutoconfigUrlKey, &proxy_pac_url)) | |
362 return CreateBadInputError(kProxyAutoconfigUrlKey, | |
363 Value::TYPE_STRING, options); | |
364 caps_->command.AppendSwitchNative(switches::kProxyPacUrl, proxy_pac_url); | |
365 return NULL; | |
366 } | |
367 | |
368 Error* CapabilitiesParser::ParseProxyServers( | |
369 const DictionaryValue* options) { | |
370 const char kNoProxy[] = "noProxy"; | |
371 const char kFtpProxy[] = "ftpProxy"; | |
372 const char kHttpProxy[] = "httpProxy"; | |
373 const char kSslProxy[] = "sslProxy"; | |
374 | |
375 std::set<std::string> proxy_servers_options; | |
376 proxy_servers_options.insert(kFtpProxy); | |
377 proxy_servers_options.insert(kHttpProxy); | |
378 proxy_servers_options.insert(kSslProxy); | |
379 | |
380 Error* error = NULL; | |
381 const Value* option = NULL; | |
382 bool has_manual_settings = false; | |
383 if (options->Get(kNoProxy, &option) && !option->IsType(Value::TYPE_NULL)) { | |
384 error = ParseNoProxy(option); | |
385 if (error) | |
386 return error; | |
387 has_manual_settings = true; | |
388 } | |
389 | |
390 std::vector<std::string> proxy_servers; | |
391 std::set<std::string>::const_iterator iter = proxy_servers_options.begin(); | |
392 for (; iter != proxy_servers_options.end(); ++iter) { | |
393 if (options->Get(*iter, &option) && !option->IsType(Value::TYPE_NULL)) { | |
394 std::string value; | |
395 if (!option->GetAsString(&value)) | |
396 return CreateBadInputError(*iter, Value::TYPE_STRING, option); | |
397 has_manual_settings = true; | |
398 // Converts into Chrome proxy scheme. | |
399 // Example: "http=localhost:9000;ftp=localhost:8000". | |
400 if (*iter == kFtpProxy) | |
401 value = "ftp=" + value; | |
402 if (*iter == kHttpProxy) | |
403 value = "http=" + value; | |
404 if (*iter == kSslProxy) | |
405 value = "https=" + value; | |
406 proxy_servers.push_back(value); | |
407 } | |
408 } | |
409 | |
410 if (!has_manual_settings) | |
411 return new Error(kBadRequest, "proxyType is 'manual' but no manual " | |
412 "proxy capabilities were found."); | |
413 | |
414 std::string proxy_server_value = JoinString(proxy_servers, ';'); | |
415 caps_->command.AppendSwitchASCII(switches::kProxyServer, proxy_server_value); | |
416 | |
417 return NULL; | |
418 } | |
419 | |
420 Error* CapabilitiesParser::ParseNoProxy(const base::Value* option){ | |
421 std::string proxy_bypass_list; | |
422 if (!option->GetAsString(&proxy_bypass_list)) | |
423 return CreateBadInputError("noProxy", Value::TYPE_STRING, option); | |
424 if (!proxy_bypass_list.empty()) | |
425 caps_->command.AppendSwitchASCII(switches::kProxyBypassList, | |
426 proxy_bypass_list); | |
427 return NULL; | |
428 } | |
429 | |
430 Error* CapabilitiesParser::ParseNoWebsiteTestingDefaults(const Value* option) { | |
431 if (!option->GetAsBoolean(&caps_->no_website_testing_defaults)) | |
432 return CreateBadInputError("noWebsiteTestingDefaults", | |
433 Value::TYPE_BOOLEAN, option); | |
434 return NULL; | |
435 } | |
436 | |
437 Error* CapabilitiesParser::ParseExcludeSwitches(const Value* option) { | |
438 const base::ListValue* switches; | |
439 if (!option->GetAsList(&switches)) | |
440 return CreateBadInputError("excludeSwitches", Value::TYPE_LIST, | |
441 option); | |
442 for (size_t i = 0; i < switches->GetSize(); ++i) { | |
443 std::string switch_name; | |
444 if (!switches->GetString(i, &switch_name)) { | |
445 return new Error(kBadRequest, | |
446 "Each switch to be removed must be a string"); | |
447 } | |
448 caps_->exclude_switches.insert(switch_name); | |
449 } | |
450 return NULL; | |
451 } | |
452 | |
453 } // namespace webdriver | |
OLD | NEW |