TUN-5029: Do not strip cf- prefixed headers
This commit is contained in:
parent
89d408e3bd
commit
d04f48d872
|
@ -54,7 +54,7 @@ const ()
|
||||||
func H2RequestHeadersToH1Request(h2 []h2mux.Header, h1 *http.Request) error {
|
func H2RequestHeadersToH1Request(h2 []h2mux.Header, h1 *http.Request) error {
|
||||||
for _, header := range h2 {
|
for _, header := range h2 {
|
||||||
name := strings.ToLower(header.Name)
|
name := strings.ToLower(header.Name)
|
||||||
if !IsControlHeader(name) {
|
if !IsControlRequestHeader(name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,13 +121,20 @@ func H2RequestHeadersToH1Request(h2 []h2mux.Header, h1 *http.Request) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsControlHeader(headerName string) bool {
|
func IsControlRequestHeader(headerName string) bool {
|
||||||
return headerName == "content-length" ||
|
return headerName == "content-length" ||
|
||||||
headerName == "connection" || headerName == "upgrade" || // Websocket headers
|
headerName == "connection" || headerName == "upgrade" || // Websocket request headers
|
||||||
strings.HasPrefix(headerName, ":") ||
|
strings.HasPrefix(headerName, ":") ||
|
||||||
strings.HasPrefix(headerName, "cf-")
|
strings.HasPrefix(headerName, "cf-")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsControlResponseHeader(headerName string) bool {
|
||||||
|
return headerName == "content-length" ||
|
||||||
|
strings.HasPrefix(headerName, ":") ||
|
||||||
|
strings.HasPrefix(headerName, "cf-int-") ||
|
||||||
|
strings.HasPrefix(headerName, "cf-cloudflared-")
|
||||||
|
}
|
||||||
|
|
||||||
// isWebsocketClientHeader returns true if the header name is required by the client to upgrade properly
|
// isWebsocketClientHeader returns true if the header name is required by the client to upgrade properly
|
||||||
func IsWebsocketClientHeader(headerName string) bool {
|
func IsWebsocketClientHeader(headerName string) bool {
|
||||||
return headerName == "sec-websocket-accept" ||
|
return headerName == "sec-websocket-accept" ||
|
||||||
|
@ -148,7 +155,7 @@ func H1ResponseToH2ResponseHeaders(status int, h1 http.Header) (h2 []h2mux.Heade
|
||||||
|
|
||||||
// Since these are http2 headers, they're required to be lowercase
|
// Since these are http2 headers, they're required to be lowercase
|
||||||
h2 = append(h2, h2mux.Header{Name: "content-length", Value: values[0]})
|
h2 = append(h2, h2mux.Header{Name: "content-length", Value: values[0]})
|
||||||
} else if !IsControlHeader(h2name) || IsWebsocketClientHeader(h2name) {
|
} else if !IsControlResponseHeader(h2name) || IsWebsocketClientHeader(h2name) {
|
||||||
// User headers, on the other hand, must all be serialized so that
|
// User headers, on the other hand, must all be serialized so that
|
||||||
// HTTP/2 header validation won't be applied to HTTP/1 header values
|
// HTTP/2 header validation won't be applied to HTTP/1 header values
|
||||||
userHeaders[header] = values
|
userHeaders[header] = values
|
||||||
|
|
|
@ -511,7 +511,7 @@ func TestDeserializeMalformed(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseHeaders(t *testing.T) {
|
func TestParseRequestHeaders(t *testing.T) {
|
||||||
mockUserHeadersToSerialize := http.Header{
|
mockUserHeadersToSerialize := http.Header{
|
||||||
"Mock-Header-One": {"1", "1.5"},
|
"Mock-Header-One": {"1", "1.5"},
|
||||||
"Mock-Header-Two": {"2"},
|
"Mock-Header-Two": {"2"},
|
||||||
|
@ -541,8 +541,8 @@ func TestParseHeaders(t *testing.T) {
|
||||||
assert.ElementsMatch(t, expectedHeaders, stdlibHeaderToH2muxHeader(h1.Header))
|
assert.ElementsMatch(t, expectedHeaders, stdlibHeaderToH2muxHeader(h1.Header))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsControlHeader(t *testing.T) {
|
func TestIsControlRequestHeader(t *testing.T) {
|
||||||
controlHeaders := []string{
|
controlRequestHeaders := []string{
|
||||||
// Anything that begins with cf-
|
// Anything that begins with cf-
|
||||||
"cf-sample-header",
|
"cf-sample-header",
|
||||||
|
|
||||||
|
@ -552,30 +552,69 @@ func TestIsControlHeader(t *testing.T) {
|
||||||
// content-length is a special case, it has to be there
|
// content-length is a special case, it has to be there
|
||||||
// for some requests to work (per the HTTP2 spec)
|
// for some requests to work (per the HTTP2 spec)
|
||||||
"content-length",
|
"content-length",
|
||||||
|
|
||||||
|
// Websocket request headers
|
||||||
|
"connection",
|
||||||
|
"upgrade",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, header := range controlHeaders {
|
for _, header := range controlRequestHeaders {
|
||||||
assert.True(t, IsControlHeader(header))
|
assert.True(t, IsControlRequestHeader(header))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsNotControlHeader(t *testing.T) {
|
func TestIsControlResponseHeader(t *testing.T) {
|
||||||
notControlHeaders := []string{
|
controlResponseHeaders := []string{
|
||||||
|
// Anything that begins with cf-int- or cf-cloudflared-
|
||||||
|
"cf-int-sample-header",
|
||||||
|
"cf-cloudflared-sample-header",
|
||||||
|
|
||||||
|
// Any http2 pseudoheader
|
||||||
|
":sample-pseudo-header",
|
||||||
|
|
||||||
|
// content-length is a special case, it has to be there
|
||||||
|
// for some requests to work (per the HTTP2 spec)
|
||||||
|
"content-length",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, header := range controlResponseHeaders {
|
||||||
|
assert.True(t, IsControlResponseHeader(header))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsNotControlRequestHeader(t *testing.T) {
|
||||||
|
notControlRequestHeaders := []string{
|
||||||
"mock-header",
|
"mock-header",
|
||||||
"another-sample-header",
|
"another-sample-header",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, header := range notControlHeaders {
|
for _, header := range notControlRequestHeaders {
|
||||||
assert.False(t, IsControlHeader(header))
|
assert.False(t, IsControlRequestHeader(header))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsNotControlResponseHeader(t *testing.T) {
|
||||||
|
notControlResponseHeaders := []string{
|
||||||
|
"mock-header",
|
||||||
|
"another-sample-header",
|
||||||
|
"upgrade",
|
||||||
|
"connection",
|
||||||
|
"cf-whatever", // On the response path, we only want to filter cf-int- and cf-cloudflared-
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, header := range notControlResponseHeaders {
|
||||||
|
assert.False(t, IsControlResponseHeader(header))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestH1ResponseToH2ResponseHeaders(t *testing.T) {
|
func TestH1ResponseToH2ResponseHeaders(t *testing.T) {
|
||||||
mockHeaders := http.Header{
|
mockHeaders := http.Header{
|
||||||
"User-header-one": {""},
|
"User-header-one": {""},
|
||||||
"User-header-two": {"1", "2"},
|
"User-header-two": {"1", "2"},
|
||||||
"cf-header": {"cf-value"},
|
"cf-header": {"cf-value"},
|
||||||
"Content-Length": {"123"},
|
"cf-int-header": {"cf-int-value"},
|
||||||
|
"cf-cloudflared-header": {"cf-cloudflared-value"},
|
||||||
|
"Content-Length": {"123"},
|
||||||
}
|
}
|
||||||
mockResponse := http.Response{
|
mockResponse := http.Response{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
|
@ -608,6 +647,7 @@ func TestH1ResponseToH2ResponseHeaders(t *testing.T) {
|
||||||
{Name: "User-header-one", Value: ""},
|
{Name: "User-header-one", Value: ""},
|
||||||
{Name: "User-header-two", Value: "1"},
|
{Name: "User-header-two", Value: "1"},
|
||||||
{Name: "User-header-two", Value: "2"},
|
{Name: "User-header-two", Value: "2"},
|
||||||
|
{Name: "cf-header", Value: "cf-value"},
|
||||||
}
|
}
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.ElementsMatch(t, expectedUserHeaders, actualUserHeaders)
|
assert.ElementsMatch(t, expectedUserHeaders, actualUserHeaders)
|
||||||
|
|
|
@ -189,7 +189,7 @@ func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) erro
|
||||||
// so it should be sent as an HTTP/2 response header.
|
// so it should be sent as an HTTP/2 response header.
|
||||||
dest[name] = values
|
dest[name] = values
|
||||||
// Since these are http2 headers, they're required to be lowercase
|
// Since these are http2 headers, they're required to be lowercase
|
||||||
} else if !IsControlHeader(h2name) || IsWebsocketClientHeader(h2name) {
|
} else if !IsControlResponseHeader(h2name) || IsWebsocketClientHeader(h2name) {
|
||||||
// User headers, on the other hand, must all be serialized so that
|
// User headers, on the other hand, must all be serialized so that
|
||||||
// HTTP/2 header validation won't be applied to HTTP/1 header values
|
// HTTP/2 header validation won't be applied to HTTP/1 header values
|
||||||
userHeaders[name] = values
|
userHeaders[name] = values
|
||||||
|
|
Loading…
Reference in New Issue