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