Commit 05682d58 authored by Antonio Sartori's avatar Antonio Sartori Committed by Commit Bot

Add validation error messages to network CSP parser

The Content-Security-Policy parser in services/network was silently
ignoring all parsing errors. This is not an issue, since CSPs are
being parsed again in blink, where errors are reported to the
console. However, after we bring CSPEE out-of-blink, the frame csp
attribute will not be parsed in blink anymore, so we will need
validation from the network CSP parser. This change implements it.

Bug: 1094909
Change-Id: I38e572900afcc9a4e55148e206690a16345cd99a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2279976Reviewed-by: default avatarMike West <mkwst@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarArthur Sonzogni <arthursonzogni@chromium.org>
Commit-Queue: Antonio Sartori <antoniosartori@chromium.org>
Cr-Commit-Position: refs/heads/master@{#789412}
parent 06c6a360
......@@ -131,6 +131,7 @@ jumbo_component("cpp") {
deps = [
"//base",
"//base/util/ranges:ranges",
"//components/prefs",
"//ipc",
"//services/proxy_resolver/public/mojom",
......
......@@ -6,12 +6,13 @@
#include <sstream>
#include <string>
#include "base/containers/flat_map.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/util/ranges/algorithm.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/content_security_policy/csp_context.h"
#include "services/network/public/cpp/content_security_policy/csp_source.h"
......@@ -27,10 +28,20 @@
namespace network {
using CSPDirectiveName = mojom::CSPDirectiveName;
using DirectivesMap = base::flat_map<base::StringPiece, base::StringPiece>;
using DirectivesMap =
std::vector<std::pair<base::StringPiece, base::StringPiece>>;
namespace {
bool IsDirectiveNameCharacter(char c) {
return base::IsAsciiAlpha(c) || c == '-';
}
bool IsDirectiveValueCharacter(char c) {
return base::IsAsciiWhitespace(c) ||
base::IsAsciiPrintable(c); // Whitespace + VCHAR
}
static CSPDirectiveName CSPFallback(CSPDirectiveName directive,
CSPDirectiveName original_directive) {
switch (directive) {
......@@ -226,21 +237,16 @@ DirectivesMap ParseHeaderValue(base::StringPiece header) {
size_t pos = directive.find_first_of(base::kWhitespaceASCII);
base::StringPiece name = directive.substr(0, pos);
// 5. If policy's directive set contains a directive whose name is
// directive name, continue.
if (result.find(name) != result.end())
continue;
// 6. Let directive value be the result of splitting token on ASCII
// 5. Let directive value be the result of splitting token on ASCII
// whitespace.
base::StringPiece value;
if (pos != std::string::npos)
value = directive.substr(pos + 1);
// 7. Let directive be a new directive whose name is directive name,
// 6. Let directive be a new directive whose name is directive name,
// and value is directive value.
// 8. Append directive to policy's directive set.
result.insert({name, value});
// 7. Append directive to policy's directive set.
result.emplace_back(std::make_pair(name, value));
}
return result;
......@@ -325,10 +331,6 @@ bool ParsePath(base::StringPiece path, mojom::CSPSource* csp_source) {
if (path[0] != '/')
return false;
// TODO(lfg): Emit a warning to the user when a path containing # or ? is
// seen.
path = path.substr(0, path.find_first_of("#?"));
url::RawCanonOutputT<base::char16> unescaped;
url::DecodeURLEscapeSequences(path.data(), path.size(),
url::DecodeURLMode::kUTF8OrIsomorphic,
......@@ -342,12 +344,13 @@ bool ParsePath(base::StringPiece path, mojom::CSPSource* csp_source) {
// https://w3c.github.io/webappsec-csp/#source-lists
//
// Return false on errors.
bool ParseSource(base::StringPiece expression, mojom::CSPSource* csp_source) {
// TODO(arthursonzogni): Blink reports an invalid source expression when
// 'none' is parsed here.
if (base::EqualsCaseInsensitiveASCII(expression, "'none'"))
return false;
// Adds parsing error messages to |parsing_errors|.
// Notice that this can return true and still add some parsing error message
// (for example, if there is a url with a non-empty query part).
bool ParseSource(CSPDirectiveName directive_name,
base::StringPiece expression,
mojom::CSPSource* csp_source,
std::vector<std::string>& parsing_errors) {
size_t position = expression.find_first_of(":/");
if (position != std::string::npos && expression[position] == ':') {
// scheme:
......@@ -394,7 +397,25 @@ bool ParseSource(base::StringPiece expression, mojom::CSPSource* csp_source) {
// /
// ^
return expression.empty() || ParsePath(expression, csp_source);
if (expression.empty())
return true;
// Emit a warning to the user when a url contains a # or ?.
position = expression.find_first_of("#?");
bool path_parsed = ParsePath(expression.substr(0, position), csp_source);
if (path_parsed && position != std::string::npos) {
const char* ignoring =
expression[position] == '?'
? "The query component, including the '?', will be ignored."
: "The fragment identifier, including the '#', will be ignored.";
parsing_errors.emplace_back(base::StringPrintf(
"The source list for Content-Security-Policy directive '%s' "
"contains a source with an invalid path: '%s'. %s",
ToString(directive_name).c_str(), expression.as_string().c_str(),
ignoring));
}
return path_parsed;
}
bool IsBase64Char(char c) {
......@@ -488,8 +509,11 @@ bool ParseHash(base::StringPiece expression, mojom::CSPHashSource* hash) {
// Parse source-list grammar.
// https://www.w3.org/TR/CSP3/#grammardef-serialized-source-list
mojom::CSPSourceListPtr ParseSourceList(CSPDirectiveName directive_name,
base::StringPiece directive_value) {
// Append parsing errors to |parsing_errors|.
mojom::CSPSourceListPtr ParseSourceList(
CSPDirectiveName directive_name,
base::StringPiece directive_value,
std::vector<std::string>& parsing_errors) {
base::StringPiece value =
base::TrimString(directive_value, base::kWhitespaceASCII, base::TRIM_ALL);
......@@ -501,6 +525,16 @@ mojom::CSPSourceListPtr ParseSourceList(CSPDirectiveName directive_name,
for (const auto& expression : base::SplitStringPiece(
value, base::kWhitespaceASCII, base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
if (base::EqualsCaseInsensitiveASCII(expression, "'none'")) {
parsing_errors.emplace_back(base::StringPrintf(
"The Content-Security-Policy directive '%s' contains the keyword "
"'none' alongside with other source expressions. The keyword 'none' "
"must be the only source expression in the directive value, "
"otherwise it is ignored.",
ToString(directive_name).c_str()));
continue;
}
if (base::EqualsCaseInsensitiveASCII(expression, "'self'")) {
directive->allow_self = true;
continue;
......@@ -511,8 +545,18 @@ mojom::CSPSourceListPtr ParseSourceList(CSPDirectiveName directive_name,
continue;
}
if (ToCSPDirectiveName(expression.as_string()) !=
CSPDirectiveName::Unknown) {
parsing_errors.emplace_back(base::StringPrintf(
"The Content-Security-Policy directive '%s' contains '%s' as a "
"source expression. Did you want to add it as a directive and forget "
"a semicolon?",
ToString(directive_name).c_str(), expression.as_string().c_str()));
}
auto csp_source = mojom::CSPSource::New();
if (ParseSource(expression, csp_source.get())) {
if (ParseSource(directive_name, expression, csp_source.get(),
parsing_errors)) {
directive->sources.push_back(std::move(csp_source));
continue;
}
......@@ -520,8 +564,10 @@ mojom::CSPSourceListPtr ParseSourceList(CSPDirectiveName directive_name,
if (directive_name == CSPDirectiveName::FrameAncestors) {
// The frame-ancestors directive does not support anything else
// https://w3c.github.io/webappsec-csp/#directive-frame-ancestors
// TODO(antoniosartori): This is a parsing error, so we should emit a
// warning.
parsing_errors.emplace_back(base::StringPrintf(
"The Content-Security-Policy directive 'frame-ancestors' does not "
"support the source expression '%s'",
expression.as_string().c_str()));
continue;
}
......@@ -576,8 +622,10 @@ mojom::CSPSourceListPtr ParseSourceList(CSPDirectiveName directive_name,
// Parsing error.
// Ignore this source-expression.
// TODO(lfg): Emit a warning to the user when parsing an invalid
// expression.
parsing_errors.emplace_back(base::StringPrintf(
"The source list for the Content-Security-Policy directive '%s' "
"contains an invalid source: '%s'.",
ToString(directive_name).c_str(), expression.as_string().c_str()));
}
return directive;
......@@ -620,42 +668,6 @@ void ParseReportDirective(const GURL& request_url,
}
}
// Parses a directive of a Content-Security-Policy header that adheres to the
// source list grammar.
void ParseSourceListTypeDirective(const mojom::ContentSecurityPolicyPtr& policy,
CSPDirectiveName directive_name,
base::StringPiece value) {
// A directive with this name has already been parsed. Skip further
// directives per
// https://www.w3.org/TR/CSP3/#parse-serialized-policy.
// TODO(arthursonzogni, lfg): Should a warning be fired to the user here?
if (policy->directives.count(directive_name))
return;
auto source_list = ParseSourceList(directive_name, value);
// TODO(lfg): Emit a warning to the user when parsing an invalid
// expression.
if (!source_list)
return;
policy->directives[directive_name] = std::move(source_list);
}
// Parses the report-uri directive of a Content-Security-Policy header.
void ParseReportEndpoint(const mojom::ContentSecurityPolicyPtr& policy,
const GURL& base_url,
base::StringPiece header_value,
bool using_reporting_api) {
// A report-uri directive has already been parsed. Skip further directives per
// https://www.w3.org/TR/CSP3/#parse-serialized-policy.
if (!policy->report_endpoints.empty())
return;
ParseReportDirective(base_url, header_value, using_reporting_api,
&(policy->report_endpoints));
}
void AddContentSecurityPolicyFromHeader(base::StringPiece header,
mojom::ContentSecurityPolicyType type,
const GURL& base_url,
......@@ -665,8 +677,38 @@ void AddContentSecurityPolicyFromHeader(base::StringPiece header,
header.as_string(), type, mojom::ContentSecurityPolicySource::kHTTP);
for (auto directive : directives) {
if (!util::ranges::all_of(directive.first, IsDirectiveNameCharacter)) {
out->parsing_errors.emplace_back(base::StringPrintf(
"The Content-Security-Policy directive name '%s' contains one or "
"more invalid characters. Only ASCII alphanumeric characters or "
"dashes '-' are allowed in directive names.",
directive.first.as_string().c_str()));
continue;
}
if (!util::ranges::all_of(directive.second, IsDirectiveValueCharacter)) {
out->parsing_errors.emplace_back(base::StringPrintf(
"The value for the Content-Security-Policy directive '%s' contains "
"one or more invalid characters. Non-whitespace characters outside "
"ASCII 0x21-0x7E must be percent-encoded, as described in RFC 3986, "
"section 2.1: http://tools.ietf.org/html/rfc3986#section-2.1.",
directive.first.as_string().c_str()));
continue;
}
CSPDirectiveName directive_name =
ToCSPDirectiveName(directive.first.as_string());
// A directive with this name has already been parsed. Skip further
// directives per
// https://www.w3.org/TR/CSP3/#parse-serialized-policy.
if (out->directives.count(directive_name)) {
out->parsing_errors.emplace_back(base::StringPrintf(
"Ignoring duplicate Content-Security-Policy directive '%s'.",
directive.first.as_string().c_str()));
continue;
}
switch (directive_name) {
case CSPDirectiveName::BaseURI:
case CSPDirectiveName::ChildSrc:
......@@ -689,33 +731,56 @@ void AddContentSecurityPolicyFromHeader(base::StringPiece header,
case CSPDirectiveName::StyleSrcAttr:
case CSPDirectiveName::StyleSrcElem:
case CSPDirectiveName::WorkerSrc:
ParseSourceListTypeDirective(out, directive_name, directive.second);
out->directives[directive_name] = ParseSourceList(
directive_name, directive.second, out->parsing_errors);
break;
case CSPDirectiveName::Sandbox:
// Note: |ParseSandboxPolicy(...).error_message| is ignored here.
// Blink's CSP parser is already in charge of displaying it.
out->sandbox = ~ParseWebSandboxPolicy(directive.second,
mojom::WebSandboxFlags::kNone)
.flags;
{
auto sandbox = ParseWebSandboxPolicy(directive.second,
mojom::WebSandboxFlags::kNone);
out->sandbox = ~sandbox.flags;
out->parsing_errors.emplace_back(std::move(sandbox.error_message));
}
break;
case CSPDirectiveName::UpgradeInsecureRequests:
out->upgrade_insecure_requests = true;
if (!directive.second.empty()) {
out->parsing_errors.emplace_back(base::StringPrintf(
"The Content Security Policy directive "
"'upgrade-insecure-requests' should be empty, but was delivered "
"with a value of '%s'. The directive has been applied, and the "
"value ignored.",
directive.second.as_string().c_str()));
}
break;
case CSPDirectiveName::TreatAsPublicAddress:
out->treat_as_public_address = true;
if (!directive.second.empty()) {
out->parsing_errors.emplace_back(base::StringPrintf(
"The Content Security Policy directive 'treat-as-public-address' "
"should be empty, but was delivered with a value of '%s'. The "
"directive has been applied, and the value ignored.",
directive.second.as_string().c_str()));
}
break;
case CSPDirectiveName::ReportTo:
out->use_reporting_api = true;
out->report_endpoints.clear();
ParseReportEndpoint(out, base_url, directive.second,
out->use_reporting_api);
ParseReportDirective(base_url, directive.second, out->use_reporting_api,
&(out->report_endpoints));
break;
case CSPDirectiveName::ReportURI:
if (!out->use_reporting_api)
ParseReportEndpoint(out, base_url, directive.second,
out->use_reporting_api);
ParseReportDirective(base_url, directive.second,
out->use_reporting_api,
&(out->report_endpoints));
break;
case CSPDirectiveName::Unknown:
out->parsing_errors.emplace_back(base::StringPrintf(
"Unrecognized Content-Security-Policy directive '%s'.",
directive.first.as_string().c_str()));
break;
}
}
......
......@@ -233,17 +233,19 @@ TEST(ContentSecurityPolicy, ParseFrameAncestors) {
TestFrameAncestorsCSPParser(test.header, &test.expected_result);
}
TEST(ContentSecurityPolicy, ParseMultipleDirectives) {
// First directive is valid, second one is ignored.
TEST(ContentSecurityPolicy, ParseDirectives) {
// One duplicate directive.
{
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders("HTTP/1.1 200 OK"));
headers->SetHeader("Content-Security-Policy",
"frame-ancestors example.com; other_directive "
"value; frame-ancestors example.org");
"frame-ancestors example.com; script-src "
"example2.com; frame-ancestors example3.com");
std::vector<mojom::ContentSecurityPolicyPtr> policies;
AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"),
&policies);
EXPECT_EQ(2U, policies[0]->directives.size());
auto& frame_ancestors =
policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors];
EXPECT_EQ(frame_ancestors->sources.size(), 1U);
......@@ -254,18 +256,37 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) {
EXPECT_EQ(frame_ancestors->sources[0]->is_host_wildcard, false);
EXPECT_EQ(frame_ancestors->sources[0]->is_port_wildcard, false);
EXPECT_EQ(frame_ancestors->allow_self, false);
auto& script_src =
policies[0]->directives[mojom::CSPDirectiveName::ScriptSrc];
EXPECT_EQ(script_src->sources.size(), 1U);
EXPECT_EQ(script_src->sources[0]->scheme, "");
EXPECT_EQ(script_src->sources[0]->host, "example2.com");
EXPECT_EQ(script_src->sources[0]->port, url::PORT_UNSPECIFIED);
EXPECT_EQ(script_src->sources[0]->path, "");
EXPECT_EQ(script_src->sources[0]->is_host_wildcard, false);
EXPECT_EQ(script_src->sources[0]->is_port_wildcard, false);
EXPECT_EQ(script_src->allow_self, false);
EXPECT_EQ(1U, policies[0]->parsing_errors.size());
EXPECT_EQ(
"Ignoring duplicate Content-Security-Policy directive "
"'frame-ancestors'.",
policies[0]->parsing_errors[0]);
}
// Skip the first directive, which is not frame-ancestors.
// One invalid directive.
{
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders("HTTP/1.1 200 OK"));
headers->SetHeader("Content-Security-Policy",
"other_directive value; frame-ancestors "
"other-directive value; frame-ancestors "
"example.org");
std::vector<mojom::ContentSecurityPolicyPtr> policies;
AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"),
&policies);
EXPECT_EQ(1U, policies[0]->directives.size());
auto& frame_ancestors =
policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors];
EXPECT_EQ(frame_ancestors->sources.size(), 1U);
......@@ -277,6 +298,143 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) {
EXPECT_EQ(frame_ancestors->sources[0]->is_port_wildcard, false);
EXPECT_EQ(frame_ancestors->allow_self, false);
EXPECT_EQ(frame_ancestors->allow_star, false);
EXPECT_EQ(1U, policies[0]->parsing_errors.size());
EXPECT_EQ(
"Unrecognized Content-Security-Policy directive 'other-directive'.",
policies[0]->parsing_errors[0]);
}
// Invalid characters in directive name.
{
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders("HTTP/1.1 200 OK"));
headers->SetHeader("Content-Security-Policy",
"frame_ancestors example.com;");
std::vector<mojom::ContentSecurityPolicyPtr> policies;
AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"),
&policies);
EXPECT_TRUE(policies[0]->directives.empty());
EXPECT_EQ(1U, policies[0]->parsing_errors.size());
EXPECT_EQ(
"The Content-Security-Policy directive name 'frame_ancestors' contains "
"one or more invalid characters. Only ASCII alphanumeric characters or "
"dashes '-' are allowed in directive names.",
policies[0]->parsing_errors[0]);
}
// Invalid characters in directive value.
{
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders("HTTP/1.1 200 OK"));
headers->SetHeader("Content-Security-Policy", "frame-ancestors ü.com;");
std::vector<mojom::ContentSecurityPolicyPtr> policies;
AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"),
&policies);
EXPECT_TRUE(policies[0]->directives.empty());
EXPECT_EQ(1U, policies[0]->parsing_errors.size());
EXPECT_EQ(
"The value for the Content-Security-Policy directive 'frame-ancestors' "
"contains one or more invalid characters. Non-whitespace characters "
"outside ASCII 0x21-0x7E must be percent-encoded, as described in RFC "
"3986, section 2.1: http://tools.ietf.org/html/rfc3986#section-2.1.",
policies[0]->parsing_errors[0]);
}
// Missing semicolon between directive names.
{
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders("HTTP/1.1 200 OK"));
headers->SetHeader("Content-Security-Policy", "frame-ancestors object-src");
std::vector<mojom::ContentSecurityPolicyPtr> policies;
AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"),
&policies);
EXPECT_EQ(1U, policies[0]->directives.size());
auto& frame_ancestors =
policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors];
EXPECT_EQ(frame_ancestors->sources.size(), 1U);
EXPECT_EQ(frame_ancestors->sources[0]->scheme, "");
EXPECT_EQ(frame_ancestors->sources[0]->host, "object-src");
EXPECT_EQ(frame_ancestors->sources[0]->port, url::PORT_UNSPECIFIED);
EXPECT_EQ(frame_ancestors->sources[0]->path, "");
EXPECT_EQ(frame_ancestors->sources[0]->is_host_wildcard, false);
EXPECT_EQ(frame_ancestors->sources[0]->is_port_wildcard, false);
EXPECT_EQ(frame_ancestors->allow_self, false);
EXPECT_EQ(frame_ancestors->allow_star, false);
EXPECT_EQ(1U, policies[0]->parsing_errors.size());
EXPECT_EQ(
"The Content-Security-Policy directive 'frame-ancestors' contains "
"'object-src' as a source expression. Did you want to add it as a "
"directive and forget a semicolon?",
policies[0]->parsing_errors[0]);
}
// Path containing query.
{
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders("HTTP/1.1 200 OK"));
headers->SetHeader("Content-Security-Policy",
"frame-ancestors http://example.org/index.html?a=b;");
std::vector<mojom::ContentSecurityPolicyPtr> policies;
AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"),
&policies);
EXPECT_EQ(1U, policies[0]->directives.size());
auto& frame_ancestors =
policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors];
EXPECT_EQ(frame_ancestors->sources.size(), 1U);
EXPECT_EQ(frame_ancestors->sources[0]->scheme, "http");
EXPECT_EQ(frame_ancestors->sources[0]->host, "example.org");
EXPECT_EQ(frame_ancestors->sources[0]->port, url::PORT_UNSPECIFIED);
EXPECT_EQ(frame_ancestors->sources[0]->path, "/index.html");
EXPECT_EQ(frame_ancestors->sources[0]->is_host_wildcard, false);
EXPECT_EQ(frame_ancestors->sources[0]->is_port_wildcard, false);
EXPECT_EQ(frame_ancestors->allow_self, false);
EXPECT_EQ(frame_ancestors->allow_star, false);
EXPECT_EQ(1U, policies[0]->parsing_errors.size());
EXPECT_EQ(
"The source list for Content-Security-Policy directive "
"'frame-ancestors' contains a source with an invalid path: "
"'/index.html?a=b'. The query component, including the '?', will be "
"ignored.",
policies[0]->parsing_errors[0]);
}
// Path containing ref.
{
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders("HTTP/1.1 200 OK"));
headers->SetHeader("Content-Security-Policy",
"frame-ancestors http://example.org/index.html#a;");
std::vector<mojom::ContentSecurityPolicyPtr> policies;
AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"),
&policies);
EXPECT_EQ(1U, policies[0]->directives.size());
auto& frame_ancestors =
policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors];
EXPECT_EQ(frame_ancestors->sources.size(), 1U);
EXPECT_EQ(frame_ancestors->sources[0]->scheme, "http");
EXPECT_EQ(frame_ancestors->sources[0]->host, "example.org");
EXPECT_EQ(frame_ancestors->sources[0]->port, url::PORT_UNSPECIFIED);
EXPECT_EQ(frame_ancestors->sources[0]->path, "/index.html");
EXPECT_EQ(frame_ancestors->sources[0]->is_host_wildcard, false);
EXPECT_EQ(frame_ancestors->sources[0]->is_port_wildcard, false);
EXPECT_EQ(frame_ancestors->allow_self, false);
EXPECT_EQ(frame_ancestors->allow_star, false);
EXPECT_EQ(1U, policies[0]->parsing_errors.size());
EXPECT_EQ(
"The source list for Content-Security-Policy directive "
"'frame-ancestors' contains a source with an invalid path: "
"'/index.html#a'. The fragment identifier, including the '#', will be "
"ignored.",
policies[0]->parsing_errors[0]);
}
// Multiple CSP headers with multiple frame-ancestors directives present.
......@@ -323,7 +481,7 @@ TEST(ContentSecurityPolicy, ParseMultipleDirectives) {
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders("HTTP/1.1 200 OK"));
headers->SetHeader("Content-Security-Policy",
"other_directive value, frame-ancestors example.org");
"other-directive value, frame-ancestors example.org");
std::vector<mojom::ContentSecurityPolicyPtr> policies;
AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"),
&policies);
......@@ -809,6 +967,7 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) {
struct TestCase {
std::string directive_value;
base::Callback<mojom::CSPSourceListPtr()> expected;
std::string expected_error;
} cases[] = {
{
"'nonce-a' 'nonce-a=' 'nonce-a==' 'nonce-a===' 'nonce-==' 'nonce-' "
......@@ -823,6 +982,7 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) {
csp->nonces.push_back("cde==");
return csp;
}),
"",
},
{
"'sha256-abc' 'sha256-ABC' 'sha256 'sha256-' 'sha384-abc' "
......@@ -846,13 +1006,39 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) {
mojom::CSPHashAlgorithm::SHA512, "cde"));
return csp;
}),
"",
},
{
"'none' ",
base::Bind([] { return mojom::CSPSourceList::New(); }),
"",
},
{
"'none' 'self'",
base::Bind([] {
auto csp = mojom::CSPSourceList::New();
csp->allow_self = true;
return csp;
}),
"The Content-Security-Policy directive 'script-src' contains the "
"keyword 'none' alongside with other source expressions. The keyword "
"'none' must be the only source expression in the directive value, "
"otherwise it is ignored.",
},
{
"'self' 'none'",
base::Bind([] {
auto csp = mojom::CSPSourceList::New();
csp->allow_self = true;
return csp;
}),
"The Content-Security-Policy directive 'script-src' contains the "
"keyword 'none' alongside with other source expressions. The keyword "
"'none' must be the only source expression in the directive value, "
"otherwise it is ignored.",
},
{
"'wrong' 'self'",
"'self'",
base::Bind([] {
auto csp = mojom::CSPSourceList::New();
csp->allow_self = true;
......@@ -866,6 +1052,8 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) {
csp->allow_star = true;
return csp;
}),
"The source list for the Content-Security-Policy directive "
"'script-src' contains an invalid source: ''wrong''.",
},
{
"'wrong' 'unsafe-inline'",
......@@ -874,6 +1062,8 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) {
csp->allow_inline = true;
return csp;
}),
"The source list for the Content-Security-Policy directive "
"'script-src' contains an invalid source: ''wrong''.",
},
{
"'wrong' 'unsafe-eval'",
......@@ -882,6 +1072,8 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) {
csp->allow_eval = true;
return csp;
}),
"The source list for the Content-Security-Policy directive "
"'script-src' contains an invalid source: ''wrong''.",
},
{
"'wrong' 'wasm-eval'",
......@@ -890,6 +1082,8 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) {
csp->allow_wasm_eval = true;
return csp;
}),
"The source list for the Content-Security-Policy directive "
"'script-src' contains an invalid source: ''wrong''.",
},
{
"'wrong' 'strict-dynamic'",
......@@ -898,6 +1092,8 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) {
csp->allow_dynamic = true;
return csp;
}),
"The source list for the Content-Security-Policy directive "
"'script-src' contains an invalid source: ''wrong''.",
},
{
"'wrong' 'unsafe-hashes'",
......@@ -906,6 +1102,8 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) {
csp->allow_unsafe_hashes = true;
return csp;
}),
"The source list for the Content-Security-Policy directive "
"'script-src' contains an invalid source: ''wrong''.",
},
{
"'wrong' 'report-sample'",
......@@ -914,6 +1112,8 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) {
csp->report_sample = true;
return csp;
}),
"The source list for the Content-Security-Policy directive "
"'script-src' contains an invalid source: ''wrong''.",
},
};
......@@ -928,6 +1128,9 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) {
&policies);
EXPECT_TRUE(test.expected.Run().Equals(
policies[0]->directives[mojom::CSPDirectiveName::ScriptSrc]));
if (!test.expected_error.empty())
EXPECT_EQ(test.expected_error, policies[0]->parsing_errors[0]);
}
}
......
......@@ -149,6 +149,9 @@ struct ContentSecurityPolicy {
// Set of reporting endpoints to which violation reports are sent.
array<string> report_endpoints;
// An array containing a set of errors occurred while parsing the CSP header.
array<string> parsing_errors;
};
// Data to report Content-Security-Policy violations.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment