Skip to content

Commit 6279e65

Browse files
authored
Handle repeated header names and values (#133)
If an HTTPHeaderMatch contains entries with equivalent header names, we will only configure the first entry. We will ignore all other entries with that name. Header names are case-insensitive, so "Foo" is equivalent to "foo". If an HTTP request has repeated headers, nginx will merge the values into a comma-delimited string. For example, -H "foo:bar" -H "foo:baz" becomes {"foo":"bar,baz"} in the headersIn object. In this case, a request satisfies the header match {"foo":"bar"} if one of the values specified in the request header "foo" is "bar".
1 parent 7f18e36 commit 6279e65

File tree

4 files changed

+44
-4
lines changed

4 files changed

+44
-4
lines changed

internal/nginx/config/generator.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"errors"
66
"fmt"
7+
"strings"
78

89
"k8s.io/apimachinery/pkg/types"
910
"sigs.k8s.io/gateway-api/apis/v1alpha2"
@@ -193,11 +194,18 @@ func createHTTPMatch(match v1alpha2.HTTPRouteMatch, redirectPath string) httpMat
193194

194195
if match.Headers != nil {
195196
headers := make([]string, 0, len(match.Headers))
197+
headerNames := make(map[string]struct{})
196198

197199
// FIXME(kate-osborn): For now we only support type "Exact".
198200
for _, h := range match.Headers {
199201
if *h.Type == v1alpha2.HeaderMatchExact {
200-
headers = append(headers, createHeaderKeyValString(h))
202+
// duplicate header names are not permitted by the spec
203+
// only configure the first entry for every header name (case-insensitive)
204+
lowerName := strings.ToLower(string(h.Name))
205+
if _, ok := headerNames[lowerName]; !ok {
206+
headers = append(headers, createHeaderKeyValString(h))
207+
headerNames[lowerName] = struct{}{}
208+
}
201209
}
202210
}
203211
hm.Headers = headers

internal/nginx/config/generator_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,16 @@ func TestCreateHTTPMatch(t *testing.T) {
640640
Value: "val-3",
641641
},
642642
}
643+
644+
testDuplicateHeaders := make([]v1alpha2.HTTPHeaderMatch, 0, 5)
645+
duplicateHeaderMatch := v1alpha2.HTTPHeaderMatch{
646+
Type: helpers.GetHeaderMatchTypePointer(v1alpha2.HeaderMatchExact),
647+
Name: "HEADER-2", // header names are case-insensitive
648+
Value: "val-2",
649+
}
650+
testDuplicateHeaders = append(testDuplicateHeaders, testHeaderMatches...)
651+
testDuplicateHeaders = append(testDuplicateHeaders, duplicateHeaderMatch)
652+
643653
testQueryParamMatches := []v1alpha2.HTTPQueryParamMatch{
644654
{
645655
Type: helpers.GetQueryParamMatchTypePointer(v1alpha2.QueryParamMatchExact),
@@ -763,6 +773,16 @@ func TestCreateHTTPMatch(t *testing.T) {
763773
},
764774
msg: "method, headers, and query params match",
765775
},
776+
{
777+
match: v1alpha2.HTTPRouteMatch{
778+
Headers: testDuplicateHeaders,
779+
},
780+
expected: httpMatch{
781+
Headers: expectedHeaders,
782+
RedirectPath: testPath,
783+
},
784+
msg: "duplicate header names",
785+
},
766786
}
767787
for _, tc := range tests {
768788
result := createHTTPMatch(tc.match, testPath)

internal/nginx/modules/src/httpmatches.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,6 @@ function testMatch(r, match) {
129129
return true;
130130
}
131131

132-
// FIXME(osborn): Need to add special handling for repeated headers.
133-
// Should follow guidance from https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2.
134132
function headersMatch(requestHeaders, headers) {
135133
for (let i = 0; i < headers.length; i++) {
136134
const h = headers[i];
@@ -144,7 +142,13 @@ function headersMatch(requestHeaders, headers) {
144142
// This means that requestHeaders['FOO'] is equivalent to requestHeaders['foo'].
145143
let val = requestHeaders[kv[0]];
146144

147-
if (!val || val !== kv[1]) {
145+
if (!val) {
146+
return false;
147+
}
148+
149+
// split on comma because nginx uses commas to delimit multiple header values
150+
const values = val.split(',');
151+
if (!values.includes(kv[1])) {
148152
return false;
149153
}
150154
}

internal/nginx/modules/test/httpmatches.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,14 @@ describe('headersMatch', () => {
253253
},
254254
expected: true,
255255
},
256+
{
257+
name: 'returns true if request has multiple values for a header name and one value matches ',
258+
headers: ['multiValueHeader:val3'],
259+
requestHeaders: {
260+
multiValueHeader: 'val1,val2,val3,val4,val5',
261+
},
262+
expected: true,
263+
},
256264
];
257265

258266
tests.forEach((test) => {

0 commit comments

Comments
 (0)