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 "CString.h" 33 #include "Document.h" 34 #include "KURL.h" 35 #include "OriginAccessEntry.h" 36 #include <wtf/StdLibExtras.h> 37 38 namespace WebCore { 39 40 static SecurityOrigin::LocalLoadPolicy localLoadPolicy = SecurityOrigin::AllowLocalLoadsForLocalOnly; 41 42 typedef Vector<OriginAccessEntry> OriginAccessWhiteList; 43 typedef HashMap<String, OriginAccessWhiteList*> OriginAccessMap; 44 45 static OriginAccessMap& originAccessMap() 46 { 47 DEFINE_STATIC_LOCAL(OriginAccessMap, originAccessMap, ()); 48 return originAccessMap; 49 } 50 51 static URLSchemesMap& localSchemes() 52 { 53 DEFINE_STATIC_LOCAL(URLSchemesMap, localSchemes, ()); 54 55 if (localSchemes.isEmpty()) { 56 localSchemes.add("file"); 57 #if PLATFORM(MAC) 58 localSchemes.add("applewebdata"); 59 #endif 60 #if PLATFORM(QT) 61 localSchemes.add("qrc"); 62 #endif 63 } 64 65 return localSchemes; 66 } 67 68 static URLSchemesMap& schemesWithUniqueOrigins() 69 { 70 DEFINE_STATIC_LOCAL(URLSchemesMap, schemesWithUniqueOrigins, ()); 71 72 // This is a willful violation of HTML5. 73 // See https://bugs.webkit.org/show_bug.cgi?id=11885 74 if (schemesWithUniqueOrigins.isEmpty()) 75 schemesWithUniqueOrigins.add("data"); 76 77 return schemesWithUniqueOrigins; 78 } 79 80 SecurityOrigin::SecurityOrigin(const KURL& url, SandboxFlags sandboxFlags) 81 : m_sandboxFlags(sandboxFlags) 82 , m_protocol(url.protocol().isNull() ? "" : url.protocol().lower()) 83 , m_host(url.host().isNull() ? "" : url.host().lower()) 84 , m_port(url.port()) 85 , m_isUnique(isSandboxed(SandboxOrigin) || shouldTreatURLSchemeAsNoAccess(m_protocol)) 86 , m_universalAccess(false) 87 , m_domainWasSetInDOM(false) 88 { 89 // These protocols do not create security origins; the owner frame provides the origin 90 if (m_protocol == "about" || m_protocol == "javascript") 91 m_protocol = ""; 92 93 // document.domain starts as m_host, but can be set by the DOM. 94 m_domain = m_host; 95 96 // By default, only local SecurityOrigins can load local resources. 97 m_canLoadLocalResources = isLocal(); 98 if (m_canLoadLocalResources) { 99 // Directories should never be readable. 100 if (!url.hasPath() || url.path().endsWith("/")) 101 m_isUnique = true; 102 } 103 104 if (isDefaultPortForProtocol(m_port, m_protocol)) 105 m_port = 0; 106 } 107 108 SecurityOrigin::SecurityOrigin(const SecurityOrigin* other) 109 : m_sandboxFlags(other->m_sandboxFlags) 110 , m_protocol(other->m_protocol.threadsafeCopy()) 111 , m_host(other->m_host.threadsafeCopy()) 112 , m_domain(other->m_domain.threadsafeCopy()) 113 , m_port(other->m_port) 114 , m_isUnique(other->m_isUnique) 115 , m_universalAccess(other->m_universalAccess) 116 , m_domainWasSetInDOM(other->m_domainWasSetInDOM) 117 , m_canLoadLocalResources(other->m_canLoadLocalResources) 118 { 119 } 120 121 bool SecurityOrigin::isEmpty() const 122 { 123 return m_protocol.isEmpty(); 124 } 125 126 PassRefPtr<SecurityOrigin> SecurityOrigin::create(const KURL& url, SandboxFlags sandboxFlags) 127 { 128 if (!url.isValid()) 129 return adoptRef(new SecurityOrigin(KURL(), sandboxFlags)); 130 return adoptRef(new SecurityOrigin(url, sandboxFlags)); 131 } 132 133 PassRefPtr<SecurityOrigin> SecurityOrigin::createEmpty() 134 { 135 return create(KURL()); 136 } 137 138 PassRefPtr<SecurityOrigin> SecurityOrigin::threadsafeCopy() 139 { 140 return adoptRef(new SecurityOrigin(this)); 141 } 142 143 void SecurityOrigin::setDomainFromDOM(const String& newDomain) 144 { 145 m_domainWasSetInDOM = true; 146 m_domain = newDomain.lower(); 147 } 148 149 static HashSet<String>& schemesForbiddenFromDomainRelaxation() 150 { 151 DEFINE_STATIC_LOCAL(HashSet<String>, schemes, ()); 152 return schemes; 153 } 154 155 void SecurityOrigin::setDomainRelaxationForbiddenForURLScheme(bool forbidden, const String& scheme) 156 { 157 if (scheme.isEmpty()) 158 return; 159 160 if (forbidden) 161 schemesForbiddenFromDomainRelaxation().add(scheme); 162 else 163 schemesForbiddenFromDomainRelaxation().remove(scheme); 164 } 165 166 bool SecurityOrigin::isDomainRelaxationForbiddenForURLScheme(const String& scheme) 167 { 168 if (scheme.isEmpty()) 169 return false; 170 171 return schemesForbiddenFromDomainRelaxation().contains(scheme); 172 } 173 174 bool SecurityOrigin::canAccess(const SecurityOrigin* other) const 175 { 176 if (m_universalAccess) 177 return true; 178 179 if (isUnique() || other->isUnique()) 180 return false; 181 182 // Here are two cases where we should permit access: 183 // 184 // 1) Neither document has set document.domain. In this case, we insist 185 // that the scheme, host, and port of the URLs match. 186 // 187 // 2) Both documents have set document.domain. In this case, we insist 188 // that the documents have set document.domain to the same value and 189 // that the scheme of the URLs match. 190 // 191 // This matches the behavior of Firefox 2 and Internet Explorer 6. 192 // 193 // Internet Explorer 7 and Opera 9 are more strict in that they require 194 // the port numbers to match when both pages have document.domain set. 195 // 196 // FIXME: Evaluate whether we can tighten this policy to require matched 197 // port numbers. 198 // 199 // Opera 9 allows access when only one page has set document.domain, but 200 // this is a security vulnerability. 201 202 if (m_protocol == other->m_protocol) { 203 if (!m_domainWasSetInDOM && !other->m_domainWasSetInDOM) { 204 if (m_host == other->m_host && m_port == other->m_port) 205 return true; 206 } else if (m_domainWasSetInDOM && other->m_domainWasSetInDOM) { 207 if (m_domain == other->m_domain) 208 return true; 209 } 210 } 211 212 return false; 213 } 214 215 bool SecurityOrigin::canRequest(const KURL& url) const 216 { 217 if (m_universalAccess) 218 return true; 219 220 if (isUnique()) 221 return false; 222 223 RefPtr<SecurityOrigin> targetOrigin = SecurityOrigin::create(url); 224 if (targetOrigin->isUnique()) 225 return false; 226 227 // We call isSameSchemeHostPort here instead of canAccess because we want 228 // to ignore document.domain effects. 229 if (isSameSchemeHostPort(targetOrigin.get())) 230 return true; 231 232 if (OriginAccessWhiteList* list = originAccessMap().get(toString())) { 233 for (size_t i = 0; i < list->size(); ++i) { 234 if (list->at(i).matchesOrigin(*targetOrigin)) 235 return true; 236 } 237 } 238 239 return false; 240 } 241 242 bool SecurityOrigin::taintsCanvas(const KURL& url) const 243 { 244 if (canRequest(url)) 245 return false; 246 247 // This function exists because we treat data URLs as having a unique origin, 248 // contrary to the current (9/19/2009) draft of the HTML5 specification. 249 // We still want to let folks paint data URLs onto untainted canvases, so 250 // we special case data URLs below. If we change to match HTML5 w.r.t. 251 // data URL security, then we can remove this function in favor of 252 // !canRequest. 253 if (url.protocolIs("data")) 254 return false; 255 256 return true; 257 } 258 259 bool SecurityOrigin::canLoad(const KURL& url, const String& referrer, Document* document) 260 { 261 if (!shouldTreatURLAsLocal(url.string())) 262 return true; 263 264 // If we were provided a document, we let its local file policy dictate the result, 265 // otherwise we allow local loads only if the supplied referrer is also local. 266 if (document) 267 return document->securityOrigin()->canLoadLocalResources(); 268 if (!referrer.isEmpty()) 269 return shouldTreatURLAsLocal(referrer); 270 return false; 271 } 272 273 void SecurityOrigin::grantLoadLocalResources() 274 { 275 // This function exists only to support backwards compatibility with older 276 // versions of WebKit. Granting privileges to some, but not all, documents 277 // in a SecurityOrigin is a security hazard because the documents without 278 // the privilege can obtain the privilege by injecting script into the 279 // documents that have been granted the privilege. 280 ASSERT(allowSubstituteDataAccessToLocal()); 281 m_canLoadLocalResources = true; 282 } 283 284 void SecurityOrigin::grantUniversalAccess() 285 { 286 m_universalAccess = true; 287 } 288 289 bool SecurityOrigin::isLocal() const 290 { 291 return shouldTreatURLSchemeAsLocal(m_protocol); 292 } 293 294 bool SecurityOrigin::isSecureTransitionTo(const KURL& url) const 295 { 296 // New window created by the application 297 if (isEmpty()) 298 return true; 299 300 RefPtr<SecurityOrigin> other = SecurityOrigin::create(url); 301 return canAccess(other.get()); 302 } 303 304 String SecurityOrigin::toString() const 305 { 306 if (isEmpty()) 307 return "null"; 308 309 if (isUnique()) 310 return "null"; 311 312 if (m_protocol == "file") 313 return String("file://"); 314 315 Vector<UChar> result; 316 result.reserveInitialCapacity(m_protocol.length() + m_host.length() + 10); 317 append(result, m_protocol); 318 append(result, "://"); 319 append(result, m_host); 320 321 if (m_port) { 322 append(result, ":"); 323 append(result, String::number(m_port)); 324 } 325 326 return String::adopt(result); 327 } 328 329 PassRefPtr<SecurityOrigin> SecurityOrigin::createFromString(const String& originString) 330 { 331 return SecurityOrigin::create(KURL(KURL(), originString)); 332 } 333 334 static const char SeparatorCharacter = '_'; 335 336 PassRefPtr<SecurityOrigin> SecurityOrigin::createFromDatabaseIdentifier(const String& databaseIdentifier) 337 { 338 // Make sure there's a first separator 339 int separator1 = databaseIdentifier.find(SeparatorCharacter); 340 if (separator1 == -1) 341 return create(KURL()); 342 343 // Make sure there's a second separator 344 int separator2 = databaseIdentifier.reverseFind(SeparatorCharacter); 345 if (separator2 == -1) 346 return create(KURL()); 347 348 // Ensure there were at least 2 separator characters. Some hostnames on intranets have 349 // underscores in them, so we'll assume that any additional underscores are part of the host. 350 if (separator1 == separator2) 351 return create(KURL()); 352 353 // Make sure the port section is a valid port number or doesn't exist 354 bool portOkay; 355 int port = databaseIdentifier.right(databaseIdentifier.length() - separator2 - 1).toInt(&portOkay); 356 bool portAbsent = (separator2 == static_cast<int>(databaseIdentifier.length()) - 1); 357 if (!(portOkay || portAbsent)) 358 return create(KURL()); 359 360 if (port < 0 || port > 65535) 361 return create(KURL()); 362 363 // Split out the 3 sections of data 364 String protocol = databaseIdentifier.substring(0, separator1); 365 String host = databaseIdentifier.substring(separator1 + 1, separator2 - separator1 - 1); 366 return create(KURL(KURL(), protocol + "://" + host + ":" + String::number(port))); 367 } 368 369 String SecurityOrigin::databaseIdentifier() const 370 { 371 DEFINE_STATIC_LOCAL(String, separatorString, (&SeparatorCharacter, 1)); 372 return m_protocol + separatorString + m_host + separatorString + String::number(m_port); 373 } 374 375 bool SecurityOrigin::equal(const SecurityOrigin* other) const 376 { 377 if (other == this) 378 return true; 379 380 if (!isSameSchemeHostPort(other)) 381 return false; 382 383 if (m_domainWasSetInDOM != other->m_domainWasSetInDOM) 384 return false; 385 386 if (m_domainWasSetInDOM && m_domain != other->m_domain) 387 return false; 388 389 return true; 390 } 391 392 bool SecurityOrigin::isSameSchemeHostPort(const SecurityOrigin* other) const 393 { 394 if (m_host != other->m_host) 395 return false; 396 397 if (m_protocol != other->m_protocol) 398 return false; 399 400 if (m_port != other->m_port) 401 return false; 402 403 return true; 404 } 405 406 void SecurityOrigin::registerURLSchemeAsLocal(const String& scheme) 407 { 408 localSchemes().add(scheme); 409 } 410 411 void SecurityOrigin::removeURLSchemeRegisteredAsLocal(const String& scheme) 412 { 413 if (scheme == "file") 414 return; 415 #if PLATFORM(MAC) 416 if (scheme == "applewebdata") 417 return; 418 #endif 419 localSchemes().remove(scheme); 420 } 421 422 const URLSchemesMap& SecurityOrigin::localURLSchemes() 423 { 424 return localSchemes(); 425 } 426 427 bool SecurityOrigin::shouldTreatURLAsLocal(const String& url) 428 { 429 // This avoids an allocation of another String and the HashSet contains() 430 // call for the file: and http: schemes. 431 if (url.length() >= 5) { 432 const UChar* s = url.characters(); 433 if (s[0] == 'h' && s[1] == 't' && s[2] == 't' && s[3] == 'p' && s[4] == ':') 434 return false; 435 if (s[0] == 'f' && s[1] == 'i' && s[2] == 'l' && s[3] == 'e' && s[4] == ':') 436 return true; 437 } 438 439 int loc = url.find(':'); 440 if (loc == -1) 441 return false; 442 443 String scheme = url.left(loc); 444 return localSchemes().contains(scheme); 445 } 446 447 bool SecurityOrigin::shouldTreatURLSchemeAsLocal(const String& scheme) 448 { 449 // This avoids an allocation of another String and the HashSet contains() 450 // call for the file: and http: schemes. 451 if (scheme.length() == 4) { 452 const UChar* s = scheme.characters(); 453 if (s[0] == 'h' && s[1] == 't' && s[2] == 't' && s[3] == 'p') 454 return false; 455 if (s[0] == 'f' && s[1] == 'i' && s[2] == 'l' && s[3] == 'e') 456 return true; 457 } 458 459 if (scheme.isEmpty()) 460 return false; 461 462 return localSchemes().contains(scheme); 463 } 464 465 void SecurityOrigin::registerURLSchemeAsNoAccess(const String& scheme) 466 { 467 schemesWithUniqueOrigins().add(scheme); 468 } 469 470 bool SecurityOrigin::shouldTreatURLSchemeAsNoAccess(const String& scheme) 471 { 472 return schemesWithUniqueOrigins().contains(scheme); 473 } 474 475 bool SecurityOrigin::shouldHideReferrer(const KURL& url, const String& referrer) 476 { 477 bool referrerIsSecureURL = protocolIs(referrer, "https"); 478 bool referrerIsWebURL = referrerIsSecureURL || protocolIs(referrer, "http"); 479 480 if (!referrerIsWebURL) 481 return true; 482 483 if (!referrerIsSecureURL) 484 return false; 485 486 bool URLIsSecureURL = url.protocolIs("https"); 487 488 return !URLIsSecureURL; 489 } 490 491 void SecurityOrigin::setLocalLoadPolicy(LocalLoadPolicy policy) 492 { 493 localLoadPolicy = policy; 494 } 495 496 bool SecurityOrigin::restrictAccessToLocal() 497 { 498 return localLoadPolicy != SecurityOrigin::AllowLocalLoadsForAll; 499 } 500 501 bool SecurityOrigin::allowSubstituteDataAccessToLocal() 502 { 503 return localLoadPolicy != SecurityOrigin::AllowLocalLoadsForLocalOnly; 504 } 505 506 void SecurityOrigin::whiteListAccessFromOrigin(const SecurityOrigin& sourceOrigin, const String& destinationProtocol, const String& destinationDomains, bool allowDestinationSubdomains) 507 { 508 ASSERT(isMainThread()); 509 ASSERT(!sourceOrigin.isEmpty()); 510 if (sourceOrigin.isEmpty()) 511 return; 512 513 String sourceString = sourceOrigin.toString(); 514 OriginAccessWhiteList* list = originAccessMap().get(sourceString); 515 if (!list) { 516 list = new OriginAccessWhiteList; 517 originAccessMap().set(sourceString, list); 518 } 519 list->append(OriginAccessEntry(destinationProtocol, destinationDomains, allowDestinationSubdomains ? OriginAccessEntry::AllowSubdomains : OriginAccessEntry::DisallowSubdomains)); 520 } 521 522 void SecurityOrigin::resetOriginAccessWhiteLists() 523 { 524 ASSERT(isMainThread()); 525 OriginAccessMap& map = originAccessMap(); 526 deleteAllValues(map); 527 map.clear(); 528 } 529 530 } // namespace WebCore 531