| // 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 |