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 33namespace 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. 38namespace { 39 40bool isDirectiveNameCharacter(UChar c) 41{ 42 return isASCIIAlphanumeric(c) || c == '-'; 43} 44 45bool isDirectiveValueCharacter(UChar c) 46{ 47 return isASCIISpace(c) || (c >= 0x21 && c <= 0x7e); // Whitespace + VCHAR 48} 49 50bool isSourceCharacter(UChar c) 51{ 52 return !isASCIISpace(c); 53} 54 55bool isHostCharacter(UChar c) 56{ 57 return isASCIIAlphanumeric(c) || c == '-'; 58} 59 60bool isOptionValueCharacter(UChar c) 61{ 62 return isASCIIAlphanumeric(c) || c == '-'; 63} 64 65bool isSchemeContinuationCharacter(UChar c) 66{ 67 return isASCIIAlphanumeric(c) || c == '+' || c == '-' || c == '.'; 68} 69 70} // namespace 71 72static 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 81template<bool characterPredicate(UChar)> 82static 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 91static void skipUtil(const UChar*& position, const UChar* end, UChar delimiter) 92{ 93 while (position < end && *position != delimiter) 94 ++position; 95} 96 97template<bool characterPredicate(UChar)> 98static void skipWhile(const UChar*& position, const UChar* end) 99{ 100 while (position < end && characterPredicate(*position)) 101 ++position; 102} 103 104class CSPSource { 105public: 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 124private: 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 157class CSPSourceList { 158public: 159 explicit CSPSourceList(SecurityOrigin*); 160 161 void parse(const String&); 162 bool matches(const KURL&); 163 164private: 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 178CSPSourceList::CSPSourceList(SecurityOrigin* origin) 179 : m_origin(origin) 180{ 181} 182 183void CSPSourceList::parse(const String& value) 184{ 185 parse(value.characters(), value.characters() + value.length()); 186} 187 188bool 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// 200void 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// 233bool 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// 299bool 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// 325bool 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// 365bool 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 391void CSPSourceList::addSourceSelf() 392{ 393 m_list.append(CSPSource(m_origin->protocol(), m_origin->host(), m_origin->port(), false, false)); 394} 395 396class CSPDirective { 397public: 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 409private: 410 CSPSourceList m_sourceList; 411}; 412 413class CSPOptions { 414public: 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 425private: 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// 435void 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 462ContentSecurityPolicy::ContentSecurityPolicy(SecurityOrigin* origin) 463 : m_havePolicy(false) 464 , m_origin(origin) 465{ 466} 467 468ContentSecurityPolicy::~ContentSecurityPolicy() 469{ 470} 471 472void 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 481bool ContentSecurityPolicy::protectAgainstXSS() const 482{ 483 return m_scriptSrc && (!m_options || !m_options->disableXSSProtection()); 484} 485 486bool ContentSecurityPolicy::allowJavaScriptURLs() const 487{ 488 return !protectAgainstXSS(); 489} 490 491bool ContentSecurityPolicy::allowInlineEventHandlers() const 492{ 493 return !protectAgainstXSS(); 494} 495 496bool ContentSecurityPolicy::allowInlineScript() const 497{ 498 return !protectAgainstXSS(); 499} 500 501bool ContentSecurityPolicy::allowEval() const 502{ 503 return !m_scriptSrc || (m_options && m_options->evalScript()); 504} 505 506bool ContentSecurityPolicy::allowScriptFromSource(const KURL& url) const 507{ 508 return !m_scriptSrc || m_scriptSrc->allows(url); 509} 510 511bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url) const 512{ 513 return !m_objectSrc || m_objectSrc->allows(url); 514} 515 516bool ContentSecurityPolicy::allowImageFromSource(const KURL& url) const 517{ 518 return !m_imgSrc || m_imgSrc->allows(url); 519} 520 521bool ContentSecurityPolicy::allowStyleFromSource(const KURL& url) const 522{ 523 return !m_styleSrc || m_styleSrc->allows(url); 524} 525 526bool ContentSecurityPolicy::allowFontFromSource(const KURL& url) const 527{ 528 return !m_fontSrc || m_fontSrc->allows(url); 529} 530 531bool 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// 539void 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// 568bool 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 607void 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