Commit dca3ad11 authored by Jeremy Roman's avatar Jeremy Roman Committed by Commit Bot

Support parsing Supports-Loading-Mode header from string and response headers.

It must be a list of simple tokens from a known list, formatted as an
HTTP structured header list. All unknown items are ignored, including
parameterized items, inner lists, and non-tokens.

Unit tests included.

This is the "parser" described in:
  https://docs.google.com/document/d/1IpqYg1Y4BX9U9LBELNUAaKlygFvOKo6MAmS8nXMLVlE/edit

Bug: 1141083
Change-Id: I914220bf6110c789c921965b65100823b9a11df9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2492882
Commit-Queue: Jeremy Roman <jbroman@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#821522}
parent a7b2f5dd
......@@ -103,6 +103,8 @@ component("cpp") {
"source_stream_to_data_pipe.h",
"spki_hash_set.cc",
"spki_hash_set.h",
"supports_loading_mode/supports_loading_mode_parser.cc",
"supports_loading_mode/supports_loading_mode_parser.h",
"trust_token_operation_authorization.h",
"weak_wrapper_shared_url_loader_factory.cc",
"weak_wrapper_shared_url_loader_factory.h",
......@@ -326,6 +328,7 @@ source_set("tests") {
"simple_url_loader_unittest.cc",
"site_for_cookies_mojom_traits_unittest.cc",
"source_stream_to_data_pipe_unittest.cc",
"supports_loading_mode/supports_loading_mode_parser_unittest.cc",
"url_request_mojom_traits_unittest.cc",
"web_sandbox_flags_unittests.cc",
]
......
This is a parser for the Supports-Loading-Mode header, described here:
https://github.com/jeremyroman/alternate-loading-modes/blob/gh-pages/opt-in.md
// 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/supports_loading_mode/supports_loading_mode_parser.h"
#include "base/optional.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_piece.h"
#include "net/http/http_response_headers.h"
#include "net/http/structured_headers.h"
#include "services/network/public/mojom/supports_loading_mode.mojom.h"
namespace network {
namespace {
constexpr base::StringPiece kSupportsLoadingMode = "Supports-Loading-Mode";
constexpr struct KnownLoadingMode {
base::StringPiece token;
mojom::LoadingMode enumerator;
} kKnownLoadingModes[] = {
{"default", mojom::LoadingMode::kDefault},
{"uncredentialed-prefetch", mojom::LoadingMode::kUncredentialedPrefetch},
{"uncredentialed-prerender", mojom::LoadingMode::kUncredentialedPrerender},
};
} // namespace
mojom::SupportsLoadingModePtr ParseSupportsLoadingMode(
base::StringPiece header_value) {
// A parse error in the HTTP structured headers syntax is a parse error for
// the header value as a whole.
auto list = net::structured_headers::ParseList(header_value);
if (!list)
return nullptr;
// The default loading mode is assumed to be supported.
std::vector<mojom::LoadingMode> modes{mojom::LoadingMode::kDefault};
for (const net::structured_headers::ParameterizedMember& member : *list) {
// No supported mode currently is specified as an inner list or takes
// parameters.
if (member.member_is_inner_list || !member.params.empty())
continue;
// All supported modes are tokens.
const net::structured_headers::ParameterizedItem& item = member.member[0];
DCHECK(item.params.empty());
if (!item.item.is_token())
continue;
// Each supported token maps 1:1 to an enumerator.
const auto& token = item.item.GetString();
const auto* it =
base::ranges::find(kKnownLoadingModes, token, &KnownLoadingMode::token);
if (it == base::ranges::end(kKnownLoadingModes))
continue;
modes.push_back(it->enumerator);
}
// Order and repetition are not significant.
// Canonicalize by making the vector sorted and unique.
base::ranges::sort(modes);
modes.erase(base::ranges::unique(modes), modes.end());
return mojom::SupportsLoadingMode::New(std::move(modes));
}
mojom::SupportsLoadingModePtr ParseSupportsLoadingMode(
const net::HttpResponseHeaders& headers) {
std::string header_value;
if (!headers.GetNormalizedHeader(kSupportsLoadingMode, &header_value))
return nullptr;
return ParseSupportsLoadingMode(header_value);
}
} // 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_SUPPORTS_LOADING_MODE_SUPPORTS_LOADING_MODE_PARSER_H_
#define SERVICES_NETWORK_PUBLIC_CPP_SUPPORTS_LOADING_MODE_SUPPORTS_LOADING_MODE_PARSER_H_
#include "base/component_export.h"
#include "base/strings/string_piece_forward.h"
#include "services/network/public/mojom/supports_loading_mode.mojom-forward.h"
namespace net {
class HttpResponseHeaders;
}
namespace network {
// This parser is intended to run in a relatively unprivileged process, such as
// the network process or a renderer process.
// Parse one or more supported loading modes from a string.
//
// Returns nullptr if the header syntax was invalid.
COMPONENT_EXPORT(NETWORK_CPP)
mojom::SupportsLoadingModePtr ParseSupportsLoadingMode(
base::StringPiece header_value);
// Parse Supports-Loading-Modes from HTTP response headers. If multiple headers
// are found, they are assumed to be canonicalized by joining them with commas,
// as typical for HTTP.
//
// Returns nullptr if the header syntax was invalid.
COMPONENT_EXPORT(NETWORK_CPP)
mojom::SupportsLoadingModePtr ParseSupportsLoadingMode(
const net::HttpResponseHeaders& headers);
} // namespace network
#endif // SERVICES_NETWORK_PUBLIC_CPP_SUPPORTS_LOADING_MODE_SUPPORTS_LOADING_MODE_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/supports_loading_mode/supports_loading_mode_parser.h"
#include "base/strings/string_piece.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/mojom/supports_loading_mode.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
namespace {
template <typename... ModeMatchers>
::testing::Matcher<mojom::SupportsLoadingModePtr> SupportedModesAre(
ModeMatchers&&... modes) {
return ::testing::Pointee(::testing::Field(
"supported_modes", &mojom::SupportsLoadingMode::supported_modes,
::testing::UnorderedElementsAre(modes...)));
}
TEST(SupportsLoadingModeParserTest, Valid) {
EXPECT_THAT(ParseSupportsLoadingMode(""),
SupportedModesAre(mojom::LoadingMode::kDefault));
EXPECT_THAT(ParseSupportsLoadingMode("default"),
SupportedModesAre(mojom::LoadingMode::kDefault));
EXPECT_THAT(ParseSupportsLoadingMode("uncredentialed-prefetch"),
SupportedModesAre(mojom::LoadingMode::kDefault,
mojom::LoadingMode::kUncredentialedPrefetch));
EXPECT_THAT(ParseSupportsLoadingMode(
"uncredentialed-prefetch, uncredentialed-prefetch"),
SupportedModesAre(mojom::LoadingMode::kDefault,
mojom::LoadingMode::kUncredentialedPrefetch));
EXPECT_THAT(ParseSupportsLoadingMode(
"uncredentialed-prerender, uncredentialed-prefetch"),
SupportedModesAre(mojom::LoadingMode::kDefault,
mojom::LoadingMode::kUncredentialedPrerender,
mojom::LoadingMode::kUncredentialedPrefetch));
}
TEST(SupportsLoadingModeParserTest, IgnoresUnknown) {
EXPECT_THAT(ParseSupportsLoadingMode("quantum-entanglement"),
SupportedModesAre(mojom::LoadingMode::kDefault));
EXPECT_THAT(
ParseSupportsLoadingMode("quantum-entanglement, uncredentialed-prefetch"),
SupportedModesAre(mojom::LoadingMode::kDefault,
mojom::LoadingMode::kUncredentialedPrefetch));
EXPECT_THAT(ParseSupportsLoadingMode("uncredentialed-prefetch; via=mars"),
SupportedModesAre(mojom::LoadingMode::kDefault));
EXPECT_THAT(ParseSupportsLoadingMode("\"uncredentialed-prefetch\""),
SupportedModesAre(mojom::LoadingMode::kDefault));
EXPECT_THAT(ParseSupportsLoadingMode("(uncredentialed-prefetch default)"),
SupportedModesAre(mojom::LoadingMode::kDefault));
}
TEST(SupportsLoadingModeParserTest, InvalidHttpStructuredHeaderList) {
EXPECT_TRUE(ParseSupportsLoadingMode("---------------").is_null());
EXPECT_TRUE(ParseSupportsLoadingMode(",,").is_null());
}
TEST(SupportsLoadingModeParserTest, ValidFromResponseHeaders) {
EXPECT_TRUE(ParseSupportsLoadingMode(
*net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\n"))
.is_null());
EXPECT_THAT(ParseSupportsLoadingMode(*net::HttpResponseHeaders::TryToCreate(
"HTTP/1.1 200 OK\n"
"Supports-Loading-Mode: \n")),
SupportedModesAre(mojom::LoadingMode::kDefault));
EXPECT_THAT(ParseSupportsLoadingMode(*net::HttpResponseHeaders::TryToCreate(
"HTTP/1.1 200 OK\n"
"supports-loading-mode: uncredentialed-prefetch\n")),
SupportedModesAre(mojom::LoadingMode::kDefault,
mojom::LoadingMode::kUncredentialedPrefetch));
EXPECT_THAT(ParseSupportsLoadingMode(*net::HttpResponseHeaders::TryToCreate(
"HTTP/1.1 200 OK\n"
"supports-loading-mode: uncredentialed-prefetch\n")),
SupportedModesAre(mojom::LoadingMode::kDefault,
mojom::LoadingMode::kUncredentialedPrefetch));
EXPECT_THAT(ParseSupportsLoadingMode(*net::HttpResponseHeaders::TryToCreate(
"HTTP/1.1 200 OK\n"
"Supports-Loading-Mode: default,\n"
" uncredentialed-prerender")),
SupportedModesAre(mojom::LoadingMode::kDefault,
mojom::LoadingMode::kUncredentialedPrerender));
EXPECT_THAT(ParseSupportsLoadingMode(*net::HttpResponseHeaders::TryToCreate(
"HTTP/1.1 200 OK\n"
"Supports-Loading-Mode: uncredentialed-prefetch\n"
"Content-Type: text/html; charset=utf8\n"
"supports-loading-mode: uncredentialed-prerender\n")),
SupportedModesAre(mojom::LoadingMode::kDefault,
mojom::LoadingMode::kUncredentialedPrefetch,
mojom::LoadingMode::kUncredentialedPrerender));
}
} // namespace
} // namespace network
......@@ -473,6 +473,7 @@ mojom("mojom") {
"referrer_policy.mojom",
"source_location.mojom",
"ssl_config.mojom",
"supports_loading_mode.mojom",
"tcp_socket.mojom",
"tls_socket.mojom",
"udp_socket.mojom",
......
// 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;
// A loading mode which may be supported by the document resource.
enum LoadingMode {
// The ordinary loading mode.
kDefault,
// A mode in which a response fetched without credentials and with other
// measures taken to obscure the user's identity may be used for navigation,
// even if credentials exist. The document should, if necessary, personalize
// itself when it loads, dynamically.
kUncredentialedPrefetch,
// A mode similar to (and implying the requirements of)
// kUncredentialedPrefetch, but where the document may load and execute
// script before navigation. The document should, if necessary, personalize
// itself after loading when credentials become available, dynamically.
kUncredentialedPrerender,
};
// https://github.com/jeremyroman/alternate-loading-modes/blob/gh-pages/opt-in.md
struct SupportsLoadingMode {
array<LoadingMode> supported_modes;
};
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