OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include <sstream> |
| 6 |
| 7 #include "base/strings/string_split.h" |
| 8 #include "base/strings/string_util.h" |
| 9 #include "content/common/content_security_policy/csp_context.h" |
| 10 |
| 11 namespace content { |
| 12 |
| 13 namespace { |
| 14 |
| 15 static CSPDirective::Name CSPFallback(CSPDirective::Name directive) { |
| 16 if (directive == CSPDirective::FrameSrc) |
| 17 return CSPDirective::ChildSrc; |
| 18 if (directive == CSPDirective::ChildSrc) |
| 19 return CSPDirective::DefaultSrc; |
| 20 return CSPDirective::Unknown; |
| 21 } |
| 22 |
| 23 void ReportUnsupportedDirective(CSPContext* context, |
| 24 const base::StringPiece& name) { |
| 25 std::string message = "Unrecognized Content-Security-Policy directive '" + |
| 26 name.as_string() + "'.\n"; |
| 27 context->LogToConsole(message); |
| 28 } |
| 29 |
| 30 void ReportDuplicateDirective(CSPContext* context, |
| 31 const base::StringPiece& directive) { |
| 32 std::string message = |
| 33 "Ignoring duplicate Content-Security-Policy directive '" + |
| 34 directive.as_string() + "'.\n"; |
| 35 context->LogToConsole(message); |
| 36 } |
| 37 |
| 38 void ReportInvalidDirectiveInMeta(CSPContext* context, |
| 39 const base::StringPiece& directive) { |
| 40 std::string message = |
| 41 "Content Security Policies delivered via a <meta> element may not " |
| 42 "contain the " + |
| 43 directive.as_string() + " directive."; |
| 44 context->LogToConsole(message); |
| 45 } |
| 46 |
| 47 void AddReportURI(CSPPolicy* policy, |
| 48 CSPContext* context, |
| 49 const base::StringPiece& directive_value) { |
| 50 if (!policy->report_end_points.empty()) |
| 51 ReportDuplicateDirective(context, "report-uri"); |
| 52 // Remove report-uri in meta policies, per |
| 53 // https://www.w3.org/TR/CSP2/#delivery-html-meta-element. |
| 54 if (policy->source == blink::WebContentSecurityPolicySourceMeta) { |
| 55 ReportInvalidDirectiveInMeta(context, "report-uri"); |
| 56 return; |
| 57 } |
| 58 |
| 59 for (const base::StringPiece& end_point : |
| 60 base::SplitStringPiece(directive_value, " ", base::TRIM_WHITESPACE, |
| 61 base::SPLIT_WANT_NONEMPTY)) { |
| 62 policy->report_end_points.push_back(end_point.as_string()); |
| 63 } |
| 64 } |
| 65 |
| 66 void AddDirective(CSPPolicy* policy, |
| 67 CSPContext* context, |
| 68 const base::StringPiece& directive_name, |
| 69 const base::StringPiece& directive_value) { |
| 70 CSPDirective::Name name = |
| 71 CSPDirective::StringToName(directive_name.as_string()); |
| 72 |
| 73 // Unknown directive |
| 74 if (name == CSPDirective::Unknown) { |
| 75 ReportUnsupportedDirective(context, directive_name); |
| 76 } |
| 77 |
| 78 // Report-uri |
| 79 if (name == CSPDirective::ReportURI) { |
| 80 AddReportURI(policy, context, directive_value); |
| 81 return; |
| 82 } |
| 83 |
| 84 // Remove frame-ancestors directives in meta policies, per |
| 85 // https://www.w3.org/TR/CSP2/#delivery-html-meta-element. |
| 86 if (name == CSPDirective::FrameAncestors && |
| 87 policy->source == blink::WebContentSecurityPolicySourceMeta) { |
| 88 ReportInvalidDirectiveInMeta(context, directive_name); |
| 89 return; |
| 90 } |
| 91 |
| 92 for (const auto& directive : policy->directives) { |
| 93 if (directive.name == name) |
| 94 ReportDuplicateDirective(context, directive_name); |
| 95 } |
| 96 |
| 97 policy->directives.emplace_back( |
| 98 name, CSPSourceList::Parse(context, directive_name, directive_value)); |
| 99 } |
| 100 |
| 101 } // namespace |
| 102 |
| 103 CSPPolicy::CSPPolicy() |
| 104 : disposition(blink::WebContentSecurityPolicyTypeEnforce), |
| 105 source(blink::WebContentSecurityPolicySourceHTTP), |
| 106 directives(), |
| 107 report_end_points() {} |
| 108 |
| 109 CSPPolicy::CSPPolicy(blink::WebContentSecurityPolicyType disposition, |
| 110 blink::WebContentSecurityPolicySource source, |
| 111 const std::vector<CSPDirective>& directives, |
| 112 const std::vector<std::string>& report_end_points) |
| 113 : disposition(disposition), |
| 114 source(source), |
| 115 directives(directives), |
| 116 report_end_points(report_end_points) {} |
| 117 |
| 118 CSPPolicy::CSPPolicy(const CSPPolicy&) = default; |
| 119 CSPPolicy::~CSPPolicy() = default; |
| 120 |
| 121 // static |
| 122 CSPPolicy CSPPolicy::Parse(CSPContext* context, |
| 123 const ContentSecurityPolicyHeader& header) { |
| 124 CSPPolicy policy; |
| 125 policy.disposition = header.type; |
| 126 policy.source = header.source; |
| 127 |
| 128 // Split by ";" |
| 129 for (const base::StringPiece& serialized_directive : |
| 130 base::SplitStringPiece(header.header_value, ";", base::TRIM_WHITESPACE, |
| 131 base::SPLIT_WANT_NONEMPTY)) { |
| 132 base::StringPiece directive_name; |
| 133 base::StringPiece directive_value; |
| 134 |
| 135 // Split serialized-directive into { directive-name , directive-value }. |
| 136 size_t first_space_pos = |
| 137 serialized_directive.find_first_of(base::kWhitespaceASCII, 0); |
| 138 directive_name = serialized_directive.substr(0, first_space_pos); |
| 139 if (first_space_pos != std::string::npos) { |
| 140 size_t first_non_space_pos = serialized_directive.find_first_not_of( |
| 141 base::kWhitespaceASCII, first_space_pos); |
| 142 directive_value = |
| 143 serialized_directive.substr(first_non_space_pos, std::string::npos); |
| 144 } |
| 145 |
| 146 AddDirective(&policy, context, directive_name, directive_value); |
| 147 } |
| 148 return policy; |
| 149 } |
| 150 |
| 151 bool CSPPolicy::Allow(CSPContext* context, |
| 152 CSPDirective::Name directive_name, |
| 153 const GURL& url, |
| 154 bool is_redirect) const { |
| 155 CSPDirective::Name current_directive_name = directive_name; |
| 156 do { |
| 157 // Search for the matching directive. |
| 158 for (const CSPDirective& directive : directives) { |
| 159 if (directive.name == current_directive_name) { |
| 160 return AllowDirective(context, directive_name, directive, url, |
| 161 is_redirect); |
| 162 } |
| 163 } |
| 164 |
| 165 current_directive_name = CSPFallback(current_directive_name); |
| 166 } while (current_directive_name != CSPDirective::Unknown); |
| 167 return false; |
| 168 } |
| 169 |
| 170 std::string CSPPolicy::ToString() const { |
| 171 std::stringstream text; |
| 172 bool is_first_policy = true; |
| 173 for (const CSPDirective& directive : directives) { |
| 174 if (!is_first_policy) |
| 175 text << "; "; |
| 176 text << directive.ToString(); |
| 177 is_first_policy = false; |
| 178 } |
| 179 return text.str(); |
| 180 } |
| 181 |
| 182 bool CSPPolicy::AllowDirective(CSPContext* context, |
| 183 CSPDirective::Name directive_name, |
| 184 const CSPDirective& directive, |
| 185 const GURL& url, |
| 186 bool is_redirect) const { |
| 187 if (directive.source_list.Allow(context, url, is_redirect)) |
| 188 return true; |
| 189 |
| 190 ReportViolation(context, directive_name, directive, url); |
| 191 |
| 192 return disposition == blink::WebContentSecurityPolicyTypeReport; |
| 193 } |
| 194 |
| 195 void CSPPolicy::ReportViolation(CSPContext* context, |
| 196 const CSPDirective::Name directive_name, |
| 197 const CSPDirective& directive, |
| 198 const GURL& url) const { |
| 199 // We should never have a violation against `child-src` or `default-src` |
| 200 // directly; the effective directive should always be one of the explicit |
| 201 // fetch directives. |
| 202 DCHECK_NE(directive_name, CSPDirective::DefaultSrc); |
| 203 DCHECK_NE(directive_name, CSPDirective::ChildSrc); |
| 204 |
| 205 std::stringstream message; |
| 206 |
| 207 if (disposition == blink::WebContentSecurityPolicyTypeReport) |
| 208 message << "[Report Only] "; |
| 209 |
| 210 if (directive_name == CSPDirective::FormAction) |
| 211 message << "Refused to send form data to '"; |
| 212 else if (directive_name == CSPDirective::FrameSrc) |
| 213 message << "Refused to frame '"; |
| 214 else if (directive_name == CSPDirective::FrameAncestors) |
| 215 message << "Refused to display '"; |
| 216 |
| 217 // TODO(arthursonzogni): Elide url. |
| 218 message << url |
| 219 << "' because it violates the following Content Security Policy " |
| 220 "directive: \"" |
| 221 // TODO(arthursonzogni): Pass the full CSPDirective. |
| 222 << directive.ToString() << "\""; |
| 223 |
| 224 if (directive.name != directive_name) |
| 225 message << " Note that '" << CSPDirective::NameToString(directive_name) |
| 226 << "' was not explicitly set, so '" |
| 227 << CSPDirective::NameToString(directive.name) |
| 228 << "' is used as a fallback."; |
| 229 |
| 230 message << "\n"; |
| 231 |
| 232 context->LogToConsole(message.str()); |
| 233 context->ReportViolation(CSPDirective::NameToString(directive.name), |
| 234 CSPDirective::NameToString(directive_name), |
| 235 message.str(), url, report_end_points, |
| 236 "", // TODO(arthursonzogni): Pass header. |
| 237 disposition); |
| 238 } |
| 239 |
| 240 } // namespace content |
OLD | NEW |