1 /*
2 * Copyright (C) 2011 Google, Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "config.h"
27 #include "ContentSecurityPolicy.h"
28
29 #include "Document.h"
30 #include "NotImplemented.h"
31 #include "SecurityOrigin.h"
32
33 namespace WebCore {
34
35 // Normally WebKit uses "static" for internal linkage, but using "static" for
36 // these functions causes a compile error because these functions are used as
37 // template parameters.
38 namespace {
39
isDirectiveNameCharacter(UChar c)40 bool isDirectiveNameCharacter(UChar c)
41 {
42 return isASCIIAlphanumeric(c) || c == '-';
43 }
44
isDirectiveValueCharacter(UChar c)45 bool isDirectiveValueCharacter(UChar c)
46 {
47 return isASCIISpace(c) || (c >= 0x21 && c <= 0x7e); // Whitespace + VCHAR
48 }
49
isSourceCharacter(UChar c)50 bool isSourceCharacter(UChar c)
51 {
52 return !isASCIISpace(c);
53 }
54
isHostCharacter(UChar c)55 bool isHostCharacter(UChar c)
56 {
57 return isASCIIAlphanumeric(c) || c == '-';
58 }
59
isOptionValueCharacter(UChar c)60 bool isOptionValueCharacter(UChar c)
61 {
62 return isASCIIAlphanumeric(c) || c == '-';
63 }
64
isSchemeContinuationCharacter(UChar c)65 bool isSchemeContinuationCharacter(UChar c)
66 {
67 return isASCIIAlphanumeric(c) || c == '+' || c == '-' || c == '.';
68 }
69
70 } // namespace
71
skipExactly(const UChar * & position,const UChar * end,UChar delimiter)72 static bool skipExactly(const UChar*& position, const UChar* end, UChar delimiter)
73 {
74 if (position < end && *position == delimiter) {
75 ++position;
76 return true;
77 }
78 return false;
79 }
80
81 template<bool characterPredicate(UChar)>
skipExactly(const UChar * & position,const UChar * end)82 static bool skipExactly(const UChar*& position, const UChar* end)
83 {
84 if (position < end && characterPredicate(*position)) {
85 ++position;
86 return true;
87 }
88 return false;
89 }
90
skipUtil(const UChar * & position,const UChar * end,UChar delimiter)91 static void skipUtil(const UChar*& position, const UChar* end, UChar delimiter)
92 {
93 while (position < end && *position != delimiter)
94 ++position;
95 }
96
97 template<bool characterPredicate(UChar)>
skipWhile(const UChar * & position,const UChar * end)98 static void skipWhile(const UChar*& position, const UChar* end)
99 {
100 while (position < end && characterPredicate(*position))
101 ++position;
102 }
103
104 class CSPSource {
105 public:
CSPSource(const String & scheme,const String & host,int port,bool hostHasWildcard,bool portHasWildcard)106 CSPSource(const String& scheme, const String& host, int port, bool hostHasWildcard, bool portHasWildcard)
107 : m_scheme(scheme)
108 , m_host(host)
109 , m_port(port)
110 , m_hostHasWildcard(hostHasWildcard)
111 , m_portHasWildcard(portHasWildcard)
112 {
113 }
114
matches(const KURL & url) const115 bool matches(const KURL& url) const
116 {
117 if (!schemeMatches(url))
118 return false;
119 if (isSchemeOnly())
120 return true;
121 return hostMatches(url) && portMatches(url);
122 }
123
124 private:
schemeMatches(const KURL & url) const125 bool schemeMatches(const KURL& url) const
126 {
127 return equalIgnoringCase(url.protocol(), m_scheme);
128 }
129
hostMatches(const KURL & url) const130 bool hostMatches(const KURL& url) const
131 {
132 if (m_hostHasWildcard)
133 notImplemented();
134
135 return equalIgnoringCase(url.host(), m_host);
136 }
137
portMatches(const KURL & url) const138 bool portMatches(const KURL& url) const
139 {
140 if (m_portHasWildcard)
141 return true;
142
143 // FIXME: Handle explicit default ports correctly.
144 return url.port() == m_port;
145 }
146
isSchemeOnly() const147 bool isSchemeOnly() const { return m_host.isEmpty(); }
148
149 String m_scheme;
150 String m_host;
151 int m_port;
152
153 bool m_hostHasWildcard;
154 bool m_portHasWildcard;
155 };
156
157 class CSPSourceList {
158 public:
159 explicit CSPSourceList(SecurityOrigin*);
160
161 void parse(const String&);
162 bool matches(const KURL&);
163
164 private:
165 void parse(const UChar* begin, const UChar* end);
166
167 bool parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, int& port, bool& hostHasWildcard, bool& portHasWildcard);
168 bool parseScheme(const UChar* begin, const UChar* end, String& scheme);
169 bool parseHost(const UChar* begin, const UChar* end, String& host, bool& hostHasWildcard);
170 bool parsePort(const UChar* begin, const UChar* end, int& port, bool& portHasWildcard);
171
172 void addSourceSelf();
173
174 SecurityOrigin* m_origin;
175 Vector<CSPSource> m_list;
176 };
177
CSPSourceList(SecurityOrigin * origin)178 CSPSourceList::CSPSourceList(SecurityOrigin* origin)
179 : m_origin(origin)
180 {
181 }
182
parse(const String & value)183 void CSPSourceList::parse(const String& value)
184 {
185 parse(value.characters(), value.characters() + value.length());
186 }
187
matches(const KURL & url)188 bool CSPSourceList::matches(const KURL& url)
189 {
190 for (size_t i = 0; i < m_list.size(); ++i) {
191 if (m_list[i].matches(url))
192 return true;
193 }
194 return false;
195 }
196
197 // source-list = *WSP [ source *( 1*WSP source ) *WSP ]
198 // / *WSP "'none'" *WSP
199 //
parse(const UChar * begin,const UChar * end)200 void CSPSourceList::parse(const UChar* begin, const UChar* end)
201 {
202 const UChar* position = begin;
203
204 bool isFirstSourceInList = true;
205 while (position < end) {
206 skipWhile<isASCIISpace>(position, end);
207 const UChar* beginSource = position;
208 skipWhile<isSourceCharacter>(position, end);
209
210 if (isFirstSourceInList && equalIgnoringCase("'none'", beginSource, position - beginSource))
211 return; // We represent 'none' as an empty m_list.
212 isFirstSourceInList = false;
213
214 String scheme, host;
215 int port = 0;
216 bool hostHasWildcard = false;
217 bool portHasWildcard = false;
218
219 if (parseSource(beginSource, position, scheme, host, port, hostHasWildcard, portHasWildcard)) {
220 if (scheme.isEmpty())
221 scheme = m_origin->protocol();
222 m_list.append(CSPSource(scheme, host, port, hostHasWildcard, portHasWildcard));
223 }
224
225 ASSERT(position == end || isASCIISpace(*position));
226 }
227 }
228
229 // source = scheme ":"
230 // / ( [ scheme "://" ] host [ port ] )
231 // / "'self'"
232 //
parseSource(const UChar * begin,const UChar * end,String & scheme,String & host,int & port,bool & hostHasWildcard,bool & portHasWildcard)233 bool CSPSourceList::parseSource(const UChar* begin, const UChar* end,
234 String& scheme, String& host, int& port,
235 bool& hostHasWildcard, bool& portHasWildcard)
236 {
237 if (begin == end)
238 return false;
239
240 if (equalIgnoringCase("'self'", begin, end - begin)) {
241 addSourceSelf();
242 return false;
243 }
244
245 const UChar* position = begin;
246
247 const UChar* beginHost = begin;
248 skipUtil(position, end, ':');
249
250 if (position == end) {
251 // This must be a host-only source.
252 if (!parseHost(beginHost, position, host, hostHasWildcard))
253 return false;
254 return true;
255 }
256
257 if (end - position == 1) {
258 ASSERT(*position == ':');
259 // This must be a scheme-only source.
260 if (!parseScheme(begin, position, scheme))
261 return false;
262 return true;
263 }
264
265 ASSERT(end - position >= 2);
266 if (position[1] == '/') {
267 if (!parseScheme(begin, position, scheme)
268 || !skipExactly(position, end, ':')
269 || !skipExactly(position, end, '/')
270 || !skipExactly(position, end, '/'))
271 return false;
272 beginHost = position;
273 skipUtil(position, end, ':');
274 }
275
276 if (position == beginHost)
277 return false;
278
279 if (!parseHost(beginHost, position, host, hostHasWildcard))
280 return false;
281
282 if (position == end) {
283 port = 0;
284 return true;
285 }
286
287 if (!skipExactly(position, end, ':'))
288 ASSERT_NOT_REACHED();
289
290 if (!parsePort(position, end, port, portHasWildcard))
291 return false;
292
293 return true;
294 }
295
296 // ; <scheme> production from RFC 3986
297 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
298 //
parseScheme(const UChar * begin,const UChar * end,String & scheme)299 bool CSPSourceList::parseScheme(const UChar* begin, const UChar* end, String& scheme)
300 {
301 ASSERT(begin <= end);
302 ASSERT(scheme.isEmpty());
303
304 if (begin == end)
305 return false;
306
307 const UChar* position = begin;
308
309 if (!skipExactly<isASCIIAlpha>(position, end))
310 return false;
311
312 skipWhile<isSchemeContinuationCharacter>(position, end);
313
314 if (position != end)
315 return false;
316
317 scheme = String(begin, end - begin);
318 return true;
319 }
320
321 // host = [ "*." ] 1*host-char *( "." 1*host-char )
322 // / "*"
323 // host-char = ALPHA / DIGIT / "-"
324 //
parseHost(const UChar * begin,const UChar * end,String & host,bool & hostHasWildcard)325 bool CSPSourceList::parseHost(const UChar* begin, const UChar* end, String& host, bool& hostHasWildcard)
326 {
327 ASSERT(begin <= end);
328 ASSERT(host.isEmpty());
329 ASSERT(!hostHasWildcard);
330
331 if (begin == end)
332 return false;
333
334 const UChar* position = begin;
335
336 if (skipExactly(position, end, '*')) {
337 hostHasWildcard = true;
338
339 if (position == end)
340 return true;
341
342 if (!skipExactly(position, end, '.'))
343 return false;
344 }
345
346 const UChar* hostBegin = position;
347
348 while (position < end) {
349 if (!skipExactly<isHostCharacter>(position, end))
350 return false;
351
352 skipWhile<isHostCharacter>(position, end);
353
354 if (position < end && !skipExactly(position, end, '.'))
355 return false;
356 }
357
358 ASSERT(position == end);
359 host = String(hostBegin, end - hostBegin);
360 return true;
361 }
362
363 // port = ":" ( 1*DIGIT / "*" )
364 //
parsePort(const UChar * begin,const UChar * end,int & port,bool & portHasWildcard)365 bool CSPSourceList::parsePort(const UChar* begin, const UChar* end, int& port, bool& portHasWildcard)
366 {
367 ASSERT(begin <= end);
368 ASSERT(!port);
369 ASSERT(!portHasWildcard);
370
371 if (begin == end)
372 return false;
373
374 if (end - begin == 1 && *begin == '*') {
375 port = 0;
376 portHasWildcard = true;
377 return true;
378 }
379
380 const UChar* position = begin;
381 skipWhile<isASCIIDigit>(position, end);
382
383 if (position != end)
384 return false;
385
386 bool ok;
387 port = charactersToIntStrict(begin, end - begin, &ok);
388 return ok;
389 }
390
addSourceSelf()391 void CSPSourceList::addSourceSelf()
392 {
393 m_list.append(CSPSource(m_origin->protocol(), m_origin->host(), m_origin->port(), false, false));
394 }
395
396 class CSPDirective {
397 public:
CSPDirective(const String & value,SecurityOrigin * origin)398 CSPDirective(const String& value, SecurityOrigin* origin)
399 : m_sourceList(origin)
400 {
401 m_sourceList.parse(value);
402 }
403
allows(const KURL & url)404 bool allows(const KURL& url)
405 {
406 return m_sourceList.matches(url);
407 }
408
409 private:
410 CSPSourceList m_sourceList;
411 };
412
413 class CSPOptions {
414 public:
CSPOptions(const String & value)415 explicit CSPOptions(const String& value)
416 : m_disableXSSProtection(false)
417 , m_evalScript(false)
418 {
419 parse(value);
420 }
421
disableXSSProtection() const422 bool disableXSSProtection() const { return m_disableXSSProtection; }
evalScript() const423 bool evalScript() const { return m_evalScript; }
424
425 private:
426 void parse(const String&);
427
428 bool m_disableXSSProtection;
429 bool m_evalScript;
430 };
431
432 // options = "options" *( 1*WSP option-value ) *WSP
433 // option-value = 1*( ALPHA / DIGIT / "-" )
434 //
parse(const String & value)435 void CSPOptions::parse(const String& value)
436 {
437 DEFINE_STATIC_LOCAL(String, disableXSSProtection, ("disable-xss-protection"));
438 DEFINE_STATIC_LOCAL(String, evalScript, ("eval-script"));
439
440 const UChar* position = value.characters();
441 const UChar* end = position + value.length();
442
443 while (position < end) {
444 skipWhile<isASCIISpace>(position, end);
445
446 const UChar* optionsValueBegin = position;
447
448 if (!skipExactly<isOptionValueCharacter>(position, end))
449 return;
450
451 skipWhile<isOptionValueCharacter>(position, end);
452
453 String optionsValue(optionsValueBegin, position - optionsValueBegin);
454
455 if (equalIgnoringCase(optionsValue, disableXSSProtection))
456 m_disableXSSProtection = true;
457 else if (equalIgnoringCase(optionsValue, evalScript))
458 m_evalScript = true;
459 }
460 }
461
ContentSecurityPolicy(SecurityOrigin * origin)462 ContentSecurityPolicy::ContentSecurityPolicy(SecurityOrigin* origin)
463 : m_havePolicy(false)
464 , m_origin(origin)
465 {
466 }
467
~ContentSecurityPolicy()468 ContentSecurityPolicy::~ContentSecurityPolicy()
469 {
470 }
471
didReceiveHeader(const String & header)472 void ContentSecurityPolicy::didReceiveHeader(const String& header)
473 {
474 if (m_havePolicy)
475 return; // The first policy wins.
476
477 parse(header);
478 m_havePolicy = true;
479 }
480
protectAgainstXSS() const481 bool ContentSecurityPolicy::protectAgainstXSS() const
482 {
483 return m_scriptSrc && (!m_options || !m_options->disableXSSProtection());
484 }
485
allowJavaScriptURLs() const486 bool ContentSecurityPolicy::allowJavaScriptURLs() const
487 {
488 return !protectAgainstXSS();
489 }
490
allowInlineEventHandlers() const491 bool ContentSecurityPolicy::allowInlineEventHandlers() const
492 {
493 return !protectAgainstXSS();
494 }
495
allowInlineScript() const496 bool ContentSecurityPolicy::allowInlineScript() const
497 {
498 return !protectAgainstXSS();
499 }
500
allowEval() const501 bool ContentSecurityPolicy::allowEval() const
502 {
503 return !m_scriptSrc || (m_options && m_options->evalScript());
504 }
505
allowScriptFromSource(const KURL & url) const506 bool ContentSecurityPolicy::allowScriptFromSource(const KURL& url) const
507 {
508 return !m_scriptSrc || m_scriptSrc->allows(url);
509 }
510
allowObjectFromSource(const KURL & url) const511 bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url) const
512 {
513 return !m_objectSrc || m_objectSrc->allows(url);
514 }
515
allowImageFromSource(const KURL & url) const516 bool ContentSecurityPolicy::allowImageFromSource(const KURL& url) const
517 {
518 return !m_imgSrc || m_imgSrc->allows(url);
519 }
520
allowStyleFromSource(const KURL & url) const521 bool ContentSecurityPolicy::allowStyleFromSource(const KURL& url) const
522 {
523 return !m_styleSrc || m_styleSrc->allows(url);
524 }
525
allowFontFromSource(const KURL & url) const526 bool ContentSecurityPolicy::allowFontFromSource(const KURL& url) const
527 {
528 return !m_fontSrc || m_fontSrc->allows(url);
529 }
530
allowMediaFromSource(const KURL & url) const531 bool ContentSecurityPolicy::allowMediaFromSource(const KURL& url) const
532 {
533 return !m_mediaSrc || m_mediaSrc->allows(url);
534 }
535
536 // policy = directive-list
537 // directive-list = [ directive *( ";" [ directive ] ) ]
538 //
parse(const String & policy)539 void ContentSecurityPolicy::parse(const String& policy)
540 {
541 ASSERT(!m_havePolicy);
542
543 if (policy.isEmpty())
544 return;
545
546 const UChar* position = policy.characters();
547 const UChar* end = position + policy.length();
548
549 while (position < end) {
550 const UChar* directiveBegin = position;
551 skipUtil(position, end, ';');
552
553 String name, value;
554 if (parseDirective(directiveBegin, position, name, value)) {
555 ASSERT(!name.isEmpty());
556 addDirective(name, value);
557 }
558
559 ASSERT(position == end || *position == ';');
560 skipExactly(position, end, ';');
561 }
562 }
563
564 // directive = *WSP [ directive-name [ WSP directive-value ] ]
565 // directive-name = 1*( ALPHA / DIGIT / "-" )
566 // directive-value = *( WSP / <VCHAR except ";"> )
567 //
parseDirective(const UChar * begin,const UChar * end,String & name,String & value)568 bool ContentSecurityPolicy::parseDirective(const UChar* begin, const UChar* end, String& name, String& value)
569 {
570 ASSERT(name.isEmpty());
571 ASSERT(value.isEmpty());
572
573 const UChar* position = begin;
574 skipWhile<isASCIISpace>(position, end);
575
576 const UChar* nameBegin = position;
577 skipWhile<isDirectiveNameCharacter>(position, end);
578
579 // The directive-name must be non-empty.
580 if (nameBegin == position)
581 return false;
582
583 name = String(nameBegin, position - nameBegin);
584
585 if (position == end)
586 return true;
587
588 if (!skipExactly<isASCIISpace>(position, end))
589 return false;
590
591 skipWhile<isASCIISpace>(position, end);
592
593 const UChar* valueBegin = position;
594 skipWhile<isDirectiveValueCharacter>(position, end);
595
596 if (position != end)
597 return false;
598
599 // The directive-value may be empty.
600 if (valueBegin == position)
601 return true;
602
603 value = String(valueBegin, position - valueBegin);
604 return true;
605 }
606
addDirective(const String & name,const String & value)607 void ContentSecurityPolicy::addDirective(const String& name, const String& value)
608 {
609 DEFINE_STATIC_LOCAL(String, scriptSrc, ("script-src"));
610 DEFINE_STATIC_LOCAL(String, objectSrc, ("object-src"));
611 DEFINE_STATIC_LOCAL(String, imgSrc, ("img-src"));
612 DEFINE_STATIC_LOCAL(String, styleSrc, ("style-src"));
613 DEFINE_STATIC_LOCAL(String, fontSrc, ("font-src"));
614 DEFINE_STATIC_LOCAL(String, mediaSrc, ("media-src"));
615 DEFINE_STATIC_LOCAL(String, options, ("options"));
616
617 ASSERT(!name.isEmpty());
618
619 if (!m_scriptSrc && equalIgnoringCase(name, scriptSrc))
620 m_scriptSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
621 else if (!m_objectSrc && equalIgnoringCase(name, objectSrc))
622 m_objectSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
623 else if (!m_imgSrc && equalIgnoringCase(name, imgSrc))
624 m_imgSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
625 else if (!m_styleSrc && equalIgnoringCase(name, styleSrc))
626 m_styleSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
627 else if (!m_fontSrc && equalIgnoringCase(name, fontSrc))
628 m_fontSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
629 else if (!m_mediaSrc && equalIgnoringCase(name, mediaSrc))
630 m_mediaSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
631 else if (!m_options && equalIgnoringCase(name, options))
632 m_options = adoptPtr(new CSPOptions(value));
633 }
634
635 }
636