From 0233ac776861761f6f268877384af40373e196e4 Mon Sep 17 00:00:00 2001 From: jww Date: Fri, 18 Mar 2016 00:03:40 -0700 Subject: [PATCH] Add SuboriginPolicy header and unsafe-postmessage-send directive This adds a SuboriginPolicy header that takes policies to bypass certain Suborigin restrictions. It also adds a specific unsafe-postmessage-send directive that specifies that the receiving end of a postMessage message event should seed the origin as the physical origin, rather than the serialized suborigin. BUG=336894 Review URL: https://codereview.chromium.org/1772873003 Cr-Commit-Position: refs/heads/master@{#381905} --- .../resources/post-document-to-parent.php | 5 +- .../suborigin-invalid-names-expected.txt | 6 +- .../suborigins/suborigin-invalid-names.html | 2 + .../suborigin-unsafe-postmessage-send.html | 36 +++++ .../suborigin-valid-names-expected.txt | 2 - .../suborigins/suborigin-valid-names.html | 2 - third_party/WebKit/Source/core/core.gypi | 4 - .../WebKit/Source/core/dom/Document.cpp | 5 +- third_party/WebKit/Source/core/dom/Document.h | 2 +- .../Source/core/dom/SecurityContext.cpp | 22 +--- .../WebKit/Source/core/dom/SecurityContext.h | 6 +- .../WebKit/Source/core/dom/Suborigin.cpp | 62 --------- .../WebKit/Source/core/dom/Suborigin.h | 23 ---- .../WebKit/Source/core/dom/SuboriginTest.cpp | 63 --------- .../core/fetch/CrossOriginAccessControl.cpp | 2 +- .../WebKit/Source/core/frame/DOMWindow.cpp | 10 +- .../Source/core/loader/DocumentLoader.cpp | 5 +- .../Source/core/loader/DocumentLoader.h | 3 - .../WebKit/Source/core/loader/FrameLoader.cpp | 17 ++- .../WebKit/Source/core/loader/HttpEquiv.cpp | 3 +- .../Source/platform/blink_platform.gypi | 2 + .../Source/platform/network/HTTPParsers.cpp | 124 ++++++++++++++++++ .../Source/platform/network/HTTPParsers.h | 7 + .../platform/network/HTTPParsersTest.cpp | 105 +++++++++++++++ .../platform/network/ResourceRequest.cpp | 2 +- .../platform/weborigin/SecurityOrigin.cpp | 59 ++++++--- .../platform/weborigin/SecurityOrigin.h | 15 ++- .../platform/weborigin/SecurityOriginHash.h | 2 +- .../platform/weborigin/SecurityOriginTest.cpp | 32 +++-- .../Source/platform/weborigin/Suborigin.cpp | 47 +++++++ .../Source/platform/weborigin/Suborigin.h | 41 ++++++ 31 files changed, 486 insertions(+), 230 deletions(-) create mode 100644 third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-unsafe-postmessage-send.html delete mode 100644 third_party/WebKit/Source/core/dom/Suborigin.cpp delete mode 100644 third_party/WebKit/Source/core/dom/Suborigin.h delete mode 100644 third_party/WebKit/Source/core/dom/SuboriginTest.cpp create mode 100644 third_party/WebKit/Source/platform/weborigin/Suborigin.cpp create mode 100644 third_party/WebKit/Source/platform/weborigin/Suborigin.h diff --git a/third_party/WebKit/LayoutTests/http/tests/security/suborigins/resources/post-document-to-parent.php b/third_party/WebKit/LayoutTests/http/tests/security/suborigins/resources/post-document-to-parent.php index 525251d1700d3..1ad8a5027b958 100644 --- a/third_party/WebKit/LayoutTests/http/tests/security/suborigins/resources/post-document-to-parent.php +++ b/third_party/WebKit/LayoutTests/http/tests/security/suborigins/resources/post-document-to-parent.php @@ -1,6 +1,9 @@ diff --git a/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-invalid-names-expected.txt b/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-invalid-names-expected.txt index 9b21d8e54c055..8bc72a657dad3 100644 --- a/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-invalid-names-expected.txt +++ b/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-invalid-names-expected.txt @@ -9,6 +9,10 @@ CONSOLE ERROR: Error with Suborigin header: Invalid character ''' in suborigin. ALERT: I am a secret CONSOLE ERROR: Error with Suborigin header: Invalid character '@' in suborigin. ALERT: I am a secret -CONSOLE ERROR: Error with Suborigin header: Whitespace is not allowed in suborigin names. +CONSOLE ERROR: Error with Suborigin header: Invalid character 'b' in suborigin policy. Suborigin policy options must start and end with a single quote. +ALERT: I am a secret +CONSOLE ERROR: Error with Suborigin header: Invalid character 'F' in suborigin. +ALERT: I am a secret +CONSOLE ERROR: Error with Suborigin header: Invalid character 'F' in suborigin. ALERT: I am a secret diff --git a/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-invalid-names.html b/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-invalid-names.html index 9e6c141a3d09c..da3c35cbe6ac1 100644 --- a/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-invalid-names.html +++ b/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-invalid-names.html @@ -23,6 +23,8 @@ "foo'bar", "foob@r", "foo bar", + "Foobar", + "FOOBAR", ]; var iframe; diff --git a/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-unsafe-postmessage-send.html b/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-unsafe-postmessage-send.html new file mode 100644 index 0000000000000..a913d8faffc53 --- /dev/null +++ b/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-unsafe-postmessage-send.html @@ -0,0 +1,36 @@ + + + +Validate that unsafe-postmessage-send allows Suborigin to send messages as physical origin via postMessage. + + + + + + diff --git a/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-valid-names-expected.txt b/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-valid-names-expected.txt index 41ab95fda2d4c..7f0c9b9309512 100644 --- a/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-valid-names-expected.txt +++ b/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-valid-names-expected.txt @@ -4,6 +4,4 @@ ALERT: SecurityError: Blocked a frame with origin "http://127.0.0.1:8000" from a ALERT: SecurityError: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a cross-origin frame. ALERT: SecurityError: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a cross-origin frame. ALERT: SecurityError: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a cross-origin frame. -ALERT: SecurityError: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a cross-origin frame. -ALERT: SecurityError: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a cross-origin frame. diff --git a/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-valid-names.html b/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-valid-names.html index 31800588c6413..f784e2a199b8c 100644 --- a/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-valid-names.html +++ b/third_party/WebKit/LayoutTests/http/tests/security/suborigins/suborigin-valid-names.html @@ -18,8 +18,6 @@ var test_suborigin_names = [ "foobar", "foob4r", - "Foobar", - "FOOBAR", "42", "foo-bar", "-foobar", diff --git a/third_party/WebKit/Source/core/core.gypi b/third_party/WebKit/Source/core/core.gypi index a57f379f1b64d..9e447dc44ab89 100644 --- a/third_party/WebKit/Source/core/core.gypi +++ b/third_party/WebKit/Source/core/core.gypi @@ -2563,8 +2563,6 @@ 'dom/StyleSheetCandidate.h', 'dom/StyleSheetCollection.cpp', 'dom/StyleSheetCollection.h', - 'dom/Suborigin.cpp', - 'dom/Suborigin.h', 'dom/TagCollection.cpp', 'dom/TagCollection.h', 'dom/Text.cpp', @@ -3928,7 +3926,6 @@ 'dom/SelectorQueryTest.cpp', 'dom/StyleElementTest.cpp', 'dom/StyleEngineTest.cpp', - 'dom/SuboriginTest.cpp', 'dom/TextTest.cpp', 'dom/TreeScopeStyleSheetCollectionTest.cpp', 'dom/TreeScopeTest.cpp', @@ -4037,7 +4034,6 @@ 'loader/LinkHeaderTest.cpp', 'loader/LinkLoaderTest.cpp', 'loader/MixedContentCheckerTest.cpp', - 'origin_trials/DocumentOriginTrialContextTest.cpp', 'origin_trials/OriginTrialContextTest.cpp', 'page/ChromeClientTest.cpp', 'page/ContextMenuControllerTest.cpp', diff --git a/third_party/WebKit/Source/core/dom/Document.cpp b/third_party/WebKit/Source/core/dom/Document.cpp index cfb9c34809da7..92c4893c36f14 100644 --- a/third_party/WebKit/Source/core/dom/Document.cpp +++ b/third_party/WebKit/Source/core/dom/Document.cpp @@ -5005,7 +5005,7 @@ void Document::initSecurityContext(const DocumentInit& initializer) } if (getSecurityOrigin()->hasSuborigin()) - enforceSuborigin(getSecurityOrigin()->suboriginName()); + enforceSuborigin(*getSecurityOrigin()->suborigin()); if (Settings* settings = initializer.settings()) { if (!settings->webSecurityEnabled()) { @@ -5030,6 +5030,9 @@ void Document::initSecurityContext(const DocumentInit& initializer) if (getSecurityOrigin()->isUnique() && SecurityOrigin::create(m_url)->isPotentiallyTrustworthy()) getSecurityOrigin()->setUniqueOriginIsPotentiallyTrustworthy(true); + + if (getSecurityOrigin()->hasSuborigin()) + enforceSuborigin(*getSecurityOrigin()->suborigin()); } void Document::initContentSecurityPolicy(PassRefPtrWillBeRawPtr csp) diff --git a/third_party/WebKit/Source/core/dom/Document.h b/third_party/WebKit/Source/core/dom/Document.h index efc719fdd0df6..58602d6d61886 100644 --- a/third_party/WebKit/Source/core/dom/Document.h +++ b/third_party/WebKit/Source/core/dom/Document.h @@ -327,7 +327,7 @@ class CORE_EXPORT Document : public ContainerNode, public TreeScope, public Secu void setHasXMLDeclaration(bool hasXMLDeclaration) { m_hasXMLDeclaration = hasXMLDeclaration ? 1 : 0; } String origin() const { return getSecurityOrigin()->toString(); } - String suborigin() const { return getSecurityOrigin()->suboriginName(); } + String suborigin() const { return getSecurityOrigin()->hasSuborigin() ? getSecurityOrigin()->suborigin()->name() : String(); } String visibilityState() const; PageVisibilityState pageVisibilityState() const; diff --git a/third_party/WebKit/Source/core/dom/SecurityContext.cpp b/third_party/WebKit/Source/core/dom/SecurityContext.cpp index b0ed8c7c08f1e..cc420920da3c0 100644 --- a/third_party/WebKit/Source/core/dom/SecurityContext.cpp +++ b/third_party/WebKit/Source/core/dom/SecurityContext.cpp @@ -90,34 +90,20 @@ String SecurityContext::addressSpaceForBindings() const return "public"; } -bool SecurityContext::hasSuborigin() -{ - ASSERT(m_securityOrigin.get()); - return m_securityOrigin->hasSuborigin(); -} - -String SecurityContext::suboriginName() -{ - ASSERT(m_securityOrigin.get()); - return m_securityOrigin->suboriginName(); -} - // Enforces the given suborigin as part of the security origin for this // security context. |name| must not be empty, although it may be null. A null // name represents a lack of a suborigin. // See: https://w3c.github.io/webappsec-suborigins/index.html -void SecurityContext::enforceSuborigin(const String& name) +void SecurityContext::enforceSuborigin(const Suborigin& suborigin) { if (!RuntimeEnabledFeatures::suboriginsEnabled()) return; - if (name.isNull()) - return; - ASSERT(!name.isEmpty()); + ASSERT(!suborigin.name().isEmpty()); ASSERT(RuntimeEnabledFeatures::suboriginsEnabled()); ASSERT(m_securityOrigin.get()); - ASSERT(!m_securityOrigin->hasSuborigin() || m_securityOrigin->suboriginName() == name); - m_securityOrigin->addSuborigin(name); + ASSERT(!m_securityOrigin->hasSuborigin() || m_securityOrigin->suborigin()->name() == suborigin.name()); + m_securityOrigin->addSuborigin(suborigin); didUpdateSecurityOrigin(); } diff --git a/third_party/WebKit/Source/core/dom/SecurityContext.h b/third_party/WebKit/Source/core/dom/SecurityContext.h index 952e1cd6692f1..f1f9483e65608 100644 --- a/third_party/WebKit/Source/core/dom/SecurityContext.h +++ b/third_party/WebKit/Source/core/dom/SecurityContext.h @@ -30,7 +30,9 @@ #include "core/CoreExport.h" #include "core/dom/SandboxFlags.h" #include "platform/heap/Handle.h" +#include "platform/weborigin/Suborigin.h" #include "public/platform/WebAddressSpace.h" +#include "public/platform/WebURLRequest.h" #include "wtf/HashSet.h" #include "wtf/Noncopyable.h" #include "wtf/PassRefPtr.h" @@ -83,9 +85,7 @@ class CORE_EXPORT SecurityContext : public WillBeGarbageCollectedMixin { void setShouldEnforceStrictMixedContentChecking(bool shouldEnforce) { m_enforceStrictMixedContentChecking = shouldEnforce; } bool shouldEnforceStrictMixedContentChecking() { return m_enforceStrictMixedContentChecking; } - void enforceSuborigin(const String& name); - bool hasSuborigin(); - String suboriginName(); + void enforceSuborigin(const Suborigin&); protected: SecurityContext(); diff --git a/third_party/WebKit/Source/core/dom/Suborigin.cpp b/third_party/WebKit/Source/core/dom/Suborigin.cpp deleted file mode 100644 index 7f3794a039b57..0000000000000 --- a/third_party/WebKit/Source/core/dom/Suborigin.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "core/dom/Suborigin.h" - -#include "core/dom/Document.h" -#include "core/inspector/ConsoleMessage.h" -#include "platform/ParsingUtilities.h" -#include "wtf/ASCIICType.h" - -namespace blink { - -template static inline bool isASCIIAlphanumericOrHyphen(CharType c) -{ - return isASCIIAlphanumeric(c) || c == '-'; -} - -void SuboriginPolicy::logSuboriginHeaderError(Document& document, const String& message) -{ - document.addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, "Error with Suborigin header: " + message)); -} - -String SuboriginPolicy::parseSuboriginName(Document& document, const String& header) -{ - Vector headers; - header.split(',', true, headers); - - if (headers.size() > 1) - logSuboriginHeaderError(document, "Multiple Suborigin headers found. Ignoring all but the first."); - - Vector characters; - headers[0].appendTo(characters); - - const UChar* position = characters.data(); - const UChar* end = position + characters.size(); - - // Parse the name of the suborigin (no spaces, single string) - skipWhile(position, end); - if (position == end) { - logSuboriginHeaderError(document, "No Suborigin name specified."); - return String(); - } - - const UChar* begin = position; - - skipWhile(position, end); - if (position != end && !isASCIISpace(*position)) { - logSuboriginHeaderError(document, "Invalid character \'" + String(position, 1) + "\' in suborigin."); - return String(); - } - size_t length = position - begin; - skipWhile(position, end); - if (position != end) { - logSuboriginHeaderError(document, "Whitespace is not allowed in suborigin names."); - return String(); - } - - return String(begin, length); -} - -} // namespace blink diff --git a/third_party/WebKit/Source/core/dom/Suborigin.h b/third_party/WebKit/Source/core/dom/Suborigin.h deleted file mode 100644 index 157a6c91417f2..0000000000000 --- a/third_party/WebKit/Source/core/dom/Suborigin.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef Suborigin_h -#define Suborigin_h - -#include "core/CoreExport.h" -#include "wtf/text/WTFString.h" - -namespace blink { - -class Document; - -class CORE_EXPORT SuboriginPolicy { -public: - static void logSuboriginHeaderError(Document&, const String& message); - static String parseSuboriginName(Document&, const String& header); -}; - -} // namespace blink - -#endif // Suborigin_h diff --git a/third_party/WebKit/Source/core/dom/SuboriginTest.cpp b/third_party/WebKit/Source/core/dom/SuboriginTest.cpp deleted file mode 100644 index 5498167a1a104..0000000000000 --- a/third_party/WebKit/Source/core/dom/SuboriginTest.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "core/dom/Suborigin.h" - -#include "core/dom/Document.h" -#include "platform/heap/Handle.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace blink { - -class SuboriginTest : public ::testing::Test { -protected: - virtual void SetUp() - { - m_document = Document::create(); - } - - RefPtrWillBePersistent m_document; -}; - -TEST_F(SuboriginTest, ParseValidHeaders) -{ - EXPECT_EQ("foo", SuboriginPolicy::parseSuboriginName(*m_document, "foo")); - EXPECT_EQ("foo", SuboriginPolicy::parseSuboriginName(*m_document, " foo ")); - EXPECT_EQ("Foo", SuboriginPolicy::parseSuboriginName(*m_document, "Foo")); - EXPECT_EQ("FOO", SuboriginPolicy::parseSuboriginName(*m_document, "FOO")); - EXPECT_EQ("f0o", SuboriginPolicy::parseSuboriginName(*m_document, "f0o")); - EXPECT_EQ("42", SuboriginPolicy::parseSuboriginName(*m_document, "42")); - EXPECT_EQ("foo-bar", SuboriginPolicy::parseSuboriginName(*m_document, "foo-bar")); - EXPECT_EQ("-foobar", SuboriginPolicy::parseSuboriginName(*m_document, "-foobar")); - EXPECT_EQ("foobar-", SuboriginPolicy::parseSuboriginName(*m_document, "foobar-")); - - // Mulitple headers should only give the first name - EXPECT_EQ("foo", SuboriginPolicy::parseSuboriginName(*m_document, "foo, bar")); - EXPECT_EQ("foo", SuboriginPolicy::parseSuboriginName(*m_document, "foo,bar")); - EXPECT_EQ("foo", SuboriginPolicy::parseSuboriginName(*m_document, "foo , bar")); - EXPECT_EQ("foo", SuboriginPolicy::parseSuboriginName(*m_document, "foo,")); - - // If the second value in multiple headers is invalid, it is still ignored. - EXPECT_EQ("foo", SuboriginPolicy::parseSuboriginName(*m_document, "foo, @bar")); -} - -TEST_F(SuboriginTest, ParseInvalidHeaders) -{ - // Single header, invalid value - EXPECT_EQ(String(), SuboriginPolicy::parseSuboriginName(*m_document, "")); - EXPECT_EQ(String(), SuboriginPolicy::parseSuboriginName(*m_document, "foo bar")); - EXPECT_EQ(String(), SuboriginPolicy::parseSuboriginName(*m_document, "'foobar'")); - EXPECT_EQ(String(), SuboriginPolicy::parseSuboriginName(*m_document, "foobar'")); - EXPECT_EQ(String(), SuboriginPolicy::parseSuboriginName(*m_document, "foo'bar")); - EXPECT_EQ(String(), SuboriginPolicy::parseSuboriginName(*m_document, "foob@r")); - EXPECT_EQ(String(), SuboriginPolicy::parseSuboriginName(*m_document, "foo bar")); - - // Multiple headers, invalid value(s) - EXPECT_EQ(String(), SuboriginPolicy::parseSuboriginName(*m_document, ", bar")); - EXPECT_EQ(String(), SuboriginPolicy::parseSuboriginName(*m_document, ",")); - EXPECT_EQ(String(), SuboriginPolicy::parseSuboriginName(*m_document, "f@oo, bar")); - EXPECT_EQ(String(), SuboriginPolicy::parseSuboriginName(*m_document, "f@oo, b@r")); -} - -} // namespace blink diff --git a/third_party/WebKit/Source/core/fetch/CrossOriginAccessControl.cpp b/third_party/WebKit/Source/core/fetch/CrossOriginAccessControl.cpp index 16b3c4584dade..1836da81c5d64 100644 --- a/third_party/WebKit/Source/core/fetch/CrossOriginAccessControl.cpp +++ b/third_party/WebKit/Source/core/fetch/CrossOriginAccessControl.cpp @@ -146,7 +146,7 @@ bool passesAccessControlCheck(const ResourceResponse& response, StoredCredential // which implies that all Suborigins are okay as well. if (securityOrigin->hasSuborigin() && allowOriginHeaderValue != starAtom) { const AtomicString& allowSuboriginHeaderValue = response.httpHeaderField(allowSuboriginHeaderName); - AtomicString atomicSuboriginName(securityOrigin->suboriginName()); + AtomicString atomicSuboriginName(securityOrigin->suborigin()->name()); if (allowSuboriginHeaderValue != starAtom && allowSuboriginHeaderValue != atomicSuboriginName) { errorDescription = buildAccessControlFailureMessage("The 'Access-Control-Allow-Suborigin' header has a value '" + allowSuboriginHeaderValue + "' that is not equal to the supplied suborigin.", securityOrigin); return false; diff --git a/third_party/WebKit/Source/core/frame/DOMWindow.cpp b/third_party/WebKit/Source/core/frame/DOMWindow.cpp index 26aee8fe4d63e..4022728a837a7 100644 --- a/third_party/WebKit/Source/core/frame/DOMWindow.cpp +++ b/third_party/WebKit/Source/core/frame/DOMWindow.cpp @@ -28,6 +28,7 @@ #include "core/page/Page.h" #include "platform/weborigin/KURL.h" #include "platform/weborigin/SecurityOrigin.h" +#include "platform/weborigin/Suborigin.h" namespace blink { @@ -199,8 +200,13 @@ void DOMWindow::postMessage(PassRefPtr message, const Mes // in order to capture the source of the message correctly. if (!sourceDocument) return; - String sourceOrigin = sourceDocument->getSecurityOrigin()->toString(); - String sourceSuborigin = sourceDocument->getSecurityOrigin()->suboriginName(); + + const SecurityOrigin* securityOrigin = sourceDocument->getSecurityOrigin(); + bool hasSuborigin = sourceDocument->getSecurityOrigin()->hasSuborigin(); + Suborigin::SuboriginPolicyOptions unsafeSendOpt = Suborigin::SuboriginPolicyOptions::UnsafePostMessageSend; + + String sourceOrigin = (hasSuborigin && securityOrigin->suborigin()->policyContains(unsafeSendOpt)) ? securityOrigin->toPhysicalOriginString() : securityOrigin->toString(); + String sourceSuborigin = hasSuborigin ? securityOrigin->suborigin()->name() : String(); KURL targetUrl = isLocalDOMWindow() ? document()->url() : KURL(KURL(), frame()->securityContext()->getSecurityOrigin()->toString()); if (MixedContentChecker::isMixedContent(sourceDocument->getSecurityOrigin(), targetUrl)) diff --git a/third_party/WebKit/Source/core/loader/DocumentLoader.cpp b/third_party/WebKit/Source/core/loader/DocumentLoader.cpp index c5b90833586a9..dc4df7cdec3a7 100644 --- a/third_party/WebKit/Source/core/loader/DocumentLoader.cpp +++ b/third_party/WebKit/Source/core/loader/DocumentLoader.cpp @@ -31,7 +31,6 @@ #include "core/dom/Document.h" #include "core/dom/DocumentParser.h" -#include "core/dom/Suborigin.h" #include "core/dom/WeakIdentifierMap.h" #include "core/events/Event.h" #include "core/fetch/CSSStyleSheetResource.h" @@ -66,6 +65,7 @@ #include "platform/UserGestureIndicator.h" #include "platform/mhtml/ArchiveResource.h" #include "platform/network/ContentSecurityPolicyResponseHeaders.h" +#include "platform/network/HTTPParsers.h" #include "platform/plugins/PluginData.h" #include "platform/weborigin/SchemeRegistry.h" #include "platform/weborigin/SecurityPolicy.h" @@ -436,9 +436,6 @@ void DocumentLoader::responseReceived(Resource* resource, const ResourceResponse } } } - HTTPHeaderMap::const_iterator it = response.httpHeaderFields().find(HTTPNames::Suborigin); - if (it != response.httpHeaderFields().end()) - m_suboriginName = SuboriginPolicy::parseSuboriginName(*frame()->document(), it->value); ASSERT(!m_frame->page()->defersLoading()); diff --git a/third_party/WebKit/Source/core/loader/DocumentLoader.h b/third_party/WebKit/Source/core/loader/DocumentLoader.h index ec207782b4d1a..27d1a2b180458 100644 --- a/third_party/WebKit/Source/core/loader/DocumentLoader.h +++ b/third_party/WebKit/Source/core/loader/DocumentLoader.h @@ -142,8 +142,6 @@ class CORE_EXPORT DocumentLoader : public RefCountedWillBeGarbageCollectedFinali void setWasBlockedAfterXFrameOptionsOrCSP() { m_wasBlockedAfterXFrameOptionsOrCSP = true; } bool wasBlockedAfterXFrameOptionsOrCSP() { return m_wasBlockedAfterXFrameOptionsOrCSP; } - String suboriginName() const { return m_suboriginName; } - Resource* startPreload(Resource::Type, FetchRequest&); DECLARE_VIRTUAL_TRACE(); @@ -220,7 +218,6 @@ class CORE_EXPORT DocumentLoader : public RefCountedWillBeGarbageCollectedFinali RefPtrWillBeMember m_contentSecurityPolicy; ClientHintsPreferences m_clientHintsPreferences; InitialScrollState m_initialScrollState; - String m_suboriginName; bool m_wasBlockedAfterXFrameOptionsOrCSP; diff --git a/third_party/WebKit/Source/core/loader/FrameLoader.cpp b/third_party/WebKit/Source/core/loader/FrameLoader.cpp index 79fd95656d747..e6d04948a281b 100644 --- a/third_party/WebKit/Source/core/loader/FrameLoader.cpp +++ b/third_party/WebKit/Source/core/loader/FrameLoader.cpp @@ -90,6 +90,7 @@ #include "platform/scroll/ScrollAnimatorBase.h" #include "platform/weborigin/SecurityOrigin.h" #include "platform/weborigin/SecurityPolicy.h" +#include "platform/weborigin/Suborigin.h" #include "public/platform/WebURLRequest.h" #include "wtf/TemporaryChange.h" #include "wtf/text/CString.h" @@ -447,8 +448,20 @@ void FrameLoader::didBeginDocument(bool dispatch) dispatchDidClearDocumentOfWindowObject(); m_frame->document()->initContentSecurityPolicy(m_documentLoader ? m_documentLoader->releaseContentSecurityPolicy() : ContentSecurityPolicy::create()); - if (m_documentLoader && !m_documentLoader->suboriginName().isNull()) - m_frame->document()->enforceSuborigin(m_documentLoader->suboriginName()); + + if (m_documentLoader) { + String suboriginHeader = m_documentLoader->response().httpHeaderField(HTTPNames::Suborigin); + if (!suboriginHeader.isNull()) { + Vector messages; + Suborigin suborigin; + if (parseSuboriginHeader(suboriginHeader, &suborigin, messages)) + m_frame->document()->enforceSuborigin(suborigin); + + for (auto& message : messages) + m_frame->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, "Error with Suborigin header: " + message)); + } + } + if (m_documentLoader) { m_frame->document()->clientHintsPreferences().updateFrom(m_documentLoader->clientHintsPreferences()); LinkLoader::loadLinksFromHeader(m_documentLoader->response().httpHeaderField(HTTPNames::Link), m_documentLoader->response().url(), diff --git a/third_party/WebKit/Source/core/loader/HttpEquiv.cpp b/third_party/WebKit/Source/core/loader/HttpEquiv.cpp index 2f5f664fea700..26447d3b2f3e8 100644 --- a/third_party/WebKit/Source/core/loader/HttpEquiv.cpp +++ b/third_party/WebKit/Source/core/loader/HttpEquiv.cpp @@ -6,7 +6,6 @@ #include "core/dom/Document.h" #include "core/dom/StyleEngine.h" -#include "core/dom/Suborigin.h" #include "core/fetch/ClientHintsPreferences.h" #include "core/frame/UseCounter.h" #include "core/frame/csp/ContentSecurityPolicy.h" @@ -42,7 +41,7 @@ void HttpEquiv::process(Document& document, const AtomicString& equiv, const Ato else document.contentSecurityPolicy()->reportMetaOutsideHead(content); } else if (equalIgnoringCase(equiv, "suborigin")) { - SuboriginPolicy::logSuboriginHeaderError(document, "Suborigin header with value '" + content + "' was delivered via a element and not an HTTP header, which is disallowed. The Suborigin has been ignored."); + document.addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, "Error with Suborigin header: Suborigin header with value '" + content + "' was delivered via a element and not an HTTP header, which is disallowed. The Suborigin has been ignored.")); } } diff --git a/third_party/WebKit/Source/platform/blink_platform.gypi b/third_party/WebKit/Source/platform/blink_platform.gypi index 67d39538bc4ff..e743a95f8ee0f 100644 --- a/third_party/WebKit/Source/platform/blink_platform.gypi +++ b/third_party/WebKit/Source/platform/blink_platform.gypi @@ -1115,6 +1115,8 @@ 'weborigin/SecurityOriginHash.h', 'weborigin/SecurityPolicy.cpp', 'weborigin/SecurityPolicy.h', + 'weborigin/Suborigin.cpp', + 'weborigin/Suborigin.h', ], 'platform_test_files': [ 'DecimalTest.cpp', diff --git a/third_party/WebKit/Source/platform/network/HTTPParsers.cpp b/third_party/WebKit/Source/platform/network/HTTPParsers.cpp index edf10dc217328..2f002fdb60cd3 100644 --- a/third_party/WebKit/Source/platform/network/HTTPParsers.cpp +++ b/third_party/WebKit/Source/platform/network/HTTPParsers.cpp @@ -32,6 +32,8 @@ #include "platform/network/HTTPParsers.h" +#include "platform/ParsingUtilities.h" +#include "platform/weborigin/Suborigin.h" #include "wtf/DateMath.h" #include "wtf/MathExtras.h" #include "wtf/text/CString.h" @@ -106,6 +108,72 @@ static inline bool skipValue(const String& str, unsigned& pos) return pos != start; } +template static inline bool isASCIILowerAlphaOrDigitOrHyphen(CharType c) +{ + return isASCIILower(c) || isASCIIDigit(c) || c == '-'; +} + +static Suborigin::SuboriginPolicyOptions getSuboriginPolicyOptionFromString(const String& policyOptionName) +{ + if (policyOptionName == "'unsafe-postmessage-send'") + return Suborigin::SuboriginPolicyOptions::UnsafePostMessageSend; + + return Suborigin::SuboriginPolicyOptions::None; +} + + +static const UChar* parseSuboriginName(const UChar* begin, const UChar* end, String& name, WTF::Vector& messages) +{ + // Parse the name of the suborigin (no spaces, single string) + skipWhile(begin, end); + if (begin == end) { + messages.append(String("No Suborigin name specified.")); + return nullptr; + } + + const UChar* position = begin; + + skipWhile(position, end); + if (position != end && !isASCIISpace(*position)) { + messages.append("Invalid character \'" + String(position, 1) + "\' in suborigin."); + return nullptr; + } + size_t length = position - begin; + skipWhile(position, end); + + name = String(begin, length).lower(); + return position; +} + +static const UChar* parseSuboriginPolicyOption(const UChar* begin, const UChar* end, String& option, WTF::Vector& messages) +{ + const UChar* position = begin; + + if (*position != '\'') { + messages.append("Invalid character \'" + String(position, 1) + "\' in suborigin policy. Suborigin policy options must start and end with a single quote."); + return nullptr; + } + position = position + 1; + + skipWhile(position, end); + if (position == end || isASCIISpace(*position)) { + messages.append(String("Expected \' to end policy option.")); + return nullptr; + } + + if (*position != '\'') { + messages.append("Invalid character \'" + String(position, 1) + "\' in suborigin policy."); + return nullptr; + } + + ASSERT(position > begin); + size_t length = (position + 1) - begin; + + option = String(begin, length); + return position + 1; +} + + bool isValidHTTPHeaderValue(const String& name) { // FIXME: This should really match name against @@ -628,4 +696,60 @@ void parseCommaDelimitedHeader(const String& headerValue, CommaDelimitedHeaderSe headerSet.add(value.stripWhiteSpace(isWhitespace)); } +bool parseSuboriginHeader(const String& header, Suborigin* suborigin, WTF::Vector& messages) +{ + Vector headers; + header.split(',', true, headers); + + if (headers.size() > 1) + messages.append("Multiple Suborigin headers found. Ignoring all but the first."); + + Vector characters; + headers[0].appendTo(characters); + + const UChar* position = characters.data(); + const UChar* end = position + characters.size(); + + skipWhile(position, end); + + String name; + position = parseSuboriginName(position, end, name, messages); + if (!position) + return false; + + suborigin->setName(name); + + while (position < end) { + skipWhile(position, end); + if (position == end) + return true; + + String optionName; + position = parseSuboriginPolicyOption(position, end, optionName, messages); + + if (!position) { + suborigin->clear(); + return false; + } + + Suborigin::SuboriginPolicyOptions option = getSuboriginPolicyOptionFromString(optionName); + if (option == Suborigin::SuboriginPolicyOptions::None) + messages.append("Ignoring unknown suborigin policy option " + optionName + "."); + else + suborigin->addPolicyOption(option); + + skipWhile(position, end); + if (position == end || *position != ';') { + String found = (position == end) ? "end of string" : String(position, 1); + messages.append("Invalid suborigin policy Expected ';' at end of policy option. Found \'" + found + "\' instead."); + suborigin->clear(); + return false; + } + + position++; + } + + return true; +} + } // namespace blink diff --git a/third_party/WebKit/Source/platform/network/HTTPParsers.h b/third_party/WebKit/Source/platform/network/HTTPParsers.h index 10ea6f9de741f..8fe5d2e95d6e5 100644 --- a/third_party/WebKit/Source/platform/network/HTTPParsers.h +++ b/third_party/WebKit/Source/platform/network/HTTPParsers.h @@ -40,6 +40,8 @@ namespace blink { +class Suborigin; + typedef enum { ContentDispositionNone, ContentDispositionInline, @@ -110,6 +112,11 @@ PLATFORM_EXPORT ReflectedXSSDisposition parseXSSProtectionHeader(const String& h PLATFORM_EXPORT XFrameOptionsDisposition parseXFrameOptionsHeader(const String&); PLATFORM_EXPORT CacheControlHeader parseCacheControlDirectives(const AtomicString& cacheControlHeader, const AtomicString& pragmaHeader); PLATFORM_EXPORT void parseCommaDelimitedHeader(const String& headerValue, CommaDelimitedHeaderSet&); +// Returns true on success, otherwise false. The Suborigin argument must be a +// non-null return argument. |messages| is a list of messages based on any +// parse warnings or errors. Even if parseSuboriginHeader returns true, there +// may be Strings in |messages|. +PLATFORM_EXPORT bool parseSuboriginHeader(const String& header, Suborigin*, WTF::Vector& messages); PLATFORM_EXPORT ContentTypeOptionsDisposition parseContentTypeOptionsHeader(const String& header); diff --git a/third_party/WebKit/Source/platform/network/HTTPParsersTest.cpp b/third_party/WebKit/Source/platform/network/HTTPParsersTest.cpp index 2349c27ee4f71..3304ac0dd0847 100644 --- a/third_party/WebKit/Source/platform/network/HTTPParsersTest.cpp +++ b/third_party/WebKit/Source/platform/network/HTTPParsersTest.cpp @@ -4,8 +4,11 @@ #include "platform/network/HTTPParsers.h" +#include "platform/heap/Handle.h" +#include "platform/weborigin/Suborigin.h" #include "testing/gtest/include/gtest/gtest.h" #include "wtf/MathExtras.h" +#include "wtf/dtoa/utils.h" #include "wtf/text/AtomicString.h" namespace blink { @@ -165,4 +168,106 @@ TEST(HTTPParsersTest, ExtractMIMETypeFromMediaType) EXPECT_EQ(textHtml, extractMIMETypeFromMediaType(AtomicString("t e x t / h t m l"))); } +void expectParseNamePass(const char* message, String header, String expectedName) +{ + SCOPED_TRACE(message); + + Vector messages; + Suborigin suborigin; + EXPECT_TRUE(parseSuboriginHeader(header, &suborigin, messages)); + EXPECT_EQ(expectedName, suborigin.name()); +} + +void expectParseNameFail(const char* message, String header) +{ + SCOPED_TRACE(message); + + Vector messages; + Suborigin suborigin; + EXPECT_FALSE(parseSuboriginHeader(header, &suborigin, messages)); + EXPECT_EQ(String(), suborigin.name()); +} + +void expectParsePolicyPass(const char* message, String header, const Suborigin::SuboriginPolicyOptions expectedPolicy[], size_t numPolicies) +{ + SCOPED_TRACE(message); + + Vector messages; + Suborigin suborigin; + EXPECT_TRUE(parseSuboriginHeader(header, &suborigin, messages)); + unsigned policiesMask = 0; + for (size_t i = 0; i < numPolicies; i++) + policiesMask |= static_cast(expectedPolicy[i]); + EXPECT_EQ(policiesMask, suborigin.optionsMask()); +} + +void expectParsePolicyFail(const char* message, String header) +{ + SCOPED_TRACE(message); + + Vector messages; + Suborigin suborigin; + EXPECT_FALSE(parseSuboriginHeader(header, &suborigin, messages)); + EXPECT_EQ(String(), suborigin.name()); +} + +TEST(HTTPParsersTest, SuboriginParseValidNames) +{ + // Single headers + expectParseNamePass("Alpha", "foo", "foo"); + expectParseNamePass("Whitespace alpha", " foo ", "foo"); + expectParseNamePass("Alphanumeric", "f0o", "f0o"); + expectParseNamePass("Numeric", "42", "42"); + expectParseNamePass("Hyphen middle", "foo-bar", "foo-bar"); + expectParseNamePass("Hyphen start", "-foobar", "-foobar"); + expectParseNamePass("Hyphen end", "foobar-", "foobar-"); + + // Mulitple headers should only give the first name + expectParseNamePass("Multiple headers, no whitespace", "foo,bar", "foo"); + expectParseNamePass("Multiple headers, whitespace before second", "foo, bar", "foo"); + expectParseNamePass("Multiple headers, whitespace after first and before second", "foo, bar", "foo"); + expectParseNamePass("Multiple headers, empty second ignored", "foo, bar", "foo"); + expectParseNamePass("Multiple headers, invalid second ignored", "foo, bar", "foo"); +} + +TEST(HTTPParsersTest, SuboriginParseInvalidNames) +{ + // Single header, invalid value + expectParseNameFail("Empty header", ""); + expectParseNameFail("Whitespace in middle", "foo bar"); + expectParseNameFail("Invalid character at end of name", "foobar'"); + expectParseNameFail("Invalid character at start of name", "'foobar"); + expectParseNameFail("Invalid character in middle of name", "foo'bar"); + expectParseNameFail("Alternate invalid character in middle of name", "foob@r"); + expectParseNameFail("First cap", "Foo"); + expectParseNameFail("All cap", "FOO"); + + // Multiple headers, invalid value(s) + expectParseNameFail("Multple headers, empty first header", ", bar"); + expectParseNameFail("Multple headers, both empty headers", ","); + expectParseNameFail("Multple headers, invalid character in first header", "f@oo, bar"); + expectParseNameFail("Multple headers, invalid character in both headers", "f@oo, b@r"); +} + +TEST(HTTPParsersTest, SuboriginParseValidPolicy) +{ + const Suborigin::SuboriginPolicyOptions unsafePostmessageSend[] = { Suborigin::SuboriginPolicyOptions::UnsafePostMessageSend }; + + expectParsePolicyPass("One policy", "foobar 'unsafe-postmessage-send';", unsafePostmessageSend, ARRAY_SIZE(unsafePostmessageSend)); + expectParsePolicyPass("One policy, whitespace all around", "foobar 'unsafe-postmessage-send' ; ", unsafePostmessageSend, ARRAY_SIZE(unsafePostmessageSend)); + expectParsePolicyPass("Multiple, same policies", "foobar 'unsafe-postmessage-send'; 'unsafe-postmessage-send';", unsafePostmessageSend, ARRAY_SIZE(unsafePostmessageSend)); + expectParsePolicyPass("One policy, unknown option", "foobar 'unknown-option';", {}, 0); +} + +TEST(HTTPParsersTest, SuboriginParseInvalidPolicy) +{ + expectParsePolicyFail("One policy, no suborigin name", "'unsafe-postmessage-send';"); + expectParsePolicyFail("One policy, invalid characters", "foobar 'un$afe-postmessage-send';"); + expectParsePolicyFail("One policy, caps", "foobar 'UNSAFE-POSTMESSAGE-SEND';"); + expectParsePolicyFail("One policy, missing first quote", "foobar unsafe-postmessage-send';"); + expectParsePolicyFail("One policy, missing last quote", "foobar 'unsafe-postmessage-send;"); + expectParsePolicyFail("One policy, missing semicolon at end", "foobar 'unsafe-postmessage-send'"); + expectParsePolicyFail("One policy, missing semicolon between options", "foobar 'unsafe-postmessage-send' 'unsafe-postmessage-send';"); +} + } // namespace blink diff --git a/third_party/WebKit/Source/platform/network/ResourceRequest.cpp b/third_party/WebKit/Source/platform/network/ResourceRequest.cpp index a42de218596f5..39dfcac65bc3a 100644 --- a/third_party/WebKit/Source/platform/network/ResourceRequest.cpp +++ b/third_party/WebKit/Source/platform/network/ResourceRequest.cpp @@ -232,7 +232,7 @@ void ResourceRequest::setHTTPOrigin(PassRefPtr origin) { setHTTPHeaderField(HTTPNames::Origin, origin->toAtomicString()); if (origin->hasSuborigin()) - setHTTPHeaderField(HTTPNames::Suborigin, AtomicString(origin->suboriginName())); + setHTTPHeaderField(HTTPNames::Suborigin, AtomicString(origin->suborigin()->name())); } void ResourceRequest::clearHTTPOrigin() diff --git a/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.cpp b/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.cpp index 4cac0e825fdbd..f8d132a275777 100644 --- a/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.cpp +++ b/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.cpp @@ -131,7 +131,7 @@ SecurityOrigin::SecurityOrigin(const KURL& url) // Suborigins are serialized into the host, so extract it if necessary. String suboriginName; if (deserializeSuboriginAndHost(m_host, suboriginName, m_host)) - addSuborigin(suboriginName); + m_suborigin.setName(suboriginName); // document.domain starts as m_host, but can be set by the DOM. m_domain = m_host; @@ -147,7 +147,6 @@ SecurityOrigin::SecurityOrigin() : m_protocol("") , m_host("") , m_domain("") - , m_suboriginName(WTF::String()) , m_port(InvalidPort) , m_effectivePort(InvalidPort) , m_isUnique(true) @@ -163,7 +162,7 @@ SecurityOrigin::SecurityOrigin(const SecurityOrigin* other) : m_protocol(other->m_protocol.isolatedCopy()) , m_host(other->m_host.isolatedCopy()) , m_domain(other->m_domain.isolatedCopy()) - , m_suboriginName(other->m_suboriginName.isolatedCopy()) + , m_suborigin(other->m_suborigin) , m_port(other->m_port) , m_effectivePort(other->m_effectivePort) , m_isUnique(other->m_isUnique) @@ -198,16 +197,6 @@ PassRefPtr SecurityOrigin::createUnique() return origin.release(); } -void SecurityOrigin::addSuborigin(const String& suborigin) -{ - ASSERT(RuntimeEnabledFeatures::suboriginsEnabled()); - // Changing suborigins midstream is bad. Very bad. It should not happen. - // This is, in fact, one of the very basic invariants that makes suborigins - // an effective security tool. - RELEASE_ASSERT(m_suboriginName.isNull() || m_suboriginName == suborigin); - m_suboriginName = suborigin; -} - PassRefPtr SecurityOrigin::isolatedCopy() const { return adoptRef(new SecurityOrigin(this)); @@ -275,7 +264,7 @@ bool SecurityOrigin::canAccessCheckSuborigins(const SecurityOrigin* other) const if (hasSuborigin() != other->hasSuborigin()) return false; - if (hasSuborigin() && suboriginName() != other->suboriginName()) + if (hasSuborigin() && suborigin()->name() != other->suborigin()->name()) return false; return canAccess(other); @@ -391,6 +380,16 @@ void SecurityOrigin::grantUniversalAccess() m_universalAccess = true; } +void SecurityOrigin::addSuborigin(const Suborigin& suborigin) +{ + ASSERT(RuntimeEnabledFeatures::suboriginsEnabled()); + // Changing suborigins midstream is bad. Very bad. It should not happen. + // This is, in fact, one of the very basic invariants that makes + // suborigins an effective security tool. + RELEASE_ASSERT(m_suborigin.name().isNull() || (m_suborigin.name() == suborigin.name())); + m_suborigin.setTo(suborigin); +} + void SecurityOrigin::blockLocalAccessFromLocalOrigin() { ASSERT(isLocal()); @@ -443,13 +442,32 @@ AtomicString SecurityOrigin::toAtomicString() const return toRawAtomicString(); } +String SecurityOrigin::toPhysicalOriginString() const +{ + if (isUnique()) + return "null"; + if (isLocal() && m_blockLocalAccessFromLocalOrigin) + return "null"; + return toRawStringIgnoreSuborigin(); +} + String SecurityOrigin::toRawString() const { if (m_protocol == "file") return "file://"; StringBuilder result; - buildRawString(result); + buildRawString(result, true); + return result.toString(); +} + +String SecurityOrigin::toRawStringIgnoreSuborigin() const +{ + if (m_protocol == "file") + return "file://"; + + StringBuilder result; + buildRawString(result, false); return result.toString(); } @@ -478,16 +496,16 @@ AtomicString SecurityOrigin::toRawAtomicString() const return AtomicString("file://", AtomicString::ConstructFromLiteral); StringBuilder result; - buildRawString(result); + buildRawString(result, true); return result.toAtomicString(); } -void SecurityOrigin::buildRawString(StringBuilder& builder) const +void SecurityOrigin::buildRawString(StringBuilder& builder, bool includeSuborigin) const { builder.append(m_protocol); builder.appendLiteral("://"); - if (hasSuborigin()) { - builder.append(m_suboriginName); + if (includeSuborigin && hasSuborigin()) { + builder.append(m_suborigin.name()); builder.appendLiteral("_"); } builder.append(m_host); @@ -531,7 +549,8 @@ bool SecurityOrigin::isSameSchemeHostPort(const SecurityOrigin* other) const bool SecurityOrigin::isSameSchemeHostPortAndSuborigin(const SecurityOrigin* other) const { - return isSameSchemeHostPort(other) && (suboriginName() == other->suboriginName()); + bool sameSuborigins = (hasSuborigin() == other->hasSuborigin()) && (!hasSuborigin() || (suborigin()->name() == other->suborigin()->name())); + return isSameSchemeHostPort(other) && sameSuborigins; } const KURL& SecurityOrigin::urlWithUniqueSecurityOrigin() diff --git a/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.h b/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.h index af13d6c3bfa12..6594a9fb6b3a1 100644 --- a/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.h +++ b/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.h @@ -31,6 +31,7 @@ #include "base/gtest_prod_util.h" #include "platform/PlatformExport.h" +#include "platform/weborigin/Suborigin.h" #include "wtf/Noncopyable.h" #include "wtf/ThreadSafeRefCounted.h" #include "wtf/text/WTFString.h" @@ -202,9 +203,9 @@ class PLATFORM_EXPORT SecurityOrigin : public RefCounted { // only ever be called once per SecurityOrigin(). If it is called on a // SecurityOrigin that has already had a suborigin assigned, it will hit a // RELEASE_ASSERT(). - void addSuborigin(const String&); - bool hasSuborigin() const { return !m_suboriginName.isNull(); } - const String& suboriginName() const { return m_suboriginName; } + bool hasSuborigin() const { return !m_suborigin.name().isNull(); } + const Suborigin* suborigin() const { return &m_suborigin; } + void addSuborigin(const Suborigin&); // By default 'file:' URLs may access other 'file:' URLs. This method // denies access. If either SecurityOrigin sets this flag, the access @@ -223,6 +224,9 @@ class PLATFORM_EXPORT SecurityOrigin : public RefCounted { // we shouldTreatURLSchemeAsNoAccess. String toString() const; AtomicString toAtomicString() const; + // Same as toString above, but ignores Suborigin, if present. This is + // generally not what you want. + String toPhysicalOriginString() const; // Similar to toString(), but does not take into account any factors that // could make the string return "null". @@ -264,14 +268,15 @@ class PLATFORM_EXPORT SecurityOrigin : public RefCounted { // FIXME: Rename this function to something more semantic. bool passesFileCheck(const SecurityOrigin*) const; - void buildRawString(StringBuilder&) const; + void buildRawString(StringBuilder&, bool includeSuborigin) const; + String toRawStringIgnoreSuborigin() const; static bool deserializeSuboriginAndHost(const String&, String&, String&); String m_protocol; String m_host; String m_domain; - String m_suboriginName; + Suborigin m_suborigin; unsigned short m_port; unsigned short m_effectivePort; bool m_isUnique; diff --git a/third_party/WebKit/Source/platform/weborigin/SecurityOriginHash.h b/third_party/WebKit/Source/platform/weborigin/SecurityOriginHash.h index 7f6972075617f..f396d760c3f5d 100644 --- a/third_party/WebKit/Source/platform/weborigin/SecurityOriginHash.h +++ b/third_party/WebKit/Source/platform/weborigin/SecurityOriginHash.h @@ -44,7 +44,7 @@ struct SecurityOriginHash { origin->protocol().impl() ? origin->protocol().impl()->hash() : 0, origin->host().impl() ? origin->host().impl()->hash() : 0, origin->port(), - origin->suboriginName().impl() ? origin->suboriginName().impl()->hash() : 0 + (origin->suborigin()->name().impl()) ? origin->suborigin()->name().impl()->hash() : 0 }; return StringHasher::hashMemory(hashCodes); } diff --git a/third_party/WebKit/Source/platform/weborigin/SecurityOriginTest.cpp b/third_party/WebKit/Source/platform/weborigin/SecurityOriginTest.cpp index c74840def2154..483f19a2e671c 100644 --- a/third_party/WebKit/Source/platform/weborigin/SecurityOriginTest.cpp +++ b/third_party/WebKit/Source/platform/weborigin/SecurityOriginTest.cpp @@ -34,6 +34,7 @@ #include "platform/blob/BlobURL.h" #include "platform/weborigin/KURL.h" #include "platform/weborigin/SecurityPolicy.h" +#include "platform/weborigin/Suborigin.h" #include "testing/gtest/include/gtest/gtest.h" #include "wtf/text/StringBuilder.h" #include "wtf/text/WTFString.h" @@ -220,19 +221,21 @@ TEST_F(SecurityOriginTest, Suborigins) RuntimeEnabledFeatures::setSuboriginsEnabled(true); RefPtr origin = SecurityOrigin::createFromString("https://test.com"); + Suborigin suborigin; + suborigin.setName("foobar"); EXPECT_FALSE(origin->hasSuborigin()); - origin->addSuborigin("foobar"); + origin->addSuborigin(suborigin); EXPECT_TRUE(origin->hasSuborigin()); - EXPECT_EQ("foobar", origin->suboriginName()); + EXPECT_EQ("foobar", origin->suborigin()->name()); origin = SecurityOrigin::createFromString("https://foobar_test.com"); EXPECT_EQ("https", origin->protocol()); EXPECT_EQ("test.com", origin->host()); - EXPECT_EQ("foobar", origin->suboriginName()); + EXPECT_EQ("foobar", origin->suborigin()->name()); origin = SecurityOrigin::createFromString("https://foobar_test.com"); EXPECT_TRUE(origin->hasSuborigin()); - EXPECT_EQ("foobar", origin->suboriginName()); + EXPECT_EQ("foobar", origin->suborigin()->name()); origin = SecurityOrigin::createFromString("https://foobar+test.com"); EXPECT_FALSE(origin->hasSuborigin()); @@ -244,7 +247,8 @@ TEST_F(SecurityOriginTest, Suborigins) EXPECT_FALSE(origin->hasSuborigin()); origin = SecurityOrigin::createFromString("https://foobar_test.com"); - EXPECT_DEATH(origin->addSuborigin("shouldhitassert"), ""); + Suborigin emptySuborigin; + EXPECT_DEATH(origin->addSuborigin(emptySuborigin), ""); } TEST_F(SecurityOriginTest, SuboriginsParsing) @@ -263,14 +267,26 @@ TEST_F(SecurityOriginTest, SuboriginsParsing) StringBuilder builder; origin = SecurityOrigin::createFromString("https://foobar_test.com"); - origin->buildRawString(builder); + origin->buildRawString(builder, true); EXPECT_EQ("https://foobar_test.com", builder.toString()); + EXPECT_EQ("https://foobar_test.com", origin->toString()); + builder.clear(); + origin->buildRawString(builder, false); + EXPECT_EQ("https://test.com", builder.toString()); + EXPECT_EQ("https://test.com", origin->toPhysicalOriginString()); + Suborigin suboriginObj; + suboriginObj.setName("foobar"); builder.clear(); origin = SecurityOrigin::createFromString("https://test.com"); - origin->addSuborigin("foobar"); - origin->buildRawString(builder); + origin->addSuborigin(suboriginObj); + origin->buildRawString(builder, true); EXPECT_EQ("https://foobar_test.com", builder.toString()); + EXPECT_EQ("https://foobar_test.com", origin->toString()); + builder.clear(); + origin->buildRawString(builder, false); + EXPECT_EQ("https://test.com", builder.toString()); + EXPECT_EQ("https://test.com", origin->toPhysicalOriginString()); } TEST_F(SecurityOriginTest, SuboriginsIsSameSchemeHostPortAndSuborigin) diff --git a/third_party/WebKit/Source/platform/weborigin/Suborigin.cpp b/third_party/WebKit/Source/platform/weborigin/Suborigin.cpp new file mode 100644 index 0000000000000..89361c7357c8d --- /dev/null +++ b/third_party/WebKit/Source/platform/weborigin/Suborigin.cpp @@ -0,0 +1,47 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "platform/weborigin/Suborigin.h" + +#include "platform/ParsingUtilities.h" +#include "wtf/ASCIICType.h" + +namespace blink { + +static unsigned emptyPolicy = static_cast(Suborigin::SuboriginPolicyOptions::None); + +Suborigin::Suborigin() + : m_optionsMask(emptyPolicy) +{ +} + +Suborigin::Suborigin(const Suborigin* other) + : m_name(other->m_name.isolatedCopy()) + , m_optionsMask(other->m_optionsMask) +{ +} + +void Suborigin::setTo(const Suborigin& other) +{ + m_name = other.m_name; + m_optionsMask = other.m_optionsMask; +} + +void Suborigin::addPolicyOption(SuboriginPolicyOptions option) +{ + m_optionsMask |= static_cast(option); +} + +bool Suborigin::policyContains(SuboriginPolicyOptions option) const +{ + return m_optionsMask & static_cast(option); +} + +void Suborigin::clear() +{ + m_name = String(); + m_optionsMask = emptyPolicy; +} + +} // namespace blink diff --git a/third_party/WebKit/Source/platform/weborigin/Suborigin.h b/third_party/WebKit/Source/platform/weborigin/Suborigin.h new file mode 100644 index 0000000000000..42dbfe0a34d54 --- /dev/null +++ b/third_party/WebKit/Source/platform/weborigin/Suborigin.h @@ -0,0 +1,41 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef Suborigin_h +#define Suborigin_h + +#include "platform/PlatformExport.h" +#include "wtf/Noncopyable.h" +#include "wtf/ThreadSafeRefCounted.h" +#include "wtf/text/WTFString.h" + +namespace blink { + +class PLATFORM_EXPORT Suborigin { +public: + enum class SuboriginPolicyOptions : unsigned { + None = 0, + UnsafePostMessageSend = 1 << 0 + }; + + Suborigin(); + explicit Suborigin(const Suborigin*); + + void setTo(const Suborigin&); + String name() const { return m_name; } + void setName(const String& name) { m_name = name; } + void addPolicyOption(SuboriginPolicyOptions); + bool policyContains(SuboriginPolicyOptions) const; + void clear(); + // For testing + unsigned optionsMask() const { return m_optionsMask; } + +private: + String m_name; + unsigned m_optionsMask; +}; + +} // namespace blink + +#endif // Suborigin_h