blob: 71c7bf87612d624963d4d5d876ad0e4ca45db85b [file] [log] [blame]
// Copyright 2015 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/CSPSource.h"
#include "core/dom/Document.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "platform/network/ResourceRequest.h"
#include "platform/weborigin/KURL.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
class CSPSourceTest : public ::testing::Test {
public:
CSPSourceTest() : csp(ContentSecurityPolicy::create()) {}
protected:
Persistent<ContentSecurityPolicy> csp;
};
TEST_F(CSPSourceTest, BasicMatching) {
KURL base;
CSPSource source(csp.get(), "http", "example.com", 8000, "/foo/",
CSPSource::NoWildcard, CSPSource::NoWildcard);
EXPECT_TRUE(source.matches(KURL(base, "http://example.com:8000/foo/")));
EXPECT_TRUE(source.matches(KURL(base, "http://example.com:8000/foo/bar")));
EXPECT_TRUE(source.matches(KURL(base, "HTTP://EXAMPLE.com:8000/foo/BAR")));
EXPECT_FALSE(source.matches(KURL(base, "http://example.com:8000/bar/")));
EXPECT_FALSE(source.matches(KURL(base, "https://example.com:8000/bar/")));
EXPECT_FALSE(source.matches(KURL(base, "http://example.com:9000/bar/")));
EXPECT_FALSE(source.matches(KURL(base, "HTTP://example.com:8000/FOO/bar")));
EXPECT_FALSE(source.matches(KURL(base, "HTTP://example.com:8000/FOO/BAR")));
}
TEST_F(CSPSourceTest, WildcardMatching) {
KURL base;
CSPSource source(csp.get(), "http", "example.com", 0, "/",
CSPSource::HasWildcard, CSPSource::HasWildcard);
EXPECT_TRUE(source.matches(KURL(base, "http://foo.example.com:8000/")));
EXPECT_TRUE(source.matches(KURL(base, "http://foo.example.com:8000/foo")));
EXPECT_TRUE(source.matches(KURL(base, "http://foo.example.com:9000/foo/")));
EXPECT_TRUE(
source.matches(KURL(base, "HTTP://FOO.EXAMPLE.com:8000/foo/BAR")));
EXPECT_FALSE(source.matches(KURL(base, "http://example.com:8000/")));
EXPECT_FALSE(source.matches(KURL(base, "http://example.com:8000/foo")));
EXPECT_FALSE(source.matches(KURL(base, "http://example.com:9000/foo/")));
EXPECT_FALSE(source.matches(KURL(base, "http://example.foo.com:8000/")));
EXPECT_FALSE(source.matches(KURL(base, "https://example.foo.com:8000/")));
EXPECT_FALSE(source.matches(KURL(base, "https://example.com:8000/bar/")));
}
TEST_F(CSPSourceTest, RedirectMatching) {
KURL base;
CSPSource source(csp.get(), "http", "example.com", 8000, "/bar/",
CSPSource::NoWildcard, CSPSource::NoWildcard);
EXPECT_TRUE(
source.matches(KURL(base, "http://example.com:8000/"),
ResourceRequest::RedirectStatus::FollowedRedirect));
EXPECT_TRUE(
source.matches(KURL(base, "http://example.com:8000/foo"),
ResourceRequest::RedirectStatus::FollowedRedirect));
EXPECT_TRUE(
source.matches(KURL(base, "https://example.com:8000/foo"),
ResourceRequest::RedirectStatus::FollowedRedirect));
EXPECT_FALSE(
source.matches(KURL(base, "http://not-example.com:8000/foo"),
ResourceRequest::RedirectStatus::FollowedRedirect));
EXPECT_FALSE(source.matches(KURL(base, "http://example.com:9000/foo/"),
ResourceRequest::RedirectStatus::NoRedirect));
}
TEST_F(CSPSourceTest, InsecureSchemeMatchesSecureScheme) {
KURL base;
CSPSource source(csp.get(), "http", "", 0, "/", CSPSource::NoWildcard,
CSPSource::HasWildcard);
EXPECT_TRUE(source.matches(KURL(base, "http://example.com:8000/")));
EXPECT_TRUE(source.matches(KURL(base, "https://example.com:8000/")));
EXPECT_TRUE(source.matches(KURL(base, "http://not-example.com:8000/")));
EXPECT_TRUE(source.matches(KURL(base, "https://not-example.com:8000/")));
EXPECT_FALSE(source.matches(KURL(base, "ftp://example.com:8000/")));
}
TEST_F(CSPSourceTest, InsecureHostSchemeMatchesSecureScheme) {
KURL base;
CSPSource source(csp.get(), "http", "example.com", 0, "/",
CSPSource::NoWildcard, CSPSource::HasWildcard);
EXPECT_TRUE(source.matches(KURL(base, "http://example.com:8000/")));
EXPECT_FALSE(source.matches(KURL(base, "http://not-example.com:8000/")));
EXPECT_TRUE(source.matches(KURL(base, "https://example.com:8000/")));
EXPECT_FALSE(source.matches(KURL(base, "https://not-example.com:8000/")));
}
TEST_F(CSPSourceTest, InsecureHostSchemePortMatchesSecurePort) {
KURL base;
CSPSource source(csp.get(), "http", "example.com", 80, "/",
CSPSource::NoWildcard, CSPSource::NoWildcard);
EXPECT_TRUE(source.matches(KURL(base, "http://example.com/")));
EXPECT_TRUE(source.matches(KURL(base, "http://example.com:80/")));
EXPECT_TRUE(source.matches(KURL(base, "http://example.com:443/")));
EXPECT_TRUE(source.matches(KURL(base, "https://example.com/")));
EXPECT_TRUE(source.matches(KURL(base, "https://example.com:80/")));
EXPECT_TRUE(source.matches(KURL(base, "https://example.com:443/")));
EXPECT_FALSE(source.matches(KURL(base, "http://example.com:8443/")));
EXPECT_FALSE(source.matches(KURL(base, "https://example.com:8443/")));
EXPECT_FALSE(source.matches(KURL(base, "http://not-example.com/")));
EXPECT_FALSE(source.matches(KURL(base, "http://not-example.com:80/")));
EXPECT_FALSE(source.matches(KURL(base, "http://not-example.com:443/")));
EXPECT_FALSE(source.matches(KURL(base, "https://not-example.com/")));
EXPECT_FALSE(source.matches(KURL(base, "https://not-example.com:80/")));
EXPECT_FALSE(source.matches(KURL(base, "https://not-example.com:443/")));
}
TEST_F(CSPSourceTest, DoesNotSubsume) {
struct Source {
const char* scheme;
const char* host;
const char* path;
const int port;
};
struct TestCase {
const Source a;
const Source b;
} cases[] = {
{{"http", "example.com", "/", 0}, {"http", "another.com", "/", 0}},
{{"wss", "example.com", "/", 0}, {"http", "example.com", "/", 0}},
{{"wss", "example.com", "/", 0}, {"about", "example.com", "/", 0}},
{{"http", "example.com", "/", 0}, {"about", "example.com", "/", 0}},
{{"http", "example.com", "/1.html", 0},
{"http", "example.com", "/2.html", 0}},
{{"http", "example.com", "/", 443}, {"about", "example.com", "/", 800}},
};
for (const auto& test : cases) {
CSPSource* returned = new CSPSource(
csp.get(), test.a.scheme, test.a.host, test.a.port, test.a.path,
CSPSource::NoWildcard, CSPSource::NoWildcard);
CSPSource* required = new CSPSource(
csp.get(), test.b.scheme, test.b.host, test.b.port, test.b.path,
CSPSource::NoWildcard, CSPSource::NoWildcard);
EXPECT_FALSE(required->subsumes(returned));
// Verify the same test with a and b swapped.
EXPECT_FALSE(required->subsumes(returned));
}
}
TEST_F(CSPSourceTest, Subsumes) {
struct Source {
const char* scheme;
const char* path;
const int port;
};
struct TestCase {
const Source a;
const Source b;
bool expected;
bool expectedWhenSwapped;
} cases[] = {
// Equal signals
{{"http", "/", 0}, {"http", "/", 0}, true, true},
{{"https", "/", 0}, {"https", "/", 0}, true, true},
{{"https", "/page1.html", 0}, {"https", "/page1.html", 0}, true, true},
{{"http", "/", 70}, {"http", "/", 70}, true, true},
{{"https", "/", 70}, {"https", "/", 70}, true, true},
{{"https", "/page1.html", 0}, {"https", "/page1.html", 0}, true, true},
{{"http", "/page1.html", 70}, {"http", "/page1.html", 70}, true, true},
{{"https", "/page1.html", 70}, {"https", "/page1.html", 70}, true, true},
// One stronger signal in the first CSPSource
{{"https", "/", 0}, {"http", "/", 0}, true, false},
{{"http", "/page1.html", 0}, {"http", "/", 0}, true, false},
{{"http", "/", 80}, {"http", "/", 0}, true, true},
{{"http", "/", 700}, {"http", "/", 0}, false, false},
// Two stronger signals in the first CSPSource
{{"https", "/page1.html", 0}, {"http", "/", 0}, true, false},
{{"https", "/", 80}, {"http", "/", 0}, false, false},
{{"http", "/page1.html", 80}, {"http", "/", 0}, true, false},
// Three stronger signals in the first CSPSource
{{"https", "/page1.html", 70}, {"http", "/", 0}, false, false},
// Mixed signals
{{"https", "/", 0}, {"http", "/page1.html", 0}, false, false},
{{"https", "/", 0}, {"http", "/", 70}, false, false},
{{"http", "/page1.html", 0}, {"http", "/", 70}, false, false},
};
for (const auto& test : cases) {
CSPSource* returned = new CSPSource(
csp.get(), test.a.scheme, "example.com", test.a.port, test.a.path,
CSPSource::NoWildcard, CSPSource::NoWildcard);
CSPSource* required = new CSPSource(
csp.get(), test.b.scheme, "example.com", test.b.port, test.b.path,
CSPSource::NoWildcard, CSPSource::NoWildcard);
EXPECT_EQ(required->subsumes(returned), test.expected);
// Verify the same test with a and b swapped.
EXPECT_EQ(returned->subsumes(required), test.expectedWhenSwapped);
}
// When returned CSP has a wildcard but the required csp doesn't, then it is
// not subsumed.
for (const auto& test : cases) {
CSPSource* returned = new CSPSource(
csp.get(), test.a.scheme, "example.com", test.a.port, test.a.path,
CSPSource::HasWildcard, CSPSource::NoWildcard);
CSPSource* required = new CSPSource(
csp.get(), test.b.scheme, "example.com", test.b.port, test.b.path,
CSPSource::NoWildcard, CSPSource::NoWildcard);
EXPECT_FALSE(required->subsumes(returned));
// If required csp also allows a wildcard in host, then the answer should be
// as expected.
CSPSource* required2 = new CSPSource(
csp.get(), test.b.scheme, "example.com", test.b.port, test.b.path,
CSPSource::HasWildcard, CSPSource::NoWildcard);
EXPECT_EQ(required2->subsumes(returned), test.expected);
}
}
TEST_F(CSPSourceTest, WildcardsSubsumes) {
struct Wildcards {
CSPSource::WildcardDisposition hostDispotion;
CSPSource::WildcardDisposition portDispotion;
};
struct TestCase {
const Wildcards a;
const Wildcards b;
bool expected;
} cases[] = {
// One out of four possible wildcards.
{{CSPSource::HasWildcard, CSPSource::NoWildcard},
{CSPSource::NoWildcard, CSPSource::NoWildcard},
false},
{{CSPSource::NoWildcard, CSPSource::HasWildcard},
{CSPSource::NoWildcard, CSPSource::NoWildcard},
false},
{{CSPSource::NoWildcard, CSPSource::NoWildcard},
{CSPSource::NoWildcard, CSPSource::HasWildcard},
true},
{{CSPSource::NoWildcard, CSPSource::NoWildcard},
{CSPSource::HasWildcard, CSPSource::NoWildcard},
true},
// Two out of four possible wildcards.
{{CSPSource::HasWildcard, CSPSource::HasWildcard},
{CSPSource::NoWildcard, CSPSource::NoWildcard},
false},
{{CSPSource::HasWildcard, CSPSource::NoWildcard},
{CSPSource::HasWildcard, CSPSource::NoWildcard},
true},
{{CSPSource::HasWildcard, CSPSource::NoWildcard},
{CSPSource::NoWildcard, CSPSource::HasWildcard},
false},
{{CSPSource::NoWildcard, CSPSource::HasWildcard},
{CSPSource::HasWildcard, CSPSource::NoWildcard},
false},
{{CSPSource::NoWildcard, CSPSource::HasWildcard},
{CSPSource::NoWildcard, CSPSource::HasWildcard},
true},
{{CSPSource::NoWildcard, CSPSource::NoWildcard},
{CSPSource::HasWildcard, CSPSource::HasWildcard},
true},
// Three out of four possible wildcards.
{{CSPSource::HasWildcard, CSPSource::HasWildcard},
{CSPSource::HasWildcard, CSPSource::NoWildcard},
false},
{{CSPSource::HasWildcard, CSPSource::HasWildcard},
{CSPSource::NoWildcard, CSPSource::HasWildcard},
false},
{{CSPSource::HasWildcard, CSPSource::NoWildcard},
{CSPSource::HasWildcard, CSPSource::HasWildcard},
true},
{{CSPSource::NoWildcard, CSPSource::HasWildcard},
{CSPSource::HasWildcard, CSPSource::HasWildcard},
true},
// Four out of four possible wildcards.
{{CSPSource::HasWildcard, CSPSource::HasWildcard},
{CSPSource::HasWildcard, CSPSource::HasWildcard},
true},
};
// There are different cases for wildcards but now also the second CSPSource
// has a more specific path.
for (const auto& test : cases) {
CSPSource* returned =
new CSPSource(csp.get(), "http", "example.com", 0, "/",
test.a.hostDispotion, test.a.portDispotion);
CSPSource* required =
new CSPSource(csp.get(), "http", "example.com", 0, "/",
test.b.hostDispotion, test.b.portDispotion);
EXPECT_EQ(required->subsumes(returned), test.expected);
// Wildcards should not matter when required csp is stricter than returned
// csp.
CSPSource* required2 =
new CSPSource(csp.get(), "https", "example.com", 0, "/",
test.b.hostDispotion, test.b.portDispotion);
EXPECT_FALSE(required2->subsumes(returned));
}
}
TEST_F(CSPSourceTest, SchemesOnlySubsumes) {
struct TestCase {
String aScheme;
String bScheme;
bool expected;
} cases[] = {
// HTTP
{"http", "http", true},
{"http", "https", false},
{"https", "http", true},
{"https", "https", true},
// WSS
{"ws", "ws", true},
{"ws", "wss", false},
{"wss", "ws", true},
{"wss", "wss", true},
// Unequal
{"ws", "http", false},
{"http", "ws", false},
{"http", "about", false},
};
for (const auto& test : cases) {
CSPSource* returned =
new CSPSource(csp.get(), test.aScheme, "example.com", 0, "/",
CSPSource::NoWildcard, CSPSource::NoWildcard);
CSPSource* required =
new CSPSource(csp.get(), test.bScheme, "example.com", 0, "/",
CSPSource::NoWildcard, CSPSource::NoWildcard);
EXPECT_EQ(required->subsumes(returned), test.expected);
}
}
} // namespace blink