diff options
Diffstat (limited to 'WebCore/page/SecurityOrigin.cpp')
-rw-r--r-- | WebCore/page/SecurityOrigin.cpp | 226 |
1 files changed, 197 insertions, 29 deletions
diff --git a/WebCore/page/SecurityOrigin.cpp b/WebCore/page/SecurityOrigin.cpp index fe6efbd..265c643 100644 --- a/WebCore/page/SecurityOrigin.cpp +++ b/WebCore/page/SecurityOrigin.cpp @@ -29,7 +29,6 @@ #include "config.h" #include "SecurityOrigin.h" -#include "CString.h" #include "Document.h" #include "KURL.h" #include "OriginAccessEntry.h" @@ -65,6 +64,19 @@ static URLSchemesMap& localSchemes() return localSchemes; } +static URLSchemesMap& secureSchemes() +{ + DEFINE_STATIC_LOCAL(URLSchemesMap, secureSchemes, ()); + + if (secureSchemes.isEmpty()) { + secureSchemes.add("https"); + secureSchemes.add("about"); + secureSchemes.add("data"); + } + + return secureSchemes; +} + static URLSchemesMap& schemesWithUniqueOrigins() { DEFINE_STATIC_LOCAL(URLSchemesMap, schemesWithUniqueOrigins, ()); @@ -85,6 +97,7 @@ SecurityOrigin::SecurityOrigin(const KURL& url, SandboxFlags sandboxFlags) , m_isUnique(isSandboxed(SandboxOrigin) || shouldTreatURLSchemeAsNoAccess(m_protocol)) , m_universalAccess(false) , m_domainWasSetInDOM(false) + , m_enforceFilePathSeparation(false) { // These protocols do not create security origins; the owner frame provides the origin if (m_protocol == "about" || m_protocol == "javascript") @@ -99,6 +112,8 @@ SecurityOrigin::SecurityOrigin(const KURL& url, SandboxFlags sandboxFlags) // Directories should never be readable. if (!url.hasPath() || url.path().endsWith("/")) m_isUnique = true; + // Store the path in case we are doing per-file origin checking. + m_filePath = url.path(); } if (isDefaultPortForProtocol(m_port, m_protocol)) @@ -109,12 +124,15 @@ SecurityOrigin::SecurityOrigin(const SecurityOrigin* other) : m_sandboxFlags(other->m_sandboxFlags) , m_protocol(other->m_protocol.threadsafeCopy()) , m_host(other->m_host.threadsafeCopy()) + , m_encodedHost(other->m_encodedHost.threadsafeCopy()) , m_domain(other->m_domain.threadsafeCopy()) + , m_filePath(other->m_filePath.threadsafeCopy()) , m_port(other->m_port) , m_isUnique(other->m_isUnique) , m_universalAccess(other->m_universalAccess) , m_domainWasSetInDOM(other->m_domainWasSetInDOM) , m_canLoadLocalResources(other->m_canLoadLocalResources) + , m_enforceFilePathSeparation(other->m_enforceFilePathSeparation) { } @@ -172,7 +190,7 @@ bool SecurityOrigin::isDomainRelaxationForbiddenForURLScheme(const String& schem } bool SecurityOrigin::canAccess(const SecurityOrigin* other) const -{ +{ if (m_universalAccess) return true; @@ -199,17 +217,31 @@ bool SecurityOrigin::canAccess(const SecurityOrigin* other) const // Opera 9 allows access when only one page has set document.domain, but // this is a security vulnerability. + bool canAccess = false; if (m_protocol == other->m_protocol) { if (!m_domainWasSetInDOM && !other->m_domainWasSetInDOM) { if (m_host == other->m_host && m_port == other->m_port) - return true; + canAccess = true; } else if (m_domainWasSetInDOM && other->m_domainWasSetInDOM) { if (m_domain == other->m_domain) - return true; + canAccess = true; } } - - return false; + + if (canAccess && isLocal()) + canAccess = passesFileCheck(other); + + return canAccess; +} + +bool SecurityOrigin::passesFileCheck(const SecurityOrigin* other) const +{ + ASSERT(isLocal() && other->isLocal()); + + if (!m_enforceFilePathSeparation && !other->m_enforceFilePathSeparation) + return true; + + return (m_filePath == other->m_filePath); } bool SecurityOrigin::canRequest(const KURL& url) const @@ -229,12 +261,8 @@ bool SecurityOrigin::canRequest(const KURL& url) const if (isSameSchemeHostPort(targetOrigin.get())) return true; - if (OriginAccessWhiteList* list = originAccessMap().get(toString())) { - for (size_t i = 0; i < list->size(); ++i) { - if (list->at(i).matchesOrigin(*targetOrigin)) - return true; - } - } + if (isAccessWhiteListed(targetOrigin.get())) + return true; return false; } @@ -256,15 +284,32 @@ bool SecurityOrigin::taintsCanvas(const KURL& url) const return true; } +bool SecurityOrigin::isAccessWhiteListed(const SecurityOrigin* targetOrigin) const +{ + if (OriginAccessWhiteList* list = originAccessMap().get(toString())) { + for (size_t i = 0; i < list->size(); ++i) { + if (list->at(i).matchesOrigin(*targetOrigin)) + return true; + } + } + return false; +} + bool SecurityOrigin::canLoad(const KURL& url, const String& referrer, Document* document) { if (!shouldTreatURLAsLocal(url.string())) return true; - // If we were provided a document, we let its local file policy dictate the result, - // otherwise we allow local loads only if the supplied referrer is also local. - if (document) - return document->securityOrigin()->canLoadLocalResources(); + // If we were provided a document, we first check if the access has been white listed. + // Then we let its local file police dictate the result. + // Otherwise we allow local loads only if the supplied referrer is also local. + if (document) { + SecurityOrigin* documentOrigin = document->securityOrigin(); + RefPtr<SecurityOrigin> targetOrigin = SecurityOrigin::create(url); + if (documentOrigin->isAccessWhiteListed(targetOrigin.get())) + return true; + return documentOrigin->canLoadLocalResources(); + } if (!referrer.isEmpty()) return shouldTreatURLAsLocal(referrer); return false; @@ -286,9 +331,10 @@ void SecurityOrigin::grantUniversalAccess() m_universalAccess = true; } -void SecurityOrigin::makeUnique() +void SecurityOrigin::enforceFilePathSeparation() { - m_isUnique = true; + ASSERT(isLocal()); + m_enforceFilePathSeparation = true; } bool SecurityOrigin::isLocal() const @@ -314,8 +360,11 @@ String SecurityOrigin::toString() const if (isUnique()) return "null"; - if (m_protocol == "file") - return String("file://"); + if (m_protocol == "file") { + if (m_enforceFilePathSeparation) + return "null"; + return "file://"; + } Vector<UChar> result; result.reserveInitialCapacity(m_protocol.length() + m_host.length() + 10); @@ -368,13 +417,92 @@ PassRefPtr<SecurityOrigin> SecurityOrigin::createFromDatabaseIdentifier(const St // Split out the 3 sections of data String protocol = databaseIdentifier.substring(0, separator1); String host = databaseIdentifier.substring(separator1 + 1, separator2 - separator1 - 1); + + host = decodeURLEscapeSequences(host); return create(KURL(KURL(), protocol + "://" + host + ":" + String::number(port))); } +// The following lower-ASCII characters need escaping to be used in a filename +// across all systems, including Windows: +// - Unprintable ASCII (00-1F) +// - Space (20) +// - Double quote (22) +// - Percent (25) (escaped because it is our escape character) +// - Asterisk (2A) +// - Slash (2F) +// - Colon (3A) +// - Less-than (3C) +// - Greater-than (3E) +// - Question Mark (3F) +// - Backslash (5C) +// - Pipe (7C) +// - Delete (7F) + +static const bool needsEscaping[128] = { + /* 00-07 */ true, true, true, true, true, true, true, true, + /* 08-0F */ true, true, true, true, true, true, true, true, + + /* 10-17 */ true, true, true, true, true, true, true, true, + /* 18-1F */ true, true, true, true, true, true, true, true, + + /* 20-27 */ true, false, true, false, false, true, false, false, + /* 28-2F */ false, false, true, false, false, false, false, true, + + /* 30-37 */ false, false, false, false, false, false, false, false, + /* 38-3F */ false, false, true, false, true, false, true, true, + + /* 40-47 */ false, false, false, false, false, false, false, false, + /* 48-4F */ false, false, false, false, false, false, false, false, + + /* 50-57 */ false, false, false, false, false, false, false, false, + /* 58-5F */ false, false, false, false, true, false, false, false, + + /* 60-67 */ false, false, false, false, false, false, false, false, + /* 68-6F */ false, false, false, false, false, false, false, false, + + /* 70-77 */ false, false, false, false, false, false, false, false, + /* 78-7F */ false, false, false, false, true, false, false, true, +}; + +static inline bool shouldEscapeUChar(UChar c) +{ + return c > 127 ? false : needsEscaping[c]; +} + +static const char hexDigits[17] = "0123456789ABCDEF"; + +static String encodedHost(const String& host) +{ + unsigned length = host.length(); + Vector<UChar, 512> buffer(length * 3 + 1); + UChar* p = buffer.data(); + + const UChar* str = host.characters(); + const UChar* strEnd = str + length; + + while (str < strEnd) { + UChar c = *str++; + if (shouldEscapeUChar(c)) { + *p++ = '%'; + *p++ = hexDigits[(c >> 4) & 0xF]; + *p++ = hexDigits[c & 0xF]; + } else + *p++ = c; + } + + ASSERT(p - buffer.data() <= static_cast<int>(buffer.size())); + + return String(buffer.data(), p - buffer.data()); +} + String SecurityOrigin::databaseIdentifier() const { - DEFINE_STATIC_LOCAL(String, separatorString, (&SeparatorCharacter, 1)); - return m_protocol + separatorString + m_host + separatorString + String::number(m_port); + String separatorString(&SeparatorCharacter, 1); + + if (m_encodedHost.isEmpty()) + m_encodedHost = encodedHost(m_host); + + return m_protocol + separatorString + m_encodedHost + separatorString + String::number(m_port); } bool SecurityOrigin::equal(const SecurityOrigin* other) const @@ -405,6 +533,9 @@ bool SecurityOrigin::isSameSchemeHostPort(const SecurityOrigin* other) const if (m_port != other->m_port) return false; + if (isLocal() && !passesFileCheck(other)) + return false; + return true; } @@ -477,6 +608,16 @@ bool SecurityOrigin::shouldTreatURLSchemeAsNoAccess(const String& scheme) return schemesWithUniqueOrigins().contains(scheme); } +void SecurityOrigin::registerURLSchemeAsSecure(const String& scheme) +{ + secureSchemes().add(scheme); +} + +bool SecurityOrigin::shouldTreatURLSchemeAsSecure(const String& scheme) +{ + return secureSchemes().contains(scheme); +} + bool SecurityOrigin::shouldHideReferrer(const KURL& url, const String& referrer) { bool referrerIsSecureURL = protocolIs(referrer, "https"); @@ -508,7 +649,7 @@ bool SecurityOrigin::allowSubstituteDataAccessToLocal() return localLoadPolicy != SecurityOrigin::AllowLocalLoadsForLocalOnly; } -void SecurityOrigin::whiteListAccessFromOrigin(const SecurityOrigin& sourceOrigin, const String& destinationProtocol, const String& destinationDomains, bool allowDestinationSubdomains) +void SecurityOrigin::addOriginAccessWhitelistEntry(const SecurityOrigin& sourceOrigin, const String& destinationProtocol, const String& destinationDomains, bool allowDestinationSubdomains) { ASSERT(isMainThread()); ASSERT(!sourceOrigin.isEmpty()); @@ -516,15 +657,42 @@ void SecurityOrigin::whiteListAccessFromOrigin(const SecurityOrigin& sourceOrigi return; String sourceString = sourceOrigin.toString(); - OriginAccessWhiteList* list = originAccessMap().get(sourceString); - if (!list) { - list = new OriginAccessWhiteList; - originAccessMap().set(sourceString, list); - } + pair<OriginAccessMap::iterator, bool> result = originAccessMap().add(sourceString, 0); + if (result.second) + result.first->second = new OriginAccessWhiteList; + + OriginAccessWhiteList* list = result.first->second; list->append(OriginAccessEntry(destinationProtocol, destinationDomains, allowDestinationSubdomains ? OriginAccessEntry::AllowSubdomains : OriginAccessEntry::DisallowSubdomains)); } -void SecurityOrigin::resetOriginAccessWhiteLists() +void SecurityOrigin::removeOriginAccessWhitelistEntry(const SecurityOrigin& sourceOrigin, const String& destinationProtocol, const String& destinationDomains, bool allowDestinationSubdomains) +{ + ASSERT(isMainThread()); + ASSERT(!sourceOrigin.isEmpty()); + if (sourceOrigin.isEmpty()) + return; + + String sourceString = sourceOrigin.toString(); + OriginAccessMap& map = originAccessMap(); + OriginAccessMap::iterator it = map.find(sourceString); + if (it == map.end()) + return; + + OriginAccessWhiteList* list = it->second; + size_t index = list->find(OriginAccessEntry(destinationProtocol, destinationDomains, allowDestinationSubdomains ? OriginAccessEntry::AllowSubdomains : OriginAccessEntry::DisallowSubdomains)); + if (index == notFound) + return; + + list->remove(index); + + if (!list->isEmpty()) + return; + + map.remove(it); + delete list; +} + +void SecurityOrigin::resetOriginAccessWhitelists() { ASSERT(isMainThread()); OriginAccessMap& map = originAccessMap(); |