Commit 3e165679 authored by Mike West's avatar Mike West Committed by Chromium LUCI CQ

Make `X-Frame-Options` part of network.mojom.ParsedHeaders.

This patch moves `X-Frame-Options` parsing out of AncestorThrottle, and
into the network.mojom.ParsedHeaders struct so that it's parsed along
with other headers that affect how we load resources during navigation.

Bug: 1153274
Change-Id: I2287de1f2610cb79a6dd4d37de6f3128c2c2b72f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2563489
Commit-Queue: Mike West <mkwst@chromium.org>
Reviewed-by: default avatarAntonio Sartori <antoniosartori@chromium.org>
Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Reviewed-by: default avatarDavid Roger <droger@chromium.org>
Reviewed-by: default avatarArthur Sonzogni <arthursonzogni@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#832392}
parent 15a8dbe6
......@@ -12,6 +12,7 @@
#include "components/google/core/common/google_util.h"
#include "net/base/url_util.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/public/mojom/x_frame_options.mojom.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "extensions/common/extension_urls.h"
......@@ -152,15 +153,21 @@ void GoogleURLLoaderThrottle::WillProcessResponse(
const GURL& response_url,
network::mojom::URLResponseHead* response_head,
bool* defer) {
// Built-in additional protection for the chrome web store origin.
// Built-in additional protection for the chrome web store origin by ensuring
// that the X-Frame-Options protection mechanism is set to either DENY or
// SAMEORIGIN.
GURL webstore_url(extension_urls::GetWebstoreLaunchURL());
if (response_url.SchemeIsHTTPOrHTTPS() &&
response_url.DomainIs(webstore_url.host_piece())) {
if (response_head && response_head->headers &&
!response_head->headers->HasHeaderValue("x-frame-options", "deny") &&
!response_head->headers->HasHeaderValue("x-frame-options",
"sameorigin")) {
response_head->headers->AddHeader("x-frame-options", "sameorigin");
// TODO(mkwst): Consider shifting this to a NavigationThrottle rather than
// relying on implicit ordering between this check and the time at which
// ParsedHeaders is created.
CHECK(response_head);
CHECK(response_head->parsed_headers);
if (response_head->parsed_headers->xfo !=
network::mojom::XFrameOptionsValue::kDeny) {
response_head->parsed_headers->xfo =
network::mojom::XFrameOptionsValue::kSameOrigin;
}
}
}
......
......@@ -263,15 +263,20 @@ const char* AncestorThrottle::GetNameForLogging() {
AncestorThrottle::AncestorThrottle(NavigationHandle* handle)
: NavigationThrottle(handle) {}
void AncestorThrottle::ParseXFrameOptionsError(const std::string& value,
HeaderDisposition disposition) {
DCHECK(disposition == HeaderDisposition::CONFLICT ||
disposition == HeaderDisposition::INVALID);
void AncestorThrottle::ParseXFrameOptionsError(
const net::HttpResponseHeaders* headers,
network::mojom::XFrameOptionsValue disposition) {
DCHECK(disposition == network::mojom::XFrameOptionsValue::kConflict ||
disposition == network::mojom::XFrameOptionsValue::kInvalid);
DCHECK(headers);
if (!navigation_handle()->GetRenderFrameHost())
return; // Some responses won't have a RFH (i.e. 204/205s or downloads).
std::string value;
headers->GetNormalizedHeader("X-Frame-Options", &value);
std::string message;
if (disposition == HeaderDisposition::CONFLICT) {
if (disposition == network::mojom::XFrameOptionsValue::kConflict) {
message = base::StringPrintf(
"Refused to display '%s' in a frame because it set multiple "
"'X-Frame-Options' headers with conflicting values "
......@@ -330,9 +335,9 @@ void AncestorThrottle::ConsoleErrorEmbeddingRequiresOptIn() {
}
void AncestorThrottle::ConsoleErrorXFrameOptions(
HeaderDisposition disposition) {
DCHECK(disposition == HeaderDisposition::DENY ||
disposition == HeaderDisposition::SAMEORIGIN);
network::mojom::XFrameOptionsValue disposition) {
DCHECK(disposition == network::mojom::XFrameOptionsValue::kDeny ||
disposition == network::mojom::XFrameOptionsValue::kSameOrigin);
if (!navigation_handle()->GetRenderFrameHost())
return; // Some responses won't have a RFH (i.e. 204/205s or downloads).
......@@ -343,7 +348,8 @@ void AncestorThrottle::ConsoleErrorXFrameOptions(
.GetURL()
.spec()
.c_str(),
disposition == HeaderDisposition::DENY ? "deny" : "sameorigin");
disposition == network::mojom::XFrameOptionsValue::kDeny ? "deny"
: "sameorigin");
// Log a console error in the parent of the current RenderFrameHost (as
// the current RenderFrameHost itself doesn't yet have a document).
......@@ -360,44 +366,43 @@ void AncestorThrottle::ConsoleErrorXFrameOptions(
AncestorThrottle::CheckResult AncestorThrottle::EvaluateXFrameOptions(
LoggingDisposition logging) {
std::string header_value;
NavigationRequest* request = NavigationRequest::From(navigation_handle());
HeaderDisposition disposition =
ParseXFrameOptionsHeader(request->GetResponseHeaders(), &header_value);
network::mojom::XFrameOptionsValue disposition =
request->response()->parsed_headers->xfo;
// If 'X-Frame-Options' would potentially block the response, check whether
// the 'frame-ancestors' CSP directive should take effect instead. See
// https://www.w3.org/TR/CSP/#frame-ancestors-and-frame-options
if (disposition != HeaderDisposition::NONE &&
disposition != HeaderDisposition::ALLOWALL &&
if (disposition != network::mojom::XFrameOptionsValue::kNone &&
disposition != network::mojom::XFrameOptionsValue::kAllowAll &&
HeadersContainFrameAncestorsCSP(request->response()->parsed_headers)) {
RecordXFrameOptionsUsage(XFrameOptionsHistogram::BYPASS);
return CheckResult::PROCEED;
}
switch (disposition) {
case HeaderDisposition::CONFLICT:
case network::mojom::XFrameOptionsValue::kConflict:
if (logging == LoggingDisposition::LOG_TO_CONSOLE)
ParseXFrameOptionsError(header_value, disposition);
ParseXFrameOptionsError(request->GetResponseHeaders(), disposition);
RecordXFrameOptionsUsage(XFrameOptionsHistogram::CONFLICT);
return CheckResult::BLOCK;
case HeaderDisposition::INVALID:
case network::mojom::XFrameOptionsValue::kInvalid:
if (logging == LoggingDisposition::LOG_TO_CONSOLE)
ParseXFrameOptionsError(header_value, disposition);
ParseXFrameOptionsError(request->GetResponseHeaders(), disposition);
RecordXFrameOptionsUsage(XFrameOptionsHistogram::INVALID);
// TODO(mkwst): Consider failing here, especially if we end up shipping
// a new default behavior which requires embedees to explicitly opt-in
// to being embedded: https://crbug.com/1153274.
return CheckResult::PROCEED;
case HeaderDisposition::DENY:
case network::mojom::XFrameOptionsValue::kDeny:
if (logging == LoggingDisposition::LOG_TO_CONSOLE)
ConsoleErrorXFrameOptions(disposition);
RecordXFrameOptionsUsage(XFrameOptionsHistogram::DENY);
return CheckResult::BLOCK;
case HeaderDisposition::SAMEORIGIN: {
case network::mojom::XFrameOptionsValue::kSameOrigin: {
// Block the request when any ancestor is not same-origin.
RenderFrameHostImpl* parent = ParentOrOuterDelegate(
request->frame_tree_node()->current_frame_host());
......@@ -428,10 +433,10 @@ AncestorThrottle::CheckResult AncestorThrottle::EvaluateXFrameOptions(
return CheckResult::PROCEED;
}
case HeaderDisposition::NONE:
case network::mojom::XFrameOptionsValue::kNone:
RecordXFrameOptionsUsage(XFrameOptionsHistogram::NONE);
return CheckResult::PROCEED;
case HeaderDisposition::ALLOWALL:
case network::mojom::XFrameOptionsValue::kAllowAll:
RecordXFrameOptionsUsage(XFrameOptionsHistogram::ALLOWALL);
return CheckResult::PROCEED;
}
......@@ -443,15 +448,13 @@ AncestorThrottle::CheckResult AncestorThrottle::EvaluateEmbeddingOptIn(
return CheckResult::PROCEED;
NavigationRequest* request = NavigationRequest::From(navigation_handle());
std::string unused_header_value;
HeaderDisposition xfo_value = ParseXFrameOptionsHeader(
request->GetResponseHeaders(), &unused_header_value);
// If embedding requires opt-in, then we check whether the response opted-into
// embedding via either an 'X-Frame-Options' header or a 'frame-ancestors'
// directive. If neither is present, the response will be blocked unless it is
// same-origin with its ancestor chain.
if (xfo_value == HeaderDisposition::NONE &&
if (request->response()->parsed_headers->xfo ==
network::mojom::XFrameOptionsValue::kNone &&
!HeadersContainFrameAncestorsCSP(request->response()->parsed_headers)) {
RenderFrameHostImpl* parent =
ParentOrOuterDelegate(request->frame_tree_node()->current_frame_host());
......@@ -599,46 +602,4 @@ bool AncestorThrottle::AllowsBlanketEnforcementOfRequiredCSP(
return false;
}
AncestorThrottle::HeaderDisposition AncestorThrottle::ParseXFrameOptionsHeader(
const net::HttpResponseHeaders* headers,
std::string* header_value) {
DCHECK(header_value);
if (!headers)
return HeaderDisposition::NONE;
// Process the 'X-Frame-Options header as per Section 2 of RFC7034:
// https://tools.ietf.org/html/rfc7034#section-2
//
// Note that we do not support the 'ALLOW-FROM' value, and we special-case
// the invalid "ALLOWALL" value due to its prevalance in the wild.
HeaderDisposition result = HeaderDisposition::NONE;
size_t iter = 0;
std::string value;
while (headers->EnumerateHeader(&iter, "x-frame-options", &value)) {
HeaderDisposition current = HeaderDisposition::INVALID;
base::StringPiece trimmed =
base::TrimWhitespaceASCII(value, base::TRIM_ALL);
if (!header_value->empty())
header_value->append(", ");
header_value->append(trimmed.as_string());
if (base::LowerCaseEqualsASCII(trimmed, "deny"))
current = HeaderDisposition::DENY;
else if (base::LowerCaseEqualsASCII(trimmed, "allowall"))
current = HeaderDisposition::ALLOWALL;
else if (base::LowerCaseEqualsASCII(trimmed, "sameorigin"))
current = HeaderDisposition::SAMEORIGIN;
else
current = HeaderDisposition::INVALID;
if (result == HeaderDisposition::NONE)
result = current;
else if (result != current)
result = HeaderDisposition::CONFLICT;
}
return result;
}
} // namespace content
......@@ -13,6 +13,7 @@
#include "content/public/browser/navigation_throttle.h"
#include "services/network/public/mojom/content_security_policy.mojom-forward.h"
#include "services/network/public/mojom/parsed_headers.mojom-forward.h"
#include "services/network/public/mojom/x_frame_options.mojom-forward.h"
class GURL;
......@@ -31,15 +32,6 @@ class NavigationHandle;
// rules, and blocking requests which violate them.
class CONTENT_EXPORT AncestorThrottle : public NavigationThrottle {
public:
enum class HeaderDisposition {
NONE = 0,
DENY,
SAMEORIGIN,
ALLOWALL,
INVALID,
CONFLICT
};
static std::unique_ptr<NavigationThrottle> MaybeCreateThrottleFor(
NavigationHandle* handle);
......@@ -65,9 +57,10 @@ class CONTENT_EXPORT AncestorThrottle : public NavigationThrottle {
NavigationThrottle::ThrottleCheckResult ProcessResponseImpl(
LoggingDisposition logging,
bool is_response_check);
void ParseXFrameOptionsError(const std::string& value,
HeaderDisposition disposition);
void ConsoleErrorXFrameOptions(HeaderDisposition disposition);
void ParseXFrameOptionsError(const net::HttpResponseHeaders* headers,
network::mojom::XFrameOptionsValue disposition);
void ConsoleErrorXFrameOptions(
network::mojom::XFrameOptionsValue disposition);
void ConsoleErrorEmbeddingRequiresOptIn();
CheckResult EvaluateXFrameOptions(LoggingDisposition logging);
CheckResult EvaluateFrameAncestors(
......@@ -80,13 +73,6 @@ class CONTENT_EXPORT AncestorThrottle : public NavigationThrottle {
const GURL& response_url,
const network::mojom::AllowCSPFromHeaderValuePtr& allow_csp_from);
// Parses an 'X-Frame-Options' header. If the result is either CONFLICT
// or INVALID, |header_value| will be populated with the value which caused
// the parse error.
HeaderDisposition ParseXFrameOptionsHeader(
const net::HttpResponseHeaders* headers,
std::string* header_value);
DISALLOW_COPY_AND_ASSIGN(AncestorThrottle);
};
......
......@@ -25,25 +25,6 @@ namespace content {
namespace {
using HeaderDisposition = AncestorThrottle::HeaderDisposition;
net::HttpResponseHeaders* GetAncestorHeaders(const char* xfo, const char* csp) {
std::string header_string("HTTP/1.1 200 OK\nX-Frame-Options: ");
header_string += xfo;
if (csp != nullptr) {
header_string += "\nContent-Security-Policy: ";
header_string += csp;
}
header_string += "\n\n";
std::replace(header_string.begin(), header_string.end(), '\n', '\0');
net::HttpResponseHeaders* headers =
new net::HttpResponseHeaders(header_string);
EXPECT_TRUE(headers->HasHeader("X-Frame-Options"));
if (csp != nullptr)
EXPECT_TRUE(headers->HasHeader("Content-Security-Policy"));
return headers;
}
network::mojom::ContentSecurityPolicyPtr ParsePolicy(
const std::string& policy) {
scoped_refptr<net::HttpResponseHeaders> headers(
......@@ -62,89 +43,6 @@ network::mojom::ContentSecurityPolicyPtr ParsePolicy(
class AncestorThrottleTest : public testing::Test {};
TEST_F(AncestorThrottleTest, ParsingXFrameOptions) {
struct TestCase {
const char* header;
AncestorThrottle::HeaderDisposition expected;
const char* value;
} cases[] = {
// Basic keywords
{"DENY", HeaderDisposition::DENY, "DENY"},
{"SAMEORIGIN", HeaderDisposition::SAMEORIGIN, "SAMEORIGIN"},
{"ALLOWALL", HeaderDisposition::ALLOWALL, "ALLOWALL"},
// Repeated keywords
{"DENY,DENY", HeaderDisposition::DENY, "DENY, DENY"},
{"SAMEORIGIN,SAMEORIGIN", HeaderDisposition::SAMEORIGIN,
"SAMEORIGIN, SAMEORIGIN"},
{"ALLOWALL,ALLOWALL", HeaderDisposition::ALLOWALL, "ALLOWALL, ALLOWALL"},
// Case-insensitive
{"deNy", HeaderDisposition::DENY, "deNy"},
{"sAmEorIgIn", HeaderDisposition::SAMEORIGIN, "sAmEorIgIn"},
{"AlLOWaLL", HeaderDisposition::ALLOWALL, "AlLOWaLL"},
// Trim whitespace
{" DENY", HeaderDisposition::DENY, "DENY"},
{"SAMEORIGIN ", HeaderDisposition::SAMEORIGIN, "SAMEORIGIN"},
{" ALLOWALL ", HeaderDisposition::ALLOWALL, "ALLOWALL"},
{" DENY", HeaderDisposition::DENY, "DENY"},
{"SAMEORIGIN ", HeaderDisposition::SAMEORIGIN, "SAMEORIGIN"},
{" ALLOWALL ", HeaderDisposition::ALLOWALL, "ALLOWALL"},
{" DENY , DENY ", HeaderDisposition::DENY, "DENY, DENY"},
{"SAMEORIGIN, SAMEORIGIN", HeaderDisposition::SAMEORIGIN,
"SAMEORIGIN, SAMEORIGIN"},
{"ALLOWALL ,ALLOWALL", HeaderDisposition::ALLOWALL,
"ALLOWALL, ALLOWALL"},
};
AncestorThrottle throttle(nullptr);
for (const auto& test : cases) {
SCOPED_TRACE(test.header);
scoped_refptr<net::HttpResponseHeaders> headers =
GetAncestorHeaders(test.header, nullptr);
std::string header_value;
EXPECT_EQ(test.expected,
throttle.ParseXFrameOptionsHeader(headers.get(), &header_value));
EXPECT_EQ(test.value, header_value);
}
}
TEST_F(AncestorThrottleTest, ErrorsParsingXFrameOptions) {
struct TestCase {
const char* header;
AncestorThrottle::HeaderDisposition expected;
const char* failure;
} cases[] = {
// Empty == Invalid.
{"", HeaderDisposition::INVALID, ""},
// Invalid
{"INVALID", HeaderDisposition::INVALID, "INVALID"},
{"INVALID DENY", HeaderDisposition::INVALID, "INVALID DENY"},
{"DENY DENY", HeaderDisposition::INVALID, "DENY DENY"},
{"DE NY", HeaderDisposition::INVALID, "DE NY"},
// Conflicts
{"INVALID,DENY", HeaderDisposition::CONFLICT, "INVALID, DENY"},
{"DENY,ALLOWALL", HeaderDisposition::CONFLICT, "DENY, ALLOWALL"},
{"SAMEORIGIN,DENY", HeaderDisposition::CONFLICT, "SAMEORIGIN, DENY"},
{"ALLOWALL,SAMEORIGIN", HeaderDisposition::CONFLICT,
"ALLOWALL, SAMEORIGIN"},
{"DENY, SAMEORIGIN", HeaderDisposition::CONFLICT, "DENY, SAMEORIGIN"}};
AncestorThrottle throttle(nullptr);
for (const auto& test : cases) {
SCOPED_TRACE(test.header);
scoped_refptr<net::HttpResponseHeaders> headers =
GetAncestorHeaders(test.header, nullptr);
std::string header_value;
EXPECT_EQ(test.expected,
throttle.ParseXFrameOptionsHeader(headers.get(), &header_value));
EXPECT_EQ(test.failure, header_value);
}
}
TEST_F(AncestorThrottleTest, AllowsBlanketEnforcementOfRequiredCSP) {
struct TestCase {
const char* name;
......
......@@ -110,6 +110,8 @@ component("cpp") {
"web_sandbox_flags.h",
"wrapper_shared_url_loader_factory.cc",
"wrapper_shared_url_loader_factory.h",
"x_frame_options_parser.cc",
"x_frame_options_parser.h",
]
if (!is_ios) {
......@@ -344,6 +346,7 @@ source_set("tests") {
"supports_loading_mode/supports_loading_mode_parser_unittest.cc",
"url_request_mojom_traits_unittest.cc",
"web_sandbox_flags_unittests.cc",
"x_frame_options_parser_unittest.cc",
]
if (!is_ios) {
......@@ -367,6 +370,12 @@ source_set("tests") {
public_deps = [ ":buildflags" ]
}
fuzzer_test("xfo_fuzzer") {
sources = [ "x_frame_options_parser_fuzzer.cc" ]
dict = "x_frame_options.dict"
deps = [ ":cpp" ]
}
fuzzer_test("cors_fuzzer") {
sources = [ "cors/cors_fuzzer.cc" ]
deps = [ ":cpp" ]
......
......@@ -11,6 +11,7 @@
#include "services/network/public/cpp/cross_origin_opener_policy_parser.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/origin_isolation_parser.h"
#include "services/network/public/cpp/x_frame_options_parser.h"
namespace network {
......@@ -49,6 +50,8 @@ mojom::ParsedHeadersPtr PopulateParsedHeaders(
if (headers->GetNormalizedHeader("Critical-CH", &critical_ch))
parsed_headers->critical_ch = ParseClientHintsHeader(critical_ch);
parsed_headers->xfo = ParseXFrameOptions(*headers);
return parsed_headers;
}
......
# Copyright 2020 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.
" "
","
"DENY"
"SAMEORIGIN"
"ALLOWALL"
"ALLOW-FROM"
"https://www.example.com/"
// Copyright 2020 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 "services/network/public/cpp/x_frame_options_parser.h"
#include <string>
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/mojom/x_frame_options.mojom-shared.h"
namespace network {
mojom::XFrameOptionsValue ParseXFrameOptions(
const net::HttpResponseHeaders& headers) {
// Process the 'X-Frame-Options' header:
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#the-x-frame-options-header
//
// Note that we do not support the 'ALLOW-FROM' value defined in RFC7034.
mojom::XFrameOptionsValue result = mojom::XFrameOptionsValue::kNone;
size_t iter = 0;
std::string value;
while (headers.EnumerateHeader(&iter, "x-frame-options", &value)) {
mojom::XFrameOptionsValue current = mojom::XFrameOptionsValue::kInvalid;
base::StringPiece trimmed =
base::TrimWhitespaceASCII(value, base::TRIM_ALL);
if (base::LowerCaseEqualsASCII(trimmed, "deny"))
current = mojom::XFrameOptionsValue::kDeny;
else if (base::LowerCaseEqualsASCII(trimmed, "allowall"))
current = mojom::XFrameOptionsValue::kAllowAll;
else if (base::LowerCaseEqualsASCII(trimmed, "sameorigin"))
current = mojom::XFrameOptionsValue::kSameOrigin;
if (result == mojom::XFrameOptionsValue::kNone)
result = current;
else if (result != current)
result = mojom::XFrameOptionsValue::kConflict;
}
return result;
}
} // namespace network
// Copyright 2020 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 SERVICES_NETWORK_PUBLIC_CPP_X_FRAME_OPTIONS_PARSER_H_
#define SERVICES_NETWORK_PUBLIC_CPP_X_FRAME_OPTIONS_PARSER_H_
#include "base/component_export.h"
#include "services/network/public/mojom/x_frame_options.mojom-forward.h"
namespace net {
class HttpResponseHeaders;
}
namespace network {
COMPONENT_EXPORT(NETWORK_CPP)
mojom::XFrameOptionsValue ParseXFrameOptions(const net::HttpResponseHeaders&);
} // namespace network
#endif // SERVICES_NETWORK_PUBLIC_CPP_X_FRAME_OPTIONS_PARSER_H_
// Copyright 2020 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 "services/network/public/cpp/x_frame_options_parser.h"
#include <string>
#include "net/http/http_response_headers.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
const std::string test_data(reinterpret_cast<const char*>(data), size);
std::string header_string("HTTP/1.1 200 OK\nX-Frame-Options: ");
header_string += test_data;
header_string += "\n\n";
std::replace(header_string.begin(), header_string.end(), '\n', '\0');
scoped_refptr<net::HttpResponseHeaders> headers =
new net::HttpResponseHeaders(header_string);
network::ParseXFrameOptions(*headers);
return 0;
}
// Copyright 2020 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 "services/network/public/cpp/x_frame_options_parser.h"
#include <string>
#include "net/http/http_response_headers.h"
#include "services/network/public/mojom/x_frame_options.mojom-shared.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
net::HttpResponseHeaders* ConstructHeader(const char* value) {
std::string header_string("HTTP/1.1 200 OK");
if (value) {
header_string += "\nX-Frame-Options: ";
header_string += value;
}
header_string += "\n\n";
std::replace(header_string.begin(), header_string.end(), '\n', '\0');
net::HttpResponseHeaders* headers =
new net::HttpResponseHeaders(header_string);
return headers;
}
} // namespace
namespace network {
TEST(XFrameOptionsTest, Parse) {
struct TestCase {
const char* header;
mojom::XFrameOptionsValue expected;
} cases[] = {
// Single values:
{nullptr, mojom::XFrameOptionsValue::kNone},
{"DENY", mojom::XFrameOptionsValue::kDeny},
{"SAMEORIGIN", mojom::XFrameOptionsValue::kSameOrigin},
{"ALLOWALL", mojom::XFrameOptionsValue::kAllowAll},
{"NOT-A-VALUE", mojom::XFrameOptionsValue::kInvalid},
{"DeNy", mojom::XFrameOptionsValue::kDeny},
{"SaMeOrIgIn", mojom::XFrameOptionsValue::kSameOrigin},
{"AllOWaLL", mojom::XFrameOptionsValue::kAllowAll},
// Repeated values:
{"DENY,DENY", mojom::XFrameOptionsValue::kDeny},
{"SAMEORIGIN,SAMEORIGIN", mojom::XFrameOptionsValue::kSameOrigin},
{"ALLOWALL,ALLOWALL", mojom::XFrameOptionsValue::kAllowAll},
{"DENY,DeNy", mojom::XFrameOptionsValue::kDeny},
{"SAMEORIGIN,SaMeOrIgIn", mojom::XFrameOptionsValue::kSameOrigin},
{"ALLOWALL,AllOWaLL", mojom::XFrameOptionsValue::kAllowAll},
{"INVALID,INVALID", mojom::XFrameOptionsValue::kInvalid},
{"INVALID,DIFFERENTLY-INVALID", mojom::XFrameOptionsValue::kInvalid},
// Conflicting values:
{"ALLOWALL,DENY", mojom::XFrameOptionsValue::kConflict},
{"ALLOWALL,SAMEORIGIN", mojom::XFrameOptionsValue::kConflict},
{"ALLOWALL,INVALID", mojom::XFrameOptionsValue::kConflict},
{"DENY,ALLOWALL", mojom::XFrameOptionsValue::kConflict},
{"DENY,SAMEORIGIN", mojom::XFrameOptionsValue::kConflict},
{"DENY,INVALID", mojom::XFrameOptionsValue::kConflict},
{"SAMEORIGIN,ALLOWALL", mojom::XFrameOptionsValue::kConflict},
{"SAMEORIGIN,DENY", mojom::XFrameOptionsValue::kConflict},
{"SAMEORIGIN,INVALID", mojom::XFrameOptionsValue::kConflict},
{"INVALID,DENY", mojom::XFrameOptionsValue::kConflict},
{"INVALID,SAMEORIGIN", mojom::XFrameOptionsValue::kConflict},
{"INVALID,ALLOWALL", mojom::XFrameOptionsValue::kConflict},
};
for (const auto& test : cases) {
SCOPED_TRACE(test.header);
scoped_refptr<net::HttpResponseHeaders> headers =
ConstructHeader(test.header);
EXPECT_EQ(test.expected, ParseXFrameOptions(*headers));
}
}
} // namespace network
......@@ -515,6 +515,7 @@ mojom("mojom") {
"url_response_head.mojom",
"web_client_hints_types.mojom",
"web_sandbox_flags.mojom",
"x_frame_options.mojom",
]
public_deps = [
......
......@@ -9,6 +9,7 @@ import "services/network/public/mojom/content_security_policy.mojom";
import "services/network/public/mojom/cross_origin_embedder_policy.mojom";
import "services/network/public/mojom/cross_origin_opener_policy.mojom";
import "services/network/public/mojom/web_client_hints_types.mojom";
import "services/network/public/mojom/x_frame_options.mojom";
// Holds the parsed representation of several security related HTTP headers.
// This struct should only be populated by network::PopulateParsedHeaders()
......@@ -62,4 +63,7 @@ struct ParsedHeaders {
// For more information, see:
// https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability#section-3
array<WebClientHintsType>? critical_ch;
// The parsed value of the X-Frame-Options header.
XFrameOptionsValue xfo = XFrameOptionsValue.kNone;
};
// Copyright 2020 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.
module network.mojom;
// This enum represents the possible values for the X-Frame-Options header:
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#the-x-frame-options-header.
enum XFrameOptionsValue {
kNone, // No XFO header is present.
kDeny, // XFO: DENY
kSameOrigin, // XFO: SAMEORIGIN
kAllowAll, // XFO: ALLOWALL
kInvalid, // XFO: [anything other than DENY, SAMEORIGIN, or ALLOWALL]
kConflict // Multiple XFO headers are present, with distinct values.
};
......@@ -209,7 +209,8 @@ blink::ParsedHeadersPtr ConvertToBlink(ParsedHeadersPtr parsed_headers) {
parsed_headers->critical_ch.has_value()
? base::make_optional(
ConvertToBlink(parsed_headers->critical_ch.value()))
: base::nullopt);
: base::nullopt,
parsed_headers->xfo);
}
} // namespace mojom
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment