diff options
Diffstat (limited to 'src/net/http')
-rw-r--r-- | src/net/http/export_test.go | 9 | ||||
-rw-r--r-- | src/net/http/request.go | 21 | ||||
-rw-r--r-- | src/net/http/serve_test.go | 7 | ||||
-rw-r--r-- | src/net/http/sniff.go | 5 | ||||
-rw-r--r-- | src/net/http/transport_test.go | 42 |
5 files changed, 64 insertions, 20 deletions
diff --git a/src/net/http/export_test.go b/src/net/http/export_test.go index a849327f452..205ca83f402 100644 --- a/src/net/http/export_test.go +++ b/src/net/http/export_test.go @@ -306,3 +306,12 @@ func ExportCloseTransportConnsAbruptly(tr *Transport) { } tr.idleMu.Unlock() } + +// ResponseWriterConnForTesting returns w's underlying connection, if w +// is a regular *response ResponseWriter. +func ResponseWriterConnForTesting(w ResponseWriter) (c net.Conn, ok bool) { + if r, ok := w.(*response); ok { + return r.conn.rwc, true + } + return nil, false +} diff --git a/src/net/http/request.go b/src/net/http/request.go index 312211977d5..d091f3c056a 100644 --- a/src/net/http/request.go +++ b/src/net/http/request.go @@ -1126,21 +1126,34 @@ func readRequest(b *bufio.Reader) (req *Request, err error) { // MaxBytesReader is similar to io.LimitReader but is intended for // limiting the size of incoming request bodies. In contrast to // io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a -// non-EOF error for a Read beyond the limit, and closes the +// MaxBytesError for a Read beyond the limit, and closes the // underlying reader when its Close method is called. // // MaxBytesReader prevents clients from accidentally or maliciously -// sending a large request and wasting server resources. +// sending a large request and wasting server resources. If possible, +// it tells the ResponseWriter to close the connection after the limit +// has been reached. func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser { if n < 0 { // Treat negative limits as equivalent to 0. n = 0 } - return &maxBytesReader{w: w, r: r, n: n} + return &maxBytesReader{w: w, r: r, i: n, n: n} +} + +// MaxBytesError is returned by MaxBytesReader when its read limit is exceeded. +type MaxBytesError struct { + Limit int64 +} + +func (e *MaxBytesError) Error() string { + // Due to Hyrum's law, this text cannot be changed. + return "http: request body too large" } type maxBytesReader struct { w ResponseWriter r io.ReadCloser // underlying reader + i int64 // max bytes initially, for MaxBytesError n int64 // max bytes remaining err error // sticky error } @@ -1182,7 +1195,7 @@ func (l *maxBytesReader) Read(p []byte) (n int, err error) { if res, ok := l.w.(requestTooLarger); ok { res.requestTooLarge() } - l.err = errors.New("http: request body too large") + l.err = &MaxBytesError{l.i} return n, l.err } diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index 1c85a665997..404cca0825a 100644 --- a/src/net/http/serve_test.go +++ b/src/net/http/serve_test.go @@ -3035,6 +3035,13 @@ func testRequestBodyLimit(t *testing.T, h2 bool) { if n != limit { t.Errorf("io.Copy = %d, want %d", n, limit) } + mbErr, ok := err.(*MaxBytesError) + if !ok { + t.Errorf("expected MaxBytesError, got %T", err) + } + if mbErr.Limit != limit { + t.Errorf("MaxBytesError.Limit = %d, want %d", mbErr.Limit, limit) + } })) defer cst.close() diff --git a/src/net/http/sniff.go b/src/net/http/sniff.go index 67a7151b0cc..ac18ab979d0 100644 --- a/src/net/http/sniff.go +++ b/src/net/http/sniff.go @@ -129,11 +129,6 @@ var sniffSignatures = []sniffSig{ // Enforce the pattern match ordering as prescribed in // https://mimesniff.spec.whatwg.org/#matching-an-audio-or-video-type-pattern &maskedSig{ - mask: []byte("\xFF\xFF\xFF\xFF"), - pat: []byte(".snd"), - ct: "audio/basic", - }, - &maskedSig{ mask: []byte("\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF"), pat: []byte("FORM\x00\x00\x00\x00AIFF"), ct: "audio/aiff", diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index 6fcb458296f..acdfae39edc 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -57,6 +57,12 @@ var hostPortHandler = HandlerFunc(func(w ResponseWriter, r *Request) { } w.Header().Set("X-Saw-Close", fmt.Sprint(r.Close)) w.Write([]byte(r.RemoteAddr)) + + // Include the address of the net.Conn in addition to the RemoteAddr, + // in case kernels reuse source ports quickly (see Issue 52450) + if c, ok := ResponseWriterConnForTesting(w); ok { + fmt.Fprintf(w, ", %T %p", c, c) + } }) // testCloseConn is a net.Conn tracked by a testConnSet. @@ -240,6 +246,12 @@ func TestTransportConnectionCloseOnResponse(t *testing.T) { connSet.check(t) } +// TestTransportConnectionCloseOnRequest tests that the Transport's doesn't reuse +// an underlying TCP connection after making an http.Request with Request.Close set. +// +// It tests the behavior by making an HTTP request to a server which +// describes the source source connection it got (remote port number + +// address of its net.Conn). func TestTransportConnectionCloseOnRequest(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(hostPortHandler) @@ -250,7 +262,7 @@ func TestTransportConnectionCloseOnRequest(t *testing.T) { c := ts.Client() tr := c.Transport.(*Transport) tr.Dial = testDial - for _, connectionClose := range []bool{false, true} { + for _, reqClose := range []bool{false, true} { fetch := func(n int) string { req := new(Request) var err error @@ -262,29 +274,37 @@ func TestTransportConnectionCloseOnRequest(t *testing.T) { req.Proto = "HTTP/1.1" req.ProtoMajor = 1 req.ProtoMinor = 1 - req.Close = connectionClose + req.Close = reqClose res, err := c.Do(req) if err != nil { - t.Fatalf("error in connectionClose=%v, req #%d, Do: %v", connectionClose, n, err) + t.Fatalf("error in Request.Close=%v, req #%d, Do: %v", reqClose, n, err) } - if got, want := res.Header.Get("X-Saw-Close"), fmt.Sprint(connectionClose); got != want { - t.Errorf("For connectionClose = %v; handler's X-Saw-Close was %v; want %v", - connectionClose, got, !connectionClose) + if got, want := res.Header.Get("X-Saw-Close"), fmt.Sprint(reqClose); got != want { + t.Errorf("for Request.Close = %v; handler's X-Saw-Close was %v; want %v", + reqClose, got, !reqClose) } body, err := io.ReadAll(res.Body) if err != nil { - t.Fatalf("error in connectionClose=%v, req #%d, ReadAll: %v", connectionClose, n, err) + t.Fatalf("for Request.Close=%v, on request %v/2: ReadAll: %v", reqClose, n, err) } return string(body) } body1 := fetch(1) body2 := fetch(2) - bodiesDiffer := body1 != body2 - if bodiesDiffer != connectionClose { - t.Errorf("error in connectionClose=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q", - connectionClose, bodiesDiffer, body1, body2) + + got := 1 + if body1 != body2 { + got++ + } + want := 1 + if reqClose { + want = 2 + } + if got != want { + t.Errorf("for Request.Close=%v: server saw %v unique connections, wanted %v\n\nbodies were: %q and %q", + reqClose, got, want, body1, body2) } tr.CloseIdleConnections() |