| // Copyright 2016 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 "core/frame/csp/CSPDirectiveList.h" |
| |
| #include "core/frame/csp/ContentSecurityPolicy.h" |
| #include "core/frame/csp/SourceListDirective.h" |
| #include "platform/loader/SubresourceIntegrity.h" |
| #include "platform/loader/fetch/ResourceRequest.h" |
| #include "platform/network/ContentSecurityPolicyParsers.h" |
| #include "platform/wtf/Vector.h" |
| #include "platform/wtf/text/StringOperators.h" |
| #include "platform/wtf/text/WTFString.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace blink { |
| |
| class CSPDirectiveListTest : public ::testing::Test { |
| public: |
| CSPDirectiveListTest() : csp(ContentSecurityPolicy::Create()) {} |
| |
| virtual void SetUp() { |
| csp->SetupSelf( |
| *SecurityOrigin::CreateFromString("https://example.test/image.png")); |
| } |
| |
| CSPDirectiveList* CreateList(const String& list, |
| ContentSecurityPolicyHeaderType type, |
| ContentSecurityPolicyHeaderSource source = |
| kContentSecurityPolicyHeaderSourceHTTP) { |
| Vector<UChar> characters; |
| list.AppendTo(characters); |
| const UChar* begin = characters.data(); |
| const UChar* end = begin + characters.size(); |
| |
| return CSPDirectiveList::Create(csp, begin, end, type, source); |
| } |
| |
| protected: |
| Persistent<ContentSecurityPolicy> csp; |
| }; |
| |
| TEST_F(CSPDirectiveListTest, Header) { |
| struct TestCase { |
| const char* list; |
| const char* expected; |
| } cases[] = {{"script-src 'self'", "script-src 'self'"}, |
| {" script-src 'self' ", "script-src 'self'"}, |
| {"\t\tscript-src 'self'", "script-src 'self'"}, |
| {"script-src 'self' \t", "script-src 'self'"}}; |
| |
| for (const auto& test : cases) { |
| Member<CSPDirectiveList> directive_list = |
| CreateList(test.list, kContentSecurityPolicyHeaderTypeReport); |
| EXPECT_EQ(test.expected, directive_list->Header()); |
| directive_list = |
| CreateList(test.list, kContentSecurityPolicyHeaderTypeEnforce); |
| EXPECT_EQ(test.expected, directive_list->Header()); |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, IsMatchingNoncePresent) { |
| struct TestCase { |
| const char* list; |
| const char* nonce; |
| bool expected; |
| } cases[] = { |
| {"script-src 'self'", "yay", false}, |
| {"script-src 'self'", "boo", false}, |
| {"script-src 'nonce-yay'", "yay", true}, |
| {"script-src 'nonce-yay'", "boo", false}, |
| {"script-src 'nonce-yay' 'nonce-boo'", "yay", true}, |
| {"script-src 'nonce-yay' 'nonce-boo'", "boo", true}, |
| |
| // Falls back to 'default-src' |
| {"default-src 'nonce-yay'", "yay", true}, |
| {"default-src 'nonce-yay'", "boo", false}, |
| {"default-src 'nonce-boo'; script-src 'nonce-yay'", "yay", true}, |
| {"default-src 'nonce-boo'; script-src 'nonce-yay'", "boo", false}, |
| |
| // Unrelated directives do not affect result |
| {"style-src 'nonce-yay'", "yay", false}, |
| {"style-src 'nonce-yay'", "boo", false}, |
| }; |
| |
| for (const auto& test : cases) { |
| // Report-only |
| Member<CSPDirectiveList> directive_list = |
| CreateList(test.list, kContentSecurityPolicyHeaderTypeReport); |
| Member<SourceListDirective> script_src = |
| directive_list->OperativeDirective(directive_list->script_src_.Get()); |
| EXPECT_EQ(test.expected, |
| directive_list->IsMatchingNoncePresent(script_src, test.nonce)); |
| // Empty/null strings are always not present, regardless of the policy. |
| EXPECT_FALSE(directive_list->IsMatchingNoncePresent(script_src, "")); |
| EXPECT_FALSE(directive_list->IsMatchingNoncePresent(script_src, String())); |
| |
| // Enforce |
| directive_list = |
| CreateList(test.list, kContentSecurityPolicyHeaderTypeEnforce); |
| script_src = |
| directive_list->OperativeDirective(directive_list->script_src_.Get()); |
| EXPECT_EQ(test.expected, |
| directive_list->IsMatchingNoncePresent(script_src, test.nonce)); |
| // Empty/null strings are always not present, regardless of the policy. |
| EXPECT_FALSE(directive_list->IsMatchingNoncePresent(script_src, "")); |
| EXPECT_FALSE(directive_list->IsMatchingNoncePresent(script_src, String())); |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, AllowScriptFromSourceNoNonce) { |
| struct TestCase { |
| const char* list; |
| const char* url; |
| bool expected; |
| } cases[] = { |
| {"script-src https://example.com", "https://example.com/script.js", true}, |
| {"script-src https://example.com/", "https://example.com/script.js", |
| true}, |
| {"script-src https://example.com/", |
| "https://example.com/script/script.js", true}, |
| {"script-src https://example.com/script", "https://example.com/script.js", |
| false}, |
| {"script-src https://example.com/script", |
| "https://example.com/script/script.js", false}, |
| {"script-src https://example.com/script/", |
| "https://example.com/script.js", false}, |
| {"script-src https://example.com/script/", |
| "https://example.com/script/script.js", true}, |
| {"script-src https://example.com", "https://not.example.com/script.js", |
| false}, |
| {"script-src https://*.example.com", "https://not.example.com/script.js", |
| true}, |
| {"script-src https://*.example.com", "https://example.com/script.js", |
| false}, |
| |
| // Falls back to default-src: |
| {"default-src https://example.com", "https://example.com/script.js", |
| true}, |
| {"default-src https://example.com/", "https://example.com/script.js", |
| true}, |
| {"default-src https://example.com/", |
| "https://example.com/script/script.js", true}, |
| {"default-src https://example.com/script", |
| "https://example.com/script.js", false}, |
| {"default-src https://example.com/script", |
| "https://example.com/script/script.js", false}, |
| {"default-src https://example.com/script/", |
| "https://example.com/script.js", false}, |
| {"default-src https://example.com/script/", |
| "https://example.com/script/script.js", true}, |
| {"default-src https://example.com", "https://not.example.com/script.js", |
| false}, |
| {"default-src https://*.example.com", "https://not.example.com/script.js", |
| true}, |
| {"default-src https://*.example.com", "https://example.com/script.js", |
| false}, |
| }; |
| |
| for (const auto& test : cases) { |
| SCOPED_TRACE(::testing::Message() |
| << "List: `" << test.list << "`, URL: `" << test.url << "`"); |
| KURL script_src = KURL(NullURL(), test.url); |
| |
| // Report-only |
| Member<CSPDirectiveList> directive_list = |
| CreateList(test.list, kContentSecurityPolicyHeaderTypeReport); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowScriptFromSource( |
| script_src, String(), IntegrityMetadataSet(), kParserInserted, |
| ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| |
| // Enforce |
| directive_list = |
| CreateList(test.list, kContentSecurityPolicyHeaderTypeEnforce); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowScriptFromSource( |
| script_src, String(), IntegrityMetadataSet(), kParserInserted, |
| ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, AllowFromSourceWithNonce) { |
| struct TestCase { |
| const char* list; |
| const char* url; |
| const char* nonce; |
| bool expected; |
| } cases[] = { |
| // Doesn't affect lists without nonces: |
| {"https://example.com", "https://example.com/file", "yay", true}, |
| {"https://example.com", "https://example.com/file", "boo", true}, |
| {"https://example.com", "https://example.com/file", "", true}, |
| {"https://example.com", "https://not.example.com/file", "yay", false}, |
| {"https://example.com", "https://not.example.com/file", "boo", false}, |
| {"https://example.com", "https://not.example.com/file", "", false}, |
| |
| // Doesn't affect URLs that match the whitelist. |
| {"https://example.com 'nonce-yay'", "https://example.com/file", "yay", |
| true}, |
| {"https://example.com 'nonce-yay'", "https://example.com/file", "boo", |
| true}, |
| {"https://example.com 'nonce-yay'", "https://example.com/file", "", true}, |
| |
| // Does affect URLs that don't. |
| {"https://example.com 'nonce-yay'", "https://not.example.com/file", "yay", |
| true}, |
| {"https://example.com 'nonce-yay'", "https://not.example.com/file", "boo", |
| false}, |
| {"https://example.com 'nonce-yay'", "https://not.example.com/file", "", |
| false}, |
| }; |
| |
| for (const auto& test : cases) { |
| SCOPED_TRACE(::testing::Message() |
| << "List: `" << test.list << "`, URL: `" << test.url << "`"); |
| KURL resource = KURL(NullURL(), test.url); |
| |
| // Report-only 'script-src' |
| Member<CSPDirectiveList> directive_list = |
| CreateList(String("script-src ") + test.list, |
| kContentSecurityPolicyHeaderTypeReport); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowScriptFromSource( |
| resource, String(test.nonce), IntegrityMetadataSet(), |
| kParserInserted, ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| |
| // Enforce 'script-src' |
| directive_list = CreateList(String("script-src ") + test.list, |
| kContentSecurityPolicyHeaderTypeEnforce); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowScriptFromSource( |
| resource, String(test.nonce), IntegrityMetadataSet(), |
| kParserInserted, ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| |
| // Report-only 'style-src' |
| directive_list = CreateList(String("style-src ") + test.list, |
| kContentSecurityPolicyHeaderTypeReport); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowStyleFromSource( |
| resource, String(test.nonce), |
| ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| |
| // Enforce 'style-src' |
| directive_list = CreateList(String("style-src ") + test.list, |
| kContentSecurityPolicyHeaderTypeEnforce); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowStyleFromSource( |
| resource, String(test.nonce), |
| ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| |
| // Report-only 'style-src' |
| directive_list = CreateList(String("default-src ") + test.list, |
| kContentSecurityPolicyHeaderTypeReport); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowScriptFromSource( |
| resource, String(test.nonce), IntegrityMetadataSet(), |
| kParserInserted, ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowStyleFromSource( |
| resource, String(test.nonce), |
| ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| |
| // Enforce 'style-src' |
| directive_list = CreateList(String("default-src ") + test.list, |
| kContentSecurityPolicyHeaderTypeEnforce); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowScriptFromSource( |
| resource, String(test.nonce), IntegrityMetadataSet(), |
| kParserInserted, ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowStyleFromSource( |
| resource, String(test.nonce), |
| ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, AllowScriptFromSourceWithHash) { |
| struct TestCase { |
| const char* list; |
| const char* url; |
| const char* integrity; |
| bool expected; |
| } cases[] = { |
| // Doesn't affect lists without hashes. |
| {"https://example.com", "https://example.com/file", "sha256-yay", true}, |
| {"https://example.com", "https://example.com/file", "sha256-boo", true}, |
| {"https://example.com", "https://example.com/file", "", true}, |
| {"https://example.com", "https://not.example.com/file", "sha256-yay", |
| false}, |
| {"https://example.com", "https://not.example.com/file", "sha256-boo", |
| false}, |
| {"https://example.com", "https://not.example.com/file", "", false}, |
| |
| // Doesn't affect URLs that match the whitelist. |
| {"https://example.com 'sha256-yay'", "https://example.com/file", |
| "sha256-yay", true}, |
| {"https://example.com 'sha256-yay'", "https://example.com/file", |
| "sha256-boo", true}, |
| {"https://example.com 'sha256-yay'", "https://example.com/file", "", |
| true}, |
| |
| // Does affect URLs that don't match the whitelist. |
| {"https://example.com 'sha256-yay'", "https://not.example.com/file", |
| "sha256-yay", true}, |
| {"https://example.com 'sha256-yay'", "https://not.example.com/file", |
| "sha256-boo", false}, |
| {"https://example.com 'sha256-yay'", "https://not.example.com/file", "", |
| false}, |
| |
| // Both algorithm and digest must match. |
| {"'sha256-yay'", "https://a.com/file", "sha384-yay", false}, |
| |
| // Sha-1 is not supported, but -384 and -512 are. |
| {"'sha1-yay'", "https://a.com/file", "sha1-yay", false}, |
| {"'sha384-yay'", "https://a.com/file", "sha384-yay", true}, |
| {"'sha512-yay'", "https://a.com/file", "sha512-yay", true}, |
| |
| // Unknown (or future) hash algorithms don't work. |
| {"'asdf256-yay'", "https://a.com/file", "asdf256-yay", false}, |
| |
| // But they also don't interfere. |
| {"'sha256-yay'", "https://a.com/file", "sha256-yay asdf256-boo", true}, |
| |
| // Additional whitelisted hashes in the CSP don't interfere. |
| {"'sha256-yay' 'sha384-boo'", "https://a.com/file", "sha256-yay", true}, |
| {"'sha256-yay' 'sha384-boo'", "https://a.com/file", "sha384-boo", true}, |
| |
| // All integrity hashes must appear in the CSP (and match). |
| {"'sha256-yay'", "https://a.com/file", "sha256-yay sha384-boo", false}, |
| {"'sha384-boo'", "https://a.com/file", "sha256-yay sha384-boo", false}, |
| {"'sha256-yay' 'sha384-boo'", "https://a.com/file", |
| "sha256-yay sha384-yay", false}, |
| {"'sha256-yay' 'sha384-boo'", "https://a.com/file", |
| "sha256-boo sha384-boo", false}, |
| {"'sha256-yay' 'sha384-boo'", "https://a.com/file", |
| "sha256-yay sha384-boo", true}, |
| |
| // At least one integrity hash must be present. |
| {"'sha256-yay'", "https://a.com/file", "", false}, |
| }; |
| |
| for (const auto& test : cases) { |
| SCOPED_TRACE(::testing::Message() |
| << "List: `" << test.list << "`, URL: `" << test.url |
| << "`, Integrity: `" << test.integrity << "`"); |
| KURL resource = KURL(NullURL(), test.url); |
| |
| IntegrityMetadataSet integrity_metadata; |
| ASSERT_EQ(SubresourceIntegrity::kIntegrityParseValidResult, |
| SubresourceIntegrity::ParseIntegrityAttribute( |
| test.integrity, integrity_metadata)); |
| |
| // Report-only 'script-src' |
| Member<CSPDirectiveList> directive_list = |
| CreateList(String("script-src ") + test.list, |
| kContentSecurityPolicyHeaderTypeReport); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowScriptFromSource( |
| resource, String(), integrity_metadata, kParserInserted, |
| ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| |
| // Enforce 'script-src' |
| directive_list = CreateList(String("script-src ") + test.list, |
| kContentSecurityPolicyHeaderTypeEnforce); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowScriptFromSource( |
| resource, String(), integrity_metadata, kParserInserted, |
| ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, allowRequestWithoutIntegrity) { |
| struct TestCase { |
| const char* list; |
| const char* url; |
| const WebURLRequest::RequestContext context; |
| bool expected; |
| } cases[] = { |
| |
| {"require-sri-for script", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, false}, |
| |
| // Extra WSP |
| {"require-sri-for script script ", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, false}, |
| {"require-sri-for style script", "https://example.com/file", |
| WebURLRequest::kRequestContextStyle, false}, |
| |
| {"require-sri-for style script", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, false}, |
| {"require-sri-for style script", "https://example.com/file", |
| WebURLRequest::kRequestContextImport, false}, |
| {"require-sri-for style script", "https://example.com/file", |
| WebURLRequest::kRequestContextImage, true}, |
| |
| {"require-sri-for script", "https://example.com/file", |
| WebURLRequest::kRequestContextAudio, true}, |
| {"require-sri-for script", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, false}, |
| {"require-sri-for script", "https://example.com/file", |
| WebURLRequest::kRequestContextImport, false}, |
| {"require-sri-for script", "https://example.com/file", |
| WebURLRequest::kRequestContextServiceWorker, false}, |
| {"require-sri-for script", "https://example.com/file", |
| WebURLRequest::kRequestContextSharedWorker, false}, |
| {"require-sri-for script", "https://example.com/file", |
| WebURLRequest::kRequestContextWorker, false}, |
| {"require-sri-for script", "https://example.com/file", |
| WebURLRequest::kRequestContextStyle, true}, |
| |
| {"require-sri-for style", "https://example.com/file", |
| WebURLRequest::kRequestContextAudio, true}, |
| {"require-sri-for style", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, true}, |
| {"require-sri-for style", "https://example.com/file", |
| WebURLRequest::kRequestContextImport, true}, |
| {"require-sri-for style", "https://example.com/file", |
| WebURLRequest::kRequestContextServiceWorker, true}, |
| {"require-sri-for style", "https://example.com/file", |
| WebURLRequest::kRequestContextSharedWorker, true}, |
| {"require-sri-for style", "https://example.com/file", |
| WebURLRequest::kRequestContextWorker, true}, |
| {"require-sri-for style", "https://example.com/file", |
| WebURLRequest::kRequestContextStyle, false}, |
| |
| // Multiple tokens |
| {"require-sri-for script style", "https://example.com/file", |
| WebURLRequest::kRequestContextStyle, false}, |
| {"require-sri-for script style", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, false}, |
| {"require-sri-for script style", "https://example.com/file", |
| WebURLRequest::kRequestContextImport, false}, |
| {"require-sri-for script style", "https://example.com/file", |
| WebURLRequest::kRequestContextImage, true}, |
| |
| // Matching is case-insensitive |
| {"require-sri-for Script", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, false}, |
| |
| // Unknown tokens do not affect result |
| {"require-sri-for blabla12 as", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, true}, |
| {"require-sri-for blabla12 as script", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, false}, |
| {"require-sri-for script style img", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, false}, |
| {"require-sri-for script style img", "https://example.com/file", |
| WebURLRequest::kRequestContextImport, false}, |
| {"require-sri-for script style img", "https://example.com/file", |
| WebURLRequest::kRequestContextStyle, false}, |
| {"require-sri-for script style img", "https://example.com/file", |
| WebURLRequest::kRequestContextImage, true}, |
| |
| // Empty token list has no effect |
| {"require-sri-for ", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, true}, |
| {"require-sri-for ", "https://example.com/file", |
| WebURLRequest::kRequestContextImport, true}, |
| {"require-sri-for ", "https://example.com/file", |
| WebURLRequest::kRequestContextStyle, true}, |
| {"require-sri-for ", "https://example.com/file", |
| WebURLRequest::kRequestContextServiceWorker, true}, |
| {"require-sri-for ", "https://example.com/file", |
| WebURLRequest::kRequestContextSharedWorker, true}, |
| {"require-sri-for ", "https://example.com/file", |
| WebURLRequest::kRequestContextWorker, true}, |
| |
| // Order does not matter |
| {"require-sri-for a b script", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, false}, |
| {"require-sri-for a script b", "https://example.com/file", |
| WebURLRequest::kRequestContextScript, false}, |
| }; |
| |
| for (const auto& test : cases) { |
| KURL resource = KURL(NullURL(), test.url); |
| // Report-only |
| Member<CSPDirectiveList> directive_list = |
| CreateList(test.list, kContentSecurityPolicyHeaderTypeReport); |
| EXPECT_EQ(true, directive_list->AllowRequestWithoutIntegrity( |
| test.context, resource, |
| ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| |
| // Enforce |
| directive_list = |
| CreateList(test.list, kContentSecurityPolicyHeaderTypeEnforce); |
| EXPECT_EQ(test.expected, |
| directive_list->AllowRequestWithoutIntegrity( |
| test.context, resource, |
| ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, WorkerSrc) { |
| struct TestCase { |
| const char* list; |
| bool allowed; |
| } cases[] = { |
| {"worker-src 'none'", false}, |
| {"worker-src http://not.example.test", false}, |
| {"worker-src https://example.test", true}, |
| {"default-src *; worker-src 'none'", false}, |
| {"default-src *; worker-src http://not.example.test", false}, |
| {"default-src *; worker-src https://example.test", true}, |
| {"script-src *; worker-src 'none'", false}, |
| {"script-src *; worker-src http://not.example.test", false}, |
| {"script-src *; worker-src https://example.test", true}, |
| {"default-src *; script-src *; worker-src 'none'", false}, |
| {"default-src *; script-src *; worker-src http://not.example.test", |
| false}, |
| {"default-src *; script-src *; worker-src https://example.test", true}, |
| |
| // Fallback to script-src. |
| {"script-src 'none'", false}, |
| {"script-src http://not.example.test", false}, |
| {"script-src https://example.test", true}, |
| {"default-src *; script-src 'none'", false}, |
| {"default-src *; script-src http://not.example.test", false}, |
| {"default-src *; script-src https://example.test", true}, |
| |
| // Fallback to default-src. |
| {"default-src 'none'", false}, |
| {"default-src http://not.example.test", false}, |
| {"default-src https://example.test", true}, |
| }; |
| |
| for (const auto& test : cases) { |
| SCOPED_TRACE(test.list); |
| KURL resource = KURL(NullURL(), "https://example.test/worker.js"); |
| Member<CSPDirectiveList> directive_list = |
| CreateList(test.list, kContentSecurityPolicyHeaderTypeEnforce); |
| EXPECT_EQ(test.allowed, |
| directive_list->AllowWorkerFromSource( |
| resource, ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, WorkerSrcChildSrcFallback) { |
| // TODO(mkwst): Remove this test once we remove the temporary fallback |
| // behavior. https://crbug.com/662930 |
| struct TestCase { |
| const char* list; |
| bool allowed; |
| } cases[] = { |
| // When 'worker-src' is not present, 'child-src' can allow a worker when |
| // present. |
| {"child-src https://example.test", true}, |
| {"child-src https://not-example.test", true}, |
| {"script-src https://example.test", true}, |
| {"script-src https://not-example.test", false}, |
| {"child-src https://example.test; script-src https://example.test", true}, |
| {"child-src https://example.test; script-src https://not-example.test", |
| true}, |
| {"child-src https://not-example.test; script-src https://example.test", |
| true}, |
| {"child-src https://not-example.test; script-src " |
| "https://not-example.test", |
| false}, |
| |
| // If 'worker-src' is present, 'child-src' will not allow a worker. |
| {"worker-src https://example.test; child-src https://example.test", true}, |
| {"worker-src https://example.test; child-src https://not-example.test", |
| true}, |
| {"worker-src https://not-example.test; child-src https://example.test", |
| false}, |
| {"worker-src https://not-example.test; child-src " |
| "https://not-example.test", |
| false}, |
| }; |
| |
| for (const auto& test : cases) { |
| SCOPED_TRACE(test.list); |
| KURL resource = KURL(NullURL(), "https://example.test/worker.js"); |
| Member<CSPDirectiveList> directive_list = |
| CreateList(test.list, kContentSecurityPolicyHeaderTypeEnforce); |
| EXPECT_EQ(test.allowed, |
| directive_list->AllowWorkerFromSource( |
| resource, ResourceRequest::RedirectStatus::kNoRedirect, |
| SecurityViolationReportingPolicy::kSuppressReporting)); |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, SubsumesBasedOnCSPSourcesOnly) { |
| CSPDirectiveList* a = CreateList( |
| "script-src http://*.one.com; img-src https://one.com " |
| "http://two.com/imgs/", |
| kContentSecurityPolicyHeaderTypeEnforce); |
| |
| struct TestCase { |
| const std::vector<const char*> policies; |
| bool expected; |
| bool expected_first_policy_opposite; |
| } cases[] = { |
| // `listB`, which is not as restrictive as `A`, is not subsumed. |
| {{""}, false, true}, |
| {{"script-src http://example.com"}, false, false}, |
| {{"img-src http://example.com"}, false, false}, |
| {{"script-src http://*.one.com"}, false, true}, |
| {{"img-src https://one.com http://two.com/imgs/"}, false, true}, |
| {{"default-src http://example.com"}, false, false}, |
| {{"default-src https://one.com http://two.com/imgs/"}, false, false}, |
| {{"default-src http://one.com"}, false, false}, |
| {{"script-src http://*.one.com; img-src http://two.com/"}, false, false}, |
| {{"script-src http://*.one.com", "img-src http://one.com"}, false, true}, |
| {{"script-src http://*.one.com", "script-src https://two.com"}, |
| false, |
| true}, |
| {{"script-src http://*.random.com", "script-src https://random.com"}, |
| false, |
| false}, |
| {{"script-src http://one.com", "script-src https://random.com"}, |
| false, |
| false}, |
| {{"script-src http://*.random.com; default-src http://one.com " |
| "http://two.com/imgs/", |
| "default-src https://random.com"}, |
| false, |
| false}, |
| // `listB`, which is as restrictive as `A`, is subsumed. |
| {{"default-src https://one.com"}, true, false}, |
| {{"default-src http://random.com", |
| "default-src https://non-random.com:*"}, |
| true, |
| false}, |
| {{"script-src http://*.one.com; img-src https://one.com"}, true, false}, |
| {{"script-src http://*.one.com; img-src https://one.com " |
| "http://two.com/imgs/"}, |
| true, |
| true}, |
| {{"script-src http://*.one.com", |
| "img-src https://one.com http://two.com/imgs/"}, |
| true, |
| true}, |
| {{"script-src http://*.random.com; default-src https://one.com " |
| "http://two.com/imgs/", |
| "default-src https://else.com"}, |
| true, |
| false}, |
| {{"script-src http://*.random.com; default-src https://one.com " |
| "http://two.com/imgs/", |
| "default-src https://one.com"}, |
| true, |
| false}, |
| }; |
| |
| CSPDirectiveList* empty_a = |
| CreateList("", kContentSecurityPolicyHeaderTypeEnforce); |
| |
| for (const auto& test : cases) { |
| HeapVector<Member<CSPDirectiveList>> list_b; |
| for (const auto& policy : test.policies) { |
| list_b.push_back( |
| CreateList(policy, kContentSecurityPolicyHeaderTypeEnforce)); |
| } |
| |
| EXPECT_EQ(test.expected, a->Subsumes(list_b)); |
| // Empty CSPDirective subsumes any list. |
| EXPECT_TRUE(empty_a->Subsumes(list_b)); |
| // Check if first policy of `listB` subsumes `A`. |
| EXPECT_EQ(test.expected_first_policy_opposite, |
| list_b[0]->Subsumes(HeapVector<Member<CSPDirectiveList>>(1, a))); |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, SubsumesIfNoneIsPresent) { |
| struct TestCase { |
| const char* policy_a; |
| const std::vector<const char*> policies_b; |
| bool expected; |
| } cases[] = { |
| // `policyA` subsumes any vector of policies. |
| {"", {""}, true}, |
| {"", {"script-src http://example.com"}, true}, |
| {"", {"script-src 'none'"}, true}, |
| {"", {"script-src http://*.one.com", "script-src https://two.com"}, true}, |
| // `policyA` is 'none', but no policy in `policiesB` is. |
| {"script-src ", {""}, false}, |
| {"script-src 'none'", {""}, false}, |
| {"script-src ", {"script-src http://example.com"}, false}, |
| {"script-src 'none'", {"script-src http://example.com"}, false}, |
| {"script-src ", {"img-src 'none'"}, false}, |
| {"script-src 'none'", {"img-src 'none'"}, false}, |
| {"script-src ", |
| {"script-src http://*.one.com", "img-src https://two.com"}, |
| false}, |
| {"script-src 'none'", |
| {"script-src http://*.one.com", "img-src https://two.com"}, |
| false}, |
| {"script-src 'none'", |
| {"script-src http://*.one.com", "script-src https://two.com"}, |
| true}, |
| {"script-src 'none'", |
| {"script-src http://*.one.com", "script-src 'self'"}, |
| true}, |
| // `policyA` is not 'none', but at least effective result of `policiesB` |
| // is. |
| {"script-src http://example.com 'none'", {"script-src 'none'"}, true}, |
| {"script-src http://example.com", {"script-src 'none'"}, true}, |
| {"script-src http://example.com 'none'", |
| {"script-src http://*.one.com", "script-src http://one.com", |
| "script-src 'none'"}, |
| true}, |
| {"script-src http://example.com", |
| {"script-src http://*.one.com", "script-src http://one.com", |
| "script-src 'none'"}, |
| true}, |
| {"script-src http://one.com 'none'", |
| {"script-src http://*.one.com", "script-src http://one.com", |
| "script-src https://one.com"}, |
| true}, |
| // `policyA` is `none` and at least effective result of `policiesB` is |
| // too. |
| {"script-src ", {"script-src ", "script-src "}, true}, |
| {"script-src 'none'", {"script-src", "script-src 'none'"}, true}, |
| {"script-src ", {"script-src 'none'", "script-src 'none'"}, true}, |
| {"script-src ", |
| {"script-src 'none' http://example.com", |
| "script-src 'none' http://example.com"}, |
| false}, |
| {"script-src 'none'", {"script-src 'none'", "script-src 'none'"}, true}, |
| {"script-src 'none'", |
| {"script-src 'none'", "script-src 'none'", "script-src 'none'"}, |
| true}, |
| {"script-src 'none'", |
| {"script-src http://*.one.com", "script-src http://one.com", |
| "script-src 'none'"}, |
| true}, |
| {"script-src 'none'", |
| {"script-src http://*.one.com", "script-src http://two.com", |
| "script-src http://three.com"}, |
| true}, |
| // Policies contain special keywords. |
| {"script-src ", {"script-src ", "script-src 'unsafe-eval'"}, true}, |
| {"script-src 'none'", |
| {"script-src 'unsafe-inline'", "script-src 'none'"}, |
| true}, |
| {"script-src ", |
| {"script-src 'none' 'unsafe-inline'", |
| "script-src 'none' 'unsafe-inline'"}, |
| false}, |
| {"script-src ", |
| {"script-src 'none' 'unsafe-inline'", |
| "script-src 'unsafe-inline' 'strict-dynamic'"}, |
| false}, |
| {"script-src 'unsafe-eval'", |
| {"script-src 'unsafe-eval'", "script 'unsafe-inline'"}, |
| true}, |
| {"script-src 'unsafe-inline'", |
| {"script-src ", "script http://example.com"}, |
| true}, |
| }; |
| |
| for (const auto& test : cases) { |
| CSPDirectiveList* a = |
| CreateList(test.policy_a, kContentSecurityPolicyHeaderTypeEnforce); |
| |
| HeapVector<Member<CSPDirectiveList>> list_b; |
| for (const auto& policy_b : test.policies_b) |
| list_b.push_back( |
| CreateList(policy_b, kContentSecurityPolicyHeaderTypeEnforce)); |
| |
| EXPECT_EQ(test.expected, a->Subsumes(list_b)); |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, SubsumesPluginTypes) { |
| struct TestCase { |
| const char* policy_a; |
| const std::vector<const char*> policies_b; |
| bool expected; |
| } cases[] = { |
| // `policyA` subsumes `policiesB`. |
| {"script-src 'unsafe-inline'", |
| {"script-src ", "script-src http://example.com", |
| "plugin-types text/plain"}, |
| true}, |
| {"script-src http://example.com", |
| {"script-src http://example.com; plugin-types "}, |
| true}, |
| {"script-src http://example.com", |
| {"script-src http://example.com; plugin-types text/plain"}, |
| true}, |
| {"script-src http://example.com; plugin-types text/plain", |
| {"script-src http://example.com; plugin-types text/plain"}, |
| true}, |
| {"script-src http://example.com; plugin-types text/plain", |
| {"script-src http://example.com; plugin-types "}, |
| true}, |
| {"script-src http://example.com; plugin-types text/plain", |
| {"script-src http://example.com; plugin-types ", "plugin-types "}, |
| true}, |
| {"plugin-types application/pdf text/plain", |
| {"plugin-types application/pdf text/plain", |
| "plugin-types application/x-blink-test-plugin"}, |
| true}, |
| {"plugin-types application/pdf text/plain", |
| {"plugin-types application/pdf text/plain", |
| "plugin-types application/pdf text/plain " |
| "application/x-blink-test-plugin"}, |
| true}, |
| {"plugin-types application/x-shockwave-flash application/pdf text/plain", |
| {"plugin-types application/x-shockwave-flash application/pdf text/plain", |
| "plugin-types application/x-shockwave-flash"}, |
| true}, |
| {"plugin-types application/x-shockwave-flash", |
| {"plugin-types application/x-shockwave-flash application/pdf text/plain", |
| "plugin-types application/x-shockwave-flash"}, |
| true}, |
| // `policyA` does not subsume `policiesB`. |
| {"script-src http://example.com; plugin-types text/plain", |
| {"script-src http://example.com"}, |
| false}, |
| {"plugin-types random-value", |
| {"script-src 'unsafe-inline'", "plugin-types text/plain"}, |
| false}, |
| {"plugin-types random-value", |
| {"script-src http://example.com", "script-src http://example.com"}, |
| false}, |
| {"plugin-types random-value", |
| {"plugin-types text/plain", "plugin-types text/plain"}, |
| false}, |
| {"script-src http://example.com; plugin-types text/plain", |
| {"plugin-types ", "plugin-types "}, |
| false}, |
| {"plugin-types application/pdf text/plain", |
| {"plugin-types application/x-blink-test-plugin", |
| "plugin-types application/x-blink-test-plugin"}, |
| false}, |
| {"plugin-types application/pdf text/plain", |
| {"plugin-types application/pdf application/x-blink-test-plugin", |
| "plugin-types application/x-blink-test-plugin"}, |
| false}, |
| }; |
| |
| for (const auto& test : cases) { |
| CSPDirectiveList* a = |
| CreateList(test.policy_a, kContentSecurityPolicyHeaderTypeEnforce); |
| |
| HeapVector<Member<CSPDirectiveList>> list_b; |
| for (const auto& policy_b : test.policies_b) |
| list_b.push_back( |
| CreateList(policy_b, kContentSecurityPolicyHeaderTypeEnforce)); |
| |
| EXPECT_EQ(test.expected, a->Subsumes(list_b)); |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, OperativeDirectiveGivenType) { |
| enum DefaultBehaviour { |
| kDefault, |
| kNoDefault, |
| kChildAndDefault, |
| kScriptAndDefault |
| }; |
| |
| struct TestCase { |
| ContentSecurityPolicy::DirectiveType directive; |
| const DefaultBehaviour type; |
| } cases[] = { |
| // Directives with default directive. |
| {ContentSecurityPolicy::DirectiveType::kChildSrc, kDefault}, |
| {ContentSecurityPolicy::DirectiveType::kConnectSrc, kDefault}, |
| {ContentSecurityPolicy::DirectiveType::kFontSrc, kDefault}, |
| {ContentSecurityPolicy::DirectiveType::kImgSrc, kDefault}, |
| {ContentSecurityPolicy::DirectiveType::kManifestSrc, kDefault}, |
| {ContentSecurityPolicy::DirectiveType::kMediaSrc, kDefault}, |
| {ContentSecurityPolicy::DirectiveType::kObjectSrc, kDefault}, |
| {ContentSecurityPolicy::DirectiveType::kScriptSrc, kDefault}, |
| {ContentSecurityPolicy::DirectiveType::kStyleSrc, kDefault}, |
| // Directives with no default directive. |
| {ContentSecurityPolicy::DirectiveType::kBaseURI, kNoDefault}, |
| {ContentSecurityPolicy::DirectiveType::kDefaultSrc, kNoDefault}, |
| {ContentSecurityPolicy::DirectiveType::kFrameAncestors, kNoDefault}, |
| {ContentSecurityPolicy::DirectiveType::kFormAction, kNoDefault}, |
| // Directive with multiple default directives. |
| {ContentSecurityPolicy::DirectiveType::kFrameSrc, kChildAndDefault}, |
| {ContentSecurityPolicy::DirectiveType::kWorkerSrc, kScriptAndDefault}, |
| }; |
| |
| // Initial set-up. |
| std::stringstream all_directives; |
| for (const auto& test : cases) { |
| const char* name = ContentSecurityPolicy::GetDirectiveName(test.directive); |
| all_directives << name << " http://" << name << ".com; "; |
| } |
| CSPDirectiveList* all_directives_list = CreateList( |
| all_directives.str().c_str(), kContentSecurityPolicyHeaderTypeEnforce); |
| CSPDirectiveList* empty = |
| CreateList("", kContentSecurityPolicyHeaderTypeEnforce); |
| |
| for (const auto& test : cases) { |
| const char* name = ContentSecurityPolicy::GetDirectiveName(test.directive); |
| // When CSPDirectiveList is empty, then `null` should be returned for any |
| // type. |
| EXPECT_FALSE(empty->OperativeDirective(test.directive)); |
| |
| // When all directives present, then given a type that directive value |
| // should be returned. |
| HeapVector<Member<CSPSource>> sources = |
| all_directives_list->OperativeDirective(test.directive)->list_; |
| EXPECT_EQ(sources.size(), 1u); |
| EXPECT_TRUE(sources[0]->host_.StartsWith(name)); |
| |
| std::stringstream all_except_this; |
| std::stringstream all_except_child_src_and_this; |
| std::stringstream all_except_script_src_and_this; |
| for (const auto& subtest : cases) { |
| if (subtest.directive == test.directive) |
| continue; |
| const char* directive_name = |
| ContentSecurityPolicy::GetDirectiveName(subtest.directive); |
| all_except_this << directive_name << " http://" << directive_name |
| << ".com; "; |
| if (subtest.directive != |
| ContentSecurityPolicy::DirectiveType::kChildSrc) { |
| all_except_child_src_and_this << directive_name << " http://" |
| << directive_name << ".com; "; |
| } |
| if (subtest.directive != |
| ContentSecurityPolicy::DirectiveType::kScriptSrc) { |
| all_except_script_src_and_this << directive_name << " http://" |
| << directive_name << ".com; "; |
| } |
| } |
| CSPDirectiveList* all_except_this_list = CreateList( |
| all_except_this.str().c_str(), kContentSecurityPolicyHeaderTypeEnforce); |
| CSPDirectiveList* all_except_child_src_and_this_list = |
| CreateList(all_except_child_src_and_this.str().c_str(), |
| kContentSecurityPolicyHeaderTypeEnforce); |
| CSPDirectiveList* all_except_script_src_and_this_list = |
| CreateList(all_except_script_src_and_this.str().c_str(), |
| kContentSecurityPolicyHeaderTypeEnforce); |
| |
| switch (test.type) { |
| case kDefault: |
| sources = |
| all_except_this_list->OperativeDirective(test.directive)->list_; |
| EXPECT_EQ(sources.size(), 1u); |
| EXPECT_EQ(sources[0]->host_, "default-src.com"); |
| break; |
| case kNoDefault: |
| EXPECT_FALSE(all_except_this_list->OperativeDirective(test.directive)); |
| break; |
| case kChildAndDefault: |
| sources = |
| all_except_this_list->OperativeDirective(test.directive)->list_; |
| EXPECT_EQ(sources.size(), 1u); |
| EXPECT_EQ(sources[0]->host_, "child-src.com"); |
| sources = all_except_child_src_and_this_list |
| ->OperativeDirective(test.directive) |
| ->list_; |
| EXPECT_EQ(sources.size(), 1u); |
| EXPECT_EQ(sources[0]->host_, "default-src.com"); |
| break; |
| case kScriptAndDefault: |
| sources = |
| all_except_this_list->OperativeDirective(test.directive)->list_; |
| EXPECT_EQ(sources.size(), 1u); |
| EXPECT_EQ(sources[0]->host_, "script-src.com"); |
| sources = all_except_script_src_and_this_list |
| ->OperativeDirective(test.directive) |
| ->list_; |
| EXPECT_EQ(sources.size(), 1u); |
| EXPECT_EQ(sources[0]->host_, "default-src.com"); |
| break; |
| } |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, GetSourceVector) { |
| const std::vector<const char*> policies = { |
| // Policy 1 |
| "default-src https://default-src.com", |
| // Policy 2 |
| "child-src http://child-src.com", |
| // Policy 3 |
| "child-src http://child-src.com; default-src https://default-src.com", |
| // Policy 4 |
| "base-uri http://base-uri.com", |
| // Policy 5 |
| "frame-src http://frame-src.com"}; |
| |
| // Check expectations on the initial set-up. |
| HeapVector<Member<CSPDirectiveList>> policy_vector; |
| for (const auto& policy : policies) { |
| policy_vector.push_back( |
| CreateList(policy, kContentSecurityPolicyHeaderTypeEnforce)); |
| } |
| HeapVector<Member<SourceListDirective>> result = |
| CSPDirectiveList::GetSourceVector( |
| ContentSecurityPolicy::DirectiveType::kDefaultSrc, policy_vector); |
| EXPECT_EQ(result.size(), 2u); |
| result = CSPDirectiveList::GetSourceVector( |
| ContentSecurityPolicy::DirectiveType::kChildSrc, policy_vector); |
| EXPECT_EQ(result.size(), 3u); |
| result = CSPDirectiveList::GetSourceVector( |
| ContentSecurityPolicy::DirectiveType::kBaseURI, policy_vector); |
| EXPECT_EQ(result.size(), 1u); |
| result = CSPDirectiveList::GetSourceVector( |
| ContentSecurityPolicy::DirectiveType::kFrameSrc, policy_vector); |
| EXPECT_EQ(result.size(), 4u); |
| |
| enum DefaultBehaviour { kDefault, kNoDefault, kChildAndDefault }; |
| |
| struct TestCase { |
| ContentSecurityPolicy::DirectiveType directive; |
| const DefaultBehaviour type; |
| size_t expected_total; |
| int expected_current; |
| int expected_default_src; |
| int expected_child_src; |
| } cases[] = { |
| // Directives with default directive. |
| {ContentSecurityPolicy::DirectiveType::kChildSrc, kDefault, 4u, 3, 1, 3}, |
| {ContentSecurityPolicy::DirectiveType::kConnectSrc, kDefault, 3u, 1, 2, |
| 0}, |
| {ContentSecurityPolicy::DirectiveType::kFontSrc, kDefault, 3u, 1, 2, 0}, |
| {ContentSecurityPolicy::DirectiveType::kImgSrc, kDefault, 3u, 1, 2, 0}, |
| {ContentSecurityPolicy::DirectiveType::kManifestSrc, kDefault, 3u, 1, 2, |
| 0}, |
| {ContentSecurityPolicy::DirectiveType::kMediaSrc, kDefault, 3u, 1, 2, 0}, |
| {ContentSecurityPolicy::DirectiveType::kObjectSrc, kDefault, 3u, 1, 2, 0}, |
| {ContentSecurityPolicy::DirectiveType::kScriptSrc, kDefault, 3u, 1, 2, 0}, |
| {ContentSecurityPolicy::DirectiveType::kStyleSrc, kDefault, 3u, 1, 2, 0}, |
| // Directives with no default directive. |
| {ContentSecurityPolicy::DirectiveType::kBaseURI, kNoDefault, 2u, 2, 0, 0}, |
| {ContentSecurityPolicy::DirectiveType::kFrameAncestors, kNoDefault, 1u, 1, |
| 0, 0}, |
| {ContentSecurityPolicy::DirectiveType::kFormAction, kNoDefault, 1u, 1, 0, |
| 0}, |
| // Directive with multiple default directives. |
| {ContentSecurityPolicy::DirectiveType::kFrameSrc, kChildAndDefault, 5u, 2, |
| 1, 2}, |
| }; |
| |
| for (const auto& test : cases) { |
| // Initial set-up. |
| HeapVector<Member<CSPDirectiveList>> policy_vector; |
| for (const auto& policy : policies) { |
| policy_vector.push_back( |
| CreateList(policy, kContentSecurityPolicyHeaderTypeEnforce)); |
| } |
| // Append current test's policy. |
| std::stringstream current_directive; |
| const char* name = ContentSecurityPolicy::GetDirectiveName(test.directive); |
| current_directive << name << " http://" << name << ".com;"; |
| policy_vector.push_back( |
| CreateList(current_directive.str().c_str(), |
| kContentSecurityPolicyHeaderTypeEnforce)); |
| |
| HeapVector<Member<SourceListDirective>> result = |
| CSPDirectiveList::GetSourceVector(test.directive, policy_vector); |
| |
| EXPECT_EQ(result.size(), test.expected_total); |
| |
| int actual_current = 0, actual_default = 0, actual_child = 0; |
| for (const auto& src_list : result) { |
| HeapVector<Member<CSPSource>> sources = src_list->list_; |
| for (const auto& source : sources) { |
| if (source->host_.StartsWith(name)) |
| actual_current += 1; |
| else if (source->host_ == "default-src.com") |
| actual_default += 1; |
| |
| if (source->host_ == "child-src.com") |
| actual_child += 1; |
| } |
| } |
| |
| EXPECT_EQ(actual_default, test.expected_default_src); |
| EXPECT_EQ(actual_current, test.expected_current); |
| EXPECT_EQ(actual_child, test.expected_child_src); |
| |
| // If another default-src is added that should only impact Fetch Directives |
| policy_vector.push_back( |
| CreateList("default-src https://default-src.com;", |
| kContentSecurityPolicyHeaderTypeEnforce)); |
| size_t udpated_total = |
| test.type != kNoDefault ? test.expected_total + 1 : test.expected_total; |
| EXPECT_EQ( |
| CSPDirectiveList::GetSourceVector(test.directive, policy_vector).size(), |
| udpated_total); |
| size_t expected_child_src = |
| test.directive == ContentSecurityPolicy::DirectiveType::kChildSrc ? 5u |
| : 4u; |
| EXPECT_EQ( |
| CSPDirectiveList::GetSourceVector( |
| ContentSecurityPolicy::DirectiveType::kChildSrc, policy_vector) |
| .size(), |
| expected_child_src); |
| |
| // If another child-src is added that should only impact frame-src and |
| // child-src |
| policy_vector.push_back( |
| CreateList("child-src http://child-src.com;", |
| kContentSecurityPolicyHeaderTypeEnforce)); |
| udpated_total = test.type == kChildAndDefault || |
| test.directive == |
| ContentSecurityPolicy::DirectiveType::kChildSrc |
| ? udpated_total + 1 |
| : udpated_total; |
| EXPECT_EQ( |
| CSPDirectiveList::GetSourceVector(test.directive, policy_vector).size(), |
| udpated_total); |
| expected_child_src = expected_child_src + 1u; |
| EXPECT_EQ( |
| CSPDirectiveList::GetSourceVector( |
| ContentSecurityPolicy::DirectiveType::kChildSrc, policy_vector) |
| .size(), |
| expected_child_src); |
| |
| // If we add sandbox, nothing should change since it is currenly not |
| // considered. |
| policy_vector.push_back( |
| CreateList("sandbox http://sandbox.com;", |
| kContentSecurityPolicyHeaderTypeEnforce)); |
| EXPECT_EQ( |
| CSPDirectiveList::GetSourceVector(test.directive, policy_vector).size(), |
| udpated_total); |
| EXPECT_EQ( |
| CSPDirectiveList::GetSourceVector( |
| ContentSecurityPolicy::DirectiveType::kChildSrc, policy_vector) |
| .size(), |
| expected_child_src); |
| } |
| } |
| |
| TEST_F(CSPDirectiveListTest, ReportEndpointsProperlyParsed) { |
| struct TestCase { |
| const char* policy; |
| ContentSecurityPolicyHeaderSource header_source; |
| Vector<String> expected_endpoints; |
| bool expected_use_reporting_api; |
| } cases[] = { |
| {"script-src 'self';", kContentSecurityPolicyHeaderSourceHTTP, {}, false}, |
| {"script-src 'self'; report-uri https://example.com", |
| kContentSecurityPolicyHeaderSourceHTTP, |
| {"https://example.com"}, |
| false}, |
| {"script-src 'self'; report-uri https://example.com " |
| "https://example2.com", |
| kContentSecurityPolicyHeaderSourceHTTP, |
| {"https://example.com", "https://example2.com"}, |
| false}, |
| {"script-src 'self'; report-uri https://example.com", |
| kContentSecurityPolicyHeaderSourceMeta, |
| {}, |
| false}, |
| {"script-src 'self'; report-to group", |
| kContentSecurityPolicyHeaderSourceHTTP, |
| {"group"}, |
| true}, |
| // report-to supersedes report-uri |
| {"script-src 'self'; report-to group; report-uri https://example.com", |
| kContentSecurityPolicyHeaderSourceHTTP, |
| {"group"}, |
| true}, |
| {"script-src 'self'; report-to group", |
| kContentSecurityPolicyHeaderSourceMeta, |
| {"group"}, |
| true}, |
| {"script-src 'self'; report-to group; report-to group2;", |
| kContentSecurityPolicyHeaderSourceHTTP, |
| {"group"}, |
| true}, |
| {"script-src 'self'; report-to group; report-uri https://example.com; " |
| "report-to group2", |
| kContentSecurityPolicyHeaderSourceHTTP, |
| {"group"}, |
| true}, |
| {"script-src 'self'; report-uri https://example.com; report-to group; " |
| "report-to group2", |
| kContentSecurityPolicyHeaderSourceHTTP, |
| {"group"}, |
| true}, |
| {"script-src 'self'; report-uri https://example.com " |
| "https://example2.com; report-to group", |
| kContentSecurityPolicyHeaderSourceHTTP, |
| {"group"}, |
| true}, |
| {"script-src 'self'; report-uri https://example.com; report-to group; " |
| "report-uri https://example.com", |
| kContentSecurityPolicyHeaderSourceHTTP, |
| {"group"}, |
| true}, |
| }; |
| |
| for (const auto& test : cases) { |
| // Test both enforce and report, there should not be a difference |
| for (const auto& header_type : {kContentSecurityPolicyHeaderTypeEnforce, |
| kContentSecurityPolicyHeaderTypeReport}) { |
| Member<CSPDirectiveList> directive_list = |
| CreateList(test.policy, header_type, test.header_source); |
| |
| EXPECT_EQ(directive_list->UseReportingApi(), |
| test.expected_use_reporting_api); |
| EXPECT_EQ(directive_list->ReportEndpoints().size(), |
| test.expected_endpoints.size()); |
| |
| for (const String& endpoint : test.expected_endpoints) { |
| EXPECT_TRUE(directive_list->ReportEndpoints().Contains(endpoint)); |
| } |
| for (const String& endpoint : directive_list->ReportEndpoints()) { |
| EXPECT_TRUE(test.expected_endpoints.Contains(endpoint)); |
| } |
| } |
| } |
| } |
| |
| } // namespace blink |