Commit 3c01b472 authored by Antonio Sartori's avatar Antonio Sartori Committed by Commit Bot

Parse plugin-types CSP directive in services/network

This CL adds logic for parsing the plugin-types
Content-Security-Policy directive to the services/network CSP parser.

Bug: 1094909
Change-Id: I05dd7caf7b2d3c513b80f2265723bc1a0be04b3b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2346657
Commit-Queue: Antonio Sartori <antoniosartori@chromium.org>
Reviewed-by: default avatarCamille Lamy <clamy@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Reviewed-by: default avatarArthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#804774}
parent 60cfc65c
......@@ -95,6 +95,7 @@ std::string URLDataSource::GetContentSecurityPolicy(
case network::mojom::CSPDirectiveName::ImgSrc:
case network::mojom::CSPDirectiveName::ManifestSrc:
case network::mojom::CSPDirectiveName::MediaSrc:
case network::mojom::CSPDirectiveName::PluginTypes:
case network::mojom::CSPDirectiveName::PrefetchSrc:
case network::mojom::CSPDirectiveName::ReportURI:
case network::mojom::CSPDirectiveName::Sandbox:
......
......@@ -92,6 +92,7 @@ static CSPDirectiveName CSPFallback(CSPDirectiveName directive,
case CSPDirectiveName::FormAction:
case CSPDirectiveName::FrameAncestors:
case CSPDirectiveName::NavigateTo:
case CSPDirectiveName::PluginTypes:
case CSPDirectiveName::ReportTo:
case CSPDirectiveName::ReportURI:
case CSPDirectiveName::RequireTrustedTypesFor:
......@@ -140,6 +141,7 @@ const char* ErrorMessage(CSPDirectiveName directive) {
case CSPDirectiveName::ManifestSrc:
case CSPDirectiveName::MediaSrc:
case CSPDirectiveName::ObjectSrc:
case CSPDirectiveName::PluginTypes:
case CSPDirectiveName::PrefetchSrc:
case CSPDirectiveName::ReportTo:
case CSPDirectiveName::ReportURI:
......@@ -637,6 +639,41 @@ mojom::CSPSourceListPtr ParseSourceList(
return directive;
}
// Checks whether |expression| is a plugin type matching the regex:
// [^\s/]+\/[^\s/]+
// We assume |expression| does not contain any whitespaces.
bool IsPluginType(base::StringPiece expression) {
auto* it = expression.begin();
auto* end = expression.end();
int count_1 = EatChar(&it, end, [](char c) { return c != '/'; });
if (it == end || *it != '/')
return false;
++it;
int count_2 = EatChar(&it, end, [](char c) { return c != '/'; });
return count_1 >= 1 && count_2 >= 1 && it == end;
}
std::vector<std::string> ParsePluginTypes(
base::StringPiece value,
std::vector<std::string>& parsing_errors) {
std::vector<std::string> out;
for (const auto expression : base::SplitStringPiece(
value, base::kWhitespaceASCII, base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
if (IsPluginType(expression))
out.emplace_back(expression.as_string());
else {
parsing_errors.emplace_back(base::StringPrintf(
"Invalid plugin type in 'plugin-types' Content Security Policy "
"directive: '%s'.",
expression.as_string().c_str()));
}
}
return out;
}
// Parses a reporting directive.
// https://w3c.github.io/webappsec-csp/#directives-reporting
// TODO(lfg): The report-to should be treated as a single token according to the
......@@ -771,6 +808,13 @@ void AddContentSecurityPolicyFromHeader(base::StringPiece header,
directive.second.as_string().c_str()));
}
break;
case CSPDirectiveName::PluginTypes:
// If the plugin-types directive is present, then always initialize
// `out->plugin_types` to be non-null, since only the plugin types
// explicitly listed will be allowed..
out->plugin_types =
ParsePluginTypes(directive.second, out->parsing_errors);
break;
// We check the following three directives so that we do not trigger a
// warning because of an unrecognized directive. However, we skip
......@@ -1070,6 +1114,8 @@ CSPDirectiveName ToCSPDirectiveName(const std::string& name) {
return CSPDirectiveName::MediaSrc;
if (name == "object-src")
return CSPDirectiveName::ObjectSrc;
if (name == "plugin-types")
return CSPDirectiveName::PluginTypes;
if (name == "prefetch-src")
return CSPDirectiveName::PrefetchSrc;
if (name == "report-uri")
......@@ -1134,6 +1180,8 @@ std::string ToString(CSPDirectiveName name) {
return "media-src";
case CSPDirectiveName::ObjectSrc:
return "object-src";
case CSPDirectiveName::PluginTypes:
return "plugin-types";
case CSPDirectiveName::PrefetchSrc:
return "prefetch-src";
case CSPDirectiveName::ReportURI:
......
......@@ -39,6 +39,16 @@ struct TestData {
ExpectedResult expected_result = ExpectedResult();
};
std::vector<mojom::ContentSecurityPolicyPtr> ParseCSP(std::string expression) {
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders("HTTP/1.1 200 OK"));
headers->SetHeader("Content-Security-Policy", expression);
std::vector<mojom::ContentSecurityPolicyPtr> policies;
AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"),
&policies);
return policies;
}
static void TestFrameAncestorsCSPParser(const std::string& header,
const ExpectedResult* expected_result) {
scoped_refptr<net::HttpResponseHeaders> headers(
......@@ -569,6 +579,51 @@ TEST(ContentSecurityPolicy, ParseDirectives) {
}
}
TEST(ContentSecurityPolicy, ParsePluginTypes) {
{
std::vector<mojom::ContentSecurityPolicyPtr> policies =
ParseCSP("plugin-types application/pdf text/plain invalid a/a/a");
EXPECT_EQ(policies[0]->directives.size(), 0u);
EXPECT_TRUE(policies[0]->plugin_types.has_value());
EXPECT_EQ(policies[0]->plugin_types.value().size(), 2u);
EXPECT_EQ(policies[0]->plugin_types.value()[0], "application/pdf");
EXPECT_EQ(policies[0]->plugin_types.value()[1], "text/plain");
EXPECT_EQ(policies[0]->parsing_errors.size(), 2u);
EXPECT_EQ(policies[0]->parsing_errors[0],
"Invalid plugin type in 'plugin-types' Content Security Policy "
"directive: 'invalid'.");
EXPECT_EQ(policies[0]->parsing_errors[1],
"Invalid plugin type in 'plugin-types' Content Security Policy "
"directive: 'a/a/a'.");
}
{
std::vector<mojom::ContentSecurityPolicyPtr> policies =
ParseCSP("plugin-types ; default-src 'self'");
EXPECT_TRUE(policies[0]->plugin_types.has_value());
EXPECT_EQ(policies[0]->plugin_types.value().size(), 0u);
EXPECT_EQ(policies[0]->parsing_errors.size(), 0u);
}
{
std::vector<mojom::ContentSecurityPolicyPtr> policies =
ParseCSP("plugin-types 'self' ; default-src 'self'");
EXPECT_TRUE(policies[0]->plugin_types.has_value());
EXPECT_EQ(policies[0]->plugin_types.value().size(), 0u);
EXPECT_EQ(policies[0]->parsing_errors.size(), 1u);
EXPECT_EQ(policies[0]->parsing_errors[0],
"Invalid plugin type in 'plugin-types' Content Security Policy "
"directive: ''self''.");
}
{
std::vector<mojom::ContentSecurityPolicyPtr> policies =
ParseCSP("default-src 'self'");
EXPECT_FALSE(policies[0]->plugin_types.has_value());
EXPECT_EQ(policies[0]->parsing_errors.size(), 0u);
}
}
TEST(ContentSecurityPolicy, ParseReportEndpoint) {
// report-uri directive.
{
......
......@@ -109,6 +109,7 @@ enum CSPDirectiveName {
MediaSrc,
NavigateTo,
ObjectSrc,
PluginTypes,
PrefetchSrc,
ReportTo,
ReportURI,
......@@ -153,6 +154,12 @@ struct ContentSecurityPolicy {
// Set of reporting endpoints to which violation reports are sent.
array<string> report_endpoints;
// Plugin types specified by the CSP. If `null`, no plugin-types directive is
// specified and hence all types are allowed. If an empty array, then the
// plugin-types directive is specified with no types, hence no types are
// allowed.
array<string>? plugin_types;
// An array containing a set of errors occurred while parsing the CSP header.
array<string> parsing_errors;
};
......
......@@ -129,6 +129,10 @@ blink::ContentSecurityPolicyPtr ConvertToBlink(
policy_in->sandbox, ConvertToBlink(std::move(policy_in->header)),
policy_in->use_reporting_api,
ConvertToBlink(std::move(policy_in->report_endpoints)),
policy_in->plugin_types.has_value()
? base::Optional<WTF::Vector<WTF::String>>(
ConvertToBlink(std::move(policy_in->plugin_types.value())))
: base::nullopt,
ConvertToBlink(std::move(policy_in->parsing_errors)));
}
......
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