8000 Redirect requests with repeated slashes (#863) · opennextjs/opennextjs-aws@d434708 · GitHub
[go: up one dir, main page]

Skip to content

Commit d434708

Browse files
dnewbound0theredandtheblue
and
theredandtheblue
authored
Redirect requests with repeated slashes (#863)
* feat: redirect requests with repeated slashes * chore: add changeset * feat: respond to PR review * chore: fix linting error * doc: document funcs with links to their Next equivalents --------- Co-authored-by: theredandtheblue <9413070-theredandtheblue@users.noreply.gitlab.com>
1 parent daaf38f commit d434708

File tree

4 files changed

+51
-0
lines changed

4 files changed

+51
-0
lines changed

.changeset/cuddly-dingos-walk.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@opennextjs/aws": patch
3+
---
4+
5+
feat: redirect requests with repeated slashes

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
RouteHas,
1010
} from "types/next-types";
1111
import type { InternalEvent, InternalResult } from "types/open-next";
12+
import { normalizeRepeatedSlashes } from "utils/normalize-path";
1213
import { emptyReadableStream, toReadableStream } from "utils/stream";
1314

1415
import { debug } from "../../adapters/logger";
@@ -262,6 +263,29 @@ export function handleRewrites<T extends RewriteDefinition>(
262263
};
263264
}
264265

266+
// Normalizes repeated slashes in the path e.g. hello//world -> hello/world
267+
// or backslashes to forward slashes. This prevents requests such as //domain
268+
// from invoking the middleware with `request.url === "domain"`.
269+
// See: https://github.com/vercel/next.js/blob/3ecf087f10fdfba4426daa02b459387bc9c3c54f/packages/next/src/server/base-server.ts#L1016-L1020
270+
function handleRepeatedSlashRedirect(
271+
event: InternalEvent,
272+
): false | InternalResult {
273+
// Redirect `https://example.com//foo` to `https://example.com/foo`.
274+
if (event.rawPath.match(/(\\|\/\/)/)) {
275+
return {
276+
type: event.type,
277+
statusCode: 308,
278+
headers: {
279+
Location: normalizeRepeatedSlashes(new URL(event.url)),
280+
},
281+
body: emptyReadableStream(),
282+
isBase64Encoded: false,
283+
};
284+
}
285+
286+
return false;
287+
}
288+
265289
function handleTrailingSlashRedirect(
266290
event: InternalEvent,
267291
): false | InternalResult {
@@ -326,6 +350,9 @@ export function handleRedirects(
326350
event: InternalEvent,
327351
redirects: RedirectDefinition[],
328352
): InternalResult | undefined {
353+
const repeatedSlashRedirect = handleRepeatedSlashRedirect(event);
354+
if (repeatedSlashRedirect) return repeatedSlashRedirect;
355+
329356
const trailingSlashRedirect = handleTrailingSlashRedirect(event);
330357
if (trailingSlashRedirect) return trailingSlashRedirect;
331358

packages/open-next/src/utils/normalize-path.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ export function normalizePath(path: string) {
44
return path.replace(/\\/g, "/");
55
}
66

7+
// See: https://github.com/vercel/next.js/blob/3ecf087f10fdfba4426daa02b459387bc9c3c54f/packages/next/src/shared/lib/utils.ts#L348
8+
export function normalizeRepeatedSlashes(url: URL) {
9+
const urlNoQuery = url.host + url.pathname;
10+
return `${url.protocol}//${urlNoQuery
11+
.replace(/\\/g, "/")
12+
.replace(/\/\/+/g, "/")}${url.search}`;
13+
}
14+
715
export function getMonorepoRelativePath(relativePath = "../.."): string {
816
return path.join(
917
globalThis.monorepoPackagePath

packages/tests-unit/tests/core/routing/matcher.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,17 @@ describe("getNextConfigHeaders", () => {
271271
});
272272

273273
describe("handleRedirects", () => {
274+
it("should redirect repeated slashes", () => {
275+
const event = createEvent({
276+
url: "https://on/api-route//foo",
277+
});
278+
279+
const result = handleRedirects(event, []);
280+
281+
expect(result.statusCode).toEqual(308);
282+
expect(result.headers.Location).toEqual("https://on/api-route/foo");
283+
});
284+
274285
it("should redirect trailing slash by default", () => {
275286
const event = createEvent({
276287
url: "https://on/api-route/",

0 commit comments

Comments
 (0)
0