Home | History | Annotate | Download | only in page
      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     m_list.append(CSPSource(m_origin->protocol(), m_origin->host(), m_origin->port(), false, false));
    394 }
    395 
    396 class CSPDirective {
    397 public:
    398     CSPDirective(const String& value, SecurityOrigin* origin)
    399         : m_sourceList(origin)
    400     {
    401         m_sourceList.parse(value);
    402     }
    403 
    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:
    415     explicit CSPOptions(const String& value)
    416         : m_disableXSSProtection(false)
    417         , m_evalScript(false)
    418     {
    419         parse(value);
    420     }
    421 
    422     bool disableXSSProtection() const { return m_disableXSSProtection; }
    423     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 //
    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 
    462 ContentSecurityPolicy::ContentSecurityPolicy(SecurityOrigin* origin)
    463     : m_havePolicy(false)
    464     , m_origin(origin)
    465 {
    466 }
    467 
    468 ContentSecurityPolicy::~ContentSecurityPolicy()
    469 {
    470 }
    471 
    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 
    481 bool ContentSecurityPolicy::protectAgainstXSS() const
    482 {
    483     return m_scriptSrc && (!m_options || !m_options->disableXSSProtection());
    484 }
    485 
    486 bool ContentSecurityPolicy::allowJavaScriptURLs() const
    487 {
    488     return !protectAgainstXSS();
    489 }
    490 
    491 bool ContentSecurityPolicy::allowInlineEventHandlers() const
    492 {
    493     return !protectAgainstXSS();
    494 }
    495 
    496 bool ContentSecurityPolicy::allowInlineScript() const
    497 {
    498     return !protectAgainstXSS();
    499 }
    500 
    501 bool ContentSecurityPolicy::allowEval() const
    502 {
    503     return !m_scriptSrc || (m_options && m_options->evalScript());
    504 }
    505 
    506 bool ContentSecurityPolicy::allowScriptFromSource(const KURL& url) const
    507 {
    508     return !m_scriptSrc || m_scriptSrc->allows(url);
    509 }
    510 
    511 bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url) const
    512 {
    513     return !m_objectSrc || m_objectSrc->allows(url);
    514 }
    515 
    516 bool ContentSecurityPolicy::allowImageFromSource(const KURL& url) const
    517 {
    518     return !m_imgSrc || m_imgSrc->allows(url);
    519 }
    520 
    521 bool ContentSecurityPolicy::allowStyleFromSource(const KURL& url) const
    522 {
    523     return !m_styleSrc || m_styleSrc->allows(url);
    524 }
    525 
    526 bool ContentSecurityPolicy::allowFontFromSource(const KURL& url) const
    527 {
    528     return !m_fontSrc || m_fontSrc->allows(url);
    529 }
    530 
    531 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 //
    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 //
    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 
    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