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 "wtf/HexNumber.h" 38#include "wtf/MainThread.h" 39#include "wtf/StdLibExtras.h" 40#include "wtf/text/StringBuilder.h" 41 42namespace WebCore { 43 44const int InvalidPort = 0; 45const int MaxAllowedPort = 65535; 46 47static SecurityOriginCache* s_originCache = 0; 48 49static 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 57static SecurityOrigin* cachedOrigin(const KURL& url) 58{ 59 if (s_originCache) 60 return s_originCache->cachedOrigin(url); 61 return 0; 62} 63 64bool 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. 77KURL 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 86void SecurityOrigin::setCache(SecurityOriginCache* originCache) 87{ 88 s_originCache = originCache; 89} 90 91static 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 118SecurityOrigin::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 141SecurityOrigin::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 155SecurityOrigin::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 170PassRefPtr<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 195PassRefPtr<SecurityOrigin> SecurityOrigin::createUnique() 196{ 197 RefPtr<SecurityOrigin> origin = adoptRef(new SecurityOrigin()); 198 ASSERT(origin->isUnique()); 199 return origin.release(); 200} 201 202PassRefPtr<SecurityOrigin> SecurityOrigin::isolatedCopy() const 203{ 204 return adoptRef(new SecurityOrigin(this)); 205} 206 207void SecurityOrigin::setDomainFromDOM(const String& newDomain) 208{ 209 m_domainWasSetInDOM = true; 210 m_domain = newDomain.lower(); 211} 212 213bool 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 226bool 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 274bool 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 284bool 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 311bool 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 328bool 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 344static 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 356bool 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 378SecurityOrigin::Policy SecurityOrigin::canShowNotifications() const 379{ 380 if (m_universalAccess) 381 return AlwaysAllow; 382 if (isUnique()) 383 return AlwaysDeny; 384 return Ask; 385} 386 387void 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 396void SecurityOrigin::grantUniversalAccess() 397{ 398 m_universalAccess = true; 399} 400 401void SecurityOrigin::enforceFilePathSeparation() 402{ 403 ASSERT(isLocal()); 404 m_enforceFilePathSeparation = true; 405} 406 407bool SecurityOrigin::isLocal() const 408{ 409 return SchemeRegistry::shouldTreatURLSchemeAsLocal(m_protocol); 410} 411 412String 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 421String 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 440PassRefPtr<SecurityOrigin> SecurityOrigin::createFromString(const String& originString) 441{ 442 return SecurityOrigin::create(KURL(KURL(), originString)); 443} 444 445PassRefPtr<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 453bool 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 470bool 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 487const String& SecurityOrigin::urlWithUniqueSecurityOrigin() 488{ 489 ASSERT(isMainThread()); 490 DEFINE_STATIC_LOCAL(const String, uniqueSecurityOriginURL, ("data:,")); 491 return uniqueSecurityOriginURL; 492} 493 494} // namespace WebCore 495