2011-04-07 Adam Barth <abarth@webkit.org>
[WebKit-https.git] / Source / WebCore / page / ContentSecurityPolicy.cpp
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
40 bool isDirectiveNameCharacter(UChar c)
41 {
42     return isASCIIAlphanumeric(c) || c == '-';
43 }
44
45 bool isDirectiveValueCharacter(UChar c)
46 {
47     return isASCIISpace(c) || (c >= 0x21 && c <= 0x7e); // Whitespace + VCHAR
48 }
49
50 bool isSourceCharacter(UChar c)
51 {
52     return !isASCIISpace(c);
53 }
54
55 bool isHostCharacter(UChar c)
56 {
57     return isASCIIAlphanumeric(c) || c == '-';
58 }
59
60 bool isOptionValueCharacter(UChar c)
61 {
62     return isASCIIAlphanumeric(c) || c == '-';
63 }
64
65 bool isSchemeContinuationCharacter(UChar c)
66 {
67     return isASCIIAlphanumeric(c) || c == '+' || c == '-' || c == '.';
68 }
69
70 } // namespace
71
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)>
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
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)>
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:
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
115     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:
125     bool schemeMatches(const KURL& url) const
126     {
127         return equalIgnoringCase(url.protocol(), m_scheme);
128     }
129
130     bool hostMatches(const KURL& url) const
131     {
132         if (m_hostHasWildcard)
133             notImplemented();
134
135         return equalIgnoringCase(url.host(), m_host);
136     }
137
138     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
147     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
178 CSPSourceList::CSPSourceList(SecurityOrigin* origin)
179     : m_origin(origin)
180 {
181 }
182
183 void CSPSourceList::parse(const String& value)
184 {
185     parse(value.characters(), value.characters() + value.length());
186 }
187
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 //
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 //
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 //
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 //
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 //
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
391 void CSPSourceList::addSourceSelf()
392 {
393     // FIXME: Inherit the scheme, host, and port from the current URL.
394     notImplemented();
395 }
396
397 class CSPDirective {
398 public:
399     CSPDirective(const String& value, SecurityOrigin* origin)
400         : m_sourceList(origin)
401     {
402         m_sourceList.parse(value);
403     }
404
405     bool allows(const KURL& url)
406     {
407         return m_sourceList.matches(url);
408     }
409
410 private:
411     CSPSourceList m_sourceList;
412 };
413
414 class CSPOptions {
415 public:
416     explicit CSPOptions(const String& value)
417         : m_disableXSSProtection(false)
418         , m_evalScript(false)
419     {
420         parse(value);
421     }
422
423     bool disableXSSProtection() const { return m_disableXSSProtection; }
424     bool evalScript() const { return m_evalScript; }
425
426 private:
427     void parse(const String&);
428
429     bool m_disableXSSProtection;
430     bool m_evalScript;
431 };
432
433 // options           = "options" *( 1*WSP option-value ) *WSP
434 // option-value      = 1*( ALPHA / DIGIT / "-" )
435 //
436 void CSPOptions::parse(const String& value)
437 {
438     DEFINE_STATIC_LOCAL(String, disableXSSProtection, ("disable-xss-protection"));
439     DEFINE_STATIC_LOCAL(String, evalScript, ("eval-script"));
440
441     const UChar* position = value.characters();
442     const UChar* end = position + value.length();
443
444     while (position < end) {
445         skipWhile<isASCIISpace>(position, end);
446
447         const UChar* optionsValueBegin = position;
448
449         if (!skipExactly<isOptionValueCharacter>(position, end))
450             return;
451
452         skipWhile<isOptionValueCharacter>(position, end);
453
454         String optionsValue(optionsValueBegin, position - optionsValueBegin);
455
456         if (equalIgnoringCase(optionsValue, disableXSSProtection))
457             m_disableXSSProtection = true;
458         else if (equalIgnoringCase(optionsValue, evalScript))
459             m_evalScript = true;
460     }
461 }
462
463 ContentSecurityPolicy::ContentSecurityPolicy(SecurityOrigin* origin)
464     : m_havePolicy(false)
465     , m_origin(origin)
466 {
467 }
468
469 ContentSecurityPolicy::~ContentSecurityPolicy()
470 {
471 }
472
473 void ContentSecurityPolicy::didReceiveHeader(const String& header)
474 {
475     if (m_havePolicy)
476         return; // The first policy wins.
477
478     parse(header);
479     m_havePolicy = true;
480 }
481
482 bool ContentSecurityPolicy::protectAgainstXSS() const
483 {
484     return m_scriptSrc && (!m_options || !m_options->disableXSSProtection());
485 }
486
487 bool ContentSecurityPolicy::allowJavaScriptURLs() const
488 {
489     return !protectAgainstXSS();
490 }
491
492 bool ContentSecurityPolicy::allowInlineEventHandlers() const
493 {
494     return !protectAgainstXSS();
495 }
496
497 bool ContentSecurityPolicy::allowInlineScript() const
498 {
499     return !protectAgainstXSS();
500 }
501
502 bool ContentSecurityPolicy::allowScriptFromSource(const KURL& url) const
503 {
504     return !m_scriptSrc || m_scriptSrc->allows(url);
505 }
506
507 bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url) const
508 {
509     return !m_objectSrc || m_objectSrc->allows(url);
510 }
511
512 bool ContentSecurityPolicy::allowImageFromSource(const KURL& url) const
513 {
514     return !m_imgSrc || m_imgSrc->allows(url);
515 }
516
517 bool ContentSecurityPolicy::allowStyleFromSource(const KURL& url) const
518 {
519     return !m_styleSrc || m_styleSrc->allows(url);
520 }
521
522 bool ContentSecurityPolicy::allowFontFromSource(const KURL& url) const
523 {
524     return !m_fontSrc || m_fontSrc->allows(url);
525 }
526
527 // policy            = directive-list
528 // directive-list    = [ directive *( ";" [ directive ] ) ]
529 //
530 void ContentSecurityPolicy::parse(const String& policy)
531 {
532     ASSERT(!m_havePolicy);
533
534     if (policy.isEmpty())
535         return;
536
537     const UChar* position = policy.characters();
538     const UChar* end = position + policy.length();
539
540     while (position < end) {
541         const UChar* directiveBegin = position;
542         skipUtil(position, end, ';');
543
544         String name, value;
545         if (parseDirective(directiveBegin, position, name, value)) {
546             ASSERT(!name.isEmpty());
547             addDirective(name, value);
548         }
549
550         ASSERT(position == end || *position == ';');
551         skipExactly(position, end, ';');
552     }
553 }
554
555 // directive         = *WSP [ directive-name [ WSP directive-value ] ]
556 // directive-name    = 1*( ALPHA / DIGIT / "-" )
557 // directive-value   = *( WSP / <VCHAR except ";"> )
558 //
559 bool ContentSecurityPolicy::parseDirective(const UChar* begin, const UChar* end, String& name, String& value)
560 {
561     ASSERT(name.isEmpty());
562     ASSERT(value.isEmpty());
563
564     const UChar* position = begin;
565     skipWhile<isASCIISpace>(position, end);
566
567     const UChar* nameBegin = position;
568     skipWhile<isDirectiveNameCharacter>(position, end);
569
570     // The directive-name must be non-empty.
571     if (nameBegin == position)
572         return false;
573
574     name = String(nameBegin, position - nameBegin);
575
576     if (position == end)
577         return true;
578
579     if (!skipExactly<isASCIISpace>(position, end))
580         return false;
581
582     skipWhile<isASCIISpace>(position, end);
583
584     const UChar* valueBegin = position;
585     skipWhile<isDirectiveValueCharacter>(position, end);
586
587     if (position != end)
588         return false;
589
590     // The directive-value may be empty.
591     if (valueBegin == position)
592         return true;
593
594     value = String(valueBegin, position - valueBegin);
595     return true;
596 }
597
598 void ContentSecurityPolicy::addDirective(const String& name, const String& value)
599 {
600     DEFINE_STATIC_LOCAL(String, scriptSrc, ("script-src"));
601     DEFINE_STATIC_LOCAL(String, objectSrc, ("object-src"));
602     DEFINE_STATIC_LOCAL(String, imgSrc, ("img-src"));
603     DEFINE_STATIC_LOCAL(String, styleSrc, ("style-src"));
604     DEFINE_STATIC_LOCAL(String, fontSrc, ("font-src"));
605     DEFINE_STATIC_LOCAL(String, options, ("options"));
606
607     ASSERT(!name.isEmpty());
608
609     if (!m_scriptSrc && equalIgnoringCase(name, scriptSrc))
610         m_scriptSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
611     else if (!m_objectSrc && equalIgnoringCase(name, objectSrc))
612         m_objectSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
613     else if (!m_imgSrc && equalIgnoringCase(name, imgSrc))
614         m_imgSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
615     else if (!m_styleSrc && equalIgnoringCase(name, styleSrc))
616         m_styleSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
617     else if (!m_fontSrc && equalIgnoringCase(name, fontSrc))
618         m_fontSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
619     else if (!m_options && equalIgnoringCase(name, options))
620         m_options = adoptPtr(new CSPOptions(value));
621 }
622
623 }