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