Skip to content

Commit

Permalink
fix: lowercase API gateway V2 event headers (#1288)
Browse files Browse the repository at this point in the history
Co-authored-by: dnalborczyk <dnalborczyk@gmail.com>
  • Loading branch information
domdomegg and dnalborczyk committed May 16, 2022
1 parent e1f60da commit 9ff4cf3
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 10 deletions.
15 changes: 5 additions & 10 deletions src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js
Expand Up @@ -5,6 +5,7 @@ import {
formatToClfTime,
nullIfEmpty,
parseHeaders,
lowerCaseKeys,
} from '../../../utils/index.js'

const { isArray } = Array
Expand Down Expand Up @@ -71,7 +72,7 @@ export default class LambdaProxyIntegrationEventV2 {
const { rawHeaders } = this.#request.raw.req

// NOTE FIXME request.raw.req.rawHeaders can only be null for testing (hapi shot inject())
const headers = parseHeaders(rawHeaders || []) || {}
const headers = lowerCaseKeys(parseHeaders(rawHeaders || [])) || {}

if (headers['sls-offline-authorizer-override']) {
try {
Expand All @@ -96,23 +97,17 @@ export default class LambdaProxyIntegrationEventV2 {
}

if (
!headers['Content-Length'] &&
!headers['content-length'] &&
!headers['Content-length'] &&
(typeof body === 'string' ||
body instanceof Buffer ||
body instanceof ArrayBuffer)
) {
headers['Content-Length'] = String(Buffer.byteLength(body))
headers['content-length'] = String(Buffer.byteLength(body))
}

// Set a default Content-Type if not provided.
if (
!headers['Content-Type'] &&
!headers['content-type'] &&
!headers['Content-type']
) {
headers['Content-Type'] = 'application/json'
if (!headers['content-type']) {
headers['content-type'] = 'application/json'
}
} else if (typeof body === 'undefined' || body === '') {
body = null
Expand Down
28 changes: 28 additions & 0 deletions src/utils/__tests__/lowerCaseKeys.test.js
@@ -0,0 +1,28 @@
import lowerCaseKeys from '../lowerCaseKeys.js'

describe('lowerCaseKeys', () => {
test(`should handle empty object`, () => {
const result = lowerCaseKeys({})
expect(result).toEqual({})
})

test(`should handle object with one key`, () => {
const result = lowerCaseKeys({ 'Some-Key': 'value' })
expect(result).toEqual({ 'some-key': 'value' })
})

test(`should handle object with multiple keys`, () => {
const result = lowerCaseKeys({
'Some-Key': 'value',
'Another-Key': 'anotherValue',
'lOts-OF-CAPitaLs': 'ButThisIsNotTouched',
'already-lowercase': 'cool',
})
expect(result).toEqual({
'some-key': 'value',
'another-key': 'anotherValue',
'lots-of-capitals': 'ButThisIsNotTouched',
'already-lowercase': 'cool',
})
})
})
1 change: 1 addition & 0 deletions src/utils/index.js
Expand Up @@ -4,6 +4,7 @@ export { default as detectExecutable } from './detectExecutable.js'
export { default as formatToClfTime } from './formatToClfTime.js'
export { default as getHttpApiCorsConfig } from './getHttpApiCorsConfig.js'
export { default as jsonPath } from './jsonPath.js'
export { default as lowerCaseKeys } from './lowerCaseKeys.js'
export { default as parseHeaders } from './parseHeaders.js'
export { default as parseMultiValueHeaders } from './parseMultiValueHeaders.js'
export { default as parseMultiValueQueryStringParameters } from './parseMultiValueQueryStringParameters.js'
Expand Down
6 changes: 6 additions & 0 deletions src/utils/lowerCaseKeys.js
@@ -0,0 +1,6 @@
const { entries, fromEntries } = Object

// (obj: { [string]: string }): { [Lowercase<string>]: string }
export default function parseHeaders(obj) {
return fromEntries(entries(obj).map(([k, v]) => [k.toLowerCase(), v]))
}
10 changes: 10 additions & 0 deletions tests/integration/httpApi-headers/handler.js
@@ -0,0 +1,10 @@
'use strict'

exports.echoHeaders = async function get(event) {
return {
body: JSON.stringify({
headersReceived: event.headers,
}),
statusCode: 200,
}
}
38 changes: 38 additions & 0 deletions tests/integration/httpApi-headers/httpApi-headers.test.js
@@ -0,0 +1,38 @@
import { resolve } from 'path'
import fetch from 'node-fetch'
import { joinUrl, setup, teardown } from '../_testHelpers/index.js'

jest.setTimeout(30000)

describe('HttpApi Headers Tests', () => {
// init
beforeAll(() =>
setup({
servicePath: resolve(__dirname),
}),
)

// cleanup
afterAll(() => teardown())

test.each(['GET', 'POST'])('%s headers', async (method) => {
const url = joinUrl(TEST_BASE_URL, '/echo-headers')
const options = {
method,
headers: {
Origin: 'http://www.example.com',
'X-Webhook-Signature': 'ABCDEF',
},
}

const response = await fetch(url, options)
expect(response.status).toEqual(200)

const body = await response.json()

expect(body.headersReceived).toMatchObject({
origin: 'http://www.example.com',
'x-webhook-signature': 'ABCDEF',
})
})
})
25 changes: 25 additions & 0 deletions tests/integration/httpApi-headers/serverless.yml
@@ -0,0 +1,25 @@
service: httpapi-headers

plugins:
- ../../../

provider:
memorySize: 128
name: aws
region: us-east-1 # default
runtime: nodejs12.x
stage: dev
versionFunctions: false
httpApi:
payload: '2.0'

functions:
echoHeaders:
events:
- httpApi:
method: get
path: echo-headers
- httpApi:
method: post
path: echo-headers
handler: handler.echoHeaders

0 comments on commit 9ff4cf3

Please sign in to comment.