Skip to content
This repository has been archived by the owner on Jan 28, 2025. It is now read-only.

Commit

Permalink
fix(lambda-at-edge): fix broken redirects with full URLs as destinati…
Browse files Browse the repository at this point in the history
…ons (#638)
  • Loading branch information
dphang authored Sep 30, 2020
1 parent c802fa4 commit ee95e54
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 18 deletions.
18 changes: 18 additions & 0 deletions packages/e2e-tests/next-app/cypress/integration/redirects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,24 @@ describe("Redirects Tests", () => {
expectedRedirect: "/ssr-page",
expectedStatus: 200,
expectedRedirectStatus: 302
},
{
path: "/external-redirect-1",
expectedRedirect: "https://api.github.com",
expectedStatus: 200,
expectedRedirectStatus: 308
},
{
path: "/external-redirect-2/abcd",
expectedRedirect: "https://api.github.com/abcd",
expectedStatus: 404,
expectedRedirectStatus: 308
},
{
path: "/external-redirect-3/abcd",
expectedRedirect: "https://api.github.com/abcd/",
expectedStatus: 404,
expectedRedirectStatus: 308
}
].forEach(
({ path, expectedRedirect, expectedStatus, expectedRedirectStatus }) => {
Expand Down
15 changes: 15 additions & 0 deletions packages/e2e-tests/next-app/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ module.exports = {
source: "/regex-redirect-2/:slug(\\d{1,})",
destination: "/regex-redirect-2-dest/:slug",
permanent: true
},
{
source: "/external-redirect-1",
destination: "https://api.github.com",
permanent: true
},
{
source: "/external-redirect-2/:id",
destination: "https://api.github.com/:id",
permanent: true
},
{
source: "/external-redirect-3/:id",
destination: "https://api.github.com/:id/",
permanent: true
}
];
}
Expand Down
32 changes: 29 additions & 3 deletions packages/libs/lambda-at-edge/src/routing/matcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,33 @@ export function matchPath(path: string, source: string): Match {
export function compileDestination(
destination: string,
params: object
): string {
const toPath = compile(destination, { encode: encodeURIComponent });
return toPath(params);
): string | null {
try {
const destinationLowerCase = destination.toLowerCase();
if (
destinationLowerCase.startsWith("https://") ||
destinationLowerCase.startsWith("http://")
) {
// Handle external URLs
const { origin, pathname } = new URL(destination);
const toPath = compile(pathname, { encode: encodeURIComponent });
const compiledDestination = `${origin}${toPath(params)}`;

// Remove trailing slash if original destination didn't have it
if (!destination.endsWith("/") && compiledDestination.endsWith("/")) {
return compiledDestination.slice(0, -1);
} else {
return compiledDestination;
}
} else {
// Handle all other paths
const toPath = compile(destination, { encode: encodeURIComponent });
return toPath(params);
}
} catch (error) {
console.error(
`Could not compile destination ${destination}, returning null instead. Error: ${error}`
);
return null;
}
}
11 changes: 10 additions & 1 deletion packages/libs/lambda-at-edge/src/routing/redirector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,17 @@ export function getRedirectPath(
const match = matchPath(path, redirect.source);

if (match) {
const compiledDestination = compileDestination(
redirect.destination,
match.params
);

if (!compiledDestination) {
return null;
}

return {
redirectPath: compileDestination(redirect.destination, match.params),
redirectPath: compiledDestination,
statusCode: redirect.statusCode
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
"destination": "/basepath/users/:post/",
"statusCode": 307,
"regex": "^/basepath/old-users(?:/(\\d{1,}))/$"
},
{
"source": "/basepath/external/",
"destination": "https://example.com",
"statusCode": 308,
"regex": "^/basepath/external/"
}
],
"rewrites": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
"destination": "/basepath/users/:post",
"statusCode": 307,
"regex": "^/basepath/old-users(?:/(\\d{1,}))$"
},
{
"source": "/basepath/external",
"destination": "https://example.com",
"statusCode": 308,
"regex": "^/basepath/external"
}
],
"rewrites": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,7 @@ describe("Lambda@Edge", () => {
${"/basepath/terms-new/"} | ${"/basepath/terms/"} | ${308}
${"/basepath/old-blog/abc/"} | ${"/basepath/news/abc/"} | ${308}
${"/basepath/old-users/1234/"} | ${"/basepath/users/1234/"} | ${307}
${"/basepath/external/"} | ${"https://example.com"} | ${308}
`(
"redirects path $path to $expectedRedirect, expectedRedirectStatusCode: $expectedRedirectStatusCode",
async ({ path, expectedRedirect, expectedRedirectStatusCode }) => {
Expand All @@ -641,6 +642,7 @@ describe("Lambda@Edge", () => {
${"/basepath/terms-new"} | ${"/basepath/terms"} | ${308}
${"/basepath/old-blog/abc"} | ${"/basepath/news/abc"} | ${308}
${"/basepath/old-users/1234"} | ${"/basepath/users/1234"} | ${307}
${"/basepath/external"} | ${"https://example.com"} | ${308}
`(
"redirects path $path to $expectedRedirect, expectedRedirectStatusCode: $expectedRedirectStatusCode",
async ({ path, expectedRedirect, expectedRedirectStatusCode }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,10 +641,11 @@ describe("Lambda@Edge", () => {
describe("Custom Redirects", () => {
if (trailingSlash) {
it.each`
path | expectedRedirect | expectedRedirectStatusCode
${"/terms-new/"} | ${"/terms/"} | ${308}
${"/old-blog/abc/"} | ${"/news/abc/"} | ${308}
${"/old-users/1234/"} | ${"/users/1234/"} | ${307}
path | expectedRedirect | expectedRedirectStatusCode
${"/terms-new/"} | ${"/terms/"} | ${308}
${"/old-blog/abc/"} | ${"/news/abc/"} | ${308}
${"/old-users/1234/"} | ${"/users/1234/"} | ${307}
${"/external/"} | ${"https://example.com"} | ${308}
`(
"redirects path $path to $expectedRedirect, expectedRedirectStatusCode: $expectedRedirectStatusCode",
async ({ path, expectedRedirect, expectedRedirectStatusCode }) => {
Expand All @@ -657,10 +658,11 @@ describe("Lambda@Edge", () => {
);
} else {
it.each`
path | expectedRedirect | expectedRedirectStatusCode
${"/terms-new"} | ${"/terms"} | ${308}
${"/old-blog/abc"} | ${"/news/abc"} | ${308}
${"/old-users/1234"} | ${"/users/1234"} | ${307}
path | expectedRedirect | expectedRedirectStatusCode
${"/terms-new"} | ${"/terms"} | ${308}
${"/old-blog/abc"} | ${"/news/abc"} | ${308}
${"/old-users/1234"} | ${"/users/1234"} | ${307}
${"/external"} | ${"https://example.com"} | ${308}
`(
"redirects path $path to $expectedRedirect, expectedRedirectStatusCode: $expectedRedirectStatusCode",
async ({ path, expectedRedirect, expectedRedirectStatusCode }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
"destination": "/users/:id/",
"statusCode": 307,
"regex": "^/old-users(?:/(\\d{1,}))/$"
},
{
"source": "/external/",
"destination": "https://example.com",
"statusCode": 308,
"regex": "^/external/"
}
],
"rewrites": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
"destination": "/users/:id",
"statusCode": 307,
"regex": "^/old-users(?:/(\\d{1,}))$"
},
{
"source": "/external",
"destination": "https://example.com",
"statusCode": 308,
"regex": "^/external"
}
],
"rewrites": [],
Expand Down
27 changes: 27 additions & 0 deletions packages/libs/lambda-at-edge/tests/routing/matcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,32 @@ describe("Matcher Tests", () => {
const match = compileDestination("/about/:a/:b", { a: "123", b: "456" });
expect(match).toEqual("/about/123/456");
});

it("compiles http URL", () => {
const match = compileDestination("http://example.com", {});
expect(match).toEqual("http://example.com");
});

it("compiles https URL", () => {
const match = compileDestination("https://example.com", {});
expect(match).toEqual("https://example.com");
});

it("compiles https URL with trailing slash", () => {
const match = compileDestination("https://example.com/", {});
expect(match).toEqual("https://example.com/");
});

it("compiles parameterized https URL", () => {
const match = compileDestination("https://example.com/:id", {
id: "123"
});
expect(match).toEqual("https://example.com/123");
});

it("invalid destination returns null", () => {
const match = compileDestination("abc://123", {});
expect(match).toBeNull();
});
});
});
26 changes: 20 additions & 6 deletions packages/libs/lambda-at-edge/tests/routing/redirector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,32 @@ describe("Redirector Tests", () => {
destination: "/users/:id",
statusCode: 307,
regex: "^/old-users(?:/(\\d{1,}))$"
},
{
source: "/external",
destination: "https://example.com",
statusCode: 308,
regex: "^/external$"
},
{
source: "/invalid-destination",
destination: "ftp://example.com",
statusCode: 308,
regex: "^/invalid-destination$"
}
]
};
});

it.each`
path | expectedRedirect | expectedStatusCode
${"/a"} | ${"/b"} | ${308}
${"/c"} | ${"/d"} | ${302}
${"/old-blog/abc"} | ${"/news/abc"} | ${308}
${"/old-users/1234"} | ${"/users/1234"} | ${307}
${"/old-users/abc"} | ${null} | ${null}
path | expectedRedirect | expectedStatusCode
${"/a"} | ${"/b"} | ${308}
${"/c"} | ${"/d"} | ${302}
${"/old-blog/abc"} | ${"/news/abc"} | ${308}
${"/old-users/1234"} | ${"/users/1234"} | ${307}
${"/old-users/abc"} | ${null} | ${null}
${"/external"} | ${"https://example.com"} | ${308}
${"/invalid-destination"} | ${null} | ${null}
`(
"redirects path $path to $expectedRedirect",
({ path, expectedRedirect, expectedStatusCode }) => {
Expand Down

0 comments on commit ee95e54

Please sign in to comment.