From 7644439614c58c6c864e3abb17b72916df7a22b9 Mon Sep 17 00:00:00 2001 From: hohmannr Date: Mon, 22 May 2023 12:07:26 +0200 Subject: [PATCH] fix(requestv2): fixes multiton/singleton header parsing (#178) --- core/requestv2.go | 44 +++++++++++++++++++++++++++++++++++++++++- core/requestv2_test.go | 29 ++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/core/requestv2.go b/core/requestv2.go index d665384..b6e3b70 100644 --- a/core/requestv2.go +++ b/core/requestv2.go @@ -11,6 +11,7 @@ import ( "fmt" "log" "net/http" + "net/textproto" "net/url" "os" "strings" @@ -170,7 +171,13 @@ func (r *RequestAccessorV2) EventToRequest(req events.APIGatewayV2HTTPRequest) ( httpRequest.Header.Add("Cookie", cookie) } - for headerKey, headerValue := range req.Headers { + singletonHeaders, headers := splitSingletonHeaders(req.Headers) + + for headerKey, headerValue := range singletonHeaders { + httpRequest.Header.Add(headerKey, headerValue) + } + + for headerKey, headerValue := range headers { for _, val := range strings.Split(headerValue, ",") { httpRequest.Header.Add(headerKey, strings.Trim(val, " ")) } @@ -227,3 +234,38 @@ type requestContextV2 struct { gatewayProxyContext events.APIGatewayV2HTTPRequestContext stageVars map[string]string } + +// splitSingletonHeaders splits the headers into single-value headers and other, +// multi-value capable, headers. +// Returns (single-value headers, multi-value-capable headers) +func splitSingletonHeaders(headers map[string]string) (map[string]string, map[string]string) { + singletons := make(map[string]string) + multitons := make(map[string]string) + for headerKey, headerValue := range headers { + if ok := singletonHeaders[textproto.CanonicalMIMEHeaderKey(headerKey)]; ok { + singletons[headerKey] = headerValue + } else { + multitons[headerKey] = headerValue + } + } + + return singletons, multitons +} + +// singletonHeaders is a set of headers, that only accept a single +// value which may be comma separated (according to RFC 7230) +var singletonHeaders = map[string]bool{ + "Content-Type": true, + "Content-Disposition": true, + "Content-Length": true, + "User-Agent": true, + "Referer": true, + "Host": true, + "Authorization": true, + "Proxy-Authorization": true, + "If-Modified-Since": true, + "If-Unmodified-Since": true, + "From": true, + "Location": true, + "Max-Forwards": true, +} diff --git a/core/requestv2_test.go b/core/requestv2_test.go index e42370d..180e22b 100644 --- a/core/requestv2_test.go +++ b/core/requestv2_test.go @@ -136,6 +136,35 @@ var _ = Describe("RequestAccessorV2 tests", func() { } }) + singletonHeaderRequest := getProxyRequestV2("/hello", "GET") + singletonHeaderRequest.Headers = map[string]string{ + // multi-value capable headers + "hello": "1", + "world": "2,3", + // singleton headers, which may be comma separated + "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", + "authorization": "some custom comma, separated authorization", + } + + It("Populates singleton header values correctly", func() { + httpReq, err := accessor.EventToRequestWithContext(context.Background(), singletonHeaderRequest) + Expect(err).To(BeNil()) + Expect("/hello").To(Equal(httpReq.URL.Path)) + Expect("GET").To(Equal(httpReq.Method)) + + headers := httpReq.Header + Expect(4).To(Equal(len(headers))) + + for k, value := range headers { + k = strings.ToLower(k) + if k == "hello" || k == "world" { + Expect(strings.Join(value, ",")).To(Equal(singletonHeaderRequest.Headers[k])) + } else { + Expect(headers.Get(k)).To(Equal(singletonHeaderRequest.Headers[k])) + } + } + }) + svhRequest := getProxyRequestV2("/hello", "GET") svhRequest.Headers = map[string]string{ "hello": "1",