blob: 78601512ff7a6a5a896b74fe0b28c41adfedc4f7 [file] [log] [blame]
/*
* Copyright (c) 2014, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "base/strings/stringprintf.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/loader/referrer_utils.h"
#include "third_party/blink/renderer/bindings/core/v8/isolated_world_csp.h"
#include "third_party/blink/renderer/core/execution_context/agent.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/scheduler/public/event_loop.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
namespace blink {
using network::mojom::ContentSecurityPolicySource;
using network::mojom::ContentSecurityPolicyType;
class LocalDOMWindowTest : public PageTestBase {};
TEST_F(LocalDOMWindowTest, AttachExecutionContext) {
auto* scheduler = GetFrame().GetFrameScheduler();
auto* window = GetFrame().DomWindow();
EXPECT_TRUE(
window->GetAgent()->event_loop()->IsSchedulerAttachedForTest(scheduler));
window->FrameDestroyed();
EXPECT_FALSE(
window->GetAgent()->event_loop()->IsSchedulerAttachedForTest(scheduler));
}
TEST_F(LocalDOMWindowTest, referrerPolicyParsing) {
LocalDOMWindow* window = GetFrame().DomWindow();
EXPECT_EQ(network::mojom::ReferrerPolicy::kDefault,
window->GetReferrerPolicy());
struct TestCase {
const char* policy;
network::mojom::ReferrerPolicy expected;
bool uses_legacy_tokens;
} tests[] = {
{"", network::mojom::ReferrerPolicy::kDefault, false},
// Test that invalid policy values are ignored.
{"not-a-real-policy", network::mojom::ReferrerPolicy::kDefault, false},
{"not-a-real-policy,also-not-a-real-policy",
network::mojom::ReferrerPolicy::kDefault, false},
{"not-a-real-policy,unsafe-url", network::mojom::ReferrerPolicy::kAlways,
false},
{"unsafe-url,not-a-real-policy", network::mojom::ReferrerPolicy::kAlways,
false},
// Test parsing each of the policy values.
{"always", network::mojom::ReferrerPolicy::kAlways, true},
{"default",
ReferrerUtils::MojoReferrerPolicyResolveDefault(
network::mojom::ReferrerPolicy::kDefault),
true},
{"never", network::mojom::ReferrerPolicy::kNever, true},
{"no-referrer", network::mojom::ReferrerPolicy::kNever, false},
{"no-referrer-when-downgrade",
network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade, false},
{"origin", network::mojom::ReferrerPolicy::kOrigin, false},
{"origin-when-crossorigin",
network::mojom::ReferrerPolicy::kOriginWhenCrossOrigin, true},
{"origin-when-cross-origin",
network::mojom::ReferrerPolicy::kOriginWhenCrossOrigin, false},
{"same-origin", network::mojom::ReferrerPolicy::kSameOrigin, false},
{"strict-origin", network::mojom::ReferrerPolicy::kStrictOrigin, false},
{"strict-origin-when-cross-origin",
network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin, false},
{"unsafe-url", network::mojom::ReferrerPolicy::kAlways},
};
for (const auto test : tests) {
window->SetReferrerPolicy(network::mojom::ReferrerPolicy::kDefault);
if (test.uses_legacy_tokens) {
// Legacy tokens are supported only for meta-specified policy.
window->ParseAndSetReferrerPolicy(test.policy, kPolicySourceHttpHeader);
EXPECT_EQ(network::mojom::ReferrerPolicy::kDefault,
window->GetReferrerPolicy());
window->ParseAndSetReferrerPolicy(test.policy, kPolicySourceMetaTag);
} else {
window->ParseAndSetReferrerPolicy(test.policy, kPolicySourceHttpHeader);
}
EXPECT_EQ(test.expected, window->GetReferrerPolicy()) << test.policy;
}
}
TEST_F(LocalDOMWindowTest, referrerPolicyParsingWithCommas) {
LocalDOMWindow* window = GetFrame().DomWindow();
EXPECT_EQ(network::mojom::ReferrerPolicy::kDefault,
window->GetReferrerPolicy());
struct TestCase {
const char* policy;
network::mojom::ReferrerPolicy expected;
} tests[] = {
{"same-origin,strict-origin",
network::mojom::ReferrerPolicy::kStrictOrigin},
{"same-origin,not-a-real-policy,strict-origin",
network::mojom::ReferrerPolicy::kStrictOrigin},
{"strict-origin, same-origin, not-a-real-policy",
network::mojom::ReferrerPolicy::kSameOrigin},
};
for (const auto test : tests) {
window->SetReferrerPolicy(network::mojom::ReferrerPolicy::kDefault);
// Policies containing commas are ignored when specified by a Meta element.
window->ParseAndSetReferrerPolicy(test.policy, kPolicySourceMetaTag);
EXPECT_EQ(network::mojom::ReferrerPolicy::kDefault,
window->GetReferrerPolicy());
// Header-specified policy permits commas and returns the last valid policy.
window->ParseAndSetReferrerPolicy(test.policy, kPolicySourceHttpHeader);
EXPECT_EQ(test.expected, window->GetReferrerPolicy()) << test.policy;
}
}
TEST_F(LocalDOMWindowTest, OutgoingReferrer) {
NavigateTo(KURL("https://www.example.com/hoge#fuga?piyo"));
EXPECT_EQ("https://www.example.com/hoge",
GetFrame().DomWindow()->OutgoingReferrer());
}
TEST_F(LocalDOMWindowTest, OutgoingReferrerWithUniqueOrigin) {
NavigateTo(KURL("https://www.example.com/hoge#fuga?piyo"),
{{http_names::kContentSecurityPolicy, "sandbox allow-scripts"}});
EXPECT_TRUE(GetFrame().DomWindow()->GetSecurityOrigin()->IsOpaque());
EXPECT_EQ(String(), GetFrame().DomWindow()->OutgoingReferrer());
}
TEST_F(LocalDOMWindowTest, EnforceSandboxFlags) {
NavigateTo(KURL("http://example.test/"), {{http_names::kContentSecurityPolicy,
"sandbox allow-same-origin"}});
EXPECT_FALSE(GetFrame().DomWindow()->GetSecurityOrigin()->IsOpaque());
EXPECT_FALSE(
GetFrame().DomWindow()->GetSecurityOrigin()->IsPotentiallyTrustworthy());
NavigateTo(KURL("http://example.test/"),
{{http_names::kContentSecurityPolicy, "sandbox"}});
EXPECT_TRUE(GetFrame().DomWindow()->GetSecurityOrigin()->IsOpaque());
EXPECT_FALSE(
GetFrame().DomWindow()->GetSecurityOrigin()->IsPotentiallyTrustworthy());
// A unique origin does not bypass secure context checks unless it
// is also potentially trustworthy.
{
url::ScopedSchemeRegistryForTests scoped_registry;
url::AddStandardScheme("very-special-scheme", url::SCHEME_WITH_HOST);
SchemeRegistry::RegisterURLSchemeBypassingSecureContextCheck(
"very-special-scheme");
NavigateTo(KURL("very-special-scheme://example.test"),
{{http_names::kContentSecurityPolicy, "sandbox"}});
EXPECT_TRUE(GetFrame().DomWindow()->GetSecurityOrigin()->IsOpaque());
EXPECT_FALSE(GetFrame()
.DomWindow()
->GetSecurityOrigin()
->IsPotentiallyTrustworthy());
}
{
url::ScopedSchemeRegistryForTests scoped_registry;
url::AddStandardScheme("very-special-scheme", url::SCHEME_WITH_HOST);
url::AddSecureScheme("very-special-scheme");
NavigateTo(KURL("very-special-scheme://example.test"),
{{http_names::kContentSecurityPolicy, "sandbox"}});
EXPECT_TRUE(GetFrame().DomWindow()->GetSecurityOrigin()->IsOpaque());
EXPECT_TRUE(GetFrame()
.DomWindow()
->GetSecurityOrigin()
->IsPotentiallyTrustworthy());
NavigateTo(KURL("https://example.test"),
{{http_names::kContentSecurityPolicy, "sandbox"}});
EXPECT_TRUE(GetFrame().DomWindow()->GetSecurityOrigin()->IsOpaque());
EXPECT_TRUE(GetFrame()
.DomWindow()
->GetSecurityOrigin()
->IsPotentiallyTrustworthy());
}
}
// Tests ExecutionContext::GetContentSecurityPolicyForCurrentWorld().
TEST_F(PageTestBase, CSPForWorld) {
using ::testing::ElementsAre;
// Set a CSP for the main world.
const char* kMainWorldCSP = "connect-src https://google.com;";
GetFrame().DomWindow()->GetContentSecurityPolicy()->DidReceiveHeader(
kMainWorldCSP, *(GetFrame().DomWindow()->GetSecurityOrigin()),
ContentSecurityPolicyType::kEnforce, ContentSecurityPolicySource::kHTTP);
const Vector<
network::mojom::blink::ContentSecurityPolicyPtr>& parsed_main_world_csp =
GetFrame().DomWindow()->GetContentSecurityPolicy()->GetParsedPolicies();
LocalFrame* frame = &GetFrame();
ScriptState* main_world_script_state = ToScriptStateForMainWorld(frame);
v8::Isolate* isolate = main_world_script_state->GetIsolate();
constexpr int kIsolatedWorldWithoutCSPId = 1;
scoped_refptr<DOMWrapperWorld> world_without_csp =
DOMWrapperWorld::EnsureIsolatedWorld(isolate, kIsolatedWorldWithoutCSPId);
ASSERT_TRUE(world_without_csp->IsIsolatedWorld());
ScriptState* isolated_world_without_csp_script_state =
ToScriptState(frame, *world_without_csp);
const char* kIsolatedWorldCSP = "script-src 'none';";
constexpr int kIsolatedWorldWithCSPId = 2;
scoped_refptr<DOMWrapperWorld> world_with_csp =
DOMWrapperWorld::EnsureIsolatedWorld(isolate, kIsolatedWorldWithCSPId);
ASSERT_TRUE(world_with_csp->IsIsolatedWorld());
ScriptState* isolated_world_with_csp_script_state =
ToScriptState(frame, *world_with_csp);
IsolatedWorldCSP::Get().SetContentSecurityPolicy(
kIsolatedWorldWithCSPId, kIsolatedWorldCSP,
SecurityOrigin::Create(KURL("chrome-extension://123")));
// Returns the csp headers being used for the current world.
auto get_csp = [this]()
-> const Vector<network::mojom::blink::ContentSecurityPolicyPtr>& {
auto* csp =
GetFrame().DomWindow()->GetContentSecurityPolicyForCurrentWorld();
return csp->GetParsedPolicies();
};
{
SCOPED_TRACE("In main world.");
ScriptState::Scope scope(main_world_script_state);
EXPECT_EQ(get_csp(), parsed_main_world_csp);
}
{
SCOPED_TRACE("In isolated world without csp.");
ScriptState::Scope scope(isolated_world_without_csp_script_state);
// If we are in an isolated world with no CSP defined, we use the main world
// CSP.
EXPECT_EQ(get_csp(), parsed_main_world_csp);
}
{
SCOPED_TRACE("In isolated world with csp.");
ScriptState::Scope scope(isolated_world_with_csp_script_state);
// We use the isolated world's CSP if it specified one.
EXPECT_EQ(get_csp()[0]->header->header_value, kIsolatedWorldCSP);
}
}
} // namespace blink