Skip to content

Commit 713f78e

Browse files
committed
add normalizeLocationHeader
1 parent f0ffff5 commit 713f78e

File tree

4 files changed

+62
-4
lines changed

4 files changed

+62
-4
lines changed

.changeset/fluffy-dingos-tap.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@opennextjs/aws": patch
3+
---
4+
5+
fix: Normalize the Location header in redirects
6+
7+
Normalizes the Location header to either be a relative path or a full URL.
8+
If the Location header is relative to the host, it will return a relative path.
9+
If it is an absolute URL, it will return the full URL.
10+
Both cases will ensure that the Location value is properly encoded according to RFC

packages/open-next/src/core/routing/middleware.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
convertBodyToReadableStream,
1616
getMiddlewareMatch,
1717
isExternal,
18+
normalizeLocationHeader,
1819
} from "./util.js";
1920

2021
const middlewareManifest = MiddlewareManifest;
@@ -94,6 +95,15 @@ export async function handleMiddleware(
9495
url,
9596
body: convertBodyToReadableStream(internalEvent.method, internalEvent.body),
9697
} as unknown as Request);
98+
if (result.headers.has("Location")) {
99+
result.headers.set(
100+
"Location",
101+
normalizeLocationHeader(
102+
result.headers.get("Location") as string,
103+
internalEvent.url,
104+
),
105+
);
106+
}
97107
const statusCode = result.status;
98108

99109
/* Apply override headers from middleware

packages/open-next/src/core/routing/util.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,3 +437,41 @@ export async function invalidateCDNOnRequest(
437437
]);
438438
}
439439
}
440+
441+
/**
442+
* Normalizes the Location header to either be a relative path or a full URL.
443+
* If the Location header is relative to the host, it will return a relative path.
444+
* If it is an absolute URL, it will return the full URL.
445+
* Both cases will ensure that the Location value is properly encoded according to RFC
446+
*
447+
* @param location The Location header value
448+
* @param base The original request URL
449+
* @returns An encoded absolute or relative Location header value
450+
*/
451+
export function normalizeLocationHeader(
452+
location: string,
453+
base: string,
454+
): string {
455+
try {
456+
const locationUrl = new URL(location);
457+
const origin = new URL(base).origin;
458+
459+
// Encode the search parameters to ensure they are valid according to RFC
460+
const encodedSearch = locationUrl.searchParams.toString()
461+
? `?${locationUrl.searchParams.toString()}`
462+
: "";
463+
const href =
464+
locationUrl.origin +
465+
locationUrl.pathname +
466+
encodedSearch +
467+
locationUrl.hash;
468+
// The URL is relative if the origin is the same as the base URL's origin
469+
if (locationUrl.origin === origin) {
470+
return href.slice(origin.length);
471+
}
472+
return href;
473+
} catch {
474+
// If the location is not a valid URL, return it as-is
475+
return location;
476+
}
477+
}

packages/open-next/src/core/routingHandler.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
dynamicRouteMatcher,
2929
staticRouteMatcher,
3030
} from "./routing/routeMatcher";
31-
import { constructNextUrl } from "./routing/util";
31+
import { constructNextUrl, normalizeLocationHeader } from "./routing/util";
3232

3333
export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
3434
export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;
@@ -110,13 +110,13 @@ export default async function routingHandler(
110110
if (redirect) {
111111
// We need to encode the value in the Location header to make sure it is valid according to RFC
112112
// https://stackoverflow.com/a/7654605/16587222
113-
redirect.headers.Location = new URL(
113+
redirect.headers.Location = normalizeLocationHeader(
114114
redirect.headers.Location as string,
115-
).href;
115+
event.url,
116+
);
116117
debug("redirect", redirect);
117118
return redirect;
118119
}
119-
120120
const middlewareEventOrResult = await handleMiddleware(
121121
eventOrResult,
122122
// We need to pass the initial search without any decoding

0 commit comments

Comments
 (0)