url_param_filterer.cc 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // Copyright 2022 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/url_param_filter/core/url_param_filterer.h"
  5. #include <vector>
  6. #include "base/base64.h"
  7. #include "base/metrics/field_trial_params.h"
  8. #include "base/metrics/histogram_functions.h"
  9. #include "base/no_destructor.h"
  10. #include "base/strings/escape.h"
  11. #include "base/strings/strcat.h"
  12. #include "base/strings/string_util.h"
  13. #include "components/url_param_filter/core/features.h"
  14. #include "components/url_param_filter/core/url_param_classifications_loader.h"
  15. #include "components/url_param_filter/core/url_param_filter_classification.pb.h"
  16. #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
  17. #include "net/base/url_util.h"
  18. #include "third_party/abseil-cpp/absl/types/optional.h"
  19. #include "third_party/zlib/google/compression_utils.h"
  20. #include "url/gurl.h"
  21. namespace url_param_filter {
  22. namespace internal {
  23. absl::optional<std::string> GetLabelFromHostname(const GURL& gurl) {
  24. if (gurl.HostIsIPAddress()) {
  25. return absl::nullopt;
  26. }
  27. std::string etld_plus_one =
  28. net::registry_controlled_domains::GetDomainAndRegistry(
  29. gurl, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
  30. if (etld_plus_one.empty()) {
  31. return absl::nullopt;
  32. }
  33. size_t etld_len = net::registry_controlled_domains::GetRegistryLength(
  34. gurl, net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
  35. net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
  36. if (etld_len == 0 || etld_len == std::string::npos ||
  37. etld_plus_one.size() - etld_len - 1 <= 0) {
  38. return absl::nullopt;
  39. }
  40. return etld_plus_one.substr(0, etld_plus_one.size() - etld_len - 1);
  41. }
  42. } // namespace internal
  43. namespace {
  44. // Get the ETLD+1 of the URL, which means any subdomain is treated equivalently.
  45. // IP addresses are returned verbatim. Note that this is schemeless, so
  46. // filtering is applied equivalently regardless of http vs https vs others.
  47. std::string GetClassifiedSite(const GURL& gurl) {
  48. if (gurl.HostIsIPAddress()) {
  49. return gurl.host();
  50. }
  51. return net::registry_controlled_domains::GetDomainAndRegistry(
  52. gurl, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
  53. }
  54. std::map<std::string, ClassificationExperimentStatus> GetBlockedParameters(
  55. const GURL& source_url,
  56. const GURL& destination_url,
  57. const ClassificationMap& classification_map,
  58. const FilterClassification::UseCase use_case) {
  59. std::string source_classified_site = GetClassifiedSite(source_url);
  60. std::string destination_classified_site = GetClassifiedSite(destination_url);
  61. absl::optional<std::string> source_label =
  62. internal::GetLabelFromHostname(source_url);
  63. std::map<std::string, ClassificationExperimentStatus> blocked_parameters;
  64. std::vector<ClassificationMapKey> search_keys = {
  65. {SourceKey(std::move(source_classified_site)),
  66. DestinationKey(std::move(destination_classified_site))}};
  67. if (source_label.has_value()) {
  68. search_keys.push_back(SourceWildcardKey(std::move(source_label.value())));
  69. }
  70. // Check whether source site, as seen by the classifier (eTLD+1 or IP), has
  71. // params classified as requiring filtering. If so, and the params are present
  72. // on the destination URL, or any nested URLs, remove them.
  73. for (const auto& key : search_keys) {
  74. auto classification_result = classification_map.find(key);
  75. if (classification_result != classification_map.end()) {
  76. auto classification_with_use_case =
  77. classification_result->second.find(use_case);
  78. if (classification_with_use_case != classification_result->second.end()) {
  79. blocked_parameters.insert(classification_with_use_case->second.begin(),
  80. classification_with_use_case->second.end());
  81. }
  82. }
  83. }
  84. return blocked_parameters;
  85. }
  86. // Filter a given URL according to the passed-in classifications, optionally
  87. // checking any encoded, nested URLs.
  88. FilterResult FilterUrl(const GURL& source_url,
  89. const GURL& destination_url,
  90. const ClassificationMap& classification_map,
  91. const bool check_nested,
  92. const FilterClassification::UseCase use_case) {
  93. GURL result = GURL{destination_url};
  94. int filtered_params_count = 0;
  95. ClassificationExperimentStatus experiment_status =
  96. ClassificationExperimentStatus::NON_EXPERIMENTAL;
  97. // If there's no query string, we can short-circuit immediately.
  98. if (!destination_url.has_query()) {
  99. return FilterResult{destination_url, filtered_params_count,
  100. experiment_status};
  101. }
  102. std::map<std::string, ClassificationExperimentStatus> blocked_parameters =
  103. GetBlockedParameters(source_url, destination_url, classification_map,
  104. use_case);
  105. // Return quickly if there are no parameters we care about.
  106. if (blocked_parameters.size() == 0) {
  107. return FilterResult{destination_url, filtered_params_count,
  108. experiment_status};
  109. }
  110. std::vector<std::string> query_parts;
  111. for (net::QueryIterator it(result); !it.IsAtEnd(); it.Advance()) {
  112. const std::string key = std::string{it.GetKey()};
  113. // If we don't find the given param in our set of blocked parameters, we can
  114. // add it to the result safely.
  115. auto classification = blocked_parameters.find(base::ToLowerASCII(key));
  116. if (classification == blocked_parameters.end()) {
  117. std::string value = std::string{it.GetValue()};
  118. if (check_nested) {
  119. GURL nested = GURL{base::UnescapeBinaryURLComponent(value)};
  120. if (nested.is_valid()) {
  121. FilterResult nested_result = FilterUrl(
  122. destination_url, nested, classification_map, false, use_case);
  123. // If a nested URL contains a param we must filter, do so now.
  124. if (nested != nested_result.filtered_url) {
  125. value = base::EscapeQueryParamValue(
  126. nested_result.filtered_url.spec(), /*use_plus=*/false);
  127. filtered_params_count += nested_result.filtered_param_count;
  128. if (nested_result.experimental_status ==
  129. ClassificationExperimentStatus::EXPERIMENTAL) {
  130. experiment_status = ClassificationExperimentStatus::EXPERIMENTAL;
  131. }
  132. }
  133. }
  134. }
  135. if (value != "") {
  136. query_parts.push_back(base::StrCat({key, "=", value}));
  137. } else {
  138. query_parts.push_back(key);
  139. }
  140. } else {
  141. filtered_params_count++;
  142. if (classification->second ==
  143. ClassificationExperimentStatus::EXPERIMENTAL) {
  144. experiment_status = classification->second;
  145. }
  146. }
  147. }
  148. std::string new_query = base::JoinString(query_parts, "&");
  149. GURL::Replacements replacements;
  150. if (new_query == "") {
  151. replacements.ClearQuery();
  152. } else {
  153. replacements.SetQueryStr(new_query);
  154. }
  155. result = result.ReplaceComponents(replacements);
  156. return FilterResult{result, filtered_params_count, experiment_status};
  157. }
  158. } // anonymous namespace
  159. FilterResult FilterUrl(const GURL& source_url,
  160. const GURL& destination_url,
  161. const ClassificationMap& classification_map,
  162. const FilterClassification::UseCase use_case) {
  163. return FilterUrl(source_url, destination_url, classification_map, true,
  164. use_case);
  165. }
  166. FilterResult FilterUrl(const GURL& source_url, const GURL& destination_url) {
  167. if (!base::FeatureList::IsEnabled(features::kIncognitoParamFilterEnabled)) {
  168. return FilterResult{destination_url, 0};
  169. }
  170. return FilterUrl(source_url, destination_url,
  171. ClassificationsLoader::GetInstance()->GetClassifications(),
  172. FilterClassification::USE_CASE_UNKNOWN);
  173. }
  174. FilterResult FilterUrl(const GURL& source_url,
  175. const GURL& destination_url,
  176. const FilterClassification::UseCase use_case) {
  177. if (!base::FeatureList::IsEnabled(features::kIncognitoParamFilterEnabled)) {
  178. return FilterResult{destination_url, 0,
  179. ClassificationExperimentStatus::NON_EXPERIMENTAL};
  180. }
  181. return FilterUrl(source_url, destination_url,
  182. ClassificationsLoader::GetInstance()->GetClassifications(),
  183. use_case);
  184. }
  185. } // namespace url_param_filter