xref: /aosp_15_r20/external/cronet/net/url_request/redirect_info.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/url_request/redirect_info.h"
6 
7 #include <string_view>
8 
9 #include "base/metrics/histogram_macros.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/string_util.h"
12 #include "net/url_request/url_request_job.h"
13 
14 namespace net {
15 
16 namespace {
17 
ComputeMethodForRedirect(const std::string & method,int http_status_code)18 std::string ComputeMethodForRedirect(const std::string& method,
19                                      int http_status_code) {
20   // For 303 redirects, all request methods except HEAD are converted to GET,
21   // as per the latest httpbis draft.  The draft also allows POST requests to
22   // be converted to GETs when following 301/302 redirects, for historical
23   // reasons. Most major browsers do this and so shall we.  Both RFC 2616 and
24   // the httpbis draft say to prompt the user to confirm the generation of new
25   // requests, other than GET and HEAD requests, but IE omits these prompts and
26   // so shall we.
27   // See: https://tools.ietf.org/html/rfc7231#section-6.4
28   if ((http_status_code == 303 && method != "HEAD") ||
29       ((http_status_code == 301 || http_status_code == 302) &&
30        method == "POST")) {
31     return "GET";
32   }
33   return method;
34 }
35 
36 // A redirect response can contain a Referrer-Policy header
37 // (https://w3c.github.io/webappsec-referrer-policy/). This function checks for
38 // a Referrer-Policy header, and parses it if present. Returns the referrer
39 // policy that should be used for the request.
ProcessReferrerPolicyHeaderOnRedirect(ReferrerPolicy original_referrer_policy,const std::optional<std::string> & referrer_policy_header)40 ReferrerPolicy ProcessReferrerPolicyHeaderOnRedirect(
41     ReferrerPolicy original_referrer_policy,
42     const std::optional<std::string>& referrer_policy_header) {
43   ReferrerPolicy new_policy = original_referrer_policy;
44   std::vector<std::string_view> policy_tokens;
45   if (referrer_policy_header) {
46     policy_tokens = base::SplitStringPiece(*referrer_policy_header, ",",
47                                            base::TRIM_WHITESPACE,
48                                            base::SPLIT_WANT_NONEMPTY);
49   }
50 
51   // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values,
52   // use the last recognized policy value, and ignore unknown policies.
53   for (const auto& token : policy_tokens) {
54     if (base::CompareCaseInsensitiveASCII(token, "no-referrer") == 0) {
55       new_policy = ReferrerPolicy::NO_REFERRER;
56       continue;
57     }
58 
59     if (base::CompareCaseInsensitiveASCII(token,
60                                           "no-referrer-when-downgrade") == 0) {
61       new_policy = ReferrerPolicy::CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE;
62       continue;
63     }
64 
65     if (base::CompareCaseInsensitiveASCII(token, "origin") == 0) {
66       new_policy = ReferrerPolicy::ORIGIN;
67       continue;
68     }
69 
70     if (base::CompareCaseInsensitiveASCII(token, "origin-when-cross-origin") ==
71         0) {
72       new_policy = ReferrerPolicy::ORIGIN_ONLY_ON_TRANSITION_CROSS_ORIGIN;
73       continue;
74     }
75 
76     if (base::CompareCaseInsensitiveASCII(token, "unsafe-url") == 0) {
77       new_policy = ReferrerPolicy::NEVER_CLEAR;
78       continue;
79     }
80 
81     if (base::CompareCaseInsensitiveASCII(token, "same-origin") == 0) {
82       new_policy = ReferrerPolicy::CLEAR_ON_TRANSITION_CROSS_ORIGIN;
83       continue;
84     }
85 
86     if (base::CompareCaseInsensitiveASCII(token, "strict-origin") == 0) {
87       new_policy =
88           ReferrerPolicy::ORIGIN_CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE;
89       continue;
90     }
91 
92     if (base::CompareCaseInsensitiveASCII(
93             token, "strict-origin-when-cross-origin") == 0) {
94       new_policy =
95           ReferrerPolicy::REDUCE_GRANULARITY_ON_TRANSITION_CROSS_ORIGIN;
96       continue;
97     }
98   }
99   return new_policy;
100 }
101 
102 }  // namespace
103 
104 RedirectInfo::RedirectInfo() = default;
105 
106 RedirectInfo::RedirectInfo(const RedirectInfo& other) = default;
107 
108 RedirectInfo::~RedirectInfo() = default;
109 
ComputeRedirectInfo(const std::string & original_method,const GURL & original_url,const SiteForCookies & original_site_for_cookies,RedirectInfo::FirstPartyURLPolicy original_first_party_url_policy,ReferrerPolicy original_referrer_policy,const std::string & original_referrer,int http_status_code,const GURL & new_location,const std::optional<std::string> & referrer_policy_header,bool insecure_scheme_was_upgraded,bool copy_fragment,bool is_signed_exchange_fallback_redirect)110 RedirectInfo RedirectInfo::ComputeRedirectInfo(
111     const std::string& original_method,
112     const GURL& original_url,
113     const SiteForCookies& original_site_for_cookies,
114     RedirectInfo::FirstPartyURLPolicy original_first_party_url_policy,
115     ReferrerPolicy original_referrer_policy,
116     const std::string& original_referrer,
117     int http_status_code,
118     const GURL& new_location,
119     const std::optional<std::string>& referrer_policy_header,
120     bool insecure_scheme_was_upgraded,
121     bool copy_fragment,
122     bool is_signed_exchange_fallback_redirect) {
123   RedirectInfo redirect_info;
124 
125   redirect_info.status_code = http_status_code;
126 
127   // The request method may change, depending on the status code.
128   redirect_info.new_method =
129       ComputeMethodForRedirect(original_method, http_status_code);
130 
131   // Move the reference fragment of the old location to the new one if the
132   // new one has none. This duplicates mozilla's behavior.
133   if (original_url.is_valid() && original_url.has_ref() &&
134       !new_location.has_ref() && copy_fragment) {
135     GURL::Replacements replacements;
136     // Reference the |ref| directly out of the original URL to avoid a
137     // malloc.
138     replacements.SetRefStr(original_url.ref_piece());
139     redirect_info.new_url = new_location.ReplaceComponents(replacements);
140   } else {
141     redirect_info.new_url = new_location;
142   }
143 
144   redirect_info.insecure_scheme_was_upgraded = insecure_scheme_was_upgraded;
145   redirect_info.is_signed_exchange_fallback_redirect =
146       is_signed_exchange_fallback_redirect;
147 
148   // Update the first-party URL if appropriate.
149   if (original_first_party_url_policy ==
150       FirstPartyURLPolicy::UPDATE_URL_ON_REDIRECT) {
151     redirect_info.new_site_for_cookies =
152         SiteForCookies::FromUrl(redirect_info.new_url);
153   } else {
154     redirect_info.new_site_for_cookies = original_site_for_cookies;
155   }
156 
157   redirect_info.new_referrer_policy = ProcessReferrerPolicyHeaderOnRedirect(
158       original_referrer_policy, referrer_policy_header);
159 
160   // Alter the referrer if redirecting cross-origin (especially HTTP->HTTPS).
161   redirect_info.new_referrer =
162       URLRequestJob::ComputeReferrerForPolicy(redirect_info.new_referrer_policy,
163                                               GURL(original_referrer),
164                                               redirect_info.new_url)
165           .spec();
166 
167   return redirect_info;
168 }
169 
170 }  // namespace net
171