net/http: strip request body headers on POST to GET redirects

According to WHATWG Fetch, when the body is dropped in a redirect,
headers that describe the body should also be dropped.
https://fetch.spec.whatwg.org/#http-redirect-fetch

Fixes #57273

Change-Id: I84598f69608e95c1b556ea0ce5953ed43bf2d824
Reviewed-on: https://go-review.googlesource.com/c/go/+/710395
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Sean Liao 2025-10-09 02:26:02 +01:00 committed by Gopher Robot
parent 584a89fe74
commit aced4c79a2
2 changed files with 46 additions and 5 deletions

View file

@ -690,8 +690,7 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) {
stripSensitiveHeaders = true
}
}
copyHeaders(req, stripSensitiveHeaders)
copyHeaders(req, stripSensitiveHeaders, !includeBody)
// Add the Referer header from the most recent
// request URL to the new one, if it's not https->http:
if ref := refererForURL(reqs[len(reqs)-1].URL, req.URL, req.Header.Get("Referer")); ref != "" {
@ -758,7 +757,7 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) {
// makeHeadersCopier makes a function that copies headers from the
// initial Request, ireq. For every redirect, this function must be called
// so that it can copy headers into the upcoming Request.
func (c *Client) makeHeadersCopier(ireq *Request) func(req *Request, stripSensitiveHeaders bool) {
func (c *Client) makeHeadersCopier(ireq *Request) func(req *Request, stripSensitiveHeaders, stripBodyHeaders bool) {
// The headers to copy are from the very initial request.
// We use a closured callback to keep a reference to these original headers.
var (
@ -772,7 +771,7 @@ func (c *Client) makeHeadersCopier(ireq *Request) func(req *Request, stripSensit
}
}
return func(req *Request, stripSensitiveHeaders bool) {
return func(req *Request, stripSensitiveHeaders, stripBodyHeaders bool) {
// If Jar is present and there was some initial cookies provided
// via the request header, then we may need to alter the initial
// cookies as we follow redirects since each redirect may end up
@ -810,12 +809,21 @@ func (c *Client) makeHeadersCopier(ireq *Request) func(req *Request, stripSensit
// (at least the safe ones).
for k, vv := range ireqhdr {
sensitive := false
body := false
switch CanonicalHeaderKey(k) {
case "Authorization", "Www-Authenticate", "Cookie", "Cookie2",
"Proxy-Authorization", "Proxy-Authenticate":
sensitive = true
case "Content-Encoding", "Content-Language", "Content-Location",
"Content-Type":
// Headers relating to the body which is removed for
// POST to GET redirects
// https://fetch.spec.whatwg.org/#http-redirect-fetch
body = true
}
if !(sensitive && stripSensitiveHeaders) {
if !(sensitive && stripSensitiveHeaders) && !(body && stripBodyHeaders) {
req.Header[k] = vv
}
}

View file

@ -1621,6 +1621,39 @@ func testClientStripHeadersOnRepeatedRedirect(t *testing.T, mode testMode) {
}
}
func TestClientStripHeadersOnPostToGetRedirect(t *testing.T) {
run(t, testClientStripHeadersOnPostToGetRedirect)
}
func testClientStripHeadersOnPostToGetRedirect(t *testing.T, mode testMode) {
ts := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
if r.Method == "POST" {
Redirect(w, r, "/redirected", StatusFound)
return
} else if r.Method != "GET" {
t.Errorf("unexpected request method: %v", r.Method)
return
}
for key, val := range r.Header {
if strings.HasPrefix(key, "Content-") {
t.Errorf("unexpected request body header after redirect: %v: %v", key, val)
}
}
})).ts
c := ts.Client()
req, _ := NewRequest("POST", ts.URL, strings.NewReader("hello world"))
req.Header.Set("Content-Encoding", "a")
req.Header.Set("Content-Language", "b")
req.Header.Set("Content-Length", "c")
req.Header.Set("Content-Type", "d")
res, err := c.Do(req)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
}
// Issue 22233: copy host when Client follows a relative redirect.
func TestClientCopyHostOnRedirect(t *testing.T) { run(t, testClientCopyHostOnRedirect) }
func testClientCopyHostOnRedirect(t *testing.T, mode testMode) {