Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cuddly-dingos-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/aws": patch
---

feat: redirect requests with repeated slashes
26 changes: 26 additions & 0 deletions packages/open-next/src/core/routing/matcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
} from "types/next-types";
import type { InternalEvent, InternalResult } from "types/open-next";
import { emptyReadableStream, toReadableStream } from "utils/stream";
import { normalizePath } from "utils/normalize-path"

import { debug } from "../../adapters/logger";
import { handleLocaleRedirect, localizePath } from "./i18n";
Expand Down Expand Up @@ -262,6 +263,28 @@ export function handleRewrites<T extends RewriteDefinition>(
};
}

function handleRepeatedSlashRedirect(
event: InternalEvent,
): false | InternalResult {
// Redirect `https://example.com//foo` to `https://example.com/foo`.
const url = new URL(event.url);
if (url.pathname.match(/(\\|\/\/)/)) {
return {
type: event.type,
statusCode: 308,
headers: {
Location: `${url.protocol}//${url.host}${normalizePath(url.pathname)}${
url.search
}`,
},
body: emptyReadableStream(),
isBase64Encoded: false,
};
}

return false;
}

function handleTrailingSlashRedirect(
event: InternalEvent,
): false | InternalResult {
Expand Down Expand Up @@ -326,6 +349,9 @@ export function handleRedirects(
event: InternalEvent,
redirects: RedirectDefinition[],
): InternalResult | undefined {
const repeatedSlashRedirect = handleRepeatedSlashRedirect(event);
if (repeatedSlashRedirect) return repeatedSlashRedirect;

const trailingSlashRedirect = handleTrailingSlashRedirect(event);
if (trailingSlashRedirect) return trailingSlashRedirect;

Expand Down
2 changes: 1 addition & 1 deletion packages/open-next/src/utils/normalize-path.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from "node:path";

export function normalizePath(path: string) {
return path.replace(/\\/g, "/");
return path.replace(/\\/g, "/").replace(/\/\/+/g, "/");
}

export function getMonorepoRelativePath(relativePath = "../.."): string {
Expand Down
11 changes: 11 additions & 0 deletions packages/tests-unit/tests/core/routing/matcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,17 @@ describe("getNextConfigHeaders", () => {
});

describe("handleRedirects", () => {
it("should redirect repeated slashes", () => {
const event = createEvent({
url: "https://on/api-route//foo",
});

const result = handleRedirects(event, []);

expect(result.statusCode).toEqual(308);
expect(result.headers.Location).toEqual("https://on/api-route/foo");
});

it("should redirect trailing slash by default", () => {
const event = createEvent({
url: "https://on/api-route/",
Expand Down