Index: content/common/content_security_policy/csp_policy.cc |
diff --git a/content/common/content_security_policy/csp_policy.cc b/content/common/content_security_policy/csp_policy.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..457962318c6354b011eae0d2b046d45b031609b2 |
--- /dev/null |
+++ b/content/common/content_security_policy/csp_policy.cc |
@@ -0,0 +1,240 @@ |
+// Copyright 2017 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 <sstream> |
+ |
+#include "base/strings/string_split.h" |
+#include "base/strings/string_util.h" |
+#include "content/common/content_security_policy/csp_context.h" |
+ |
+namespace content { |
+ |
+namespace { |
+ |
+static CSPDirective::Name CSPFallback(CSPDirective::Name directive) { |
+ if (directive == CSPDirective::FrameSrc) |
+ return CSPDirective::ChildSrc; |
+ if (directive == CSPDirective::ChildSrc) |
+ return CSPDirective::DefaultSrc; |
+ return CSPDirective::Unknown; |
+} |
+ |
+void ReportUnsupportedDirective(CSPContext* context, |
+ const base::StringPiece& name) { |
+ std::string message = "Unrecognized Content-Security-Policy directive '" + |
+ name.as_string() + "'.\n"; |
+ context->LogToConsole(message); |
+} |
+ |
+void ReportDuplicateDirective(CSPContext* context, |
+ const base::StringPiece& directive) { |
+ std::string message = |
+ "Ignoring duplicate Content-Security-Policy directive '" + |
+ directive.as_string() + "'.\n"; |
+ context->LogToConsole(message); |
+} |
+ |
+void ReportInvalidDirectiveInMeta(CSPContext* context, |
+ const base::StringPiece& directive) { |
+ std::string message = |
+ "Content Security Policies delivered via a <meta> element may not " |
+ "contain the " + |
+ directive.as_string() + " directive."; |
+ context->LogToConsole(message); |
+} |
+ |
+void AddReportURI(CSPPolicy* policy, |
+ CSPContext* context, |
+ const base::StringPiece& directive_value) { |
+ if (!policy->report_end_points.empty()) |
+ ReportDuplicateDirective(context, "report-uri"); |
+ // Remove report-uri in meta policies, per |
+ // https://www.w3.org/TR/CSP2/#delivery-html-meta-element. |
+ if (policy->source == blink::WebContentSecurityPolicySourceMeta) { |
+ ReportInvalidDirectiveInMeta(context, "report-uri"); |
+ return; |
+ } |
+ |
+ for (const base::StringPiece& end_point : |
+ base::SplitStringPiece(directive_value, " ", base::TRIM_WHITESPACE, |
+ base::SPLIT_WANT_NONEMPTY)) { |
+ policy->report_end_points.push_back(end_point.as_string()); |
+ } |
+} |
+ |
+void AddDirective(CSPPolicy* policy, |
+ CSPContext* context, |
+ const base::StringPiece& directive_name, |
+ const base::StringPiece& directive_value) { |
+ CSPDirective::Name name = |
+ CSPDirective::StringToName(directive_name.as_string()); |
+ |
+ // Unknown directive |
+ if (name == CSPDirective::Unknown) { |
+ ReportUnsupportedDirective(context, directive_name); |
+ } |
+ |
+ // Report-uri |
+ if (name == CSPDirective::ReportURI) { |
+ AddReportURI(policy, context, directive_value); |
+ return; |
+ } |
+ |
+ // Remove frame-ancestors directives in meta policies, per |
+ // https://www.w3.org/TR/CSP2/#delivery-html-meta-element. |
+ if (name == CSPDirective::FrameAncestors && |
+ policy->source == blink::WebContentSecurityPolicySourceMeta) { |
+ ReportInvalidDirectiveInMeta(context, directive_name); |
+ return; |
+ } |
+ |
+ for (const auto& directive : policy->directives) { |
+ if (directive.name == name) |
+ ReportDuplicateDirective(context, directive_name); |
+ } |
+ |
+ policy->directives.emplace_back( |
+ name, CSPSourceList::Parse(context, directive_name, directive_value)); |
+} |
+ |
+} // namespace |
+ |
+CSPPolicy::CSPPolicy() |
+ : disposition(blink::WebContentSecurityPolicyTypeEnforce), |
+ source(blink::WebContentSecurityPolicySourceHTTP), |
+ directives(), |
+ report_end_points() {} |
+ |
+CSPPolicy::CSPPolicy(blink::WebContentSecurityPolicyType disposition, |
+ blink::WebContentSecurityPolicySource source, |
+ const std::vector<CSPDirective>& directives, |
+ const std::vector<std::string>& report_end_points) |
+ : disposition(disposition), |
+ source(source), |
+ directives(directives), |
+ report_end_points(report_end_points) {} |
+ |
+CSPPolicy::CSPPolicy(const CSPPolicy&) = default; |
+CSPPolicy::~CSPPolicy() = default; |
+ |
+// static |
+CSPPolicy CSPPolicy::Parse(CSPContext* context, |
+ const ContentSecurityPolicyHeader& header) { |
+ CSPPolicy policy; |
+ policy.disposition = header.type; |
+ policy.source = header.source; |
+ |
+ // Split by ";" |
+ for (const base::StringPiece& serialized_directive : |
+ base::SplitStringPiece(header.header_value, ";", base::TRIM_WHITESPACE, |
+ base::SPLIT_WANT_NONEMPTY)) { |
+ base::StringPiece directive_name; |
+ base::StringPiece directive_value; |
+ |
+ // Split serialized-directive into { directive-name , directive-value }. |
+ size_t first_space_pos = |
+ serialized_directive.find_first_of(base::kWhitespaceASCII, 0); |
+ directive_name = serialized_directive.substr(0, first_space_pos); |
+ if (first_space_pos != std::string::npos) { |
+ size_t first_non_space_pos = serialized_directive.find_first_not_of( |
+ base::kWhitespaceASCII, first_space_pos); |
+ directive_value = |
+ serialized_directive.substr(first_non_space_pos, std::string::npos); |
+ } |
+ |
+ AddDirective(&policy, context, directive_name, directive_value); |
+ } |
+ return policy; |
+} |
+ |
+bool CSPPolicy::Allow(CSPContext* context, |
+ CSPDirective::Name directive_name, |
+ const GURL& url, |
+ bool is_redirect) const { |
+ CSPDirective::Name current_directive_name = directive_name; |
+ do { |
+ // Search for the matching directive. |
+ for (const CSPDirective& directive : directives) { |
+ if (directive.name == current_directive_name) { |
+ return AllowDirective(context, directive_name, directive, url, |
+ is_redirect); |
+ } |
+ } |
+ |
+ current_directive_name = CSPFallback(current_directive_name); |
+ } while (current_directive_name != CSPDirective::Unknown); |
+ return false; |
+} |
+ |
+std::string CSPPolicy::ToString() const { |
+ std::stringstream text; |
+ bool is_first_policy = true; |
+ for (const CSPDirective& directive : directives) { |
+ if (!is_first_policy) |
+ text << "; "; |
+ text << directive.ToString(); |
+ is_first_policy = false; |
+ } |
+ return text.str(); |
+} |
+ |
+bool CSPPolicy::AllowDirective(CSPContext* context, |
+ CSPDirective::Name directive_name, |
+ const CSPDirective& directive, |
+ const GURL& url, |
+ bool is_redirect) const { |
+ if (directive.source_list.Allow(context, url, is_redirect)) |
+ return true; |
+ |
+ ReportViolation(context, directive_name, directive, url); |
+ |
+ return disposition == blink::WebContentSecurityPolicyTypeReport; |
+} |
+ |
+void CSPPolicy::ReportViolation(CSPContext* context, |
+ const CSPDirective::Name directive_name, |
+ const CSPDirective& directive, |
+ const GURL& url) const { |
+ // We should never have a violation against `child-src` or `default-src` |
+ // directly; the effective directive should always be one of the explicit |
+ // fetch directives. |
+ DCHECK_NE(directive_name, CSPDirective::DefaultSrc); |
+ DCHECK_NE(directive_name, CSPDirective::ChildSrc); |
+ |
+ std::stringstream message; |
+ |
+ if (disposition == blink::WebContentSecurityPolicyTypeReport) |
+ message << "[Report Only] "; |
+ |
+ if (directive_name == CSPDirective::FormAction) |
+ message << "Refused to send form data to '"; |
+ else if (directive_name == CSPDirective::FrameSrc) |
+ message << "Refused to frame '"; |
+ else if (directive_name == CSPDirective::FrameAncestors) |
+ message << "Refused to display '"; |
+ |
+ // TODO(arthursonzogni): Elide url. |
+ message << url |
+ << "' because it violates the following Content Security Policy " |
+ "directive: \"" |
+ // TODO(arthursonzogni): Pass the full CSPDirective. |
+ << directive.ToString() << "\""; |
+ |
+ if (directive.name != directive_name) |
+ message << " Note that '" << CSPDirective::NameToString(directive_name) |
+ << "' was not explicitly set, so '" |
+ << CSPDirective::NameToString(directive.name) |
+ << "' is used as a fallback."; |
+ |
+ message << "\n"; |
+ |
+ context->LogToConsole(message.str()); |
+ context->ReportViolation(CSPDirective::NameToString(directive.name), |
+ CSPDirective::NameToString(directive_name), |
+ message.str(), url, report_end_points, |
+ "", // TODO(arthursonzogni): Pass header. |
+ disposition); |
+} |
+ |
+} // namespace content |