Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
23 changes: 23 additions & 0 deletions packages/open-next/src/core/routing/matcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
RouteHas,
} from "types/next-types";
import type { InternalEvent, InternalResult } from "types/open-next";
import { normalizeRepeatedSlashes } from "utils/normalize-path";
import { emptyReadableStream, toReadableStream } from "utils/stream";

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

function handleRepeatedSlashRedirect(
event: InternalEvent,
): false | InternalResult {
// Redirect `https://example.com//foo` to `https://example.com/foo`.
if (event.rawPath.match(/(\\|\/\/)/)) {
return {
type: event.type,
statusCode: 308,
headers: {
Location: normalizeRepeatedSlashes(new URL(event.url)),
},
body: emptyReadableStream(),
isBase64Encoded: false,
};
}

return false;
}

function handleTrailingSlashRedirect(
event: InternalEvent,
): false | InternalResult {
Expand Down Expand Up @@ -326,6 +346,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
7 changes: 7 additions & 0 deletions packages/open-next/src/utils/normalize-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ export function normalizePath(path: string) {
return path.replace(/\\/g, "/");
}

export function normalizeRepeatedSlashes(url: URL) {
const urlNoQuery = url.host + url.pathname;
return `${url.protocol}//${urlNoQuery
.replace(/\\/g, "/")
.replace(/\/\/+/g, "/")}${url.search}`;
}

export function getMonorepoRelativePath(relativePath = "../.."): string {
return path.join(
globalThis.monorepoPackagePath
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
Loading