Skip to content

Commit d04f48d

Browse files
committed
TUN-5029: Do not strip cf- prefixed headers
1 parent 89d408e commit d04f48d

File tree

3 files changed

+65
-18
lines changed

3 files changed

+65
-18
lines changed

connection/header.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const ()
5454
func H2RequestHeadersToH1Request(h2 []h2mux.Header, h1 *http.Request) error {
5555
for _, header := range h2 {
5656
name := strings.ToLower(header.Name)
57-
if !IsControlHeader(name) {
57+
if !IsControlRequestHeader(name) {
5858
continue
5959
}
6060

@@ -121,13 +121,20 @@ func H2RequestHeadersToH1Request(h2 []h2mux.Header, h1 *http.Request) error {
121121
return nil
122122
}
123123

124-
func IsControlHeader(headerName string) bool {
124+
func IsControlRequestHeader(headerName string) bool {
125125
return headerName == "content-length" ||
126-
headerName == "connection" || headerName == "upgrade" || // Websocket headers
126+
headerName == "connection" || headerName == "upgrade" || // Websocket request headers
127127
strings.HasPrefix(headerName, ":") ||
128128
strings.HasPrefix(headerName, "cf-")
129129
}
130130

131+
func IsControlResponseHeader(headerName string) bool {
132+
return headerName == "content-length" ||
133+
strings.HasPrefix(headerName, ":") ||
134+
strings.HasPrefix(headerName, "cf-int-") ||
135+
strings.HasPrefix(headerName, "cf-cloudflared-")
136+
}
137+
131138
// isWebsocketClientHeader returns true if the header name is required by the client to upgrade properly
132139
func IsWebsocketClientHeader(headerName string) bool {
133140
return headerName == "sec-websocket-accept" ||
@@ -148,7 +155,7 @@ func H1ResponseToH2ResponseHeaders(status int, h1 http.Header) (h2 []h2mux.Heade
148155

149156
// Since these are http2 headers, they're required to be lowercase
150157
h2 = append(h2, h2mux.Header{Name: "content-length", Value: values[0]})
151-
} else if !IsControlHeader(h2name) || IsWebsocketClientHeader(h2name) {
158+
} else if !IsControlResponseHeader(h2name) || IsWebsocketClientHeader(h2name) {
152159
// User headers, on the other hand, must all be serialized so that
153160
// HTTP/2 header validation won't be applied to HTTP/1 header values
154161
userHeaders[header] = values

connection/header_test.go

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ func TestDeserializeMalformed(t *testing.T) {
511511
}
512512
}
513513

514-
func TestParseHeaders(t *testing.T) {
514+
func TestParseRequestHeaders(t *testing.T) {
515515
mockUserHeadersToSerialize := http.Header{
516516
"Mock-Header-One": {"1", "1.5"},
517517
"Mock-Header-Two": {"2"},
@@ -541,8 +541,8 @@ func TestParseHeaders(t *testing.T) {
541541
assert.ElementsMatch(t, expectedHeaders, stdlibHeaderToH2muxHeader(h1.Header))
542542
}
543543

544-
func TestIsControlHeader(t *testing.T) {
545-
controlHeaders := []string{
544+
func TestIsControlRequestHeader(t *testing.T) {
545+
controlRequestHeaders := []string{
546546
// Anything that begins with cf-
547547
"cf-sample-header",
548548

@@ -552,30 +552,69 @@ func TestIsControlHeader(t *testing.T) {
552552
// content-length is a special case, it has to be there
553553
// for some requests to work (per the HTTP2 spec)
554554
"content-length",
555+
556+
// Websocket request headers
557+
"connection",
558+
"upgrade",
559+
}
560+
561+
for _, header := range controlRequestHeaders {
562+
assert.True(t, IsControlRequestHeader(header))
563+
}
564+
}
565+
566+
func TestIsControlResponseHeader(t *testing.T) {
567+
controlResponseHeaders := []string{
568+
// Anything that begins with cf-int- or cf-cloudflared-
569+
"cf-int-sample-header",
570+
"cf-cloudflared-sample-header",
571+
572+
// Any http2 pseudoheader
573+
":sample-pseudo-header",
574+
575+
// content-length is a special case, it has to be there
576+
// for some requests to work (per the HTTP2 spec)
577+
"content-length",
578+
}
579+
580+
for _, header := range controlResponseHeaders {
581+
assert.True(t, IsControlResponseHeader(header))
582+
}
583+
}
584+
585+
func TestIsNotControlRequestHeader(t *testing.T) {
586+
notControlRequestHeaders := []string{
587+
"mock-header",
588+
"another-sample-header",
555589
}
556590

557-
for _, header := range controlHeaders {
558-
assert.True(t, IsControlHeader(header))
591+
for _, header := range notControlRequestHeaders {
592+
assert.False(t, IsControlRequestHeader(header))
559593
}
560594
}
561595

562-
func TestIsNotControlHeader(t *testing.T) {
563-
notControlHeaders := []string{
596+
func TestIsNotControlResponseHeader(t *testing.T) {
597+
notControlResponseHeaders := []string{
564598
"mock-header",
565599
"another-sample-header",
600+
"upgrade",
601+
"connection",
602+
"cf-whatever", // On the response path, we only want to filter cf-int- and cf-cloudflared-
566603
}
567604

568-
for _, header := range notControlHeaders {
569-
assert.False(t, IsControlHeader(header))
605+
for _, header := range notControlResponseHeaders {
606+
assert.False(t, IsControlResponseHeader(header))
570607
}
571608
}
572609

573610
func TestH1ResponseToH2ResponseHeaders(t *testing.T) {
574611
mockHeaders := http.Header{
575-
"User-header-one": {""},
576-
"User-header-two": {"1", "2"},
577-
"cf-header": {"cf-value"},
578-
"Content-Length": {"123"},
612+
"User-header-one": {""},
613+
"User-header-two": {"1", "2"},
614+
"cf-header": {"cf-value"},
615+
"cf-int-header": {"cf-int-value"},
616+
"cf-cloudflared-header": {"cf-cloudflared-value"},
617+
"Content-Length": {"123"},
579618
}
580619
mockResponse := http.Response{
581620
StatusCode: 200,
@@ -608,6 +647,7 @@ func TestH1ResponseToH2ResponseHeaders(t *testing.T) {
608647
{Name: "User-header-one", Value: ""},
609648
{Name: "User-header-two", Value: "1"},
610649
{Name: "User-header-two", Value: "2"},
650+
{Name: "cf-header", Value: "cf-value"},
611651
}
612652
assert.NoError(t, err)
613653
assert.ElementsMatch(t, expectedUserHeaders, actualUserHeaders)

connection/http2.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) erro
189189
// so it should be sent as an HTTP/2 response header.
190190
dest[name] = values
191191
// Since these are http2 headers, they're required to be lowercase
192-
} else if !IsControlHeader(h2name) || IsWebsocketClientHeader(h2name) {
192+
} else if !IsControlResponseHeader(h2name) || IsWebsocketClientHeader(h2name) {
193193
// User headers, on the other hand, must all be serialized so that
194194
// HTTP/2 header validation won't be applied to HTTP/1 header values
195195
userHeaders[name] = values

0 commit comments

Comments
 (0)