| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/url_rewrite/browser/url_request_rewrite_rules_validation.h" |
| |
| #include <algorithm> |
| #include <string_view> |
| |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "net/http/http_util.h" |
| #include "url/url_constants.h" |
| |
| namespace url_rewrite { |
| namespace { |
| |
| bool IsValidUrlHost(std::string_view host) { |
| return GURL(base::StrCat({url::kHttpScheme, "://", host})).is_valid(); |
| } |
| |
| bool ValidateAddHeaders( |
| const mojom::UrlRequestRewriteAddHeadersPtr& add_headers) { |
| if (!add_headers || add_headers->headers.empty()) { |
| LOG(ERROR) << "Add headers is missing"; |
| return false; |
| } |
| return std::ranges::all_of( |
| add_headers->headers, [](const mojom::UrlHeaderPtr& header) { |
| if (!net::HttpUtil::IsValidHeaderName(header->name)) { |
| LOG(ERROR) << "Invalid header name: " << header->name; |
| return false; |
| } |
| if (!net::HttpUtil::IsValidHeaderValue(header->value)) { |
| LOG(ERROR) << "Invalid header value: " << header->value; |
| return false; |
| } |
| return true; |
| }); |
| } |
| |
| bool ValidateRemoveHeader( |
| const mojom::UrlRequestRewriteRemoveHeaderPtr& remove_header) { |
| if (!remove_header) { |
| LOG(ERROR) << "Remove headers is missing"; |
| return false; |
| } |
| if (!net::HttpUtil::IsValidHeaderName(remove_header->header_name)) { |
| LOG(ERROR) << "Invalid header name: " << remove_header->header_name; |
| return false; |
| } |
| return true; |
| } |
| |
| bool ValidateSubstituteQueryPattern( |
| const mojom::UrlRequestRewriteSubstituteQueryPatternPtr& |
| substitute_query_pattern) { |
| if (!substitute_query_pattern) { |
| LOG(ERROR) << "Substitute query pattern is missing"; |
| return false; |
| } |
| if (substitute_query_pattern->pattern.empty()) { |
| LOG(ERROR) << "Substitute query pattern is empty"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool ValidateReplaceUrl( |
| const mojom::UrlRequestRewriteReplaceUrlPtr& replace_url) { |
| if (!replace_url) { |
| LOG(ERROR) << "Replace url is missing"; |
| return false; |
| } |
| if (!GURL("http://site.com/" + replace_url->url_ends_with).is_valid()) { |
| LOG(ERROR) << "url_ends_with is not valid: " << replace_url->url_ends_with; |
| return false; |
| } |
| if (!replace_url->new_url.is_valid()) { |
| LOG(ERROR) << "new_url is not valid: " << replace_url->new_url; |
| return false; |
| } |
| return true; |
| } |
| |
| bool ValidateAppendToQuery( |
| const mojom::UrlRequestRewriteAppendToQueryPtr& append_to_query) { |
| if (!append_to_query) { |
| LOG(ERROR) << "Append to query is missing"; |
| return false; |
| } |
| if (append_to_query->query.empty()) { |
| LOG(ERROR) << "Append to query is empty"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool ValidateRewrite(const mojom::UrlRequestActionPtr& action) { |
| switch (action->which()) { |
| case mojom::UrlRequestAction::Tag::kAddHeaders: |
| return ValidateAddHeaders(action->get_add_headers()); |
| case mojom::UrlRequestAction::Tag::kRemoveHeader: |
| return ValidateRemoveHeader(action->get_remove_header()); |
| case mojom::UrlRequestAction::Tag::kSubstituteQueryPattern: |
| return ValidateSubstituteQueryPattern( |
| action->get_substitute_query_pattern()); |
| case mojom::UrlRequestAction::Tag::kReplaceUrl: |
| return ValidateReplaceUrl(action->get_replace_url()); |
| case mojom::UrlRequestAction::Tag::kAppendToQuery: |
| return ValidateAppendToQuery(action->get_append_to_query()); |
| case mojom::UrlRequestAction::Tag::kPolicy: |
| return true; |
| } |
| } |
| |
| } // namespace |
| |
| bool ValidateRules(const mojom::UrlRequestRewriteRules* rules) { |
| static constexpr std::string_view kWildcard("*."); |
| if (!rules) |
| return false; |
| for (const auto& rule : rules->rules) { |
| if (!rule) |
| return false; |
| if (rule->hosts_filter) { |
| if (rule->hosts_filter->empty()) { |
| LOG(ERROR) << "Hosts filter is empty"; |
| return false; |
| } |
| for (const std::string_view host : *rule->hosts_filter) { |
| if (base::StartsWith(host, kWildcard, base::CompareCase::SENSITIVE)) { |
| if (!IsValidUrlHost(host.substr(2))) { |
| LOG(ERROR) << "Host filter is not valid: " << host; |
| return false; |
| } |
| } else { |
| if (!IsValidUrlHost(host)) { |
| LOG(ERROR) << "Host filter is not valid: " << host; |
| return false; |
| } |
| } |
| } |
| } |
| if (rule->schemes_filter && rule->schemes_filter->empty()) { |
| LOG(ERROR) << "Schemes filter is empty"; |
| return false; |
| } |
| |
| if (rule->actions.empty()) { |
| // No rewrites, no action = no point! |
| LOG(ERROR) << "Actions are empty"; |
| return false; |
| } |
| |
| for (const auto& action : rule->actions) { |
| if (!action) |
| return false; |
| if (action->is_policy() && rule->actions.size() > 1) |
| // |policy| cannot be combined with other rewrites. |
| return false; |
| if (!ValidateRewrite(action)) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace url_rewrite |