diff --git a/.changeset/seven-ducks-breathe.md b/.changeset/seven-ducks-breathe.md new file mode 100644 index 000000000..830070962 --- /dev/null +++ b/.changeset/seven-ducks-breathe.md @@ -0,0 +1,5 @@ +--- +"@opennextjs/aws": patch +--- + +fix: Avoid merging Location header on response when its an array diff --git a/packages/open-next/src/http/util.ts b/packages/open-next/src/http/util.ts index d5b29768d..de3b07de6 100644 --- a/packages/open-next/src/http/util.ts +++ b/packages/open-next/src/http/util.ts @@ -1,4 +1,5 @@ import type http from "node:http"; +import logger from "../logger"; export const parseHeaders = ( headers?: http.OutgoingHttpHeader[] | http.OutgoingHttpHeaders, @@ -12,7 +13,27 @@ export const parseHeaders = ( if (value === undefined) { continue; } - result[key.toLowerCase()] = convertHeader(value); + const keyLower = key.toLowerCase(); + /** + * Next can return an Array for the Location header when you return null from a get in the cacheHandler on a page that has a redirect() + * We dont want to merge that into a comma-separated string + * If they are the same just return one of them + * Otherwise return the last one + * See: https://github.com/opennextjs/opennextjs-cloudflare/issues/875#issuecomment-3258248276 + * and https://github.com/opennextjs/opennextjs-aws/pull/977#issuecomment-3261763114 + */ + if (keyLower === "location" && Array.isArray(value)) { + if (value[0] === value[1]) { + result[keyLower] = value[0]; + } else { + logger.warn( + "Multiple different values for Location header found. Using the last one", + ); + result[keyLower] = value[value.length - 1]; + } + continue; + } + result[keyLower] = convertHeader(value); } return result; diff --git a/packages/tests-unit/tests/http/utils.test.ts b/packages/tests-unit/tests/http/utils.test.ts index 635f1d742..34f0717dc 100644 --- a/packages/tests-unit/tests/http/utils.test.ts +++ b/packages/tests-unit/tests/http/utils.test.ts @@ -1,4 +1,9 @@ -import { parseSetCookieHeader } from "@opennextjs/aws/http/util.js"; +import type http from "node:http"; + +import { + parseHeaders, + parseSetCookieHeader, +} from "@opennextjs/aws/http/util.js"; describe("parseSetCookieHeader", () => { it("returns an empty list if cookies is emptyish", () => { @@ -43,3 +48,21 @@ describe("parseSetCookieHeader", () => { ]); }); }); + +describe("parseHeaders", () => { + it("parses headers correctly", () => { + const headers = parseHeaders({ + location: ["/target", "/target"], + "x-custom-header": "customValue", + "x-multiple-values": ["value1", "value2"], + "x-undefined-header": undefined, + "x-opennext": "is-so-cool", + } as unknown as http.OutgoingHttpHeaders); + expect(headers).toEqual({ + location: "/target", + "x-custom-header": "customValue", + "x-multiple-values": "value1,value2", + "x-opennext": "is-so-cool", + }); + }); +});