| OLD | NEW |
| (Empty) |
| 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::ext
ractInnerURL(url) : url; | |
| 98 | |
| 99 // FIXME: Check whether innerURL is valid. | |
| 100 | |
| 101 // For edge case URLs that were probably misparsed, make sure that the origi
n 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 calle
d. | |
| 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_needsDatabaseIdentifierQui
rkForFiles) | |
| 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.proto
col())) | |
| 217 return true; | |
| 218 | |
| 219 // URLs that wrap inner URLs are secure if those inner URLs are secure. | |
| 220 if (shouldUseInnerURL(url) && SchemeRegistry::shouldTreatURLSchemeAsSecure(e
xtractInnerURL(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) con
st | |
| 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 remov
e 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 i
t, so it can be | |
| 340 // local or remote depending on what is nested. Currently we just check if we ar
e 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 th
em. | |
| 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("fe
ed:https:", false) | |
| 352 || urlString.startsWith("feeds:http:", false) || urlString.startsWith("f
eeds:https:", false) | |
| 353 || urlString.startsWith("feedsearch:http:", false) || urlString.startsWi
th("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::isAccessToURLWhiteListe
d(this, url); | |
| 371 | |
| 372 if (SchemeRegistry::shouldTreatURLSchemeAsLocal(protocol)) | |
| 373 return canLoadLocalResources() || SecurityPolicy::isAccessToURLWhiteList
ed(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& origin
String) | |
| 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(po
rt) + "/")); | |
| 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 | |
| OLD | NEW |