ladybird/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp
Callum Law 71b039a721 LibWeb: Use document's global object in is_base_allowed_for_document
Previously we were using the document's window - this was both contrary
to spec and causing crashes when the document did not have a window (for
instance the `temp_document` in `HTMLParser::parse_html_fragment`.

This means we no longer crash when navigating between pages on
https://rocketlabcorp.com
2025-08-16 14:19:05 +02:00

672 lines
35 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2025, Luke Wilde <luke@ladybird.org>
* Copyright (c) 2025, Kenneth Myhra <kennethmyhra@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h>
#include <LibWeb/ContentSecurityPolicy/Directives/DirectiveOperations.h>
#include <LibWeb/ContentSecurityPolicy/Directives/KeywordSources.h>
#include <LibWeb/ContentSecurityPolicy/Directives/Names.h>
#include <LibWeb/ContentSecurityPolicy/PolicyList.h>
#include <LibWeb/ContentSecurityPolicy/Violation.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
#include <LibWeb/Fetch/Infrastructure/URL.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/HTML/WorkerGlobalScope.h>
#include <LibWeb/Infra/Strings.h>
#include <LibWeb/SRI/SRI.h>
#include <LibWeb/WebAssembly/WebAssembly.h>
namespace Web::ContentSecurityPolicy {
// https://w3c.github.io/webappsec-csp/#does-resource-hint-violate-policy
[[nodiscard]] static GC::Ptr<Directives::Directive> does_resource_hint_request_violate_policy(GC::Heap& heap, GC::Ref<Fetch::Infrastructure::Request const> request, GC::Ref<Policy const> policy)
{
// 1. Let defaultDirective be policys 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 directives pre-request check on request and policy.
auto result = directive->pre_request_check(heap, 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<Directives::Directive> does_request_violate_policy(GC::Heap& heap, GC::Ref<Fetch::Infrastructure::Request const> request, GC::Ref<Policy const> policy)
{
// 1. If requests 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(heap, request, policy);
// 2. Let violates be "Does Not Violate".
GC::Ptr<Directives::Directive> violates;
// 3. For each directive of policy:
for (auto directive : policy->directives()) {
// 1. Let result be the result of executing directives pre-request check on request and policy.
auto result = directive->pre_request_check(heap, 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;
}
// https://w3c.github.io/webappsec-csp/#report-for-request
void report_content_security_policy_violations_for_request(JS::Realm& realm, GC::Ref<Fetch::Infrastructure::Request> request)
{
// 1. Let CSP list be requests policy container's CSP list.
auto csp_list = request->policy_container().get<GC::Ref<HTML::PolicyContainer>>()->csp_list;
// 2. For each policy of CSP list:
for (auto policy : csp_list->policies()) {
// 1. If policys disposition is "enforce", then skip to the next policy.
if (policy->disposition() == Policy::Disposition::Enforce)
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.heap(), request, policy);
// 3. If violates is not "Does Not Violate", then 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.
if (violates) {
auto violation = Violation::create_a_violation_object_for_request_and_policy(realm, request, policy);
violation->report_a_violation(realm);
}
}
}
// https://w3c.github.io/webappsec-csp/#should-block-request
Directives::Directive::Result should_request_be_blocked_by_content_security_policy(JS::Realm& realm, GC::Ref<Fetch::Infrastructure::Request> request)
{
// 1. Let CSP list be requests policy container's CSP list.
auto csp_list = request->policy_container().get<GC::Ref<HTML::PolicyContainer>>()->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 policys 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.heap(), 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;
}
// https://w3c.github.io/webappsec-subresource-integrity/#should-request-be-blocked-by-integrity-policy
Directives::Directive::Result should_request_be_blocked_by_integrity_policy(GC::Ref<Fetch::Infrastructure::Request> request)
{
VERIFY(request->policy_container().has<GC::Ref<HTML::PolicyContainer>>());
// 1. Let policyContainer be requests policy container.
auto const& policy_container = request->policy_container().get<GC::Ref<HTML::PolicyContainer>>();
// 2. Let parsedMetadata be the result of calling parse metadata with requests integrity metadata.
auto parsed_metadata = MUST(SRI::parse_metadata(request->integrity_metadata()));
// 3. If parsedMetadata is not the empty set and requests mode is either "cors" or "same-origin", return "Allowed".
if (!parsed_metadata.is_empty() && (request->mode() == Fetch::Infrastructure::Request::Mode::CORS || request->mode() == Fetch::Infrastructure::Request::Mode::SameOrigin))
return Directives::Directive::Result::Allowed;
// 4. If requests url is local, return "Allowed".
if (Fetch::Infrastructure::is_local_url(request->url()))
return Directives::Directive::Result::Allowed;
// 5. Let policy be policyContainers integrity policy.
auto const& policy = policy_container->integrity_policy;
// 6. Let reportPolicy be policyContainers report only integrity policy.
auto const& report_policy = policy_container->report_only_integrity_policy;
// 7. If both policy and reportPolicy are empty integrity policys, return "Allowed".
if (policy.is_empty() && report_policy.is_empty())
return Directives::Directive::Result::Allowed;
// 8. Let global be requests clients global object.
auto& global = request->client()->global_object();
// 9. If global is not a Window nor a WorkerGlobalScope, return "Allowed".
if (!is<HTML::Window>(global) && !is<HTML::WorkerGlobalScope>(global))
return Directives::Directive::Result::Allowed;
// 10. Let block be a boolean, initially false.
bool block = false;
// FIXME: 11. Let reportBlock be a boolean, initially false.
[[maybe_unused]] auto report_block = false;
// 12. If policys sources contains "inline" and policys blocked destinations contains requests destination, set block to true.
if (policy.sources.contains_slow("inline"sv)
&& request->destination().has_value()
&& policy.blocked_destinations.contains_slow(request->destination().value()))
block = true;
// 13. If reportPolicys sources contains "inline" and reportPolicys blocked destinations contains requests destination, set reportBlock to true.
if (report_policy.sources.contains_slow("inline"sv)
&& request->destination().has_value()
&& report_policy.blocked_destinations.contains_slow(request->destination().value()))
report_block = true;
// FIXME: 14. If block is true or reportBlock is true, then report violation with request, block, reportBlock, policy and reportPolicy.
// 15. If block is true, then return "Blocked"; otherwise "Allowed".
return block ? Directives::Directive::Result::Blocked : Directives::Directive::Result::Allowed;
}
// https://w3c.github.io/webappsec-csp/#should-block-response
Directives::Directive::Result should_response_to_request_be_blocked_by_content_security_policy(JS::Realm& realm, GC::Ref<Fetch::Infrastructure::Response> response, GC::Ref<Fetch::Infrastructure::Request> request)
{
// 1. Let CSP list be requests policy container's CSP list.
auto csp_list = request->policy_container().get<GC::Ref<HTML::PolicyContainer>>()->csp_list;
// 2. Let result be "Allowed".
auto result = Directives::Directive::Result::Allowed;
// 3. For each policy of CSP list:
// Spec Note: This portion of the check verifies that the page can load the response. That is, that a Service
// Worker hasn't substituted a file which would violate the pages CSP.
for (auto policy : csp_list->policies()) {
// 1. For each directive of policy:
for (auto directive : policy->directives()) {
// 1. If the result of executing directives post-request check is "Blocked", then:
if (directive->post_request_check(realm.heap(), request, response, policy) == Directives::Directive::Result::Blocked) {
// 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. If policys disposition is "enforce", then set result to "Blocked".
if (policy->disposition() == Policy::Disposition::Enforce) {
result = Directives::Directive::Result::Blocked;
}
}
}
}
// 4. Return result.
return result;
}
// https://w3c.github.io/webappsec-csp/#should-block-navigation-request
Directives::Directive::Result should_navigation_request_of_type_be_blocked_by_content_security_policy(GC::Ref<Fetch::Infrastructure::Request> navigation_request, Directives::Directive::NavigationType navigation_type)
{
// 1. Let result be "Allowed".
auto result = Directives::Directive::Result::Allowed;
// 2. For each policy of navigation requests policy containers CSP list:
auto policy_container = navigation_request->policy_container().get<GC::Ref<HTML::PolicyContainer>>();
for (auto policy : policy_container->csp_list->policies()) {
// 1. For each directive of policy:
for (auto directive : policy->directives()) {
// 1. If directives pre-navigation check returns "Allowed" when executed upon navigation request, type, and policy skip to the next directive.
auto directive_result = directive->pre_navigation_check(navigation_request, navigation_type, policy);
if (directive_result == Directives::Directive::Result::Allowed)
continue;
// 2. Otherwise, let violation be the result of executing § 2.4.1 Create a violation object for global, policy, and directive on navigation requests
// clients global object, policy, and directives name.
auto& realm = navigation_request->client()->realm();
auto violation = Violation::create_a_violation_object_for_global_policy_and_directive(realm, navigation_request->client()->global_object(), policy, directive->name());
// 3. Set violations resource to navigation requests URL.
violation->set_resource(navigation_request->url());
// 4. Execute § 5.5 Report a violation on violation.
violation->report_a_violation(realm);
// 5. If policys disposition is "enforce", then set result to "Blocked".
if (policy->disposition() == Policy::Disposition::Enforce)
result = Directives::Directive::Result::Blocked;
}
}
// 3. If result is "Allowed", and if navigation requests current URLs scheme is javascript:
if (result == Directives::Directive::Result::Allowed && navigation_request->current_url().scheme() == "javascript"sv) {
// 1. For each policy of navigation requests policy containers CSP list:
VERIFY(navigation_request->policy_container().has<GC::Ref<HTML::PolicyContainer>>());
auto csp_list = navigation_request->policy_container().get<GC::Ref<HTML::PolicyContainer>>()->csp_list;
for (auto policy : csp_list->policies()) {
// 1. For each directive of policy:
for (auto directive : policy->directives()) {
// 1. Let directive-name be the result of executing § 6.8.2 Get the effective directive for inline
// checks on type.
// FIXME: File spec issue that the type should probably always be "navigation", as NavigationType would
// cause this algorithm to return null, making directive-name null, then piping directive-name
// into a Violation object where the directive name is defined to be a non-empty string.
// Other parts of the spec seem to refer to the "navigation" inline type as being for
// javascript: URLs. Additionally, this doesn't have an impact on the security decision here,
// just which directive is reported to have been violated.
auto directive_name = Directives::get_the_effective_directive_for_inline_checks(Directives::Directive::InlineType::Navigation);
// 2. If directives inline check returns "Allowed" when executed upon null, "navigation" and
// navigation requests current URL, skip to the next directive.
// FIXME: File spec issue that they forgot to pass in "policy" here.
// FIXME: File spec issue that current URL is a URL object and not a string, therefore they must use a
// spec operation to serialize the URL.
auto& realm = navigation_request->client()->realm();
auto serialized_url = navigation_request->current_url().to_string();
if (directive->inline_check(realm.heap(), nullptr, Directives::Directive::InlineType::Navigation, policy, serialized_url) == Directives::Directive::Result::Allowed)
continue;
// 3. Otherwise, let violation be the result of executing § 2.4.1 Create a violation object for global,
// policy, and directive on navigation requests clients global object, policy, and directive-name.
auto violation = Violation::create_a_violation_object_for_global_policy_and_directive(realm, navigation_request->client()->global_object(), policy, directive_name.to_string());
// 4. Set violations resource to navigation requests URL.
violation->set_resource(navigation_request->url());
// 5. Execute § 5.5 Report a violation on violation.
violation->report_a_violation(realm);
// 6. If policys disposition is "enforce", then set result to "Blocked".
if (policy->disposition() == Policy::Disposition::Enforce)
result = Directives::Directive::Result::Blocked;
}
}
}
// 4. Return result.
return result;
}
// https://w3c.github.io/webappsec-csp/#should-block-navigation-response
Directives::Directive::Result should_navigation_response_to_navigation_request_of_type_in_target_be_blocked_by_content_security_policy(
GC::Ptr<Fetch::Infrastructure::Request> navigation_request,
GC::Ref<Fetch::Infrastructure::Response> navigation_response,
GC::Ref<PolicyList> response_csp_list,
Directives::Directive::NavigationType navigation_type,
GC::Ref<HTML::Navigable> target)
{
// 1. Let result be "Allowed".
auto result = Directives::Directive::Result::Allowed;
// FIXME: File spec issue stating that the request can be null (e.g. from a srcdoc resource).
if (!navigation_request) {
dbgln("FIXME: Handle null navigation_request in navigation response Content Security Policy check.");
return result;
}
// 2. For each policy of response CSP list:
for (auto policy : response_csp_list->policies()) {
// Spec Note: Some directives (like frame-ancestors) allow a responses Content Security Policy to act on the navigation.
// 1. For each directive of policy:
for (auto directive : policy->directives()) {
// 1. If directives navigation response check returns "Allowed" when executed upon navigation request, type, navigation response, target, "response", and policy skip to the next directive.
auto directive_result = directive->navigation_response_check(*navigation_request, navigation_type, navigation_response, target, Directives::Directive::CheckType::Response, policy);
if (directive_result == Directives::Directive::Result::Allowed)
continue;
// 2. Otherwise, let violation be the result of executing § 2.4.1 Create a violation object for global, policy, and directive on null, policy, and directives name.
// Spec Note: We use null for the global object, as no global exists: we havent processed the navigation to create a Document yet.
// FIXME: What should the realm be here?
auto& realm = navigation_request->client()->realm();
auto violation = Violation::create_a_violation_object_for_global_policy_and_directive(realm, nullptr, policy, directive->name());
// 3. Set violations resource to navigation responses URL.
if (navigation_response->url().has_value()) {
violation->set_resource(navigation_response->url().value());
} else {
violation->set_resource(Empty {});
}
// 4. Execute § 5.5 Report a violation on violation.
violation->report_a_violation(realm);
// 5. If policys disposition is "enforce", then set result to "Blocked".
if (policy->disposition() == Policy::Disposition::Enforce)
result = Directives::Directive::Result::Blocked;
}
}
// 3. For each policy of navigation requests policy containers CSP list:
auto request_policy_container = navigation_request->policy_container().get<GC::Ref<HTML::PolicyContainer>>();
for (auto policy : request_policy_container->csp_list->policies()) {
// Spec Note: NOTE: Some directives in the navigation requests context (like frame-ancestors) need the response before acting on the navigation.
// 1. For each directive of policy:
for (auto directive : policy->directives()) {
// 1. If directives navigation response check returns "Allowed" when executed upon navigation request, type, navigation response, target, "source", and policy skip to the next directive.
auto directive_result = directive->navigation_response_check(*navigation_request, navigation_type, navigation_response, target, Directives::Directive::CheckType::Source, policy);
if (directive_result == Directives::Directive::Result::Allowed)
continue;
// 2. Otherwise, let violation be the result of executing § 2.4.1 Create a violation object for global, policy, and directive on navigation requests clients global object, policy, and directives name.
auto& realm = navigation_request->client()->realm();
auto violation = Violation::create_a_violation_object_for_global_policy_and_directive(realm, navigation_request->client()->global_object(), policy, directive->name());
// 3. Set violations resource to navigation requests URL.
violation->set_resource(navigation_request->url());
// 4. Execute § 5.5 Report a violation on violation.
violation->report_a_violation(realm);
// 5. If policys disposition is "enforce", then set result to "Blocked".
if (policy->disposition() == Policy::Disposition::Enforce)
result = Directives::Directive::Result::Blocked;
}
}
// 4. Return result.
return result;
}
// https://w3c.github.io/webappsec-csp/#should-block-inline
Directives::Directive::Result should_elements_inline_type_behavior_be_blocked_by_content_security_policy(JS::Realm& realm, GC::Ref<DOM::Element> element, Directives::Directive::InlineType type, String const& source)
{
// Spec Note: The valid values for type are "script", "script attribute", "style", and "style attribute".
VERIFY(type == Directives::Directive::InlineType::Script || type == Directives::Directive::InlineType::ScriptAttribute || type == Directives::Directive::InlineType::Style || type == Directives::Directive::InlineType::StyleAttribute);
// 1. Assert: element is not null.
// NOTE: Already done by only accepting a GC::Ref.
// 2. Let result be "Allowed".
auto result = Directives::Directive::Result::Allowed;
// 3. For each policy of elements Document's global objects CSP list:
auto& global_object = element->document().realm().global_object();
auto csp_list = PolicyList::from_object(global_object);
VERIFY(csp_list);
for (auto const policy : csp_list->policies()) {
// 1. For each directive of policys directive set:
for (auto const directive : policy->directives()) {
// 1. If directives inline check returns "Allowed" when executed upon element, type, policy and source,
// skip to the next directive.
if (directive->inline_check(realm.heap(), element, type, policy, source) == Directives::Directive::Result::Allowed)
continue;
// 2. Let directive-name be the result of executing § 6.8.2 Get the effective directive for inline checks
// on type.
auto directive_name = Directives::get_the_effective_directive_for_inline_checks(type);
// 3. Otherwise, let violation be the result of executing § 2.4.1 Create a violation object for global,
// policy, and directive on the current settings objects global object, policy, and directive-name.
// FIXME: File spec issue about using "current settings object" here, as it can run outside of a script
// context (for example, a just parsed inline script being prepared)
auto violation = Violation::create_a_violation_object_for_global_policy_and_directive(realm, global_object, policy, directive_name.to_string());
// 4. Set violations resource to "inline".
violation->set_resource(Violation::Resource::Inline);
// 5. Set violations element to element.
violation->set_element(element);
// 6. If directives value contains the expression "'report-sample'", then set violations sample to the
// substring of source containing its first 40 characters.
// FIXME: Should this be case insensitive?
auto maybe_report_sample = directive->value().find_if([](auto const& directive_value) {
return directive_value.equals_ignoring_ascii_case(Directives::KeywordSources::ReportSample);
});
if (!maybe_report_sample.is_end()) {
Utf8View source_view { source };
auto sample = source_view.unicode_substring_view(0, min(source_view.length(), 40));
violation->set_sample(String::from_utf8_without_validation(sample.as_string().bytes()));
}
// 7. Execute § 5.5 Report a violation on violation.
violation->report_a_violation(realm);
// 8. If policys disposition is "enforce", then set result to "Blocked".
if (policy->disposition() == Policy::Disposition::Enforce) {
result = Directives::Directive::Result::Blocked;
}
}
}
// 4. Return result.
return result;
}
// https://w3c.github.io/webappsec-csp/#can-compile-strings
JS::ThrowCompletionOr<void> ensure_csp_does_not_block_string_compilation(JS::Realm& realm, ReadonlySpan<String>, StringView, StringView code_string, JS::CompilationType, ReadonlySpan<JS::Value>, JS::Value)
{
// FIXME: 1. If compilationType is "TIMER", then:
// 1. Let sourceString be codeString.
StringView source_string = code_string;
// FIXME: 2. Else:
// FIXME: We don't do these two steps as we don't currently support Trusted Types.
// 3. Let result be "Allowed".
auto result = Directives::Directive::Result::Allowed;
// 4. Let global be realms global object.
auto& global = realm.global_object();
// 5. For each policy of globals CSP list:
auto csp_list = PolicyList::from_object(global);
VERIFY(csp_list);
for (auto const policy : csp_list->policies()) {
// 1. Let source-list be null.
Optional<Vector<String>> maybe_source_list;
// 2. If policy contains a directive whose name is "script-src", then set source-list to that directive's value.
auto maybe_script_src = policy->directives().find_if([](auto const& directive) {
return directive->name() == Directives::Names::ScriptSrc;
});
if (!maybe_script_src.is_end()) {
maybe_source_list = (*maybe_script_src)->value();
} else {
// Otherwise if policy contains a directive whose name is "default-src", then set source-list to that
// directives value.
auto maybe_default_src = policy->directives().find_if([](auto const& directive) {
return directive->name() == Directives::Names::DefaultSrc;
});
if (!maybe_default_src.is_end())
maybe_source_list = (*maybe_default_src)->value();
}
// 3. If source-list is not null, and does not contain a source expression which is an ASCII case-insensitive
// match for the string "'unsafe-eval'", then:
if (maybe_source_list.has_value()) {
auto const& source_list = maybe_source_list.value();
auto maybe_unsafe_eval = source_list.find_if([](auto const& directive_value) {
return directive_value.equals_ignoring_ascii_case(Directives::KeywordSources::UnsafeEval);
});
if (maybe_unsafe_eval.is_end()) {
// 1. Let violation be the result of executing § 2.4.1 Create a violation object for global, policy,
// and directive on global, policy, and "script-src".
auto script_src_string = Directives::Names::ScriptSrc.to_string();
auto violation = Violation::create_a_violation_object_for_global_policy_and_directive(realm, global, policy, script_src_string);
// 2. Set violations resource to "eval".
violation->set_resource(Violation::Resource::Eval);
// 3. If source-list contains the expression "'report-sample'", then set violations sample to the
// substring of sourceString containing its first 40 characters.
// FIXME: Should this be case insensitive?
auto maybe_report_sample = source_list.find_if([](auto const& directive_value) {
return directive_value.equals_ignoring_ascii_case(Directives::KeywordSources::ReportSample);
});
if (!maybe_report_sample.is_end()) {
Utf8View source_view { source_string };
auto sample = source_view.unicode_substring_view(0, min(source_view.length(), 40));
violation->set_sample(String::from_utf8_without_validation(sample.as_string().bytes()));
}
// 4. Execute § 5.5 Report a violation on violation.
violation->report_a_violation(realm);
// 5. If policys disposition is "enforce", then set result to "Blocked".
if (policy->disposition() == Policy::Disposition::Enforce)
result = Directives::Directive::Result::Blocked;
}
}
}
// 6. If result is "Blocked", throw an EvalError exception.
if (result == Directives::Directive::Result::Blocked) {
return realm.vm().throw_completion<JS::EvalError>("Blocked by Content Security Policy"sv);
}
return {};
}
// https://w3c.github.io/webappsec-csp/#can-compile-wasm-bytes
JS::ThrowCompletionOr<void> ensure_csp_does_not_block_wasm_byte_compilation(JS::Realm& realm)
{
// 1. Let global be realms global object.
auto& global = realm.global_object();
// 2. Let result be "Allowed".
auto result = Directives::Directive::Result::Allowed;
// 3. For each policy of globals CSP list:
auto csp_list = PolicyList::from_object(global);
VERIFY(csp_list);
for (auto const policy : csp_list->policies()) {
// 1. Let source-list be null.
Optional<Vector<String>> maybe_source_list;
// 2. If policy contains a directive whose name is "script-src", then set source-list to that directive's value.
auto maybe_script_src = policy->directives().find_if([](auto const& directive) {
return directive->name() == Directives::Names::ScriptSrc;
});
if (!maybe_script_src.is_end()) {
maybe_source_list = (*maybe_script_src)->value();
} else {
// Otherwise if policy contains a directive whose name is "default-src", then set source-list to that
// directives value.
auto maybe_default_src = policy->directives().find_if([](auto const& directive) {
return directive->name() == Directives::Names::DefaultSrc;
});
if (!maybe_default_src.is_end())
maybe_source_list = (*maybe_default_src)->value();
}
// 3. If source-list is non-null, and does not contain a source expression which is an ASCII case-insensitive
// match for the string "'unsafe-eval'", and does not contain a source expression which is an ASCII
// case-insensitive match for the string "'wasm-unsafe-eval'", then:
if (maybe_source_list.has_value()) {
auto const& source_list = maybe_source_list.value();
auto maybe_unsafe_eval = source_list.find_if([](auto const& directive_value) {
return directive_value.equals_ignoring_ascii_case(Directives::KeywordSources::UnsafeEval)
|| directive_value.equals_ignoring_ascii_case(Directives::KeywordSources::WasmUnsafeEval);
});
if (maybe_unsafe_eval.is_end()) {
// 1. Let violation be the result of executing § 2.4.1 Create a violation object for global, policy,
// and directive on global, policy, and "script-src".
auto script_src_string = Directives::Names::ScriptSrc.to_string();
auto violation = Violation::create_a_violation_object_for_global_policy_and_directive(realm, global, policy, script_src_string);
// 2. Set violations resource to "wasm-eval".
violation->set_resource(Violation::Resource::WasmEval);
// 3. Execute § 5.5 Report a violation on violation.
violation->report_a_violation(realm);
// 4. If policys disposition is "enforce", then set result to "Blocked".
if (policy->disposition() == Policy::Disposition::Enforce)
result = Directives::Directive::Result::Blocked;
}
}
}
// 4. If result is "Blocked", throw a WebAssembly.CompileError exception.
if (result == Directives::Directive::Result::Blocked) {
return realm.vm().throw_completion<WebAssembly::CompileError>("Blocked by Content Security Policy"sv);
}
return {};
}
// https://w3c.github.io/webappsec-csp/#allow-base-for-document
Directives::Directive::Result is_base_allowed_for_document(JS::Realm& realm, URL::URL const& base, GC::Ref<DOM::Document const> document)
{
// 1. For each policy of documents global objects csp list:
auto csp_list = PolicyList::from_object(document->realm().global_object());
VERIFY(csp_list);
for (auto const policy : csp_list->policies()) {
// 1. Let source list be null.
// NOTE: Not necessary.
// 2. If a directive whose name is "base-uri" is present in policys directive set, set source list to that
// directives value.
auto maybe_base_uri = policy->directives().find_if([](auto const& directive) {
return directive->name() == Directives::Names::BaseUri;
});
// 3. If source list is null, skip to the next policy.
if (maybe_base_uri.is_end())
continue;
auto const& source_list = (*maybe_base_uri)->value();
// 4. If the result of executing § 6.7.2.7 Does url match source list in origin with redirect count? on base,
// source list, policys self-origin, and 0 is "Does Not Match":
// Spec Note: We compare against the fallback base URL in order to deal correctly with things like an iframe
// srcdoc Document which has been sandboxed into an opaque origin.
if (Directives::does_url_match_source_list_in_origin_with_redirect_count(base, source_list, policy->self_origin(), 0) == Directives::MatchResult::DoesNotMatch) {
// 1. Let violation be the result of executing § 2.4.1 Create a violation object for global, policy, and
// directive on documents global object, policy, and "base-uri".
auto base_uri_string = Directives::Names::BaseUri.to_string();
auto violation = Violation::create_a_violation_object_for_global_policy_and_directive(realm, document->window(), policy, base_uri_string);
// 2. Set violations resource to "inline".
violation->set_resource(Violation::Resource::Inline);
// 3. Execute § 5.5 Report a violation on violation.
violation->report_a_violation(realm);
// 4. If policys disposition is "enforce", return "Blocked".
if (policy->disposition() == Policy::Disposition::Enforce)
return Directives::Directive::Result::Blocked;
}
}
// 2. Return "Allowed".
return Directives::Directive::Result::Allowed;
}
}