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