2 * Copyright (C) 2011 Google, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "ContentSecurityPolicy.h"
30 #include "NotImplemented.h"
31 #include "SecurityOrigin.h"
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.
40 bool isDirectiveNameCharacter(UChar c)
42 return isASCIIAlphanumeric(c) || c == '-';
45 bool isDirectiveValueCharacter(UChar c)
47 return isASCIISpace(c) || (c >= 0x21 && c <= 0x7e); // Whitespace + VCHAR
50 bool isSourceCharacter(UChar c)
52 return !isASCIISpace(c);
55 bool isHostCharacter(UChar c)
57 return isASCIIAlphanumeric(c) || c == '-';
60 bool isOptionValueCharacter(UChar c)
62 return isASCIIAlphanumeric(c) || c == '-';
65 bool isSchemeContinuationCharacter(UChar c)
67 return isASCIIAlphanumeric(c) || c == '+' || c == '-' || c == '.';
72 static bool skipExactly(const UChar*& position, const UChar* end, UChar delimiter)
74 if (position < end && *position == delimiter) {
81 template<bool characterPredicate(UChar)>
82 static bool skipExactly(const UChar*& position, const UChar* end)
84 if (position < end && characterPredicate(*position)) {
91 static void skipUtil(const UChar*& position, const UChar* end, UChar delimiter)
93 while (position < end && *position != delimiter)
97 template<bool characterPredicate(UChar)>
98 static void skipWhile(const UChar*& position, const UChar* end)
100 while (position < end && characterPredicate(*position))
106 CSPSource(const String& scheme, const String& host, int port, bool hostHasWildcard, bool portHasWildcard)
110 , m_hostHasWildcard(hostHasWildcard)
111 , m_portHasWildcard(portHasWildcard)
115 bool matches(const KURL& url) const
117 if (!schemeMatches(url))
121 return hostMatches(url) && portMatches(url);
125 bool schemeMatches(const KURL& url) const
127 return equalIgnoringCase(url.protocol(), m_scheme);
130 bool hostMatches(const KURL& url) const
132 if (m_hostHasWildcard)
135 return equalIgnoringCase(url.host(), m_host);
138 bool portMatches(const KURL& url) const
140 if (m_portHasWildcard)
143 // FIXME: Handle explicit default ports correctly.
144 return url.port() == m_port;
147 bool isSchemeOnly() const { return m_host.isEmpty(); }
153 bool m_hostHasWildcard;
154 bool m_portHasWildcard;
157 class CSPSourceList {
159 explicit CSPSourceList(SecurityOrigin*);
161 void parse(const String&);
162 bool matches(const KURL&);
165 void parse(const UChar* begin, const UChar* end);
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);
172 void addSourceSelf();
174 SecurityOrigin* m_origin;
175 Vector<CSPSource> m_list;
178 CSPSourceList::CSPSourceList(SecurityOrigin* origin)
183 void CSPSourceList::parse(const String& value)
185 parse(value.characters(), value.characters() + value.length());
188 bool CSPSourceList::matches(const KURL& url)
190 for (size_t i = 0; i < m_list.size(); ++i) {
191 if (m_list[i].matches(url))
197 // source-list = *WSP [ source *( 1*WSP source ) *WSP ]
198 // / *WSP "'none'" *WSP
200 void CSPSourceList::parse(const UChar* begin, const UChar* end)
202 const UChar* position = begin;
204 bool isFirstSourceInList = true;
205 while (position < end) {
206 skipWhile<isASCIISpace>(position, end);
207 const UChar* beginSource = position;
208 skipWhile<isSourceCharacter>(position, end);
210 if (isFirstSourceInList && equalIgnoringCase("'none'", beginSource, position - beginSource))
211 return; // We represent 'none' as an empty m_list.
212 isFirstSourceInList = false;
216 bool hostHasWildcard = false;
217 bool portHasWildcard = false;
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));
225 ASSERT(position == end || isASCIISpace(*position));
229 // source = scheme ":"
230 // / ( [ scheme "://" ] host [ port ] )
233 bool CSPSourceList::parseSource(const UChar* begin, const UChar* end,
234 String& scheme, String& host, int& port,
235 bool& hostHasWildcard, bool& portHasWildcard)
240 if (equalIgnoringCase("'self'", begin, end - begin)) {
245 const UChar* position = begin;
247 const UChar* beginHost = begin;
248 skipUtil(position, end, ':');
250 if (position == end) {
251 // This must be a host-only source.
252 if (!parseHost(beginHost, position, host, hostHasWildcard))
257 if (end - position == 1) {
258 ASSERT(*position == ':');
259 // This must be a scheme-only source.
260 if (!parseScheme(begin, position, scheme))
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, '/'))
272 beginHost = position;
273 skipUtil(position, end, ':');
276 if (position == beginHost)
279 if (!parseHost(beginHost, position, host, hostHasWildcard))
282 if (position == end) {
287 if (!skipExactly(position, end, ':'))
288 ASSERT_NOT_REACHED();
290 if (!parsePort(position, end, port, portHasWildcard))
296 // ; <scheme> production from RFC 3986
297 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
299 bool CSPSourceList::parseScheme(const UChar* begin, const UChar* end, String& scheme)
301 ASSERT(begin <= end);
302 ASSERT(scheme.isEmpty());
307 const UChar* position = begin;
309 if (!skipExactly<isASCIIAlpha>(position, end))
312 skipWhile<isSchemeContinuationCharacter>(position, end);
317 scheme = String(begin, end - begin);
321 // host = [ "*." ] 1*host-char *( "." 1*host-char )
323 // host-char = ALPHA / DIGIT / "-"
325 bool CSPSourceList::parseHost(const UChar* begin, const UChar* end, String& host, bool& hostHasWildcard)
327 ASSERT(begin <= end);
328 ASSERT(host.isEmpty());
329 ASSERT(!hostHasWildcard);
334 const UChar* position = begin;
336 if (skipExactly(position, end, '*')) {
337 hostHasWildcard = true;
342 if (!skipExactly(position, end, '.'))
346 const UChar* hostBegin = position;
348 while (position < end) {
349 if (!skipExactly<isHostCharacter>(position, end))
352 skipWhile<isHostCharacter>(position, end);
354 if (position < end && !skipExactly(position, end, '.'))
358 ASSERT(position == end);
359 host = String(hostBegin, end - hostBegin);
363 // port = ":" ( 1*DIGIT / "*" )
365 bool CSPSourceList::parsePort(const UChar* begin, const UChar* end, int& port, bool& portHasWildcard)
367 ASSERT(begin <= end);
369 ASSERT(!portHasWildcard);
374 if (end - begin == 1 && *begin == '*') {
376 portHasWildcard = true;
380 const UChar* position = begin;
381 skipWhile<isASCIIDigit>(position, end);
387 port = charactersToIntStrict(begin, end - begin, &ok);
391 void CSPSourceList::addSourceSelf()
393 // FIXME: Inherit the scheme, host, and port from the current URL.
399 CSPDirective(const String& value, SecurityOrigin* origin)
400 : m_sourceList(origin)
402 m_sourceList.parse(value);
405 bool allows(const KURL& url)
407 return m_sourceList.matches(url);
411 CSPSourceList m_sourceList;
416 explicit CSPOptions(const String& value)
417 : m_disableXSSProtection(false)
418 , m_evalScript(false)
423 bool disableXSSProtection() const { return m_disableXSSProtection; }
424 bool evalScript() const { return m_evalScript; }
427 void parse(const String&);
429 bool m_disableXSSProtection;
433 // options = "options" *( 1*WSP option-value ) *WSP
434 // option-value = 1*( ALPHA / DIGIT / "-" )
436 void CSPOptions::parse(const String& value)
438 DEFINE_STATIC_LOCAL(String, disableXSSProtection, ("disable-xss-protection"));
439 DEFINE_STATIC_LOCAL(String, evalScript, ("eval-script"));
441 const UChar* position = value.characters();
442 const UChar* end = position + value.length();
444 while (position < end) {
445 skipWhile<isASCIISpace>(position, end);
447 const UChar* optionsValueBegin = position;
449 if (!skipExactly<isOptionValueCharacter>(position, end))
452 skipWhile<isOptionValueCharacter>(position, end);
454 String optionsValue(optionsValueBegin, position - optionsValueBegin);
456 if (equalIgnoringCase(optionsValue, disableXSSProtection))
457 m_disableXSSProtection = true;
458 else if (equalIgnoringCase(optionsValue, evalScript))
463 ContentSecurityPolicy::ContentSecurityPolicy(SecurityOrigin* origin)
464 : m_havePolicy(false)
469 ContentSecurityPolicy::~ContentSecurityPolicy()
473 void ContentSecurityPolicy::didReceiveHeader(const String& header)
476 return; // The first policy wins.
482 bool ContentSecurityPolicy::protectAgainstXSS() const
484 return m_scriptSrc && (!m_options || !m_options->disableXSSProtection());
487 bool ContentSecurityPolicy::allowJavaScriptURLs() const
489 return !protectAgainstXSS();
492 bool ContentSecurityPolicy::allowInlineEventHandlers() const
494 return !protectAgainstXSS();
497 bool ContentSecurityPolicy::allowInlineScript() const
499 return !protectAgainstXSS();
502 bool ContentSecurityPolicy::allowScriptFromSource(const KURL& url) const
504 return !m_scriptSrc || m_scriptSrc->allows(url);
507 bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url) const
509 return !m_objectSrc || m_objectSrc->allows(url);
512 // policy = directive-list
513 // directive-list = [ directive *( ";" [ directive ] ) ]
515 void ContentSecurityPolicy::parse(const String& policy)
517 ASSERT(!m_havePolicy);
519 if (policy.isEmpty())
522 const UChar* position = policy.characters();
523 const UChar* end = position + policy.length();
525 while (position < end) {
526 const UChar* directiveBegin = position;
527 skipUtil(position, end, ';');
530 if (parseDirective(directiveBegin, position, name, value)) {
531 ASSERT(!name.isEmpty());
532 addDirective(name, value);
535 ASSERT(position == end || *position == ';');
536 skipExactly(position, end, ';');
540 // directive = *WSP [ directive-name [ WSP directive-value ] ]
541 // directive-name = 1*( ALPHA / DIGIT / "-" )
542 // directive-value = *( WSP / <VCHAR except ";"> )
544 bool ContentSecurityPolicy::parseDirective(const UChar* begin, const UChar* end, String& name, String& value)
546 ASSERT(name.isEmpty());
547 ASSERT(value.isEmpty());
549 const UChar* position = begin;
550 skipWhile<isASCIISpace>(position, end);
552 const UChar* nameBegin = position;
553 skipWhile<isDirectiveNameCharacter>(position, end);
555 // The directive-name must be non-empty.
556 if (nameBegin == position)
559 name = String(nameBegin, position - nameBegin);
564 if (!skipExactly<isASCIISpace>(position, end))
567 skipWhile<isASCIISpace>(position, end);
569 const UChar* valueBegin = position;
570 skipWhile<isDirectiveValueCharacter>(position, end);
575 // The directive-value may be empty.
576 if (valueBegin == position)
579 value = String(valueBegin, position - valueBegin);
583 void ContentSecurityPolicy::addDirective(const String& name, const String& value)
585 DEFINE_STATIC_LOCAL(String, scriptSrc, ("script-src"));
586 DEFINE_STATIC_LOCAL(String, objectSrc, ("object-src"));
587 DEFINE_STATIC_LOCAL(String, options, ("options"));
589 ASSERT(!name.isEmpty());
591 if (!m_scriptSrc && equalIgnoringCase(name, scriptSrc))
592 m_scriptSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
593 else if (!m_objectSrc && equalIgnoringCase(name, objectSrc))
594 m_objectSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
595 else if (!m_options && equalIgnoringCase(name, options))
596 m_options = adoptPtr(new CSPOptions(value));