LibWeb/CSP: Implement the script-src directive

This commit is contained in:
Luke Wilde 2024-12-02 16:01:19 +00:00 committed by Andrew Kaster
commit 0cff47828d
Notes: github-actions[bot] 2025-07-09 21:53:59 +00:00
17 changed files with 839 additions and 14 deletions

View file

@ -49,6 +49,7 @@ enum class CompilationType {
DirectEval,
IndirectEval,
Function,
Timer,
};
class JS_API VM : public RefCounted<VM> {

View file

@ -25,6 +25,9 @@
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/SyntheticHostDefined.h>
#include <LibWeb/Bindings/WindowExposedInterfaces.h>
#include <LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h>
#include <LibWeb/ContentSecurityPolicy/Directives/KeywordSources.h>
#include <LibWeb/ContentSecurityPolicy/Directives/Names.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/CustomElements/CustomElementDefinition.h>
#include <LibWeb/HTML/EventNames.h>
@ -114,7 +117,19 @@ void initialize_main_thread_vm(AgentType type)
return {};
};
// FIXME: Implement 8.1.5.2 HostEnsureCanCompileStrings(callerRealm, calleeRealm), https://html.spec.whatwg.org/multipage/webappapis.html#hostensurecancompilestrings(callerrealm,-calleerealm)
// 8.1.6.2 HostEnsureCanCompileStrings(realm, parameterStrings, bodyString, codeString, compilationType, parameterArgs, bodyArg), https://html.spec.whatwg.org/multipage/webappapis.html#hostensurecancompilestrings(realm,-parameterstrings,-bodystring,-codestring,-compilationtype,-parameterargs,-bodyarg)
s_main_thread_vm->host_ensure_can_compile_strings = [](JS::Realm& realm, ReadonlySpan<String> parameter_strings, StringView body_string, StringView code_string, JS::CompilationType compilation_type, ReadonlySpan<JS::Value> parameter_args, JS::Value body_arg) -> JS::ThrowCompletionOr<void> {
// 1. Perform ? EnsureCSPDoesNotBlockStringCompilation(realm, parameterStrings, bodyString, codeString, compilationType, parameterArgs, bodyArg). [CSP]
return ContentSecurityPolicy::ensure_csp_does_not_block_string_compilation(realm, parameter_strings, body_string, code_string, compilation_type, parameter_args, body_arg);
};
// 8.1.6.3 HostGetCodeForEval(argument), https://html.spec.whatwg.org/multipage/webappapis.html#hostgetcodeforeval(argument)
s_main_thread_vm->host_get_code_for_eval = [](JS::Object const&) -> GC::Ptr<JS::PrimitiveString> {
// FIXME: 1. If argument is a TrustedScript object, then return argument's data.
// 2. Otherwise, return no-code.
return {};
};
// 8.1.5.3 HostPromiseRejectionTracker(promise, operation), https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
// https://whatpr.org/html/9893/webappapis.html#the-hostpromiserejectiontracker-implementation

View file

@ -55,6 +55,7 @@ set(SOURCES
ContentSecurityPolicy/Directives/MediaSourceDirective.cpp
ContentSecurityPolicy/Directives/Names.cpp
ContentSecurityPolicy/Directives/ObjectSourceDirective.cpp
ContentSecurityPolicy/Directives/ScriptSourceDirective.cpp
ContentSecurityPolicy/Directives/SerializedDirective.cpp
ContentSecurityPolicy/Directives/SourceExpression.cpp
ContentSecurityPolicy/Policy.cpp

View file

@ -6,11 +6,16 @@
#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/Infra/Strings.h>
#include <LibWeb/WebAssembly/WebAssembly.h>
namespace Web::ContentSecurityPolicy {
@ -322,4 +327,233 @@ Directives::Directive::Result should_navigation_response_to_navigation_request_o
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 {};
}
}

View file

@ -6,6 +6,7 @@
#pragma once
#include <LibJS/Runtime/VM.h>
#include <LibWeb/ContentSecurityPolicy/Directives/Directive.h>
namespace Web::ContentSecurityPolicy {
@ -22,4 +23,8 @@ Directives::Directive::Result should_navigation_response_to_navigation_request_o
Directives::Directive::NavigationType navigation_type,
GC::Ref<HTML::Navigable> target);
Directives::Directive::Result should_elements_inline_type_behavior_be_blocked_by_content_security_policy(JS::Realm&, GC::Ref<DOM::Element> element, Directives::Directive::InlineType type, String const& source);
JS::ThrowCompletionOr<void> ensure_csp_does_not_block_string_compilation(JS::Realm& realm, ReadonlySpan<String> parameter_strings, StringView body_string, StringView code_string, JS::CompilationType compilation_type, ReadonlySpan<JS::Value> parameter_args, JS::Value body_arg);
JS::ThrowCompletionOr<void> ensure_csp_does_not_block_wasm_byte_compilation(JS::Realm&);
}

View file

@ -15,6 +15,7 @@
#include <LibWeb/ContentSecurityPolicy/Directives/MediaSourceDirective.h>
#include <LibWeb/ContentSecurityPolicy/Directives/Names.h>
#include <LibWeb/ContentSecurityPolicy/Directives/ObjectSourceDirective.h>
#include <LibWeb/ContentSecurityPolicy/Directives/ScriptSourceDirective.h>
namespace Web::ContentSecurityPolicy::Directives {
@ -41,6 +42,9 @@ GC::Ref<Directive> create_directive(GC::Heap& heap, String name, Vector<String>
if (name == Names::ObjectSrc)
return heap.allocate<ObjectSourceDirective>(move(name), move(value));
if (name == Names::ScriptSrc)
return heap.allocate<ScriptSourceDirective>(move(name), move(value));
return heap.allocate<Directive>(move(name), move(value));
}

View file

@ -4,18 +4,26 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Base64.h>
#include <AK/FlyString.h>
#include <AK/HashMap.h>
#include <AK/Vector.h>
#include <LibCrypto/Hash/SHA2.h>
#include <LibWeb/ContentSecurityPolicy/Directives/DirectiveOperations.h>
#include <LibWeb/ContentSecurityPolicy/Directives/KeywordSources.h>
#include <LibWeb/ContentSecurityPolicy/Directives/Names.h>
#include <LibWeb/ContentSecurityPolicy/Directives/SourceExpression.h>
#include <LibWeb/DOM/Attr.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOM/NamedNodeMap.h>
#include <LibWeb/DOMURL/DOMURL.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
#include <LibWeb/Fetch/Infrastructure/URL.h>
#include <LibWeb/HTML/HTMLScriptElement.h>
#include <LibWeb/Infra/Strings.h>
#include <LibWeb/SRI/SRI.h>
#include <LibWeb/SVG/SVGElement.h>
namespace Web::ContentSecurityPolicy::Directives {
@ -604,4 +612,401 @@ MatchResult does_response_match_source_list(GC::Ref<Fetch::Infrastructure::Respo
return does_url_match_source_list_in_origin_with_redirect_count(response->url().value(), source_list, policy->self_origin(), request->redirect_count());
}
// https://w3c.github.io/webappsec-csp/#match-nonce-to-source-list
MatchResult does_nonce_match_source_list(String const& nonce, Vector<String> const& source_list)
{
// 1. Assert: source list is not null.
// Already done by only accept references.
// 2. If nonce is the empty string, return "Does Not Match".
if (nonce.is_empty())
return MatchResult::DoesNotMatch;
// 3. For each expression of source list:
for (auto const& expression : source_list) {
// 1. If expression matches the nonce-source grammar, and nonce is identical to expressions base64-value part,
// return "Matches".
auto nonce_source_match_result = parse_source_expression(Production::NonceSource, expression);
if (nonce_source_match_result.has_value()) {
VERIFY(nonce_source_match_result->base64_value.has_value());
if (nonce == nonce_source_match_result->base64_value.value())
return MatchResult::Matches;
}
}
// 4. Return "Does Not Match".
return MatchResult::DoesNotMatch;
}
// https://w3c.github.io/webappsec-csp/#match-integrity-metadata-to-source-list
// Spec Note: Here, we verify only whether the integrity metadata is a non-empty subset of the hash-source sources in
// source list. We rely on the browsers enforcement of Subresource Integrity [SRI] to block non-matching
// resources upon response.
static MatchResult does_integrity_metadata_match_source_list(String const& integrity_metadata, Vector<String> const& source_list)
{
// 1. Assert: source list is not null.
// NOTE: This is already done by passing in source_list by reference.
// 2. Let integrity expressions be the set of source expressions in source list that match the hash-source grammar.
Vector<SourceExpressionParseResult> integrity_expressions;
for (auto const& expression : source_list) {
auto hash_source_parse_result = parse_source_expression(Production::HashSource, expression);
if (hash_source_parse_result.has_value())
integrity_expressions.append(hash_source_parse_result.release_value());
}
// 3. If integrity expressions is empty, return "Does Not Match".
if (integrity_expressions.is_empty())
return MatchResult::DoesNotMatch;
// 4. Let integrity sources be the result of executing the algorithm defined in SRI § 3.3.3 Parse metadata. on
// integrity metadata. [SRI]
auto integrity_sources = MUST(SRI::parse_metadata(integrity_metadata));
// 5. If integrity sources is "no metadata" or an empty set, return "Does Not Match".
// FIXME: File a spec issue stating that this is targetting an older version of the SRI spec, which does not return
// "no metadata", but instead simply just returns an empty list if there is no metadata.
// The up-to-date spec is located at https://w3c.github.io/webappsec-subresource-integrity/
if (integrity_sources.is_empty())
return MatchResult::DoesNotMatch;
// 6. For each source of integrity sources:
for (auto const& source : integrity_sources) {
// 1. If integrity expressions does not contain a source expression whose hash-algorithm is an ASCII
// case-insensitive match for sources hash-algorithm, and whose base64-value is identical to sources
// base64-value, return "Does Not Match".
auto maybe_match = integrity_expressions.find_if([&source](auto const& integrity_expression) {
VERIFY(integrity_expression.hash_algorithm.has_value());
VERIFY(integrity_expression.base64_value.has_value());
return integrity_expression.hash_algorithm.value().equals_ignoring_ascii_case(source.algorithm)
&& integrity_expression.base64_value.value() == source.base64_value;
});
if (maybe_match.is_end())
return MatchResult::DoesNotMatch;
}
// 7. Return "Matches".
return MatchResult::Matches;
}
// https://w3c.github.io/webappsec-csp/#script-pre-request
Directive::Result script_directives_pre_request_check(GC::Ref<Fetch::Infrastructure::Request const> request, GC::Ref<Directive const> directive, GC::Ref<Policy const> policy)
{
// 1. If requests destination is script-like:
if (request->destination_is_script_like()) {
// 1. If the result of executing § 6.7.2.3 Does nonce match source list? on requests cryptographic nonce
// metadata and this directives value is "Matches", return "Allowed".
if (does_nonce_match_source_list(request->cryptographic_nonce_metadata(), directive->value()) == MatchResult::Matches)
return Directive::Result::Allowed;
// 2. If the result of executing § 6.7.2.4 Does integrity metadata match source list? on requests integrity
// metadata and this directives value is "Matches", return "Allowed".
if (does_integrity_metadata_match_source_list(request->integrity_metadata(), directive->value()) == MatchResult::Matches)
return Directive::Result::Allowed;
// 3. If directives value contains a source expression that is an ASCII case-insensitive match for the
// "'strict-dynamic'" keyword-source:
// Spec Note: "'strict-dynamic'" is explained in more detail in § 8.2 Usage of "'strict-dynamic'".
// https://w3c.github.io/webappsec-csp/#strict-dynamic-usage
auto maybe_strict_dynamic = directive->value().find_if([](auto const& directive_value) {
return directive_value.equals_ignoring_ascii_case(KeywordSources::StrictDynamic);
});
if (!maybe_strict_dynamic.is_end()) {
// 1. If the requests parser metadata is "parser-inserted", return "Blocked".
// Otherwise, return "Allowed".
if (request->parser_metadata() == Fetch::Infrastructure::Request::ParserMetadata::ParserInserted)
return Directive::Result::Blocked;
return Directive::Result::Allowed;
}
// 4. If the result of executing § 6.7.2.5 Does request match source list? on request, directives value, and
// policy, is "Does Not Match", return "Blocked".
if (does_request_match_source_list(request, directive->value(), policy) == MatchResult::DoesNotMatch)
return Directive::Result::Blocked;
}
// 2. Return "Allowed".
return Directive::Result::Allowed;
}
// https://w3c.github.io/webappsec-csp/#script-post-request
Directive::Result script_directives_post_request_check(GC::Ref<Fetch::Infrastructure::Request const> request, GC::Ref<Fetch::Infrastructure::Response const> response, GC::Ref<Directive const> directive, GC::Ref<Policy const> policy)
{
// 1. If requests destination is script-like:
if (request->destination_is_script_like()) {
// 1. If the result of executing § 6.7.2.3 Does nonce match source list? on requests cryptographic nonce
// metadata and this directives value is "Matches", return "Allowed".
if (does_nonce_match_source_list(request->cryptographic_nonce_metadata(), directive->value()) == MatchResult::Matches)
return Directive::Result::Allowed;
// 2. If the result of executing § 6.7.2.4 Does integrity metadata match source list? on requests integrity
// metadata and this directives value is "Matches", return "Allowed".
if (does_integrity_metadata_match_source_list(request->integrity_metadata(), directive->value()) == MatchResult::Matches)
return Directive::Result::Allowed;
// 3. If directives value contains "'strict-dynamic'":
// FIXME: Should this be case insensitive?
auto maybe_strict_dynamic = directive->value().find_if([](auto const& directive_value) {
return directive_value.equals_ignoring_ascii_case(KeywordSources::StrictDynamic);
});
if (!maybe_strict_dynamic.is_end()) {
// 1. If requests parser metadata is not "parser-inserted", return "Allowed".
// Otherwise, return "Blocked".
if (request->parser_metadata() != Fetch::Infrastructure::Request::ParserMetadata::ParserInserted)
return Directive::Result::Allowed;
return Directive::Result::Blocked;
}
// 4. If the result of executing § 6.7.2.6 Does response to request match source list? on response, request,
// directives value, and policy, is "Does Not Match", return "Blocked".
if (does_response_match_source_list(response, request, directive->value(), policy) == MatchResult::DoesNotMatch)
return Directive::Result::Blocked;
}
// 2. Return "Allowed".
return Directive::Result::Allowed;
}
enum class [[nodiscard]] AllowsResult {
DoesNotAllow,
Allows,
};
static AllowsResult does_a_source_list_allow_all_inline_behavior_for_type(Vector<String> const& source_list, Directive::InlineType type)
{
// 1. Let allow all inline be false.
bool allow_all_inline = false;
// 2. For each expression of list:
for (auto const& expression : source_list) {
// 1. If expression matches the nonce-source or hash-source grammar, return "Does Not Allow".
auto nonce_source_parse_result = parse_source_expression(Production::NonceSource, expression);
if (nonce_source_parse_result.has_value())
return AllowsResult::DoesNotAllow;
auto hash_source_parse_result = parse_source_expression(Production::HashSource, expression);
if (hash_source_parse_result.has_value())
return AllowsResult::DoesNotAllow;
// 2. If type is "script", "script attribute" or "navigation" and expression matches the keyword-source
// "'strict-dynamic'", return "Does Not Allow".
if (type == Directive::InlineType::Script || type == Directive::InlineType::ScriptAttribute || type == Directive::InlineType::Navigation) {
if (expression.equals_ignoring_ascii_case(KeywordSources::StrictDynamic))
return AllowsResult::DoesNotAllow;
}
// 3. If expression is an ASCII case-insensitive match for the keyword-source "'unsafe-inline'", set allow all
// inline to true.
if (expression.equals_ignoring_ascii_case(KeywordSources::UnsafeInline))
allow_all_inline = true;
}
// 3. If allow all inline is true, return "Allows". Otherwise, return "Does Not Allow".
return allow_all_inline ? AllowsResult::Allows : AllowsResult::DoesNotAllow;
}
enum class NonceableResult {
NotNonceable,
Nonceable,
};
// https://w3c.github.io/webappsec-csp/#is-element-nonceable
[[nodiscard]] static NonceableResult is_element_nonceable(GC::Ptr<DOM::Element const> element)
{
// SPEC ISSUE 7: This processing is meant to mitigate the risk of dangling markup attacks that steal the nonce from
// an existing element in order to load injected script. It is fairly expensive, however, as it
// requires that we walk through all attributes and their values in order to determine whether the
// script should execute. Here, we try to minimize the impact by doing this check only for script
// elements when a nonce is present, but we should probably consider this algorithm as "at risk"
// until we know its impact. [Issue #w3c/webappsec-csp#98] (https://github.com/w3c/webappsec-csp/issues/98)
// FIXME: See FIXME in `does_element_match_source_list_for_type_and_source`
if (!element)
return NonceableResult::NotNonceable;
// 1. If element does not have an attribute named "nonce", return "Not Nonceable".
if (!is<HTML::HTMLElement>(element.ptr()) && !is<SVG::SVGElement>(element.ptr()))
return NonceableResult::NotNonceable;
if (!element->has_attribute(HTML::AttributeNames::nonce))
return NonceableResult::NotNonceable;
// 2. If element is a script element, then for each attribute of elements attribute list:
// FIXME: File spec issue to ask if this should include SVGScriptElement.
if (is<HTML::HTMLScriptElement>(element.ptr())) {
for (size_t attribute_index = 0; attribute_index < element->attributes()->length(); ++attribute_index) {
auto const* attribute = element->attributes()->item(attribute_index);
VERIFY(attribute);
// 1. If attributes name contains an ASCII case-insensitive match for "<script" or "<style", return
// "Not Nonceable".
auto attribute_name = attribute->name().to_string();
if (attribute_name.contains("<script"sv, CaseSensitivity::CaseInsensitive) || attribute_name.contains("<style"sv, CaseSensitivity::CaseInsensitive))
return NonceableResult::NotNonceable;
// 2. If attributes value contains an ASCII case-insensitive match for "<script" or "<style", return
// "Not Nonceable".
auto const& attribute_value = attribute->value();
if (attribute_value.contains("<script"sv, CaseSensitivity::CaseInsensitive) || attribute_value.contains("<style"sv, CaseSensitivity::CaseInsensitive))
return NonceableResult::NotNonceable;
}
}
// 3. If element had a duplicate-attribute parse error during tokenization, return "Not Nonceable".
// SPEC ISSUE 6: We need some sort of hook in HTML to record this error if were planning on using it here.
// [Issue #whatwg/html#3257] (https://github.com/whatwg/html/issues/3257)
if (element->had_duplicate_attribute_during_tokenization())
return NonceableResult::NotNonceable;
// 4. Return "Nonceable".
return NonceableResult::Nonceable;
}
// https://w3c.github.io/webappsec-csp/#match-element-to-source-list
MatchResult does_element_match_source_list_for_type_and_source(GC::Ptr<DOM::Element const> element, Vector<String> const& source_list, Directive::InlineType type, String const& source)
{
// Spec Note: Regardless of the encoding of the document, source will be converted to UTF-8 before applying any
// hashing algorithms.
// 1. If § 6.7.3.2 Does a source list allow all inline behavior for type? returns "Allows" given list and type,
// return "Matches".
if (does_a_source_list_allow_all_inline_behavior_for_type(source_list, type) == AllowsResult::Allows)
return MatchResult::Matches;
// 2. If type is "script" or "style", and § 6.7.3.1 Is element nonceable? returns "Nonceable" when executed upon
// element:
// Spec Note: Nonces only apply to inline script and inline style, not to attributes of either element or to
// javascript: navigations.
// FIXME: File spec issue that this algorithm doesn't handle `element` being null, which is it when doing a
// javascript: URL navigation. For now, we say that the element is not nonceable if it's null, because
// we simply can't pull a nonce attribute value from a null element.
if ((type == Directive::InlineType::Script || type == Directive::InlineType::Style) && is_element_nonceable(element) == NonceableResult::Nonceable) {
// 1. For each expression of list:
for (auto const& expression : source_list) {
// 1. If expression matches the nonce-source grammar, and element has a nonce attribute whose value is
// expression's base64-value part, return "Matches".
auto nonce_source_parse_result = parse_source_expression(Production::NonceSource, expression);
if (nonce_source_parse_result.has_value()) {
VERIFY(element);
VERIFY(is<HTML::HTMLElement>(element.ptr()) || is<SVG::SVGElement>(element.ptr()));
String element_nonce;
if (is<HTML::HTMLElement>(element.ptr())) {
auto const& html_element = static_cast<HTML::HTMLElement const&>(*element);
element_nonce = html_element.nonce();
} else {
auto const& svg_element = as<SVG::SVGElement>(*element);
element_nonce = svg_element.nonce();
}
if (nonce_source_parse_result->base64_value == element_nonce)
return MatchResult::Matches;
}
}
}
// 3. Let unsafe-hashes flag be false.
bool unsafe_hashes_flag = false;
// 4. For each expression of list:
for (auto const& expression : source_list) {
// 1. If expression is an ASCII case-insensitive match for the keyword-source "'unsafe-hashes'", set
// unsafe-hashes flag to true. Break out of the loop.
if (expression.equals_ignoring_ascii_case(KeywordSources::UnsafeHashes)) {
unsafe_hashes_flag = true;
break;
}
}
// 5. If type is "script" or "style", or unsafe-hashes flag is true:
// NOTE: Hashes apply to inline script and inline style. If the "'unsafe-hashes'" source expression is present,
// they will also apply to event handlers, style attributes and javascript: navigations.
// SPEC ISSUE 8: This should handle 'strict-dynamic' for dynamically inserted inline scripts.
// [Issue #w3c/webappsec-csp#426] (https://github.com/w3c/webappsec-csp/issues/426)
if (type == Directive::InlineType::Script || type == Directive::InlineType::Style || unsafe_hashes_flag) {
// 1. Set source to the result of executing UTF-8 encode on the result of executing JavaScript string
// converting on source.
auto converted_source = MUST(Infra::convert_to_scalar_value_string(source));
// NOTE: converted_source is already UTF-8 encoded.
auto converted_source_bytes = converted_source.bytes();
// 2. For each expression of list:
for (auto const& expression : source_list) {
// 1. If expression matches the hash-source grammar:
auto hash_source_parse_result = parse_source_expression(Production::HashSource, expression);
if (hash_source_parse_result.has_value()) {
// 1. Let algorithm be null.
StringView algorithm;
// 2. If expressions hash-algorithm part is an ASCII case-insensitive match for "sha256", set
// algorithm to SHA-256.
VERIFY(hash_source_parse_result->hash_algorithm.has_value());
auto hash_algorithm_from_expression = hash_source_parse_result->hash_algorithm.value();
if (hash_algorithm_from_expression.equals_ignoring_ascii_case("sha256"sv))
algorithm = "SHA-256"sv;
// 3. If expressions hash-algorithm part is an ASCII case-insensitive match for "sha384", set
// algorithm to SHA-384.
if (hash_algorithm_from_expression.equals_ignoring_ascii_case("sha384"sv))
algorithm = "SHA-384"sv;
// 4. If expressions hash-algorithm part is an ASCII case-insensitive match for "sha512", set
// algorithm to SHA-512.
if (hash_algorithm_from_expression.equals_ignoring_ascii_case("sha512"sv))
algorithm = "SHA-512"sv;
// 5. If algorithm is not null:
if (!algorithm.is_null()) {
// 1. Let actual be the result of base64 encoding the result of applying algorithm to source.
auto apply_algorithm_to_source = [&] {
if (algorithm == "SHA-256"sv) {
auto result = ::Crypto::Hash::SHA256::hash(converted_source_bytes);
return MUST(encode_base64(result.bytes()));
}
if (algorithm == "SHA-384"sv) {
auto result = ::Crypto::Hash::SHA384::hash(converted_source_bytes);
return MUST(encode_base64(result.bytes()));
}
if (algorithm == "SHA-512"sv) {
auto result = ::Crypto::Hash::SHA512::hash(converted_source_bytes);
return MUST(encode_base64(result.bytes()));
}
VERIFY_NOT_REACHED();
};
auto actual = apply_algorithm_to_source();
// 2. Let expected be expressions base64-value part, with all '-' characters replaced with '+',
// and all '_' characters replaced with '/'.
// Spec Note: This replacement normalizes hashes expressed in base64url encoding into base64
// encoding for matching.
VERIFY(hash_source_parse_result->base64_value.has_value());
auto base64_value_string = MUST(String::from_utf8(hash_source_parse_result->base64_value.value()));
auto expected = MUST(base64_value_string.replace("-"sv, "+"sv, ReplaceMode::All));
expected = MUST(expected.replace("_"sv, "/"sv, ReplaceMode::All));
// 3. If actual is identical to expected, return "Matches".
if (actual == expected)
return MatchResult::Matches;
}
}
}
}
// 6. Return "Does Not Match".
return MatchResult::DoesNotMatch;
}
}

View file

@ -35,5 +35,11 @@ MatchResult does_url_match_source_list_in_origin_with_redirect_count(URL::URL co
MatchResult does_request_match_source_list(GC::Ref<Fetch::Infrastructure::Request const> request, Vector<String> const& source_list, GC::Ref<Policy const> policy);
MatchResult does_response_match_source_list(GC::Ref<Fetch::Infrastructure::Response const> response, GC::Ref<Fetch::Infrastructure::Request const> request, Vector<String> const& source_list, GC::Ref<Policy const> policy);
MatchResult does_nonce_match_source_list(String const& nonce, Vector<String> const& source_list);
Directive::Result script_directives_pre_request_check(GC::Ref<Fetch::Infrastructure::Request const> request, GC::Ref<Directive const> directive, GC::Ref<Policy const> policy);
Directive::Result script_directives_post_request_check(GC::Ref<Fetch::Infrastructure::Request const> request, GC::Ref<Fetch::Infrastructure::Response const> response, GC::Ref<Directive const> directive, GC::Ref<Policy const> policy);
MatchResult does_element_match_source_list_for_type_and_source(GC::Ptr<DOM::Element const> element, Vector<String> const& source_list, Directive::InlineType type, String const& source);
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2024, Luke Wilde <luke@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/ContentSecurityPolicy/Directives/DirectiveOperations.h>
#include <LibWeb/ContentSecurityPolicy/Directives/Names.h>
#include <LibWeb/ContentSecurityPolicy/Directives/ScriptSourceDirective.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
namespace Web::ContentSecurityPolicy::Directives {
GC_DEFINE_ALLOCATOR(ScriptSourceDirective);
ScriptSourceDirective::ScriptSourceDirective(String name, Vector<String> value)
: Directive(move(name), move(value))
{
}
// https://w3c.github.io/webappsec-csp/#script-src-pre-request
Directive::Result ScriptSourceDirective::pre_request_check(GC::Heap&, GC::Ref<Fetch::Infrastructure::Request const> request, GC::Ref<Policy const> policy) const
{
// 1. Let name be the result of executing § 6.8.1 Get the effective directive for request on request.
auto name = get_the_effective_directive_for_request(request);
// 2. If the result of executing § 6.8.4 Should fetch directive execute on name, script-src and policy is "No",
// return "Allowed".
if (should_fetch_directive_execute(name, Names::ScriptSrc, policy) == ShouldExecute::No)
return Result::Allowed;
// 3. Return the result of executing § 6.7.1.1 Script directives pre-request check on request, this directive,
// and policy.
return script_directives_pre_request_check(request, *this, policy);
}
// https://w3c.github.io/webappsec-csp/#script-src-post-request
Directive::Result ScriptSourceDirective::post_request_check(GC::Heap&, GC::Ref<Fetch::Infrastructure::Request const> request, GC::Ref<Fetch::Infrastructure::Response const> response, GC::Ref<Policy const> policy) const
{
// 1. Let name be the result of executing § 6.8.1 Get the effective directive for request on request.
auto name = get_the_effective_directive_for_request(request);
// 2. If the result of executing § 6.8.4 Should fetch directive execute on name, script-src and policy is "No",
// return "Allowed".
if (should_fetch_directive_execute(name, Names::ScriptSrc, policy) == ShouldExecute::No)
return Result::Allowed;
// 3. Return the result of executing § 6.7.1.2 Script directives post-request check on request, response, this
// directive, and policy.
return script_directives_post_request_check(request, response, *this, policy);
}
// https://w3c.github.io/webappsec-csp/#script-src-inline
Directive::Result ScriptSourceDirective::inline_check(GC::Heap&, GC::Ptr<DOM::Element const> element, InlineType type, GC::Ref<Policy const> policy, String const& source) const
{
// 1. Assert: element is not null or type is "navigation".
VERIFY(element || type == InlineType::Navigation);
// 2. Let name be the result of executing § 6.8.2 Get the effective directive for inline checks on type.
auto name = get_the_effective_directive_for_inline_checks(type);
// 3. If the result of executing § 6.8.4 Should fetch directive execute on name, script-src and policy is "No",
// return "Allowed".
if (should_fetch_directive_execute(name, Names::ScriptSrc, policy) == ShouldExecute::No)
return Result::Allowed;
// 4. If the result of executing § 6.7.3.3 Does element match source list for type and source? on element, this
// directives value, type, and source, is "Does Not Match", return "Blocked".
if (does_element_match_source_list_for_type_and_source(element, value(), type, source) == MatchResult::DoesNotMatch)
return Result::Blocked;
// 5. Return "Allowed".
return Result::Allowed;
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2024, Luke Wilde <luke@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/ContentSecurityPolicy/Directives/Directive.h>
namespace Web::ContentSecurityPolicy::Directives {
// https://w3c.github.io/webappsec-csp/#directive-script-src
class ScriptSourceDirective final : public Directive {
GC_CELL(ScriptSourceDirective, Directive)
GC_DECLARE_ALLOCATOR(ScriptSourceDirective);
public:
virtual ~ScriptSourceDirective() = default;
virtual Result pre_request_check(GC::Heap&, GC::Ref<Fetch::Infrastructure::Request const>, GC::Ref<Policy const>) const override;
virtual Result post_request_check(GC::Heap&, GC::Ref<Fetch::Infrastructure::Request const>, GC::Ref<Fetch::Infrastructure::Response const>, GC::Ref<Policy const>) const override;
virtual Result inline_check(GC::Heap&, GC::Ptr<DOM::Element const>, InlineType, GC::Ref<Policy const>, String const&) const override;
private:
ScriptSourceDirective(String name, Vector<String> value);
};
}

View file

@ -19,6 +19,7 @@
#include <LibWeb/Bindings/EventTargetPrototype.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/PrincipalHostDefined.h>
#include <LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h>
#include <LibWeb/DOM/AbortSignal.h>
#include <LibWeb/DOM/DOMEventListener.h>
#include <LibWeb/DOM/Document.h>
@ -776,7 +777,12 @@ void EventTarget::element_event_handler_attribute_changed(FlyString const& local
}
// 5. Otherwise:
// FIXME: 1. If the Should element's inline behavior be blocked by Content Security Policy? algorithm returns "Blocked" when executed upon element, "script attribute", and value, then return. [CSP]
// 1. If the Should element's inline behavior be blocked by Content Security Policy? algorithm returns "Blocked" when executed upon element, "script attribute", and value, then return. [CSP]
auto& this_as_element = as<DOM::Element>(*this);
if (ContentSecurityPolicy::should_elements_inline_type_behavior_be_blocked_by_content_security_policy(realm(), this_as_element, ContentSecurityPolicy::Directives::Directive::InlineType::ScriptAttribute, value.value()) == ContentSecurityPolicy::Directives::Directive::Result::Blocked) {
dbgln("EventTarget: Refusing to add inline event handler as it violates the Content Security Policy.");
return;
}
// 2. Let handlerMap be eventTarget's event handler map.
auto& handler_map = event_target->ensure_data().event_handler_map;

View file

@ -138,6 +138,7 @@ class ImageSourceDirective;
class ManifestSourceDirective;
class MediaSourceDirective;
class ObjectSourceDirective;
class ScriptSourceDirective;
struct SerializedDirective;
}

View file

@ -18,7 +18,7 @@ public:
[[nodiscard]] GC::Ref<DOMStringMap> dataset();
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#dom-noncedelement-nonce
String const& nonce() { return m_cryptographic_nonce; }
String const& nonce() const { return m_cryptographic_nonce; }
void set_nonce(String const& nonce) { m_cryptographic_nonce = nonce; }
void focus();

View file

@ -10,6 +10,7 @@
#include <LibTextCodec/Decoder.h>
#include <LibWeb/Bindings/HTMLScriptElementPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/ShadowRoot.h>
@ -274,8 +275,13 @@ void HTMLScriptElement::prepare_script()
return;
}
// FIXME: 19. If el does not have a src content attribute, and the Should element's inline behavior be blocked by Content Security Policy?
// algorithm returns "Blocked" when given el, "script", and source text, then return. [CSP]
// 19. If el does not have a src content attribute, and the Should element's inline behavior be blocked by Content Security Policy?
// algorithm returns "Blocked" when given el, "script", and source text, then return. [CSP]
if (!has_attribute(AttributeNames::src)
&& ContentSecurityPolicy::should_elements_inline_type_behavior_be_blocked_by_content_security_policy(realm(), *this, ContentSecurityPolicy::Directives::Directive::InlineType::Script, source_text) == ContentSecurityPolicy::Directives::Directive::Result::Blocked) {
dbgln("HTMLScriptElement: Refusing to run inline script because it violates the Content Security Policy.");
return;
}
// 20. If el has an event attribute and a for attribute, and el's type is "classic", then:
if (m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::event) && has_attribute(HTML::AttributeNames::for_)) {
@ -325,7 +331,8 @@ void HTMLScriptElement::prepare_script()
// 23. Let module script credentials mode be the CORS settings attribute credentials mode for el's crossorigin content attribute.
auto module_script_credential_mode = cors_settings_attribute_credentials_mode(m_crossorigin);
// FIXME: 24. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value.
// 24. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value.
auto cryptographic_nonce = m_cryptographic_nonce;
// 25. If el has an integrity attribute, then let integrity metadata be that attribute's value.
// Otherwise, let integrity metadata be the empty string.
@ -350,7 +357,7 @@ void HTMLScriptElement::prepare_script()
// credentials mode is module script credentials mode, referrer policy is referrer policy,
// and fetch priority is fetch priority.
ScriptFetchOptions options {
.cryptographic_nonce = {}, // FIXME
.cryptographic_nonce = move(cryptographic_nonce),
.integrity_metadata = move(integrity_metadata),
.parser_metadata = parser_metadata,
.credentials_mode = module_script_credential_mode,

View file

@ -15,6 +15,7 @@
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h>
#include <LibWeb/Crypto/Crypto.h>
#include <LibWeb/Fetch/FetchMethod.h>
#include <LibWeb/HTML/CanvasRenderingContext2D.h>
@ -342,7 +343,9 @@ i32 WindowOrWorkerGlobalScopeMixin::run_timer_initialization_steps(TimerHandler
timeout = 0;
// FIXME: 5. If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4.
// FIXME: 6. Let realm be global's relevant realm.
// 6. Let realm be global's relevant realm.
auto& realm = relevant_realm(this_impl());
// 7. Let initiating script be the active script.
auto const* initiating_script = Web::Bindings::active_script();
@ -352,7 +355,7 @@ i32 WindowOrWorkerGlobalScopeMixin::run_timer_initialization_steps(TimerHandler
// FIXME 8. Let uniqueHandle be null.
// 9. Let task be a task that runs the following substeps:
auto task = GC::create_function(vm.heap(), Function<void()>([this, handler = move(handler), timeout, arguments = move(arguments), repeat, id, initiating_script, previous_id]() {
auto task = GC::create_function(vm.heap(), Function<void()>([this, handler = move(handler), timeout, arguments = move(arguments), repeat, id, initiating_script, previous_id, &vm, &realm]() {
// FIXME: 1. Assert: uniqueHandle is a unique internal value, not null.
// 2. If id does not exist in global's map of setTimeout and setInterval IDs, then abort these steps.
@ -362,10 +365,11 @@ i32 WindowOrWorkerGlobalScopeMixin::run_timer_initialization_steps(TimerHandler
// FIXME: 3. If global's map of setTimeout and setInterval IDs[id] does not equal uniqueHandle, then abort these steps.
// FIXME: 4. Record timing info for timer handler given handler, global's relevant settings object, and repeat.
handler.visit(
bool continue_ = handler.visit(
// 5. If handler is a Function, then invoke handler given arguments and "report", and with callback this value set to thisArg.
[&](GC::Root<WebIDL::CallbackType> const& callback) {
(void)WebIDL::invoke_callback(*callback, &this_impl(), WebIDL::ExceptionBehavior::Report, arguments);
return true;
},
// 6. Otherwise:
[&](String const& source) {
@ -383,8 +387,14 @@ i32 WindowOrWorkerGlobalScopeMixin::run_timer_initialization_steps(TimerHandler
// FIXME: 4. Set handler to the result of invoking the Get Trusted Type compliant string algorithm with TrustedScript, global, handler, sink, and "script".
}
// FIXME: 2. Assert: handler is a string.
// FIXME: 3. Perform EnsureCSPDoesNotBlockStringCompilation(realm, « », handler, handler, timer, « », handler). If this throws an exception, catch it, report it for global, and abort these steps.
// 2. Assert: handler is a string.
// 3. Perform EnsureCSPDoesNotBlockStringCompilation(realm, « », handler, handler, timer, « », handler).
// If this throws an exception, catch it, report it for global, and abort these steps.
auto handler_primitive_string = JS::PrimitiveString::create(vm, source);
if (auto result = ContentSecurityPolicy::ensure_csp_does_not_block_string_compilation(realm, {}, source, source, JS::CompilationType::Timer, {}, handler_primitive_string); result.is_throw_completion()) {
report_exception(result, realm);
return false;
}
// 4. Let settings object be global's relevant settings object.
auto& settings_object = relevant_settings_object(this_impl());
@ -415,8 +425,12 @@ i32 WindowOrWorkerGlobalScopeMixin::run_timer_initialization_steps(TimerHandler
// 9. Run the classic script script.
(void)script->run();
return true;
});
if (!continue_)
return;
// 7. If id does not exist in global's map of setTimeout and setInterval IDs, then abort these steps.
if (!m_timers.contains(id))
return;

View file

@ -22,6 +22,7 @@
#include <LibWasm/AbstractMachine/Validator.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ResponsePrototype.h>
#include <LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h>
#include <LibWeb/Fetch/Response.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
@ -149,6 +150,7 @@ WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> compile_streaming(JS::VM& vm, GC::
}
// https://webassembly.github.io/spec/js-api/#dom-webassembly-instantiate
// https://webassembly.github.io/content-security-policy/js-api/#dom-webassembly-instantiate
WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> instantiate(JS::VM& vm, GC::Root<WebIDL::BufferSource>& bytes, Optional<GC::Root<JS::Object>>& import_object_handle)
{
auto& realm = *vm.current_realm();
@ -160,10 +162,13 @@ WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> instantiate(JS::VM& vm, GC::Root<W
return WebIDL::create_rejected_promise_from_exception(realm, vm.throw_completion<JS::InternalError>(vm.error_message(JS::VM::ErrorMessage::OutOfMemory)));
}
// 2. Asynchronously compile a WebAssembly module from stableBytes and let promiseOfModule be the result.
// 2. Perform HostEnsureCanCompileWasmBytes()
TRY(Detail::host_ensure_can_compile_wasm_bytes(vm));
// 3. Asynchronously compile a WebAssembly module from stableBytes and let promiseOfModule be the result.
auto promise_of_module = asynchronously_compile_webassembly_module(vm, stable_bytes.release_value());
// 3. Instantiate promiseOfModule with imports importObject and return the result.
// 4. Instantiate promiseOfModule with imports importObject and return the result.
GC::Ptr<JS::Object> const import_object = import_object_handle.has_value() ? import_object_handle.value().ptr() : nullptr;
return instantiate_promise_of_module(vm, promise_of_module, import_object);
}
@ -410,8 +415,11 @@ JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS
}
// // https://webassembly.github.io/spec/js-api/#compile-a-webassembly-module
// https://webassembly.github.io/content-security-policy/js-api/#compile-a-webassembly-module
JS::ThrowCompletionOr<NonnullRefPtr<CompiledWebAssemblyModule>> compile_a_webassembly_module(JS::VM& vm, ByteBuffer data)
{
TRY(host_ensure_can_compile_wasm_bytes(vm));
FixedMemoryStream stream { data.bytes() };
auto module_result = Wasm::Module::parse(stream);
if (module_result.is_error()) {
@ -626,6 +634,18 @@ JS::Value to_js_value(JS::VM& vm, Wasm::Value& wasm_value, Wasm::ValueType type)
VERIFY_NOT_REACHED();
}
// https://webassembly.github.io/content-security-policy/js-api/#abstract-opdef-hostensurecancompilewasmbytes
JS::ThrowCompletionOr<void> host_ensure_can_compile_wasm_bytes(JS::VM& vm)
{
// 1. Let realm be the current Realm.
auto& realm = *vm.current_realm();
// 2. Perform EnsureCSPDoesNotBlockWasmByteCompilation(realm)
// This algorithm does not return a value, but raises a CompileError exception if the operation cannot complete
// successfully.
return ContentSecurityPolicy::ensure_csp_does_not_block_wasm_byte_compilation(realm);
}
}
// https://webassembly.github.io/spec/js-api/#asynchronously-compile-a-webassembly-module

View file

@ -94,6 +94,7 @@ JS::NativeFunction* create_native_function(JS::VM&, Wasm::FunctionAddress addres
JS::ThrowCompletionOr<Wasm::Value> to_webassembly_value(JS::VM&, JS::Value value, Wasm::ValueType const& type);
Wasm::Value default_webassembly_value(JS::VM&, Wasm::ValueType type);
JS::Value to_js_value(JS::VM&, Wasm::Value& wasm_value, Wasm::ValueType type);
JS::ThrowCompletionOr<void> host_ensure_can_compile_wasm_bytes(JS::VM&);
extern HashMap<GC::Ptr<JS::Object>, WebAssemblyCache> s_caches;