blob: 32d553b87a554852abc4ee0e895ddf238ae9093f [file] [log] [blame]
// 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 "third_party/blink/renderer/core/html/html_iframe_element.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
namespace blink {
class HTMLIFrameElementTest : public testing::Test {
public:
scoped_refptr<const SecurityOrigin> GetOriginForFeaturePolicy(
HTMLIFrameElement* element) {
return element->GetOriginForFeaturePolicy();
}
void SetUp() final {
const KURL document_url("http://example.com");
page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
window_ = page_holder_->GetFrame().DomWindow();
window_->document()->SetURL(document_url);
window_->GetSecurityContext().SetSecurityOriginForTesting(
SecurityOrigin::Create(document_url));
frame_element_ =
MakeGarbageCollected<HTMLIFrameElement>(*window_->document());
}
void TearDown() final {
frame_element_.Clear();
window_.Clear();
page_holder_.reset();
}
protected:
std::unique_ptr<DummyPageHolder> page_holder_;
Persistent<LocalDOMWindow> window_;
Persistent<HTMLIFrameElement> frame_element_;
};
// Test that the correct origin is used when constructing the container policy,
// and that frames which should inherit their parent document's origin do so.
TEST_F(HTMLIFrameElementTest, FramesUseCorrectOrigin) {
frame_element_->setAttribute(html_names::kSrcAttr, "about:blank");
scoped_refptr<const SecurityOrigin> effective_origin =
GetOriginForFeaturePolicy(frame_element_);
EXPECT_TRUE(effective_origin->IsSameOriginWith(window_->GetSecurityOrigin()));
frame_element_->setAttribute(
html_names::kSrcAttr, "data:text/html;base64,PHRpdGxlPkFCQzwvdGl0bGU+");
effective_origin = GetOriginForFeaturePolicy(frame_element_);
EXPECT_FALSE(
effective_origin->IsSameOriginWith(window_->GetSecurityOrigin()));
EXPECT_TRUE(effective_origin->IsOpaque());
frame_element_->setAttribute(html_names::kSrcAttr, "http://example.net/");
effective_origin = GetOriginForFeaturePolicy(frame_element_);
EXPECT_FALSE(
effective_origin->IsSameOriginWith(window_->GetSecurityOrigin()));
EXPECT_FALSE(effective_origin->IsOpaque());
}
// Test that a unique origin is used when constructing the container policy in a
// sandboxed iframe.
TEST_F(HTMLIFrameElementTest, SandboxFramesUseCorrectOrigin) {
frame_element_->setAttribute(html_names::kSandboxAttr, "");
frame_element_->setAttribute(html_names::kSrcAttr, "http://example.com/");
scoped_refptr<const SecurityOrigin> effective_origin =
GetOriginForFeaturePolicy(frame_element_);
EXPECT_FALSE(
effective_origin->IsSameOriginWith(window_->GetSecurityOrigin()));
EXPECT_TRUE(effective_origin->IsOpaque());
frame_element_->setAttribute(html_names::kSrcAttr, "http://example.net/");
effective_origin = GetOriginForFeaturePolicy(frame_element_);
EXPECT_FALSE(
effective_origin->IsSameOriginWith(window_->GetSecurityOrigin()));
EXPECT_TRUE(effective_origin->IsOpaque());
}
// Test that a sandboxed iframe with the allow-same-origin sandbox flag uses the
// parent document's origin for the container policy.
TEST_F(HTMLIFrameElementTest, SameOriginSandboxFramesUseCorrectOrigin) {
frame_element_->setAttribute(html_names::kSandboxAttr, "allow-same-origin");
frame_element_->setAttribute(html_names::kSrcAttr, "http://example.com/");
scoped_refptr<const SecurityOrigin> effective_origin =
GetOriginForFeaturePolicy(frame_element_);
EXPECT_TRUE(effective_origin->IsSameOriginWith(window_->GetSecurityOrigin()));
EXPECT_FALSE(effective_origin->IsOpaque());
}
// Test that the parent document's origin is used when constructing the
// container policy in a srcdoc iframe.
TEST_F(HTMLIFrameElementTest, SrcdocFramesUseCorrectOrigin) {
frame_element_->setAttribute(html_names::kSrcdocAttr, "<title>title</title>");
scoped_refptr<const SecurityOrigin> effective_origin =
GetOriginForFeaturePolicy(frame_element_);
EXPECT_TRUE(effective_origin->IsSameOriginWith(window_->GetSecurityOrigin()));
}
// Test that a unique origin is used when constructing the container policy in a
// sandboxed iframe with a srcdoc.
TEST_F(HTMLIFrameElementTest, SandboxedSrcdocFramesUseCorrectOrigin) {
frame_element_->setAttribute(html_names::kSandboxAttr, "");
frame_element_->setAttribute(html_names::kSrcdocAttr, "<title>title</title>");
scoped_refptr<const SecurityOrigin> effective_origin =
GetOriginForFeaturePolicy(frame_element_);
EXPECT_FALSE(
effective_origin->IsSameOriginWith(window_->GetSecurityOrigin()));
EXPECT_TRUE(effective_origin->IsOpaque());
}
// Test that iframes with relative src urls correctly construct their origin
// relative to the parent document.
TEST_F(HTMLIFrameElementTest, RelativeURLsUseCorrectOrigin) {
// Host-relative URLs should resolve to the same domain as the parent.
frame_element_->setAttribute(html_names::kSrcAttr, "index2.html");
scoped_refptr<const SecurityOrigin> effective_origin =
GetOriginForFeaturePolicy(frame_element_);
EXPECT_TRUE(effective_origin->IsSameOriginWith(window_->GetSecurityOrigin()));
// Scheme-relative URLs should not resolve to the same domain as the parent.
frame_element_->setAttribute(html_names::kSrcAttr,
"//example.net/index2.html");
effective_origin = GetOriginForFeaturePolicy(frame_element_);
EXPECT_FALSE(
effective_origin->IsSameOriginWith(window_->GetSecurityOrigin()));
}
// Test that various iframe attribute configurations result in the correct
// container policies.
// Test that the correct container policy is constructed on an iframe element.
TEST_F(HTMLIFrameElementTest, DefaultContainerPolicy) {
frame_element_->setAttribute(html_names::kSrcAttr, "http://example.net/");
frame_element_->UpdateContainerPolicyForTests();
const ParsedFeaturePolicy& container_policy =
frame_element_->GetFramePolicy().container_policy;
EXPECT_EQ(0UL, container_policy.size());
}
// Test that the allow attribute results in a container policy which is
// restricted to the domain in the src attribute.
TEST_F(HTMLIFrameElementTest, AllowAttributeContainerPolicy) {
frame_element_->setAttribute(html_names::kSrcAttr, "http://example.net/");
frame_element_->setAttribute(html_names::kAllowAttr, "fullscreen");
frame_element_->UpdateContainerPolicyForTests();
const ParsedFeaturePolicy& container_policy1 =
frame_element_->GetFramePolicy().container_policy;
EXPECT_EQ(1UL, container_policy1.size());
EXPECT_EQ(mojom::blink::FeaturePolicyFeature::kFullscreen,
container_policy1[0].feature);
EXPECT_FALSE(container_policy1[0].matches_all_origins);
EXPECT_EQ(1UL, container_policy1[0].allowed_origins.size());
EXPECT_EQ("http://example.net",
container_policy1[0].allowed_origins.begin()->Serialize());
frame_element_->setAttribute(html_names::kAllowAttr, "payment; fullscreen");
frame_element_->UpdateContainerPolicyForTests();
const ParsedFeaturePolicy& container_policy2 =
frame_element_->GetFramePolicy().container_policy;
EXPECT_EQ(2UL, container_policy2.size());
EXPECT_TRUE(container_policy2[0].feature ==
mojom::blink::FeaturePolicyFeature::kFullscreen ||
container_policy2[1].feature ==
mojom::blink::FeaturePolicyFeature::kFullscreen);
EXPECT_TRUE(container_policy2[0].feature ==
mojom::blink::FeaturePolicyFeature::kPayment ||
container_policy2[1].feature ==
mojom::blink::FeaturePolicyFeature::kPayment);
EXPECT_EQ(1UL, container_policy2[0].allowed_origins.size());
EXPECT_EQ("http://example.net",
container_policy2[0].allowed_origins.begin()->Serialize());
EXPECT_FALSE(container_policy2[1].matches_all_origins);
EXPECT_EQ(1UL, container_policy2[1].allowed_origins.size());
EXPECT_EQ("http://example.net",
container_policy2[1].allowed_origins.begin()->Serialize());
}
// Test the ConstructContainerPolicy method when no attributes are set on the
// iframe element.
TEST_F(HTMLIFrameElementTest, ConstructEmptyContainerPolicy) {
ParsedFeaturePolicy container_policy =
frame_element_->ConstructContainerPolicy();
EXPECT_EQ(0UL, container_policy.size());
}
// Test the ConstructContainerPolicy method when the "allow" attribute is used
// to enable features in the frame.
TEST_F(HTMLIFrameElementTest, ConstructContainerPolicy) {
frame_element_->setAttribute(html_names::kAllowAttr, "payment; usb");
ParsedFeaturePolicy container_policy =
frame_element_->ConstructContainerPolicy();
EXPECT_EQ(2UL, container_policy.size());
EXPECT_EQ(mojom::blink::FeaturePolicyFeature::kPayment,
container_policy[0].feature);
EXPECT_FALSE(container_policy[0].matches_all_origins);
EXPECT_EQ(1UL, container_policy[0].allowed_origins.size());
EXPECT_TRUE(container_policy[0].allowed_origins.begin()->IsSameOriginWith(
GetOriginForFeaturePolicy(frame_element_)->ToUrlOrigin()));
EXPECT_EQ(mojom::blink::FeaturePolicyFeature::kUsb,
container_policy[1].feature);
EXPECT_EQ(1UL, container_policy[1].allowed_origins.size());
EXPECT_TRUE(container_policy[1].allowed_origins.begin()->IsSameOriginWith(
GetOriginForFeaturePolicy(frame_element_)->ToUrlOrigin()));
}
// Test the ConstructContainerPolicy method when the "allowfullscreen" attribute
// is used to enable fullscreen in the frame.
TEST_F(HTMLIFrameElementTest, ConstructContainerPolicyWithAllowFullscreen) {
frame_element_->SetBooleanAttribute(html_names::kAllowfullscreenAttr, true);
ParsedFeaturePolicy container_policy =
frame_element_->ConstructContainerPolicy();
EXPECT_EQ(1UL, container_policy.size());
EXPECT_EQ(mojom::blink::FeaturePolicyFeature::kFullscreen,
container_policy[0].feature);
EXPECT_TRUE(container_policy[0].matches_all_origins);
}
// Test the ConstructContainerPolicy method when the "allowpaymentrequest"
// attribute is used to enable the paymentrequest API in the frame.
TEST_F(HTMLIFrameElementTest, ConstructContainerPolicyWithAllowPaymentRequest) {
frame_element_->setAttribute(html_names::kAllowAttr, "usb");
frame_element_->SetBooleanAttribute(html_names::kAllowpaymentrequestAttr,
true);
ParsedFeaturePolicy container_policy =
frame_element_->ConstructContainerPolicy();
EXPECT_EQ(2UL, container_policy.size());
EXPECT_EQ(mojom::blink::FeaturePolicyFeature::kUsb,
container_policy[0].feature);
EXPECT_FALSE(container_policy[0].matches_all_origins);
EXPECT_EQ(1UL, container_policy[0].allowed_origins.size());
EXPECT_TRUE(container_policy[0].allowed_origins.begin()->IsSameOriginWith(
GetOriginForFeaturePolicy(frame_element_)->ToUrlOrigin()));
EXPECT_EQ(mojom::blink::FeaturePolicyFeature::kPayment,
container_policy[1].feature);
}
// Test the ConstructContainerPolicy method when both "allowfullscreen" and
// "allowpaymentrequest" attributes are set on the iframe element, and the
// "allow" attribute is also used to override the paymentrequest feature. In the
// resulting container policy, the payment and usb features should be enabled
// only for the frame's origin, (since the allow attribute overrides
// allowpaymentrequest,) while fullscreen should be enabled for all origins.
TEST_F(HTMLIFrameElementTest, ConstructContainerPolicyWithAllowAttributes) {
frame_element_->setAttribute(html_names::kAllowAttr, "payment; usb");
frame_element_->SetBooleanAttribute(html_names::kAllowfullscreenAttr, true);
frame_element_->SetBooleanAttribute(html_names::kAllowpaymentrequestAttr,
true);
ParsedFeaturePolicy container_policy =
frame_element_->ConstructContainerPolicy();
EXPECT_EQ(3UL, container_policy.size());
EXPECT_EQ(mojom::blink::FeaturePolicyFeature::kPayment,
container_policy[0].feature);
EXPECT_FALSE(container_policy[0].matches_all_origins);
EXPECT_EQ(1UL, container_policy[0].allowed_origins.size());
EXPECT_TRUE(container_policy[0].allowed_origins.begin()->IsSameOriginWith(
GetOriginForFeaturePolicy(frame_element_)->ToUrlOrigin()));
EXPECT_EQ(mojom::blink::FeaturePolicyFeature::kUsb,
container_policy[1].feature);
EXPECT_EQ(1UL, container_policy[1].allowed_origins.size());
EXPECT_TRUE(container_policy[1].allowed_origins.begin()->IsSameOriginWith(
GetOriginForFeaturePolicy(frame_element_)->ToUrlOrigin()));
EXPECT_EQ(mojom::blink::FeaturePolicyFeature::kFullscreen,
container_policy[2].feature);
}
using HTMLIFrameElementSimTest = SimTest;
TEST_F(HTMLIFrameElementSimTest, PolicyAttributeParsingError) {
blink::ScopedDocumentPolicyForTest sdp(true);
SimRequest main_resource("https://example.com", "text/html");
LoadURL("https://example.com");
main_resource.Complete(R"(
<iframe policy="bad-feature-name"></iframe>
)");
// Note: Parsing of policy attribute string, i.e. call to
// HTMLFrameOwnerElement::UpdateRequiredPolicy(), happens twice in above
// situation:
// - HTMLFrameOwnerElement::LoadOrRedirectSubframe()
// - HTMLIFrameElement::ParseAttribute()
EXPECT_EQ(ConsoleMessages().size(), 2u);
for (const auto& message : ConsoleMessages()) {
EXPECT_TRUE(
message.StartsWith("Unrecognized document policy feature name"));
}
}
TEST_F(HTMLIFrameElementSimTest, AllowAttributeParsingError) {
SimRequest main_resource("https://example.com", "text/html");
LoadURL("https://example.com");
main_resource.Complete(R"(
<iframe
allow="bad-feature-name"
allowfullscreen
allowpayment
sandbox=""></iframe>
)");
EXPECT_EQ(ConsoleMessages().size(), 1u)
<< "Allow attribute parsing should only generate console message once, "
"even though there might be multiple call to "
"FeaturePolicyParser::ParseAttribute.";
EXPECT_TRUE(ConsoleMessages().front().StartsWith("Unrecognized feature"))
<< "Expect feature policy parser raising error for unrecognized feature "
"but got: "
<< ConsoleMessages().front();
}
} // namespace blink