| /* |
| * Copyright (C) 2011 Google, Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/debug/dump_without_crashing.h" |
| #include "services/network/public/cpp/web_sandbox_flags.h" |
| #include "services/network/public/mojom/web_sandbox_flags.mojom-blink.h" |
| #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h" |
| #include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom-blink.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/public/platform/web_url_request.h" |
| #include "third_party/blink/renderer/bindings/core/v8/isolated_world_csp.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_controller.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_security_policy_violation_event_init.h" |
| #include "third_party/blink/renderer/core/dom/dom_node_ids.h" |
| #include "third_party/blink/renderer/core/dom/dom_string_list.h" |
| #include "third_party/blink/renderer/core/dom/element.h" |
| #include "third_party/blink/renderer/core/dom/events/event_queue.h" |
| #include "third_party/blink/renderer/core/frame/csp/csp_directive_list.h" |
| #include "third_party/blink/renderer/core/frame/csp/csp_source.h" |
| #include "third_party/blink/renderer/core/frame/frame_client.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_client.h" |
| #include "third_party/blink/renderer/core/frame/location.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/bindings/dom_wrapper_world.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/json/json_values.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/integrity_metadata.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" |
| #include "third_party/blink/renderer/platform/network/content_security_policy_parsers.h" |
| #include "third_party/blink/renderer/platform/network/content_security_policy_response_headers.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/weborigin/known_ports.h" |
| #include "third_party/blink/renderer/platform/weborigin/kurl.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.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_hasher.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" |
| #include "third_party/blink/renderer/platform/wtf/wtf_size_t.h" |
| #include "v8/include/v8.h" |
| |
| namespace blink { |
| |
| using network::mojom::ContentSecurityPolicySource; |
| using network::mojom::ContentSecurityPolicyType; |
| |
| namespace { |
| |
| // Helper function that returns true if the given |header_type| should be |
| // checked when the CheckHeaderType is |check_header_type|. |
| bool CheckHeaderTypeMatches( |
| ContentSecurityPolicy::CheckHeaderType check_header_type, |
| ContentSecurityPolicyType header_type) { |
| switch (check_header_type) { |
| case ContentSecurityPolicy::CheckHeaderType::kCheckAll: |
| return true; |
| case ContentSecurityPolicy::CheckHeaderType::kCheckReportOnly: |
| return header_type == ContentSecurityPolicyType::kReport; |
| case ContentSecurityPolicy::CheckHeaderType::kCheckEnforce: |
| return header_type == ContentSecurityPolicyType::kEnforce; |
| } |
| NOTREACHED(); |
| return false; |
| } |
| |
| int32_t HashAlgorithmsUsed( |
| const network::mojom::blink::CSPSourceList* source_list) { |
| int32_t hash_algorithms_used = 0; |
| if (!source_list) |
| return hash_algorithms_used; |
| for (const auto& hash : source_list->hashes) { |
| hash_algorithms_used |= static_cast<int32_t>(hash->algorithm); |
| } |
| return hash_algorithms_used; |
| } |
| |
| } // namespace |
| |
| bool ContentSecurityPolicy::IsNonceableElement(const Element* element) { |
| if (element->nonce().IsNull()) |
| return false; |
| |
| bool nonceable = true; |
| |
| // To prevent an attacker from hijacking an existing nonce via a dangling |
| // markup injection, we walk through the attributes of each nonced script |
| // element: if their names or values contain "<script" or "<style", we won't |
| // apply the nonce when loading script. |
| // |
| // See http://blog.innerht.ml/csp-2015/#danglingmarkupinjection for an example |
| // of the kind of attack this is aimed at mitigating. |
| |
| if (element->HasDuplicateAttribute()) |
| nonceable = false; |
| |
| if (nonceable) { |
| static const char kScriptString[] = "<SCRIPT"; |
| static const char kStyleString[] = "<STYLE"; |
| for (const Attribute& attr : element->Attributes()) { |
| const AtomicString& name = attr.LocalName(); |
| const AtomicString& value = attr.Value(); |
| if (name.FindIgnoringASCIICase(kScriptString) != WTF::kNotFound || |
| name.FindIgnoringASCIICase(kStyleString) != WTF::kNotFound || |
| value.FindIgnoringASCIICase(kScriptString) != WTF::kNotFound || |
| value.FindIgnoringASCIICase(kStyleString) != WTF::kNotFound) { |
| nonceable = false; |
| break; |
| } |
| } |
| } |
| |
| UseCounter::Count( |
| element->GetExecutionContext(), |
| nonceable ? WebFeature::kCleanScriptElementWithNonce |
| : WebFeature::kPotentiallyInjectedScriptElementWithNonce); |
| |
| return nonceable; |
| } |
| |
| static WebFeature GetUseCounterHelperType(ContentSecurityPolicyType type) { |
| switch (type) { |
| case ContentSecurityPolicyType::kEnforce: |
| return WebFeature::kContentSecurityPolicy; |
| case ContentSecurityPolicyType::kReport: |
| return WebFeature::kContentSecurityPolicyReportOnly; |
| } |
| NOTREACHED(); |
| return WebFeature::kNumberOfFeatures; |
| } |
| |
| ContentSecurityPolicy::ContentSecurityPolicy() |
| : delegate_(nullptr), |
| override_inline_style_allowed_(false), |
| script_hash_algorithms_used_(kContentSecurityPolicyHashAlgorithmNone), |
| style_hash_algorithms_used_(kContentSecurityPolicyHashAlgorithmNone), |
| sandbox_mask_(network::mojom::blink::WebSandboxFlags::kNone), |
| require_trusted_types_(false), |
| insecure_request_policy_( |
| mojom::blink::InsecureRequestPolicy::kLeaveInsecureRequestsAlone) {} |
| |
| bool ContentSecurityPolicy::IsBound() { |
| return delegate_; |
| } |
| |
| void ContentSecurityPolicy::BindToDelegate( |
| ContentSecurityPolicyDelegate& delegate) { |
| // TODO(crbug.com/915954): Add DCHECK(!delegate_). It seems some call sites |
| // call this function multiple times. |
| delegate_ = &delegate; |
| ApplyPolicySideEffectsToDelegate(); |
| |
| // Report use counters for all the policies that have been parsed until now. |
| ReportUseCounters(policies_); |
| } |
| |
| void ContentSecurityPolicy::ApplyPolicySideEffectsToDelegate() { |
| DCHECK(delegate_); |
| |
| // Set mixed content checking and sandbox flags, then dump all the parsing |
| // error messages, then poke at histograms. |
| if (sandbox_mask_ != network::mojom::blink::WebSandboxFlags::kNone) { |
| Count(WebFeature::kSandboxViaCSP); |
| delegate_->SetSandboxFlags(sandbox_mask_); |
| } |
| |
| if (require_trusted_types_) { |
| delegate_->SetRequireTrustedTypes(); |
| Count(WebFeature::kTrustedTypesEnabled); |
| } |
| |
| delegate_->AddInsecureRequestPolicy(insecure_request_policy_); |
| |
| for (const auto& console_message : console_messages_) |
| delegate_->AddConsoleMessage(console_message); |
| console_messages_.clear(); |
| |
| // We disable 'eval()' even in the case of report-only policies, and rely on |
| // the check in the V8Initializer::codeGenerationCheckCallbackInMainThread |
| // callback to determine whether the call should execute or not. |
| if (!disable_eval_error_message_.IsNull()) |
| delegate_->DisableEval(disable_eval_error_message_); |
| } |
| |
| void ContentSecurityPolicy::ReportUseCounters( |
| const Vector<network::mojom::blink::ContentSecurityPolicyPtr>& policies) { |
| for (const auto& policy : policies) { |
| Count(GetUseCounterHelperType(policy->header->type)); |
| if (CSPDirectiveListAllowDynamic(*policy, |
| CSPDirectiveName::ScriptSrcAttr) || |
| CSPDirectiveListAllowDynamic(*policy, |
| CSPDirectiveName::ScriptSrcElem)) { |
| Count(WebFeature::kCSPWithStrictDynamic); |
| } |
| |
| if (CSPDirectiveListAllowEval(*policy, this, |
| ReportingDisposition::kSuppressReporting, |
| kWillNotThrowException, g_empty_string)) { |
| Count(WebFeature::kCSPWithUnsafeEval); |
| } |
| |
| // We consider a policy to be "reasonably secure" if it: |
| // |
| // 1. Asserts `object-src 'none'`. |
| // 2. Asserts `base-uri 'none'` or `base-uri 'self'`. |
| // 3. Avoids URL-based matching, in favor of hashes and nonces. |
| // |
| // https://chromium.googlesource.com/chromium/src/+/master/docs/security/web-mitigation-metrics.md |
| // has more detail. |
| if (CSPDirectiveListIsObjectRestrictionReasonable(*policy)) { |
| Count(policy->header->type == ContentSecurityPolicyType::kEnforce |
| ? WebFeature::kCSPWithReasonableObjectRestrictions |
| : WebFeature::kCSPROWithReasonableObjectRestrictions); |
| } |
| if (CSPDirectiveListIsBaseRestrictionReasonable(*policy)) { |
| Count(policy->header->type == ContentSecurityPolicyType::kEnforce |
| ? WebFeature::kCSPWithReasonableBaseRestrictions |
| : WebFeature::kCSPROWithReasonableBaseRestrictions); |
| } |
| if (CSPDirectiveListIsScriptRestrictionReasonable(*policy)) { |
| Count(policy->header->type == ContentSecurityPolicyType::kEnforce |
| ? WebFeature::kCSPWithReasonableScriptRestrictions |
| : WebFeature::kCSPROWithReasonableScriptRestrictions); |
| } |
| if (CSPDirectiveListIsObjectRestrictionReasonable(*policy) && |
| CSPDirectiveListIsBaseRestrictionReasonable(*policy) && |
| CSPDirectiveListIsScriptRestrictionReasonable(*policy)) { |
| Count(policy->header->type == ContentSecurityPolicyType::kEnforce |
| ? WebFeature::kCSPWithReasonableRestrictions |
| : WebFeature::kCSPROWithReasonableRestrictions); |
| |
| if (!CSPDirectiveListAllowDynamic(*policy, |
| CSPDirectiveName::ScriptSrcElem)) { |
| Count(policy->header->type == ContentSecurityPolicyType::kEnforce |
| ? WebFeature::kCSPWithBetterThanReasonableRestrictions |
| : WebFeature::kCSPROWithBetterThanReasonableRestrictions); |
| } |
| } |
| if (CSPDirectiveListRequiresTrustedTypes(*policy)) { |
| Count(CSPDirectiveListIsReportOnly(*policy) |
| ? WebFeature::kTrustedTypesEnabledReportOnly |
| : WebFeature::kTrustedTypesEnabledEnforcing); |
| } |
| if (policy->trusted_types && policy->trusted_types->allow_duplicates) { |
| Count(WebFeature::kTrustedTypesAllowDuplicates); |
| } |
| } |
| } |
| |
| ContentSecurityPolicy::~ContentSecurityPolicy() = default; |
| |
| void ContentSecurityPolicy::Trace(Visitor* visitor) const { |
| visitor->Trace(delegate_); |
| visitor->Trace(console_messages_); |
| } |
| |
| void ContentSecurityPolicy::CopyStateFrom(const ContentSecurityPolicy* other) { |
| DCHECK(policies_.IsEmpty()); |
| policies_ = mojo::Clone(other->policies_); |
| for (const auto& policy : policies_) |
| ComputeInternalStateForParsedPolicy(*policy); |
| if (delegate_) |
| ReportAccumulatedHeaders(); |
| } |
| |
| void ContentSecurityPolicy::DidReceiveHeaders( |
| const ContentSecurityPolicyResponseHeaders& headers) { |
| scoped_refptr<SecurityOrigin> self_origin = |
| SecurityOrigin::Create(headers.ResponseUrl()); |
| if (headers.ShouldParseWasmEval()) |
| supports_wasm_eval_ = true; |
| |
| Vector<network::mojom::blink::ContentSecurityPolicyPtr> parsed_policies; |
| if (!headers.ContentSecurityPolicy().IsEmpty()) { |
| parsed_policies = Parse(headers.ContentSecurityPolicy(), *self_origin, |
| ContentSecurityPolicyType::kEnforce, |
| ContentSecurityPolicySource::kHTTP); |
| } |
| if (!headers.ContentSecurityPolicyReportOnly().IsEmpty()) { |
| for (auto& policy : Parse(headers.ContentSecurityPolicyReportOnly(), |
| *self_origin, ContentSecurityPolicyType::kReport, |
| ContentSecurityPolicySource::kHTTP)) { |
| parsed_policies.push_back(std::move(policy)); |
| } |
| } |
| |
| AddPolicies(std::move(parsed_policies)); |
| } |
| |
| // static |
| WTF::Vector<network::mojom::blink::ContentSecurityPolicyPtr> |
| ContentSecurityPolicy::ParseHeaders( |
| const ContentSecurityPolicyResponseHeaders& headers) { |
| auto* content_security_policy = MakeGarbageCollected<ContentSecurityPolicy>(); |
| content_security_policy->DidReceiveHeaders(headers); |
| return std::move(content_security_policy->policies_); |
| } |
| |
| void ContentSecurityPolicy::DidReceiveHeader( |
| const String& header, |
| const SecurityOrigin& self_origin, |
| ContentSecurityPolicyType type, |
| ContentSecurityPolicySource source) { |
| AddPolicies(Parse(header, self_origin, type, source)); |
| } |
| |
| void ContentSecurityPolicy::AddPolicies( |
| Vector<network::mojom::blink::ContentSecurityPolicyPtr> policies) { |
| Vector<network::mojom::blink::ContentSecurityPolicyPtr> policies_to_report; |
| if (delegate_) { |
| policies_to_report = mojo::Clone(policies); |
| } |
| |
| for (network::mojom::blink::ContentSecurityPolicyPtr& policy : policies) { |
| ComputeInternalStateForParsedPolicy(*policy); |
| policies_.push_back(std::move(policy)); |
| } |
| |
| // If this ContentSecurityPolicy is not bound to a delegate yet, return. The |
| // following logic will be executed in BindToDelegate when that will happen. |
| if (!delegate_) |
| return; |
| |
| ApplyPolicySideEffectsToDelegate(); |
| ReportUseCounters(policies_to_report); |
| |
| // Notify about the new header, so that it can be reported back to the |
| // browser process. This is needed in order to: |
| // 1) replicate CSP directives (i.e. frame-src) to OOPIFs (only for now / |
| // short-term). |
| // 2) enforce CSP in the browser process (long-term - see |
| // https://crbug.com/376522). |
| // TODO(arthursonzogni): policies are actually replicated (1) and some of |
| // them are enforced on the browser process (2). Stop doing (1) when (2) is |
| // finished. |
| delegate_->DidAddContentSecurityPolicies(std::move(policies_to_report)); |
| } |
| |
| Vector<network::mojom::blink::ContentSecurityPolicyPtr> |
| ContentSecurityPolicy::Parse(const String& header, |
| const SecurityOrigin& self_origin, |
| ContentSecurityPolicyType type, |
| ContentSecurityPolicySource source) { |
| Vector<network::mojom::blink::ContentSecurityPolicyPtr> policies; |
| |
| // If this is a report-only header inside a <meta> element, bail out. |
| if (source == ContentSecurityPolicySource::kMeta && |
| type == ContentSecurityPolicyType::kReport) { |
| ReportReportOnlyInMeta(header); |
| return policies; |
| } |
| |
| Vector<UChar> characters; |
| header.AppendTo(characters); |
| |
| const UChar* begin = characters.data(); |
| const UChar* end = begin + characters.size(); |
| |
| // RFC2616, section 4.2 specifies that headers appearing multiple times can |
| // be combined with a comma. Walk the header string, and parse each comma |
| // separated chunk as a separate header. |
| const UChar* position = begin; |
| while (position < end) { |
| SkipUntil<UChar>(position, end, ','); |
| |
| // header1,header2 OR header1 |
| // ^ ^ |
| policies.push_back(CSPDirectiveListParse(this, begin, position, self_origin, |
| type, source)); |
| |
| // Skip the comma, and begin the next header from the current position. |
| DCHECK(position == end || *position == ','); |
| SkipExactly<UChar>(position, end, ','); |
| begin = position; |
| } |
| return policies; |
| } |
| |
| void ContentSecurityPolicy::ComputeInternalStateForParsedPolicy( |
| const network::mojom::blink::ContentSecurityPolicy& csp) { |
| if (csp.header->source == ContentSecurityPolicySource::kHTTP) |
| header_delivered_ = true; |
| |
| if (csp.block_all_mixed_content && !CSPDirectiveListIsReportOnly(csp)) |
| EnforceStrictMixedContentChecking(); |
| |
| if (CSPDirectiveListRequiresTrustedTypes(csp)) |
| RequireTrustedTypes(); |
| |
| EnforceSandboxFlags(csp.sandbox); |
| |
| if (csp.upgrade_insecure_requests) |
| UpgradeInsecureRequests(); |
| |
| String disable_eval_message; |
| if (CSPDirectiveListShouldDisableEval(csp, disable_eval_message) && |
| disable_eval_error_message_.IsNull()) { |
| disable_eval_error_message_ = disable_eval_message; |
| } |
| |
| for (const auto& directive : csp.directives) { |
| switch (directive.key) { |
| case CSPDirectiveName::DefaultSrc: |
| // TODO(mkwst) It seems unlikely that developers would use different |
| // algorithms for scripts and styles. We may want to combine the |
| // usesScriptHashAlgorithms() and usesStyleHashAlgorithms. |
| UsesScriptHashAlgorithms(HashAlgorithmsUsed(directive.value.get())); |
| UsesStyleHashAlgorithms(HashAlgorithmsUsed(directive.value.get())); |
| break; |
| case CSPDirectiveName::ScriptSrc: |
| case CSPDirectiveName::ScriptSrcAttr: |
| case CSPDirectiveName::ScriptSrcElem: |
| UsesScriptHashAlgorithms(HashAlgorithmsUsed(directive.value.get())); |
| break; |
| case CSPDirectiveName::StyleSrc: |
| case CSPDirectiveName::StyleSrcAttr: |
| case CSPDirectiveName::StyleSrcElem: |
| UsesStyleHashAlgorithms(HashAlgorithmsUsed(directive.value.get())); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| void ContentSecurityPolicy::ReportAccumulatedHeaders() const { |
| DCHECK(delegate_); |
| delegate_->DidAddContentSecurityPolicies(mojo::Clone(GetParsedPolicies())); |
| } |
| |
| void ContentSecurityPolicy::SetOverrideAllowInlineStyle(bool value) { |
| override_inline_style_allowed_ = value; |
| } |
| |
| // static |
| void ContentSecurityPolicy::FillInCSPHashValues( |
| const String& source, |
| uint8_t hash_algorithms_used, |
| Vector<network::mojom::blink::CSPHashSourcePtr>& csp_hash_values) { |
| // Any additions or subtractions from this struct should also modify the |
| // respective entries in the kSupportedPrefixes array in |
| // SourceListDirective::parseHash(). |
| static const struct { |
| network::mojom::blink::CSPHashAlgorithm csp_hash_algorithm; |
| HashAlgorithm algorithm; |
| } kAlgorithmMap[] = { |
| {network::mojom::blink::CSPHashAlgorithm::SHA256, kHashAlgorithmSha256}, |
| {network::mojom::blink::CSPHashAlgorithm::SHA384, kHashAlgorithmSha384}, |
| {network::mojom::blink::CSPHashAlgorithm::SHA512, kHashAlgorithmSha512}}; |
| |
| // Only bother normalizing the source/computing digests if there are any |
| // checks to be done. |
| if (hash_algorithms_used == kContentSecurityPolicyHashAlgorithmNone) |
| return; |
| |
| StringUTF8Adaptor utf8_source( |
| source, kStrictUTF8ConversionReplacingUnpairedSurrogatesWithFFFD); |
| |
| for (const auto& algorithm_map : kAlgorithmMap) { |
| DigestValue digest; |
| if (static_cast<int32_t>(algorithm_map.csp_hash_algorithm) & |
| hash_algorithms_used) { |
| bool digest_success = |
| ComputeDigest(algorithm_map.algorithm, utf8_source.data(), |
| utf8_source.size(), digest); |
| if (digest_success) { |
| csp_hash_values.push_back(network::mojom::blink::CSPHashSource::New( |
| algorithm_map.csp_hash_algorithm, Vector<uint8_t>(digest))); |
| } |
| } |
| } |
| } |
| |
| // static |
| bool ContentSecurityPolicy::CheckHashAgainstPolicy( |
| Vector<network::mojom::blink::CSPHashSourcePtr>& csp_hash_values, |
| const network::mojom::blink::ContentSecurityPolicy& csp, |
| InlineType inline_type) { |
| for (const auto& csp_hash_value : csp_hash_values) { |
| if (CSPDirectiveListAllowHash(csp, *csp_hash_value, inline_type)) |
| return true; |
| } |
| return false; |
| } |
| |
| // https://w3c.github.io/webappsec-csp/#should-block-inline |
| bool ContentSecurityPolicy::AllowInline( |
| InlineType inline_type, |
| Element* element, |
| const String& content, |
| const String& nonce, |
| const String& context_url, |
| const WTF::OrdinalNumber& context_line, |
| ReportingDisposition reporting_disposition) { |
| DCHECK(element || inline_type == InlineType::kScriptAttribute || |
| inline_type == InlineType::kNavigation); |
| |
| const bool is_script = IsScriptInlineType(inline_type); |
| if (!is_script && override_inline_style_allowed_) { |
| return true; |
| } |
| |
| Vector<network::mojom::blink::CSPHashSourcePtr> csp_hash_values; |
| FillInCSPHashValues( |
| content, |
| is_script ? script_hash_algorithms_used_ : style_hash_algorithms_used_, |
| csp_hash_values); |
| |
| // Step 2. Let result be "Allowed". [spec text] |
| bool is_allowed = true; |
| |
| // Step 3. For each policy in element’s Document's global object’s CSP list: |
| // [spec text] |
| for (const auto& policy : policies_) { |
| // May be allowed by hash, if 'unsafe-hashes' is present in a policy. |
| // Check against the digest of the |content| and also check whether inline |
| // script is allowed. |
| is_allowed &= |
| CheckHashAgainstPolicy(csp_hash_values, *policy, inline_type) || |
| CSPDirectiveListAllowInline(*policy, this, inline_type, element, |
| content, nonce, context_url, context_line, |
| reporting_disposition); |
| } |
| |
| return is_allowed; |
| } |
| |
| bool ContentSecurityPolicy::IsScriptInlineType(InlineType inline_type) { |
| switch (inline_type) { |
| case ContentSecurityPolicy::InlineType::kNavigation: |
| case ContentSecurityPolicy::InlineType::kScriptAttribute: |
| case ContentSecurityPolicy::InlineType::kScript: |
| return true; |
| |
| case ContentSecurityPolicy::InlineType::kStyleAttribute: |
| case ContentSecurityPolicy::InlineType::kStyle: |
| return false; |
| } |
| } |
| |
| bool ContentSecurityPolicy::ShouldCheckEval() const { |
| for (const auto& policy : policies_) { |
| if (CSPDirectiveListShouldCheckEval(*policy)) |
| return true; |
| } |
| return IsRequireTrustedTypes(); |
| } |
| |
| bool ContentSecurityPolicy::AllowEval( |
| ReportingDisposition reporting_disposition, |
| ContentSecurityPolicy::ExceptionStatus exception_status, |
| const String& script_content) { |
| bool is_allowed = true; |
| for (const auto& policy : policies_) { |
| is_allowed &= CSPDirectiveListAllowEval( |
| *policy, this, reporting_disposition, exception_status, script_content); |
| } |
| return is_allowed; |
| } |
| |
| bool ContentSecurityPolicy::AllowWasmEval( |
| ReportingDisposition reporting_disposition, |
| ContentSecurityPolicy::ExceptionStatus exception_status, |
| const String& script_content) { |
| bool is_allowed = true; |
| for (const auto& policy : policies_) { |
| is_allowed &= CSPDirectiveListAllowWasmEval( |
| *policy, this, reporting_disposition, exception_status, script_content); |
| } |
| return is_allowed; |
| } |
| |
| String ContentSecurityPolicy::EvalDisabledErrorMessage() const { |
| for (const auto& policy : policies_) { |
| String message; |
| if (CSPDirectiveListShouldDisableEval(*policy, message)) |
| return message; |
| } |
| return String(); |
| } |
| |
| static base::Optional<CSPDirectiveName> GetDirectiveTypeFromRequestContextType( |
| mojom::blink::RequestContextType context) { |
| switch (context) { |
| case mojom::blink::RequestContextType::AUDIO: |
| case mojom::blink::RequestContextType::TRACK: |
| case mojom::blink::RequestContextType::VIDEO: |
| return CSPDirectiveName::MediaSrc; |
| |
| case mojom::blink::RequestContextType::BEACON: |
| case mojom::blink::RequestContextType::EVENT_SOURCE: |
| case mojom::blink::RequestContextType::FETCH: |
| case mojom::blink::RequestContextType::PING: |
| case mojom::blink::RequestContextType::XML_HTTP_REQUEST: |
| case mojom::blink::RequestContextType::SUBRESOURCE: |
| case mojom::blink::RequestContextType::SUBRESOURCE_WEBBUNDLE: |
| return CSPDirectiveName::ConnectSrc; |
| |
| case mojom::blink::RequestContextType::EMBED: |
| case mojom::blink::RequestContextType::OBJECT: |
| return CSPDirectiveName::ObjectSrc; |
| |
| case mojom::blink::RequestContextType::PREFETCH: |
| return CSPDirectiveName::PrefetchSrc; |
| |
| case mojom::blink::RequestContextType::FAVICON: |
| case mojom::blink::RequestContextType::IMAGE: |
| case mojom::blink::RequestContextType::IMAGE_SET: |
| return CSPDirectiveName::ImgSrc; |
| |
| case mojom::blink::RequestContextType::FONT: |
| return CSPDirectiveName::FontSrc; |
| |
| case mojom::blink::RequestContextType::FORM: |
| return CSPDirectiveName::FormAction; |
| |
| case mojom::blink::RequestContextType::FRAME: |
| case mojom::blink::RequestContextType::IFRAME: |
| return CSPDirectiveName::FrameSrc; |
| |
| case mojom::blink::RequestContextType::IMPORT: |
| case mojom::blink::RequestContextType::SCRIPT: |
| case mojom::blink::RequestContextType::XSLT: |
| return CSPDirectiveName::ScriptSrcElem; |
| |
| case mojom::blink::RequestContextType::MANIFEST: |
| return CSPDirectiveName::ManifestSrc; |
| |
| case mojom::blink::RequestContextType::SERVICE_WORKER: |
| case mojom::blink::RequestContextType::SHARED_WORKER: |
| case mojom::blink::RequestContextType::WORKER: |
| return CSPDirectiveName::WorkerSrc; |
| |
| case mojom::blink::RequestContextType::STYLE: |
| return CSPDirectiveName::StyleSrcElem; |
| |
| case mojom::blink::RequestContextType::CSP_REPORT: |
| case mojom::blink::RequestContextType::DOWNLOAD: |
| case mojom::blink::RequestContextType::HYPERLINK: |
| case mojom::blink::RequestContextType::INTERNAL: |
| case mojom::blink::RequestContextType::LOCATION: |
| case mojom::blink::RequestContextType::PLUGIN: |
| case mojom::blink::RequestContextType::UNSPECIFIED: |
| return base::nullopt; |
| } |
| } |
| |
| bool ContentSecurityPolicy::AllowRequest( |
| mojom::blink::RequestContextType context, |
| network::mojom::RequestDestination request_destination, |
| const KURL& url, |
| const String& nonce, |
| const IntegrityMetadataSet& integrity_metadata, |
| ParserDisposition parser_disposition, |
| const KURL& url_before_redirects, |
| RedirectStatus redirect_status, |
| ReportingDisposition reporting_disposition, |
| CheckHeaderType check_header_type) { |
| base::Optional<CSPDirectiveName> type = |
| GetDirectiveTypeFromRequestContextType(context); |
| |
| if (!type) |
| return true; |
| return AllowFromSource(*type, url, url_before_redirects, redirect_status, |
| reporting_disposition, check_header_type, nonce, |
| integrity_metadata, parser_disposition); |
| } |
| |
| void ContentSecurityPolicy::UsesScriptHashAlgorithms(uint8_t algorithms) { |
| script_hash_algorithms_used_ |= algorithms; |
| } |
| |
| void ContentSecurityPolicy::UsesStyleHashAlgorithms(uint8_t algorithms) { |
| style_hash_algorithms_used_ |= algorithms; |
| } |
| |
| bool ContentSecurityPolicy::AllowFromSource( |
| CSPDirectiveName type, |
| const KURL& url, |
| const KURL& url_before_redirects, |
| RedirectStatus redirect_status, |
| ReportingDisposition reporting_disposition, |
| CheckHeaderType check_header_type, |
| const String& nonce, |
| const IntegrityMetadataSet& hashes, |
| ParserDisposition parser_disposition) { |
| SchemeRegistry::PolicyAreas area = SchemeRegistry::kPolicyAreaAll; |
| if (type == CSPDirectiveName::ImgSrc) |
| area = SchemeRegistry::kPolicyAreaImage; |
| else if (type == CSPDirectiveName::StyleSrcElem) |
| area = SchemeRegistry::kPolicyAreaStyle; |
| |
| if (ShouldBypassContentSecurityPolicy(url, area)) { |
| if (type != CSPDirectiveName::ScriptSrcElem) |
| return true; |
| |
| Count(parser_disposition == kParserInserted |
| ? WebFeature::kScriptWithCSPBypassingSchemeParserInserted |
| : WebFeature::kScriptWithCSPBypassingSchemeNotParserInserted); |
| |
| // If we're running experimental features, bypass CSP only for |
| // non-parser-inserted resources whose scheme otherwise bypasses CSP. If |
| // we're not running experimental features, bypass CSP for all resources |
| // regardless of parser state. Once we have more data via the |
| // 'ScriptWithCSPBypassingScheme*' metrics, make a decision about what |
| // behavior to ship. https://crbug.com/653521 |
| if ((parser_disposition == kNotParserInserted || |
| !ExperimentalFeaturesEnabled()) && |
| // The schemes where javascript:-URLs are blocked are usually |
| // privileged pages, so do not allow the CSP to be bypassed either. |
| !SchemeRegistry::ShouldTreatURLSchemeAsNotAllowingJavascriptURLs( |
| delegate_->GetSecurityOrigin()->Protocol())) { |
| return true; |
| } |
| } |
| |
| bool is_allowed = true; |
| for (const auto& policy : policies_) { |
| if (!CheckHeaderTypeMatches(check_header_type, policy->header->type)) |
| continue; |
| is_allowed &= CSPDirectiveListAllowFromSource( |
| *policy, this, type, url, url_before_redirects, redirect_status, |
| reporting_disposition, nonce, hashes, parser_disposition); |
| } |
| |
| return is_allowed; |
| } |
| |
| bool ContentSecurityPolicy::AllowBaseURI(const KURL& url) { |
| // `base-uri` isn't affected by 'upgrade-insecure-requests', so we use |
| // CheckHeaderType::kCheckAll to check both report-only and enforce headers |
| // here. |
| return AllowFromSource(CSPDirectiveName::BaseURI, url, url, |
| RedirectStatus::kNoRedirect); |
| } |
| |
| bool ContentSecurityPolicy::AllowConnectToSource( |
| const KURL& url, |
| const KURL& url_before_redirects, |
| RedirectStatus redirect_status, |
| ReportingDisposition reporting_disposition, |
| CheckHeaderType check_header_type) { |
| return AllowFromSource(CSPDirectiveName::ConnectSrc, url, |
| url_before_redirects, redirect_status, |
| reporting_disposition, check_header_type); |
| } |
| |
| bool ContentSecurityPolicy::AllowFormAction(const KURL& url) { |
| return AllowFromSource(CSPDirectiveName::FormAction, url, url, |
| RedirectStatus::kNoRedirect); |
| } |
| |
| bool ContentSecurityPolicy::AllowImageFromSource( |
| const KURL& url, |
| const KURL& url_before_redirects, |
| RedirectStatus redirect_status, |
| ReportingDisposition reporting_disposition, |
| CheckHeaderType check_header_type) { |
| return AllowFromSource(CSPDirectiveName::ImgSrc, url, url_before_redirects, |
| redirect_status, reporting_disposition, |
| check_header_type); |
| } |
| |
| bool ContentSecurityPolicy::AllowMediaFromSource(const KURL& url) { |
| return AllowFromSource(CSPDirectiveName::MediaSrc, url, url, |
| RedirectStatus::kNoRedirect); |
| } |
| |
| bool ContentSecurityPolicy::AllowObjectFromSource(const KURL& url) { |
| return AllowFromSource(CSPDirectiveName::ObjectSrc, url, url, |
| RedirectStatus::kNoRedirect); |
| } |
| |
| bool ContentSecurityPolicy::AllowScriptFromSource( |
| const KURL& url, |
| const String& nonce, |
| const IntegrityMetadataSet& hashes, |
| ParserDisposition parser_disposition, |
| const KURL& url_before_redirects, |
| RedirectStatus redirect_status, |
| ReportingDisposition reporting_disposition, |
| CheckHeaderType check_header_type) { |
| return AllowFromSource(CSPDirectiveName::ScriptSrcElem, url, |
| url_before_redirects, redirect_status, |
| reporting_disposition, check_header_type, nonce, |
| hashes, parser_disposition); |
| } |
| |
| bool ContentSecurityPolicy::AllowWorkerContextFromSource(const KURL& url) { |
| return AllowFromSource(CSPDirectiveName::WorkerSrc, url, url, |
| RedirectStatus::kNoRedirect); |
| } |
| |
| // The return value indicates whether the policy is allowed or not. |
| // If the return value is false, the out-parameter violation_details indicates |
| // the type of the violation, and if the return value is true, |
| // it indicates if a report-only violation occurred. |
| bool ContentSecurityPolicy::AllowTrustedTypePolicy( |
| const String& policy_name, |
| bool is_duplicate, |
| AllowTrustedTypePolicyDetails& violation_details) { |
| bool is_allowed = true; |
| violation_details = AllowTrustedTypePolicyDetails::kAllowed; |
| for (const auto& policy : policies_) { |
| if (!CheckHeaderTypeMatches(CheckHeaderType::kCheckAll, |
| policy->header->type)) { |
| continue; |
| } |
| auto new_violation_details = AllowTrustedTypePolicyDetails::kAllowed; |
| bool new_allowed = CSPDirectiveListAllowTrustedTypePolicy( |
| *policy, this, policy_name, is_duplicate, new_violation_details); |
| // Report the first violation that is enforced. |
| // If there is none, report the first violation that is report-only. |
| if ((is_allowed && !new_allowed) || |
| violation_details == AllowTrustedTypePolicyDetails::kAllowed) { |
| violation_details = new_violation_details; |
| } |
| is_allowed &= new_allowed; |
| } |
| |
| return is_allowed; |
| } |
| |
| bool ContentSecurityPolicy::AllowTrustedTypeAssignmentFailure( |
| const String& message, |
| const String& sample, |
| const String& sample_prefix) { |
| bool allow = true; |
| for (const auto& policy : policies_) { |
| allow &= CSPDirectiveListAllowTrustedTypeAssignmentFailure( |
| *policy, this, message, sample, sample_prefix); |
| } |
| return allow; |
| } |
| |
| bool ContentSecurityPolicy::IsActive() const { |
| return !policies_.IsEmpty(); |
| } |
| |
| bool ContentSecurityPolicy::IsActiveForConnections() const { |
| for (const auto& policy : policies_) { |
| if (CSPDirectiveListIsActiveForConnections(*policy)) |
| return true; |
| } |
| return false; |
| } |
| |
| const KURL ContentSecurityPolicy::FallbackUrlForPlugin() const { |
| return delegate_ ? delegate_->Url() : KURL(); |
| } |
| |
| void ContentSecurityPolicy::EnforceSandboxFlags( |
| network::mojom::blink::WebSandboxFlags mask) { |
| sandbox_mask_ |= mask; |
| } |
| |
| void ContentSecurityPolicy::RequireTrustedTypes() { |
| // We store whether CSP demands a policy. The caller still needs to check |
| // whether the feature is enabled in the first place. |
| require_trusted_types_ = true; |
| } |
| |
| void ContentSecurityPolicy::EnforceStrictMixedContentChecking() { |
| insecure_request_policy_ |= |
| mojom::blink::InsecureRequestPolicy::kBlockAllMixedContent; |
| } |
| |
| void ContentSecurityPolicy::UpgradeInsecureRequests() { |
| insecure_request_policy_ |= |
| mojom::blink::InsecureRequestPolicy::kUpgradeInsecureRequests; |
| } |
| |
| static String StripURLForUseInReport(const SecurityOrigin* security_origin, |
| const KURL& url, |
| RedirectStatus redirect_status, |
| CSPDirectiveName effective_type) { |
| if (!url.IsValid()) |
| return String(); |
| if (!url.IsHierarchical() || url.ProtocolIs("file")) |
| return url.Protocol(); |
| |
| // Until we're more careful about the way we deal with navigations in frames |
| // (and, by extension, in plugin documents), strip cross-origin 'frame-src' |
| // and 'object-src' violations down to an origin. https://crbug.com/633306 |
| bool can_safely_expose_url = |
| security_origin->CanRequest(url) || |
| (redirect_status == RedirectStatus::kNoRedirect && |
| effective_type != CSPDirectiveName::FrameSrc && |
| effective_type != CSPDirectiveName::ObjectSrc); |
| |
| if (can_safely_expose_url) { |
| // 'KURL::strippedForUseAsReferrer()' dumps 'String()' for non-webby URLs. |
| // It's better for developers if we return the origin of those URLs rather |
| // than nothing. |
| if (url.ProtocolIsInHTTPFamily()) |
| return url.StrippedForUseAsReferrer(); |
| } |
| return SecurityOrigin::Create(url)->ToString(); |
| } |
| |
| namespace { |
| std::unique_ptr<SourceLocation> GatherSecurityPolicyViolationEventData( |
| SecurityPolicyViolationEventInit* init, |
| ContentSecurityPolicyDelegate* delegate, |
| const String& directive_text, |
| CSPDirectiveName effective_type, |
| const KURL& blocked_url, |
| const String& header, |
| RedirectStatus redirect_status, |
| ContentSecurityPolicyType header_type, |
| ContentSecurityPolicy::ContentSecurityPolicyViolationType violation_type, |
| std::unique_ptr<SourceLocation> source_location, |
| const String& script_source, |
| const String& sample_prefix) { |
| if (effective_type == CSPDirectiveName::FrameAncestors) { |
| // If this load was blocked via 'frame-ancestors', then the URL of |
| // |document| has not yet been initialized. In this case, we'll set both |
| // 'documentURI' and 'blockedURI' to the blocked document's URL. |
| String stripped_url = StripURLForUseInReport( |
| delegate->GetSecurityOrigin(), blocked_url, RedirectStatus::kNoRedirect, |
| CSPDirectiveName::DefaultSrc); |
| init->setDocumentURI(stripped_url); |
| init->setBlockedURI(stripped_url); |
| } else { |
| String stripped_url = StripURLForUseInReport( |
| delegate->GetSecurityOrigin(), delegate->Url(), |
| RedirectStatus::kNoRedirect, CSPDirectiveName::DefaultSrc); |
| init->setDocumentURI(stripped_url); |
| switch (violation_type) { |
| case ContentSecurityPolicy::kInlineViolation: |
| init->setBlockedURI("inline"); |
| break; |
| case ContentSecurityPolicy::kEvalViolation: |
| init->setBlockedURI("eval"); |
| break; |
| case ContentSecurityPolicy::kURLViolation: |
| // We pass RedirectStatus::kNoRedirect so that StripURLForUseInReport |
| // does not strip path and query from the URL. This is safe since |
| // blocked_url at this point is always the original url (before |
| // redirects). |
| init->setBlockedURI(StripURLForUseInReport( |
| delegate->GetSecurityOrigin(), blocked_url, |
| RedirectStatus::kNoRedirect, effective_type)); |
| break; |
| case ContentSecurityPolicy::kTrustedTypesSinkViolation: |
| init->setBlockedURI("trusted-types-sink"); |
| break; |
| case ContentSecurityPolicy::kTrustedTypesPolicyViolation: |
| init->setBlockedURI("trusted-types-policy"); |
| break; |
| } |
| } |
| |
| String effective_directive = |
| ContentSecurityPolicy::GetDirectiveName(effective_type); |
| init->setViolatedDirective(effective_directive); |
| init->setEffectiveDirective(effective_directive); |
| init->setOriginalPolicy(header); |
| init->setDisposition(header_type == ContentSecurityPolicyType::kEnforce |
| ? "enforce" |
| : "report"); |
| init->setStatusCode(0); |
| |
| // See https://w3c.github.io/webappsec-csp/#create-violation-for-global. |
| // Step 3. If global is a Window object, set violation’s referrer to global’s |
| // document's referrer. [spec text] |
| String referrer = delegate->GetDocumentReferrer(); |
| if (referrer) |
| init->setReferrer(referrer); |
| |
| // Step 4. Set violation’s status to the HTTP status code for the resource |
| // associated with violation’s global object. [spec text] |
| base::Optional<uint16_t> status_code = delegate->GetStatusCode(); |
| if (status_code) |
| init->setStatusCode(*status_code); |
| |
| // If no source location is provided, use the source location from the |
| // |delegate|. |
| // Step 2. If the user agent is currently executing script, and can extract a |
| // source file’s URL, line number, and column number from the global, set |
| // violation’s source file, line number, and column number accordingly. |
| // [spec text] |
| if (!source_location) |
| source_location = delegate->GetSourceLocation(); |
| if (source_location && source_location->LineNumber()) { |
| KURL source_url = KURL(source_location->Url()); |
| // The source file might be a script loaded from a redirect. Web browser |
| // usually tries to hide post-redirect information. The script might be |
| // cross-origin with the document, but also with other scripts. As a result, |
| // everything is cleared no matter the |source_url| origin. |
| // See https://crbug.com/1074317 |
| // |
| // Note: The username, password and ref are stripped later below by |
| // StripURLForUseInReport(..) |
| source_url.SetQuery(String()); |
| |
| // The |source_url| is the URL of the script that triggered the CSP |
| // violation. It is the URL pre-redirect. So it is safe to expose it in |
| // reports without leaking any new informations to the document. See |
| // https://crrev.com/c/2187792. |
| String source_file = |
| StripURLForUseInReport(delegate->GetSecurityOrigin(), source_url, |
| RedirectStatus::kNoRedirect, effective_type); |
| |
| init->setSourceFile(source_file); |
| init->setLineNumber(source_location->LineNumber()); |
| init->setColumnNumber(source_location->ColumnNumber()); |
| } else { |
| init->setSourceFile(String()); |
| init->setLineNumber(0); |
| init->setColumnNumber(0); |
| } |
| |
| // Build the sample string. CSP demands that the sample is restricted to |
| // 40 characters (kMaxSampleLength), to prevent inadvertent exfiltration of |
| // user data. For some use cases, we also have a sample prefix, which |
| // must not depend on user data and where we will apply the sample limit |
| // separately. |
| StringBuilder sample; |
| if (!sample_prefix.IsEmpty()) { |
| sample.Append(sample_prefix.StripWhiteSpace().Left( |
| ContentSecurityPolicy::kMaxSampleLength)); |
| sample.Append("|"); |
| } |
| if (!script_source.IsEmpty()) { |
| sample.Append(script_source.StripWhiteSpace().Left( |
| ContentSecurityPolicy::kMaxSampleLength)); |
| } |
| if (!sample.IsEmpty()) |
| init->setSample(sample.ToString()); |
| |
| return source_location; |
| } |
| } // namespace |
| |
| void ContentSecurityPolicy::ReportViolation( |
| const String& directive_text, |
| CSPDirectiveName effective_type, |
| const String& console_message, |
| const KURL& blocked_url, |
| const Vector<String>& report_endpoints, |
| bool use_reporting_api, |
| const String& header, |
| ContentSecurityPolicyType header_type, |
| ContentSecurityPolicyViolationType violation_type, |
| std::unique_ptr<SourceLocation> source_location, |
| LocalFrame* context_frame, |
| RedirectStatus redirect_status, |
| Element* element, |
| const String& source, |
| const String& source_prefix) { |
| DCHECK(violation_type == kURLViolation || blocked_url.IsEmpty()); |
| |
| // TODO(lukasza): Support sending reports from OOPIFs - |
| // https://crbug.com/611232 (or move CSP child-src and frame-src checks to the |
| // browser process - see https://crbug.com/376522). |
| if (!delegate_ && !context_frame) { |
| DCHECK(effective_type == CSPDirectiveName::ChildSrc || |
| effective_type == CSPDirectiveName::FrameSrc || |
| effective_type == CSPDirectiveName::TrustedTypes || |
| effective_type == CSPDirectiveName::RequireTrustedTypesFor); |
| return; |
| } |
| DCHECK( |
| (delegate_ && !context_frame) || |
| ((effective_type == CSPDirectiveName::FrameAncestors) && context_frame)); |
| |
| SecurityPolicyViolationEventInit* violation_data = |
| SecurityPolicyViolationEventInit::Create(); |
| |
| // If we're processing 'frame-ancestors', use the delegate for the |
| // |context_frame|'s document to gather data. Otherwise, use the policy's |
| // |delegate_|. |
| ContentSecurityPolicyDelegate* relevant_delegate = |
| context_frame |
| ? &context_frame->DomWindow()->GetContentSecurityPolicyDelegate() |
| : delegate_.Get(); |
| DCHECK(relevant_delegate); |
| // Let GatherSecurityPolicyViolationEventData decide which source location to |
| // report. |
| source_location = GatherSecurityPolicyViolationEventData( |
| violation_data, relevant_delegate, directive_text, effective_type, |
| blocked_url, header, redirect_status, header_type, violation_type, |
| std::move(source_location), source, source_prefix); |
| |
| // TODO(mkwst): Obviously, we shouldn't hit this check, as extension-loaded |
| // resources should be allowed regardless. We apparently do, however, so |
| // we should at least stop spamming reporting endpoints. See |
| // https://crbug.com/524356 for detail. |
| if (!violation_data->sourceFile().IsEmpty() && |
| ShouldBypassContentSecurityPolicy(KURL(violation_data->sourceFile()))) { |
| return; |
| } |
| |
| PostViolationReport(violation_data, context_frame, report_endpoints, |
| use_reporting_api); |
| |
| // Fire a violation event if we're working with a delegate (e.g. we're not |
| // processing 'frame-ancestors'). |
| if (delegate_) |
| delegate_->DispatchViolationEvent(*violation_data, element); |
| |
| ReportContentSecurityPolicyIssue(*violation_data, header_type, violation_type, |
| context_frame, element, |
| source_location.get()); |
| } |
| |
| void ContentSecurityPolicy::PostViolationReport( |
| const SecurityPolicyViolationEventInit* violation_data, |
| LocalFrame* context_frame, |
| const Vector<String>& report_endpoints, |
| bool use_reporting_api) { |
| // We need to be careful here when deciding what information to send to the |
| // report-uri. Currently, we send only the current document's URL and the |
| // directive that was violated. The document's URL is safe to send because |
| // it's the document itself that's requesting that it be sent. You could |
| // make an argument that we shouldn't send HTTPS document URLs to HTTP |
| // report-uris (for the same reasons that we supress the Referer in that |
| // case), but the Referer is sent implicitly whereas this request is only |
| // sent explicitly. As for which directive was violated, that's pretty |
| // harmless information. |
| // |
| // TODO(mkwst): This justification is BS. Insecure reports are mixed content, |
| // let's kill them. https://crbug.com/695363 |
| |
| auto csp_report = std::make_unique<JSONObject>(); |
| csp_report->SetString("document-uri", violation_data->documentURI()); |
| csp_report->SetString("referrer", violation_data->referrer()); |
| csp_report->SetString("violated-directive", |
| violation_data->violatedDirective()); |
| csp_report->SetString("effective-directive", |
| violation_data->effectiveDirective()); |
| csp_report->SetString("original-policy", violation_data->originalPolicy()); |
| csp_report->SetString("disposition", violation_data->disposition()); |
| csp_report->SetString("blocked-uri", violation_data->blockedURI()); |
| if (violation_data->lineNumber()) |
| csp_report->SetInteger("line-number", violation_data->lineNumber()); |
| if (violation_data->columnNumber()) |
| csp_report->SetInteger("column-number", violation_data->columnNumber()); |
| if (!violation_data->sourceFile().IsEmpty()) |
| csp_report->SetString("source-file", violation_data->sourceFile()); |
| csp_report->SetInteger("status-code", violation_data->statusCode()); |
| |
| csp_report->SetString("script-sample", violation_data->sample()); |
| |
| auto report_object = std::make_unique<JSONObject>(); |
| report_object->SetObject("csp-report", std::move(csp_report)); |
| String stringified_report = report_object->ToJSONString(); |
| |
| // Only POST unique reports to the external endpoint; repeated reports add no |
| // value on the server side, as they're indistinguishable. Note that we'll |
| // fire the DOM event for every violation, as the page has enough context to |
| // react in some reasonable way to each violation as it occurs. |
| if (ShouldSendViolationReport(stringified_report)) { |
| DidSendViolationReport(stringified_report); |
| |
| // If we're processing 'frame-ancestors', use the delegate for the |
| // |context_frame|'s document to post violation report. Otherwise, use the |
| // policy's |delegate_|. |
| bool is_frame_ancestors_violation = !!context_frame; |
| ContentSecurityPolicyDelegate* relevant_delegate = |
| is_frame_ancestors_violation |
| ? &context_frame->DomWindow()->GetContentSecurityPolicyDelegate() |
| : delegate_.Get(); |
| DCHECK(relevant_delegate); |
| |
| relevant_delegate->PostViolationReport(*violation_data, stringified_report, |
| is_frame_ancestors_violation, |
| report_endpoints, use_reporting_api); |
| } |
| } |
| |
| void ContentSecurityPolicy::ReportMixedContent(const KURL& blocked_url, |
| RedirectStatus redirect_status) { |
| for (const auto& policy : policies_) { |
| if (policy->block_all_mixed_content) { |
| ReportViolation(GetDirectiveName(CSPDirectiveName::BlockAllMixedContent), |
| CSPDirectiveName::BlockAllMixedContent, String(), |
| blocked_url, policy->report_endpoints, |
| policy->use_reporting_api, policy->header->header_value, |
| policy->header->type, |
| ContentSecurityPolicy::kURLViolation, |
| std::unique_ptr<SourceLocation>(), |
| /*contextFrame=*/nullptr, redirect_status); |
| } |
| } |
| } |
| |
| void ContentSecurityPolicy::ReportReportOnlyInMeta(const String& header) { |
| LogToConsole("The report-only Content Security Policy '" + header + |
| "' was delivered via a <meta> element, which is disallowed. The " |
| "policy has been ignored."); |
| } |
| |
| void ContentSecurityPolicy::ReportMetaOutsideHead(const String& header) { |
| LogToConsole("The Content Security Policy '" + header + |
| "' was delivered via a <meta> element outside the document's " |
| "<head>, which is disallowed. The policy has been ignored."); |
| } |
| |
| void ContentSecurityPolicy::ReportValueForEmptyDirective(const String& name, |
| const String& value) { |
| LogToConsole("The Content Security Policy directive '" + name + |
| "' should be empty, but was delivered with a value of '" + |
| value + |
| "'. The directive has been applied, and the value ignored."); |
| } |
| |
| void ContentSecurityPolicy::ReportMixedContentReportURI( |
| const String& endpoint) { |
| LogToConsole("The Content Security Policy directive specifies as endpoint '" + |
| endpoint + |
| "'. This endpoint will be ignored since it violates the policy " |
| "for Mixed Content."); |
| } |
| |
| void ContentSecurityPolicy::ReportInvalidInReportOnly(const String& name) { |
| LogToConsole("The Content Security Policy directive '" + name + |
| "' is ignored when delivered in a report-only policy."); |
| } |
| |
| void ContentSecurityPolicy::ReportInvalidDirectiveInMeta( |
| const String& directive) { |
| LogToConsole( |
| "Content Security Policies delivered via a <meta> element may not " |
| "contain the " + |
| directive + " directive."); |
| } |
| |
| void ContentSecurityPolicy::ReportUnsupportedDirective(const String& name) { |
| static const char kAllow[] = "allow"; |
| static const char kOptions[] = "options"; |
| static const char kPolicyURI[] = "policy-uri"; |
| static const char kPluginTypes[] = "plugin-types"; |
| static const char kAllowMessage[] = |
| "The 'allow' directive has been replaced with 'default-src'. Please use " |
| "that directive instead, as 'allow' has no effect."; |
| static const char kOptionsMessage[] = |
| "The 'options' directive has been replaced with 'unsafe-inline' and " |
| "'unsafe-eval' source expressions for the 'script-src' and 'style-src' " |
| "directives. Please use those directives instead, as 'options' has no " |
| "effect."; |
| static const char kPolicyURIMessage[] = |
| "The 'policy-uri' directive has been removed from the " |
| "specification. Please specify a complete policy via " |
| "the Content-Security-Policy header."; |
| static const char kPluginTypesMessage[] = |
| "The Content-Security-Policy directive 'plugin-types' has been removed " |
| "from the specification. " |
| "If you want to block plugins, consider specifying \"object-src 'none'\" " |
| "instead."; |
| |
| String message = |
| "Unrecognized Content-Security-Policy directive '" + name + "'.\n"; |
| mojom::ConsoleMessageLevel level = mojom::ConsoleMessageLevel::kError; |
| if (EqualIgnoringASCIICase(name, kAllow)) { |
| message = kAllowMessage; |
| } else if (EqualIgnoringASCIICase(name, kOptions)) { |
| message = kOptionsMessage; |
| } else if (EqualIgnoringASCIICase(name, kPolicyURI)) { |
| message = kPolicyURIMessage; |
| } else if (EqualIgnoringASCIICase(name, kPluginTypes)) { |
| message = kPluginTypesMessage; |
| } else if (GetDirectiveType(name) != CSPDirectiveName::Unknown) { |
| message = "The Content-Security-Policy directive '" + name + |
| "' is implemented behind a flag which is currently disabled.\n"; |
| level = mojom::ConsoleMessageLevel::kInfo; |
| } |
| |
| LogToConsole(message, level); |
| } |
| |
| void ContentSecurityPolicy::ReportDirectiveAsSourceExpression( |
| const String& directive_name, |
| const String& source_expression) { |
| String message = "The Content Security Policy directive '" + directive_name + |
| "' contains '" + source_expression + |
| "' as a source expression. Did you mean '" + directive_name + |
| " ...; " + source_expression + "...' (note the semicolon)?"; |
| LogToConsole(message); |
| } |
| |
| void ContentSecurityPolicy::ReportDuplicateDirective(const String& name) { |
| String message = |
| "Ignoring duplicate Content-Security-Policy directive '" + name + "'.\n"; |
| LogToConsole(message); |
| } |
| |
| void ContentSecurityPolicy::ReportInvalidRequireTrustedTypesFor( |
| const String& require_trusted_types_for) { |
| String message; |
| if (require_trusted_types_for.IsNull()) { |
| message = |
| "'require-trusted-types-for' Content Security Policy directive is " |
| "empty; The directive has no effect.\n"; |
| } else { |
| const char* hint = ""; |
| if (require_trusted_types_for == "script" || |
| require_trusted_types_for == "scripts" || |
| require_trusted_types_for == "'scripts'") { |
| hint = " Did you mean 'script'?"; |
| } |
| |
| message = |
| "Invalid expression in 'require-trusted-types-for' " |
| "Content Security Policy directive: " + |
| require_trusted_types_for + "." + hint + "\n"; |
| } |
| LogToConsole(message, mojom::ConsoleMessageLevel::kWarning); |
| } |
| |
| void ContentSecurityPolicy::ReportInvalidSandboxFlags( |
| const String& invalid_flags) { |
| LogToConsole( |
| "Error while parsing the 'sandbox' Content Security Policy directive: " + |
| invalid_flags); |
| } |
| |
| void ContentSecurityPolicy::ReportInvalidDirectiveValueCharacter( |
| const String& directive_name, |
| const String& value) { |
| String message = |
| "The value for Content Security Policy directive '" + directive_name + |
| "' contains an invalid character: '" + value + |
| "'. In a source expression, non-whitespace characters outside ASCII " |
| "0x21-0x7E must be Punycode-encoded, as described in RFC 3492 " |
| "(https://tools.ietf.org/html/rfc3492), if part of the hostname and " |
| "percent-encoded, as described in RFC 3986, section 2.1 " |
| "(http://tools.ietf.org/html/rfc3986#section-2.1), if part of the path."; |
| LogToConsole(message); |
| } |
| |
| void ContentSecurityPolicy::ReportInvalidPathCharacter( |
| const String& directive_name, |
| const String& value, |
| const char invalid_char) { |
| DCHECK(invalid_char == '#' || invalid_char == '?'); |
| |
| String ignoring = |
| "The fragment identifier, including the '#', will be ignored."; |
| if (invalid_char == '?') |
| ignoring = "The query component, including the '?', will be ignored."; |
| String message = "The source list for Content Security Policy directive '" + |
| directive_name + |
| "' contains a source with an invalid path: '" + value + |
| "'. " + ignoring; |
| LogToConsole(message); |
| } |
| |
| void ContentSecurityPolicy::ReportInvalidSourceExpression( |
| const String& directive_name, |
| const String& source) { |
| String message = "The source list for Content Security Policy directive '" + |
| directive_name + "' contains an invalid source: '" + source + |
| "'. It will be ignored."; |
| if (EqualIgnoringASCIICase(source, "'none'")) |
| message = message + |
| " Note that 'none' has no effect unless it is the only " |
| "expression in the source list."; |
| LogToConsole(message); |
| } |
| |
| void ContentSecurityPolicy::ReportMultipleReportToEndpoints() { |
| LogToConsole( |
| "The Content Security Policy directive 'report-to' contains more than " |
| "one endpoint. Only the first one will be used, the other ones will be " |
| "ignored."); |
| } |
| |
| void ContentSecurityPolicy::LogToConsole(const String& message, |
| mojom::ConsoleMessageLevel level) { |
| LogToConsole(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kSecurity, level, message)); |
| } |
| |
| mojom::blink::ContentSecurityPolicyViolationType |
| ContentSecurityPolicy::BuildCSPViolationType( |
| ContentSecurityPolicy::ContentSecurityPolicyViolationType violation_type) { |
| switch (violation_type) { |
| case blink::ContentSecurityPolicy::ContentSecurityPolicyViolationType:: |
| kEvalViolation: |
| return mojom::blink::ContentSecurityPolicyViolationType::kEvalViolation; |
| case blink::ContentSecurityPolicy::ContentSecurityPolicyViolationType:: |
| kInlineViolation: |
| return mojom::blink::ContentSecurityPolicyViolationType::kInlineViolation; |
| case blink::ContentSecurityPolicy::ContentSecurityPolicyViolationType:: |
| kTrustedTypesPolicyViolation: |
| return mojom::blink::ContentSecurityPolicyViolationType:: |
| kTrustedTypesPolicyViolation; |
| case blink::ContentSecurityPolicy::ContentSecurityPolicyViolationType:: |
| kTrustedTypesSinkViolation: |
| return mojom::blink::ContentSecurityPolicyViolationType:: |
| kTrustedTypesSinkViolation; |
| case blink::ContentSecurityPolicy::ContentSecurityPolicyViolationType:: |
| kURLViolation: |
| return mojom::blink::ContentSecurityPolicyViolationType::kURLViolation; |
| } |
| } |
| |
| void ContentSecurityPolicy::ReportContentSecurityPolicyIssue( |
| const blink::SecurityPolicyViolationEventInit& violation_data, |
| ContentSecurityPolicyType header_type, |
| ContentSecurityPolicyViolationType violation_type, |
| LocalFrame* frame_ancestor, |
| Element* element, |
| SourceLocation* source_location) { |
| auto cspDetails = mojom::blink::ContentSecurityPolicyIssueDetails::New(); |
| cspDetails->is_report_only = |
| header_type == ContentSecurityPolicyType::kReport; |
| if (violation_type == ContentSecurityPolicyViolationType::kURLViolation || |
| violation_data.violatedDirective() == "frame-ancestors") { |
| cspDetails->blocked_url = KURL(violation_data.blockedURI()); |
| } |
| cspDetails->violated_directive = violation_data.violatedDirective(); |
| cspDetails->content_security_policy_violation_type = |
| BuildCSPViolationType(violation_type); |
| if (frame_ancestor) { |
| auto affected_frame = mojom::blink::AffectedFrame::New(); |
| affected_frame->frame_id = |
| frame_ancestor->GetDevToolsFrameToken().ToString().c_str(); |
| cspDetails->frame_ancestor = std::move(affected_frame); |
| } |
| if (violation_data.sourceFile() && violation_data.lineNumber()) { |
| auto affected_location = mojom::blink::AffectedLocation::New(); |
| affected_location->url = violation_data.sourceFile(); |
| // The frontend expects 0-based line numbers. |
| affected_location->line = violation_data.lineNumber() - 1; |
| affected_location->column = violation_data.columnNumber(); |
| if (source_location) { |
| affected_location->script_id = |
| String::Number(source_location->ScriptId()); |
| } |
| cspDetails->affected_location = std::move(affected_location); |
| } |
| if (element) { |
| cspDetails->violating_node_id = DOMNodeIds::IdForNode(element); |
| } |
| |
| auto details = mojom::blink::InspectorIssueDetails::New(); |
| details->csp_issue_details = std::move(cspDetails); |
| |
| mojom::blink::InspectorIssueInfoPtr info = |
| mojom::blink::InspectorIssueInfo::New( |
| mojom::blink::InspectorIssueCode::kContentSecurityPolicyIssue, |
| std::move(details)); |
| |
| if (frame_ancestor) |
| frame_ancestor->AddInspectorIssue(std::move(info)); |
| else if (delegate_) |
| delegate_->AddInspectorIssue(std::move(info)); |
| } |
| |
| void ContentSecurityPolicy::LogToConsole(ConsoleMessage* console_message, |
| LocalFrame* frame) { |
| if (frame) |
| frame->DomWindow()->AddConsoleMessage(console_message); |
| else if (delegate_) |
| delegate_->AddConsoleMessage(console_message); |
| else |
| console_messages_.push_back(console_message); |
| } |
| |
| void ContentSecurityPolicy::ReportBlockedScriptExecutionToInspector( |
| const String& directive_text) const { |
| if (delegate_) |
| delegate_->ReportBlockedScriptExecutionToInspector(directive_text); |
| } |
| |
| bool ContentSecurityPolicy::ExperimentalFeaturesEnabled() const { |
| return RuntimeEnabledFeatures:: |
| ExperimentalContentSecurityPolicyFeaturesEnabled(); |
| } |
| |
| bool ContentSecurityPolicy::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; |
| } |
| |
| // static |
| bool ContentSecurityPolicy::ShouldBypassMainWorld( |
| const ExecutionContext* context) { |
| if (!context) |
| return false; |
| |
| return ShouldBypassMainWorld(context->GetCurrentWorld().get()); |
| } |
| |
| // static |
| bool ContentSecurityPolicy::ShouldBypassMainWorld( |
| const DOMWrapperWorld* world) { |
| if (!world || !world->IsIsolatedWorld()) |
| return false; |
| |
| return IsolatedWorldCSP::Get().HasContentSecurityPolicy(world->GetWorldId()); |
| } |
| |
| bool ContentSecurityPolicy::ShouldSendViolationReport( |
| const String& report) const { |
| // Collisions have no security impact, so we can save space by storing only |
| // the string's hash rather than the whole report. |
| return !violation_reports_sent_.Contains(report.Impl()->GetHash()); |
| } |
| |
| void ContentSecurityPolicy::DidSendViolationReport(const String& report) { |
| violation_reports_sent_.insert(report.Impl()->GetHash()); |
| } |
| |
| const char* ContentSecurityPolicy::GetDirectiveName(CSPDirectiveName type) { |
| switch (type) { |
| case CSPDirectiveName::BaseURI: |
| return "base-uri"; |
| case CSPDirectiveName::BlockAllMixedContent: |
| return "block-all-mixed-content"; |
| case CSPDirectiveName::ChildSrc: |
| return "child-src"; |
| case CSPDirectiveName::ConnectSrc: |
| return "connect-src"; |
| case CSPDirectiveName::DefaultSrc: |
| return "default-src"; |
| case CSPDirectiveName::FontSrc: |
| return "font-src"; |
| case CSPDirectiveName::FormAction: |
| return "form-action"; |
| case CSPDirectiveName::FrameAncestors: |
| return "frame-ancestors"; |
| case CSPDirectiveName::FrameSrc: |
| return "frame-src"; |
| case CSPDirectiveName::ImgSrc: |
| return "img-src"; |
| case CSPDirectiveName::ManifestSrc: |
| return "manifest-src"; |
| case CSPDirectiveName::MediaSrc: |
| return "media-src"; |
| case CSPDirectiveName::NavigateTo: |
| return "navigate-to"; |
| case CSPDirectiveName::ObjectSrc: |
| return "object-src"; |
| case CSPDirectiveName::PrefetchSrc: |
| return "prefetch-src"; |
| case CSPDirectiveName::ReportTo: |
| return "report-to"; |
| case CSPDirectiveName::ReportURI: |
| return "report-uri"; |
| case CSPDirectiveName::RequireTrustedTypesFor: |
| return "require-trusted-types-for"; |
| case CSPDirectiveName::Sandbox: |
| return "sandbox"; |
| case CSPDirectiveName::ScriptSrc: |
| return "script-src"; |
| case CSPDirectiveName::ScriptSrcAttr: |
| return "script-src-attr"; |
| case CSPDirectiveName::ScriptSrcElem: |
| return "script-src-elem"; |
| case CSPDirectiveName::StyleSrc: |
| return "style-src"; |
| case CSPDirectiveName::StyleSrcAttr: |
| return "style-src-attr"; |
| case CSPDirectiveName::StyleSrcElem: |
| return "style-src-elem"; |
| case CSPDirectiveName::TreatAsPublicAddress: |
| return "treat-as-public-address"; |
| case CSPDirectiveName::TrustedTypes: |
| return "trusted-types"; |
| case CSPDirectiveName::UpgradeInsecureRequests: |
| return "upgrade-insecure-requests"; |
| case CSPDirectiveName::WorkerSrc: |
| return "worker-src"; |
| |
| case CSPDirectiveName::Unknown: |
| NOTREACHED(); |
| return ""; |
| } |
| |
| NOTREACHED(); |
| return ""; |
| } |
| |
| CSPDirectiveName ContentSecurityPolicy::GetDirectiveType(const String& name) { |
| if (name == "base-uri") |
| return CSPDirectiveName::BaseURI; |
| if (name == "block-all-mixed-content") |
| return CSPDirectiveName::BlockAllMixedContent; |
| if (name == "child-src") |
| return CSPDirectiveName::ChildSrc; |
| if (name == "connect-src") |
| return CSPDirectiveName::ConnectSrc; |
| if (name == "default-src") |
| return CSPDirectiveName::DefaultSrc; |
| if (name == "font-src") |
| return CSPDirectiveName::FontSrc; |
| if (name == "form-action") |
| return CSPDirectiveName::FormAction; |
| if (name == "frame-ancestors") |
| return CSPDirectiveName::FrameAncestors; |
| if (name == "frame-src") |
| return CSPDirectiveName::FrameSrc; |
| if (name == "img-src") |
| return CSPDirectiveName::ImgSrc; |
| if (name == "manifest-src") |
| return CSPDirectiveName::ManifestSrc; |
| if (name == "media-src") |
| return CSPDirectiveName::MediaSrc; |
| if (name == "navigate-to") |
| return CSPDirectiveName::NavigateTo; |
| if (name == "object-src") |
| return CSPDirectiveName::ObjectSrc; |
| if (name == "prefetch-src") |
| return CSPDirectiveName::PrefetchSrc; |
| if (name == "report-to") |
| return CSPDirectiveName::ReportTo; |
| if (name == "report-uri") |
| return CSPDirectiveName::ReportURI; |
| if (name == "require-trusted-types-for") |
| return CSPDirectiveName::RequireTrustedTypesFor; |
| if (name == "sandbox") |
| return CSPDirectiveName::Sandbox; |
| if (name == "script-src") |
| return CSPDirectiveName::ScriptSrc; |
| if (name == "script-src-attr") |
| return CSPDirectiveName::ScriptSrcAttr; |
| if (name == "script-src-elem") |
| return CSPDirectiveName::ScriptSrcElem; |
| if (name == "style-src") |
| return CSPDirectiveName::StyleSrc; |
| if (name == "style-src-attr") |
| return CSPDirectiveName::StyleSrcAttr; |
| if (name == "style-src-elem") |
| return CSPDirectiveName::StyleSrcElem; |
| if (name == "treat-as-public-address") |
| return CSPDirectiveName::TreatAsPublicAddress; |
| if (name == "trusted-types") |
| return CSPDirectiveName::TrustedTypes; |
| if (name == "upgrade-insecure-requests") |
| return CSPDirectiveName::UpgradeInsecureRequests; |
| if (name == "worker-src") |
| return CSPDirectiveName::WorkerSrc; |
| |
| return CSPDirectiveName::Unknown; |
| } |
| |
| bool ContentSecurityPolicy::ShouldBypassContentSecurityPolicy( |
| const KURL& url, |
| SchemeRegistry::PolicyAreas area) const { |
| bool should_bypass_csp; |
| if (SecurityOrigin::ShouldUseInnerURL(url)) { |
| should_bypass_csp = SchemeRegistry::SchemeShouldBypassContentSecurityPolicy( |
| SecurityOrigin::ExtractInnerURL(url).Protocol(), area); |
| if (should_bypass_csp) { |
| Count(WebFeature::kInnerSchemeBypassesCSP); |
| } |
| } else { |
| should_bypass_csp = SchemeRegistry::SchemeShouldBypassContentSecurityPolicy( |
| url.Protocol(), area); |
| } |
| if (should_bypass_csp) { |
| Count(WebFeature::kSchemeBypassesCSP); |
| } |
| |
| return should_bypass_csp; |
| } |
| |
| const WTF::Vector<network::mojom::blink::ContentSecurityPolicyPtr>& |
| ContentSecurityPolicy::GetParsedPolicies() const { |
| return policies_; |
| } |
| |
| bool ContentSecurityPolicy::HasPolicyFromSource( |
| ContentSecurityPolicySource source) const { |
| for (const auto& policy : policies_) { |
| if (policy->header->source == source) |
| return true; |
| } |
| return false; |
| } |
| |
| void ContentSecurityPolicy::Count(WebFeature feature) const { |
| if (delegate_) |
| delegate_->Count(feature); |
| } |
| |
| } // namespace blink |