| // Licensed to the Software Freedom Conservancy (SFC) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The SFC licenses this file |
| // to you under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "NewSessionCommandHandler.h" |
| #include "errorcodes.h" |
| #include "logging.h" |
| #include "../Alert.h" |
| #include "../Browser.h" |
| #include "../BrowserFactory.h" |
| #include "../IECommandExecutor.h" |
| #include "../InputManager.h" |
| #include "../ProxyManager.h" |
| #include "../WebDriverConstants.h" |
| |
| namespace webdriver { |
| |
| NewSessionCommandHandler::NewSessionCommandHandler(void) { |
| } |
| |
| NewSessionCommandHandler::~NewSessionCommandHandler(void) { |
| } |
| |
| void NewSessionCommandHandler::ExecuteInternal( |
| const IECommandExecutor& executor, |
| const ParametersMap& command_parameters, |
| Response* response) { |
| Json::Value returned_capabilities; |
| std::string error_message = ""; |
| |
| // Find W3C capabilities first. |
| IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor); |
| |
| ParametersMap::const_iterator it = command_parameters.find("capabilities"); |
| if (it != command_parameters.end()) { |
| LOG(DEBUG) << "Found W3C capabilities structure"; |
| Json::Value validated_capabilities = this->ValidateArguments(it->second, |
| &error_message); |
| if (validated_capabilities.size() == 0) { |
| // validated_capabilities returns an array with validated capabilities |
| // in it. If there are no entries in the array, then something failed |
| // validation. The error_message string will tell us what. |
| mutable_executor.set_is_valid(false); |
| response->SetErrorResponse(ERROR_INVALID_ARGUMENT, error_message); |
| return; |
| } |
| error_message = ""; |
| returned_capabilities = this->ProcessCapabilities(executor, |
| validated_capabilities, |
| &error_message); |
| } else { |
| error_message = "No property named 'capabilities' found in new session request body."; |
| mutable_executor.set_is_valid(false); |
| response->SetErrorResponse(ERROR_INVALID_ARGUMENT, error_message); |
| return; |
| } |
| |
| if (returned_capabilities.isNull()) { |
| // The browser was not created successfully, therefore the |
| // session must be marked as invalid so the server can |
| // properly shut it down. |
| mutable_executor.set_is_valid(false); |
| response->SetErrorResponse(ERROR_SESSION_NOT_CREATED, error_message); |
| return; |
| } |
| |
| error_message = ""; |
| int result_code = mutable_executor.CreateNewBrowser(&error_message); |
| if (result_code != WD_SUCCESS) { |
| // The browser was not created successfully, therefore the |
| // session must be marked as invalid so the server can |
| // properly shut it down. |
| mutable_executor.set_is_valid(false); |
| response->SetErrorResponse(ERROR_SESSION_NOT_CREATED, |
| "Unexpected error launching Internet Explorer. " + error_message); |
| return; |
| } |
| Json::Value new_session_response_object; |
| new_session_response_object["sessionId"] = executor.session_id(); |
| new_session_response_object["capabilities"] = returned_capabilities; |
| response->SetSuccessResponse(new_session_response_object); |
| } |
| |
| Json::Value NewSessionCommandHandler::GetCapability( |
| const Json::Value& capabilities, |
| const std::string& capability_name, |
| const Json::ValueType& expected_capability_type, |
| const Json::Value& default_value) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::GetCapability " |
| << "for capability " << capability_name; |
| Json::Value capability_value = capabilities.get(capability_name, default_value); |
| if (!this->IsEquivalentType(capability_value.type(), expected_capability_type)) { |
| LOG(WARN) << "Invalid capability setting: " << capability_name |
| << " is type " << this->GetJsonTypeDescription(capability_value.type()) |
| << " instead of " << this->GetJsonTypeDescription(expected_capability_type) |
| << ". Default value will be used: " << default_value.toStyledString(); |
| return default_value; |
| } |
| return capability_value; |
| } |
| |
| bool NewSessionCommandHandler::IsEquivalentType( |
| const Json::ValueType& actual_type, |
| const Json::ValueType& expected_type) { |
| if (expected_type == actual_type) { |
| return true; |
| } |
| if ((expected_type == Json::intValue || expected_type == Json::uintValue || expected_type == Json::realValue) && |
| (actual_type == Json::intValue || actual_type == Json::uintValue || actual_type == Json::realValue)) { |
| // All numeric types are equivalent for our purposes. |
| return true; |
| } |
| return false; |
| } |
| |
| std::string NewSessionCommandHandler::GetJsonTypeDescription( |
| const Json::ValueType& type) { |
| switch (type) { |
| case Json::booleanValue: |
| return "boolean"; |
| case Json::intValue: |
| case Json::uintValue: |
| case Json::realValue: |
| return "number"; |
| case Json::objectValue: |
| return "object"; |
| case Json::arrayValue: |
| return "array"; |
| case Json::stringValue: |
| return "string"; |
| } |
| return "null"; |
| } |
| |
| std::string NewSessionCommandHandler::GetUnexpectedAlertBehaviorValue( |
| const std::string& desired_value) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::GetUnexpectedAlertBehaviorValue"; |
| std::string value = DISMISS_AND_NOTIFY_UNEXPECTED_ALERTS; |
| if (desired_value == DISMISS_UNEXPECTED_ALERTS || |
| desired_value == ACCEPT_UNEXPECTED_ALERTS || |
| desired_value == ACCEPT_AND_NOTIFY_UNEXPECTED_ALERTS || |
| desired_value == DISMISS_AND_NOTIFY_UNEXPECTED_ALERTS || |
| desired_value == IGNORE_UNEXPECTED_ALERTS) { |
| value = desired_value; |
| } else { |
| LOG(WARN) << "Desired value of " << desired_value << " for " |
| << UNHANDLED_PROMPT_BEHAVIOR_CAPABILITY << " is not" |
| << " a valid value. Using default of " << value; |
| } |
| return value; |
| } |
| |
| std::string NewSessionCommandHandler::GetPageLoadStrategyValue( |
| const std::string& desired_value) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::GetPageLoadStrategyValue"; |
| std::string value = NORMAL_PAGE_LOAD_STRATEGY; |
| if (desired_value == NORMAL_PAGE_LOAD_STRATEGY || |
| desired_value == EAGER_PAGE_LOAD_STRATEGY || |
| desired_value == NONE_PAGE_LOAD_STRATEGY) { |
| value = desired_value; |
| } else { |
| LOG(WARN) << "Desired value of " << desired_value << " for " |
| << PAGE_LOAD_STRATEGY_CAPABILITY << " is not" |
| << " a valid value. Using default of " << value; |
| } |
| return value; |
| } |
| |
| Json::Value NewSessionCommandHandler::ValidateArguments(const Json::Value& capabilities, |
| std::string* error_message) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::ValidateArguments"; |
| Json::Value validated_capabilities(Json::arrayValue); |
| if (!capabilities.isObject()) { |
| *error_message = "'capabilities' in new session request body is not a JSON object."; |
| return validated_capabilities; |
| } |
| Json::Value always_match(Json::objectValue); |
| if (capabilities.isMember("alwaysMatch")) { |
| LOG(DEBUG) << "Found alwaysMatch in capabilities"; |
| always_match = capabilities["alwaysMatch"]; |
| if (!always_match.isObject()) { |
| *error_message = "alwaysMatch must be a JSON object"; |
| return validated_capabilities; |
| } |
| } |
| |
| LOG(DEBUG) << "Validating alwaysMatch capability set"; |
| if (this->ValidateCapabilities(always_match, "alwaysMatch", error_message)) { |
| Json::Value empty_capabilities(Json::objectValue); |
| Json::Value first_match_candidates(Json::arrayValue); |
| first_match_candidates.append(empty_capabilities); |
| if (capabilities.isMember("firstMatch")) { |
| first_match_candidates = capabilities["firstMatch"]; |
| } |
| if (!first_match_candidates.isArray()) { |
| *error_message = "firstMatch must be a JSON list"; |
| return validated_capabilities; |
| } else { |
| // If the user passed a "firstMatch" array, but it was empty, |
| // seed the firstMatch array with an empty object for merging |
| // purposes. |
| if (first_match_candidates.size() == 0) { |
| first_match_candidates.append(empty_capabilities); |
| } |
| bool first_match_validation_failure = false; |
| Json::Value validated_first_match_candidates(Json::arrayValue); |
| for (size_t i = 0; i < first_match_candidates.size(); ++i) { |
| LOG(DEBUG) << "Validating firstMatch capability set with index " << i; |
| std::string first_match_validation_error = ""; |
| Json::Value first_match_candidate = first_match_candidates[static_cast<int>(i)]; |
| if (this->ValidateCapabilities(first_match_candidate, |
| "firstMatch element " + std::to_string(i), |
| &first_match_validation_error)) { |
| validated_first_match_candidates.append(first_match_candidate); |
| } else { |
| first_match_validation_failure = true; |
| if (error_message->size() == 0) { |
| error_message->append("All firstMatch elements failed validation\n"); |
| } else { |
| error_message->append("\n"); |
| } |
| error_message->append(first_match_validation_error); |
| } |
| } |
| |
| if (first_match_validation_failure) { |
| return validated_capabilities; |
| } |
| |
| // Because we seed the list of "firstMatch" values with an empty value |
| // if none were passed in, we should always have at least one element |
| // in the array of firstMatch candidates. If we don't then every one |
| // has failed validation, and we need to return back out. |
| if (validated_first_match_candidates.size() > 0) { |
| // Reset the error message in the event any of the firstMatch |
| // candidates failed validation. |
| *error_message = ""; |
| |
| for (size_t i = 0; i < validated_first_match_candidates.size(); ++i) { |
| Json::Value merged_capabilities(Json::objectValue); |
| Json::Value first_match = validated_first_match_candidates[static_cast<int>(i)]; |
| if (!this->MergeCapabilities(always_match, |
| first_match, |
| &merged_capabilities, |
| error_message)) { |
| // If any of the capabilities can't be merged, this is a failure |
| // condition according to the spec, so we fail here, returning an |
| // empty array. |
| return Json::Value(Json::arrayValue); |
| } |
| validated_capabilities.append(merged_capabilities); |
| } |
| } |
| } |
| } |
| return validated_capabilities; |
| } |
| |
| Json::Value NewSessionCommandHandler::ProcessCapabilities(const IECommandExecutor& executor, |
| const Json::Value& capabilities, |
| std::string* error_message) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::ProcessCapabilities"; |
| for (size_t i = 0; i < capabilities.size(); ++i) { |
| std::string match_error = ""; |
| Json::Value merged_capabilities = capabilities[static_cast<int>(i)]; |
| if (this->MatchCapabilities(executor, merged_capabilities, &match_error)) { |
| LOG(DEBUG) << "Processing matched capability set with index " << i; |
| IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor); |
| |
| Json::Value unexpected_alert_behavior = this->GetCapability(merged_capabilities, |
| UNHANDLED_PROMPT_BEHAVIOR_CAPABILITY, |
| Json::stringValue, |
| Json::Value(Json::stringValue)); |
| mutable_executor.set_unexpected_alert_behavior(unexpected_alert_behavior.asString()); |
| |
| Json::Value page_load_strategy = this->GetCapability(merged_capabilities, |
| PAGE_LOAD_STRATEGY_CAPABILITY, |
| Json::stringValue, |
| NORMAL_PAGE_LOAD_STRATEGY); |
| mutable_executor.set_page_load_strategy(this->GetPageLoadStrategyValue(page_load_strategy.asString())); |
| |
| Json::Value use_strict_file_interactability = this->GetCapability(merged_capabilities, |
| STRICT_FILE_INTERACTABILITY_CAPABILITY, |
| Json::booleanValue, |
| false); |
| mutable_executor.set_use_strict_file_interactability(use_strict_file_interactability.asBool()); |
| |
| Json::Value timeouts = this->GetCapability(merged_capabilities, |
| TIMEOUTS_CAPABILITY, |
| Json::objectValue, |
| Json::Value()); |
| this->SetTimeoutSettings(executor, timeouts); |
| |
| Json::Value ie_options(Json::objectValue); |
| if (merged_capabilities.isMember(IE_DRIVER_EXTENSIONS_CAPABILITY)) { |
| ie_options = merged_capabilities[IE_DRIVER_EXTENSIONS_CAPABILITY]; |
| } |
| |
| this->SetBrowserFactorySettings(executor, ie_options); |
| |
| this->SetInputSettings(executor, ie_options); |
| |
| if (merged_capabilities.isMember(PROXY_CAPABILITY)) { |
| Json::Value use_per_process_proxy_capability = this->GetCapability(ie_options, |
| USE_PER_PROCESS_PROXY_CAPABILITY, |
| Json::booleanValue, |
| false); |
| bool use_per_process_proxy = use_per_process_proxy_capability.asBool(); |
| this->SetProxySettings(executor, |
| merged_capabilities[PROXY_CAPABILITY], |
| use_per_process_proxy); |
| } |
| |
| // Use CreateReturnedCapabilities to fill in unspecified capabilities values. |
| return this->CreateReturnedCapabilities(executor); |
| } else { |
| if (error_message->size() == 0) { |
| error_message->append("No matching capability sets found.\n"); |
| } else { |
| error_message->append("\n"); |
| } |
| error_message->append("Unable to match capability set "); |
| error_message->append(std::to_string(i)); |
| error_message->append(": "); |
| error_message->append(match_error); |
| } |
| } |
| return Json::Value(Json::nullValue); |
| } |
| |
| void NewSessionCommandHandler::SetTimeoutSettings(const IECommandExecutor& executor, |
| const Json::Value& capabilities) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::SetTimeoutSettings"; |
| IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor); |
| if (capabilities.isMember(IMPLICIT_WAIT_TIMEOUT_NAME)) { |
| mutable_executor.set_implicit_wait_timeout(capabilities[IMPLICIT_WAIT_TIMEOUT_NAME].asUInt64()); |
| } |
| if (capabilities.isMember(PAGE_LOAD_TIMEOUT_NAME)) { |
| mutable_executor.set_page_load_timeout(capabilities[PAGE_LOAD_TIMEOUT_NAME].asUInt64()); |
| } |
| if (capabilities.isMember(SCRIPT_TIMEOUT_NAME)) { |
| if (capabilities[SCRIPT_TIMEOUT_NAME].isNull()) { |
| mutable_executor.set_async_script_timeout(-1); |
| } else { |
| mutable_executor.set_async_script_timeout(capabilities[SCRIPT_TIMEOUT_NAME].asInt64()); |
| } |
| } |
| } |
| |
| void NewSessionCommandHandler::SetBrowserFactorySettings(const IECommandExecutor& executor, |
| const Json::Value& capabilities) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::SetBrowserFactorySettings"; |
| std::string default_initial_url = "http://localhost:" + std::to_string(static_cast<long long>(executor.port())) + "/"; |
| if (!capabilities.isNull()) { |
| BrowserFactorySettings factory_settings; |
| Json::Value ignore_protected_mode_settings = this->GetCapability(capabilities, |
| IGNORE_PROTECTED_MODE_CAPABILITY, |
| Json::booleanValue, |
| false); |
| factory_settings.ignore_protected_mode_settings = ignore_protected_mode_settings.asBool(); |
| |
| Json::Value ignore_zoom_setting = this->GetCapability(capabilities, |
| IGNORE_ZOOM_SETTING_CAPABILITY, |
| Json::booleanValue, |
| false); |
| factory_settings.ignore_zoom_setting = ignore_zoom_setting.asBool(); |
| |
| Json::Value browser_attach_timeout = this->GetCapability(capabilities, |
| BROWSER_ATTACH_TIMEOUT_CAPABILITY, |
| Json::intValue, |
| Json::Value(Json::intValue)); |
| factory_settings.browser_attach_timeout = browser_attach_timeout.asInt(); |
| |
| Json::Value initial_url = this->GetCapability(capabilities, |
| INITIAL_BROWSER_URL_CAPABILITY, |
| Json::stringValue, default_initial_url); |
| factory_settings.initial_browser_url = initial_url.asString(); |
| |
| Json::Value force_create_process_api = this->GetCapability(capabilities, |
| FORCE_CREATE_PROCESS_API_CAPABILITY, |
| Json::booleanValue, |
| false); |
| factory_settings.force_create_process_api = force_create_process_api.asBool(); |
| |
| Json::Value force_shell_windows_api = this->GetCapability(capabilities, |
| FORCE_SHELL_WINDOWS_API_CAPABILITY, |
| Json::booleanValue, |
| false); |
| factory_settings.force_shell_windows_api = force_shell_windows_api.asBool(); |
| |
| Json::Value browser_command_line_switches = this->GetCapability(capabilities, |
| BROWSER_COMMAND_LINE_SWITCHES_CAPABILITY, |
| Json::stringValue, |
| Json::Value(Json::stringValue)); |
| factory_settings.browser_command_line_switches = browser_command_line_switches.asString(); |
| |
| Json::Value ensure_clean_session = this->GetCapability(capabilities, |
| ENSURE_CLEAN_SESSION_CAPABILITY, |
| Json::booleanValue, |
| false); |
| factory_settings.clear_cache_before_launch = ensure_clean_session.asBool(); |
| |
| // By default, we should not be attaching to edge_ie |
| factory_settings.attach_to_edge_ie = false; |
| Json::Value attach_to_edgechrome = this->GetCapability(capabilities, |
| ATTACH_TO_EDGE_CHROME, |
| Json::booleanValue, |
| false); |
| factory_settings.attach_to_edge_ie = attach_to_edgechrome.asBool(); |
| |
| // Ignore window handle process id match when launching Edge on IE Mode |
| // Useful when the process is launched with admin/privilege rights. This |
| // assumes only one Edge browser is running on the host. |
| factory_settings.ignore_process_match = false; |
| Json::Value ignore_process_match_ie_mode = this->GetCapability(capabilities, |
| IGNORE_PROCESS_MATCH, |
| Json::booleanValue, |
| false); |
| factory_settings.ignore_process_match = ignore_process_match_ie_mode.asBool(); |
| |
| Json::Value edge_executable_path = this->GetCapability(capabilities, |
| EDGE_EXECUTABLE_PATH, |
| Json::stringValue, |
| Json::Value(Json::stringValue)); |
| factory_settings.edge_executable_path = edge_executable_path.asString(); |
| |
| IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor); |
| mutable_executor.browser_factory()->Initialize(factory_settings); |
| mutable_executor.set_is_edge_mode(factory_settings.attach_to_edge_ie); |
| mutable_executor.set_edge_executable_path(factory_settings.edge_executable_path); |
| } |
| } |
| |
| void NewSessionCommandHandler::SetProxySettings(const IECommandExecutor& executor, |
| const Json::Value& proxy_capability, |
| const bool use_per_process_proxy) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::SetProxySettings"; |
| ProxySettings proxy_settings = { false, "", "", "", "", "", "", "", "", "" }; |
| if (!proxy_capability.isNull()) { |
| // TODO(JimEvans): Validate the members of the proxy JSON object. |
| std::string proxy_type = proxy_capability.get("proxyType", "").asString(); |
| proxy_settings.proxy_type = proxy_type; |
| std::string http_proxy = proxy_capability.get("httpProxy", "").asString(); |
| proxy_settings.http_proxy = http_proxy; |
| std::string ftp_proxy = proxy_capability.get("ftpProxy", "").asString(); |
| proxy_settings.ftp_proxy = ftp_proxy; |
| std::string ssl_proxy = proxy_capability.get("sslProxy", "").asString(); |
| proxy_settings.ssl_proxy = ssl_proxy; |
| std::string socks_proxy = proxy_capability.get("socksProxy", "").asString(); |
| proxy_settings.socks_proxy = socks_proxy; |
| if (socks_proxy.length() > 0) { |
| // SOCKS proxy user name and password capabilities are ignored if the |
| // SOCKS proxy is unset. |
| std::string socks_user_name = proxy_capability.get("socksUsername", "").asString(); |
| proxy_settings.socks_user_name = socks_user_name; |
| std::string socks_password = proxy_capability.get("socksPassword", "").asString(); |
| proxy_settings.socks_password = socks_password; |
| } |
| std::string autoconfig_url = proxy_capability.get("proxyAutoconfigUrl", "").asString(); |
| proxy_settings.proxy_autoconfig_url = autoconfig_url; |
| |
| Json::Value proxy_bypass_list = proxy_capability.get("noProxy", Json::Value::null); |
| if (!proxy_bypass_list.isNull() && proxy_bypass_list.isArray()) { |
| std::string no_proxy = ""; |
| for (size_t i = 0; i < proxy_bypass_list.size(); ++i) { |
| if (no_proxy.size() > 0) { |
| no_proxy.append(";"); |
| } |
| no_proxy.append(proxy_bypass_list[static_cast<int>(i)].asString()); |
| } |
| proxy_settings.proxy_bypass = no_proxy; |
| } |
| |
| proxy_settings.use_per_process_proxy = use_per_process_proxy; |
| |
| IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor); |
| mutable_executor.proxy_manager()->Initialize(proxy_settings); |
| } |
| } |
| |
| void NewSessionCommandHandler::SetInputSettings(const IECommandExecutor& executor, |
| const Json::Value& capabilities) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::SetInputSettings"; |
| IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor); |
| InputManagerSettings input_manager_settings; |
| input_manager_settings.element_repository = mutable_executor.element_manager(); |
| |
| Json::Value enable_native_events = this->GetCapability(capabilities, |
| NATIVE_EVENTS_CAPABILITY, |
| Json::booleanValue, |
| true); |
| input_manager_settings.use_native_events = enable_native_events.asBool(); |
| |
| Json::Value scroll_behavior = this->GetCapability(capabilities, |
| ELEMENT_SCROLL_BEHAVIOR_CAPABILITY, |
| Json::intValue, |
| Json::Value(Json::intValue)); |
| input_manager_settings.scroll_behavior = static_cast<ElementScrollBehavior>(scroll_behavior.asInt()); |
| |
| Json::Value require_window_focus = this->GetCapability(capabilities, |
| REQUIRE_WINDOW_FOCUS_CAPABILITY, |
| Json::booleanValue, |
| false); |
| input_manager_settings.require_window_focus = require_window_focus.asBool(); |
| |
| Json::Value file_upload_dialog_timeout = this->GetCapability(capabilities, |
| FILE_UPLOAD_DIALOG_TIMEOUT_CAPABILITY, |
| Json::intValue, |
| Json::Value(Json::intValue)); |
| if (file_upload_dialog_timeout.asInt() > 0) { |
| mutable_executor.set_file_upload_dialog_timeout(file_upload_dialog_timeout.asInt()); |
| } |
| |
| Json::Value enable_persistent_hover = this->GetCapability(capabilities, |
| ENABLE_PERSISTENT_HOVER_CAPABILITY, |
| Json::booleanValue, |
| true); |
| if (require_window_focus.asBool() || !enable_native_events.asBool()) { |
| // Setting "require_window_focus" implies SendInput() API, and does not |
| // therefore require persistent hover. Likewise, not using native events |
| // requires no persistent hover either. |
| input_manager_settings.enable_persistent_hover = false; |
| } else { |
| input_manager_settings.enable_persistent_hover = enable_persistent_hover.asBool(); |
| } |
| mutable_executor.input_manager()->Initialize(input_manager_settings); |
| } |
| |
| Json::Value NewSessionCommandHandler::CreateReturnedCapabilities(const IECommandExecutor& executor) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::CreateReturnedCapabilities"; |
| Json::Value capabilities; |
| capabilities[BROWSER_NAME_CAPABILITY] = "internet explorer"; |
| capabilities[BROWSER_VERSION_CAPABILITY] = std::to_string(static_cast<long long>(executor.browser_factory()->browser_version())); |
| capabilities[PLATFORM_NAME_CAPABILITY] = "windows"; |
| capabilities[ACCEPT_INSECURE_CERTS_CAPABILITY] = false; |
| capabilities[PAGE_LOAD_STRATEGY_CAPABILITY] = executor.page_load_strategy(); |
| capabilities[STRICT_FILE_INTERACTABILITY_CAPABILITY] = executor.use_strict_file_interactability(); |
| capabilities[SET_WINDOW_RECT_CAPABILITY] = true; |
| |
| if (executor.unexpected_alert_behavior().size() > 0) { |
| capabilities[UNHANDLED_PROMPT_BEHAVIOR_CAPABILITY] = executor.unexpected_alert_behavior(); |
| } else { |
| capabilities[UNHANDLED_PROMPT_BEHAVIOR_CAPABILITY] = DISMISS_AND_NOTIFY_UNEXPECTED_ALERTS; |
| } |
| |
| Json::Value timeouts; |
| timeouts[IMPLICIT_WAIT_TIMEOUT_NAME] = executor.implicit_wait_timeout(); |
| timeouts[PAGE_LOAD_TIMEOUT_NAME] = executor.page_load_timeout(); |
| long long script_timeout = executor.async_script_timeout(); |
| if (script_timeout < 0) { |
| timeouts[SCRIPT_TIMEOUT_NAME] = Json::Value::null; |
| } else { |
| timeouts[SCRIPT_TIMEOUT_NAME] = script_timeout; |
| } |
| capabilities[TIMEOUTS_CAPABILITY] = timeouts; |
| |
| Json::Value ie_options; |
| ie_options[IGNORE_PROTECTED_MODE_CAPABILITY] = executor.browser_factory()->ignore_protected_mode_settings(); |
| ie_options[IGNORE_ZOOM_SETTING_CAPABILITY] = executor.browser_factory()->ignore_zoom_setting(); |
| ie_options[INITIAL_BROWSER_URL_CAPABILITY] = executor.browser_factory()->initial_browser_url(); |
| ie_options[BROWSER_ATTACH_TIMEOUT_CAPABILITY] = executor.browser_factory()->browser_attach_timeout(); |
| ie_options[BROWSER_COMMAND_LINE_SWITCHES_CAPABILITY] = executor.browser_factory()->browser_command_line_switches(); |
| ie_options[FORCE_CREATE_PROCESS_API_CAPABILITY] = executor.browser_factory()->force_createprocess_api(); |
| ie_options[ENSURE_CLEAN_SESSION_CAPABILITY] = executor.browser_factory()->clear_cache(); |
| ie_options[IGNORE_PROCESS_MATCH] = executor.browser_factory()->ignore_process_match(); |
| ie_options[NATIVE_EVENTS_CAPABILITY] = executor.input_manager()->enable_native_events(); |
| ie_options[ENABLE_PERSISTENT_HOVER_CAPABILITY] = executor.input_manager()->use_persistent_hover(); |
| ie_options[ELEMENT_SCROLL_BEHAVIOR_CAPABILITY] = executor.input_manager()->scroll_behavior(); |
| ie_options[REQUIRE_WINDOW_FOCUS_CAPABILITY] = executor.input_manager()->require_window_focus(); |
| ie_options[FILE_UPLOAD_DIALOG_TIMEOUT_CAPABILITY] = executor.file_upload_dialog_timeout(); |
| ie_options[ATTACH_TO_EDGE_CHROME] = executor.is_edge_mode(); |
| ie_options[EDGE_EXECUTABLE_PATH] = executor.edge_executable_path(); |
| |
| if (executor.proxy_manager()->is_proxy_set()) { |
| ie_options[USE_PER_PROCESS_PROXY_CAPABILITY] = executor.proxy_manager()->use_per_process_proxy(); |
| capabilities[PROXY_CAPABILITY] = executor.proxy_manager()->GetProxyAsJson(); |
| } else { |
| capabilities[PROXY_CAPABILITY] = Json::Value(Json::objectValue); |
| } |
| |
| capabilities[IE_DRIVER_EXTENSIONS_CAPABILITY] = ie_options; |
| return capabilities; |
| } |
| |
| bool NewSessionCommandHandler::MatchCapabilities(const IECommandExecutor& executor, |
| const Json::Value& merged_capabilities, |
| std::string* error_message) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::MatchCapabilities"; |
| std::vector<std::string> capability_names = merged_capabilities.getMemberNames(); |
| std::vector<std::string>::const_iterator name_iterator = capability_names.begin(); |
| for (; name_iterator != capability_names.end(); ++name_iterator) { |
| std::string capability_name = *name_iterator; |
| if (capability_name == BROWSER_NAME_CAPABILITY && |
| merged_capabilities[BROWSER_NAME_CAPABILITY].asString() != "internet explorer") { |
| *error_message = "browserName must be 'internet explorer', but was '" + |
| merged_capabilities[BROWSER_NAME_CAPABILITY].asString() + |
| "'"; |
| return false; |
| } |
| |
| if (capability_name == PLATFORM_NAME_CAPABILITY && |
| merged_capabilities[PLATFORM_NAME_CAPABILITY].asString() != "windows") { |
| *error_message = "platformName must be 'windows', but was '" + |
| merged_capabilities[PLATFORM_NAME_CAPABILITY].asString() + |
| "'"; |
| return false; |
| } |
| |
| if (capability_name == BROWSER_VERSION_CAPABILITY) { |
| // TODO: Support string version comparisons with '<', '>', '<=', and '>=' |
| std::string requested_browser_version_value = merged_capabilities[BROWSER_VERSION_CAPABILITY].asString(); |
| int available_browser_version = executor.browser_factory()->browser_version(); |
| int requested_browser_version = atoi(requested_browser_version_value.c_str()); |
| if (available_browser_version != requested_browser_version) { |
| *error_message = "requested browserVersion value was '" + |
| requested_browser_version_value + |
| "', but the installed version of IE is " + |
| std::to_string(available_browser_version) + |
| " (note: only exact matches on major version " + |
| "number are supported)"; |
| return false; |
| } |
| } |
| |
| if (capability_name == ACCEPT_INSECURE_CERTS_CAPABILITY && |
| merged_capabilities[ACCEPT_INSECURE_CERTS_CAPABILITY].asBool()) { |
| *error_message = "acceptInsecureCerts was 'true', but the IE driver does not allow bypassing insecure (self-signed) SSL certificates"; |
| return false; |
| } |
| |
| if (capability_name.find(":") != std::string::npos && |
| (capability_name != IE_DRIVER_EXTENSIONS_CAPABILITY && |
| capability_name.find("test:") == std::string::npos)) { |
| *error_message = capability_name + " is an unknown extension capability for IE"; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool NewSessionCommandHandler::MergeCapabilities( |
| const Json::Value& primary_capabilities, |
| const Json::Value& secondary_capabilities, |
| Json::Value* merged_capabilities, |
| std::string* error_message) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::MergeCapabilities"; |
| std::vector<std::string> primary_property_names = primary_capabilities.getMemberNames(); |
| for (size_t i = 0; i < primary_property_names.size(); ++i) { |
| std::string property_name = primary_property_names[i]; |
| (*merged_capabilities)[property_name] = primary_capabilities[property_name]; |
| } |
| |
| std::vector<std::string> secondary_property_names = secondary_capabilities.getMemberNames(); |
| for (size_t i = 0; i < secondary_property_names.size(); ++i) { |
| std::string property_name = secondary_property_names[i]; |
| if (merged_capabilities->isMember(property_name)) { |
| *error_message = "Cannot merge capabilities: " + |
| property_name + " is already specified"; |
| return false; |
| } |
| (*merged_capabilities)[property_name] = secondary_capabilities[property_name]; |
| } |
| return true; |
| } |
| |
| bool NewSessionCommandHandler::ValidateCapabilities( |
| const Json::Value& capabilities, |
| const std::string& capability_set_name, |
| std::string* error_message) { |
| LOG(TRACE) << "Entering NewSessionCommandHandler::ValidateCapabilities"; |
| LOG(DEBUG) << "Validating capabilities object"; |
| if (!capabilities.isObject() && !capabilities.isNull()) { |
| *error_message = capability_set_name + " is not a JSON object."; |
| return false; |
| } |
| |
| if (capabilities.isNull()) { |
| return true; |
| } |
| |
| std::vector<std::string> capability_names = capabilities.getMemberNames(); |
| std::vector<std::string>::const_iterator name_iterator = capability_names.begin(); |
| for (; name_iterator != capability_names.end(); ++name_iterator) { |
| std::string capability_name = *name_iterator; |
| std::string capability_error_message; |
| if (capabilities[capability_name].isNull()) { |
| // Cast away the const modifier only in this case. |
| const_cast<Json::Value&>(capabilities).removeMember(capability_name); |
| continue; |
| } |
| if (capability_name == ACCEPT_INSECURE_CERTS_CAPABILITY) { |
| LOG(DEBUG) << "Found " << ACCEPT_INSECURE_CERTS_CAPABILITY << " capability." |
| << " Validating value type is boolean."; |
| if (!this->ValidateCapabilityType(capabilities, |
| capability_name, |
| Json::ValueType::booleanValue, |
| &capability_error_message)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + capability_error_message; |
| return false; |
| } |
| continue; |
| } |
| |
| if (capability_name == STRICT_FILE_INTERACTABILITY_CAPABILITY) { |
| LOG(DEBUG) << "Found " << STRICT_FILE_INTERACTABILITY_CAPABILITY << " capability." |
| << " Validating value type is boolean."; |
| if (!this->ValidateCapabilityType(capabilities, |
| capability_name, |
| Json::ValueType::booleanValue, |
| &capability_error_message)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + capability_error_message; |
| return false; |
| } |
| continue; |
| } |
| |
| if (capability_name == BROWSER_NAME_CAPABILITY) { |
| LOG(DEBUG) << "Found " << BROWSER_NAME_CAPABILITY << " capability." |
| << " Validating value type is string."; |
| if (!this->ValidateCapabilityType(capabilities, |
| capability_name, |
| Json::ValueType::stringValue, |
| &capability_error_message)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + capability_error_message; |
| return false; |
| } |
| continue; |
| } |
| |
| if (capability_name == BROWSER_VERSION_CAPABILITY) { |
| LOG(DEBUG) << "Found " << BROWSER_VERSION_CAPABILITY << " capability." |
| << " Validating value type is string."; |
| if (!this->ValidateCapabilityType(capabilities, |
| capability_name, |
| Json::ValueType::stringValue, |
| &capability_error_message)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| capability_error_message; |
| return false; |
| } |
| continue; |
| } |
| |
| if (capability_name == PLATFORM_NAME_CAPABILITY) { |
| LOG(DEBUG) << "Found " << PLATFORM_NAME_CAPABILITY << " capability." |
| << " Validating value type is string."; |
| if (!this->ValidateCapabilityType(capabilities, |
| capability_name, |
| Json::ValueType::stringValue, |
| &capability_error_message)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + capability_error_message; |
| return false; |
| } |
| continue; |
| } |
| |
| if (capability_name == UNHANDLED_PROMPT_BEHAVIOR_CAPABILITY) { |
| LOG(DEBUG) << "Found " << UNHANDLED_PROMPT_BEHAVIOR_CAPABILITY << " capability." |
| << " Validating value type is string."; |
| if (!this->ValidateCapabilityType(capabilities, |
| capability_name, |
| Json::ValueType::stringValue, |
| &capability_error_message)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + capability_error_message; |
| return false; |
| } else { |
| LOG(DEBUG) << "Validating " << UNHANDLED_PROMPT_BEHAVIOR_CAPABILITY << " capability" |
| << " is a valid value."; |
| std::string unhandled_prompt_behavior = capabilities[capability_name].asString(); |
| if (unhandled_prompt_behavior != ACCEPT_UNEXPECTED_ALERTS && |
| unhandled_prompt_behavior != DISMISS_UNEXPECTED_ALERTS && |
| unhandled_prompt_behavior != ACCEPT_AND_NOTIFY_UNEXPECTED_ALERTS && |
| unhandled_prompt_behavior != DISMISS_AND_NOTIFY_UNEXPECTED_ALERTS && |
| unhandled_prompt_behavior != IGNORE_UNEXPECTED_ALERTS) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "unhandledPromptBehavior is " + |
| unhandled_prompt_behavior + |
| " but must be 'accept' or 'dismiss'"; |
| return false; |
| } |
| } |
| |
| continue; |
| } |
| |
| if (capability_name == PAGE_LOAD_STRATEGY_CAPABILITY) { |
| std::string page_load_strategy = ""; |
| LOG(DEBUG) << "Found " << PAGE_LOAD_STRATEGY_CAPABILITY << " capability." |
| << " Validating value type is string."; |
| if (!this->ValidateCapabilityType(capabilities, |
| capability_name, |
| Json::ValueType::stringValue, |
| &capability_error_message)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + capability_error_message; |
| return false; |
| } else { |
| LOG(DEBUG) << "Validating " << PAGE_LOAD_STRATEGY_CAPABILITY << " capability" |
| << " is a valid value."; |
| page_load_strategy = capabilities[capability_name].asString(); |
| if (page_load_strategy != NONE_PAGE_LOAD_STRATEGY && |
| page_load_strategy != EAGER_PAGE_LOAD_STRATEGY && |
| page_load_strategy != NORMAL_PAGE_LOAD_STRATEGY) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "pageLoadStrategy is " + page_load_strategy + |
| " but must be 'none', 'eager', or 'normal'"; |
| return false; |
| } |
| } |
| continue; |
| } |
| |
| if (capability_name == TIMEOUTS_CAPABILITY) { |
| LOG(DEBUG) << "Found " << TIMEOUTS_CAPABILITY << " capability." |
| << " Validating value type is object."; |
| if (!this->ValidateCapabilityType(capabilities, |
| capability_name, |
| Json::ValueType::objectValue, |
| &capability_error_message)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + capability_error_message; |
| return false; |
| } else { |
| LOG(DEBUG) << "Validating " << TIMEOUTS_CAPABILITY << " capability" |
| << " object contains correct property names."; |
| Json::Value timeouts = capabilities[capability_name]; |
| std::vector<std::string> timeout_names = timeouts.getMemberNames(); |
| std::vector<std::string>::const_iterator timeout_name_iterator = timeout_names.begin(); |
| for (; timeout_name_iterator != timeout_names.end(); ++timeout_name_iterator) { |
| std::string timeout_name = *timeout_name_iterator; |
| if (timeout_name != PAGE_LOAD_TIMEOUT_NAME && |
| timeout_name != IMPLICIT_WAIT_TIMEOUT_NAME && |
| timeout_name != SCRIPT_TIMEOUT_NAME) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "a timeout named " + timeout_name + |
| " is specified, but timeout names must be " + |
| "'implicit', 'pageLoad', or 'script'"; |
| return false; |
| } |
| std::string timeout_error = ""; |
| Json::Value timeout_value = timeouts[timeout_name]; |
| // Special case: script timeout may be null. |
| if (timeout_name != SCRIPT_TIMEOUT_NAME || !timeout_value.isNull()) { |
| if (!timeout_value.isNumeric() || !timeout_value.isIntegral()) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "timeout " + timeout_name + |
| "must be an integer"; |
| return false; |
| } |
| if (!timeout_value.isInt64()) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "timeout " + timeout_name + |
| "must be an integer between 0 and 2^53 - 1"; |
| return false; |
| } |
| long long timeout = timeout_value.asInt64(); |
| if (timeout < 0 || timeout > MAX_SAFE_INTEGER) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "timeout " + timeout_name + |
| "must be an integer between 0 and 2^53 - 1"; |
| return false; |
| } |
| } |
| } |
| } |
| continue; |
| } |
| |
| if (capability_name == PROXY_CAPABILITY) { |
| LOG(DEBUG) << "Found " << PROXY_CAPABILITY << " capability." |
| << " Validating value type is object."; |
| if (!this->ValidateCapabilityType(capabilities, |
| capability_name, |
| Json::ValueType::objectValue, |
| &capability_error_message)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| capability_error_message; |
| return false; |
| } else { |
| LOG(DEBUG) << "Validating " << PROXY_CAPABILITY << "capability" |
| << " object structure."; |
| Json::Value proxy = capabilities[capability_name]; |
| std::vector<std::string> proxy_setting_names = proxy.getMemberNames(); |
| std::vector<std::string>::const_iterator proxy_setting_iterator = proxy_setting_names.begin(); |
| for (; proxy_setting_iterator != proxy_setting_names.end(); ++proxy_setting_iterator) { |
| std::string proxy_error = ""; |
| std::string proxy_setting = *proxy_setting_iterator; |
| if (proxy_setting == "proxyType") { |
| if (!this->ValidateCapabilityType(proxy, |
| proxy_setting, |
| Json::ValueType::stringValue, |
| &proxy_error)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "proxy setting " + proxy_error; |
| return false; |
| } |
| std::string proxy_type = proxy[proxy_setting].asString(); |
| if (proxy_type != "pac" && |
| proxy_type != "direct" && |
| proxy_type != "autodetect" && |
| proxy_type != "system" && |
| proxy_type != "manual") { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "a proxy type named " + proxy_type + |
| " is specified, but proxy type must be " + |
| "'pac', 'direct', 'autodetect', 'system', " + |
| "or 'manual'"; |
| return false; |
| } |
| continue; |
| } |
| |
| if (proxy_setting == "proxyAutoconfigUrl" || |
| proxy_setting == "ftpProxy" || |
| proxy_setting == "httpProxy" || |
| proxy_setting == "sslProxy" || |
| proxy_setting == "socksProxy") { |
| if (!this->ValidateCapabilityType(proxy, |
| proxy_setting, |
| Json::ValueType::stringValue, |
| &proxy_error)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "proxy setting " + proxy_error; |
| return false; |
| } |
| continue; |
| } |
| |
| if (proxy_setting == "noProxy") { |
| if (!this->ValidateCapabilityType(proxy, |
| proxy_setting, |
| Json::ValueType::arrayValue, |
| &proxy_error)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "proxy setting " + proxy_error; |
| return false; |
| } |
| continue; |
| } |
| |
| if (proxy_setting == "socksVersion") { |
| if (!this->ValidateCapabilityType(proxy, proxy_setting, |
| Json::ValueType::intValue, |
| &proxy_error)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "proxy setting " + proxy_error; |
| return false; |
| } |
| int socks_version = proxy[proxy_setting].asInt(); |
| if (socks_version < 0 || socks_version > 255) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "SOCKS version must be between 0 and 255."; |
| return false; |
| } |
| continue; |
| } |
| |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| "unknown proxy setting named " + proxy_error; |
| return false; |
| } |
| } |
| continue; |
| } |
| |
| if (capability_name == IE_DRIVER_EXTENSIONS_CAPABILITY) { |
| LOG(DEBUG) << "Found " << IE_DRIVER_EXTENSIONS_CAPABILITY << " capability." |
| << " Validating value type is object."; |
| if (!this->ValidateCapabilityType(capabilities, |
| capability_name, |
| Json::ValueType::objectValue, |
| &capability_error_message)) { |
| *error_message = "Invalid capabilities in " + |
| capability_set_name + ": " + |
| capability_error_message; |
| return false; |
| } |
| continue; |
| } |
| |
| if (capability_name.find(":") != std::string::npos) { |
| LOG(DEBUG) << "Found extension capability named " << capability_name << "." |
| << " Nothing further to validate."; |
| continue; |
| } |
| |
| *error_message = "Invalid capabilities in " + capability_set_name + ": " + |
| "unknown capability named " + capability_name; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool NewSessionCommandHandler::ValidateCapabilityType( |
| const Json::Value& capabilities, |
| const std::string& capability_name, |
| const Json::ValueType& expected_capability_type, |
| std::string* error_message) { |
| Json::Value capability_value = capabilities[capability_name]; |
| if (!this->IsEquivalentType(capability_value.type(), |
| expected_capability_type)) { |
| *error_message = capability_name + " is type " + |
| this->GetJsonTypeDescription(capability_value.type()) + |
| " instead of " + |
| this->GetJsonTypeDescription(expected_capability_type); |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace webdriver |