Skip to content

Commit

Permalink
Merge pull request #179 from hohmannr/fix/singleton-header-splitting
Browse files Browse the repository at this point in the history
fix(requestv2): fixes multiton/singleton header parsing (#178)
  • Loading branch information
sapessi authored Sep 22, 2023
2 parents 3b1b7ab + 7644439 commit 429835c
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 1 deletion.
44 changes: 43 additions & 1 deletion core/requestv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"fmt"
"log"
"net/http"
"net/textproto"
"net/url"
"os"
"strings"
Expand Down Expand Up @@ -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, " "))
}
Expand Down Expand Up @@ -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,
}
29 changes: 29 additions & 0 deletions core/requestv2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 429835c

Please sign in to comment.