0

[DNR] Support append operation in modifyHeader rules for request headers

This CL allows modifyHeaders rules to append values onto request
headers. Since request headers do not really support multiple entries,
appends will add a delimiter and the specified value onto the end of a
header.

E.g. If a rule wishes to append "en-us" to the access-language header,
then: "access-language: en-ca" becomes "access-language: en-ca, en-us".

Headers that are eligible to be appended are standard HTTP headers
that can support multiple values. These headers, along with their
delimiters, are stored in an internal allowlist inside:
e/b/api/declarative_net_request/constants.h

Also of note: for request headers, WebRequest only tracks which
headers are removed, and which are modified by actions. DNR header
appends for request headers are treated as a modification, and
appended headers cannot be modified by webrequest actions.

Bug: 1355626
Change-Id: Ie5ef2bae40e7c3d1d04f284775d9a9c1044790ea
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3922770
Reviewed-by: Devlin Cronin <rdevlin.cronin@chromium.org>
Commit-Queue: Kelvin Jiang <kelvinjiang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1064697}
This commit is contained in:
Kelvin Jiang
2022-10-28 04:04:23 +00:00
committed by Chromium LUCI CQ
parent c25106b84f
commit d377adec68
9 changed files with 369 additions and 87 deletions
chrome
browser
extensions
test
data
extensions
api_test
declarative_net_request
modify_headers
extensions/browser/api

@ -61,6 +61,7 @@
namespace helpers = extension_web_request_api_helpers;
namespace keys = extension_web_request_api_constants;
namespace web_request = extensions::api::web_request;
namespace dnr_api = extensions::api::declarative_net_request;
using base::DictionaryValue;
using base::ListValue;
@ -942,6 +943,12 @@ TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeSendHeadersResponses) {
// Check that headers set by Declarative Net Request API can't be further
// modified and result in a conflict.
{
EventResponseDelta d4("extid4", base::Time::FromInternalValue(1000));
d4.modified_request_headers.SetHeader("cookie", "extid=extid4");
deltas.push_back(std::move(d4));
}
deltas.sort(&InDecreasingExtensionInstallationTimeOrder);
ignored_actions.clear();
ignore1.clear();
ignore2.clear();
@ -962,7 +969,14 @@ TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeSendHeadersResponses) {
"value 3"),
DNRRequestAction::HeaderInfo(
"key5", api::declarative_net_request::HEADER_OPERATION_SET,
"dnr_value")};
"dnr_value"),
// Unlike for response headers, appends for request headers are treated
// as set operations which set the header as
// "<existing value><appended value>" and will conflict with WebRequest
// modifications.
DNRRequestAction::HeaderInfo(
"cookie", api::declarative_net_request::HEADER_OPERATION_APPEND,
"cookey=value")};
info.dnr_actions = std::vector<DNRRequestAction>();
info.dnr_actions->push_back(std::move(set_headers_action));
@ -977,49 +991,235 @@ TEST(ExtensionWebRequestHelpersTest, TestMergeOnBeforeSendHeadersResponses) {
// Set by Declarative Net Request API.
ASSERT_TRUE(headers5.GetHeader("key5", &header_value));
EXPECT_EQ("dnr_value", header_value);
// Added by Declarative Net Request API.
ASSERT_TRUE(headers5.GetHeader("cookie", &header_value));
EXPECT_EQ("cookey=value", header_value);
EXPECT_EQ(2u, ignored_actions.size());
EXPECT_EQ(3u, ignored_actions.size());
EXPECT_TRUE(
HasIgnoredAction(ignored_actions, "extid2",
web_request::IGNORED_ACTION_TYPE_REQUEST_HEADERS));
EXPECT_TRUE(
HasIgnoredAction(ignored_actions, "extid3",
web_request::IGNORED_ACTION_TYPE_REQUEST_HEADERS));
EXPECT_TRUE(
HasIgnoredAction(ignored_actions, "extid4",
web_request::IGNORED_ACTION_TYPE_REQUEST_HEADERS));
EXPECT_TRUE(request_headers_modified4);
}
// Test conflict resolution for declarative net request actions from the same
// extension modifying the same request header.
namespace {
struct ExpectedHeader {
std::string header_name;
absl::optional<std::string> expected_value;
};
// Applies the DNR actions in `info` to `base_headers` and compares the results
// to the expected headers in `expected_headers`.
void ExecuteDNRActionsAndCheckHeaders(
const WebRequestInfo& info,
net::HttpRequestHeaders base_headers,
const std::vector<ExpectedHeader>& expected_headers) {
helpers::IgnoredActions ignored_actions;
EventResponseDeltas deltas;
bool request_headers_modified = false;
std::set<std::string> removed_headers;
std::set<std::string> set_headers;
std::vector<const DNRRequestAction*> matched_dnr_actions;
MergeOnBeforeSendHeadersResponses(
info, deltas, &base_headers, &ignored_actions, &removed_headers,
&set_headers, &request_headers_modified, &matched_dnr_actions);
for (const auto& expected_header : expected_headers) {
SCOPED_TRACE(base::StringPrintf("Testing header %s",
expected_header.header_name.c_str()));
if (expected_header.expected_value.has_value()) {
std::string header_value;
ASSERT_TRUE(
base_headers.GetHeader(expected_header.header_name, &header_value));
EXPECT_EQ(expected_header.expected_value, header_value);
} else {
EXPECT_FALSE(base_headers.HasHeader(expected_header.header_name));
}
}
EXPECT_EQ(0u, ignored_actions.size());
EXPECT_TRUE(request_headers_modified);
}
} // namespace
TEST(ExtensionWebRequestHelpersTest,
TestMergeOnBeforeSendHeadersResponses_DeclarativeNetRequest) {
TestMergeOnBeforeSendHeadersResponses_DeclarativeNetRequest_Append) {
using RequestHeaderType =
extension_web_request_api_helpers::RequestHeaderType;
base::HistogramTester histogram_tester;
const ExtensionId ext_1 = "ext_1";
const ExtensionId ext_2 = "ext_2";
DNRRequestAction action_1 =
CreateRequestActionForTesting(DNRRequestAction::Type::MODIFY_HEADERS);
action_1.extension_id = ext_1;
action_1.request_headers_to_modify = {
DNRRequestAction::HeaderInfo("accept", dnr_api::HEADER_OPERATION_APPEND,
"dnr_action_1"),
DNRRequestAction::HeaderInfo(
"connection", dnr_api::HEADER_OPERATION_APPEND, "dnr_action_1"),
DNRRequestAction::HeaderInfo(
"forwarded", dnr_api::HEADER_OPERATION_APPEND, "dnr_action_1")};
DNRRequestAction action_2 =
CreateRequestActionForTesting(DNRRequestAction::Type::MODIFY_HEADERS);
action_2.extension_id = ext_2;
action_2.request_headers_to_modify = {
DNRRequestAction::HeaderInfo("accept", dnr_api::HEADER_OPERATION_APPEND,
"dnr_action_2"),
DNRRequestAction::HeaderInfo("connection", dnr_api::HEADER_OPERATION_SET,
"dnr_action_2"),
DNRRequestAction::HeaderInfo(
"forwarded", dnr_api::HEADER_OPERATION_REMOVE, absl::nullopt)};
WebRequestInfoInitParams info_params;
WebRequestInfo info(std::move(info_params));
info.dnr_actions = std::vector<DNRRequestAction>();
info.dnr_actions->push_back(std::move(action_1));
info.dnr_actions->push_back(std::move(action_2));
net::HttpRequestHeaders base_headers;
base_headers.SetHeader("accept", "original accept");
base_headers.SetHeader("connection", "original connection");
std::vector<ExpectedHeader> expected_headers(
{// Multiple append actions can apply to the same header.
{"accept", "original accept, dnr_action_1, dnr_action_2"},
// Set and remove actions that are a lower priority than an append action
// will not be applied.
{"connection", "original connection, dnr_action_1"},
{"forwarded", "dnr_action_1"}});
ExecuteDNRActionsAndCheckHeaders(info, base_headers, expected_headers);
// Check that the appropriate values are recorded for histograms.
histogram_tester.ExpectBucketCount(
"Extensions.DeclarativeNetRequest.RequestHeaderAdded",
RequestHeaderType::kForwarded, 1);
histogram_tester.ExpectBucketCount(
"Extensions.DeclarativeNetRequest.RequestHeaderChanged",
RequestHeaderType::kAccept, 1);
histogram_tester.ExpectBucketCount(
"Extensions.DeclarativeNetRequest.RequestHeaderChanged",
RequestHeaderType::kConnection, 1);
}
TEST(ExtensionWebRequestHelpersTest,
TestMergeOnBeforeSendHeadersResponses_DeclarativeNetRequest_Set) {
using RequestHeaderType =
extension_web_request_api_helpers::RequestHeaderType;
base::HistogramTester histogram_tester;
const ExtensionId ext_1 = "ext_1";
const ExtensionId ext_2 = "ext_2";
DNRRequestAction action_1 =
CreateRequestActionForTesting(DNRRequestAction::Type::MODIFY_HEADERS);
action_1.extension_id = ext_1;
action_1.request_headers_to_modify = {
DNRRequestAction::HeaderInfo("range", dnr_api::HEADER_OPERATION_SET,
"dnr_action_1"),
DNRRequestAction::HeaderInfo("key5", dnr_api::HEADER_OPERATION_SET,
"dnr_action_1"),
DNRRequestAction::HeaderInfo("key6", dnr_api::HEADER_OPERATION_SET,
"dnr_action_1"),
DNRRequestAction::HeaderInfo("cookie", dnr_api::HEADER_OPERATION_SET,
"dnr_action_1")};
DNRRequestAction action_2 =
CreateRequestActionForTesting(DNRRequestAction::Type::MODIFY_HEADERS);
action_2.extension_id = ext_1;
action_2.request_headers_to_modify = {DNRRequestAction::HeaderInfo(
"cookie", dnr_api::HEADER_OPERATION_APPEND, "dnr_action_2")};
DNRRequestAction action_3 =
CreateRequestActionForTesting(DNRRequestAction::Type::MODIFY_HEADERS);
action_3.extension_id = ext_2;
action_3.request_headers_to_modify = {
DNRRequestAction::HeaderInfo("range", dnr_api::HEADER_OPERATION_APPEND,
"dnr_action_3"),
DNRRequestAction::HeaderInfo("key5", dnr_api::HEADER_OPERATION_SET,
"dnr_action_3"),
DNRRequestAction::HeaderInfo("key6", dnr_api::HEADER_OPERATION_REMOVE,
absl::nullopt)};
WebRequestInfoInitParams info_params;
WebRequestInfo info(std::move(info_params));
info.dnr_actions = std::vector<DNRRequestAction>();
info.dnr_actions->push_back(std::move(action_1));
info.dnr_actions->push_back(std::move(action_2));
info.dnr_actions->push_back(std::move(action_3));
net::HttpRequestHeaders base_headers;
base_headers.SetHeader("range", "original range");
base_headers.SetHeader("key6", "value 6");
std::vector<ExpectedHeader> expected_headers({
// Only one DNR action can set a header value, subsequent actions are
// ignored for that header.
{"range", "dnr_action_1"},
{"key5", "dnr_action_1"},
{"key6", "dnr_action_1"},
// DNR actions from the same extension can set, then append onto a header.
{"cookie", "dnr_action_1; dnr_action_2"},
});
ExecuteDNRActionsAndCheckHeaders(info, base_headers, expected_headers);
// Check that the appropriate values are recorded for histograms.
histogram_tester.ExpectBucketCount(
"Extensions.DeclarativeNetRequest.RequestHeaderAdded",
RequestHeaderType::kOther, 1);
histogram_tester.ExpectBucketCount(
"Extensions.DeclarativeNetRequest.RequestHeaderAdded",
RequestHeaderType::kCookie, 1);
histogram_tester.ExpectBucketCount(
"Extensions.DeclarativeNetRequest.RequestHeaderChanged",
RequestHeaderType::kOther, 1);
}
TEST(ExtensionWebRequestHelpersTest,
TestMergeOnBeforeSendHeadersResponses_DeclarativeNetRequest_Remove) {
using RequestHeaderType =
extension_web_request_api_helpers::RequestHeaderType;
base::HistogramTester histogram_tester;
const ExtensionId ext_1 = "ext_1";
const ExtensionId ext_2 = "ext_2";
DNRRequestAction action_1 =
CreateRequestActionForTesting(DNRRequestAction::Type::MODIFY_HEADERS);
action_1.extension_id = ext_1;
action_1.request_headers_to_modify = {
DNRRequestAction::HeaderInfo(
"referer", api::declarative_net_request::HEADER_OPERATION_SET,
"dnr_action_1"),
"upgrade", api::declarative_net_request::HEADER_OPERATION_REMOVE,
absl::nullopt),
DNRRequestAction::HeaderInfo(
"cookie", api::declarative_net_request::HEADER_OPERATION_SET,
"dnr_action_1"),
DNRRequestAction::HeaderInfo(
"key3", api::declarative_net_request::HEADER_OPERATION_REMOVE,
"key8", api::declarative_net_request::HEADER_OPERATION_REMOVE,
absl::nullopt)};
DNRRequestAction action_2 =
CreateRequestActionForTesting(DNRRequestAction::Type::MODIFY_HEADERS);
action_2.extension_id = ext_2;
action_2.request_headers_to_modify = {
DNRRequestAction::HeaderInfo(
"referer", api::declarative_net_request::HEADER_OPERATION_REMOVE,
absl::nullopt),
DNRRequestAction::HeaderInfo(
"cookie", api::declarative_net_request::HEADER_OPERATION_SET,
"upgrade", api::declarative_net_request::HEADER_OPERATION_APPEND,
"dnr_action_2"),
DNRRequestAction::HeaderInfo(
"key3", api::declarative_net_request::HEADER_OPERATION_SET,
"key8", api::declarative_net_request::HEADER_OPERATION_SET,
"dnr_action_2")};
WebRequestInfoInitParams info_params;
@ -1029,45 +1229,25 @@ TEST(ExtensionWebRequestHelpersTest,
info.dnr_actions->push_back(std::move(action_2));
net::HttpRequestHeaders base_headers;
base_headers.SetHeader("referer", "original");
base_headers.SetHeader("key3", "value 3");
helpers::IgnoredActions ignored_actions;
std::string header_value;
EventResponseDeltas deltas;
bool request_headers_modified;
std::set<std::string> ignore1, ignore2;
std::vector<const DNRRequestAction*> matched_dnr_actions;
base_headers.SetHeader("upgrade", "original upgrade");
base_headers.SetHeader("key8", "value 8");
// Header modifications specified by |action1| are processed before those
// specified by |action2|.
MergeOnBeforeSendHeadersResponses(
info, deltas, &base_headers, &ignored_actions, &ignore1, &ignore2,
&request_headers_modified, &matched_dnr_actions);
// Header set by a prior action cannot be removed by a subsequent action.
ASSERT_TRUE(base_headers.GetHeader("referer", &header_value));
EXPECT_EQ("dnr_action_1", header_value);
std::vector<ExpectedHeader> expected_headers({
// Once a header is removed by a DNR action, it cannot be changed by
// subsequent actions.
{"upgrade", absl::nullopt},
{"key8", absl::nullopt},
});
// Header set by a prior action cannot be set to a different value by a
// subsequent action.
ASSERT_TRUE(base_headers.GetHeader("cookie", &header_value));
EXPECT_EQ("dnr_action_1", header_value);
// Header removed by a prior action cannot be set by a subsequent action.
EXPECT_FALSE(base_headers.HasHeader("key3"));
ExecuteDNRActionsAndCheckHeaders(info, base_headers, expected_headers);
// Check that the appropriate values are recorded for histograms.
histogram_tester.ExpectUniqueSample(
"Extensions.DeclarativeNetRequest.RequestHeaderAdded",
RequestHeaderType::kCookie, 1);
histogram_tester.ExpectUniqueSample(
"Extensions.DeclarativeNetRequest.RequestHeaderChanged",
RequestHeaderType::kReferer, 1);
histogram_tester.ExpectUniqueSample(
histogram_tester.ExpectBucketCount(
"Extensions.DeclarativeNetRequest.RequestHeaderRemoved",
RequestHeaderType::kUpgrade, 1);
histogram_tester.ExpectBucketCount(
"Extensions.DeclarativeNetRequest.RequestHeaderRemoved",
RequestHeaderType::kOther, 1);
EXPECT_EQ(0u, ignored_actions.size());
EXPECT_TRUE(request_headers_modified);
}
// Ensure conflicts between different extensions are handled correctly with

@ -278,6 +278,15 @@ var setCustomRequestHeaderRule = {
requestHeaders: [{header: 'header1', operation: 'set', value: 'value-1'}]
}
};
var appendRequestHeadersRule = {
id: 100,
priority: 1,
condition: {urlFilter: host, resourceTypes: ['main_frame']},
action: {
type: 'modifyHeaders',
requestHeaders: [{header: 'cookie', operation: 'append', value: 'dnr=val'}]
}
};
var tests = [
function testCookieWithoutRules() {
@ -286,10 +295,21 @@ var tests = [
});
},
function testAppendRequestHeaderRule() {
var rules = [appendRequestHeadersRule];
chrome.declarativeNetRequest.updateDynamicRules(
{addRules: rules}, function() {
chrome.test.assertNoLastError();
checkCustomRequestHeaderValue(
'cookie', 'foo1=bar1; foo2=bar2; dnr=val');
});
},
function addRulesAndTestCookieRemoval() {
var rules = [removeCookieRule];
chrome.declarativeNetRequest.updateDynamicRules(
{addRules: rules}, function() {
{removeRuleIds: [appendRequestHeadersRule.id], addRules: rules},
function() {
chrome.test.assertNoLastError();
checkCookieHeaderRemoved(true);
});

@ -71,8 +71,10 @@ const char kErrorNoHeaderValueSpecified[] =
const char kErrorHeaderValuePresent[] =
"Rule with id * must not provide a header value for a header to be "
"removed.";
const char kErrorCannotAppendRequestHeader[] =
"Rule with id * must not specify a request header to be appended.";
const char kErrorAppendInvalidRequestHeader[] =
"Rule with id * specifies an invalid request header to be appended. Only "
"standard HTTP request headers that can specify multiple values for a "
"single entry are supported.";
const char kErrorTabIdsOnNonSessionRule[] =
"Rule with id * specifies a value for \"*\" or \"*\" key. These are only "
"supported for session-scoped rules.";

@ -8,6 +8,8 @@
#include <cstddef>
#include <cstdint>
#include "base/containers/fixed_flat_map.h"
#include "base/strings/string_piece.h"
#include "extensions/common/api/declarative_net_request/constants.h"
namespace extensions {
@ -68,7 +70,7 @@ enum class ParseResult {
ERROR_INVALID_HEADER_VALUE,
ERROR_HEADER_VALUE_NOT_SPECIFIED,
ERROR_HEADER_VALUE_PRESENT,
ERROR_APPEND_REQUEST_HEADER_UNSUPPORTED,
ERROR_APPEND_INVALID_REQUEST_HEADER,
ERROR_EMPTY_TAB_IDS_LIST,
ERROR_TAB_IDS_ON_NON_SESSION_RULE,
@ -176,7 +178,7 @@ extern const char kErrorInvalidHeaderName[];
extern const char kErrorInvalidHeaderValue[];
extern const char kErrorNoHeaderValueSpecified[];
extern const char kErrorHeaderValuePresent[];
extern const char kErrorCannotAppendRequestHeader[];
extern const char kErrorAppendInvalidRequestHeader[];
extern const char kErrorTabIdsOnNonSessionRule[];
extern const char kErrorTabIdDuplicated[];
@ -241,6 +243,33 @@ constexpr int kMaxStaticRulesPerProfile = 300000;
// root.
extern const char kEmbedderConditionsBufferIdentifier[];
// An allowlist of request headers that can be appended onto, in the form of
// (header name, header delimiter). Currently, this list contains all standard
// HTTP request headers that support multiple values in a single entry. This
// list may be extended in the future to support custom headers.
constexpr auto kDNRRequestHeaderAppendAllowList =
base::MakeFixedFlatMap<base::StringPiece, base::StringPiece>(
{{"accept", ", "},
{"accept-encoding", ", "},
{"accept-language", ", "},
{"access-control-request-headers", ", "},
{"cache-control", ", "},
{"connection", ", "},
{"content-language", ", "},
{"cookie", "; "},
{"forwarded", ", "},
{"if-match", ", "},
{"if-none-match", ", "},
{"keep-alive", ", "},
{"range", ", "},
{"te", ", "},
{"trailer", ""},
{"transfer-encoding", ", "},
{"upgrade", ", "},
{"via", ", "},
{"want-digest", ", "},
{"x-forwarded-for", ", "}});
} // namespace declarative_net_request
} // namespace extensions

@ -427,10 +427,12 @@ ParseResult ValidateHeaders(
if (!net::HttpUtil::IsValidHeaderName(header_info.header))
return ParseResult::ERROR_INVALID_HEADER_NAME;
// Ensure that request headers cannot be appended.
if (are_request_headers &&
header_info.operation == dnr_api::HEADER_OPERATION_APPEND) {
return ParseResult::ERROR_APPEND_REQUEST_HEADER_UNSUPPORTED;
DCHECK(
base::ranges::none_of(header_info.header, base::IsAsciiUpper<char>));
if (!base::Contains(kDNRRequestHeaderAppendAllowList, header_info.header))
return ParseResult::ERROR_APPEND_INVALID_REQUEST_HEADER;
}
if (header_info.value) {

@ -889,14 +889,16 @@ TEST_F(IndexedRuleTest, ModifyHeadersParsing) {
{{dnr_api::HEADER_OPERATION_APPEND, "set-cookie", absl::nullopt}}),
ParseResult::ERROR_HEADER_VALUE_NOT_SPECIFIED},
// Raise an error if a rule specifies a request header to be appended.
// Raise an error if a rule specifies an invalid request header to be
// appended.
{RawHeaderInfoList(
{{dnr_api::HEADER_OPERATION_APPEND, "cookie", "cookie-value"}}),
absl::nullopt, ParseResult::ERROR_APPEND_REQUEST_HEADER_UNSUPPORTED},
{{dnr_api::HEADER_OPERATION_APPEND, "invalid-header", "value"}}),
absl::nullopt, ParseResult::ERROR_APPEND_INVALID_REQUEST_HEADER},
{RawHeaderInfoList(
{{dnr_api::HEADER_OPERATION_REMOVE, "cookie", absl::nullopt},
{dnr_api::HEADER_OPERATION_SET, "referer", ""}}),
{dnr_api::HEADER_OPERATION_SET, "referer", ""},
{dnr_api::HEADER_OPERATION_APPEND, "accept-language", "en-US"}}),
absl::nullopt, ParseResult::SUCCESS},
{RawHeaderInfoList(

@ -308,8 +308,8 @@ std::ostream& operator<<(std::ostream& output, const ParseResult& result) {
case ParseResult::ERROR_HEADER_VALUE_PRESENT:
output << "ERROR_HEADER_VALUE_PRESENT";
break;
case ParseResult::ERROR_APPEND_REQUEST_HEADER_UNSUPPORTED:
output << "ERROR_APPEND_REQUEST_HEADER_UNSUPPORTED";
case ParseResult::ERROR_APPEND_INVALID_REQUEST_HEADER:
output << "ERROR_APPEND_INVALID_REQUEST_HEADER";
break;
case ParseResult::ERROR_EMPTY_TAB_IDS_LIST:
output << "ERROR_EMPTY_TAB_IDS_LIST";

@ -438,6 +438,8 @@ bool HasDNRFeedbackPermission(const Extension* extension,
mojom::APIPermissionID::kDeclarativeNetRequestFeedback);
}
// TODO(crbug.com/1370166): Add a parameter that allows more specific strings
// for error messages that can pinpoint the error within a single rule.
std::string GetParseError(ParseResult error_reason, int rule_id) {
switch (error_reason) {
case ParseResult::NONE:
@ -583,8 +585,8 @@ std::string GetParseError(ParseResult error_reason, int rule_id) {
case ParseResult::ERROR_HEADER_VALUE_PRESENT:
return ErrorUtils::FormatErrorMessage(kErrorHeaderValuePresent,
base::NumberToString(rule_id));
case ParseResult::ERROR_APPEND_REQUEST_HEADER_UNSUPPORTED:
return ErrorUtils::FormatErrorMessage(kErrorCannotAppendRequestHeader,
case ParseResult::ERROR_APPEND_INVALID_REQUEST_HEADER:
return ErrorUtils::FormatErrorMessage(kErrorAppendInvalidRequestHeader,
base::NumberToString(rule_id));
case ParseResult::ERROR_REGEX_TOO_LARGE:
return ErrorUtils::FormatErrorMessage(

@ -20,6 +20,7 @@
#include "base/metrics/histogram_macros.h"
#include "base/ranges/algorithm.h"
#include "base/stl_util.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
@ -31,6 +32,7 @@
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/api/declarative_net_request/constants.h"
#include "extensions/browser/api/declarative_net_request/request_action.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/browser/api/web_request/web_request_api_constants.h"
@ -331,6 +333,28 @@ static_assert(static_cast<size_t>(ResponseHeaderType::kMaxValue) - 1 ==
static_assert(ValidateHeaderEntries(kResponseHeaderEntries),
"Invalid response header entries");
// Returns the new value for the header with `header_name` after `operation` is
// applied to it with the specified `header_value`. This will just return
// `header_value` unless the operation is APPEND and the header already exists,
// which will return <existing header value><delimiter><`header_value`>.
std::string GetDNRNewRequestHeaderValue(net::HttpRequestHeaders* headers,
const std::string& header_name,
const std::string& header_value,
dnr_api::HeaderOperation operation) {
namespace dnr = extensions::declarative_net_request;
std::string existing_value;
bool has_header = headers->GetHeader(header_name, &existing_value);
if (has_header && operation == dnr_api::HEADER_OPERATION_APPEND) {
const auto* it = dnr::kDNRRequestHeaderAppendAllowList.find(header_name);
DCHECK(it != dnr::kDNRRequestHeaderAppendAllowList.end());
return base::StrCat({existing_value, it->second, header_value});
}
return header_value;
}
// Represents an action to be taken on a given header.
struct DNRHeaderAction {
DNRHeaderAction(const DNRRequestAction::HeaderInfo* header_info,
@ -373,7 +397,7 @@ bool ModifyRequestHeadersForAction(
const DNRRequestAction& request_action,
std::set<std::string>* removed_headers,
std::set<std::string>* set_headers,
std::map<base::StringPiece, DNRHeaderAction>* header_actions) {
std::map<base::StringPiece, std::vector<DNRHeaderAction>>* header_actions) {
bool request_headers_modified = false;
for (const DNRRequestAction::HeaderInfo& header_info :
request_action.request_headers_to_modify) {
@ -382,27 +406,47 @@ bool ModifyRequestHeadersForAction(
DNRHeaderAction header_action(&header_info, &request_action.extension_id);
auto iter = header_actions->find(header);
// Checking the first DNRHeaderAction should suffice for determining if a
// conflict exists, since the contents of |header_actions| for a given
// header will always be one of:
// [remove]
// [append+] one or more appends
// [set, append*] set, any number of appends from the same extension
// This is enforced in ConflictsWithSubsequentAction by checking the
// operation type of the subsequent action against the first action.
if (iter != header_actions->end() &&
iter->second.ConflictsWithSubsequentAction(header_action)) {
iter->second[0].ConflictsWithSubsequentAction(header_action)) {
continue;
}
header_actions->emplace(header, header_action);
auto& actions_for_header = (*header_actions)[header];
actions_for_header.push_back(header_action);
switch (header_info.operation) {
case extensions::api::declarative_net_request::HEADER_OPERATION_SET: {
case dnr_api::HEADER_OPERATION_APPEND:
case dnr_api::HEADER_OPERATION_SET: {
DCHECK(header_info.value.has_value());
bool has_header = headers->HasHeader(header);
headers->SetHeader(header, *header_info.value);
headers->SetHeader(header, GetDNRNewRequestHeaderValue(
headers, header, *header_info.value,
header_info.operation));
header_modified = true;
set_headers->insert(header);
if (has_header)
RecordRequestHeader(header, &RecordDNRRequestHeaderChanged);
else
RecordRequestHeader(header, &RecordDNRRequestHeaderAdded);
// Record only the first time a header is changed by a DNR action, which
// means only one action (this one) is currently in |header_actions| for
// this header, Each header should only contribute one count into the
// histogram as the count represents the total number of headers that
// have been changed by DNR actions.
if (actions_for_header.size() == 1) {
if (has_header)
RecordRequestHeader(header, &RecordDNRRequestHeaderChanged);
else
RecordRequestHeader(header, &RecordDNRRequestHeaderAdded);
}
break;
}
case extensions::api::declarative_net_request::HEADER_OPERATION_REMOVE: {
case dnr_api::HEADER_OPERATION_REMOVE: {
while (headers->HasHeader(header)) {
header_modified = true;
headers->RemoveHeader(header);
@ -414,8 +458,7 @@ bool ModifyRequestHeadersForAction(
}
break;
}
case extensions::api::declarative_net_request::HEADER_OPERATION_APPEND:
case extensions::api::declarative_net_request::HEADER_OPERATION_NONE:
case dnr_api::HEADER_OPERATION_NONE:
NOTREACHED();
}
@ -471,15 +514,17 @@ bool ModifyResponseHeadersForAction(
// [remove]
// [append+] one or more appends
// [set, append*] set, any number of appends from the same extension
// This is enforced in ConflictsWithSubsequentAction by checking the
// operation type of the subsequent action against the first action.
if (iter != header_actions->end() &&
(*header_actions)[header][0].ConflictsWithSubsequentAction(
header_action)) {
iter->second[0].ConflictsWithSubsequentAction(header_action)) {
continue;
}
(*header_actions)[header].push_back(header_action);
auto& actions_for_header = (*header_actions)[header];
actions_for_header.push_back(header_action);
switch (header_info.operation) {
case extensions::api::declarative_net_request::HEADER_OPERATION_REMOVE: {
case dnr_api::HEADER_OPERATION_REMOVE: {
if (has_header(header)) {
header_modified = true;
create_override_headers_if_needed(override_response_headers);
@ -489,7 +534,7 @@ bool ModifyResponseHeadersForAction(
break;
}
case extensions::api::declarative_net_request::HEADER_OPERATION_APPEND: {
case dnr_api::HEADER_OPERATION_APPEND: {
header_modified = true;
create_override_headers_if_needed(override_response_headers);
override_response_headers->get()->AddHeader(header, *header_info.value);
@ -497,12 +542,12 @@ bool ModifyResponseHeadersForAction(
// Record only the first time a header is appended. appends following a
// set from the same extension are treated as part of the set and are
// not logged.
if ((*header_actions)[header].size() == 1)
if (actions_for_header.size() == 1)
RecordResponseHeader(header, &RecordDNRResponseHeaderAdded);
break;
}
case extensions::api::declarative_net_request::HEADER_OPERATION_SET: {
case dnr_api::HEADER_OPERATION_SET: {
header_modified = true;
create_override_headers_if_needed(override_response_headers);
override_response_headers->get()->RemoveHeader(header);
@ -510,7 +555,7 @@ bool ModifyResponseHeadersForAction(
RecordResponseHeader(header, &RecordDNRResponseHeaderChanged);
break;
}
case extensions::api::declarative_net_request::HEADER_OPERATION_NONE:
case dnr_api::HEADER_OPERATION_NONE:
NOTREACHED();
}
@ -1105,7 +1150,7 @@ void MergeOnBeforeSendHeadersResponses(
DCHECK(matched_dnr_actions);
*request_headers_modified = false;
std::map<base::StringPiece, DNRHeaderAction> dnr_header_actions;
std::map<base::StringPiece, std::vector<DNRHeaderAction>> dnr_header_actions;
for (const auto& action : *request.dnr_actions) {
bool headers_modified_for_action =
ModifyRequestHeadersForAction(request_headers, action, removed_headers,
@ -1153,7 +1198,7 @@ void MergeOnBeforeSendHeadersResponses(
// in |removed_headers| and |set_headers|.
auto iter = dnr_header_actions.find(key);
if (iter != dnr_header_actions.end() &&
iter->second.header_info->operation ==
iter->second[0].header_info->operation ==
dnr_api::HEADER_OPERATION_REMOVE) {
extension_conflicts = true;
break;