Skip to content

Commit 831af61

Browse files
committed
net/http: use HTTP 307 redirects in ServeMux
Clients receiving an HTTP 301 Moved Permanently may conservatively change the method of a POST request to GET. The newer HTTP 307 Temporary Redirect and 308 Permanent Redirect explicitly allows retrying POST requests after the redirect. These should be safe for ServeMux as this internal redirect is generated before user provided handlers are called. As ServeMux is making the redirect for the user without explicit direction, and clients may cache Permanent Redirects indefinitely, Temporary Redirect is used in case the user adds a handler for a path, that was previously redirected but no longer should. Fixes #50243 Fixes #60769 Change-Id: I6c0b735bab03bb7b50f05457b3b8a8ba813badb2 Reviewed-on: https://go-review.googlesource.com/c/go/+/720820 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Mark Freeman <[email protected]>
1 parent 8726922 commit 831af61

File tree

3 files changed

+55
-24
lines changed

3 files changed

+55
-24
lines changed

src/net/http/serve_test.go

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ func testHostHandlers(t *testing.T, mode testMode) {
288288
if s != vt.expected {
289289
t.Errorf("Get(%q) = %q, want %q", vt.url, s, vt.expected)
290290
}
291-
case StatusMovedPermanently:
291+
case StatusTemporaryRedirect:
292292
s := r.Header.Get("Location")
293293
if s != vt.expected {
294294
t.Errorf("Get(%q) = %q, want %q", vt.url, s, vt.expected)
@@ -340,7 +340,7 @@ var serveMuxTests = []struct {
340340
pattern string
341341
}{
342342
{"GET", "google.com", "/", 404, ""},
343-
{"GET", "google.com", "/dir", 301, "/dir/"},
343+
{"GET", "google.com", "/dir", 307, "/dir/"},
344344
{"GET", "google.com", "/dir/", 200, "/dir/"},
345345
{"GET", "google.com", "/dir/file", 200, "/dir/"},
346346
{"GET", "google.com", "/search", 201, "/search"},
@@ -354,14 +354,14 @@ var serveMuxTests = []struct {
354354
{"GET", "images.google.com", "/search", 201, "/search"},
355355
{"GET", "images.google.com", "/search/", 404, ""},
356356
{"GET", "images.google.com", "/search/foo", 404, ""},
357-
{"GET", "google.com", "/../search", 301, "/search"},
358-
{"GET", "google.com", "/dir/..", 301, ""},
359-
{"GET", "google.com", "/dir/..", 301, ""},
360-
{"GET", "google.com", "/dir/./file", 301, "/dir/"},
357+
{"GET", "google.com", "/../search", 307, "/search"},
358+
{"GET", "google.com", "/dir/..", 307, ""},
359+
{"GET", "google.com", "/dir/..", 307, ""},
360+
{"GET", "google.com", "/dir/./file", 307, "/dir/"},
361361

362362
// The /foo -> /foo/ redirect applies to CONNECT requests
363363
// but the path canonicalization does not.
364-
{"CONNECT", "google.com", "/dir", 301, "/dir/"},
364+
{"CONNECT", "google.com", "/dir", 307, "/dir/"},
365365
{"CONNECT", "google.com", "/../search", 404, ""},
366366
{"CONNECT", "google.com", "/dir/..", 200, "/dir/"},
367367
{"CONNECT", "google.com", "/dir/..", 200, "/dir/"},
@@ -454,7 +454,7 @@ func TestServeMuxHandlerRedirects(t *testing.T) {
454454
h, _ := mux.Handler(r)
455455
rr := httptest.NewRecorder()
456456
h.ServeHTTP(rr, r)
457-
if rr.Code != 301 {
457+
if rr.Code != 307 {
458458
if rr.Code != tt.code {
459459
t.Errorf("%s %s %s = %d, want %d", tt.method, tt.host, tt.url, rr.Code, tt.code)
460460
}
@@ -473,6 +473,37 @@ func TestServeMuxHandlerRedirects(t *testing.T) {
473473
}
474474
}
475475

476+
func TestServeMuxHandlerRedirectPost(t *testing.T) {
477+
setParallel(t)
478+
mux := NewServeMux()
479+
mux.HandleFunc("POST /test/", func(w ResponseWriter, r *Request) {
480+
w.WriteHeader(200)
481+
})
482+
483+
var code, retries int
484+
startURL := "http://example.com/test"
485+
reqURL := startURL
486+
for retries = 0; retries <= 1; retries++ {
487+
r := httptest.NewRequest("POST", reqURL, strings.NewReader("hello world"))
488+
h, _ := mux.Handler(r)
489+
rr := httptest.NewRecorder()
490+
h.ServeHTTP(rr, r)
491+
code = rr.Code
492+
switch rr.Code {
493+
case 307:
494+
reqURL = rr.Result().Header.Get("Location")
495+
continue
496+
case 200:
497+
// ok
498+
default:
499+
t.Errorf("unhandled response code: %v", rr.Code)
500+
}
501+
}
502+
if code != 200 {
503+
t.Errorf("POST %s = %d after %d retries, want = 200", startURL, code, retries)
504+
}
505+
}
506+
476507
// Tests for https://golang.org/issue/900
477508
func TestMuxRedirectLeadingSlashes(t *testing.T) {
478509
setParallel(t)
@@ -492,8 +523,8 @@ func TestMuxRedirectLeadingSlashes(t *testing.T) {
492523
return
493524
}
494525

495-
if code, expected := resp.Code, StatusMovedPermanently; code != expected {
496-
t.Errorf("Expected response code of StatusMovedPermanently; got %d", code)
526+
if code, expected := resp.Code, StatusTemporaryRedirect; code != expected {
527+
t.Errorf("Expected response code of StatusPermanentRedirect; got %d", code)
497528
return
498529
}
499530
}
@@ -579,18 +610,18 @@ func TestServeWithSlashRedirectForHostPatterns(t *testing.T) {
579610
want string
580611
}{
581612
{"GET", "http://example.com/", 404, "", ""},
582-
{"GET", "http://example.com/pkg/foo", 301, "/pkg/foo/", ""},
613+
{"GET", "http://example.com/pkg/foo", 307, "/pkg/foo/", ""},
583614
{"GET", "http://example.com/pkg/bar", 200, "", "example.com/pkg/bar"},
584615
{"GET", "http://example.com/pkg/bar/", 200, "", "example.com/pkg/bar/"},
585-
{"GET", "http://example.com/pkg/baz", 301, "/pkg/baz/", ""},
586-
{"GET", "http://example.com:3000/pkg/foo", 301, "/pkg/foo/", ""},
616+
{"GET", "http://example.com/pkg/baz", 307, "/pkg/baz/", ""},
617+
{"GET", "http://example.com:3000/pkg/foo", 307, "/pkg/foo/", ""},
587618
{"CONNECT", "http://example.com/", 404, "", ""},
588619
{"CONNECT", "http://example.com:3000/", 404, "", ""},
589620
{"CONNECT", "http://example.com:9000/", 200, "", "example.com:9000/"},
590-
{"CONNECT", "http://example.com/pkg/foo", 301, "/pkg/foo/", ""},
621+
{"CONNECT", "http://example.com/pkg/foo", 307, "/pkg/foo/", ""},
591622
{"CONNECT", "http://example.com:3000/pkg/foo", 404, "", ""},
592-
{"CONNECT", "http://example.com:3000/pkg/baz", 301, "/pkg/baz/", ""},
593-
{"CONNECT", "http://example.com:3000/pkg/connect", 301, "/pkg/connect/", ""},
623+
{"CONNECT", "http://example.com:3000/pkg/baz", 307, "/pkg/baz/", ""},
624+
{"CONNECT", "http://example.com:3000/pkg/connect", 307, "/pkg/connect/", ""},
594625
}
595626

596627
for i, tt := range tests {
@@ -6940,7 +6971,7 @@ func TestMuxRedirectRelative(t *testing.T) {
69406971
if got, want := resp.Header().Get("Location"), "/"; got != want {
69416972
t.Errorf("Location header expected %q; got %q", want, got)
69426973
}
6943-
if got, want := resp.Code, StatusMovedPermanently; got != want {
6974+
if got, want := resp.Code, StatusTemporaryRedirect; got != want {
69446975
t.Errorf("Expected response code %d; got %d", want, got)
69456976
}
69466977
}

src/net/http/server.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2704,7 +2704,7 @@ func (mux *ServeMux) findHandler(r *Request) (h Handler, patStr string, _ *patte
27042704
// but the path canonicalization does not.
27052705
_, _, u := mux.matchOrRedirect(host, r.Method, path, r.URL)
27062706
if u != nil {
2707-
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path, nil, nil
2707+
return RedirectHandler(u.String(), StatusTemporaryRedirect), u.Path, nil, nil
27082708
}
27092709
// Redo the match, this time with r.Host instead of r.URL.Host.
27102710
// Pass a nil URL to skip the trailing-slash redirect logic.
@@ -2720,7 +2720,7 @@ func (mux *ServeMux) findHandler(r *Request) (h Handler, patStr string, _ *patte
27202720
var u *url.URL
27212721
n, matches, u = mux.matchOrRedirect(host, r.Method, path, r.URL)
27222722
if u != nil {
2723-
return RedirectHandler(u.String(), StatusMovedPermanently), n.pattern.String(), nil, nil
2723+
return RedirectHandler(u.String(), StatusTemporaryRedirect), n.pattern.String(), nil, nil
27242724
}
27252725
if path != escapedPath {
27262726
// Redirect to cleaned path.
@@ -2729,7 +2729,7 @@ func (mux *ServeMux) findHandler(r *Request) (h Handler, patStr string, _ *patte
27292729
patStr = n.pattern.String()
27302730
}
27312731
u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}
2732-
return RedirectHandler(u.String(), StatusMovedPermanently), patStr, nil, nil
2732+
return RedirectHandler(u.String(), StatusTemporaryRedirect), patStr, nil, nil
27332733
}
27342734
}
27352735
if n == nil {

src/net/http/server_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,12 @@ func TestFindHandler(t *testing.T) {
9191
wantHandler string
9292
}{
9393
{"GET", "/", "&http.handler{i:1}"},
94-
{"GET", "//", `&http.redirectHandler{url:"/", code:301}`},
95-
{"GET", "/foo/../bar/./..//baz", `&http.redirectHandler{url:"/baz", code:301}`},
94+
{"GET", "//", `&http.redirectHandler{url:"/", code:307}`},
95+
{"GET", "/foo/../bar/./..//baz", `&http.redirectHandler{url:"/baz", code:307}`},
9696
{"GET", "/foo", "&http.handler{i:3}"},
9797
{"GET", "/foo/x", "&http.handler{i:2}"},
9898
{"GET", "/bar/x", "&http.handler{i:4}"},
99-
{"GET", "/bar", `&http.redirectHandler{url:"/bar/", code:301}`},
99+
{"GET", "/bar", `&http.redirectHandler{url:"/bar/", code:307}`},
100100
{"CONNECT", "", "(http.HandlerFunc)(.*)"},
101101
{"CONNECT", "/", "&http.handler{i:1}"},
102102
{"CONNECT", "//", "&http.handler{i:1}"},
@@ -105,7 +105,7 @@ func TestFindHandler(t *testing.T) {
105105
{"CONNECT", "/foo", "&http.handler{i:3}"},
106106
{"CONNECT", "/foo/x", "&http.handler{i:2}"},
107107
{"CONNECT", "/bar/x", "&http.handler{i:4}"},
108-
{"CONNECT", "/bar", `&http.redirectHandler{url:"/bar/", code:301}`},
108+
{"CONNECT", "/bar", `&http.redirectHandler{url:"/bar/", code:307}`},
109109
} {
110110
var r Request
111111
r.Method = test.method

0 commit comments

Comments
 (0)