diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index ed20adac3d9..b783216acc6 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -828,6 +828,7 @@ set(SOURCES WebDriver/InputSource.cpp WebDriver/InputState.cpp WebDriver/JSON.cpp + WebDriver/Proxy.cpp WebDriver/Response.cpp WebDriver/Screenshot.cpp WebDriver/TimeoutsConfiguration.cpp diff --git a/Libraries/LibWeb/WebDriver/Capabilities.cpp b/Libraries/LibWeb/WebDriver/Capabilities.cpp index 3731ffeb6ed..233d2461cac 100644 --- a/Libraries/LibWeb/WebDriver/Capabilities.cpp +++ b/Libraries/LibWeb/WebDriver/Capabilities.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -31,33 +32,6 @@ static Response deserialize_as_a_page_load_strategy(JsonValue value) return value; } -// https://w3c.github.io/webdriver/#dfn-deserialize-as-a-proxy -static ErrorOr deserialize_as_a_proxy(JsonValue parameter) -{ - // 1. If parameter is not a JSON Object return an error with error code invalid argument. - if (!parameter.is_object()) - return Error::from_code(ErrorCode::InvalidArgument, "Capability proxy must be an object"sv); - - // 2. Let proxy be a new, empty proxy configuration object. - JsonObject proxy; - - // 3. For each enumerable own property in parameter run the following substeps: - TRY(parameter.as_object().try_for_each_member([&](auto const& key, JsonValue const& value) -> ErrorOr { - // 1. Let key be the name of the property. - // 2. Let value be the result of getting a property named name from capability. - - // FIXME: 3. If there is no matching key for key in the proxy configuration table return an error with error code invalid argument. - // FIXME: 4. If value is not one of the valid values for that key, return an error with error code invalid argument. - - // 5. Set a property key to value on proxy. - proxy.set(key, value); - - return {}; - })); - - return proxy; -} - static InterfaceMode default_interface_mode { InterfaceMode::Graphical }; void set_default_interface_mode(InterfaceMode interface_mode) @@ -332,7 +306,10 @@ static JsonValue match_capabilities(JsonObject const& capabilities, SessionFlags } // -> "proxy" else if (name == "proxy"sv) { - // FIXME: If the has proxy configuration flag is set, or if the proxy configuration defined in value is not one that passes the endpoint node's implementation-specific validity checks, return success with data null. + // If the has proxy configuration flag is set, or if the proxy configuration defined in value is not one that + // passes the endpoint node's implementation-specific validity checks, return success with data null. + if (has_proxy_configuration()) + return AK::Error::from_string_literal("proxy"); } // -> "unhandledPromptBehavior" else if (name == "unhandledPromptBehavior"sv) { diff --git a/Libraries/LibWeb/WebDriver/Proxy.cpp b/Libraries/LibWeb/WebDriver/Proxy.cpp new file mode 100644 index 00000000000..9bf1bec6544 --- /dev/null +++ b/Libraries/LibWeb/WebDriver/Proxy.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::WebDriver { + +// https://w3c.github.io/webdriver/#dfn-has-proxy-configuration +// An endpoint node has an associated has proxy configuration flag that indicates whether the proxy is already configured. +// The default value of the flag is true if the endpoint doesn't support proxy configuration, or false otherwise. +static constexpr bool s_default_has_proxy_configuration = true; +static bool s_has_proxy_configuration = s_default_has_proxy_configuration; + +bool has_proxy_configuration() +{ + return s_has_proxy_configuration; +} + +void set_has_proxy_configuration(bool has_proxy_configuration) +{ + s_has_proxy_configuration = has_proxy_configuration; +} + +void reset_has_proxy_configuration() +{ + s_has_proxy_configuration = s_default_has_proxy_configuration; +} + +// https://w3c.github.io/webdriver/#dfn-proxy-configuration +static ErrorOr validate_proxy_item(StringView key, JsonValue const& value) +{ + if (key == "proxyType"sv) { + if (!value.is_string()) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'proxyType' must be a string"sv); + if (!value.as_string().is_one_of("pac"sv, "direct"sv, "autodetect"sv, "system"sv, "manual"sv)) + return Error::from_code(ErrorCode::InvalidArgument, "Invalid 'proxyType' value"sv); + return {}; + } + + if (key == "proxyAutoconfigUrl"sv) { + if (!value.is_string()) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'proxyAutoconfigUrl' must be a string"sv); + if (URL::URL url { value.as_string() }; !url.is_valid()) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'proxyAutoconfigUrl' must be a valid URL"sv); + return {}; + } + + if (key == "ftpProxy"sv) { + if (!value.is_string()) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'ftpProxy' must be a string"sv); + if (URL::URL url { value.as_string() }; !url.is_valid() || url.scheme() != "ftp"sv) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'ftpProxy' must be a valid FTP URL"sv); + return {}; + } + + if (key == "httpProxy"sv) { + if (!value.is_string()) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'httpProxy' must be a string"sv); + if (URL::URL url { value.as_string() }; !url.is_valid() || url.scheme() != "http"sv) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'httpProxy' must be a valid HTTP URL"sv); + return {}; + } + + if (key == "noProxy"sv) { + if (!value.is_array()) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'noProxy' must be a list"sv); + + TRY(value.as_array().try_for_each([&](JsonValue const& item) -> ErrorOr { + if (!item.is_string()) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'noProxy' must be a list of strings"sv); + return {}; + })); + + return {}; + } + + if (key == "sslProxy"sv) { + if (!value.is_string()) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'sslProxy' must be a string"sv); + if (URL::URL url { value.as_string() }; !url.is_valid() || url.scheme() != "https"sv) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'sslProxy' must be a valid HTTPS URL"sv); + return {}; + } + + if (key == "socksProxy"sv) { + if (!value.is_string()) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'proxyAutoconfigUrl' must be a string"sv); + if (URL::URL url { value.as_string() }; !url.is_valid()) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'proxyAutoconfigUrl' must be a valid URL"sv); + return {}; + } + + if (key == "socksVersion"sv) { + if (!value.is_integer()) + return Error::from_code(ErrorCode::InvalidArgument, "Proxy configuration item 'socksVersion' must be an integer in the range [0, 255]"sv); + return {}; + } + + return Error::from_code(ErrorCode::InvalidArgument, "Invalid proxy configuration item"sv); +} + +// https://w3c.github.io/webdriver/#dfn-deserialize-as-a-proxy +ErrorOr deserialize_as_a_proxy(JsonValue const& parameter) +{ + // 1. If parameter is not a JSON Object return an error with error code invalid argument. + if (!parameter.is_object()) + return Error::from_code(ErrorCode::InvalidArgument, "Capability proxy must be an object"sv); + + // 2. Let proxy be a new, empty proxy configuration object. + JsonObject proxy; + + // 3. For each enumerable own property in parameter run the following substeps: + TRY(parameter.as_object().try_for_each_member([&](ByteString const& key, JsonValue const& value) -> ErrorOr { + // 1. Let key be the name of the property. + // 2. Let value be the result of getting a property named name from capability. + + // 3. If there is no matching key for key in the proxy configuration table return an error with error code invalid argument. + // 4. If value is not one of the valid values for that key, return an error with error code invalid argument. + TRY(validate_proxy_item(key, value)); + + // 5. Set a property key to value on proxy. + proxy.set(key, value); + + return {}; + })); + + return proxy; +} + +} diff --git a/Libraries/LibWeb/WebDriver/Proxy.h b/Libraries/LibWeb/WebDriver/Proxy.h new file mode 100644 index 00000000000..dc2e53171b9 --- /dev/null +++ b/Libraries/LibWeb/WebDriver/Proxy.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::WebDriver { + +bool has_proxy_configuration(); +void set_has_proxy_configuration(bool); +void reset_has_proxy_configuration(); + +ErrorOr deserialize_as_a_proxy(JsonValue const&); + +} diff --git a/Services/WebDriver/Session.cpp b/Services/WebDriver/Session.cpp index 77a3539a688..925ae32cf8d 100644 --- a/Services/WebDriver/Session.cpp +++ b/Services/WebDriver/Session.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -35,11 +36,19 @@ ErrorOr> Session::create(NonnullRefPtr client, Js auto session = adopt_ref(*new Session(client, capabilities, move(session_id), flags)); TRY(session->start(client->launch_browser_callbacks())); - // FIXME: 3. Let proxy be the result of getting property "proxy" from capabilities and run the substeps of the first matching statement: - // -> proxy is a proxy configuration object - // Take implementation-defined steps to set the user agent proxy using the extracted proxy configuration. If the defined proxy cannot be configured return error with error code session not created. Otherwise set the has proxy configuration flag to true. - // -> Otherwise - // Set a property of capabilities with name "proxy" and a value that is a new JSON Object. + // 3. Let proxy be the result of getting property "proxy" from capabilities and run the substeps of the first matching statement: + // -> proxy is a proxy configuration object + if (auto proxy = capabilities.get_object("proxy"sv); proxy.has_value()) { + // Take implementation-defined steps to set the user agent proxy using the extracted proxy configuration. If the + // defined proxy cannot be configured return error with error code session not created. Otherwise set the has + // proxy configuration flag to true. + return Error::from_string_literal("Proxy configuration is not yet supported"); + } + // -> Otherwise + else { + // Set a property of capabilities with name "proxy" and a value that is a new JSON Object. + capabilities.set("proxy", JsonObject {}); + } // FIXME: 4. If capabilites has a property named "acceptInsecureCerts", set the endpoint node's accept insecure TLS flag // to the result of getting a property named "acceptInsecureCerts" from capabilities. @@ -161,7 +170,9 @@ void Session::close() Web::WebDriver::set_user_prompt_handler({}); // FIXME: 3. Unset the accept insecure TLS flag. - // FIXME: 4. Reset the has proxy configuration flag to its default value. + + // 4. Reset the has proxy configuration flag to its default value. + Web::WebDriver::reset_has_proxy_configuration(); // 5. Optionally, close all top-level browsing contexts, without prompting to unload. for (auto& it : m_windows)