1 /* 2 * Copyright (C) 2007 Apple 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 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "config.h" 30 #include "SecurityOrigin.h" 31 32 #include "BlobURL.h" 33 #include "Document.h" 34 #include "FileSystem.h" 35 #include "KURL.h" 36 #include "OriginAccessEntry.h" 37 #include "SchemeRegistry.h" 38 #include <wtf/StdLibExtras.h> 39 40 namespace WebCore { 41 42 static SecurityOrigin::LocalLoadPolicy localLoadPolicy = SecurityOrigin::AllowLocalLoadsForLocalOnly; 43 const int MaxAllowedPort = 65535; 44 45 typedef Vector<OriginAccessEntry> OriginAccessWhiteList; 46 typedef HashMap<String, OriginAccessWhiteList*> OriginAccessMap; 47 48 static OriginAccessMap& originAccessMap() 49 { 50 DEFINE_STATIC_LOCAL(OriginAccessMap, originAccessMap, ()); 51 return originAccessMap; 52 } 53 54 static bool schemeRequiresAuthority(const String& scheme) 55 { 56 DEFINE_STATIC_LOCAL(URLSchemesMap, schemes, ()); 57 58 if (schemes.isEmpty()) { 59 schemes.add("http"); 60 schemes.add("https"); 61 schemes.add("ftp"); 62 } 63 64 return schemes.contains(scheme); 65 } 66 67 68 SecurityOrigin::SecurityOrigin(const KURL& url, SandboxFlags sandboxFlags) 69 : m_sandboxFlags(sandboxFlags) 70 , m_protocol(url.protocol().isNull() ? "" : url.protocol().lower()) 71 , m_host(url.host().isNull() ? "" : url.host().lower()) 72 , m_port(url.port()) 73 , m_isUnique(isSandboxed(SandboxOrigin) || SchemeRegistry::shouldTreatURLSchemeAsNoAccess(m_protocol)) 74 , m_universalAccess(false) 75 , m_domainWasSetInDOM(false) 76 , m_enforceFilePathSeparation(false) 77 { 78 // These protocols do not create security origins; the owner frame provides the origin 79 if (m_protocol == "about" || m_protocol == "javascript") 80 m_protocol = ""; 81 82 #if ENABLE(BLOB) || ENABLE(FILE_SYSTEM) 83 bool isBlobOrFileSystemProtocol = false; 84 #if ENABLE(BLOB) 85 if (m_protocol == BlobURL::blobProtocol()) 86 isBlobOrFileSystemProtocol = true; 87 #endif 88 #if ENABLE(FILE_SYSTEM) 89 if (m_protocol == "filesystem") 90 isBlobOrFileSystemProtocol = true; 91 #endif 92 if (isBlobOrFileSystemProtocol) { 93 KURL originURL(ParsedURLString, url.path()); 94 if (originURL.isValid()) { 95 m_protocol = originURL.protocol().lower(); 96 m_host = originURL.host().lower(); 97 m_port = originURL.port(); 98 } else 99 m_isUnique = true; 100 } 101 #endif 102 103 // For edge case URLs that were probably misparsed, make sure that the origin is unique. 104 if (schemeRequiresAuthority(m_protocol) && m_host.isEmpty()) 105 m_isUnique = true; 106 if (m_protocol.isEmpty()) 107 m_isUnique = true; 108 109 // document.domain starts as m_host, but can be set by the DOM. 110 m_domain = m_host; 111 112 // By default, only local SecurityOrigins can load local resources. 113 m_canLoadLocalResources = isLocal(); 114 if (m_canLoadLocalResources) { 115 // Directories should never be readable. 116 // Note that we do not do this check for blob or filesystem url because its origin is file:/// when it is created from local file urls. 117 #if ENABLE(BLOB) || ENABLE(FILE_SYSTEM) 118 bool doDirectoryCheck = !isBlobOrFileSystemProtocol; 119 #else 120 bool doDirectoryCheck = true; 121 #endif 122 if (doDirectoryCheck && (!url.hasPath() || url.path().endsWith("/"))) 123 m_isUnique = true; 124 // Store the path in case we are doing per-file origin checking. 125 m_filePath = url.path(); 126 } 127 128 if (isDefaultPortForProtocol(m_port, m_protocol)) 129 m_port = 0; 130 } 131 132 SecurityOrigin::SecurityOrigin(const SecurityOrigin* other) 133 : m_sandboxFlags(other->m_sandboxFlags) 134 , m_protocol(other->m_protocol.threadsafeCopy()) 135 , m_host(other->m_host.threadsafeCopy()) 136 , m_encodedHost(other->m_encodedHost.threadsafeCopy()) 137 , m_domain(other->m_domain.threadsafeCopy()) 138 , m_filePath(other->m_filePath.threadsafeCopy()) 139 , m_port(other->m_port) 140 , m_isUnique(other->m_isUnique) 141 , m_universalAccess(other->m_universalAccess) 142 , m_domainWasSetInDOM(other->m_domainWasSetInDOM) 143 , m_canLoadLocalResources(other->m_canLoadLocalResources) 144 , m_enforceFilePathSeparation(other->m_enforceFilePathSeparation) 145 { 146 } 147 148 bool SecurityOrigin::isEmpty() const 149 { 150 return m_protocol.isEmpty(); 151 } 152 153 PassRefPtr<SecurityOrigin> SecurityOrigin::create(const KURL& url, SandboxFlags sandboxFlags) 154 { 155 if (!url.isValid()) 156 return adoptRef(new SecurityOrigin(KURL(), sandboxFlags)); 157 return adoptRef(new SecurityOrigin(url, sandboxFlags)); 158 } 159 160 PassRefPtr<SecurityOrigin> SecurityOrigin::createEmpty() 161 { 162 return create(KURL()); 163 } 164 165 PassRefPtr<SecurityOrigin> SecurityOrigin::threadsafeCopy() 166 { 167 return adoptRef(new SecurityOrigin(this)); 168 } 169 170 void SecurityOrigin::setDomainFromDOM(const String& newDomain) 171 { 172 m_domainWasSetInDOM = true; 173 m_domain = newDomain.lower(); 174 } 175 176 static HashSet<String>& schemesForbiddenFromDomainRelaxation() 177 { 178 DEFINE_STATIC_LOCAL(HashSet<String>, schemes, ()); 179 return schemes; 180 } 181 182 void SecurityOrigin::setDomainRelaxationForbiddenForURLScheme(bool forbidden, const String& scheme) 183 { 184 if (scheme.isEmpty()) 185 return; 186 187 if (forbidden) 188 schemesForbiddenFromDomainRelaxation().add(scheme); 189 else 190 schemesForbiddenFromDomainRelaxation().remove(scheme); 191 } 192 193 bool SecurityOrigin::isDomainRelaxationForbiddenForURLScheme(const String& scheme) 194 { 195 if (scheme.isEmpty()) 196 return false; 197 198 return schemesForbiddenFromDomainRelaxation().contains(scheme); 199 } 200 201 bool SecurityOrigin::canAccess(const SecurityOrigin* other) const 202 { 203 if (m_universalAccess) 204 return true; 205 206 if (this == other) 207 return true; 208 209 if (isUnique() || other->isUnique()) 210 return false; 211 212 // Here are two cases where we should permit access: 213 // 214 // 1) Neither document has set document.domain. In this case, we insist 215 // that the scheme, host, and port of the URLs match. 216 // 217 // 2) Both documents have set document.domain. In this case, we insist 218 // that the documents have set document.domain to the same value and 219 // that the scheme of the URLs match. 220 // 221 // This matches the behavior of Firefox 2 and Internet Explorer 6. 222 // 223 // Internet Explorer 7 and Opera 9 are more strict in that they require 224 // the port numbers to match when both pages have document.domain set. 225 // 226 // FIXME: Evaluate whether we can tighten this policy to require matched 227 // port numbers. 228 // 229 // Opera 9 allows access when only one page has set document.domain, but 230 // this is a security vulnerability. 231 232 bool canAccess = false; 233 if (m_protocol == other->m_protocol) { 234 if (!m_domainWasSetInDOM && !other->m_domainWasSetInDOM) { 235 if (m_host == other->m_host && m_port == other->m_port) 236 canAccess = true; 237 } else if (m_domainWasSetInDOM && other->m_domainWasSetInDOM) { 238 if (m_domain == other->m_domain) 239 canAccess = true; 240 } 241 } 242 243 if (canAccess && isLocal()) 244 canAccess = passesFileCheck(other); 245 246 return canAccess; 247 } 248 249 bool SecurityOrigin::passesFileCheck(const SecurityOrigin* other) const 250 { 251 ASSERT(isLocal() && other->isLocal()); 252 253 if (!m_enforceFilePathSeparation && !other->m_enforceFilePathSeparation) 254 return true; 255 256 return (m_filePath == other->m_filePath); 257 } 258 259 bool SecurityOrigin::canRequest(const KURL& url) const 260 { 261 if (m_universalAccess) 262 return true; 263 264 if (isUnique()) 265 return false; 266 267 RefPtr<SecurityOrigin> targetOrigin = SecurityOrigin::create(url); 268 269 if (targetOrigin->isUnique()) 270 return false; 271 272 // We call isSameSchemeHostPort here instead of canAccess because we want 273 // to ignore document.domain effects. 274 if (isSameSchemeHostPort(targetOrigin.get())) 275 return true; 276 277 if (isAccessWhiteListed(targetOrigin.get())) 278 return true; 279 280 return false; 281 } 282 283 bool SecurityOrigin::taintsCanvas(const KURL& url) const 284 { 285 if (canRequest(url)) 286 return false; 287 288 // This function exists because we treat data URLs as having a unique origin, 289 // contrary to the current (9/19/2009) draft of the HTML5 specification. 290 // We still want to let folks paint data URLs onto untainted canvases, so 291 // we special case data URLs below. If we change to match HTML5 w.r.t. 292 // data URL security, then we can remove this function in favor of 293 // !canRequest. 294 if (url.protocolIsData()) 295 return false; 296 297 return true; 298 } 299 300 bool SecurityOrigin::canReceiveDragData(const SecurityOrigin* dragInitiator) const 301 { 302 if (this == dragInitiator) 303 return true; 304 305 // FIXME: Currently we treat data URLs as having a unique origin, contrary to the 306 // current (9/19/2009) draft of the HTML5 specification. We still want to allow 307 // drop across data URLs, so we special case data URLs below. If we change to 308 // match HTML5 w.r.t. data URL security, then we can remove this check. 309 if (m_protocol == "data") 310 return true; 311 312 return canAccess(dragInitiator); 313 } 314 315 bool SecurityOrigin::isAccessWhiteListed(const SecurityOrigin* targetOrigin) const 316 { 317 if (OriginAccessWhiteList* list = originAccessMap().get(toString())) { 318 for (size_t i = 0; i < list->size(); ++i) { 319 if (list->at(i).matchesOrigin(*targetOrigin)) 320 return true; 321 } 322 } 323 return false; 324 } 325 326 bool SecurityOrigin::isAccessToURLWhiteListed(const KURL& url) const 327 { 328 RefPtr<SecurityOrigin> targetOrigin = SecurityOrigin::create(url); 329 return isAccessWhiteListed(targetOrigin.get()); 330 } 331 332 bool SecurityOrigin::canDisplay(const KURL& url) const 333 { 334 String protocol = url.protocol().lower(); 335 336 if (SchemeRegistry::canDisplayOnlyIfCanRequest(protocol)) 337 return canRequest(url); 338 339 if (SchemeRegistry::shouldTreatURLSchemeAsDisplayIsolated(protocol)) 340 return m_protocol == protocol || isAccessToURLWhiteListed(url); 341 342 if (restrictAccessToLocal() && SchemeRegistry::shouldTreatURLSchemeAsLocal(protocol)) 343 return canLoadLocalResources() || isAccessToURLWhiteListed(url); 344 345 return true; 346 } 347 348 void SecurityOrigin::grantLoadLocalResources() 349 { 350 // This function exists only to support backwards compatibility with older 351 // versions of WebKit. Granting privileges to some, but not all, documents 352 // in a SecurityOrigin is a security hazard because the documents without 353 // the privilege can obtain the privilege by injecting script into the 354 // documents that have been granted the privilege. 355 ASSERT(allowSubstituteDataAccessToLocal()); 356 m_canLoadLocalResources = true; 357 } 358 359 void SecurityOrigin::grantUniversalAccess() 360 { 361 m_universalAccess = true; 362 } 363 364 void SecurityOrigin::enforceFilePathSeparation() 365 { 366 ASSERT(isLocal()); 367 m_enforceFilePathSeparation = true; 368 } 369 370 bool SecurityOrigin::isLocal() const 371 { 372 return SchemeRegistry::shouldTreatURLSchemeAsLocal(m_protocol); 373 } 374 375 bool SecurityOrigin::isSecureTransitionTo(const KURL& url) const 376 { 377 // New window created by the application 378 if (isEmpty()) 379 return true; 380 381 RefPtr<SecurityOrigin> other = SecurityOrigin::create(url); 382 return canAccess(other.get()); 383 } 384 385 String SecurityOrigin::toString() const 386 { 387 if (isEmpty()) 388 return "null"; 389 390 if (isUnique()) 391 return "null"; 392 393 if (m_protocol == "file") { 394 if (m_enforceFilePathSeparation) 395 return "null"; 396 return "file://"; 397 } 398 399 Vector<UChar> result; 400 result.reserveInitialCapacity(m_protocol.length() + m_host.length() + 10); 401 append(result, m_protocol); 402 append(result, "://"); 403 append(result, m_host); 404 405 if (m_port) { 406 append(result, ":"); 407 append(result, String::number(m_port)); 408 } 409 410 return String::adopt(result); 411 } 412 413 PassRefPtr<SecurityOrigin> SecurityOrigin::createFromString(const String& originString) 414 { 415 return SecurityOrigin::create(KURL(KURL(), originString)); 416 } 417 418 static const char SeparatorCharacter = '_'; 419 420 PassRefPtr<SecurityOrigin> SecurityOrigin::createFromDatabaseIdentifier(const String& databaseIdentifier) 421 { 422 // Make sure there's a first separator 423 size_t separator1 = databaseIdentifier.find(SeparatorCharacter); 424 if (separator1 == notFound) 425 return create(KURL()); 426 427 // Make sure there's a second separator 428 size_t separator2 = databaseIdentifier.reverseFind(SeparatorCharacter); 429 if (separator2 == notFound) 430 return create(KURL()); 431 432 // Ensure there were at least 2 separator characters. Some hostnames on intranets have 433 // underscores in them, so we'll assume that any additional underscores are part of the host. 434 if (separator1 == separator2) 435 return create(KURL()); 436 437 // Make sure the port section is a valid port number or doesn't exist 438 bool portOkay; 439 int port = databaseIdentifier.right(databaseIdentifier.length() - separator2 - 1).toInt(&portOkay); 440 bool portAbsent = (separator2 == databaseIdentifier.length() - 1); 441 if (!(portOkay || portAbsent)) 442 return create(KURL()); 443 444 if (port < 0 || port > MaxAllowedPort) 445 return create(KURL()); 446 447 // Split out the 3 sections of data 448 String protocol = databaseIdentifier.substring(0, separator1); 449 String host = databaseIdentifier.substring(separator1 + 1, separator2 - separator1 - 1); 450 451 host = decodeURLEscapeSequences(host); 452 return create(KURL(KURL(), protocol + "://" + host + ":" + String::number(port))); 453 } 454 455 PassRefPtr<SecurityOrigin> SecurityOrigin::create(const String& protocol, const String& host, int port) 456 { 457 if (port < 0 || port > MaxAllowedPort) 458 create(KURL()); 459 String decodedHost = decodeURLEscapeSequences(host); 460 return create(KURL(KURL(), protocol + "://" + host + ":" + String::number(port))); 461 } 462 463 String SecurityOrigin::databaseIdentifier() const 464 { 465 String separatorString(&SeparatorCharacter, 1); 466 467 if (m_encodedHost.isEmpty()) 468 m_encodedHost = encodeForFileName(m_host); 469 470 return m_protocol + separatorString + m_encodedHost + separatorString + String::number(m_port); 471 } 472 473 bool SecurityOrigin::equal(const SecurityOrigin* other) const 474 { 475 if (other == this) 476 return true; 477 478 if (!isSameSchemeHostPort(other)) 479 return false; 480 481 if (m_domainWasSetInDOM != other->m_domainWasSetInDOM) 482 return false; 483 484 if (m_domainWasSetInDOM && m_domain != other->m_domain) 485 return false; 486 487 return true; 488 } 489 490 bool SecurityOrigin::isSameSchemeHostPort(const SecurityOrigin* other) const 491 { 492 if (m_host != other->m_host) 493 return false; 494 495 if (m_protocol != other->m_protocol) 496 return false; 497 498 if (m_port != other->m_port) 499 return false; 500 501 if (isLocal() && !passesFileCheck(other)) 502 return false; 503 504 return true; 505 } 506 507 bool SecurityOrigin::shouldHideReferrer(const KURL& url, const String& referrer) 508 { 509 bool referrerIsSecureURL = protocolIs(referrer, "https"); 510 bool referrerIsWebURL = referrerIsSecureURL || protocolIs(referrer, "http"); 511 512 if (!referrerIsWebURL) 513 return true; 514 515 if (!referrerIsSecureURL) 516 return false; 517 518 bool URLIsSecureURL = url.protocolIs("https"); 519 520 return !URLIsSecureURL; 521 } 522 523 void SecurityOrigin::setLocalLoadPolicy(LocalLoadPolicy policy) 524 { 525 localLoadPolicy = policy; 526 } 527 528 bool SecurityOrigin::restrictAccessToLocal() 529 { 530 return localLoadPolicy != SecurityOrigin::AllowLocalLoadsForAll; 531 } 532 533 bool SecurityOrigin::allowSubstituteDataAccessToLocal() 534 { 535 return localLoadPolicy != SecurityOrigin::AllowLocalLoadsForLocalOnly; 536 } 537 538 void SecurityOrigin::addOriginAccessWhitelistEntry(const SecurityOrigin& sourceOrigin, const String& destinationProtocol, const String& destinationDomains, bool allowDestinationSubdomains) 539 { 540 ASSERT(isMainThread()); 541 ASSERT(!sourceOrigin.isEmpty()); 542 if (sourceOrigin.isEmpty()) 543 return; 544 545 String sourceString = sourceOrigin.toString(); 546 pair<OriginAccessMap::iterator, bool> result = originAccessMap().add(sourceString, 0); 547 if (result.second) 548 result.first->second = new OriginAccessWhiteList; 549 550 OriginAccessWhiteList* list = result.first->second; 551 list->append(OriginAccessEntry(destinationProtocol, destinationDomains, allowDestinationSubdomains ? OriginAccessEntry::AllowSubdomains : OriginAccessEntry::DisallowSubdomains)); 552 } 553 554 void SecurityOrigin::removeOriginAccessWhitelistEntry(const SecurityOrigin& sourceOrigin, const String& destinationProtocol, const String& destinationDomains, bool allowDestinationSubdomains) 555 { 556 ASSERT(isMainThread()); 557 ASSERT(!sourceOrigin.isEmpty()); 558 if (sourceOrigin.isEmpty()) 559 return; 560 561 String sourceString = sourceOrigin.toString(); 562 OriginAccessMap& map = originAccessMap(); 563 OriginAccessMap::iterator it = map.find(sourceString); 564 if (it == map.end()) 565 return; 566 567 OriginAccessWhiteList* list = it->second; 568 size_t index = list->find(OriginAccessEntry(destinationProtocol, destinationDomains, allowDestinationSubdomains ? OriginAccessEntry::AllowSubdomains : OriginAccessEntry::DisallowSubdomains)); 569 if (index == notFound) 570 return; 571 572 list->remove(index); 573 574 if (!list->isEmpty()) 575 return; 576 577 map.remove(it); 578 delete list; 579 } 580 581 void SecurityOrigin::resetOriginAccessWhitelists() 582 { 583 ASSERT(isMainThread()); 584 OriginAccessMap& map = originAccessMap(); 585 deleteAllValues(map); 586 map.clear(); 587 } 588 589 } // namespace WebCore 590