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 "platform/weborigin/SecurityOrigin.h" 31 32 #include "platform/weborigin/KURL.h" 33 #include "platform/weborigin/KnownPorts.h" 34 #include "platform/weborigin/SchemeRegistry.h" 35 #include "platform/weborigin/SecurityOriginCache.h" 36 #include "platform/weborigin/SecurityPolicy.h" 37 #include "url/url_canon_ip.h" 38 #include "wtf/HexNumber.h" 39 #include "wtf/MainThread.h" 40 #include "wtf/StdLibExtras.h" 41 #include "wtf/text/StringBuilder.h" 42 43 namespace blink { 44 45 const int InvalidPort = 0; 46 const int MaxAllowedPort = 65535; 47 48 static SecurityOriginCache* s_originCache = 0; 49 50 static bool schemeRequiresAuthority(const KURL& url) 51 { 52 // We expect URLs with these schemes to have authority components. If the 53 // URL lacks an authority component, we get concerned and mark the origin 54 // as unique. 55 return url.protocolIsInHTTPFamily() || url.protocolIs("ftp"); 56 } 57 58 static SecurityOrigin* cachedOrigin(const KURL& url) 59 { 60 if (s_originCache) 61 return s_originCache->cachedOrigin(url); 62 return 0; 63 } 64 65 bool SecurityOrigin::shouldUseInnerURL(const KURL& url) 66 { 67 // FIXME: Blob URLs don't have inner URLs. Their form is "blob:<inner-origin>/<UUID>", so treating the part after "blob:" as a URL is incorrect. 68 if (url.protocolIs("blob")) 69 return true; 70 if (url.protocolIs("filesystem")) 71 return true; 72 return false; 73 } 74 75 // In general, extracting the inner URL varies by scheme. It just so happens 76 // that all the URL schemes we currently support that use inner URLs for their 77 // security origin can be parsed using this algorithm. 78 KURL SecurityOrigin::extractInnerURL(const KURL& url) 79 { 80 if (url.innerURL()) 81 return *url.innerURL(); 82 // FIXME: Update this callsite to use the innerURL member function when 83 // we finish implementing it. 84 return KURL(ParsedURLString, decodeURLEscapeSequences(url.path())); 85 } 86 87 void SecurityOrigin::setCache(SecurityOriginCache* originCache) 88 { 89 s_originCache = originCache; 90 } 91 92 static bool shouldTreatAsUniqueOrigin(const KURL& url) 93 { 94 if (!url.isValid()) 95 return true; 96 97 // FIXME: Do we need to unwrap the URL further? 98 KURL innerURL = SecurityOrigin::shouldUseInnerURL(url) ? SecurityOrigin::extractInnerURL(url) : url; 99 100 // FIXME: Check whether innerURL is valid. 101 102 // For edge case URLs that were probably misparsed, make sure that the origin is unique. 103 // FIXME: Do we really need to do this? This looks to be a hack around a 104 // security bug in CFNetwork that might have been fixed. 105 if (schemeRequiresAuthority(innerURL) && innerURL.host().isEmpty()) 106 return true; 107 108 // SchemeRegistry needs a lower case protocol because it uses HashMaps 109 // that assume the scheme has already been canonicalized. 110 String protocol = innerURL.protocol().lower(); 111 112 if (SchemeRegistry::shouldTreatURLSchemeAsNoAccess(protocol)) 113 return true; 114 115 // This is the common case. 116 return false; 117 } 118 119 SecurityOrigin::SecurityOrigin(const KURL& url) 120 : m_protocol(url.protocol().isNull() ? "" : url.protocol().lower()) 121 , m_host(url.host().isNull() ? "" : url.host().lower()) 122 , m_port(url.port()) 123 , m_isUnique(false) 124 , m_universalAccess(false) 125 , m_domainWasSetInDOM(false) 126 , m_enforceFilePathSeparation(false) 127 , m_needsDatabaseIdentifierQuirkForFiles(false) 128 { 129 // document.domain starts as m_host, but can be set by the DOM. 130 m_domain = m_host; 131 132 if (isDefaultPortForProtocol(m_port, m_protocol)) 133 m_port = InvalidPort; 134 135 // By default, only local SecurityOrigins can load local resources. 136 m_canLoadLocalResources = isLocal(); 137 138 if (m_canLoadLocalResources) 139 m_filePath = url.path(); // In case enforceFilePathSeparation() is called. 140 } 141 142 SecurityOrigin::SecurityOrigin() 143 : m_protocol("") 144 , m_host("") 145 , m_domain("") 146 , m_port(InvalidPort) 147 , m_isUnique(true) 148 , m_universalAccess(false) 149 , m_domainWasSetInDOM(false) 150 , m_canLoadLocalResources(false) 151 , m_enforceFilePathSeparation(false) 152 , m_needsDatabaseIdentifierQuirkForFiles(false) 153 { 154 } 155 156 SecurityOrigin::SecurityOrigin(const SecurityOrigin* other) 157 : m_protocol(other->m_protocol.isolatedCopy()) 158 , m_host(other->m_host.isolatedCopy()) 159 , m_domain(other->m_domain.isolatedCopy()) 160 , m_filePath(other->m_filePath.isolatedCopy()) 161 , m_port(other->m_port) 162 , m_isUnique(other->m_isUnique) 163 , m_universalAccess(other->m_universalAccess) 164 , m_domainWasSetInDOM(other->m_domainWasSetInDOM) 165 , m_canLoadLocalResources(other->m_canLoadLocalResources) 166 , m_enforceFilePathSeparation(other->m_enforceFilePathSeparation) 167 , m_needsDatabaseIdentifierQuirkForFiles(other->m_needsDatabaseIdentifierQuirkForFiles) 168 { 169 } 170 171 PassRefPtr<SecurityOrigin> SecurityOrigin::create(const KURL& url) 172 { 173 if (RefPtr<SecurityOrigin> origin = cachedOrigin(url)) 174 return origin.release(); 175 176 if (shouldTreatAsUniqueOrigin(url)) { 177 RefPtr<SecurityOrigin> origin = adoptRef(new SecurityOrigin()); 178 179 if (url.protocolIs("file")) { 180 // Unfortunately, we can't represent all unique origins exactly 181 // the same way because we need to produce a quirky database 182 // identifier for file URLs due to persistent storage in some 183 // embedders of WebKit. 184 origin->m_needsDatabaseIdentifierQuirkForFiles = true; 185 } 186 187 return origin.release(); 188 } 189 190 if (shouldUseInnerURL(url)) 191 return adoptRef(new SecurityOrigin(extractInnerURL(url))); 192 193 return adoptRef(new SecurityOrigin(url)); 194 } 195 196 PassRefPtr<SecurityOrigin> SecurityOrigin::createUnique() 197 { 198 RefPtr<SecurityOrigin> origin = adoptRef(new SecurityOrigin()); 199 ASSERT(origin->isUnique()); 200 return origin.release(); 201 } 202 203 PassRefPtr<SecurityOrigin> SecurityOrigin::isolatedCopy() const 204 { 205 return adoptRef(new SecurityOrigin(this)); 206 } 207 208 void SecurityOrigin::setDomainFromDOM(const String& newDomain) 209 { 210 m_domainWasSetInDOM = true; 211 m_domain = newDomain.lower(); 212 } 213 214 bool SecurityOrigin::isSecure(const KURL& url) 215 { 216 // Invalid URLs are secure, as are URLs which have a secure protocol. 217 if (!url.isValid() || SchemeRegistry::shouldTreatURLSchemeAsSecure(url.protocol())) 218 return true; 219 220 // URLs that wrap inner URLs are secure if those inner URLs are secure. 221 if (shouldUseInnerURL(url) && SchemeRegistry::shouldTreatURLSchemeAsSecure(extractInnerURL(url).protocol())) 222 return true; 223 224 return false; 225 } 226 227 bool SecurityOrigin::canAccess(const SecurityOrigin* other) const 228 { 229 if (m_universalAccess) 230 return true; 231 232 if (this == other) 233 return true; 234 235 if (isUnique() || other->isUnique()) 236 return false; 237 238 // Here are two cases where we should permit access: 239 // 240 // 1) Neither document has set document.domain. In this case, we insist 241 // that the scheme, host, and port of the URLs match. 242 // 243 // 2) Both documents have set document.domain. In this case, we insist 244 // that the documents have set document.domain to the same value and 245 // that the scheme of the URLs match. 246 // 247 // This matches the behavior of Firefox 2 and Internet Explorer 6. 248 // 249 // Internet Explorer 7 and Opera 9 are more strict in that they require 250 // the port numbers to match when both pages have document.domain set. 251 // 252 // FIXME: Evaluate whether we can tighten this policy to require matched 253 // port numbers. 254 // 255 // Opera 9 allows access when only one page has set document.domain, but 256 // this is a security vulnerability. 257 258 bool canAccess = false; 259 if (m_protocol == other->m_protocol) { 260 if (!m_domainWasSetInDOM && !other->m_domainWasSetInDOM) { 261 if (m_host == other->m_host && m_port == other->m_port) 262 canAccess = true; 263 } else if (m_domainWasSetInDOM && other->m_domainWasSetInDOM) { 264 if (m_domain == other->m_domain) 265 canAccess = true; 266 } 267 } 268 269 if (canAccess && isLocal()) 270 canAccess = passesFileCheck(other); 271 272 return canAccess; 273 } 274 275 bool SecurityOrigin::passesFileCheck(const SecurityOrigin* other) const 276 { 277 ASSERT(isLocal() && other->isLocal()); 278 279 if (!m_enforceFilePathSeparation && !other->m_enforceFilePathSeparation) 280 return true; 281 282 return (m_filePath == other->m_filePath); 283 } 284 285 bool SecurityOrigin::canRequest(const KURL& url) const 286 { 287 if (m_universalAccess) 288 return true; 289 290 if (cachedOrigin(url) == this) 291 return true; 292 293 if (isUnique()) 294 return false; 295 296 RefPtr<SecurityOrigin> targetOrigin = SecurityOrigin::create(url); 297 298 if (targetOrigin->isUnique()) 299 return false; 300 301 // We call isSameSchemeHostPort here instead of canAccess because we want 302 // to ignore document.domain effects. 303 if (isSameSchemeHostPort(targetOrigin.get())) 304 return true; 305 306 if (SecurityPolicy::isAccessWhiteListed(this, targetOrigin.get())) 307 return true; 308 309 return false; 310 } 311 312 bool SecurityOrigin::taintsCanvas(const KURL& url) const 313 { 314 if (canRequest(url)) 315 return false; 316 317 // This function exists because we treat data URLs as having a unique origin, 318 // contrary to the current (9/19/2009) draft of the HTML5 specification. 319 // We still want to let folks paint data URLs onto untainted canvases, so 320 // we special case data URLs below. If we change to match HTML5 w.r.t. 321 // data URL security, then we can remove this function in favor of 322 // !canRequest. 323 if (url.protocolIsData()) 324 return false; 325 326 return true; 327 } 328 329 bool SecurityOrigin::canReceiveDragData(const SecurityOrigin* dragInitiator) const 330 { 331 if (this == dragInitiator) 332 return true; 333 334 return canAccess(dragInitiator); 335 } 336 337 // This is a hack to allow keep navigation to http/https feeds working. To remove this 338 // we need to introduce new API akin to registerURLSchemeAsLocal, that registers a 339 // protocols navigation policy. 340 // feed(|s|search): is considered a 'nesting' scheme by embedders that support it, so it can be 341 // local or remote depending on what is nested. Currently we just check if we are nesting 342 // http or https, otherwise we ignore the nesting for the purpose of a security check. We need 343 // a facility for registering nesting schemes, and some generalized logic for them. 344 // This function should be removed as an outcome of https://bugs.webkit.org/show_bug.cgi?id=69196 345 static bool isFeedWithNestedProtocolInHTTPFamily(const KURL& url) 346 { 347 const String& urlString = url.string(); 348 if (!urlString.startsWith("feed", false)) 349 return false; 350 351 return urlString.startsWith("feed://", false) 352 || urlString.startsWith("feed:http:", false) || urlString.startsWith("feed:https:", false) 353 || urlString.startsWith("feeds:http:", false) || urlString.startsWith("feeds:https:", false) 354 || urlString.startsWith("feedsearch:http:", false) || urlString.startsWith("feedsearch:https:", false); 355 } 356 357 bool SecurityOrigin::canDisplay(const KURL& url) const 358 { 359 if (m_universalAccess) 360 return true; 361 362 String protocol = url.protocol().lower(); 363 364 if (isFeedWithNestedProtocolInHTTPFamily(url)) 365 return true; 366 367 if (SchemeRegistry::canDisplayOnlyIfCanRequest(protocol)) 368 return canRequest(url); 369 370 if (SchemeRegistry::shouldTreatURLSchemeAsDisplayIsolated(protocol)) 371 return m_protocol == protocol || SecurityPolicy::isAccessToURLWhiteListed(this, url); 372 373 if (SchemeRegistry::shouldTreatURLSchemeAsLocal(protocol)) 374 return canLoadLocalResources() || SecurityPolicy::isAccessToURLWhiteListed(this, url); 375 376 return true; 377 } 378 379 bool SecurityOrigin::canAccessFeatureRequiringSecureOrigin(String& errorMessage) const 380 { 381 ASSERT(m_protocol != "data"); 382 if (SchemeRegistry::shouldTreatURLSchemeAsSecure(m_protocol) || isLocal() || isLocalhost()) 383 return true; 384 385 errorMessage = "Only secure origins are allowed. http://goo.gl/lq4gCo"; 386 return false; 387 } 388 389 SecurityOrigin::Policy SecurityOrigin::canShowNotifications() const 390 { 391 if (m_universalAccess) 392 return AlwaysAllow; 393 if (isUnique()) 394 return AlwaysDeny; 395 return Ask; 396 } 397 398 void SecurityOrigin::grantLoadLocalResources() 399 { 400 // Granting privileges to some, but not all, documents in a SecurityOrigin 401 // is a security hazard because the documents without the privilege can 402 // obtain the privilege by injecting script into the documents that have 403 // been granted the privilege. 404 m_canLoadLocalResources = true; 405 } 406 407 void SecurityOrigin::grantUniversalAccess() 408 { 409 m_universalAccess = true; 410 } 411 412 void SecurityOrigin::enforceFilePathSeparation() 413 { 414 ASSERT(isLocal()); 415 m_enforceFilePathSeparation = true; 416 } 417 418 bool SecurityOrigin::isLocal() const 419 { 420 return SchemeRegistry::shouldTreatURLSchemeAsLocal(m_protocol); 421 } 422 423 bool SecurityOrigin::isLocalhost() const 424 { 425 if (m_host == "localhost") 426 return true; 427 428 if (m_host == "[::1]") 429 return true; 430 431 // Test if m_host matches 127.0.0.1/8 432 ASSERT(m_host.containsOnlyASCII()); 433 CString hostAscii = m_host.ascii(); 434 Vector<uint8, 4> ipNumber; 435 ipNumber.resize(4); 436 437 int numComponents; 438 url::Component hostComponent(0, hostAscii.length()); 439 url::CanonHostInfo::Family family = url::IPv4AddressToNumber( 440 hostAscii.data(), hostComponent, &(ipNumber)[0], &numComponents); 441 if (family != url::CanonHostInfo::IPV4) 442 return false; 443 return ipNumber[0] == 127; 444 } 445 446 String SecurityOrigin::toString() const 447 { 448 if (isUnique()) 449 return "null"; 450 if (m_protocol == "file" && m_enforceFilePathSeparation) 451 return "null"; 452 return toRawString(); 453 } 454 455 AtomicString SecurityOrigin::toAtomicString() const 456 { 457 if (isUnique()) 458 return AtomicString("null", AtomicString::ConstructFromLiteral); 459 if (m_protocol == "file" && m_enforceFilePathSeparation) 460 return AtomicString("null", AtomicString::ConstructFromLiteral); 461 return toRawAtomicString(); 462 } 463 464 String SecurityOrigin::toRawString() const 465 { 466 if (m_protocol == "file") 467 return "file://"; 468 469 StringBuilder result; 470 buildRawString(result); 471 return result.toString(); 472 } 473 474 AtomicString SecurityOrigin::toRawAtomicString() const 475 { 476 if (m_protocol == "file") 477 return AtomicString("file://", AtomicString::ConstructFromLiteral); 478 479 StringBuilder result; 480 buildRawString(result); 481 return result.toAtomicString(); 482 } 483 484 inline void SecurityOrigin::buildRawString(StringBuilder& builder) const 485 { 486 builder.reserveCapacity(m_protocol.length() + m_host.length() + 10); 487 builder.append(m_protocol); 488 builder.appendLiteral("://"); 489 builder.append(m_host); 490 491 if (m_port) { 492 builder.append(':'); 493 builder.appendNumber(m_port); 494 } 495 } 496 497 PassRefPtr<SecurityOrigin> SecurityOrigin::createFromString(const String& originString) 498 { 499 return SecurityOrigin::create(KURL(KURL(), originString)); 500 } 501 502 PassRefPtr<SecurityOrigin> SecurityOrigin::create(const String& protocol, const String& host, int port) 503 { 504 if (port < 0 || port > MaxAllowedPort) 505 return createUnique(); 506 String decodedHost = decodeURLEscapeSequences(host); 507 return create(KURL(KURL(), protocol + "://" + host + ":" + String::number(port) + "/")); 508 } 509 510 bool SecurityOrigin::isSameSchemeHostPort(const SecurityOrigin* other) const 511 { 512 if (m_host != other->m_host) 513 return false; 514 515 if (m_protocol != other->m_protocol) 516 return false; 517 518 if (m_port != other->m_port) 519 return false; 520 521 if (isLocal() && !passesFileCheck(other)) 522 return false; 523 524 return true; 525 } 526 527 const String& SecurityOrigin::urlWithUniqueSecurityOrigin() 528 { 529 ASSERT(isMainThread()); 530 DEFINE_STATIC_LOCAL(const String, uniqueSecurityOriginURL, ("data:,")); 531 return uniqueSecurityOriginURL; 532 } 533 534 } // namespace blink 535