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