url_loader_throttle.cc 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. // Copyright 2019 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_rewrite/common/url_loader_throttle.h"
  5. #include <string>
  6. #include "base/strings/string_split.h"
  7. #include "base/strings/string_util.h"
  8. #include "net/base/net_errors.h"
  9. #include "services/network/public/cpp/resource_request.h"
  10. #include "url/url_constants.h"
  11. namespace url_rewrite {
  12. namespace {
  13. // Returns a string representing the URL stripped of query and ref.
  14. std::string ClearUrlQueryRef(const GURL& url) {
  15. GURL::Replacements replacements;
  16. replacements.ClearQuery();
  17. replacements.ClearRef();
  18. return url.ReplaceComponents(replacements).spec();
  19. }
  20. void ApplySubstituteQueryPattern(
  21. network::ResourceRequest* request,
  22. const mojom::UrlRequestRewriteSubstituteQueryPatternPtr&
  23. substitute_query_pattern) {
  24. std::string url_query = request->url.query();
  25. base::ReplaceSubstringsAfterOffset(&url_query, 0,
  26. substitute_query_pattern->pattern,
  27. substitute_query_pattern->substitution);
  28. GURL::Replacements replacements;
  29. replacements.SetQueryStr(url_query);
  30. request->url = request->url.ReplaceComponents(replacements);
  31. }
  32. void ApplyReplaceUrl(network::ResourceRequest* request,
  33. const mojom::UrlRequestRewriteReplaceUrlPtr& replace_url) {
  34. if (!base::EndsWith(ClearUrlQueryRef(request->url),
  35. replace_url->url_ends_with, base::CompareCase::SENSITIVE))
  36. return;
  37. GURL new_url = replace_url->new_url;
  38. if (new_url.SchemeIs(url::kDataScheme)) {
  39. request->url = new_url;
  40. return;
  41. }
  42. if (new_url.has_scheme() &&
  43. new_url.scheme().compare(request->url.scheme()) != 0) {
  44. // No cross-scheme redirect allowed.
  45. return;
  46. }
  47. GURL::Replacements replacements;
  48. std::string host = new_url.host();
  49. replacements.SetHostStr(host);
  50. std::string port = new_url.port();
  51. replacements.SetPortStr(port);
  52. std::string path = new_url.path();
  53. replacements.SetPathStr(path);
  54. request->url = request->url.ReplaceComponents(replacements);
  55. }
  56. void ApplyRemoveHeader(
  57. network::ResourceRequest* request,
  58. const mojom::UrlRequestRewriteRemoveHeaderPtr& remove_header) {
  59. absl::optional<std::string> query_pattern = remove_header->query_pattern;
  60. if (query_pattern &&
  61. request->url.query().find(query_pattern.value()) == std::string::npos) {
  62. // Per the FIDL API, the header should be removed if there is no query
  63. // pattern or if the pattern matches. Neither is true here.
  64. return;
  65. }
  66. request->headers.RemoveHeader(remove_header->header_name);
  67. request->cors_exempt_headers.RemoveHeader(remove_header->header_name);
  68. }
  69. void ApplyAppendToQuery(
  70. network::ResourceRequest* request,
  71. const mojom::UrlRequestRewriteAppendToQueryPtr& append_to_query) {
  72. std::string url_query;
  73. if (request->url.has_query() && !request->url.query().empty())
  74. url_query = request->url.query() + "&";
  75. url_query += append_to_query->query;
  76. GURL::Replacements replacements;
  77. replacements.SetQueryStr(url_query);
  78. request->url = request->url.ReplaceComponents(replacements);
  79. }
  80. bool HostMatches(const base::StringPiece& url_host,
  81. const base::StringPiece& rule_host) {
  82. const base::StringPiece kWildcard("*.");
  83. if (base::StartsWith(rule_host, kWildcard, base::CompareCase::SENSITIVE)) {
  84. if (base::EndsWith(url_host, rule_host.substr(1),
  85. base::CompareCase::SENSITIVE)) {
  86. return true;
  87. }
  88. // Check |url_host| is exactly |rule_host| without the wildcard. i.e. if
  89. // |rule_host| is "*.test.xyz", check |url_host| is exactly "test.xyz".
  90. return base::CompareCaseInsensitiveASCII(url_host, rule_host.substr(2)) ==
  91. 0;
  92. }
  93. return base::CompareCaseInsensitiveASCII(url_host, rule_host) == 0;
  94. }
  95. // Returns true if the host and scheme filters defined in |rule| match
  96. // |request|.
  97. bool RuleFiltersMatchRequest(network::ResourceRequest* request,
  98. const mojom::UrlRequestRulePtr& rule) {
  99. const GURL& url = request->url;
  100. if (rule->hosts_filter) {
  101. bool found = false;
  102. for (const base::StringPiece host : rule->hosts_filter.value()) {
  103. if ((found = HostMatches(url.host(), host)))
  104. break;
  105. }
  106. if (!found)
  107. return false;
  108. }
  109. if (rule->schemes_filter) {
  110. bool found = false;
  111. for (const auto& scheme : rule->schemes_filter.value()) {
  112. if (url.scheme().compare(scheme) == 0) {
  113. found = true;
  114. break;
  115. }
  116. }
  117. if (!found)
  118. return false;
  119. }
  120. return true;
  121. }
  122. // Returns true if |request| is either allowed or left unblocked by any rules.
  123. bool IsRequestAllowed(network::ResourceRequest* request,
  124. const mojom::UrlRequestRewriteRulesPtr& rules) {
  125. for (const auto& rule : rules->rules) {
  126. if (rule->actions.size() != 1)
  127. continue;
  128. if (rule->actions[0]->which() != mojom::UrlRequestAction::Tag::kPolicy)
  129. continue;
  130. if (!RuleFiltersMatchRequest(request, rule))
  131. continue;
  132. switch (rule->actions[0]->get_policy()) {
  133. case mojom::UrlRequestAccessPolicy::kAllow:
  134. return true;
  135. case mojom::UrlRequestAccessPolicy::kDeny:
  136. return false;
  137. }
  138. }
  139. return true;
  140. }
  141. } // namespace
  142. URLLoaderThrottle::URLLoaderThrottle(
  143. scoped_refptr<UrlRequestRewriteRules> rules,
  144. IsHeaderCorsExemptCallback is_header_cors_exempt_callback)
  145. : rules_(std::move(rules)),
  146. is_header_cors_exempt_callback_(
  147. std::move(is_header_cors_exempt_callback)) {
  148. DCHECK(rules_);
  149. DCHECK(is_header_cors_exempt_callback_);
  150. }
  151. URLLoaderThrottle::~URLLoaderThrottle() = default;
  152. void URLLoaderThrottle::DetachFromCurrentSequence() {}
  153. void URLLoaderThrottle::WillStartRequest(network::ResourceRequest* request,
  154. bool* defer) {
  155. if (!IsRequestAllowed(request, rules_->data)) {
  156. delegate_->CancelWithError(net::ERR_ABORTED,
  157. "Resource load blocked by embedder policy.");
  158. return;
  159. }
  160. for (const auto& rule : rules_->data->rules)
  161. ApplyRule(request, rule);
  162. *defer = false;
  163. }
  164. bool URLLoaderThrottle::makes_unsafe_redirect() {
  165. // WillStartRequest() does not make cross-scheme redirects.
  166. return false;
  167. }
  168. void URLLoaderThrottle::ApplyRule(network::ResourceRequest* request,
  169. const mojom::UrlRequestRulePtr& rule) {
  170. if (!RuleFiltersMatchRequest(request, rule))
  171. return;
  172. for (const auto& rewrite : rule->actions)
  173. ApplyRewrite(request, rewrite);
  174. }
  175. void URLLoaderThrottle::ApplyRewrite(
  176. network::ResourceRequest* request,
  177. const mojom::UrlRequestActionPtr& rewrite) {
  178. switch (rewrite->which()) {
  179. case mojom::UrlRequestAction::Tag::kAddHeaders:
  180. ApplyAddHeaders(request, rewrite->get_add_headers());
  181. return;
  182. case mojom::UrlRequestAction::Tag::kRemoveHeader:
  183. ApplyRemoveHeader(request, rewrite->get_remove_header());
  184. return;
  185. case mojom::UrlRequestAction::Tag::kSubstituteQueryPattern:
  186. ApplySubstituteQueryPattern(request,
  187. rewrite->get_substitute_query_pattern());
  188. return;
  189. case mojom::UrlRequestAction::Tag::kReplaceUrl:
  190. ApplyReplaceUrl(request, rewrite->get_replace_url());
  191. return;
  192. case mojom::UrlRequestAction::Tag::kAppendToQuery:
  193. ApplyAppendToQuery(request, rewrite->get_append_to_query());
  194. return;
  195. case mojom::UrlRequestAction::Tag::kPolicy:
  196. // "Policy" is interpreted elsewhere; it is a no-op for rewriting.
  197. return;
  198. }
  199. NOTREACHED(); // Invalid enum value.
  200. }
  201. void URLLoaderThrottle::ApplyAddHeaders(
  202. network::ResourceRequest* request,
  203. const mojom::UrlRequestRewriteAddHeadersPtr& add_headers) {
  204. // Bucket each |header| into the regular/CORS-compliant header list or the
  205. // CORS-exempt header list.
  206. for (const auto& header : add_headers->headers) {
  207. if (request->headers.HasHeader(header->name) ||
  208. request->cors_exempt_headers.HasHeader(header->name)) {
  209. // Skip headers already present in the request at this point.
  210. continue;
  211. }
  212. if (is_header_cors_exempt_callback_.Run(header->name)) {
  213. request->cors_exempt_headers.SetHeader(header->name, header->value);
  214. } else {
  215. request->headers.SetHeader(header->name, header->value);
  216. }
  217. }
  218. }
  219. } // namespace url_rewrite