http2: encode :protocol pseudo-header before regular headers

HTTP/2 requires that pseudo-headers (which start with : and are
used to pass information other than the regular request headers)
be encoded before all regular headers.

The x/net/http2 Transport's extended CONNECT support is enabled by
the user setting a ":protocol" header in the Request. This header
matches the pseudo-header that will be sent on the wire.

Ensure that the :protocol pseudo-header is sent before any regular
headers.

For golang/go#70728

Change-Id: I70de7ad524ab9457d6dfb61cb3fabe3d53c6b39b
Reviewed-on: https://go-review.googlesource.com/c/net/+/641476
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Antonio Ojea <aojea@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
Damien Neil
2025-01-08 11:11:03 -08:00
committed by Gopher Robot
parent 5566b43feb
commit 445eead606
2 changed files with 23 additions and 6 deletions

View File

@@ -2088,10 +2088,6 @@ func validateHeaders(hdrs http.Header) string {
var errNilRequestURL = errors.New("http2: Request.URI is nil")
func isNormalConnect(req *http.Request) bool {
return req.Method == "CONNECT" && req.Header.Get(":protocol") == ""
}
// requires cc.wmu be held.
func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) ([]byte, error) {
cc.hbuf.Reset()
@@ -2111,8 +2107,17 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
return nil, errors.New("http2: invalid Host header")
}
// isNormalConnect is true if this is a non-extended CONNECT request.
isNormalConnect := false
protocol := req.Header.Get(":protocol")
if req.Method == "CONNECT" && protocol == "" {
isNormalConnect = true
} else if protocol != "" && req.Method != "CONNECT" {
return nil, errors.New("http2: invalid :protocol header in non-CONNECT request")
}
var path string
if !isNormalConnect(req) {
if !isNormalConnect {
path = req.URL.RequestURI()
if !validPseudoPath(path) {
orig := path
@@ -2149,10 +2154,13 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
m = http.MethodGet
}
f(":method", m)
if !isNormalConnect(req) {
if !isNormalConnect {
f(":path", path)
f(":scheme", req.URL.Scheme)
}
if protocol != "" {
f(":protocol", protocol)
}
if trailers != "" {
f("trailer", trailers)
}
@@ -2209,6 +2217,9 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
}
}
continue
} else if k == ":protocol" {
// :protocol pseudo-header was already sent above.
continue
}
for _, v := range vv {

View File

@@ -5872,6 +5872,9 @@ func TestExtendedConnectClientWithServerSupport(t *testing.T) {
pwDone := make(chan struct{})
req, _ := http.NewRequest("CONNECT", ts.URL, pr)
req.Header.Set(":protocol", "extended-connect")
req.Header.Set("X-A", "A")
req.Header.Set("X-B", "B")
req.Header.Set("X-C", "C")
go func() {
pw.Write([]byte("hello, extended connect"))
pw.Close()
@@ -5905,6 +5908,9 @@ func TestExtendedConnectClientWithoutServerSupport(t *testing.T) {
pwDone := make(chan struct{})
req, _ := http.NewRequest("CONNECT", ts.URL, pr)
req.Header.Set(":protocol", "extended-connect")
req.Header.Set("X-A", "A")
req.Header.Set("X-B", "B")
req.Header.Set("X-C", "C")
go func() {
pw.Write([]byte("hello, extended connect"))
pw.Close()