Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(276)

Side by Side Diff: content/common/content_security_policy/csp_source.cc

Issue 2612793002: Implement ContentSecurityPolicy on the browser-side. (Closed)
Patch Set: Temporary re-add the parser + transmit parsed CSP over IPC. Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 <algorithm>
6 #include <sstream>
7
8 #include "base/strings/string_split.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "content/common/content_security_policy/csp_context.h"
12 #include "url/url_canon.h"
13 #include "url/url_util.h"
14
15 namespace content {
16
17 namespace {
18 bool IsHostCharacter(char c) {
19 return base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) || c == '-';
20 }
21
22 bool IsSchemeContinuationCharacter(char c) {
23 return base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) || c == '-' ||
24 c == '+' || c == '.';
25 }
26
27 bool DecodePath(const base::StringPiece& path, std::string* output) {
28 url::RawCanonOutputT<base::char16> unescaped;
29 url::DecodeURLEscapeSequences(path.data(), path.size(), &unescaped);
30 return base::UTF16ToUTF8(unescaped.data(), unescaped.length(), output);
31 }
32
33 int DefaultPortForScheme(const std::string& scheme) {
34 return url::DefaultPortForScheme(scheme.data(), scheme.size());
35 }
36
37 // ; <scheme> production from RFC 3986
38 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
39 bool ParseScheme(CSPSource* csp_source, const base::StringPiece& scheme) {
40 std::string scheme_lower = base::ToLowerASCII(scheme);
41
42 if (scheme.empty())
43 return false;
44
45 if (!base::IsAsciiAlpha(scheme[0]) ||
46 !std::all_of(scheme.begin() + 1, scheme.end(),
47 IsSchemeContinuationCharacter))
48 return false;
49
50 csp_source->scheme = scheme.as_string();
51 return true;
52 }
53
54 // host = [ "*." ] 1*host-char *( "." 1*host-char )
55 // / "*"
56 // host-char = ALPHA / DIGIT / "-"
57 //
58 bool ParseHost(CSPSource* csp_source, const base::StringPiece& host) {
59 if (host.empty())
60 return false;
61
62 // Parse and skip leading "*."
63 if (host[0] == '*') {
64 csp_source->is_host_wildcard = true;
65 if (host.size() == 1)
66 return true;
67
68 if (host[1] != '.')
69 return false;
70
71 csp_source->host = host.substr(2, std::string::npos).as_string();
72
73 // Check the '*.' case.
74 if (csp_source->host.empty())
75 return false;
76
77 } else {
78 csp_source->host = host.as_string();
79 }
80
81 for (const base::StringPiece& piece :
82 base::SplitStringPiece(csp_source->host, ".", base::KEEP_WHITESPACE,
83 base::SPLIT_WANT_ALL)) {
84 if (piece.empty() ||
85 !std::all_of(piece.begin(), piece.end(), IsHostCharacter))
86 return false;
87 }
88
89 return true;
90 }
91
92 // port = 1*DIGIT
93 // / "*"
94 bool ParsePort(CSPSource* csp_source, const base::StringPiece& port) {
95 if (port.empty())
96 return false;
97
98 if (port == "*") {
99 csp_source->is_port_wildcard = true;
100 return true;
101 }
102
103 std::string port_as_string = port.as_string();
104 if (!std::all_of(port_as_string.begin(), port_as_string.end(),
105 base::IsAsciiDigit<char>))
106 return false;
107
108 csp_source->port = std::stoi(port.as_string());
109 return true;
110 }
111
112 bool ParsePath(CSPSource* source, const base::StringPiece& path) {
113 DCHECK(base::StartsWith(path, "/", base::CompareCase::INSENSITIVE_ASCII));
114
115 size_t position = path.find_first_of("?#");
116 if (position != std::string::npos) {
117 // path/to/file.js?query=string || path/to/file.js#anchor
118 // ^ ^
119 // TODO(arthursonzogni): Report that the part after {%,?} will be ignored.
120 }
121
122 return DecodePath(path.substr(0, position), &(source->path));
123 }
124
125 bool ParseSource(CSPSource* csp_source, const std::string& source) {
126 size_t begin = 0;
127 size_t position = 0;
128 position = source.find_first_of(":/", begin);
129
130 if (position == std::string::npos) {
131 // host
132 // ^
133 return ParseHost(csp_source, source);
134 }
135
136 if (source[position] == '/') {
137 // host/path
138 // ^
139 return ParseHost(csp_source, source.substr(begin, position - begin)) &&
140 ParsePath(csp_source, source.substr(position, std::string::npos));
141 }
142
143 if (source[position] == ':') {
144 if (position == source.size() - 1) {
145 // scheme:
146 // ^
147 return ParseScheme(csp_source, source.substr(begin, position - begin));
148 }
149
150 if (source[position + 1] == '/') {
151 // scheme://(.*)
152 // ^ ^
153 if (!ParseScheme(csp_source, source.substr(begin, position - begin)))
154 return false;
155
156 // check the presence of "://"
157 if (position + 3 >= source.size() || source.substr(position, 3) != "://")
158 return false;
159
160 if (position + 3 == source.size()) {
161 // scheme://
162 // ^
163 return ParseScheme(csp_source, source.substr(begin, position - begin));
164 }
165
166 // Skip scheme://
167 begin = position + 3;
168 position = source.find_first_of(":/", begin);
169 if (position == std::string::npos) {
170 // host
171 return ParseHost(csp_source, source.substr(begin, std::string::npos));
172 }
173 }
174 }
175
176 // At this point, the scheme part (if any) has been Skipped.
177 // host/path || host:port[/path]
178 // ^ ^
179 if (!ParseHost(csp_source, source.substr(begin, position - begin)))
180 return false;
181
182 if (source[position] == '/') {
183 // host/path
184 // ^
185 return ParsePath(csp_source, source.substr(position, std::string::npos));
186 } else {
187 // host:port[/path]
188 // ^
189 DCHECK(source[position] == ':');
190
191 // Skip host:
192 begin = position + 1;
193 position = source.find_first_of("/", begin);
194
195 if (!ParsePort(csp_source, source.substr(begin, position - begin)))
196 return false;
197
198 if (position == std::string::npos)
199 return true;
200
201 // port/path
202 // ^
203 return ParsePath(csp_source, source.substr(position, std::string::npos));
204 }
205 }
206
207 } // namespace
208
209 CSPSource::CSPSource()
210 : scheme(),
211 host(),
212 is_host_wildcard(false),
213 port(url::PORT_UNSPECIFIED),
214 is_port_wildcard(false),
215 path() {}
216
217 CSPSource::CSPSource(const std::string& scheme,
218 const std::string& host,
219 bool is_host_wildcard,
220 int port,
221 int is_port_wildcard,
222 const std::string& path)
223 : scheme(scheme),
224 host(host),
225 is_host_wildcard(is_host_wildcard),
226 port(port),
227 is_port_wildcard(is_port_wildcard),
228 path(path) {
229 DCHECK(!has_port() || has_host()); // port => host
230 DCHECK(!has_path() || has_host()); // path => host
231 DCHECK(!is_port_wildcard || port == url::PORT_UNSPECIFIED);
232 }
233
234 CSPSource::CSPSource(const CSPSource& source) = default;
235 CSPSource::~CSPSource() = default;
236
237 // source = scheme ":"
238 // / ( [ scheme "://" ] host [ port ] [ path ] )
239 // / "'self'"
240 // static
241 base::Optional<CSPSource> CSPSource::Parse(const std::string& text) {
242 CSPSource csp_source;
243 if (ParseSource(&csp_source, text))
244 return csp_source;
245 else
246 return {};
247 }
248
249 bool CSPSource::Allow(CSPContext* context,
250 const GURL& url,
251 bool is_redirect) const {
252 if (IsSchemeOnly())
253 return AllowScheme(url, context);
254 else
255 return AllowScheme(url, context) && AllowHost(url) && AllowPort(url) &&
256 AllowPath(url, is_redirect);
257 }
258
259 std::string CSPSource::ToString() const {
260 // scheme
261 if (IsSchemeOnly())
262 return scheme;
263
264 std::stringstream text;
265 if (!scheme.empty())
266 text << scheme << "://";
267
268 // host
269 if (is_host_wildcard) {
270 if (host.empty())
271 text << "*";
272 else
273 text << "*." << host;
274 } else {
275 text << host;
276 }
277
278 // port
279 if (is_port_wildcard)
280 text << ":*";
281 if (port != url::PORT_UNSPECIFIED)
282 text << ":" << port;
283
284 // path
285 text << path;
286
287 return text.str();
288 }
289
290 bool CSPSource::IsSchemeOnly() const {
291 return !has_host();
292 }
293
294 bool CSPSource::has_port() const {
295 return port != url::PORT_UNSPECIFIED || is_port_wildcard;
296 }
297
298 bool CSPSource::has_host() const {
299 return !host.empty() || is_host_wildcard;
300 }
301
302 bool CSPSource::has_path() const {
303 return !path.empty();
304 }
305
306 bool CSPSource::AllowScheme(const GURL& url, CSPContext* context) const {
307 if (scheme.empty())
308 return context->ProtocolMatchesSelf(url);
309 if (scheme == url::kHttpScheme)
310 return url.SchemeIsHTTPOrHTTPS();
311 if (scheme == url::kWsScheme)
312 return url.SchemeIsWSOrWSS();
313 return url.SchemeIs(scheme);
314 }
315
316 bool CSPSource::AllowHost(const GURL& url) const {
317 if (is_host_wildcard) {
318 if (host.empty())
319 return true;
320 // TODO(arthursonzogni): Chrome used to, incorrectly, match *.x.y to x.y.
321 // The renderer version of this function count how many times it happens.
322 // See third_party/WebKit/Source/core/frame/csp/CSPSource.cpp
323 return base::EndsWith(url.host(), '.' + host,
324 base::CompareCase::INSENSITIVE_ASCII);
325 } else
326 return url.host() == host;
327 }
328
329 bool CSPSource::AllowPort(const GURL& url) const {
330 int url_port = url.EffectiveIntPort();
331
332 if (is_port_wildcard)
333 return true;
334
335 if (port == url::PORT_UNSPECIFIED)
336 return DefaultPortForScheme(url.scheme()) == url_port;
337
338 if (port == url_port)
339 return true;
340
341 if (port == 80 && url_port == 443)
342 return true;
343
344 return false;
345 }
346
347 bool CSPSource::AllowPath(const GURL& url, bool is_redirect) const {
348 if (is_redirect)
349 return true;
350
351 if (path.empty() || url.path().empty())
352 return true;
353
354 std::string url_path;
355 if (!DecodePath(url.path(), &url_path)) {
356 // TODO(arthursonzogni). Considering doing something else here.
357 return false;
358 }
359
360 // If the path represents a directory.
361 if (base::EndsWith(path, "/", base::CompareCase::SENSITIVE))
362 return base::StartsWith(url_path, path, base::CompareCase::SENSITIVE);
363
364 // The path represents a file.
365 return path == url_path;
366 }
367
368 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698