From 229937c503616f6ef5479d8a1dda44b480141e8d Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 28 Aug 2024 20:31:58 +0800 Subject: [PATCH] http3: reject the Transfer-Encoding header field (#4646) The use of Transfer-Encoding is forbidden in HTTP/3, see section 4.1 of RFC 9114. --- http3/headers.go | 2 ++ http3/headers_test.go | 9 +++++++++ http3/http_stream.go | 3 +-- http3/response_writer.go | 6 ++---- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/http3/headers.go b/http3/headers.go index c51f48d00..7c67342e2 100644 --- a/http3/headers.go +++ b/http3/headers.go @@ -75,6 +75,8 @@ func parseHeaders(headers []qpack.HeaderField, isRequest bool) (header, error) { } readFirstRegularHeader = true switch h.Name { + case "transfer-encoding": + return header{}, errors.New("invalid header field: Transfer-Encoding") case "content-length": // Ignore duplicate Content-Length headers. // Fail if the duplicates differ. diff --git a/http3/headers_test.go b/http3/headers_test.go index 65fa38370..e9bf7a769 100644 --- a/http3/headers_test.go +++ b/http3/headers_test.go @@ -359,6 +359,15 @@ var _ = Describe("Response", func() { Expect(err).To(MatchError("invalid response pseudo header: :method")) }) + It("rejects the Transfer-Encoding header field", func() { + headers := []qpack.HeaderField{ + {Name: ":status", Value: "404"}, + {Name: "transfer-encoding", Value: "chunked"}, + } + err := updateResponseFromHeaders(&http.Response{}, headers) + Expect(err).To(MatchError("invalid header field: Transfer-Encoding")) + }) + It("parses trailers", func() { headers := []qpack.HeaderField{ {Name: "content-length", Value: "42"}, diff --git a/http3/http_stream.go b/http3/http_stream.go index dd7150645..af46226ba 100644 --- a/http3/http_stream.go +++ b/http3/http_stream.go @@ -245,11 +245,10 @@ func (s *requestStream) ReadResponse() (*http.Response, error) { respBody := newResponseBody(s.stream, contentLength, s.reqDone) // Rules for when to set Content-Length are defined in https://tools.ietf.org/html/rfc7230#section-3.3.2. - _, hasTransferEncoding := res.Header["Transfer-Encoding"] isInformational := res.StatusCode >= 100 && res.StatusCode < 200 isNoContent := res.StatusCode == http.StatusNoContent isSuccessfulConnect := s.isConnect && res.StatusCode >= 200 && res.StatusCode < 300 - if !hasTransferEncoding && !isInformational && !isNoContent && !isSuccessfulConnect { + if !isInformational && !isNoContent && !isSuccessfulConnect { res.ContentLength = -1 if clens, ok := res.Header["Content-Length"]; ok && len(clens) == 1 { if clen64, err := strconv.ParseInt(clens[0], 10, 64); err == nil { diff --git a/http3/response_writer.go b/http3/response_writer.go index 8638ec577..71f6551ef 100644 --- a/http3/response_writer.go +++ b/http3/response_writer.go @@ -117,11 +117,9 @@ func (w *responseWriter) sniffContentType(p []byte) { // We can't use `w.header.Get` here since if the Content-Type was set to nil, we shouldn't do sniffing. _, haveType := w.header["Content-Type"] - // If the Transfer-Encoding or Content-Encoding was set and is non-blank, - // we shouldn't sniff the body. - hasTE := w.header.Get("Transfer-Encoding") != "" + // If the Content-Encoding was set and is non-blank, we shouldn't sniff the body. hasCE := w.header.Get("Content-Encoding") != "" - if !hasCE && !haveType && !hasTE && len(p) > 0 { + if !hasCE && !haveType && len(p) > 0 { w.header.Set("Content-Type", http.DetectContentType(p)) } }