diff --git a/http3/headers.go b/http3/headers.go index edad2c80..e9f2df70 100644 --- a/http3/headers.go +++ b/http3/headers.go @@ -30,7 +30,7 @@ type header struct { func parseHeaders(headers []qpack.HeaderField, isRequest bool) (header, error) { hdr := header{Headers: make(http.Header, len(headers))} - var readFirstRegularHeader bool + var readFirstRegularHeader, readContentLength bool var contentLengthStr string for _, h := range headers { // field names need to be lowercase, see section 4.2 of RFC 9114 @@ -74,7 +74,14 @@ func parseHeaders(headers []qpack.HeaderField, isRequest bool) (header, error) { readFirstRegularHeader = true switch h.Name { case "content-length": - contentLengthStr = h.Value + // Ignore duplicate Content-Length headers. + // Fail if the duplicates differ. + if !readContentLength { + readContentLength = true + contentLengthStr = h.Value + } else if contentLengthStr != h.Value { + return header{}, fmt.Errorf("contradicting content lengths (%s and %s)", contentLengthStr, h.Value) + } default: hdr.Headers.Add(h.Name, h.Value) } diff --git a/http3/headers_test.go b/http3/headers_test.go index ac09e932..7fcf675d 100644 --- a/http3/headers_test.go +++ b/http3/headers_test.go @@ -89,6 +89,32 @@ var _ = Describe("Request", func() { Expect(err.Error()).To(ContainSubstring("invalid content length")) }) + It("rejects multiple Content-Length headers, if they differ", func() { + headers := []qpack.HeaderField{ + {Name: ":path", Value: "/foo"}, + {Name: ":authority", Value: "quic.clemente.io"}, + {Name: ":method", Value: "GET"}, + {Name: "content-length", Value: "42"}, + {Name: "content-length", Value: "1337"}, + } + _, err := requestFromHeaders(headers) + Expect(err).To(MatchError("contradicting content lengths (42 and 1337)")) + }) + + It("deduplicates multiple Content-Length headers, if they're the same", func() { + headers := []qpack.HeaderField{ + {Name: ":path", Value: "/foo"}, + {Name: ":authority", Value: "quic.clemente.io"}, + {Name: ":method", Value: "GET"}, + {Name: "content-length", Value: "42"}, + {Name: "content-length", Value: "42"}, + } + req, err := requestFromHeaders(headers) + Expect(err).ToNot(HaveOccurred()) + Expect(req.ContentLength).To(Equal(int64(42))) + Expect(req.Header.Get("Content-Length")).To(Equal("42")) + }) + It("rejects pseudo header fields defined for responses", func() { headers := []qpack.HeaderField{ {Name: ":path", Value: "/foo"},