[go: nahoru, domu]

blob: a9521b2a26dccfa42d335e88f448e21492106d24 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/public/cpp/content_security_policy/csp_source_list.h"
#include "base/memory/raw_ptr.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/content_security_policy/content_security_policy.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
namespace network {
namespace {
// A CSPSource used in test not interested checking the interactions with
// 'self'. It doesn't match any URL.
static const network::mojom::CSPSource no_self;
// Allow() is an abbreviation of CheckCSPSourceList. Useful for writing
// test expectations on one line.
bool Allow(
const mojom::CSPSourceListPtr& source_list,
const GURL& url,
const mojom::CSPSource& self,
bool is_redirect = false,
bool is_response_check = false,
mojom::CSPDirectiveName directive_name = mojom::CSPDirectiveName::FrameSrc,
bool is_opaque_fenced_frame = false) {
return CheckCSPSourceList(directive_name, *source_list, url, self,
is_redirect, is_response_check,
is_opaque_fenced_frame);
}
std::vector<mojom::ContentSecurityPolicyPtr> Parse(
const std::vector<std::string>& policies) {
auto headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
for (const auto& policy : policies) {
headers->AddHeader("Content-Security-Policy", policy);
}
std::vector<mojom::ContentSecurityPolicyPtr> parsed_policies;
AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"),
&parsed_policies);
return parsed_policies;
}
mojom::CSPSourceListPtr ParseToSourceList(mojom::CSPDirectiveName directive,
const std::string& value) {
return std::move(
Parse({ToString(directive) + " " + value})[0]->directives[directive]);
}
std::vector<mojom::CSPSourceListPtr> ParseToVectorOfSourceLists(
mojom::CSPDirectiveName directive,
const std::vector<std::string>& values) {
std::vector<std::string> csp_values(values.size());
std::transform(values.begin(), values.end(), csp_values.begin(),
[directive](const std::string& s) -> std::string {
return ToString(directive) + " " + s;
});
std::vector<mojom::ContentSecurityPolicyPtr> policies = Parse(csp_values);
std::vector<mojom::CSPSourceListPtr> sources(policies.size());
std::transform(policies.begin(), policies.end(), sources.begin(),
[directive](mojom::ContentSecurityPolicyPtr& p)
-> mojom::CSPSourceListPtr {
return std::move(p->directives[directive]);
});
return sources;
}
std::vector<const mojom::CSPSourceList*> ToRawPointers(
const std::vector<mojom::CSPSourceListPtr>& list) {
std::vector<const mojom::CSPSourceList*> out(list.size());
std::transform(
list.begin(), list.end(), out.begin(),
[](const mojom::CSPSourceListPtr& item) -> const mojom::CSPSourceList* {
return item.get();
});
return out;
}
} // namespace
TEST(CSPSourceList, MultipleSource) {
auto self = network::mojom::CSPSource::New("http", "example.com", 80, "",
false, false);
std::vector<mojom::CSPSourcePtr> sources;
sources.push_back(mojom::CSPSource::New("", "a.com", url::PORT_UNSPECIFIED,
"", false, false));
sources.push_back(mojom::CSPSource::New("", "b.com", url::PORT_UNSPECIFIED,
"", false, false));
auto source_list = mojom::CSPSourceList::New();
source_list->sources = std::move(sources);
EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), *self));
EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), *self));
EXPECT_FALSE(Allow(source_list, GURL("http://c.com"), *self));
}
TEST(CSPSourceList, AllowStar) {
auto self = network::mojom::CSPSource::New("http", "example.com", 80, "",
false, false);
auto source_list = mojom::CSPSourceList::New();
source_list->allow_star = true;
EXPECT_TRUE(Allow(source_list, GURL("http://not-example.com"), *self));
EXPECT_TRUE(Allow(source_list, GURL("https://not-example.com"), *self));
EXPECT_TRUE(Allow(source_list, GURL("ws://not-example.com"), *self));
EXPECT_TRUE(Allow(source_list, GURL("wss://not-example.com"), *self));
EXPECT_TRUE(Allow(source_list, GURL("ftp://not-example.com"), *self));
EXPECT_FALSE(Allow(source_list, GURL("file://not-example.com"), *self));
EXPECT_FALSE(Allow(source_list, GURL("applewebdata://a.test"), *self));
{
// With a protocol of 'file', '*' allow 'file:'
auto file = network::mojom::CSPSource::New(
"file", "example.com", url::PORT_UNSPECIFIED, "", false, false);
EXPECT_TRUE(Allow(source_list, GURL("file://not-example.com"), *file));
EXPECT_FALSE(Allow(source_list, GURL("applewebdata://a.test"), *file));
}
}
TEST(CSPSourceList, AllowSelf) {
auto self = network::mojom::CSPSource::New("http", "example.com", 80, "",
false, false);
auto source_list = mojom::CSPSourceList::New();
source_list->allow_self = true;
EXPECT_TRUE(Allow(source_list, GURL("http://example.com"), *self));
EXPECT_FALSE(Allow(source_list, GURL("http://not-example.com"), *self));
EXPECT_TRUE(Allow(source_list, GURL("https://example.com"), *self));
EXPECT_FALSE(Allow(source_list, GURL("ws://example.com"), *self));
}
TEST(CSPSourceList, AllowStarAndSelf) {
auto self =
network::mojom::CSPSource::New("https", "a.com", 443, "", false, false);
auto source_list = mojom::CSPSourceList::New();
// If the request is allowed by {*} and not by {'self'} then it should be
// allowed by the union {*,'self'}.
source_list->allow_self = true;
source_list->allow_star = false;
EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self));
source_list->allow_self = false;
source_list->allow_star = true;
EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), *self));
source_list->allow_self = true;
source_list->allow_star = true;
EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), *self));
}
TEST(CSPSourceList, AllowSelfWithUnspecifiedPort) {
auto self = network::mojom::CSPSource::New("https", "example.com", 443, "",
false, false);
auto source_list = mojom::CSPSourceList::New();
source_list->allow_self = true;
EXPECT_TRUE(Allow(source_list, GURL("https://example.com/print.pdf"), *self));
}
TEST(CSPSourceList, AllowNone) {
auto self = network::mojom::CSPSource::New("http", "example.com", 80, "",
false, false);
auto source_list = mojom::CSPSourceList::New();
EXPECT_FALSE(Allow(source_list, GURL("http://example.com"), *self));
EXPECT_FALSE(Allow(source_list, GURL("https://example.test/"), *self));
}
TEST(CSPSourceTest, SelfIsUnique) {
// Policy: 'self'
auto source_list = mojom::CSPSourceList::New();
source_list->allow_self = true;
auto self =
network::mojom::CSPSource::New("http", "a.com", 80, "", false, false);
EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), *self));
EXPECT_FALSE(Allow(source_list, GURL("data:text/html,hello"), *self));
// Self doesn't match anything.
auto no_self_source = network::mojom::CSPSource::New(
"", "", url::PORT_UNSPECIFIED, "", false, false);
EXPECT_FALSE(Allow(source_list, GURL("http://a.com"), *no_self_source));
EXPECT_FALSE(
Allow(source_list, GURL("data:text/html,hello"), *no_self_source));
}
// Test that 'unsafe-allow-redirects' is only applied to navigate-to.
TEST(CSPSourceList, UnsafeAllowRedirects) {
auto self = network::mojom::CSPSource::New("http", "example.com", 80, "",
false, false);
std::vector<mojom::CSPSourcePtr> sources;
sources.push_back(mojom::CSPSource::New("", "a.com", url::PORT_UNSPECIFIED,
"", false, false));
auto source_list = mojom::CSPSourceList::New();
source_list->sources = std::move(sources);
source_list->allow_response_redirects = true;
EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), *self,
/*is_redirect=*/false, /*is_response_check=*/false,
mojom::CSPDirectiveName::NavigateTo));
EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), *self,
/*is_redirect=*/false, /*is_response_check=*/false,
mojom::CSPDirectiveName::NavigateTo));
EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self,
/*is_redirect=*/false, /*is_response_check=*/true,
mojom::CSPDirectiveName::NavigateTo));
EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), *self,
/*is_redirect=*/false, /*is_response_check=*/false,
mojom::CSPDirectiveName::FrameSrc));
EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self,
/*is_redirect=*/false, /*is_response_check=*/false,
mojom::CSPDirectiveName::FrameSrc));
EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self,
/*is_redirect=*/false, /*is_response_check=*/true,
mojom::CSPDirectiveName::FrameSrc));
EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), *self,
/*is_redirect=*/false, /*is_response_check=*/false,
mojom::CSPDirectiveName::FormAction));
EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self,
/*is_redirect=*/false, /*is_response_check=*/false,
mojom::CSPDirectiveName::FormAction));
EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self,
/*is_redirect=*/false, /*is_response_check=*/true,
mojom::CSPDirectiveName::FormAction));
}
TEST(CSPSourceList, Subsume) {
std::string required =
"http://example1.com/foo/ https://*.example2.com/bar/ "
"http://*.example3.com:*/bar/";
mojom::CSPSourceListPtr required_sources =
ParseToSourceList(mojom::CSPDirectiveName::ScriptSrc, required);
struct TestCase {
std::vector<std::string> response_csp;
bool expected;
} cases[] = {
// Non-intersecting source lists give an effective policy of 'none', which
// is always subsumed.
{{"http://example1.com/bar/", "http://*.example3.com:*/bar/"}, true},
{{"http://example1.com/bar/",
"http://*.example3.com:*/bar/ https://*.example2.com/bar/"},
true},
// Lists that intersect into one of the required sources are subsumed.
{{"http://example1.com/foo/"}, true},
{{"https://*.example2.com/bar/"}, true},
{{"http://*.example3.com:*/bar/"}, true},
{{"https://example1.com/foo/",
"http://*.example1.com/foo/ https://*.example2.com/bar/"},
true},
{{"https://example2.com/bar/",
"http://*.example3.com:*/bar/ https://*.example2.com/bar/"},
true},
{{"http://example3.com:100/bar/",
"http://*.example3.com:*/bar/ https://*.example2.com/bar/"},
true},
// Lists that intersect into two of the required sources are subsumed.
{{"http://example1.com/foo/ https://*.example2.com/bar/"}, true},
{{"http://example1.com/foo/ https://a.example2.com/bar/",
"https://a.example2.com/bar/ http://example1.com/foo/"},
true},
{{"http://example1.com/foo/ https://a.example2.com/bar/",
"http://*.example2.com/bar/ http://example1.com/foo/"},
true},
// Ordering should not matter.
{{"https://example1.com/foo/ https://a.example2.com/bar/",
"http://a.example2.com/bar/ http://example1.com/foo/"},
true},
// Lists that intersect into a policy identical to the required list are
// subsumed.
{{"http://example1.com/foo/ https://*.example2.com/bar/ "
"http://*.example3.com:*/bar/ http://example1.com/foo/"},
true},
{{"http://example1.com/foo/ https://*.example2.com/bar/ "
"http://*.example3.com:*/bar/"},
true},
{{"http://example1.com/foo/ https://*.example2.com/bar/ "
"http://*.example3.com:*/bar/",
"http://example1.com/foo/ https://*.example2.com/bar/ "
"http://*.example3.com:*/bar/ http://example4.com/foo/"},
true},
{{"http://example1.com/foo/ https://*.example2.com/bar/ "
"http://*.example3.com:*/bar/",
"http://example1.com/foo/ https://*.example2.com/bar/ "
"http://*.example3.com:*/bar/ http://example1.com/foo/"},
true},
// Lists that include sources which are not subsumed by the required list
// are not subsumed.
{{"http://example1.com/foo/ https://*.example2.com/bar/ "
"http://*.example3.com:*/bar/ http://*.example4.com:*/bar/"},
false},
{{"http://example1.com/foo/ http://example2.com/foo/"}, false},
{{"http://*.com/bar/", "http://example1.com/bar/"}, false},
{{"http://*.example1.com/bar/"}, false},
{{"http://example1.com/bar/"}, false},
{{"http://*.example1.com/foo/"}, false},
{{"wss://example2.com/bar/"}, false},
{{"http://*.non-example3.com:*/bar/"}, false},
{{"http://example3.com/foo/"}, false},
{{"http://not-example1.com", "http://not-example1.com"}, false},
// Lists that intersect into sources which are not subsumed by the
// required
// list are not subsumed.
{{"http://not-example1.com/foo/", "https:"}, false},
{{"http://not-example1.com/foo/ http://example1.com/foo/", "https:"},
false},
{{"http://*", "http://*.com http://*.example3.com:*/bar/"}, false},
};
auto origin_b =
mojom::CSPSource::New("https", "frame.test", 443, "", false, false);
for (const auto& test : cases) {
auto response_sources = ParseToVectorOfSourceLists(
mojom::CSPDirectiveName::ScriptSrc, test.response_csp);
EXPECT_EQ(test.expected,
CSPSourceListSubsumes(
*required_sources, ToRawPointers(response_sources),
mojom::CSPDirectiveName::ScriptSrc, origin_b.get()))
<< required << " should " << (test.expected ? "" : "not ") << "subsume "
<< base::JoinString(test.response_csp, ", ");
}
}
TEST(CSPSourceList, SubsumeWithSelf) {
std::string required =
"http://example1.com/foo/ http://*.example2.com/bar/ "
"http://*.example3.com:*/bar/ 'self'";
mojom::CSPSourceListPtr required_sources =
ParseToSourceList(mojom::CSPDirectiveName::ScriptSrc, required);
struct TestCase {
std::vector<std::string> response_csp;
const char* origin;
bool expected;
} cases[] = {
// "https://example.test/" is a secure origin for both `required` and
// `response_csp`.
{{"'self'"}, "https://example.test/", true},
{{"https://example.test"}, "https://example.test/", true},
{{"https://example.test/"}, "https://example.test/", true},
{{"'self' 'self' 'self'"}, "https://example.test/", true},
{{"'self'", "'self'", "'self'"}, "https://example.test/", true},
{{"'self'", "'self'", "https://*.example.test/"},
"https://example.test/",
true},
{{"'self'", "'self'", "https://*.example.test/bar/"},
"https://example.test/",
true},
{{"'self' https://another.test/bar", "'self' http://*.example.test/bar",
"https://*.example.test/bar/"},
"https://example.test/",
true},
{{"http://example1.com/foo/ 'self'"}, "https://example.test/", true},
{{"http://example1.com/foo/ https://example.test/"},
"https://example.test/",
true},
{{"http://example1.com/foo/ http://*.example2.com/bar/"},
"https://example.test/",
true},
{{"http://example1.com/foo/ http://*.example2.com/bar/ "
"http://*.example3.com:*/bar/ https://example.test/"},
"https://example.test/",
true},
{{"http://example1.com/foo/ http://*.example2.com/bar/ "
"http://*.example3.com:*/bar/ 'self'"},
"https://example.test/",
true},
{{"'self'", "'self'", "https://example.test/"},
"https://example.test/",
true},
{{"'self'", "https://example.test/folder/"},
"https://example.test/",
true},
{{"'self'", "http://example.test/folder/"},
"https://example.test/",
true},
{{"'self' https://example.com/", "https://example.com/"},
"https://example.test/",
false},
{{"http://example1.com/foo/ http://*.example2.com/bar/",
"http://example1.com/foo/ http://*.example2.com/bar/ 'self'"},
"https://example.test/",
true},
{{"http://*.example1.com/foo/", "http://*.example1.com/foo/ 'self'"},
"https://example.test/",
false},
{{"https://*.example.test/", "https://*.example.test/ 'self'"},
"https://example.test/",
false},
{{"http://example.test/"}, "https://example.test/", false},
{{"https://example.test/"}, "https://example.test/", true},
// Origins of `required` and `response_csp` do not match.
{{"https://example.test/"}, "https://other-origin.test/", false},
{{"'self'"}, "https://other-origin.test/", true},
{{"http://example1.com/foo/ http://*.example2.com/bar/ "
"http://*.example3.com:*/bar/ 'self'"},
"https://other-origin.test/",
true},
{{"http://example1.com/foo/ http://*.example2.com/bar/ "
"http://*.example3.com:*/bar/ https://other-origin.test/"},
"https://other-origin.test/",
true},
{{"http://example1.com/foo/ 'self'"}, "https://other-origin.test/", true},
{{"'self'", "https://example.test/"}, "https://other-origin.test/", true},
{{"'self' https://example.test/", "https://example.test/"},
"https://other-origin.test/",
false},
{{"https://example.test/", "http://example.test/"},
"https://other-origin.test/",
false},
{{"'self'", "http://other-origin.test/"},
"https://other-origin.test/",
true},
{{"'self'", "https://non-example.test/"},
"https://other-origin.test/",
true},
// `response_csp`'s origin matches one of the sources in the source list
// of `required`.
{{"'self'", "http://*.example1.com/foo/"}, "http://example1.com/", true},
{{"http://*.example2.com/bar/", "'self'"},
"http://example2.com/bar/",
true},
{{"'self' http://*.example1.com/foo/", "http://*.example1.com/foo/"},
"http://example1.com/",
false},
{{"http://*.example2.com/bar/ http://example1.com/",
"'self' http://example1.com/"},
"http://example2.com/bar/",
false},
};
for (const auto& test : cases) {
auto response_sources = ParseToVectorOfSourceLists(
mojom::CSPDirectiveName::ScriptSrc, test.response_csp);
GURL parsed_test_origin(test.origin);
auto origin_b = mojom::CSPSource::New(
parsed_test_origin.scheme(), parsed_test_origin.host(),
parsed_test_origin.EffectiveIntPort(), "", false, false);
EXPECT_EQ(test.expected,
CSPSourceListSubsumes(
*required_sources, ToRawPointers(response_sources),
mojom::CSPDirectiveName::ScriptSrc, origin_b.get()))
<< required << "from origin " << test.origin << " should "
<< (test.expected ? "" : "not ") << "subsume "
<< base::JoinString(test.response_csp, ", ");
}
}
TEST(CSPSourceList, SubsumeAllowAllInline) {
struct TestCase {
mojom::CSPDirectiveName directive;
std::string required;
std::vector<std::string> response_csp;
bool expected;
} cases[] = {
// `required` allows all inline behavior.
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
{"'unsafe-inline' http://example1.com/foo/bar.html"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline'",
{"http://example1.com/foo/ 'unsafe-inline'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline'",
{"'unsafe-inline' 'nonce-yay'", "'unsafe-inline'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline'",
{"'unsafe-inline' 'nonce-yay'", "'unsafe-inline'", "'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline'",
{"'unsafe-inline' 'nonce-yay'", "'unsafe-inline'",
"'strict-dynamic' 'nonce-yay'"},
true},
// `required` does not allow all inline behavior.
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'self' 'strict-dynamic'",
{"'unsafe-inline' http://example1.com/foo/bar.html"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self'",
{"'unsafe-inline'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline'",
{"'unsafe-inline' 'nonce-yay'", "'nonce-abc'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self'",
{"'unsafe-inline' https://example.test/"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
{"'unsafe-inline' https://example.test/"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
{"'unsafe-inline' 'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'nonce-yay'",
{"'unsafe-inline' 'nonce-yay'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic' "
"'nonce-yay'",
{"'unsafe-inline' 'nonce-yay'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic' "
"'nonce-yay'",
{"http://example1.com/foo/ 'unsafe-inline' 'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba' "
"'strict-dynamic'",
{"'unsafe-inline' 'sha512-321cba'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic' "
"'sha512-321cba'",
{"http://example1.com/foo/ 'unsafe-inline' 'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
{"http://example1.com/foo/ 'unsafe-inline'",
"http://example1.com/foo/ 'sha512-321cba'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
{"http://example1.com/foo/ 'unsafe-inline'",
"http://example1.com/foo/ 'unsafe-inline' 'sha512-321cba'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
{"http://example1.com/foo/ 'unsafe-inline' 'nonce-yay'",
"http://example1.com/foo/ 'unsafe-inline' 'sha512-321cba'"},
true},
{mojom::CSPDirectiveName::DefaultSrc,
"http://example1.com/foo/ 'unsafe-inline' 'strict-dynamic'",
{"http://example1.com/foo/ 'unsafe-inline'"},
false},
};
auto origin_b =
mojom::CSPSource::New("https", "frame.test", 443, "", false, false);
for (const auto& test : cases) {
mojom::CSPSourceListPtr required_sources =
ParseToSourceList(test.directive, test.required);
auto response_sources =
ParseToVectorOfSourceLists(test.directive, test.response_csp);
EXPECT_EQ(test.expected,
CSPSourceListSubsumes(*required_sources,
ToRawPointers(response_sources),
test.directive, origin_b.get()))
<< test.required << " should " << (test.expected ? "" : "not ")
<< "subsume " << base::JoinString(test.response_csp, ", ");
}
}
TEST(CSPSourceList, SubsumeUnsafeAttributes) {
struct TestCase {
mojom::CSPDirectiveName directive;
std::string required;
std::vector<std::string> response_csp;
bool expected;
} cases[] = {
// `required` or `response_csp` contain `unsafe-eval`.
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic' "
"'unsafe-eval'",
{"http://example1.com/foo/bar.html 'unsafe-eval'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-eval'",
{"http://example1.com/foo/ 'unsafe-inline'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-eval'",
{"http://example1.com/foo/ 'unsafe-inline' 'unsafe-eval'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-eval'",
{"http://example1.com/foo/ 'unsafe-eval'",
"http://example1.com/foo/bar 'self' unsafe-eval'",
"http://non-example.com/foo/ 'unsafe-eval' 'self'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self'",
{"http://example1.com/foo/ 'unsafe-eval'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline'",
{"http://example1.com/foo/ 'unsafe-eval'",
"http://example1.com/foo/bar 'self' 'unsafe-eval'",
"http://non-example.com/foo/ 'unsafe-eval' 'self'"},
false},
// `required` or `response_csp` contain `unsafe-hashes`.
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'unsafe-eval' "
"'strict-dynamic' "
"'unsafe-hashes'",
{"http://example1.com/foo/bar.html 'unsafe-hashes'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-hashes'",
{"http://example1.com/foo/ 'unsafe-inline'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-hashes'",
{"http://example1.com/foo/ 'unsafe-inline' 'unsafe-hashes'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-eval' "
"'unsafe-hashes'",
{"http://example1.com/foo/ 'unsafe-eval' 'unsafe-hashes'",
"http://example1.com/foo/bar 'self' 'unsafe-hashes'",
"http://non-example.com/foo/ 'unsafe-hashes' 'self'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self'",
{"http://example1.com/foo/ 'unsafe-hashes'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline'",
{"http://example1.com/foo/ 'unsafe-hashes'",
"http://example1.com/foo/bar 'self' 'unsafe-hashes'",
"https://example1.com/foo/bar 'unsafe-hashes' 'self'"},
false},
};
auto origin_b =
mojom::CSPSource::New("https", "frame.test", 443, "", false, false);
for (const auto& test : cases) {
mojom::CSPSourceListPtr required_sources =
ParseToSourceList(test.directive, test.required);
auto response_sources =
ParseToVectorOfSourceLists(test.directive, test.response_csp);
EXPECT_EQ(test.expected,
CSPSourceListSubsumes(*required_sources,
ToRawPointers(response_sources),
test.directive, origin_b.get()))
<< test.required << " should " << (test.expected ? "" : "not ")
<< "subsume " << base::JoinString(test.response_csp, ", ");
}
}
TEST(CSPSourceList, SubsumeNoncesAndHashes) {
// For |required| to subsume |response_csp|:
// - If |response_csp| enforces some nonce, then |required| must contain some
// nonce, but they do not need to match.
// - On the other side, all hashes enforced by |response_csp| must be
// contained in |required|.
struct TestCase {
mojom::CSPDirectiveName directive;
std::string required;
std::vector<std::string> response_csp;
bool expected;
} cases[] = {
// Check nonces.
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'unsafe-inline' 'nonce-abc'",
{"'unsafe-inline'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'nonce-abc'",
{"'nonce-abc'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline'",
{"'unsafe-inline' 'nonce-yay'", "'nonce-yay'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'nonce-yay'",
{"'unsafe-inline' 'nonce-yay'", "'nonce-yay'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'nonce-abc' 'nonce-yay'",
{"'unsafe-inline' https://example.test/"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'nonce-abc' 'nonce-yay'",
{"'nonce-abc' https://example1.com/foo/"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'nonce-yay' "
"'strict-dynamic'",
{"https://example.test/ 'nonce-yay'"},
false},
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'nonce-yay' "
"'strict-dynamic'",
{"'nonce-yay' https://example1.com/foo/"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'nonce-abc'",
{"http://example1.com/foo/ 'nonce-xyz'"},
true},
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'nonce-abc'",
{"http://example1.com/foo/ 'nonce-xyz'"},
true},
// Check hashes.
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
{"http://example1.com/foo/page.html 'strict-dynamic'",
"https://example1.com/foo/ 'sha512-321cba'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
{"http://some-other.com/ 'strict-dynamic' 'sha512-321cba'",
"http://example1.com/foo/ 'unsafe-inline' 'sha512-321cba'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
{"http://example1.com/foo/ 'sha512-321abc' 'sha512-321cba'",
"http://example1.com/foo/ 'sha512-321abc' 'sha512-321cba'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
{"http://example1.com/foo/ 'unsafe-inline'",
"http://example1.com/foo/ 'sha512-321cba'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc'",
{"http://example1.com/foo/ 'unsafe-inline' 'sha512-321abc'",
"http://example1.com/foo/ 'sha512-321abc'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc'",
{"'unsafe-inline' 'sha512-321abc'",
"http://example1.com/foo/ 'sha512-321abc'"},
true},
// Nonces and hashes together.
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc' "
"'nonce-abc'",
{"'unsafe-inline' 'sha512-321abc' 'self'",
"'unsafe-inline''sha512-321abc' https://example.test/"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc' "
"'nonce-abc'",
{"'unsafe-inline' 'sha512-321abc' 'self' 'nonce-abc'",
"'sha512-321abc' https://example.test/"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc' "
"'nonce-abc'",
{"'unsafe-inline' 'sha512-321abc' 'self'",
" 'sha512-321abc' https://example.test/ 'nonce-abc'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc' "
"'nonce-abc'",
{"'unsafe-inline' 'sha512-321abc' 'self' 'nonce-xyz'",
"unsafe-inline' 'sha512-321abc' https://example.test/ 'nonce-xyz'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc' "
"'nonce-abc'",
{"'unsafe-inline' 'sha512-321abc' 'self' 'sha512-xyz'",
"unsafe-inline' 'sha512-321abc' https://example.test/ 'sha512-xyz'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'nonce-abc' 'sha512-321abc'",
{"http://example1.com/foo/ 'nonce-xyz' 'sha512-321abc'"},
true},
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'nonce-abc' 'sha512-321abc'",
{"http://example1.com/foo/ 'nonce-xyz' 'sha512-321abc'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'nonce-abc' 'sha512-321abc'",
{"http://example1.com/foo/ 'nonce-xyz' 'sha512-xyz'"},
false},
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'nonce-abc' 'sha512-321abc'",
{"http://example1.com/foo/ 'nonce-xyz' 'sha512-xyz'",
"http://example1.com/foo/ 'nonce-zyx' 'nonce-xyz' 'sha512-xyz'"},
false},
};
auto origin_b =
mojom::CSPSource::New("https", "frame.test", 443, "", false, false);
for (const auto& test : cases) {
mojom::CSPSourceListPtr required_sources =
ParseToSourceList(test.directive, test.required);
auto response_sources =
ParseToVectorOfSourceLists(test.directive, test.response_csp);
EXPECT_EQ(test.expected,
CSPSourceListSubsumes(*required_sources,
ToRawPointers(response_sources),
test.directive, origin_b.get()))
<< test.required << " should " << (test.expected ? "" : "not ")
<< "subsume " << base::JoinString(test.response_csp, ", ");
}
}
TEST(CSPSourceList, SubsumeStrictDynamic) {
struct TestCase {
mojom::CSPDirectiveName directive;
std::string required;
std::vector<std::string> response_csp;
bool expected;
} cases[] = {
// Neither `required` nor effective policy of `response_csp` has
// `strict-dynamic`.
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'self' 'nonce-yay' 'strict-dynamic'",
{"'strict-dynamic' 'nonce-yay'", "'nonce-yay' 'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'self' 'nonce-yay' 'strict-dynamic'",
{"'strict-dynamic' 'nonce-yay'", "'nonce-abc' 'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'self' 'sha512-321abc'",
{"'strict-dynamic' 'nonce-yay' 'sha512-321abc'",
"'sha512-321abc' 'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'self' 'sha512-321abc'",
{"'strict-dynamic' 'nonce-yay' 'sha512-321abc'",
"'sha512-321abc' 'strict-dynamic'", "'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::StyleSrc,
"http://example1.com/foo/ 'self' 'sha512-321abc'",
{"'strict-dynamic' 'nonce-yay' http://example1.com/",
"http://example1.com/ 'strict-dynamic'"},
false},
// `required` has `strict-dynamic`, effective policy of `response_csp`
// does not.
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'nonce-yay' 'strict-dynamic'",
{"'strict-dynamic' 'nonce-yay'", "'nonce-yay'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
{"'strict-dynamic' 'sha512-321abc'", "'unsafe-inline' 'sha512-321abc'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
{"'sha512-321abc'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
{"http://example1.com/foo/ 'sha512-321abc'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
{"'self' 'sha512-321abc'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
{"'strict-dynamic' 'nonce-yay'", "'nonce-yay'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
{"http://example1.com/ 'sha512-321abc'",
"http://example1.com/ 'sha512-321abc'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'sha512-321abc' 'strict-dynamic'",
{"https://example1.com/foo/ 'sha512-321abc'",
"http://example1.com/foo/ 'sha512-321abc'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'nonce-yay' 'strict-dynamic'",
{"'strict-dynamic' 'nonce-yay'", "'nonce-yay'", "'sha512-321abc'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-hashes' "
"'strict-dynamic'",
{"'strict-dynamic' 'unsafe-hashes'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'nonce-yay' 'strict-dynamic'",
{"'strict-dynamic' 'nonce-yay' 'unsafe-hashes'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-eval' 'strict-dynamic'",
{"'strict-dynamic' 'unsafe-eval'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'nonce-yay' 'strict-dynamic'",
{"'strict-dynamic' 'nonce-yay' 'unsafe-eval'"},
false},
// `required` does not have `strict-dynamic`, but effective policy of
// `response_csp` does.
// Note that any subsumption in this set-up should be `false`.
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'nonce-yay'",
{"'strict-dynamic' 'nonce-yay'", "'sha512-321abc' 'strict-dynamic'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'sha512-321abc'",
{"'strict-dynamic' 'sha512-321abc'", "'strict-dynamic' 'sha512-321abc'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline'",
{"'strict-dynamic'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc'",
{"'strict-dynamic'"},
false},
// Both `required` and effective policy of `response_csp` has
// `strict-dynamic`.
{mojom::CSPDirectiveName::ScriptSrc,
"'strict-dynamic'",
{"'strict-dynamic'", "'strict-dynamic'", "'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"'strict-dynamic'",
{"'strict-dynamic'", "'strict-dynamic' 'nonce-yay'",
"'strict-dynamic' 'nonce-yay'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"'strict-dynamic' 'nonce-yay'",
{"http://example.com 'strict-dynamic' 'nonce-yay'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
{"'strict-dynamic' 'nonce-yay'", "'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
{"'strict-dynamic' http://another.com/",
"http://another.com/ 'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
{"'self' 'sha512-321abc' 'strict-dynamic'",
"'self' 'strict-dynamic' 'sha512-321abc'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'sha512-321abc' 'strict-dynamic'",
{"'self' 'sha512-321abc' 'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
{"'unsafe-inline' 'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
{"'unsafe-inline' 'sha512-123xyz' 'strict-dynamic'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"'unsafe-eval' 'self' 'sha512-321abc' 'strict-dynamic'",
{"'unsafe-eval' 'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
{"'unsafe-eval' 'strict-dynamic'"},
false},
{mojom::CSPDirectiveName::ScriptSrc,
"'unsafe-hashes' 'self' 'sha512-321abc' 'strict-dynamic'",
{"'unsafe-hashes' 'strict-dynamic'"},
true},
{mojom::CSPDirectiveName::ScriptSrc,
"http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
{"'unsafe-hashes' 'strict-dynamic'"},
false},
};
auto origin_b =
mojom::CSPSource::New("https", "frame.test", 443, "", false, false);
for (const auto& test : cases) {
mojom::CSPSourceListPtr required_sources =
ParseToSourceList(test.directive, test.required);
auto response_sources =
ParseToVectorOfSourceLists(test.directive, test.response_csp);
EXPECT_EQ(test.expected,
CSPSourceListSubsumes(*required_sources,
ToRawPointers(response_sources),
test.directive, origin_b.get()))
<< test.required << " should " << (test.expected ? "" : "not ")
<< "subsume " << base::JoinString(test.response_csp, ", ");
}
}
TEST(CSPSourceList, SubsumeListWildcard) {
struct TestCase {
std::string required;
std::vector<std::string> response_csp;
bool expected;
} cases[] = {
// `required` subsumes `response_csp`.
{"*", {""}, true},
{"*", {"'none'"}, true},
{"*", {"*"}, true},
{"*", {"*", "*", "*"}, true},
{"*", {"*", "* https: http: ftp: ws: wss:"}, true},
{"*", {"*", "https: http: ftp: ws: wss:"}, true},
{"https: http: ftp: ws: wss:", {"*", "https: http: ftp: ws: wss:"}, true},
{"http: ftp: ws:", {"*", "https: http: ftp: ws: wss:"}, true},
{"http: ftp: ws:", {"*", "https: 'strict-dynamic'"}, true},
{"http://another.test", {"*", "'self'"}, true},
{"http://another.test/", {"*", "'self'"}, true},
{"http://another.test", {"https:", "'self'"}, true},
{"'self'", {"*", "'self'"}, true},
{"'unsafe-eval' * ", {"'unsafe-eval'"}, true},
{"'unsafe-hashes' * ", {"'unsafe-hashes'"}, true},
{"'unsafe-inline' * ", {"'unsafe-inline'"}, true},
{"*", {"*", "http://a.com ws://b.com ftp://c.com"}, true},
{"*", {"* data: blob:", "http://a.com ws://b.com ftp://c.com"}, true},
{"*", {"data: blob:", "http://a.com ws://b.com ftp://c.com"}, true},
{"*", {"*", "data://a.com ws://b.com ftp://c.com"}, true},
{"* data:",
{"data: blob: *", "data://a.com ws://b.com ftp://c.com"},
true},
{"http://a.com ws://b.com ftp://c.com",
{"*", "http://a.com ws://b.com ftp://c.com"},
true},
// `required` does not subsume `response_csp`.
{"*", std::vector<std::string>(), false},
{"", {"*"}, false},
{"'none'", {"*"}, false},
{"*", {"data:"}, false},
{"*", {"blob:"}, false},
{"http: ftp: ws:",
{"* 'strict-dynamic'", "https: 'strict-dynamic'"},
false},
{"https://another.test", {"*"}, false},
{"*", {"* 'unsafe-eval'"}, false},
{"*", {"* 'unsafe-hashes'"}, false},
{"*", {"* 'unsafe-inline'"}, false},
{"'unsafe-eval'", {"* 'unsafe-eval'"}, false},
{"'unsafe-hashes'", {"* 'unsafe-hashes'"}, false},
{"'unsafe-inline'", {"* 'unsafe-inline'"}, false},
{"*", {"data: blob:", "data://a.com ws://b.com ftp://c.com"}, false},
{"* data:",
{"data: blob:", "blob://a.com ws://b.com ftp://c.com"},
false},
};
auto origin_b =
mojom::CSPSource::New("https", "another.test", 443, "", false, false);
for (const auto& test : cases) {
mojom::CSPSourceListPtr required_sources =
ParseToSourceList(mojom::CSPDirectiveName::ScriptSrc, test.required);
auto response_sources = ParseToVectorOfSourceLists(
mojom::CSPDirectiveName::ScriptSrc, test.response_csp);
EXPECT_EQ(test.expected,
CSPSourceListSubsumes(
*required_sources, ToRawPointers(response_sources),
mojom::CSPDirectiveName::ScriptSrc, origin_b.get()))
<< test.required << " should " << (test.expected ? "" : "not ")
<< "subsume " << base::JoinString(test.response_csp, ", ");
}
}
TEST(CSPSourceList, SubsumeListNoScheme) {
auto origin_http =
mojom::CSPSource::New("http", "example.org", 80, "", false, false);
auto origin_https =
mojom::CSPSource::New("https", "example.org", 443, "", false, false);
struct TestCase {
std::string required;
std::vector<std::string> response_csp;
raw_ptr<mojom::CSPSource> origin;
bool expected;
} cases[] = {
{"http://a.com", {"a.com"}, origin_https.get(), true},
{"https://a.com", {"a.com"}, origin_https.get(), true},
{"https://a.com", {"a.com"}, origin_http.get(), false},
{"data://a.com", {"a.com"}, origin_https.get(), false},
{"a.com", {"a.com"}, origin_https.get(), true},
{"a.com", {"a.com"}, origin_http.get(), true},
{"a.com", {"https://a.com"}, origin_http.get(), true},
{"a.com", {"https://a.com"}, origin_https.get(), true},
{"a.com", {"http://a.com"}, origin_https.get(), false},
{"https:", {"a.com"}, origin_http.get(), false},
{"http:", {"a.com"}, origin_http.get(), true},
{"https:", {"a.com", "https:"}, origin_http.get(), true},
{"https:", {"a.com"}, origin_https.get(), true},
};
for (const auto& test : cases) {
mojom::CSPSourceListPtr required_sources =
ParseToSourceList(mojom::CSPDirectiveName::ScriptSrc, test.required);
auto response_sources = ParseToVectorOfSourceLists(
mojom::CSPDirectiveName::ScriptSrc, test.response_csp);
EXPECT_EQ(test.expected,
CSPSourceListSubsumes(
*required_sources, ToRawPointers(response_sources),
mojom::CSPDirectiveName::ScriptSrc, test.origin))
<< test.required << " on origin with scheme " << test.origin->scheme
<< " should " << (test.expected ? "" : "not ") << "subsume "
<< base::JoinString(test.response_csp, ", ");
}
}
TEST(CSPSourceList, OpaqueURLMatchingAllowStar) {
auto source_list = mojom::CSPSourceList::New();
source_list->allow_star = true;
EXPECT_TRUE(Allow(source_list, GURL("https://not-example.com"), no_self,
/*is_redirect=*/false,
/*is_response_check=*/false,
mojom::CSPDirectiveName::FencedFrameSrc,
/*is_opaque_fenced_frame=*/true));
}
TEST(CSPSourceList, OpaqueURLMatchingAllowSelf) {
auto self = network::mojom::CSPSource::New("https", "example.com", 443, "",
false, false);
auto source_list = mojom::CSPSourceList::New();
source_list->allow_self = true;
EXPECT_FALSE(Allow(
source_list, GURL("https://example.com"), *self, /*is_redirect=*/false,
/*is_response_check=*/false, mojom::CSPDirectiveName::FencedFrameSrc,
/*is_opaque_fenced_frame=*/true));
}
} // namespace network