From 514df55288913cb3caa2a17577cc0b46c9021343 Mon Sep 17 00:00:00 2001
From: Marten Seemann <martenseemann@gmail.com>
Date: Tue, 18 Jul 2023 09:13:16 -0700
Subject: [PATCH] http3: enforce ordering requirement between pseudo and
 regular headers (#3968)

* http3: enforce ordering requirement between pseudo and regular headers

* simplify logic
---
 http3/request.go      | 16 +++++++++++++---
 http3/request_test.go | 11 +++++++++++
 2 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/http3/request.go b/http3/request.go
index 6105144f..f82feb8f 100644
--- a/http3/request.go
+++ b/http3/request.go
@@ -17,6 +17,7 @@ func requestFromHeaders(headers []qpack.HeaderField) (*http.Request, error) {
 	var path, authority, method, protocol, scheme, contentLengthStr string
 
 	httpHeaders := http.Header{}
+	var readFirstRegularHeader bool
 	for _, h := range headers {
 		// field names need to be lowercase, see section 4.2 of RFC 9114
 		if strings.ToLower(h.Name) != h.Name {
@@ -25,6 +26,18 @@ func requestFromHeaders(headers []qpack.HeaderField) (*http.Request, error) {
 		if !httpguts.ValidHeaderFieldValue(h.Value) {
 			return nil, fmt.Errorf("invalid header field value for %s: %q", h.Name, h.Value)
 		}
+		if h.IsPseudo() {
+			if readFirstRegularHeader {
+				// all pseudo headers must appear before regular header fields, see section 4.3 of RFC 9114
+				return nil, fmt.Errorf("received pseudo header %s after a regular header field", h.Name)
+			}
+		} else {
+			if !httpguts.ValidHeaderFieldName(h.Name) {
+				return nil, fmt.Errorf("invalid header field name: %q", h.Name)
+			}
+			readFirstRegularHeader = true
+		}
+
 		switch h.Name {
 		case ":path":
 			path = h.Value
@@ -40,9 +53,6 @@ func requestFromHeaders(headers []qpack.HeaderField) (*http.Request, error) {
 			contentLengthStr = h.Value
 		default:
 			if !h.IsPseudo() {
-				if !httpguts.ValidHeaderFieldName(h.Name) {
-					return nil, fmt.Errorf("invalid header field name: %q", h.Name)
-				}
 				httpHeaders.Add(h.Name, h.Value)
 			}
 		}
diff --git a/http3/request_test.go b/http3/request_test.go
index 0edfa285..87275c9c 100644
--- a/http3/request_test.go
+++ b/http3/request_test.go
@@ -66,6 +66,17 @@ var _ = Describe("Request", func() {
 		Expect(err).To(MatchError(`invalid header field value for content: "\n"`))
 	})
 
+	It("rejects pseudo header fields after regular header fields", func() {
+		headers := []qpack.HeaderField{
+			{Name: ":path", Value: "/foo"},
+			{Name: "content-length", Value: "42"},
+			{Name: ":authority", Value: "quic.clemente.io"},
+			{Name: ":method", Value: "GET"},
+		}
+		_, err := requestFromHeaders(headers)
+		Expect(err).To(MatchError("received pseudo header :authority after a regular header field"))
+	})
+
 	It("rejects negative Content-Length values", func() {
 		headers := []qpack.HeaderField{
 			{Name: ":path", Value: "/foo"},