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.
This commit is contained in:
Marten Seemann
2024-08-28 20:31:58 +08:00
committed by GitHub
parent 920bfb46af
commit 229937c503
4 changed files with 14 additions and 6 deletions

View File

@@ -75,6 +75,8 @@ func parseHeaders(headers []qpack.HeaderField, isRequest bool) (header, error) {
} }
readFirstRegularHeader = true readFirstRegularHeader = true
switch h.Name { switch h.Name {
case "transfer-encoding":
return header{}, errors.New("invalid header field: Transfer-Encoding")
case "content-length": case "content-length":
// Ignore duplicate Content-Length headers. // Ignore duplicate Content-Length headers.
// Fail if the duplicates differ. // Fail if the duplicates differ.

View File

@@ -359,6 +359,15 @@ var _ = Describe("Response", func() {
Expect(err).To(MatchError("invalid response pseudo header: :method")) 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() { It("parses trailers", func() {
headers := []qpack.HeaderField{ headers := []qpack.HeaderField{
{Name: "content-length", Value: "42"}, {Name: "content-length", Value: "42"},

View File

@@ -245,11 +245,10 @@ func (s *requestStream) ReadResponse() (*http.Response, error) {
respBody := newResponseBody(s.stream, contentLength, s.reqDone) 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. // 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 isInformational := res.StatusCode >= 100 && res.StatusCode < 200
isNoContent := res.StatusCode == http.StatusNoContent isNoContent := res.StatusCode == http.StatusNoContent
isSuccessfulConnect := s.isConnect && res.StatusCode >= 200 && res.StatusCode < 300 isSuccessfulConnect := s.isConnect && res.StatusCode >= 200 && res.StatusCode < 300
if !hasTransferEncoding && !isInformational && !isNoContent && !isSuccessfulConnect { if !isInformational && !isNoContent && !isSuccessfulConnect {
res.ContentLength = -1 res.ContentLength = -1
if clens, ok := res.Header["Content-Length"]; ok && len(clens) == 1 { if clens, ok := res.Header["Content-Length"]; ok && len(clens) == 1 {
if clen64, err := strconv.ParseInt(clens[0], 10, 64); err == nil { if clen64, err := strconv.ParseInt(clens[0], 10, 64); err == nil {

View File

@@ -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. // 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"] _, haveType := w.header["Content-Type"]
// If the Transfer-Encoding or Content-Encoding was set and is non-blank, // If the Content-Encoding was set and is non-blank, we shouldn't sniff the body.
// we shouldn't sniff the body.
hasTE := w.header.Get("Transfer-Encoding") != ""
hasCE := w.header.Get("Content-Encoding") != "" 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)) w.header.Set("Content-Type", http.DetectContentType(p))
} }
} }