• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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