Skip to content

Commit 7644439

Browse files
committed
fix(requestv2): fixes multiton/singleton header parsing (awslabs#178)
1 parent 06305ac commit 7644439

File tree

2 files changed

+72
-1
lines changed

2 files changed

+72
-1
lines changed

core/requestv2.go

+43-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"fmt"
1212
"log"
1313
"net/http"
14+
"net/textproto"
1415
"net/url"
1516
"os"
1617
"strings"
@@ -170,7 +171,13 @@ func (r *RequestAccessorV2) EventToRequest(req events.APIGatewayV2HTTPRequest) (
170171
httpRequest.Header.Add("Cookie", cookie)
171172
}
172173

173-
for headerKey, headerValue := range req.Headers {
174+
singletonHeaders, headers := splitSingletonHeaders(req.Headers)
175+
176+
for headerKey, headerValue := range singletonHeaders {
177+
httpRequest.Header.Add(headerKey, headerValue)
178+
}
179+
180+
for headerKey, headerValue := range headers {
174181
for _, val := range strings.Split(headerValue, ",") {
175182
httpRequest.Header.Add(headerKey, strings.Trim(val, " "))
176183
}
@@ -227,3 +234,38 @@ type requestContextV2 struct {
227234
gatewayProxyContext events.APIGatewayV2HTTPRequestContext
228235
stageVars map[string]string
229236
}
237+
238+
// splitSingletonHeaders splits the headers into single-value headers and other,
239+
// multi-value capable, headers.
240+
// Returns (single-value headers, multi-value-capable headers)
241+
func splitSingletonHeaders(headers map[string]string) (map[string]string, map[string]string) {
242+
singletons := make(map[string]string)
243+
multitons := make(map[string]string)
244+
for headerKey, headerValue := range headers {
245+
if ok := singletonHeaders[textproto.CanonicalMIMEHeaderKey(headerKey)]; ok {
246+
singletons[headerKey] = headerValue
247+
} else {
248+
multitons[headerKey] = headerValue
249+
}
250+
}
251+
252+
return singletons, multitons
253+
}
254+
255+
// singletonHeaders is a set of headers, that only accept a single
256+
// value which may be comma separated (according to RFC 7230)
257+
var singletonHeaders = map[string]bool{
258+
"Content-Type": true,
259+
"Content-Disposition": true,
260+
"Content-Length": true,
261+
"User-Agent": true,
262+
"Referer": true,
263+
"Host": true,
264+
"Authorization": true,
265+
"Proxy-Authorization": true,
266+
"If-Modified-Since": true,
267+
"If-Unmodified-Since": true,
268+
"From": true,
269+
"Location": true,
270+
"Max-Forwards": true,
271+
}

core/requestv2_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,35 @@ var _ = Describe("RequestAccessorV2 tests", func() {
136136
}
137137
})
138138

139+
singletonHeaderRequest := getProxyRequestV2("/hello", "GET")
140+
singletonHeaderRequest.Headers = map[string]string{
141+
// multi-value capable headers
142+
"hello": "1",
143+
"world": "2,3",
144+
// singleton headers, which may be comma separated
145+
"user-agent": "Mozilla/5.0 (Linux; Android 11; Pixel 5 Build/RQ3A.210805.001.A1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.159 Mobile Safari/537.36",
146+
"authorization": "some custom comma, separated authorization",
147+
}
148+
149+
It("Populates singleton header values correctly", func() {
150+
httpReq, err := accessor.EventToRequestWithContext(context.Background(), singletonHeaderRequest)
151+
Expect(err).To(BeNil())
152+
Expect("/hello").To(Equal(httpReq.URL.Path))
153+
Expect("GET").To(Equal(httpReq.Method))
154+
155+
headers := httpReq.Header
156+
Expect(4).To(Equal(len(headers)))
157+
158+
for k, value := range headers {
159+
k = strings.ToLower(k)
160+
if k == "hello" || k == "world" {
161+
Expect(strings.Join(value, ",")).To(Equal(singletonHeaderRequest.Headers[k]))
162+
} else {
163+
Expect(headers.Get(k)).To(Equal(singletonHeaderRequest.Headers[k]))
164+
}
165+
}
166+
})
167+
139168
svhRequest := getProxyRequestV2("/hello", "GET")
140169
svhRequest.Headers = map[string]string{
141170
"hello": "1",

0 commit comments

Comments
 (0)