diff options
6 files changed, 156 insertions, 14 deletions
diff --git a/third_party/WebKit/LayoutTests/http/tests/navigation/pushstate-at-unique-origin-denied.html b/third_party/WebKit/LayoutTests/http/tests/navigation/pushstate-at-unique-origin-denied.html index a3f0189..ee8c324 100644 --- a/third_party/WebKit/LayoutTests/http/tests/navigation/pushstate-at-unique-origin-denied.html +++ b/third_party/WebKit/LayoutTests/http/tests/navigation/pushstate-at-unique-origin-denied.html @@ -4,7 +4,15 @@ <script> test(function () { assert_throws('SecurityError', function () { - history.pushState(null, null, document.URL); + history.pushState(null, null, document.URL + "/path"); }); -}, 'pushState at unique origin should fail with SecurityError'); +}, 'pushState to a new path in unique origin should fail with SecurityError'); +test(function () { + try { + history.pushState(null, null, document.URL + "#hash"); + done(); + } catch (e) { + assert_unreached("pushState to a new hash should not fail."); + } +}, 'pushState to new hash in unique origin should not fail with SecurityError'); </script> diff --git a/third_party/WebKit/LayoutTests/http/tests/navigation/pushstate-whitelisted-at-unique-origin-denied.html b/third_party/WebKit/LayoutTests/http/tests/navigation/pushstate-whitelisted-at-unique-origin-denied.html index c89da59..7a48831 100644 --- a/third_party/WebKit/LayoutTests/http/tests/navigation/pushstate-whitelisted-at-unique-origin-denied.html +++ b/third_party/WebKit/LayoutTests/http/tests/navigation/pushstate-whitelisted-at-unique-origin-denied.html @@ -8,7 +8,16 @@ test(function () { test(function () { assert_throws('SecurityError', function () { - history.pushState(null, null, document.URL); + history.pushState(null, null, document.URL + "/path"); }); }, 'pushState at unique origin should fail with SecurityError (even with whitelisted origins)'); + +test(function () { + try { + history.pushState(null, null, document.URL + "#hash"); + done(); + } catch (e) { + assert_unreached("pushState to a new hash should not fail."); + } +}, 'pushState to new hash in unique origin should not fail with SecurityError'); </script> diff --git a/third_party/WebKit/Source/core/core.gypi b/third_party/WebKit/Source/core/core.gypi index a14d16f..cd89ae0 100644 --- a/third_party/WebKit/Source/core/core.gypi +++ b/third_party/WebKit/Source/core/core.gypi @@ -3949,6 +3949,7 @@ 'fileapi/FileListTest.cpp', 'fileapi/FileTest.cpp', 'frame/FrameViewTest.cpp', + 'frame/HistoryTest.cpp', 'frame/ImageBitmapTest.cpp', 'frame/OriginsUsingFeaturesTest.cpp', 'frame/RootFrameViewportTest.cpp', diff --git a/third_party/WebKit/Source/core/frame/History.cpp b/third_party/WebKit/Source/core/frame/History.cpp index cfbaefc..0e6fee8 100644 --- a/third_party/WebKit/Source/core/frame/History.cpp +++ b/third_party/WebKit/Source/core/frame/History.cpp @@ -182,24 +182,25 @@ KURL History::urlForState(const String& urlString) return KURL(document->baseURL(), urlString); } -bool History::canChangeToUrl(const KURL& url) +bool History::canChangeToUrl(const KURL& url, SecurityOrigin* documentOrigin, const KURL& documentURL) { if (!url.isValid()) return false; - Document* document = m_frame->document(); - SecurityOrigin* origin = document->securityOrigin(); - if (origin->isGrantedUniversalAccess()) + if (documentOrigin->isGrantedUniversalAccess()) return true; - if (origin->isUnique()) - return false; + // We allow sandboxed documents, `data:`/`file:` URLs, etc. to use + // 'pushState'/'replaceState' to modify the URL fragment: see + // https://crbug.com/528681 for the compatibility concerns. + if (documentOrigin->isUnique() || documentOrigin->isLocal()) + return equalIgnoringFragmentIdentifier(url, documentURL); - if (!equalIgnoringPathQueryAndFragment(url, document->url())) + if (!equalIgnoringPathQueryAndFragment(url, documentURL)) return false; RefPtr<SecurityOrigin> requestedOrigin = SecurityOrigin::create(url); - if (requestedOrigin->isUnique() || !requestedOrigin->isSameSchemeHostPort(origin)) + if (requestedOrigin->isUnique() || !requestedOrigin->isSameSchemeHostPort(documentOrigin)) return false; return true; @@ -211,7 +212,7 @@ void History::stateObjectAdded(PassRefPtr<SerializedScriptValue> data, const Str return; KURL fullURL = urlForState(urlString); - if (!canChangeToUrl(fullURL)) { + if (!canChangeToUrl(fullURL, m_frame->document()->securityOrigin(), m_frame->document()->url())) { // We can safely expose the URL to JavaScript, as a) no redirection takes place: JavaScript already had this URL, b) JavaScript can only access a same-origin History object. exceptionState.throwSecurityError("A history state object with URL '" + fullURL.elidedString() + "' cannot be created in a document with origin '" + m_frame->document()->securityOrigin()->toString() + "' and URL '" + m_frame->document()->url().elidedString() + "'."); return; diff --git a/third_party/WebKit/Source/core/frame/History.h b/third_party/WebKit/Source/core/frame/History.h index 626f64a..4b63ba5 100644 --- a/third_party/WebKit/Source/core/frame/History.h +++ b/third_party/WebKit/Source/core/frame/History.h @@ -26,6 +26,7 @@ #ifndef History_h #define History_h +#include "base/gtest_prod_util.h" #include "bindings/core/v8/ScriptWrappable.h" #include "bindings/core/v8/SerializedScriptValue.h" #include "core/loader/FrameLoaderTypes.h" @@ -39,8 +40,9 @@ class LocalFrame; class KURL; class ExecutionContext; class ExceptionState; +class SecurityOrigin; -class History final : public GarbageCollectedFinalized<History>, public ScriptWrappable, public DOMWindowProperty { +class CORE_EXPORT History final : public GarbageCollectedFinalized<History>, public ScriptWrappable, public DOMWindowProperty { DEFINE_WRAPPERTYPEINFO(); WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(History); public: @@ -76,10 +78,15 @@ public: DECLARE_VIRTUAL_TRACE(); private: + FRIEND_TEST_ALL_PREFIXES(HistoryTest, CanChangeToURL); + FRIEND_TEST_ALL_PREFIXES(HistoryTest, CanChangeToURLInFileOrigin); + FRIEND_TEST_ALL_PREFIXES(HistoryTest, CanChangeToURLInUniqueOrigin); + explicit History(LocalFrame*); + static bool canChangeToUrl(const KURL&, SecurityOrigin*, const KURL& documentURL); + KURL urlForState(const String& url); - bool canChangeToUrl(const KURL& url); void stateObjectAdded(PassRefPtr<SerializedScriptValue>, const String& title, const String& url, HistoryScrollRestorationType, FrameLoadType, ExceptionState&); SerializedScriptValue* stateInternal() const; diff --git a/third_party/WebKit/Source/core/frame/HistoryTest.cpp b/third_party/WebKit/Source/core/frame/HistoryTest.cpp new file mode 100644 index 0000000..bb5e573 --- /dev/null +++ b/third_party/WebKit/Source/core/frame/HistoryTest.cpp @@ -0,0 +1,116 @@ +// 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/frame/History.h" + +#include "platform/weborigin/KURL.h" +#include "platform/weborigin/SecurityOrigin.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace blink { + +class HistoryTest : public ::testing::Test { +}; + +TEST_F(HistoryTest, CanChangeToURL) +{ + struct TestCase { + const char* url; + const char* documentURL; + bool expected; + } cases[] = { + {"http://example.com/", "http://example.com/", true}, + {"http://example.com/#hash", "http://example.com/", true}, + {"http://example.com/path", "http://example.com/", true}, + {"http://example.com/path#hash", "http://example.com/", true}, + {"http://example.com/path?query", "http://example.com/", true}, + {"http://example.com/path?query#hash", "http://example.com/", true}, + {"http://example.com:80/", "http://example.com/", true}, + {"http://example.com:80/#hash", "http://example.com/", true}, + {"http://example.com:80/path", "http://example.com/", true}, + {"http://example.com:80/path#hash", "http://example.com/", true}, + {"http://example.com:80/path?query", "http://example.com/", true}, + {"http://example.com:80/path?query#hash", "http://example.com/", true}, + {"http://not-example.com:80/", "http://example.com/", false}, + {"http://not-example.com:80/#hash", "http://example.com/", false}, + {"http://not-example.com:80/path", "http://example.com/", false}, + {"http://not-example.com:80/path#hash", "http://example.com/", false}, + {"http://not-example.com:80/path?query", "http://example.com/", false}, + {"http://not-example.com:80/path?query#hash", "http://example.com/", false}, + {"http://example.com:81/", "http://example.com/", false}, + {"http://example.com:81/#hash", "http://example.com/", false}, + {"http://example.com:81/path", "http://example.com/", false}, + {"http://example.com:81/path#hash", "http://example.com/", false}, + {"http://example.com:81/path?query", "http://example.com/", false}, + {"http://example.com:81/path?query#hash", "http://example.com/", false}, + }; + + for (const auto& test : cases) { + KURL url(ParsedURLString, test.url); + KURL documentURL(ParsedURLString, test.documentURL); + RefPtr<SecurityOrigin> documentOrigin = SecurityOrigin::create(documentURL); + EXPECT_EQ(test.expected, History::canChangeToUrl(url, documentOrigin.get(), documentURL)); + } +} + +TEST_F(HistoryTest, CanChangeToURLInFileOrigin) +{ + struct TestCase { + const char* url; + const char* documentURL; + bool expected; + } cases[] = { + {"file:///path/to/file/", "file:///path/to/file/", true}, + {"file:///path/to/file/#hash", "file:///path/to/file/", true}, + {"file:///path/to/file/path", "file:///path/to/file/", false}, + {"file:///path/to/file/path#hash", "file:///path/to/file/", false}, + {"file:///path/to/file/path?query", "file:///path/to/file/", false}, + {"file:///path/to/file/path?query#hash", "file:///path/to/file/", false}, + }; + + for (const auto& test : cases) { + KURL url(ParsedURLString, test.url); + KURL documentURL(ParsedURLString, test.documentURL); + RefPtr<SecurityOrigin> documentOrigin = SecurityOrigin::create(documentURL); + EXPECT_EQ(test.expected, History::canChangeToUrl(url, documentOrigin.get(), documentURL)); + } +} + +TEST_F(HistoryTest, CanChangeToURLInUniqueOrigin) +{ + struct TestCase { + const char* url; + const char* documentURL; + bool expected; + } cases[] = { + {"http://example.com/", "http://example.com/", true}, + {"http://example.com/#hash", "http://example.com/", true}, + {"http://example.com/path", "http://example.com/", false}, + {"http://example.com/path#hash", "http://example.com/", false}, + {"http://example.com/path?query", "http://example.com/", false}, + {"http://example.com/path?query#hash", "http://example.com/", false}, + {"http://example.com:80/", "http://example.com/", true}, + {"http://example.com:80/#hash", "http://example.com/", true}, + {"http://example.com:80/path", "http://example.com/", false}, + {"http://example.com:80/path#hash", "http://example.com/", false}, + {"http://example.com:80/path?query", "http://example.com/", false}, + {"http://example.com:80/path?query#hash", "http://example.com/", false}, + {"http://example.com:81/", "http://example.com/", false}, + {"http://example.com:81/#hash", "http://example.com/", false}, + {"http://example.com:81/path", "http://example.com/", false}, + {"http://example.com:81/path#hash", "http://example.com/", false}, + {"http://example.com:81/path?query", "http://example.com/", false}, + {"http://example.com:81/path?query#hash", "http://example.com/", false}, + }; + + for (const auto& test : cases) { + KURL url(ParsedURLString, test.url); + KURL documentURL(ParsedURLString, test.documentURL); + RefPtr<SecurityOrigin> documentOrigin = SecurityOrigin::createUnique(); + EXPECT_EQ(test.expected, History::canChangeToUrl(url, documentOrigin.get(), documentURL)); + } +} + +} // namespace blink + |
