| 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 | 
|  |