Commit dc6ef0f7 authored by Kelvin Jiang's avatar Kelvin Jiang Committed by Commit Bot

[DNR] Add parsing logic for modifyHeaders rules

IndexedRule now has request and response header lists. Indexing these
via FlatRulesetIndexer will be done in a follow-up.

Bug: 947591
Change-Id: I0855ce4a85310b0df4992dd4dd326d66ff41ba14
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2095861
Commit-Queue: Kelvin Jiang <kelvinjiang@chromium.org>
Reviewed-by: default avatarKaran Bhatia <karandeepb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#749517}
parent 57a77062
......@@ -55,12 +55,11 @@ InstallWarning GetLargeRegexWarning(int rule_id) {
manifest_keys::kDeclarativeRuleResourcesKey);
}
// Fixure testing that declarative rules corresponding to the Declarative Net
// Request API are correctly indexed, for both packed and unpacked
// extensions.
// Fixture testing that declarative rules corresponding to the Declarative Net
// Request API are correctly indexed, for both packed and unpacked extensions.
class RuleIndexingTest : public DNRTestBase {
public:
RuleIndexingTest() {}
RuleIndexingTest() = default;
// DNRTestBase override.
void SetUp() override {
......
......@@ -63,6 +63,11 @@ const char kErrorRegexTooLarge[] =
const char kErrorRegexesTooLarge[] =
"Rules with ids [*] specified a more complex regex than allowed as part of "
"the \"*\" key.";
const char kErrorNoHeaderListsSpecified[] =
"Rule with id * does not specify a value for \"*\" or \"*\" key. At least "
"one of these keys must be specified with a non-empty list.";
const char kErrorInvalidHeaderName[] =
"Rule with id * must specify a valid header name to be modified.";
const char kErrorListNotPassed[] = "Rules file must contain a list.";
const char kRuleCountExceeded[] =
......
......@@ -53,6 +53,11 @@ enum class ParseResult {
ERROR_REGEX_SUBSTITUTION_WITHOUT_FILTER,
ERROR_INVALID_REGEX_SUBSTITUTION,
ERROR_INVALID_ALLOW_ALL_REQUESTS_RESOURCE_TYPE,
ERROR_NO_HEADERS_SPECIFIED,
ERROR_EMPTY_REQUEST_HEADERS_LIST,
ERROR_EMPTY_RESPONSE_HEADERS_LIST,
ERROR_INVALID_HEADER_NAME
};
// Describes the ways in which updating dynamic rules can fail.
......@@ -105,6 +110,8 @@ extern const char kErrorRegexSubstitutionWithoutFilter[];
extern const char kErrorInvalidAllowAllRequestsResourceType[];
extern const char kErrorRegexTooLarge[];
extern const char kErrorRegexesTooLarge[];
extern const char kErrorNoHeaderListsSpecified[];
extern const char kErrorInvalidHeaderName[];
extern const char kErrorListNotPassed[];
......
......@@ -17,6 +17,7 @@
#include "extensions/browser/api/declarative_net_request/utils.h"
#include "extensions/common/api/declarative_net_request.h"
#include "extensions/common/api/declarative_net_request/utils.h"
#include "net/http/http_util.h"
#include "third_party/re2/src/re2/re2.h"
#include "url/gurl.h"
#include "url/url_constants.h"
......@@ -388,10 +389,10 @@ bool DoesActionSupportPriority(dnr_api::RuleActionType type) {
case dnr_api::RULE_ACTION_TYPE_ALLOW:
case dnr_api::RULE_ACTION_TYPE_UPGRADESCHEME:
case dnr_api::RULE_ACTION_TYPE_ALLOWALLREQUESTS:
case dnr_api::RULE_ACTION_TYPE_MODIFYHEADERS:
return true;
case dnr_api::RULE_ACTION_TYPE_REMOVEHEADERS:
return false;
case dnr_api::RULE_ACTION_TYPE_MODIFYHEADERS:
case dnr_api::RULE_ACTION_TYPE_NONE:
break;
}
......@@ -412,8 +413,8 @@ uint8_t GetActionTypePriority(dnr_api::RuleActionType action_type) {
case dnr_api::RULE_ACTION_TYPE_REDIRECT:
return 1;
case dnr_api::RULE_ACTION_TYPE_REMOVEHEADERS:
return 0;
case dnr_api::RULE_ACTION_TYPE_MODIFYHEADERS:
return 0;
case dnr_api::RULE_ACTION_TYPE_NONE:
break;
}
......@@ -425,6 +426,22 @@ void RecordLargeRegexUMA(bool is_large_regex) {
UMA_HISTOGRAM_BOOLEAN(kIsLargeRegexHistogram, is_large_regex);
}
ParseResult ValidateHeaders(
const std::vector<dnr_api::ModifyHeaderInfo>& headers,
bool are_request_headers) {
if (headers.empty()) {
return are_request_headers ? ParseResult::ERROR_EMPTY_REQUEST_HEADERS_LIST
: ParseResult::ERROR_EMPTY_RESPONSE_HEADERS_LIST;
}
for (const auto& header_info : headers) {
if (!net::HttpUtil::IsValidHeaderName(header_info.header))
return ParseResult::ERROR_INVALID_HEADER_NAME;
}
return ParseResult::SUCCESS;
}
} // namespace
IndexedRule::IndexedRule() = default;
......@@ -580,6 +597,32 @@ ParseResult IndexedRule::CreateIndexedRule(dnr_api::Rule parsed_rule,
parsed_rule.action.remove_headers_list->end());
}
if (parsed_rule.action.type == dnr_api::RULE_ACTION_TYPE_MODIFYHEADERS) {
if (!parsed_rule.action.request_headers &&
!parsed_rule.action.response_headers)
return ParseResult::ERROR_NO_HEADERS_SPECIFIED;
if (parsed_rule.action.request_headers) {
indexed_rule->request_headers =
std::move(*parsed_rule.action.request_headers);
ParseResult result = ValidateHeaders(indexed_rule->request_headers,
true /* are_request_headers */);
if (result != ParseResult::SUCCESS)
return result;
}
if (parsed_rule.action.response_headers) {
indexed_rule->response_headers =
std::move(*parsed_rule.action.response_headers);
ParseResult result = ValidateHeaders(indexed_rule->response_headers,
false /* are_request_headers */);
if (result != ParseResult::SUCCESS)
return result;
}
}
// Some sanity checks to ensure we return a valid IndexedRule.
DCHECK_GE(indexed_rule->id, static_cast<uint32_t>(kMinValidID));
DCHECK_GE(indexed_rule->priority, static_cast<uint32_t>(kMinValidPriority));
......
......@@ -68,6 +68,13 @@ struct IndexedRule {
// List of headers to remove, valid iff this is a remove headers rule.
std::set<api::declarative_net_request::RemoveHeaderType> remove_headers_set;
// List of request headers to modify. Valid iff this is a modify headers rule.
std::vector<api::declarative_net_request::ModifyHeaderInfo> request_headers;
// List of response headers to modify. Valid iff this is a modify headers
// rule.
std::vector<api::declarative_net_request::ModifyHeaderInfo> response_headers;
DISALLOW_COPY_AND_ASSIGN(IndexedRule);
};
......
......@@ -832,6 +832,104 @@ TEST_F(IndexedRuleTest, InvalidAllowAllRequestsResourceType) {
}
}
TEST_F(IndexedRuleTest, ModifyHeadersParsing) {
struct RawHeaderInfo {
dnr_api::HeaderOperation operation;
std::string header;
};
using RawHeaderInfoList = std::vector<RawHeaderInfo>;
using ModifyHeaderInfoList = std::vector<dnr_api::ModifyHeaderInfo>;
// A copy-able version of dnr_api::ModifyHeaderInfo is used for ease of
// specifying test cases because elements are copied when initializing a
// vector from an array.
struct {
base::Optional<RawHeaderInfoList> request_headers;
base::Optional<RawHeaderInfoList> response_headers;
ParseResult expected_result;
} cases[] = {
// Raise an error if no headers are specified.
{base::nullopt, base::nullopt, ParseResult::ERROR_NO_HEADERS_SPECIFIED},
// Raise an error if the request or response headers list is specified,
// but empty.
{RawHeaderInfoList(),
RawHeaderInfoList({{dnr_api::HEADER_OPERATION_REMOVE, "set-cookie"}}),
ParseResult::ERROR_EMPTY_REQUEST_HEADERS_LIST},
{base::nullopt, RawHeaderInfoList(),
ParseResult::ERROR_EMPTY_RESPONSE_HEADERS_LIST},
// Raise an error if a header list contains an empty or invalid header
// name.
{base::nullopt,
RawHeaderInfoList({{dnr_api::HEADER_OPERATION_REMOVE, ""}}),
ParseResult::ERROR_INVALID_HEADER_NAME},
{base::nullopt,
RawHeaderInfoList({{dnr_api::HEADER_OPERATION_REMOVE, "<<invalid>>"}}),
ParseResult::ERROR_INVALID_HEADER_NAME},
// Parsing should succeed if only one non-empty header list is specified.
{RawHeaderInfoList({{dnr_api::HEADER_OPERATION_REMOVE, "cookie"}}),
base::nullopt, ParseResult::SUCCESS},
// Parsing should succeed if both header lists are specified and
// non-empty.
{RawHeaderInfoList({{dnr_api::HEADER_OPERATION_REMOVE, "referer"}}),
RawHeaderInfoList({{dnr_api::HEADER_OPERATION_REMOVE, "set-cookie"}}),
ParseResult::SUCCESS},
};
for (size_t i = 0; i < base::size(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
dnr_api::Rule rule = CreateGenericParsedRule();
rule.action.type = dnr_api::RULE_ACTION_TYPE_MODIFYHEADERS;
ModifyHeaderInfoList expected_request_headers;
if (cases[i].request_headers) {
rule.action.request_headers = std::make_unique<ModifyHeaderInfoList>();
for (auto header : *cases[i].request_headers) {
rule.action.request_headers->push_back(
CreateModifyHeaderInfo(header.operation, header.header));
expected_request_headers.push_back(
CreateModifyHeaderInfo(header.operation, header.header));
}
}
ModifyHeaderInfoList expected_response_headers;
if (cases[i].response_headers) {
rule.action.response_headers = std::make_unique<ModifyHeaderInfoList>();
for (auto header : *cases[i].response_headers) {
rule.action.response_headers->push_back(
CreateModifyHeaderInfo(header.operation, header.header));
expected_response_headers.push_back(
CreateModifyHeaderInfo(header.operation, header.header));
}
}
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(cases[i].expected_result, result);
if (result != ParseResult::SUCCESS)
continue;
EXPECT_EQ(dnr_api::RULE_ACTION_TYPE_MODIFYHEADERS,
indexed_rule.action_type);
EXPECT_TRUE(std::equal(
expected_request_headers.begin(), expected_request_headers.end(),
indexed_rule.request_headers.begin(), EqualsForTesting));
EXPECT_TRUE(std::equal(
expected_response_headers.begin(), expected_response_headers.end(),
indexed_rule.response_headers.begin(), EqualsForTesting));
}
}
} // namespace
} // namespace declarative_net_request
} // namespace extensions
......@@ -167,6 +167,24 @@ void ParseInfo::SetError(ParseResult error_reason, const int* rule_id) {
error_ = ErrorUtils::FormatErrorMessage(
kErrorInvalidKey, base::NumberToString(*rule_id), kRegexFilterKey);
break;
case ParseResult::ERROR_NO_HEADERS_SPECIFIED:
error_ = ErrorUtils::FormatErrorMessage(
kErrorNoHeaderListsSpecified, base::NumberToString(*rule_id),
kRequestHeadersPath, kResponseHeadersPath);
break;
case ParseResult::ERROR_EMPTY_REQUEST_HEADERS_LIST:
error_ = ErrorUtils::FormatErrorMessage(
kErrorEmptyList, base::NumberToString(*rule_id), kRequestHeadersPath);
break;
case ParseResult::ERROR_EMPTY_RESPONSE_HEADERS_LIST:
error_ = ErrorUtils::FormatErrorMessage(kErrorEmptyList,
base::NumberToString(*rule_id),
kResponseHeadersPath);
break;
case ParseResult::ERROR_INVALID_HEADER_NAME:
error_ = ErrorUtils::FormatErrorMessage(kErrorInvalidHeaderName,
base::NumberToString(*rule_id));
break;
case ParseResult::ERROR_REGEX_TOO_LARGE:
// These rules are ignored while indexing and so SetError won't be called
// for them. See AddRegexLimitExceededRule().
......
......@@ -222,6 +222,18 @@ std::ostream& operator<<(std::ostream& output, const ParseResult& result) {
case ParseResult::ERROR_INVALID_REGEX_FILTER:
output << "ERROR_INVALID_REGEX_FILTER";
break;
case ParseResult::ERROR_NO_HEADERS_SPECIFIED:
output << "ERROR_NO_HEADERS_SPECIFIED";
break;
case ParseResult::ERROR_EMPTY_REQUEST_HEADERS_LIST:
output << "ERROR_EMPTY_REQUEST_HEADERS_LIST";
break;
case ParseResult::ERROR_EMPTY_RESPONSE_HEADERS_LIST:
output << "ERROR_EMPTY_RESPONSE_HEADERS_LIST";
break;
case ParseResult::ERROR_INVALID_HEADER_NAME:
output << "ERROR_INVALID_HEADER_NAME";
break;
case ParseResult::ERROR_REGEX_TOO_LARGE:
output << "ERROR_REGEX_TOO_LARGE";
break;
......@@ -296,5 +308,21 @@ RulesetSource CreateTemporarySource(size_t id,
return source->Clone();
}
dnr_api::ModifyHeaderInfo CreateModifyHeaderInfo(
dnr_api::HeaderOperation operation,
std::string header) {
dnr_api::ModifyHeaderInfo header_info;
header_info.operation = operation;
header_info.header = header;
return header_info;
}
bool EqualsForTesting(const dnr_api::ModifyHeaderInfo& lhs,
const dnr_api::ModifyHeaderInfo& rhs) {
return lhs.operation == rhs.operation && lhs.header == rhs.header;
}
} // namespace declarative_net_request
} // namespace extensions
......@@ -9,6 +9,7 @@
#include <ostream>
#include <vector>
#include "base/optional.h"
#include "extensions/browser/api/declarative_net_request/constants.h"
#include "extensions/browser/api/declarative_net_request/request_action.h"
#include "extensions/common/api/declarative_net_request.h"
......@@ -73,6 +74,14 @@ RulesetSource CreateTemporarySource(
size_t rule_count_limit = 100,
ExtensionId extension_id = "extensionid");
api::declarative_net_request::ModifyHeaderInfo CreateModifyHeaderInfo(
api::declarative_net_request::HeaderOperation operation,
std::string header);
bool EqualsForTesting(
const api::declarative_net_request::ModifyHeaderInfo& lhs,
const api::declarative_net_request::ModifyHeaderInfo& rhs);
} // namespace declarative_net_request
} // namespace extensions
......
......@@ -52,6 +52,8 @@ const char kQueryKeyKey[] = "key";
const char kQueryValueKey[] = "value";
const char kRegexSubstitutionKey[] = "regexSubstitution";
const char kRegexSubstitutionPath[] = "action.redirect.regexSubstitution";
const char kRequestHeadersPath[] = "action.requestHeaders";
const char kResponseHeadersPath[] = "action.responseHeaders";
} // namespace declarative_net_request
} // namespace extensions
......@@ -66,6 +66,8 @@ extern const char kQueryKeyKey[];
extern const char kQueryValueKey[];
extern const char kRegexSubstitutionKey[];
extern const char kRegexSubstitutionPath[];
extern const char kRequestHeadersPath[];
extern const char kResponseHeadersPath[];
} // namespace declarative_net_request
} // namespace extensions
......
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