LibWeb+WebDriver: Validate WebDriver proxy capabilities

We don't yet support a proxy configuration, but we can still validate
the capability received from the WebDriver client. We should also fail
to create a WebDriver session if a proxy configuration is present.
This commit is contained in:
Timothy Flynn 2025-02-07 13:52:41 -05:00 committed by Tim Flynn
parent 88eda159af
commit d873dc0744
Notes: github-actions[bot] 2025-02-10 16:34:53 +00:00
5 changed files with 178 additions and 34 deletions

View file

@ -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

View file

@ -11,6 +11,7 @@
#include <AK/Optional.h>
#include <LibWeb/Loader/UserAgent.h>
#include <LibWeb/WebDriver/Capabilities.h>
#include <LibWeb/WebDriver/Proxy.h>
#include <LibWeb/WebDriver/TimeoutsConfiguration.h>
#include <LibWeb/WebDriver/UserPrompt.h>
@ -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<JsonObject, Error> 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<void, Error> {
// 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) {

View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/JsonValue.h>
#include <LibURL/URL.h>
#include <LibWeb/WebDriver/Proxy.h>
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<void, Error> 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<void, Error> {
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<u8>())
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<JsonObject, Error> 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<void, Error> {
// 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;
}
}

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/JsonObject.h>
#include <LibWeb/WebDriver/Error.h>
namespace Web::WebDriver {
bool has_proxy_configuration();
void set_has_proxy_configuration(bool);
void reset_has_proxy_configuration();
ErrorOr<JsonObject, Error> deserialize_as_a_proxy(JsonValue const&);
}

View file

@ -16,6 +16,7 @@
#include <LibCore/StandardPaths.h>
#include <LibCore/System.h>
#include <LibWeb/Crypto/Crypto.h>
#include <LibWeb/WebDriver/Proxy.h>
#include <LibWeb/WebDriver/TimeoutsConfiguration.h>
#include <LibWeb/WebDriver/UserPrompt.h>
#include <unistd.h>
@ -35,11 +36,19 @@ ErrorOr<NonnullRefPtr<Session>> Session::create(NonnullRefPtr<Client> 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)