blob: 5ece268d1027efa5d61d4db4486c9fd24e506f15 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/api/declarative_net_request/flat_ruleset_indexer.h"
#include <string>
#include "base/check_op.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/escape.h"
#include "base/strings/string_util.h"
#include "extensions/browser/api/declarative_net_request/constants.h"
#include "extensions/browser/api/declarative_net_request/flat/extension_ruleset_generated.h"
#include "extensions/browser/api/declarative_net_request/indexed_rule.h"
#include "extensions/browser/api/declarative_net_request/utils.h"
namespace extensions::declarative_net_request {
namespace {
namespace dnr_api = extensions::api::declarative_net_request;
namespace flat_rule = url_pattern_index::flat;
template <typename T>
using FlatOffset = flatbuffers::Offset<T>;
template <typename T>
using FlatVectorOffset = FlatOffset<flatbuffers::Vector<FlatOffset<T>>>;
using FlatStringOffset = FlatOffset<flatbuffers::String>;
using FlatStringListOffset = FlatVectorOffset<flatbuffers::String>;
using FlatIntListOffset = FlatOffset<flatbuffers::Vector<int32_t>>;
// Returns if `indexed_rule` should be evaluated before a request has been
// initiated. This checks that conditions that depend on information from a
// request's response, such as headers, are empty or not specified.
bool IsBeforeRequestRule(const IndexedRule& indexed_rule) {
return indexed_rule.response_headers.empty() &&
indexed_rule.excluded_response_headers.empty();
}
template <typename T>
FlatStringListOffset BuildVectorOfSharedStringsImpl(
flatbuffers::FlatBufferBuilder* builder,
const T& container,
bool is_lower_case) {
if (container.empty()) {
return FlatStringListOffset();
}
std::vector<FlatStringOffset> offsets;
offsets.reserve(container.size());
for (const std::string& str : container) {
offsets.push_back(builder->CreateSharedString(
is_lower_case ? base::ToLowerASCII(str) : str));
}
return builder->CreateVector(offsets);
}
// Writes to `builder` a flatbuffer vector of shared strings corresponding to
// `container` and returns the offset to it. If `container` is empty, returns an
// empty offset.
template <typename T>
FlatStringListOffset BuildVectorOfSharedStrings(
flatbuffers::FlatBufferBuilder* builder,
const T& container) {
return BuildVectorOfSharedStringsImpl(builder, container,
/*is_lower_case=*/false);
}
// Same as `BuildVectorOfSharedStrings` except all strings are converted to
// lower-case in the offset.
template <typename T>
FlatStringListOffset BuildVectorOfSharedLowercaseStrings(
flatbuffers::FlatBufferBuilder* builder,
const T& container) {
return BuildVectorOfSharedStringsImpl(builder, container,
/*is_lower_case=*/true);
}
FlatIntListOffset BuildIntVector(flatbuffers::FlatBufferBuilder* builder,
const base::flat_set<int>& input) {
if (input.empty()) {
return FlatIntListOffset();
}
return builder->CreateVector(
std::vector<int32_t>(input.begin(), input.end()));
}
std::vector<std::unique_ptr<url_pattern_index::UrlPatternIndexBuilder>>
CreateIndexBuilders(flatbuffers::FlatBufferBuilder* builder) {
std::vector<std::unique_ptr<url_pattern_index::UrlPatternIndexBuilder>>
result(flat::IndexType_count);
for (size_t i = 0; i < flat::IndexType_count; ++i) {
result[i] =
std::make_unique<url_pattern_index::UrlPatternIndexBuilder>(builder);
}
return result;
}
FlatOffset<flat::UrlTransform> BuildTransformOffset(
flatbuffers::FlatBufferBuilder* builder,
const dnr_api::URLTransform& transform) {
auto create_string_offset = [builder](const std::optional<std::string>& str) {
if (!str) {
return FlatStringOffset();
}
return builder->CreateSharedString(*str);
};
auto skip_separator_and_create_string_offset =
[builder](const std::optional<std::string>& str, char separator) {
if (!str) {
return FlatStringOffset();
}
DCHECK(!str->empty());
DCHECK_EQ(separator, str->at(0));
return builder->CreateSharedString(std::string_view(*str).substr(1));
};
auto should_clear_component = [](const std::optional<std::string>& str) {
return str && str->empty();
};
const FlatStringOffset kNullOffset;
FlatStringOffset scheme = create_string_offset(transform.scheme);
FlatStringOffset host = create_string_offset(transform.host);
bool clear_port = should_clear_component(transform.port);
FlatStringOffset port =
clear_port ? kNullOffset : create_string_offset(transform.port);
// Don't skip separator for path. Not all paths begin with '/'.
bool clear_path = should_clear_component(transform.path);
FlatStringOffset path =
clear_path ? kNullOffset : create_string_offset(transform.path);
bool clear_query = should_clear_component(transform.query);
FlatStringOffset query =
clear_query
? kNullOffset
: skip_separator_and_create_string_offset(transform.query, '?');
bool clear_fragment = should_clear_component(transform.fragment);
FlatStringOffset fragment =
clear_fragment
? kNullOffset
: skip_separator_and_create_string_offset(transform.fragment, '#');
FlatStringOffset username = create_string_offset(transform.username);
FlatStringOffset password = create_string_offset(transform.password);
FlatStringListOffset remove_query_params;
const bool use_plus = true;
if (transform.query_transform && transform.query_transform->remove_params) {
// Escape, sort and remove duplicates.
std::set<std::string> remove_params_escaped;
for (const std::string& remove_param :
*transform.query_transform->remove_params) {
remove_params_escaped.insert(
base::EscapeQueryParamValue(remove_param, use_plus));
}
remove_query_params =
BuildVectorOfSharedStrings(builder, remove_params_escaped);
}
FlatVectorOffset<flat::QueryKeyValue> add_or_replace_params;
if (transform.query_transform &&
transform.query_transform->add_or_replace_params &&
!transform.query_transform->add_or_replace_params->empty()) {
std::vector<FlatOffset<flat::QueryKeyValue>> add_or_replace_queries;
add_or_replace_queries.reserve(
transform.query_transform->add_or_replace_params->size());
for (const dnr_api::QueryKeyValue& query_pair :
*transform.query_transform->add_or_replace_params) {
FlatStringOffset key = builder->CreateSharedString(
base::EscapeQueryParamValue(query_pair.key, use_plus));
FlatStringOffset value = builder->CreateSharedString(
base::EscapeQueryParamValue(query_pair.value, use_plus));
bool replace_only = query_pair.replace_only && *query_pair.replace_only;
add_or_replace_queries.push_back(
flat::CreateQueryKeyValue(*builder, key, value, replace_only));
}
add_or_replace_params = builder->CreateVector(add_or_replace_queries);
}
return flat::CreateUrlTransform(*builder, scheme, host, clear_port, port,
clear_path, path, clear_query, query,
remove_query_params, add_or_replace_params,
clear_fragment, fragment, username, password);
}
FlatVectorOffset<flat::ModifyHeaderInfo> BuildModifyHeaderInfoOffset(
flatbuffers::FlatBufferBuilder* builder,
const std::vector<dnr_api::ModifyHeaderInfo>& modify_header_list) {
std::vector<FlatOffset<flat::ModifyHeaderInfo>> flat_modify_header_list;
flat_modify_header_list.reserve(modify_header_list.size());
for (const dnr_api::ModifyHeaderInfo& header_info : modify_header_list) {
flat::HeaderOperation operation = flat::HeaderOperation_remove;
FlatStringOffset header_value;
switch (header_info.operation) {
case dnr_api::HeaderOperation::kNone:
case dnr_api::HeaderOperation::kAppend:
operation = flat::HeaderOperation_append;
header_value = builder->CreateSharedString(*header_info.value);
break;
case dnr_api::HeaderOperation::kSet:
operation = flat::HeaderOperation_set;
header_value = builder->CreateSharedString(*header_info.value);
break;
case dnr_api::HeaderOperation::kRemove:
operation = flat::HeaderOperation_remove;
break;
}
FlatStringOffset header_name =
builder->CreateSharedString(base::ToLowerASCII(header_info.header));
FlatStringOffset header_regex_filter =
header_info.regex_filter
? builder->CreateSharedString(*header_info.regex_filter)
: FlatStringOffset();
FlatStringOffset header_substitution_filter =
header_info.regex_substitution
? builder->CreateSharedString(*header_info.regex_substitution)
: FlatStringOffset();
flatbuffers::Offset<flat::RegexFilterOptions> header_regex_options =
header_info.regex_options
? flat::CreateRegexFilterOptions(
*builder, *header_info.regex_options->match_all)
: flat::CreateRegexFilterOptions(*builder);
flat_modify_header_list.push_back(flat::CreateModifyHeaderInfo(
*builder, operation, header_name, header_value, header_regex_filter,
header_substitution_filter, header_regex_options));
}
return builder->CreateVector(flat_modify_header_list);
}
FlatVectorOffset<flat::HeaderCondition> BuildHeaderConditionsOffset(
flatbuffers::FlatBufferBuilder* builder,
const std::vector<dnr_api::HeaderInfo>& header_infos) {
std::vector<FlatOffset<flat::HeaderCondition>> flat_header_infos;
flat_header_infos.reserve(header_infos.size());
for (const dnr_api::HeaderInfo& info : header_infos) {
FlatStringOffset header = builder->CreateSharedString(info.header);
FlatStringListOffset values =
info.values ? BuildVectorOfSharedLowercaseStrings(builder, *info.values)
: FlatStringListOffset();
FlatStringListOffset excluded_values =
info.excluded_values ? BuildVectorOfSharedLowercaseStrings(
builder, *info.excluded_values)
: FlatStringListOffset();
flat_header_infos.push_back(
flat::CreateHeaderCondition(*builder, header, values, excluded_values));
}
return builder->CreateVector(flat_header_infos);
}
FlatOffset<flatbuffers::Vector<uint8_t>> BuildEmbedderConditionsOffset(
flatbuffers::FlatBufferBuilder* builder,
const IndexedRule& indexed_rule) {
if (indexed_rule.tab_ids.empty() && indexed_rule.excluded_tab_ids.empty() &&
indexed_rule.top_domains.empty() &&
indexed_rule.excluded_top_domains.empty() &&
indexed_rule.response_headers.empty() &&
indexed_rule.excluded_response_headers.empty()) {
return FlatOffset<flatbuffers::Vector<uint8_t>>();
}
// Build a nested Flatbuffer for the `flat::EmbedderConditions` table.
flatbuffers::FlatBufferBuilder nested_builder;
{
FlatIntListOffset tab_ids_included_offset =
BuildIntVector(&nested_builder, indexed_rule.tab_ids);
FlatIntListOffset tab_ids_excluded_offset =
BuildIntVector(&nested_builder, indexed_rule.excluded_tab_ids);
FlatVectorOffset<flat::HeaderCondition> response_headers_offset =
BuildHeaderConditionsOffset(&nested_builder,
indexed_rule.response_headers);
FlatVectorOffset<flat::HeaderCondition> excluded_response_headers_offset =
BuildHeaderConditionsOffset(&nested_builder,
indexed_rule.excluded_response_headers);
FlatStringListOffset top_domains_included_offset =
BuildVectorOfSharedStrings(&nested_builder, indexed_rule.top_domains);
FlatStringListOffset top_domains_excluded_offset =
BuildVectorOfSharedStrings(&nested_builder,
indexed_rule.excluded_top_domains);
auto nested_flatbuffer_root_offset = flat::CreateEmbedderConditions(
nested_builder, tab_ids_included_offset, tab_ids_excluded_offset,
top_domains_included_offset, top_domains_excluded_offset,
response_headers_offset, excluded_response_headers_offset);
nested_builder.Finish(nested_flatbuffer_root_offset,
kEmbedderConditionsBufferIdentifier);
}
// Now we can store the buffer in the parent. Note that by default, vectors
// are only aligned to their elements or size field, so in this case if the
// buffer contains 64-bit elements, they may not be correctly aligned. We fix
// that with:
builder->ForceVectorAlignment(nested_builder.GetSize(), sizeof(uint8_t),
nested_builder.GetBufferMinAlignment());
return builder->CreateVector(nested_builder.GetBufferPointer(),
nested_builder.GetSize());
}
} // namespace
FlatRulesetIndexer::FlatRulesetIndexer()
: before_request_index_builders_(CreateIndexBuilders(&builder_)),
headers_received_index_builders_(CreateIndexBuilders(&builder_)) {}
FlatRulesetIndexer::~FlatRulesetIndexer() = default;
void FlatRulesetIndexer::AddUrlRule(const IndexedRule& indexed_rule) {
DCHECK(!finished_);
++indexed_rules_count_;
FlatStringListOffset initiator_domains_included_offset =
BuildVectorOfSharedStrings(&builder_, indexed_rule.initiator_domains);
FlatStringListOffset initiator_domains_excluded_offset =
BuildVectorOfSharedStrings(&builder_,
indexed_rule.excluded_initiator_domains);
FlatStringListOffset request_domains_included_offset =
BuildVectorOfSharedStrings(&builder_, indexed_rule.request_domains);
FlatStringListOffset request_domains_excluded_offset =
BuildVectorOfSharedStrings(&builder_,
indexed_rule.excluded_request_domains);
FlatStringOffset url_pattern_offset =
builder_.CreateSharedString(indexed_rule.url_pattern);
auto embedder_conditions_offset =
BuildEmbedderConditionsOffset(&builder_, indexed_rule);
FlatOffset<flat_rule::UrlRule> offset = flat_rule::CreateUrlRule(
builder_, indexed_rule.options, indexed_rule.element_types,
indexed_rule.request_methods, indexed_rule.activation_types,
indexed_rule.url_pattern_type, indexed_rule.anchor_left,
indexed_rule.anchor_right, initiator_domains_included_offset,
initiator_domains_excluded_offset, request_domains_included_offset,
request_domains_excluded_offset, url_pattern_offset, indexed_rule.id,
indexed_rule.priority, embedder_conditions_offset);
if (indexed_rule.url_pattern_type !=
url_pattern_index::flat::UrlPatternType_REGEXP) {
std::vector<UrlPatternIndexBuilder*> builders = GetBuilders(indexed_rule);
CHECK(!builders.empty());
for (UrlPatternIndexBuilder* builder : builders) {
builder->IndexUrlRule(offset);
}
} else {
// A UrlPatternIndex is not built for regex rules. These are stored
// separately as part of flat::ExtensionIndexedRuleset.
FlatStringOffset regex_substitution_offset =
indexed_rule.regex_substitution
? builder_.CreateSharedString(*indexed_rule.regex_substitution)
: FlatStringOffset();
auto regex_rule = flat::CreateRegexRule(
builder_, offset, ConvertToFlatActionType(indexed_rule.action_type),
regex_substitution_offset);
if (IsBeforeRequestRule(indexed_rule)) {
before_request_regex_rules_.push_back(std::move(regex_rule));
} else {
headers_received_regex_rules_.push_back(std::move(regex_rule));
}
}
FlatStringOffset redirect_url_offset;
FlatOffset<flat::UrlTransform> transform_offset;
if (indexed_rule.redirect_url) {
DCHECK(!indexed_rule.redirect_url->empty());
redirect_url_offset =
builder_.CreateSharedString(*indexed_rule.redirect_url);
} else if (indexed_rule.url_transform) {
transform_offset =
BuildTransformOffset(&builder_, *indexed_rule.url_transform);
}
FlatVectorOffset<flat::ModifyHeaderInfo> request_headers_offset =
BuildModifyHeaderInfoOffset(&builder_,
indexed_rule.request_headers_to_modify);
FlatVectorOffset<flat::ModifyHeaderInfo> response_headers_offset =
BuildModifyHeaderInfoOffset(&builder_,
indexed_rule.response_headers_to_modify);
metadata_.push_back(flat::CreateUrlRuleMetadata(
builder_, indexed_rule.id,
ConvertToFlatActionType(indexed_rule.action_type), redirect_url_offset,
transform_offset, request_headers_offset, response_headers_offset));
}
flatbuffers::DetachedBuffer FlatRulesetIndexer::FinishAndReleaseBuffer() {
DCHECK(!finished_);
finished_ = true;
auto create_index_offsets =
[this](const std::vector<std::unique_ptr<UrlPatternIndexBuilder>>&
index_builders) {
std::vector<url_pattern_index::UrlPatternIndexOffset> index_offsets;
index_offsets.reserve(index_builders.size());
for (const auto& builder : index_builders) {
index_offsets.push_back(builder->Finish());
}
FlatVectorOffset<url_pattern_index::flat::UrlPatternIndex>
index_vector_offset = builder_.CreateVector(index_offsets);
return index_vector_offset;
};
FlatVectorOffset<url_pattern_index::flat::UrlPatternIndex>
before_request_index_vector_offset =
create_index_offsets(before_request_index_builders_);
FlatVectorOffset<url_pattern_index::flat::UrlPatternIndex>
headers_received_index_vector_offset =
create_index_offsets(headers_received_index_builders_);
// Store the extension metadata sorted by ID to support fast lookup through
// binary search.
FlatVectorOffset<flat::UrlRuleMetadata> extension_metadata_offset =
builder_.CreateVectorOfSortedTables(&metadata_);
FlatVectorOffset<flat::RegexRule> before_request_regex_rules_offset =
builder_.CreateVector(before_request_regex_rules_);
FlatVectorOffset<flat::RegexRule> headers_received_regex_rules_offset =
builder_.CreateVector(headers_received_regex_rules_);
FlatOffset<flat::ExtensionIndexedRuleset> root_offset =
flat::CreateExtensionIndexedRuleset(
builder_, before_request_index_vector_offset,
headers_received_index_vector_offset,
before_request_regex_rules_offset,
headers_received_regex_rules_offset, extension_metadata_offset);
flat::FinishExtensionIndexedRulesetBuffer(builder_, root_offset);
return builder_.Release();
}
std::vector<FlatRulesetIndexer::UrlPatternIndexBuilder*>
FlatRulesetIndexer::GetBuilders(const IndexedRule& indexed_rule) {
const std::vector<std::unique_ptr<UrlPatternIndexBuilder>>& builders =
IsBeforeRequestRule(indexed_rule) ? before_request_index_builders_
: headers_received_index_builders_;
switch (indexed_rule.action_type) {
case dnr_api::RuleActionType::kBlock:
case dnr_api::RuleActionType::kAllow:
case dnr_api::RuleActionType::kRedirect:
case dnr_api::RuleActionType::kUpgradeScheme:
return {builders[flat::IndexType_before_request_except_allow_all_requests]
.get()};
case dnr_api::RuleActionType::kAllowAllRequests:
return {builders[flat::IndexType_allow_all_requests].get()};
case dnr_api::RuleActionType::kModifyHeaders:
return {builders[flat::IndexType_modify_headers].get()};
case dnr_api::RuleActionType::kNone:
break;
}
NOTREACHED();
}
} // namespace extensions::declarative_net_request