text: // 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"
  
  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 {
      // W3C capabilities not found. Try legacy capabilities.
      // Note that we ignore requiredCapabilities. This is by design.
      it = command_parameters.find("desiredCapabilities");
      if (it != command_parameters.end()) {
        returned_capabilities = this->ProcessLegacyCapabilities(executor, it->second, &error_message);
      } else {
        error_message = "No property named 'capabilities' or 'desiredCapabilities' 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) {
    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) {
    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) {
    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::ProcessLegacyCapabilities(const IECommandExecutor& executor, const Json::Value& capabilities, std::string* error_message) {
    this->SetBrowserFactorySettings(executor, capabilities);
  
    this->SetInputSettings(executor, capabilities);
  
    IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor);
  
    Json::Value unexpected_alert_behavior = this->GetCapability(capabilities, UNHANDLED_PROMPT_BEHAVIOR_CAPABILITY, Json::stringValue, DISMISS_UNEXPECTED_ALERTS);
    mutable_executor.set_unexpected_alert_behavior(this->GetUnexpectedAlertBehaviorValue(unexpected_alert_behavior.asString()));
  
    Json::Value page_load_strategy = this->GetCapability(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_per_process_proxy_capability = this->GetCapability(capabilities, USE_PER_PROCESS_PROXY_CAPABILITY, Json::booleanValue, false);
    bool use_per_process_proxy = use_per_process_proxy_capability.asBool();
    Json::Value proxy = capabilities.get(PROXY_CAPABILITY, Json::nullValue);
    this->SetProxySettings(executor, proxy, use_per_process_proxy);
  
    return this->CreateReturnedCapabilities(executor);
  }
  
  Json::Value NewSessionCommandHandler::ValidateArguments(const Json::Value& capabilities, std::string* error_message) {
    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;
      }
    }
  
    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) {
          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) {
    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)) {
        IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor);
  
        Json::Value unexpected_alert_behavior = this->GetCapability(merged_capabilities, UNHANDLED_PROMPT_BEHAVIOR_CAPABILITY, 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 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];
        }
  
        std::string default_initial_url = "http://localhost:" + std::to_string(static_cast<long long>(executor.port())) + "/";
        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) {
    IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor);
    if (capabilities.isMember("implicit")) {
      mutable_executor.set_implicit_wait_timeout(capabilities["implicit"].asUInt64());
    }
    if (capabilities.isMember("pageLoad")) {
      mutable_executor.set_page_load_timeout(capabilities["pageLoad"].asUInt64());
    }
    if (capabilities.isMember("script")) {
      mutable_executor.set_async_script_timeout(capabilities["script"].asUInt64());
    }
  }
  
  void NewSessionCommandHandler::SetBrowserFactorySettings(const IECommandExecutor& executor, const Json::Value& capabilities) {
    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, 0);
      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, "");
      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();
  
      IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor);
      mutable_executor.browser_factory()->Initialize(factory_settings);
    }
  }
  
  void NewSessionCommandHandler::SetProxySettings(const IECommandExecutor& executor, const Json::Value& proxy_capability, const bool use_per_process_proxy) {
    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) {
    IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor);
  
    Json::Value enable_native_events = this->GetCapability(capabilities, NATIVE_EVENTS_CAPABILITY, Json::booleanValue, true);
    mutable_executor.input_manager()->set_enable_native_events(enable_native_events.asBool());
  
    Json::Value scroll_behavior = this->GetCapability(capabilities, ELEMENT_SCROLL_BEHAVIOR_CAPABILITY, Json::intValue, 0);
    mutable_executor.input_manager()->set_scroll_behavior(static_cast<ElementScrollBehavior>(scroll_behavior.asInt()));
  
    Json::Value require_window_focus = this->GetCapability(capabilities, REQUIRE_WINDOW_FOCUS_CAPABILITY, Json::booleanValue, false);
    mutable_executor.input_manager()->set_require_window_focus(require_window_focus.asBool());
  
    Json::Value file_upload_dialog_timeout = this->GetCapability(capabilities, FILE_UPLOAD_DIALOG_TIMEOUT_CAPABILITY, Json::intValue, 0);
    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.
      mutable_executor.input_manager()->set_use_persistent_hover(false);
    } else {
      mutable_executor.input_manager()->set_use_persistent_hover(enable_persistent_hover.asBool());
    }
  }
  
  Json::Value NewSessionCommandHandler::CreateReturnedCapabilities(const IECommandExecutor& executor) {
    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[SET_WINDOW_RECT_CAPABILITY] = true;
  
    if (executor.unexpected_alert_behavior().size() > 0) {
      capabilities[UNHANDLED_PROMPT_BEHAVIOR_CAPABILITY] = executor.unexpected_alert_behavior();
    }
  
    Json::Value timeouts;
    timeouts["implicit"] = executor.implicit_wait_timeout();
    timeouts["pageLoad"] = executor.page_load_timeout();
    timeouts["script"] = executor.async_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[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();
  
    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) {
    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) {
        *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) {
    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(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 == 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 != "eager" &&
              page_load_strategy != "normal") {
            *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();
          if (timeout_names.size() == 0) {
            *error_message = "Invalid capabilities in " +
                             capability_set_name + ": " +
                             "no timeouts specified";
            return false;
          }
          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 != "pageLoad" &&
                timeout_name != "implicit" &&
                timeout_name != "script") {
              *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];
            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.isUInt64()) {
              *error_message = "Invalid capabilities in " +
                               capability_set_name + ": " +
                               "timeout " + timeout_name +
                               "must be an integer between 0 and 2^64 - 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
  