aboutsummaryrefslogtreecommitdiff
path: root/src/net/http
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/http')
-rw-r--r--src/net/http/export_test.go9
-rw-r--r--src/net/http/request.go21
-rw-r--r--src/net/http/serve_test.go7
-rw-r--r--src/net/http/sniff.go5
-rw-r--r--src/net/http/transport_test.go42
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()