| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/frame/csp/csp_directive_list.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "third_party/blink/renderer/bindings/core/v8/source_location.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/space_split_string.h" |
| #include "third_party/blink/renderer/core/execution_context/security_context.h" |
| #include "third_party/blink/renderer/core/frame/deprecation.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/use_counter.h" |
| #include "third_party/blink/renderer/core/html/html_script_element.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/platform/crypto.h" |
| #include "third_party/blink/renderer/platform/network/content_security_policy_parsers.h" |
| #include "third_party/blink/renderer/platform/weborigin/kurl.h" |
| #include "third_party/blink/renderer/platform/wtf/text/base64.h" |
| #include "third_party/blink/renderer/platform/wtf/text/parsing_utilities.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| String GetSha256String(const String& content) { |
| DigestValue digest; |
| StringUTF8Adaptor utf8_content(content); |
| bool digest_success = ComputeDigest(kHashAlgorithmSha256, utf8_content.Data(), |
| utf8_content.length(), digest); |
| if (!digest_success) { |
| return "sha256-..."; |
| } |
| |
| return "sha256-" + Base64Encode(reinterpret_cast<char*>(digest.data()), |
| digest.size(), kBase64DoNotInsertLFs); |
| } |
| |
| ContentSecurityPolicyHashAlgorithm ConvertHashAlgorithmToCSPHashAlgorithm( |
| IntegrityAlgorithm algorithm) { |
| switch (algorithm) { |
| case IntegrityAlgorithm::kSha256: |
| return kContentSecurityPolicyHashAlgorithmSha256; |
| case IntegrityAlgorithm::kSha384: |
| return kContentSecurityPolicyHashAlgorithmSha384; |
| case IntegrityAlgorithm::kSha512: |
| return kContentSecurityPolicyHashAlgorithmSha512; |
| case IntegrityAlgorithm::kEd25519: |
| return kContentSecurityPolicyHashAlgorithmEd25519; |
| } |
| NOTREACHED(); |
| return kContentSecurityPolicyHashAlgorithmNone; |
| } |
| |
| // IntegrityMetadata (from SRI) has base64-encoded digest values, but CSP uses |
| // binary format. This converts from the former to the latter. |
| bool ParseBase64Digest(String base64, DigestValue* hash) { |
| Vector<char> hash_vector; |
| // We accept base64url-encoded data here by normalizing it to base64. |
| if (!Base64Decode(NormalizeToBase64(base64), hash_vector)) |
| return false; |
| if (hash_vector.IsEmpty() || hash_vector.size() > kMaxDigestSize) |
| return false; |
| hash->Append(reinterpret_cast<uint8_t*>(hash_vector.data()), |
| hash_vector.size()); |
| return true; |
| } |
| |
| } // namespace |
| |
| CSPDirectiveList::CSPDirectiveList(ContentSecurityPolicy* policy, |
| ContentSecurityPolicyHeaderType type, |
| ContentSecurityPolicyHeaderSource source) |
| : policy_(policy), |
| header_type_(type), |
| header_source_(source), |
| has_sandbox_policy_(false), |
| strict_mixed_content_checking_enforced_(false), |
| upgrade_insecure_requests_(false), |
| treat_as_public_address_(false), |
| require_sri_for_(RequireSRIForToken::kNone), |
| use_reporting_api_(false) {} |
| |
| CSPDirectiveList* CSPDirectiveList::Create( |
| ContentSecurityPolicy* policy, |
| const UChar* begin, |
| const UChar* end, |
| ContentSecurityPolicyHeaderType type, |
| ContentSecurityPolicyHeaderSource source, |
| bool should_parse_wasm_eval) { |
| CSPDirectiveList* directives = new CSPDirectiveList(policy, type, source); |
| directives->Parse(begin, end, should_parse_wasm_eval); |
| |
| if (!directives->CheckEval(directives->OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kScriptSrc))) { |
| String message = |
| "Refused to evaluate a string as JavaScript because 'unsafe-eval' is " |
| "not an allowed source of script in the following Content Security " |
| "Policy directive: \"" + |
| directives |
| ->OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kScriptSrc) |
| ->GetText() + |
| "\".\n"; |
| directives->SetEvalDisabledErrorMessage(message); |
| } |
| |
| if (directives->IsReportOnly() && |
| source != kContentSecurityPolicyHeaderSourceMeta && |
| directives->ReportEndpoints().IsEmpty()) |
| policy->ReportMissingReportURI(String(begin, end - begin)); |
| |
| return directives; |
| } |
| |
| void CSPDirectiveList::ReportViolation( |
| const String& directive_text, |
| const ContentSecurityPolicy::DirectiveType effective_type, |
| const String& console_message, |
| const KURL& blocked_url, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| String message = |
| IsReportOnly() ? "[Report Only] " + console_message : console_message; |
| policy_->LogToConsole(ConsoleMessage::Create(kSecurityMessageSource, |
| kErrorMessageLevel, message)); |
| policy_->ReportViolation(directive_text, effective_type, message, blocked_url, |
| report_endpoints_, use_reporting_api_, header_, |
| header_type_, ContentSecurityPolicy::kURLViolation, |
| std::unique_ptr<SourceLocation>(), |
| nullptr, // localFrame |
| redirect_status); |
| } |
| |
| void CSPDirectiveList::ReportViolationWithFrame( |
| const String& directive_text, |
| const ContentSecurityPolicy::DirectiveType effective_type, |
| const String& console_message, |
| const KURL& blocked_url, |
| LocalFrame* frame) const { |
| String message = |
| IsReportOnly() ? "[Report Only] " + console_message : console_message; |
| policy_->LogToConsole(ConsoleMessage::Create(kSecurityMessageSource, |
| kErrorMessageLevel, message), |
| frame); |
| policy_->ReportViolation(directive_text, effective_type, message, blocked_url, |
| report_endpoints_, use_reporting_api_, header_, |
| header_type_, ContentSecurityPolicy::kURLViolation, |
| std::unique_ptr<SourceLocation>(), frame); |
| } |
| |
| void CSPDirectiveList::ReportViolationWithLocation( |
| const String& directive_text, |
| const ContentSecurityPolicy::DirectiveType effective_type, |
| const String& console_message, |
| const KURL& blocked_url, |
| const String& context_url, |
| const WTF::OrdinalNumber& context_line, |
| Element* element, |
| const String& source) const { |
| String message = |
| IsReportOnly() ? "[Report Only] " + console_message : console_message; |
| std::unique_ptr<SourceLocation> source_location = |
| SourceLocation::Capture(context_url, context_line.OneBasedInt(), 0); |
| policy_->LogToConsole(ConsoleMessage::Create(kSecurityMessageSource, |
| kErrorMessageLevel, message, |
| source_location->Clone())); |
| policy_->ReportViolation(directive_text, effective_type, message, blocked_url, |
| report_endpoints_, use_reporting_api_, header_, |
| header_type_, |
| ContentSecurityPolicy::kInlineViolation, |
| std::move(source_location), nullptr, // localFrame |
| RedirectStatus::kNoRedirect, element, source); |
| } |
| |
| void CSPDirectiveList::ReportEvalViolation( |
| const String& directive_text, |
| const ContentSecurityPolicy::DirectiveType effective_type, |
| const String& message, |
| const KURL& blocked_url, |
| ScriptState* script_state, |
| const ContentSecurityPolicy::ExceptionStatus exception_status, |
| const String& content) const { |
| String report_message = IsReportOnly() ? "[Report Only] " + message : message; |
| // Print a console message if it won't be redundant with a |
| // JavaScript exception that the caller will throw. (Exceptions will |
| // never get thrown in report-only mode because the caller won't see |
| // a violation.) |
| if (IsReportOnly() || |
| exception_status == ContentSecurityPolicy::kWillNotThrowException) { |
| ConsoleMessage* console_message = ConsoleMessage::Create( |
| kSecurityMessageSource, kErrorMessageLevel, report_message); |
| policy_->LogToConsole(console_message); |
| } |
| policy_->ReportViolation(directive_text, effective_type, message, blocked_url, |
| report_endpoints_, use_reporting_api_, header_, |
| header_type_, ContentSecurityPolicy::kEvalViolation, |
| std::unique_ptr<SourceLocation>(), nullptr, |
| RedirectStatus::kFollowedRedirect, nullptr, content); |
| } |
| |
| bool CSPDirectiveList::CheckEval(SourceListDirective* directive) const { |
| return !directive || directive->AllowEval(); |
| } |
| |
| bool CSPDirectiveList::CheckWasmEval(SourceListDirective* directive) const { |
| return !directive || directive->AllowWasmEval(); |
| } |
| |
| bool CSPDirectiveList::IsMatchingNoncePresent(SourceListDirective* directive, |
| const String& nonce) const { |
| return directive && directive->AllowNonce(nonce); |
| } |
| |
| bool CSPDirectiveList::AreAllMatchingHashesPresent( |
| SourceListDirective* directive, |
| const IntegrityMetadataSet& hashes) const { |
| if (!directive || hashes.IsEmpty()) |
| return false; |
| for (const std::pair<String, IntegrityAlgorithm>& hash : hashes) { |
| // Convert the hash from integrity metadata format to CSP format. |
| CSPHashValue csp_hash; |
| csp_hash.first = ConvertHashAlgorithmToCSPHashAlgorithm(hash.second); |
| if (!ParseBase64Digest(hash.first, &csp_hash.second)) |
| return false; |
| // All integrity hashes must be listed in the CSP. |
| if (!directive->AllowHash(csp_hash)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool CSPDirectiveList::CheckHash(SourceListDirective* directive, |
| const CSPHashValue& hash_value) const { |
| return !directive || directive->AllowHash(hash_value); |
| } |
| |
| bool CSPDirectiveList::CheckUnsafeHashesAllowed( |
| SourceListDirective* directive) const { |
| return !directive || directive->AllowUnsafeHashes(); |
| } |
| |
| bool CSPDirectiveList::CheckDynamic(SourceListDirective* directive) const { |
| return !directive || directive->AllowDynamic(); |
| } |
| |
| void CSPDirectiveList::ReportMixedContent( |
| const KURL& mixed_url, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| if (StrictMixedContentChecking()) { |
| policy_->ReportViolation( |
| ContentSecurityPolicy::GetDirectiveName( |
| ContentSecurityPolicy::DirectiveType::kBlockAllMixedContent), |
| ContentSecurityPolicy::DirectiveType::kBlockAllMixedContent, String(), |
| mixed_url, report_endpoints_, use_reporting_api_, header_, header_type_, |
| ContentSecurityPolicy::kURLViolation, std::unique_ptr<SourceLocation>(), |
| nullptr, // contextFrame, |
| redirect_status); |
| } |
| } |
| |
| bool CSPDirectiveList::CheckSource( |
| SourceListDirective* directive, |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| // If |url| is empty, fall back to the policy URL to ensure that <object>'s |
| // without a `src` can be blocked/allowed, as they can still load plugins |
| // even though they don't actually have a URL. |
| return !directive || directive->Allows(url.IsEmpty() ? policy_->Url() : url, |
| redirect_status); |
| } |
| |
| bool CSPDirectiveList::CheckAncestors(SourceListDirective* directive, |
| LocalFrame* frame) const { |
| if (!frame || !directive) |
| return true; |
| |
| for (Frame* current = frame->Tree().Parent(); current; |
| current = current->Tree().Parent()) { |
| // The |current| frame might be a remote frame which has no URL, so use |
| // its origin instead. This should suffice for this check since it |
| // doesn't do path comparisons. See https://crbug.com/582544. |
| // |
| // TODO(mkwst): Move this check up into the browser process. See |
| // https://crbug.com/555418. |
| KURL url(NullURL(), |
| current->GetSecurityContext()->GetSecurityOrigin()->ToString()); |
| if (!directive->Allows(url, ResourceRequest::RedirectStatus::kNoRedirect)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool CSPDirectiveList::CheckRequestWithoutIntegrity( |
| WebURLRequest::RequestContext context) const { |
| if (require_sri_for_ == RequireSRIForToken::kNone) |
| return true; |
| // SRI specification |
| // (https://w3c.github.io/webappsec-subresource-integrity/#apply-algorithm-to-request) |
| // says to match token with request's destination with the token. |
| // Keep this logic aligned with ContentSecurityPolicy::allowRequest |
| if ((require_sri_for_ & RequireSRIForToken::kScript) && |
| (context == WebURLRequest::kRequestContextScript || |
| context == WebURLRequest::kRequestContextImport || |
| context == WebURLRequest::kRequestContextServiceWorker || |
| context == WebURLRequest::kRequestContextSharedWorker || |
| context == WebURLRequest::kRequestContextWorker)) { |
| return false; |
| } |
| if ((require_sri_for_ & RequireSRIForToken::kStyle) && |
| context == WebURLRequest::kRequestContextStyle) |
| return false; |
| return true; |
| } |
| |
| bool CSPDirectiveList::CheckRequestWithoutIntegrityAndReportViolation( |
| WebURLRequest::RequestContext context, |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| if (CheckRequestWithoutIntegrity(context)) |
| return true; |
| String resource_type; |
| switch (context) { |
| case WebURLRequest::kRequestContextScript: |
| case WebURLRequest::kRequestContextImport: |
| resource_type = "script"; |
| break; |
| case WebURLRequest::kRequestContextStyle: |
| resource_type = "stylesheet"; |
| break; |
| case WebURLRequest::kRequestContextServiceWorker: |
| resource_type = "service worker"; |
| break; |
| case WebURLRequest::kRequestContextSharedWorker: |
| resource_type = "shared worker"; |
| break; |
| case WebURLRequest::kRequestContextWorker: |
| resource_type = "worker"; |
| break; |
| default: |
| break; |
| } |
| |
| ReportViolation(ContentSecurityPolicy::GetDirectiveName( |
| ContentSecurityPolicy::DirectiveType::kRequireSRIFor), |
| ContentSecurityPolicy::DirectiveType::kRequireSRIFor, |
| "Refused to load the " + resource_type + " '" + |
| url.ElidedString() + |
| "' because 'require-sri-for' directive requires " |
| "integrity attribute be present for all " + |
| resource_type + "s.", |
| url, redirect_status); |
| return DenyIfEnforcingPolicy(); |
| } |
| |
| bool CSPDirectiveList::AllowRequestWithoutIntegrity( |
| WebURLRequest::RequestContext context, |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) |
| return CheckRequestWithoutIntegrityAndReportViolation(context, url, |
| redirect_status); |
| return DenyIfEnforcingPolicy() || CheckRequestWithoutIntegrity(context); |
| } |
| |
| bool CSPDirectiveList::CheckMediaType(MediaListDirective* directive, |
| const String& type, |
| const String& type_attribute) const { |
| if (!directive) |
| return true; |
| if (type_attribute.IsEmpty() || type_attribute.StripWhiteSpace() != type) |
| return false; |
| return directive->Allows(type); |
| } |
| |
| bool CSPDirectiveList::CheckEvalAndReportViolation( |
| SourceListDirective* directive, |
| const String& console_message, |
| ScriptState* script_state, |
| ContentSecurityPolicy::ExceptionStatus exception_status, |
| const String& content) const { |
| if (CheckEval(directive)) |
| return true; |
| |
| String suffix = String(); |
| if (directive == default_src_) |
| suffix = |
| " Note that 'script-src' was not explicitly set, so 'default-src' is " |
| "used as a fallback."; |
| |
| ReportEvalViolation( |
| directive->GetText(), ContentSecurityPolicy::DirectiveType::kScriptSrc, |
| console_message + "\"" + directive->GetText() + "\"." + suffix + "\n", |
| KURL(), script_state, exception_status, |
| directive->AllowReportSample() ? content : g_empty_string); |
| if (!IsReportOnly()) { |
| policy_->ReportBlockedScriptExecutionToInspector(directive->GetText()); |
| return false; |
| } |
| return true; |
| } |
| |
| bool CSPDirectiveList::CheckWasmEvalAndReportViolation( |
| SourceListDirective* directive, |
| const String& console_message, |
| ScriptState* script_state, |
| ContentSecurityPolicy::ExceptionStatus exception_status, |
| const String& content) const { |
| if (CheckWasmEval(directive)) |
| return true; |
| |
| String suffix = String(); |
| if (directive == default_src_) { |
| suffix = |
| " Note that 'script-src' was not explicitly set, so 'default-src' is " |
| "used as a fallback."; |
| } |
| |
| ReportEvalViolation( |
| directive->GetText(), ContentSecurityPolicy::DirectiveType::kScriptSrc, |
| console_message + "\"" + directive->GetText() + "\"." + suffix + "\n", |
| KURL(), script_state, exception_status, |
| directive->AllowReportSample() ? content : g_empty_string); |
| if (!IsReportOnly()) { |
| policy_->ReportBlockedScriptExecutionToInspector(directive->GetText()); |
| return false; |
| } |
| return true; |
| } |
| |
| bool CSPDirectiveList::CheckMediaTypeAndReportViolation( |
| MediaListDirective* directive, |
| const String& type, |
| const String& type_attribute, |
| const String& console_message) const { |
| if (CheckMediaType(directive, type, type_attribute)) |
| return true; |
| |
| String message = console_message + "\'" + directive->GetText() + "\'."; |
| if (type_attribute.IsEmpty()) |
| message = message + |
| " When enforcing the 'plugin-types' directive, the plugin's " |
| "media type must be explicitly declared with a 'type' attribute " |
| "on the containing element (e.g. '<object type=\"[TYPE GOES " |
| "HERE]\" ...>')."; |
| |
| // 'RedirectStatus::NoRedirect' is safe here, as we do the media type check |
| // before actually loading data; this means that we shouldn't leak redirect |
| // targets, as we won't have had a chance to redirect yet. |
| ReportViolation( |
| directive->GetText(), ContentSecurityPolicy::DirectiveType::kPluginTypes, |
| message + "\n", NullURL(), ResourceRequest::RedirectStatus::kNoRedirect); |
| return DenyIfEnforcingPolicy(); |
| } |
| |
| bool CSPDirectiveList::CheckInlineAndReportViolation( |
| SourceListDirective* directive, |
| const String& console_message, |
| Element* element, |
| const String& source, |
| const String& context_url, |
| const WTF::OrdinalNumber& context_line, |
| bool is_script, |
| const String& hash_value, |
| ContentSecurityPolicy::DirectiveType effective_type) const { |
| if (!directive || directive->AllowAllInline()) |
| return true; |
| |
| String suffix = String(); |
| if (directive->AllowInline() && directive->IsHashOrNoncePresent()) { |
| // If inline is allowed, but a hash or nonce is present, we ignore |
| // 'unsafe-inline'. Throw a reasonable error. |
| suffix = |
| " Note that 'unsafe-inline' is ignored if either a hash or nonce value " |
| "is present in the source list."; |
| } else { |
| suffix = |
| " Either the 'unsafe-inline' keyword, a hash ('" + hash_value + |
| "'), or a nonce ('nonce-...') is required to enable inline execution."; |
| if (directive == default_src_) |
| suffix = suffix + " Note also that '" + |
| String(is_script ? "script" : "style") + |
| "-src' was not explicitly set, so 'default-src' is used as a " |
| "fallback."; |
| } |
| |
| ReportViolationWithLocation( |
| directive->GetText(), effective_type, |
| console_message + "\"" + directive->GetText() + "\"." + suffix + "\n", |
| KURL(), context_url, context_line, element, |
| directive->AllowReportSample() ? source : g_empty_string); |
| |
| if (!IsReportOnly()) { |
| if (is_script) |
| policy_->ReportBlockedScriptExecutionToInspector(directive->GetText()); |
| return false; |
| } |
| return true; |
| } |
| |
| bool CSPDirectiveList::CheckSourceAndReportViolation( |
| SourceListDirective* directive, |
| const KURL& url, |
| const ContentSecurityPolicy::DirectiveType effective_type, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| if (!directive) |
| return true; |
| |
| // We ignore URL-based whitelists if we're allowing dynamic script injection. |
| if (CheckSource(directive, url, redirect_status) && !CheckDynamic(directive)) |
| return true; |
| |
| // We should never have a violation against `child-src` or `default-src` |
| // directly; the effective directive should always be one of the explicit |
| // fetch directives. |
| DCHECK_NE(ContentSecurityPolicy::DirectiveType::kChildSrc, effective_type); |
| DCHECK_NE(ContentSecurityPolicy::DirectiveType::kDefaultSrc, effective_type); |
| |
| String prefix = "Refused to "; |
| if (ContentSecurityPolicy::DirectiveType::kBaseURI == effective_type) |
| prefix = prefix + "set the document's base URI to '"; |
| else if (ContentSecurityPolicy::DirectiveType::kWorkerSrc == effective_type) |
| prefix = prefix + "create a worker from '"; |
| else if (ContentSecurityPolicy::DirectiveType::kConnectSrc == effective_type) |
| prefix = prefix + "connect to '"; |
| else if (ContentSecurityPolicy::DirectiveType::kFontSrc == effective_type) |
| prefix = prefix + "load the font '"; |
| else if (ContentSecurityPolicy::DirectiveType::kFormAction == effective_type) |
| prefix = prefix + "send form data to '"; |
| else if (ContentSecurityPolicy::DirectiveType::kFrameSrc == effective_type) |
| prefix = prefix + "frame '"; |
| else if (ContentSecurityPolicy::DirectiveType::kImgSrc == effective_type) |
| prefix = prefix + "load the image '"; |
| else if (ContentSecurityPolicy::DirectiveType::kMediaSrc == effective_type) |
| prefix = prefix + "load media from '"; |
| else if (ContentSecurityPolicy::DirectiveType::kManifestSrc == effective_type) |
| prefix = prefix + "load manifest from '"; |
| else if (ContentSecurityPolicy::DirectiveType::kObjectSrc == effective_type) |
| prefix = prefix + "load plugin data from '"; |
| else if (ContentSecurityPolicy::DirectiveType::kPrefetchSrc == effective_type) |
| prefix = prefix + "prefetch content from '"; |
| else if (ContentSecurityPolicy::IsScriptDirective(effective_type)) |
| prefix = prefix + "load the script '"; |
| else if (ContentSecurityPolicy::IsStyleDirective(effective_type)) |
| prefix = prefix + "load the stylesheet '"; |
| else if (ContentSecurityPolicy::DirectiveType::kNavigateTo == effective_type) |
| prefix = prefix + "navigate to '"; |
| |
| String suffix = String(); |
| if (CheckDynamic(directive)) |
| suffix = |
| " 'strict-dynamic' is present, so host-based whitelisting is disabled."; |
| |
| String directive_name = directive->GetName(); |
| String effective_directive_name = |
| ContentSecurityPolicy::GetDirectiveName(effective_type); |
| if (directive_name != effective_directive_name) { |
| suffix = suffix + " Note that '" + effective_directive_name + |
| "' was not explicitly set, so '" + directive_name + |
| "' is used as a fallback."; |
| } |
| |
| ReportViolation(directive->GetText(), effective_type, |
| prefix + url.ElidedString() + |
| "' because it violates the following Content Security " |
| "Policy directive: \"" + |
| directive->GetText() + "\"." + suffix + "\n", |
| url, redirect_status); |
| return DenyIfEnforcingPolicy(); |
| } |
| |
| bool CSPDirectiveList::CheckAncestorsAndReportViolation( |
| SourceListDirective* directive, |
| LocalFrame* frame, |
| const KURL& url) const { |
| if (CheckAncestors(directive, frame)) |
| return true; |
| |
| ReportViolationWithFrame( |
| directive->GetText(), |
| ContentSecurityPolicy::DirectiveType::kFrameAncestors, |
| "Refused to display '" + url.ElidedString() + |
| "' in a frame because an ancestor violates the " |
| "following Content Security Policy directive: " |
| "\"" + |
| directive->GetText() + "\".", |
| url, frame); |
| return DenyIfEnforcingPolicy(); |
| } |
| |
| bool CSPDirectiveList::AllowJavaScriptURLs( |
| Element* element, |
| const String& source, |
| const String& context_url, |
| const WTF::OrdinalNumber& context_line, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| SourceListDirective* directive = |
| OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrcElem); |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) { |
| return CheckInlineAndReportViolation( |
| directive, |
| "Refused to run the JavaScript URL because it violates the following " |
| "Content Security Policy directive: ", |
| element, source, context_url, context_line, true, "sha256-...", |
| ContentSecurityPolicy::DirectiveType::kScriptSrcElem); |
| } |
| |
| return !directive || directive->AllowAllInline(); |
| } |
| |
| bool CSPDirectiveList::AllowInlineEventHandlers( |
| Element* element, |
| const String& source, |
| const String& context_url, |
| const WTF::OrdinalNumber& context_line, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| SourceListDirective* directive = |
| OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrcAttr); |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) { |
| return CheckInlineAndReportViolation( |
| directive, |
| "Refused to execute inline event handler because it violates the " |
| "following Content Security Policy directive: ", |
| element, source, context_url, context_line, true, "sha256-...", |
| ContentSecurityPolicy::DirectiveType::kScriptSrcAttr); |
| } |
| |
| return !directive || directive->AllowAllInline(); |
| } |
| |
| bool CSPDirectiveList::AllowInlineScript( |
| Element* element, |
| const String& context_url, |
| const String& nonce, |
| const WTF::OrdinalNumber& context_line, |
| SecurityViolationReportingPolicy reporting_policy, |
| const String& content) const { |
| SourceListDirective* directive = |
| OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrcElem); |
| if (IsMatchingNoncePresent(directive, nonce)) |
| return true; |
| if (element && IsHTMLScriptElement(element) && |
| !ToHTMLScriptElement(element)->Loader()->IsParserInserted() && |
| AllowDynamic(ContentSecurityPolicy::DirectiveType::kScriptSrcElem)) { |
| return true; |
| } |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) { |
| return CheckInlineAndReportViolation( |
| directive, |
| "Refused to execute inline script because it violates the following " |
| "Content Security Policy directive: ", |
| element, content, context_url, context_line, true, |
| GetSha256String(content), |
| ContentSecurityPolicy::DirectiveType::kScriptSrcElem); |
| } |
| |
| return !directive || directive->AllowAllInline(); |
| } |
| |
| bool CSPDirectiveList::AllowInlineStyle( |
| Element* element, |
| const String& context_url, |
| const String& nonce, |
| const WTF::OrdinalNumber& context_line, |
| SecurityViolationReportingPolicy reporting_policy, |
| const String& content, |
| ContentSecurityPolicy::InlineType inline_type) const { |
| ContentSecurityPolicy::DirectiveType effective_type = |
| inline_type == ContentSecurityPolicy::InlineType::kAttribute |
| ? ContentSecurityPolicy::DirectiveType::kStyleSrcAttr |
| : ContentSecurityPolicy::DirectiveType::kStyleSrcElem; |
| SourceListDirective* directive = OperativeDirective(effective_type); |
| |
| if (IsMatchingNoncePresent(directive, nonce)) |
| return true; |
| |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) { |
| return CheckInlineAndReportViolation( |
| directive, |
| "Refused to apply inline style because it violates the following " |
| "Content Security Policy directive: ", |
| element, content, context_url, context_line, false, |
| GetSha256String(content), effective_type); |
| } |
| |
| return !directive || directive->AllowAllInline(); |
| } |
| |
| bool CSPDirectiveList::AllowEval( |
| ScriptState* script_state, |
| SecurityViolationReportingPolicy reporting_policy, |
| ContentSecurityPolicy::ExceptionStatus exception_status, |
| const String& content) const { |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) { |
| return CheckEvalAndReportViolation( |
| OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrc), |
| "Refused to evaluate a string as JavaScript because 'unsafe-eval' is " |
| "not an allowed source of script in the following Content Security " |
| "Policy directive: ", |
| script_state, exception_status, content); |
| } |
| return CheckEval( |
| OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrc)); |
| } |
| |
| bool CSPDirectiveList::AllowWasmEval( |
| ScriptState* script_state, |
| SecurityViolationReportingPolicy reporting_policy, |
| ContentSecurityPolicy::ExceptionStatus exception_status, |
| const String& content) const { |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) { |
| return CheckWasmEvalAndReportViolation( |
| OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrc), |
| "Refused to compile or instantiate WebAssembly module because " |
| "'wasm-eval' is not an allowed source of script in the following " |
| "Content Security Policy directive: ", |
| script_state, exception_status, content); |
| } |
| return CheckWasmEval( |
| OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrc)); |
| } |
| |
| bool CSPDirectiveList::AllowPluginType( |
| const String& type, |
| const String& type_attribute, |
| const KURL& url, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckMediaTypeAndReportViolation( |
| plugin_types_.Get(), type, type_attribute, |
| "Refused to load '" + url.ElidedString() + "' (MIME type '" + |
| type_attribute + |
| "') because it violates the following Content Security " |
| "Policy Directive: ") |
| : CheckMediaType(plugin_types_.Get(), type, type_attribute); |
| } |
| |
| bool CSPDirectiveList::AllowScriptFromSource( |
| const KURL& url, |
| const String& nonce, |
| const IntegrityMetadataSet& hashes, |
| ParserDisposition parser_disposition, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| SourceListDirective* directive = |
| OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrcElem); |
| if (IsMatchingNoncePresent(directive, nonce)) |
| return true; |
| if (parser_disposition == kNotParserInserted && |
| AllowDynamic(ContentSecurityPolicy::DirectiveType::kScriptSrcElem)) |
| return true; |
| if (AreAllMatchingHashesPresent(directive, hashes)) |
| return true; |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| directive, url, |
| ContentSecurityPolicy::DirectiveType::kScriptSrcElem, |
| redirect_status) |
| : CheckSource(directive, url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowObjectFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| if (url.ProtocolIsAbout()) |
| return true; |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kObjectSrc), |
| url, ContentSecurityPolicy::DirectiveType::kObjectSrc, |
| redirect_status) |
| : CheckSource( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kObjectSrc), |
| url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowPrefetchFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kPrefetchSrc), |
| url, ContentSecurityPolicy::DirectiveType::kPrefetchSrc, |
| redirect_status) |
| : CheckSource( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kPrefetchSrc), |
| url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowFrameFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| if (url.ProtocolIsAbout()) |
| return true; |
| |
| // 'frame-src' overrides 'child-src', which overrides the default |
| // sources. So, we do this nested set of calls to 'operativeDirective()' to |
| // grab 'frame-src' if it exists, 'child-src' if it doesn't, and 'defaut-src' |
| // if neither are available. |
| SourceListDirective* directive = |
| OperativeDirective(ContentSecurityPolicy::DirectiveType::kFrameSrc); |
| |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| directive, url, |
| ContentSecurityPolicy::DirectiveType::kFrameSrc, |
| redirect_status) |
| : CheckSource(directive, url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowImageFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kImgSrc), |
| url, ContentSecurityPolicy::DirectiveType::kImgSrc, |
| redirect_status) |
| : CheckSource(OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kImgSrc), |
| url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowStyleFromSource( |
| const KURL& url, |
| const String& nonce, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| SourceListDirective* directive = |
| OperativeDirective(ContentSecurityPolicy::DirectiveType::kStyleSrcElem); |
| |
| if (IsMatchingNoncePresent(directive, nonce)) |
| return true; |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| directive, url, |
| ContentSecurityPolicy::DirectiveType::kStyleSrcElem, |
| redirect_status) |
| : CheckSource(directive, url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowFontFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kFontSrc), |
| url, ContentSecurityPolicy::DirectiveType::kFontSrc, |
| redirect_status) |
| : CheckSource(OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kFontSrc), |
| url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowMediaFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kMediaSrc), |
| url, ContentSecurityPolicy::DirectiveType::kMediaSrc, |
| redirect_status) |
| : CheckSource(OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kMediaSrc), |
| url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowManifestFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kManifestSrc), |
| url, ContentSecurityPolicy::DirectiveType::kManifestSrc, |
| redirect_status) |
| : CheckSource( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kManifestSrc), |
| url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowConnectToSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kConnectSrc), |
| url, ContentSecurityPolicy::DirectiveType::kConnectSrc, |
| redirect_status) |
| : CheckSource( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kConnectSrc), |
| url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowFormAction( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kFormAction), |
| url, ContentSecurityPolicy::DirectiveType::kFormAction, |
| redirect_status) |
| : CheckSource( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kFormAction), |
| url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowBaseURI( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| bool result = |
| reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kBaseURI), |
| url, ContentSecurityPolicy::DirectiveType::kBaseURI, |
| redirect_status) |
| : CheckSource(OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kBaseURI), |
| url, redirect_status); |
| |
| if (result && |
| !CheckSource( |
| OperativeDirective(ContentSecurityPolicy::DirectiveType::kBaseURI), |
| url, redirect_status)) { |
| UseCounter::Count(policy_->GetDocument(), |
| WebFeature::kBaseWouldBeBlockedByDefaultSrc); |
| } |
| |
| return result; |
| } |
| |
| bool CSPDirectiveList::AllowTrustedTypePolicy(const String& policy_name) const { |
| if (trusted_types_->Allows(policy_name)) |
| return true; |
| |
| ReportViolation( |
| "trusted-types", ContentSecurityPolicy::DirectiveType::kTrustedTypes, |
| String::Format( |
| "Refused to create a TrustedTypePolicy named '%s' because " |
| "it violates the following Content Security Policy directive: " |
| "\"%s\".", |
| policy_name.Utf8().data(), |
| trusted_types_.Get()->GetText().Utf8().data()), |
| KURL(), RedirectStatus::kNoRedirect); |
| |
| return DenyIfEnforcingPolicy(); |
| } |
| |
| bool CSPDirectiveList::AllowWorkerFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| if (AllowDynamicWorker()) |
| return true; |
| |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kWorkerSrc), |
| url, ContentSecurityPolicy::DirectiveType::kWorkerSrc, |
| redirect_status) |
| : CheckSource( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kWorkerSrc), |
| url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowAncestors( |
| LocalFrame* frame, |
| const KURL& url, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckAncestorsAndReportViolation( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kFrameAncestors), |
| frame, url) |
| : CheckAncestors( |
| OperativeDirective( |
| ContentSecurityPolicy::DirectiveType::kFrameAncestors), |
| frame); |
| } |
| |
| bool CSPDirectiveList::AllowHash( |
| const CSPHashValue& hash_value, |
| const ContentSecurityPolicy::InlineType type, |
| const ContentSecurityPolicy::DirectiveType directive_type) const { |
| if (type == ContentSecurityPolicy::InlineType::kAttribute) { |
| if (!policy_->ExperimentalFeaturesEnabled()) |
| return false; |
| if (!CheckUnsafeHashesAllowed(OperativeDirective(directive_type))) |
| return false; |
| } |
| return CheckHash(OperativeDirective(directive_type), hash_value); |
| } |
| |
| bool CSPDirectiveList::AllowScriptHash( |
| const CSPHashValue& hash_value, |
| ContentSecurityPolicy::InlineType type) const { |
| return AllowHash(hash_value, type, |
| type == ContentSecurityPolicy::InlineType::kAttribute |
| ? ContentSecurityPolicy::DirectiveType::kScriptSrcAttr |
| : ContentSecurityPolicy::DirectiveType::kScriptSrcElem); |
| } |
| |
| bool CSPDirectiveList::AllowStyleHash( |
| const CSPHashValue& hash_value, |
| ContentSecurityPolicy::InlineType type) const { |
| return AllowHash(hash_value, type, |
| type == ContentSecurityPolicy::InlineType::kAttribute |
| ? ContentSecurityPolicy::DirectiveType::kStyleSrcAttr |
| : ContentSecurityPolicy::DirectiveType::kStyleSrcElem); |
| } |
| |
| bool CSPDirectiveList::AllowDynamic( |
| ContentSecurityPolicy::DirectiveType directive_type) const { |
| return CheckDynamic(OperativeDirective(directive_type)); |
| } |
| |
| bool CSPDirectiveList::AllowDynamicWorker() const { |
| SourceListDirective* worker_src = |
| OperativeDirective(ContentSecurityPolicy::DirectiveType::kWorkerSrc); |
| return CheckDynamic(worker_src); |
| } |
| |
| const String& CSPDirectiveList::PluginTypesText() const { |
| DCHECK(HasPluginTypes()); |
| return plugin_types_->GetText(); |
| } |
| |
| bool CSPDirectiveList::ShouldSendCSPHeader(ResourceType type) const { |
| // TODO(mkwst): Revisit this once the CORS prefetch issue with the 'CSP' |
| // header is worked out, one way or another: |
| // https://github.com/whatwg/fetch/issues/52 |
| return false; |
| } |
| |
| // policy = directive-list |
| // directive-list = [ directive *( ";" [ directive ] ) ] |
| // |
| void CSPDirectiveList::Parse(const UChar* begin, |
| const UChar* end, |
| bool should_parse_wasm_eval) { |
| header_ = String(begin, end - begin).StripWhiteSpace(); |
| |
| if (begin == end) |
| return; |
| |
| const UChar* position = begin; |
| while (position < end) { |
| const UChar* directive_begin = position; |
| SkipUntil<UChar>(position, end, ';'); |
| |
| String name, value; |
| if (ParseDirective(directive_begin, position, &name, &value)) { |
| DCHECK(!name.IsEmpty()); |
| AddDirective(name, value); |
| } |
| |
| DCHECK(position == end || *position == ';'); |
| SkipExactly<UChar>(position, end, ';'); |
| } |
| } |
| |
| // directive = *WSP [ directive-name [ WSP directive-value ] ] |
| // directive-name = 1*( ALPHA / DIGIT / "-" ) |
| // directive-value = *( WSP / <VCHAR except ";"> ) |
| // |
| bool CSPDirectiveList::ParseDirective(const UChar* begin, |
| const UChar* end, |
| String* name, |
| String* value) { |
| DCHECK(name->IsEmpty()); |
| DCHECK(value->IsEmpty()); |
| |
| const UChar* position = begin; |
| SkipWhile<UChar, IsASCIISpace>(position, end); |
| |
| // Empty directive (e.g. ";;;"). Exit early. |
| if (position == end) |
| return false; |
| |
| const UChar* name_begin = position; |
| SkipWhile<UChar, IsCSPDirectiveNameCharacter>(position, end); |
| |
| // The directive-name must be non-empty. |
| if (name_begin == position) { |
| SkipWhile<UChar, IsNotASCIISpace>(position, end); |
| policy_->ReportUnsupportedDirective( |
| String(name_begin, position - name_begin)); |
| return false; |
| } |
| |
| *name = String(name_begin, position - name_begin); |
| |
| if (position == end) |
| return true; |
| |
| if (!SkipExactly<UChar, IsASCIISpace>(position, end)) { |
| SkipWhile<UChar, IsNotASCIISpace>(position, end); |
| policy_->ReportUnsupportedDirective( |
| String(name_begin, position - name_begin)); |
| return false; |
| } |
| |
| SkipWhile<UChar, IsASCIISpace>(position, end); |
| |
| const UChar* value_begin = position; |
| SkipWhile<UChar, IsCSPDirectiveValueCharacter>(position, end); |
| |
| if (position != end) { |
| policy_->ReportInvalidDirectiveValueCharacter( |
| *name, String(value_begin, end - value_begin)); |
| return false; |
| } |
| |
| // The directive-value may be empty. |
| if (value_begin == position) |
| return true; |
| |
| *value = String(value_begin, position - value_begin); |
| return true; |
| } |
| |
| void CSPDirectiveList::ParseRequireSRIFor(const String& name, |
| const String& value) { |
| if (require_sri_for_ != 0) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| StringBuilder token_errors; |
| unsigned number_of_token_errors = 0; |
| Vector<UChar> characters; |
| value.AppendTo(characters); |
| |
| const UChar* position = characters.data(); |
| const UChar* end = position + characters.size(); |
| |
| while (position < end) { |
| SkipWhile<UChar, IsASCIISpace>(position, end); |
| |
| const UChar* token_begin = position; |
| SkipWhile<UChar, IsNotASCIISpace>(position, end); |
| |
| if (token_begin < position) { |
| String token = String(token_begin, position - token_begin); |
| if (EqualIgnoringASCIICase(token, "script")) { |
| require_sri_for_ |= RequireSRIForToken::kScript; |
| } else if (EqualIgnoringASCIICase(token, "style")) { |
| require_sri_for_ |= RequireSRIForToken::kStyle; |
| } else { |
| if (number_of_token_errors) |
| token_errors.Append(", \'"); |
| else |
| token_errors.Append('\''); |
| token_errors.Append(token); |
| token_errors.Append('\''); |
| number_of_token_errors++; |
| } |
| } |
| } |
| |
| if (number_of_token_errors == 0) |
| return; |
| |
| String invalid_tokens_error_message; |
| if (number_of_token_errors > 1) |
| token_errors.Append(" are invalid 'require-sri-for' tokens."); |
| else |
| token_errors.Append(" is an invalid 'require-sri-for' token."); |
| |
| invalid_tokens_error_message = token_errors.ToString(); |
| |
| DCHECK(!invalid_tokens_error_message.IsEmpty()); |
| |
| policy_->ReportInvalidRequireSRIForTokens(invalid_tokens_error_message); |
| } |
| |
| void CSPDirectiveList::ParseReportTo(const String& name, const String& value) { |
| if (!use_reporting_api_) { |
| use_reporting_api_ = true; |
| report_endpoints_.clear(); |
| } |
| |
| if (!report_endpoints_.IsEmpty()) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| |
| ParseAndAppendReportEndpoints(value); |
| } |
| |
| void CSPDirectiveList::ParseReportURI(const String& name, const String& value) { |
| // report-to supersedes report-uri |
| if (use_reporting_api_) |
| return; |
| |
| if (!report_endpoints_.IsEmpty()) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| |
| // Remove report-uri in meta policies, per |
| // https://html.spec.whatwg.org/#attr-meta-http-equiv-content-security-policy. |
| if (header_source_ == kContentSecurityPolicyHeaderSourceMeta) { |
| policy_->ReportInvalidDirectiveInMeta(name); |
| return; |
| } |
| |
| ParseAndAppendReportEndpoints(value); |
| } |
| |
| void CSPDirectiveList::ParseAndAppendReportEndpoints(const String& value) { |
| Vector<UChar> characters; |
| value.AppendTo(characters); |
| |
| const UChar* position = characters.data(); |
| const UChar* end = position + characters.size(); |
| |
| while (position < end) { |
| SkipWhile<UChar, IsASCIISpace>(position, end); |
| |
| const UChar* endpoint_begin = position; |
| SkipWhile<UChar, IsNotASCIISpace>(position, end); |
| |
| if (endpoint_begin < position) { |
| String endpoint = String(endpoint_begin, position - endpoint_begin); |
| report_endpoints_.push_back(endpoint); |
| } |
| } |
| |
| if (report_endpoints_.size() > 1) { |
| UseCounter::Count(policy_->GetDocument(), |
| WebFeature::kReportUriMultipleEndpoints); |
| } else { |
| UseCounter::Count(policy_->GetDocument(), |
| WebFeature::kReportUriSingleEndpoint); |
| } |
| } |
| |
| template <class CSPDirectiveType> |
| void CSPDirectiveList::SetCSPDirective(const String& name, |
| const String& value, |
| Member<CSPDirectiveType>& directive, |
| bool should_parse_wasm_eval) { |
| if (directive) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| |
| // Remove frame-ancestors directives in meta policies, per |
| // https://www.w3.org/TR/CSP2/#delivery-html-meta-element. |
| if (header_source_ == kContentSecurityPolicyHeaderSourceMeta && |
| ContentSecurityPolicy::GetDirectiveType(name) == |
| ContentSecurityPolicy::DirectiveType::kFrameAncestors) { |
| policy_->ReportInvalidDirectiveInMeta(name); |
| return; |
| } |
| |
| directive = new CSPDirectiveType(name, value, policy_); |
| } |
| |
| void CSPDirectiveList::ApplySandboxPolicy(const String& name, |
| const String& sandbox_policy) { |
| // Remove sandbox directives in meta policies, per |
| // https://www.w3.org/TR/CSP2/#delivery-html-meta-element. |
| if (header_source_ == kContentSecurityPolicyHeaderSourceMeta) { |
| policy_->ReportInvalidDirectiveInMeta(name); |
| return; |
| } |
| if (IsReportOnly()) { |
| policy_->ReportInvalidInReportOnly(name); |
| return; |
| } |
| if (has_sandbox_policy_) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| has_sandbox_policy_ = true; |
| String invalid_tokens; |
| SpaceSplitString policy_tokens = |
| SpaceSplitString(AtomicString(sandbox_policy)); |
| policy_->EnforceSandboxFlags( |
| ParseSandboxPolicy(policy_tokens, invalid_tokens)); |
| if (!invalid_tokens.IsNull()) |
| policy_->ReportInvalidSandboxFlags(invalid_tokens); |
| } |
| |
| void CSPDirectiveList::TreatAsPublicAddress(const String& name, |
| const String& value) { |
| if (IsReportOnly()) { |
| policy_->ReportInvalidInReportOnly(name); |
| return; |
| } |
| if (treat_as_public_address_) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| treat_as_public_address_ = true; |
| policy_->TreatAsPublicAddress(); |
| if (!value.IsEmpty()) |
| policy_->ReportValueForEmptyDirective(name, value); |
| } |
| |
| void CSPDirectiveList::RequireTrustedTypes(const String& name, |
| const String& value) { |
| if (IsReportOnly()) { |
| policy_->ReportInvalidInReportOnly(name); |
| return; |
| } |
| if (trusted_types_) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| policy_->RequireTrustedTypes(); |
| trusted_types_ = new StringListDirective(name, value, policy_); |
| } |
| |
| void CSPDirectiveList::EnforceStrictMixedContentChecking(const String& name, |
| const String& value) { |
| if (strict_mixed_content_checking_enforced_) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| if (!value.IsEmpty()) |
| policy_->ReportValueForEmptyDirective(name, value); |
| |
| strict_mixed_content_checking_enforced_ = true; |
| |
| if (!IsReportOnly()) |
| policy_->EnforceStrictMixedContentChecking(); |
| } |
| |
| void CSPDirectiveList::EnableInsecureRequestsUpgrade(const String& name, |
| const String& value) { |
| if (IsReportOnly()) { |
| policy_->ReportInvalidInReportOnly(name); |
| return; |
| } |
| if (upgrade_insecure_requests_) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| upgrade_insecure_requests_ = true; |
| |
| policy_->UpgradeInsecureRequests(); |
| if (!value.IsEmpty()) |
| policy_->ReportValueForEmptyDirective(name, value); |
| } |
| |
| void CSPDirectiveList::AddDirective(const String& name, const String& value) { |
| DCHECK(!name.IsEmpty()); |
| |
| ContentSecurityPolicy::DirectiveType type = |
| ContentSecurityPolicy::GetDirectiveType(name); |
| if (type == ContentSecurityPolicy::DirectiveType::kDefaultSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, default_src_); |
| // TODO(mkwst) It seems unlikely that developers would use different |
| // algorithms for scripts and styles. We may want to combine the |
| // usesScriptHashAlgorithms() and usesStyleHashAlgorithms. |
| policy_->UsesScriptHashAlgorithms(default_src_->HashAlgorithmsUsed()); |
| policy_->UsesStyleHashAlgorithms(default_src_->HashAlgorithmsUsed()); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kScriptSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, script_src_); |
| policy_->UsesScriptHashAlgorithms(script_src_->HashAlgorithmsUsed()); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kScriptSrcAttr && |
| policy_->ExperimentalFeaturesEnabled()) { |
| SetCSPDirective<SourceListDirective>(name, value, script_src_attr_); |
| policy_->UsesScriptHashAlgorithms(script_src_attr_->HashAlgorithmsUsed()); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kScriptSrcElem && |
| policy_->ExperimentalFeaturesEnabled()) { |
| SetCSPDirective<SourceListDirective>(name, value, script_src_elem_); |
| policy_->UsesScriptHashAlgorithms(script_src_elem_->HashAlgorithmsUsed()); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kObjectSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, object_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kFrameAncestors) { |
| SetCSPDirective<SourceListDirective>(name, value, frame_ancestors_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kFrameSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, frame_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kImgSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, img_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kStyleSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, style_src_); |
| policy_->UsesStyleHashAlgorithms(style_src_->HashAlgorithmsUsed()); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kStyleSrcAttr && |
| policy_->ExperimentalFeaturesEnabled()) { |
| SetCSPDirective<SourceListDirective>(name, value, style_src_attr_); |
| policy_->UsesStyleHashAlgorithms(style_src_attr_->HashAlgorithmsUsed()); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kStyleSrcElem && |
| policy_->ExperimentalFeaturesEnabled()) { |
| SetCSPDirective<SourceListDirective>(name, value, style_src_elem_); |
| policy_->UsesStyleHashAlgorithms(style_src_elem_->HashAlgorithmsUsed()); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kFontSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, font_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kMediaSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, media_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kConnectSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, connect_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kSandbox) { |
| ApplySandboxPolicy(name, value); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kReportURI) { |
| ParseReportURI(name, value); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kBaseURI) { |
| SetCSPDirective<SourceListDirective>(name, value, base_uri_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kChildSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, child_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kWorkerSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, worker_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kFormAction) { |
| SetCSPDirective<SourceListDirective>(name, value, form_action_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kPluginTypes) { |
| SetCSPDirective<MediaListDirective>(name, value, plugin_types_); |
| } else if (type == |
| ContentSecurityPolicy::DirectiveType::kUpgradeInsecureRequests) { |
| EnableInsecureRequestsUpgrade(name, value); |
| } else if (type == |
| ContentSecurityPolicy::DirectiveType::kBlockAllMixedContent) { |
| EnforceStrictMixedContentChecking(name, value); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kManifestSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, manifest_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kNavigateTo) { |
| SetCSPDirective<SourceListDirective>(name, value, navigate_to_); |
| } else if (type == |
| ContentSecurityPolicy::DirectiveType::kTreatAsPublicAddress) { |
| TreatAsPublicAddress(name, value); |
| } else if (policy_->ExperimentalFeaturesEnabled()) { |
| if (type == ContentSecurityPolicy::DirectiveType::kRequireSRIFor) { |
| ParseRequireSRIFor(name, value); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kReportTo) { |
| ParseReportTo(name, value); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kTrustedTypes && |
| RuntimeEnabledFeatures::TrustedDOMTypesEnabled()) { |
| RequireTrustedTypes(name, value); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kPrefetchSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, prefetch_src_); |
| } else { |
| policy_->ReportUnsupportedDirective(name); |
| } |
| } else { |
| policy_->ReportUnsupportedDirective(name); |
| } |
| } |
| |
| ContentSecurityPolicy::DirectiveType CSPDirectiveList::FallbackDirective( |
| const ContentSecurityPolicy::DirectiveType current_directive, |
| const ContentSecurityPolicy::DirectiveType original_directive) const { |
| switch (current_directive) { |
| case ContentSecurityPolicy::DirectiveType::kConnectSrc: |
| case ContentSecurityPolicy::DirectiveType::kFontSrc: |
| case ContentSecurityPolicy::DirectiveType::kImgSrc: |
| case ContentSecurityPolicy::DirectiveType::kManifestSrc: |
| case ContentSecurityPolicy::DirectiveType::kMediaSrc: |
| case ContentSecurityPolicy::DirectiveType::kPrefetchSrc: |
| case ContentSecurityPolicy::DirectiveType::kObjectSrc: |
| case ContentSecurityPolicy::DirectiveType::kScriptSrc: |
| case ContentSecurityPolicy::DirectiveType::kStyleSrc: |
| return ContentSecurityPolicy::DirectiveType::kDefaultSrc; |
| |
| case ContentSecurityPolicy::DirectiveType::kScriptSrcAttr: |
| case ContentSecurityPolicy::DirectiveType::kScriptSrcElem: |
| return ContentSecurityPolicy::DirectiveType::kScriptSrc; |
| |
| case ContentSecurityPolicy::DirectiveType::kStyleSrcAttr: |
| case ContentSecurityPolicy::DirectiveType::kStyleSrcElem: |
| return ContentSecurityPolicy::DirectiveType::kStyleSrc; |
| |
| case ContentSecurityPolicy::DirectiveType::kFrameSrc: |
| case ContentSecurityPolicy::DirectiveType::kWorkerSrc: |
| return ContentSecurityPolicy::DirectiveType::kChildSrc; |
| |
| // Because the fallback chain of child-src can be different if we are |
| // checking a worker or a frame request, we need to know the original type |
| // of the request to decide. These are the fallback chains for worker-src |
| // and frame-src specifically. |
| |
| // worker-src > child-src > script-src > default-src |
| // frame-src > child-src > default-src |
| |
| // Since there are some situations and tests that will operate on the |
| // `child-src` directive directly (like for example the EE subsumption |
| // algorithm), we consider the child-src > default-src fallback path as the |
| // "default" and the worker-src fallback path as an exception. |
| case ContentSecurityPolicy::DirectiveType::kChildSrc: |
| if (original_directive == |
| ContentSecurityPolicy::DirectiveType::kWorkerSrc) |
| return ContentSecurityPolicy::DirectiveType::kScriptSrc; |
| |
| return ContentSecurityPolicy::DirectiveType::kDefaultSrc; |
| |
| default: |
| return ContentSecurityPolicy::DirectiveType::kUndefined; |
| } |
| } |
| |
| SourceListDirective* CSPDirectiveList::OperativeDirective( |
| const ContentSecurityPolicy::DirectiveType type, |
| ContentSecurityPolicy::DirectiveType original_type) const { |
| if (type == ContentSecurityPolicy::DirectiveType::kUndefined) { |
| return nullptr; |
| } |
| |
| SourceListDirective* directive; |
| if (original_type == ContentSecurityPolicy::DirectiveType::kUndefined) { |
| original_type = type; |
| } |
| |
| switch (type) { |
| case ContentSecurityPolicy::DirectiveType::kBaseURI: |
| directive = base_uri_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kDefaultSrc: |
| directive = default_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kFrameAncestors: |
| directive = frame_ancestors_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kFormAction: |
| directive = form_action_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kNavigateTo: |
| directive = navigate_to_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kChildSrc: |
| directive = child_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kConnectSrc: |
| directive = connect_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kFontSrc: |
| directive = font_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kImgSrc: |
| directive = img_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kManifestSrc: |
| directive = manifest_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kMediaSrc: |
| directive = media_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kObjectSrc: |
| directive = object_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kPrefetchSrc: |
| directive = prefetch_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kScriptSrc: |
| directive = script_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kScriptSrcAttr: |
| directive = policy_->ExperimentalFeaturesEnabled() ? script_src_attr_ |
| : script_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kScriptSrcElem: |
| directive = policy_->ExperimentalFeaturesEnabled() ? script_src_elem_ |
| : script_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kStyleSrc: |
| directive = style_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kStyleSrcAttr: |
| directive = |
| policy_->ExperimentalFeaturesEnabled() ? style_src_attr_ : style_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kStyleSrcElem: |
| directive = |
| policy_->ExperimentalFeaturesEnabled() ? style_src_elem_ : style_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kFrameSrc: |
| directive = frame_src_; |
| break; |
| case ContentSecurityPolicy::DirectiveType::kWorkerSrc: |
| directive = worker_src_; |
| break; |
| default: |
| return nullptr; |
| } |
| |
| // if the directive does not exist, rely on the fallback directive |
| return directive ? directive |
| : OperativeDirective(FallbackDirective(type, original_type), |
| original_type); |
| } |
| |
| SourceListDirectiveVector CSPDirectiveList::GetSourceVector( |
| const ContentSecurityPolicy::DirectiveType type, |
| const CSPDirectiveListVector& policies) { |
| SourceListDirectiveVector source_list_directives; |
| for (const auto& policy : policies) { |
| if (SourceListDirective* directive = policy->OperativeDirective(type)) { |
| if (directive->IsNone()) |
| return SourceListDirectiveVector(1, directive); |
| source_list_directives.push_back(directive); |
| } |
| } |
| |
| return source_list_directives; |
| } |
| |
| bool CSPDirectiveList::Subsumes(const CSPDirectiveListVector& other) { |
| // A white-list of directives that we consider for subsumption. |
| // See more about source lists here: |
| // https://w3c.github.io/webappsec-csp/#framework-directive-source-list |
| static ContentSecurityPolicy::DirectiveType directives[] = { |
| ContentSecurityPolicy::DirectiveType::kChildSrc, |
| ContentSecurityPolicy::DirectiveType::kConnectSrc, |
| ContentSecurityPolicy::DirectiveType::kFontSrc, |
| ContentSecurityPolicy::DirectiveType::kFrameSrc, |
| ContentSecurityPolicy::DirectiveType::kImgSrc, |
| ContentSecurityPolicy::DirectiveType::kManifestSrc, |
| ContentSecurityPolicy::DirectiveType::kMediaSrc, |
| ContentSecurityPolicy::DirectiveType::kObjectSrc, |
| ContentSecurityPolicy::DirectiveType::kScriptSrc, |
| ContentSecurityPolicy::DirectiveType::kScriptSrcAttr, |
| ContentSecurityPolicy::DirectiveType::kScriptSrcElem, |
| ContentSecurityPolicy::DirectiveType::kStyleSrc, |
| ContentSecurityPolicy::DirectiveType::kStyleSrcAttr, |
| ContentSecurityPolicy::DirectiveType::kStyleSrcElem, |
| ContentSecurityPolicy::DirectiveType::kWorkerSrc, |
| ContentSecurityPolicy::DirectiveType::kBaseURI, |
| ContentSecurityPolicy::DirectiveType::kFrameAncestors, |
| ContentSecurityPolicy::DirectiveType::kFormAction, |
| ContentSecurityPolicy::DirectiveType::kNavigateTo}; |
| |
| for (const auto& directive : directives) { |
| // There should only be one SourceListDirective for each directive in |
| // Embedding-CSP. |
| SourceListDirectiveVector required_list = |
| GetSourceVector(directive, CSPDirectiveListVector(1, this)); |
| if (!required_list.size()) |
| continue; |
| SourceListDirective* required = required_list[0]; |
| // Aggregate all serialized source lists of the returned CSP into a vector |
| // based on a directive type, defaulting accordingly (for example, to |
| // `default-src`). |
| SourceListDirectiveVector returned = GetSourceVector(directive, other); |
| // TODO(amalika): Add checks for plugin-types, sandbox, disown-opener, |
| // navigation-to, worker-src. |
| if (!required->Subsumes(returned)) |
| return false; |
| } |
| |
| if (!HasPluginTypes()) |
| return true; |
| |
| HeapVector<Member<MediaListDirective>> plugin_types_other; |
| for (const auto& policy : other) { |
| if (policy->HasPluginTypes()) |
| plugin_types_other.push_back(policy->plugin_types_); |
| } |
| |
| return plugin_types_->Subsumes(plugin_types_other); |
| } |
| |
| WebContentSecurityPolicy CSPDirectiveList::ExposeForNavigationalChecks() const { |
| WebContentSecurityPolicy policy; |
| policy.disposition = static_cast<WebContentSecurityPolicyType>(header_type_); |
| policy.source = static_cast<WebContentSecurityPolicySource>(header_source_); |
| std::vector<WebContentSecurityPolicyDirective> directives; |
| for (const auto& directive : |
| {child_src_, default_src_, form_action_, frame_src_, navigate_to_}) { |
| if (directive) { |
| directives.push_back(WebContentSecurityPolicyDirective{ |
| directive->DirectiveName(), |
| directive->ExposeForNavigationalChecks()}); |
| } |
| } |
| if (upgrade_insecure_requests_) { |
| directives.push_back(WebContentSecurityPolicyDirective{ |
| blink::WebString("upgrade-insecure-requests"), |
| WebContentSecurityPolicySourceList()}); |
| } |
| policy.directives = directives; |
| |
| std::vector<WebString> report_endpoints; |
| for (const auto& report_endpoint : ReportEndpoints()) { |
| report_endpoints.push_back(report_endpoint); |
| } |
| |
| policy.use_reporting_api = use_reporting_api_; |
| |
| policy.report_endpoints = report_endpoints; |
| |
| policy.header = Header(); |
| |
| return policy; |
| } |
| |
| void CSPDirectiveList::Trace(blink::Visitor* visitor) { |
| visitor->Trace(policy_); |
| visitor->Trace(plugin_types_); |
| visitor->Trace(base_uri_); |
| visitor->Trace(child_src_); |
| visitor->Trace(connect_src_); |
| visitor->Trace(default_src_); |
| visitor->Trace(font_src_); |
| visitor->Trace(form_action_); |
| visitor->Trace(frame_ancestors_); |
| visitor->Trace(frame_src_); |
| visitor->Trace(img_src_); |
| visitor->Trace(media_src_); |
| visitor->Trace(manifest_src_); |
| visitor->Trace(object_src_); |
| visitor->Trace(prefetch_src_); |
| visitor->Trace(script_src_); |
| visitor->Trace(script_src_attr_); |
| visitor->Trace(script_src_elem_); |
| visitor->Trace(style_src_); |
| visitor->Trace(style_src_attr_); |
| visitor->Trace(style_src_elem_); |
| visitor->Trace(worker_src_); |
| visitor->Trace(navigate_to_); |
| visitor->Trace(trusted_types_); |
| } |
| |
| } // namespace blink |