xref: /aosp_15_r20/external/cronet/net/url_request/redirect_util.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2017 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_util.h"
6 
7 #include "base/check.h"
8 #include "base/memory/scoped_refptr.h"
9 #include "base/strings/stringprintf.h"
10 #include "net/http/http_request_headers.h"
11 #include "net/http/http_response_headers.h"
12 #include "net/http/http_util.h"
13 #include "net/url_request/redirect_info.h"
14 #include "url/gurl.h"
15 #include "url/origin.h"
16 
17 namespace net {
18 
19 // static
UpdateHttpRequest(const GURL & original_url,const std::string & original_method,const RedirectInfo & redirect_info,const std::optional<std::vector<std::string>> & removed_headers,const std::optional<net::HttpRequestHeaders> & modified_headers,HttpRequestHeaders * request_headers,bool * should_clear_upload)20 void RedirectUtil::UpdateHttpRequest(
21     const GURL& original_url,
22     const std::string& original_method,
23     const RedirectInfo& redirect_info,
24     const std::optional<std::vector<std::string>>& removed_headers,
25     const std::optional<net::HttpRequestHeaders>& modified_headers,
26     HttpRequestHeaders* request_headers,
27     bool* should_clear_upload) {
28   DCHECK(request_headers);
29   DCHECK(should_clear_upload);
30 
31   *should_clear_upload = false;
32 
33   if (removed_headers) {
34     for (const std::string& key : removed_headers.value())
35       request_headers->RemoveHeader(key);
36   }
37 
38   if (redirect_info.new_method != original_method) {
39     // TODO(davidben): This logic still needs to be replicated at the consumers.
40     //
41     // The Origin header is sent on anything that is not a GET or HEAD, which
42     // suggests all redirects that change methods (since they always change to
43     // GET) should drop the Origin header.
44     // See https://fetch.spec.whatwg.org/#origin-header
45     // TODO(jww): This is Origin header removal is probably layering violation
46     // and should be refactored into //content. See https://crbug.com/471397.
47     // See also: https://crbug.com/760487
48     request_headers->RemoveHeader(HttpRequestHeaders::kOrigin);
49 
50     // This header should only be present further down the stack, but remove it
51     // here just in case.
52     request_headers->RemoveHeader(HttpRequestHeaders::kContentLength);
53 
54     // These are "request-body-headers" and should be removed on redirects that
55     // change the method, per the fetch spec.
56     // https://fetch.spec.whatwg.org/
57     request_headers->RemoveHeader(HttpRequestHeaders::kContentType);
58     request_headers->RemoveHeader("Content-Encoding");
59     request_headers->RemoveHeader("Content-Language");
60     request_headers->RemoveHeader("Content-Location");
61 
62     *should_clear_upload = true;
63   }
64 
65   // Cross-origin redirects should not result in an Origin header value that is
66   // equal to the original request's Origin header. This is necessary to prevent
67   // a reflection of POST requests to bypass CSRF protections. If the header was
68   // not set to "null", a POST request from origin A to a malicious origin M
69   // could be redirected by M back to A.
70   //
71   // This behavior is specified in step 10 of the HTTP-redirect fetch
72   // algorithm[1] (which supercedes the behavior outlined in RFC 6454[2].
73   //
74   // [1]: https://fetch.spec.whatwg.org/#http-redirect-fetch
75   // [2]: https://tools.ietf.org/html/rfc6454#section-7
76   //
77   // TODO(crbug.com/471397, crbug.com/1406737): This is a layering violation and
78   // should be refactored somewhere into //net's embedder. Also, step 13 of
79   // https://fetch.spec.whatwg.org/#http-redirect-fetch is implemented in
80   // Blink.
81   if (!url::IsSameOriginWith(redirect_info.new_url, original_url) &&
82       request_headers->HasHeader(HttpRequestHeaders::kOrigin)) {
83     request_headers->SetHeader(HttpRequestHeaders::kOrigin,
84                                url::Origin().Serialize());
85   }
86 
87   if (modified_headers)
88     request_headers->MergeFrom(modified_headers.value());
89 }
90 
91 // static
GetReferrerPolicyHeader(const HttpResponseHeaders * response_headers)92 std::optional<std::string> RedirectUtil::GetReferrerPolicyHeader(
93     const HttpResponseHeaders* response_headers) {
94   if (!response_headers)
95     return std::nullopt;
96   std::string referrer_policy_header;
97   if (!response_headers->GetNormalizedHeader("Referrer-Policy",
98                                              &referrer_policy_header)) {
99     return std::nullopt;
100   }
101   return referrer_policy_header;
102 }
103 
104 // static
SynthesizeRedirectHeaders(const GURL & redirect_destination,ResponseCode response_code,const std::string & redirect_reason,const HttpRequestHeaders & request_headers)105 scoped_refptr<HttpResponseHeaders> RedirectUtil::SynthesizeRedirectHeaders(
106     const GURL& redirect_destination,
107     ResponseCode response_code,
108     const std::string& redirect_reason,
109     const HttpRequestHeaders& request_headers) {
110   std::string header_string = base::StringPrintf(
111       "HTTP/1.1 %i Internal Redirect\n"
112       "Location: %s\n"
113       "Cross-Origin-Resource-Policy: Cross-Origin\n"
114       "Non-Authoritative-Reason: %s",
115       static_cast<int>(response_code), redirect_destination.spec().c_str(),
116       redirect_reason.c_str());
117 
118   std::string http_origin;
119   if (request_headers.GetHeader("Origin", &http_origin)) {
120     // If this redirect is used in a cross-origin request, add CORS headers to
121     // make sure that the redirect gets through. Note that the destination URL
122     // is still subject to the usual CORS policy, i.e. the resource will only
123     // be available to web pages if the server serves the response with the
124     // required CORS response headers.
125     header_string += base::StringPrintf(
126         "\n"
127         "Access-Control-Allow-Origin: %s\n"
128         "Access-Control-Allow-Credentials: true",
129         http_origin.c_str());
130   }
131 
132   auto fake_headers = base::MakeRefCounted<HttpResponseHeaders>(
133       HttpUtil::AssembleRawHeaders(header_string));
134   DCHECK(fake_headers->IsRedirect(nullptr));
135 
136   return fake_headers;
137 }
138 
139 }  // namespace net
140