diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 07d1daea425..4e0a70eac0f 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -41,6 +41,7 @@ set(SOURCES ContentSecurityPolicy/Directives/DirectiveOperations.cpp ContentSecurityPolicy/Directives/Names.cpp ContentSecurityPolicy/Directives/SerializedDirective.cpp + ContentSecurityPolicy/BlockingAlgorithms.cpp ContentSecurityPolicy/Policy.cpp ContentSecurityPolicy/PolicyList.cpp ContentSecurityPolicy/SecurityPolicyViolationEvent.cpp diff --git a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp new file mode 100644 index 00000000000..c3af4811408 --- /dev/null +++ b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::ContentSecurityPolicy { + +// https://w3c.github.io/webappsec-csp/#does-resource-hint-violate-policy +[[nodiscard]] static GC::Ptr does_resource_hint_request_violate_policy(JS::Realm& realm, GC::Ref request, GC::Ref policy) +{ + // 1. Let defaultDirective be policy’s first directive whose name is "default-src". + auto default_directive_iterator = policy->directives().find_if([](auto const& directive) { + return directive->name() == Directives::Names::DefaultSrc; + }); + + // 2. If defaultDirective does not exist, return "Does Not Violate". + if (default_directive_iterator.is_end()) + return {}; + + // 3. For each directive of policy: + for (auto directive : policy->directives()) { + // 1. Let result be the result of executing directive’s pre-request check on request and policy. + auto result = directive->pre_request_check(realm, request, policy); + + // 2. If result is "Allowed", then return "Does Not Violate". + if (result == Directives::Directive::Result::Allowed) { + return {}; + } + } + + // 4. Return defaultDirective. + return *default_directive_iterator; +} + +// https://w3c.github.io/webappsec-csp/#does-request-violate-policy +[[nodiscard]] static GC::Ptr does_request_violate_policy(JS::Realm& realm, GC::Ref request, GC::Ref policy) +{ + // 1. If request’s initiator is "prefetch", then return the result of executing § 6.7.2.2 Does resource hint + // request violate policy? on request and policy. + if (request->initiator() == Fetch::Infrastructure::Request::Initiator::Prefetch) + return does_resource_hint_request_violate_policy(realm, request, policy); + + // 2. Let violates be "Does Not Violate". + GC::Ptr violates; + + // 3. For each directive of policy: + for (auto directive : policy->directives()) { + // 1. Let result be the result of executing directive’s pre-request check on request and policy. + auto result = directive->pre_request_check(realm, request, policy); + + // 2. If result is "Blocked", then let violates be directive. + if (result == Directives::Directive::Result::Blocked) { + violates = directive; + } + } + + // 4. Return violates. + return violates; +} + +Directives::Directive::Result should_request_be_blocked_by_content_security_policy(JS::Realm& realm, GC::Ref request) +{ + // 1. Let CSP list be request’s policy container's CSP list. + auto csp_list = request->policy_container().get>()->csp_list; + + // 2. Let result be "Allowed". + auto result = Directives::Directive::Result::Allowed; + + // 3. For each policy of CSP list: + for (auto policy : csp_list->policies()) { + // 1. If policy’s disposition is "report", then skip to the next policy. + if (policy->disposition() == Policy::Disposition::Report) + continue; + + // 2. Let violates be the result of executing § 6.7.2.1 Does request violate policy? on request and policy. + auto violates = does_request_violate_policy(realm, request, policy); + + // 3. If violates is not "Does Not Violate", then: + if (violates) { + // 1. Execute § 5.5 Report a violation on the result of executing § 2.4.2 Create a violation object for + // request, and policy. on request, and policy. + auto violation = Violation::create_a_violation_object_for_request_and_policy(realm, request, policy); + violation->report_a_violation(realm); + + // 2. Set result to "Blocked". + result = Directives::Directive::Result::Blocked; + } + } + + // 4. Return result. + return result; +} + +} diff --git a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h new file mode 100644 index 00000000000..572d0e8e630 --- /dev/null +++ b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::ContentSecurityPolicy { + +[[nodiscard]] Directives::Directive::Result should_request_be_blocked_by_content_security_policy(JS::Realm&, GC::Ref); + +} diff --git a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index 4d382b74974..47663360a4f 100644 --- a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -295,8 +296,7 @@ WebIDL::ExceptionOr> main_fetch(JS::Realm& realm, Infra // should request be blocked by Content Security Policy returns blocked, then set response to a network error. if (Infrastructure::block_bad_port(request) == Infrastructure::RequestOrResponseBlocking::Blocked || MixedContent::should_fetching_request_be_blocked_as_mixed_content(request) == Infrastructure::RequestOrResponseBlocking::Blocked - || false // FIXME: "should request be blocked by Content Security Policy returns blocked" - ) { + || ContentSecurityPolicy::should_request_be_blocked_by_content_security_policy(realm, request) == ContentSecurityPolicy::Directives::Directive::Result::Blocked) { response = Infrastructure::Response::network_error(vm, "Request was blocked"sv); } diff --git a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Requests.cpp b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Requests.cpp index c1fb681660b..4febb443950 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Requests.cpp +++ b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Requests.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include #include #include