From a17a1536d807b6f5a85b00c3ebbc375aacfa229a Mon Sep 17 00:00:00 2001 From: klarstrup Date: Wed, 15 Dec 2021 17:16:34 +0100 Subject: [PATCH 01/11] Add failing colon rewrite test --- .../middleware/core/test/index.test.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/integration/middleware/core/test/index.test.js b/test/integration/middleware/core/test/index.test.js index d29e399ada09..ef4d177ed437 100644 --- a/test/integration/middleware/core/test/index.test.js +++ b/test/integration/middleware/core/test/index.test.js @@ -156,6 +156,27 @@ function rewriteTests(locale = '') { expect($('.title').text()).toBe('About Page') }) + it(`${locale} should rewrite with respect to colons in path`, async () => { + const res = await fetchViaHTTP( + context.appPort, + `${locale}/rewrites/not:param` + ) + const html = await res.text() + const $ = cheerio.load(html) + expect($('#props').text()).toBe('not:param') + const browser = await webdriver( + context.appPort, + `${locale}/rewrites/not:param` + ) + try { + expect(await browser.eval(`window.location.pathname`)).toBe( + `${locale}/rewrites/not:param` + ) + } finally { + await browser.close() + } + }) + it(`${locale} should rewrite when not using localhost`, async () => { const res = await fetchViaHTTP( `http://localtest.me:${context.appPort}`, From 56a79c40d512b10d81a654b5c6fceed04a1524b2 Mon Sep 17 00:00:00 2001 From: klarstrup Date: Wed, 15 Dec 2021 17:18:32 +0100 Subject: [PATCH 02/11] add test fixture --- test/integration/middleware/core/pages/rewrites/[param].js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 test/integration/middleware/core/pages/rewrites/[param].js diff --git a/test/integration/middleware/core/pages/rewrites/[param].js b/test/integration/middleware/core/pages/rewrites/[param].js new file mode 100644 index 000000000000..1bc7e31b9cae --- /dev/null +++ b/test/integration/middleware/core/pages/rewrites/[param].js @@ -0,0 +1,7 @@ +export const getServerSideProps = ({ params }) => { + return { props: params } +} + +export default function Page({ param }) { + return

{param}

+} From 4e3f97b16057c3dd0a0840c766568632bcb6bddf Mon Sep 17 00:00:00 2001 From: klarstrup Date: Wed, 15 Dec 2021 17:44:48 +0100 Subject: [PATCH 03/11] better colon rewrite tests --- .../core/pages/rewrites/_middleware.js | 8 ++++ .../middleware/core/test/index.test.js | 45 ++++++++++++++----- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/test/integration/middleware/core/pages/rewrites/_middleware.js b/test/integration/middleware/core/pages/rewrites/_middleware.js index 166bd6475165..19cacf2f2915 100644 --- a/test/integration/middleware/core/pages/rewrites/_middleware.js +++ b/test/integration/middleware/core/pages/rewrites/_middleware.js @@ -19,6 +19,14 @@ export async function middleware(request) { return NextResponse.rewrite('/rewrites/about') } + if (url.pathname === '/rewrites/rewrite-me-with-a-colon') { + return NextResponse.rewrite('/rewrites/with:colon') + } + + if (url.pathname === '/rewrites/colon:here') { + return NextResponse.rewrite('/rewrites/no-colon-here') + } + if (url.pathname === '/rewrites/rewrite-me-to-vercel') { return NextResponse.rewrite('https://vercel.com') } diff --git a/test/integration/middleware/core/test/index.test.js b/test/integration/middleware/core/test/index.test.js index ef4d177ed437..fd8ee3f75411 100644 --- a/test/integration/middleware/core/test/index.test.js +++ b/test/integration/middleware/core/test/index.test.js @@ -156,22 +156,43 @@ function rewriteTests(locale = '') { expect($('.title').text()).toBe('About Page') }) - it(`${locale} should rewrite with respect to colons in path`, async () => { - const res = await fetchViaHTTP( - context.appPort, - `${locale}/rewrites/not:param` - ) + it(`${locale} support colons in path`, async () => { + const path = `${locale}/rewrites/not:param` + const res = await fetchViaHTTP(context.appPort, path) const html = await res.text() const $ = cheerio.load(html) expect($('#props').text()).toBe('not:param') - const browser = await webdriver( - context.appPort, - `${locale}/rewrites/not:param` - ) + const browser = await webdriver(context.appPort, path) try { - expect(await browser.eval(`window.location.pathname`)).toBe( - `${locale}/rewrites/not:param` - ) + expect(await browser.eval(`window.location.pathname`)).toBe(path) + } finally { + await browser.close() + } + }) + + it(`${locale} can rewrite to path with colon`, async () => { + const path = `${locale}/rewrites/rewrite-me-with-a-colon` + const res = await fetchViaHTTP(context.appPort, path) + const html = await res.text() + const $ = cheerio.load(html) + expect($('#props').text()).toBe('with:colon') + const browser = await webdriver(context.appPort, path) + try { + expect(await browser.eval(`window.location.pathname`)).toBe(path) + } finally { + await browser.close() + } + }) + + it(`${locale} can rewrite from path with colon`, async () => { + const path = `${locale}/rewrites/colon:here` + const res = await fetchViaHTTP(context.appPort, path) + const html = await res.text() + const $ = cheerio.load(html) + expect($('#props').text()).toBe('no-colon-here') + const browser = await webdriver(context.appPort, path) + try { + expect(await browser.eval(`window.location.pathname`)).toBe(path) } finally { await browser.close() } From eda9dfbf3f89850910c30f4a57bdc3bea07c2e20 Mon Sep 17 00:00:00 2001 From: klarstrup Date: Wed, 15 Dec 2021 18:43:21 +0100 Subject: [PATCH 04/11] middleware rewrite colon tests with query parameters --- .../middleware/core/pages/rewrites/[param].js | 13 +++++-- .../middleware/core/test/index.test.js | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/test/integration/middleware/core/pages/rewrites/[param].js b/test/integration/middleware/core/pages/rewrites/[param].js index 1bc7e31b9cae..d33f75dd2da6 100644 --- a/test/integration/middleware/core/pages/rewrites/[param].js +++ b/test/integration/middleware/core/pages/rewrites/[param].js @@ -1,7 +1,12 @@ -export const getServerSideProps = ({ params }) => { - return { props: params } +export const getServerSideProps = ({ params, query }) => { + return { props: { params, query } } } -export default function Page({ param }) { - return

{param}

+export default function Page({ params: { param }, query: { qp } }) { + return ( + <> +

{param}

+

{qp}

+ + ) } diff --git a/test/integration/middleware/core/test/index.test.js b/test/integration/middleware/core/test/index.test.js index fd8ee3f75411..77ca20c48b87 100644 --- a/test/integration/middleware/core/test/index.test.js +++ b/test/integration/middleware/core/test/index.test.js @@ -198,6 +198,44 @@ function rewriteTests(locale = '') { } }) + it(`${locale} can rewrite from path with colon and retain query parameter`, async () => { + const path = `${locale}/rewrites/colon:here?qp=arg` + const res = await fetchViaHTTP(context.appPort, path) + const html = await res.text() + const $ = cheerio.load(html) + expect($('#props').text()).toBe('no-colon-here') + expect($('#qp').text()).toBe('arg') + const browser = await webdriver(context.appPort, path) + try { + expect( + await browser.eval( + `window.location.href.replace(window.location.origin, '')` + ) + ).toBe(path) + } finally { + await browser.close() + } + }) + + it(`${locale} can rewrite to path with colon and retain query parameter`, async () => { + const path = `${locale}/rewrites/rewrite-me-with-a-colon?qp=arg` + const res = await fetchViaHTTP(context.appPort, path) + const html = await res.text() + const $ = cheerio.load(html) + expect($('#props').text()).toBe('with:colon') + expect($('#qp').text()).toBe('arg') + const browser = await webdriver(context.appPort, path) + try { + expect( + await browser.eval( + `window.location.href.replace(window.location.origin, '')` + ) + ).toBe(path) + } finally { + await browser.close() + } + }) + it(`${locale} should rewrite when not using localhost`, async () => { const res = await fetchViaHTTP( `http://localtest.me:${context.appPort}`, From 39545b7a21f17c36b9bad6adfc9122508f4c93d0 Mon Sep 17 00:00:00 2001 From: klarstrup Date: Wed, 15 Dec 2021 19:32:43 +0100 Subject: [PATCH 05/11] fix #31523 this addresses the symptom but the real systemic issue is that prepareDestination is called on rewrite/redirect URLs, which have no defined special behavior for colons and they should not be compiled at all --- packages/next/server/base-server.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 6293360db00f..0783801db9e2 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1292,12 +1292,20 @@ export default abstract class Server { } if (result.response.headers.has('x-middleware-rewrite')) { - const { newUrl, parsedDestination } = prepareDestination({ + const destination = parseUrl( + result.response.headers.get('x-middleware-rewrite')! + ) + + let { newUrl, parsedDestination } = prepareDestination({ appendParamsToQuery: true, - destination: result.response.headers.get('x-middleware-rewrite')!, + destination: formatUrl({ + ...destination, + pathname: destination.pathname?.replace(/:/g, '__ESC__COLON__'), + }), params: _params, query: parsedUrl.query, }) + newUrl = newUrl.replace(/__ESC__COLON__/g, ':') if ( parsedDestination.protocol && From 7fbece7c8210957530ba233473546c68517cf751 Mon Sep 17 00:00:00 2001 From: klarstrup Date: Wed, 15 Dec 2021 20:09:43 +0100 Subject: [PATCH 06/11] hack around prepareDestination to skip compiling x-middleware-rewrite this is a bit nicer than just escaping colons, but ideally we find a way to obviate prepareDestination --- packages/next/server/base-server.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 0783801db9e2..1b6f905eeba0 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1296,16 +1296,15 @@ export default abstract class Server { result.response.headers.get('x-middleware-rewrite')! ) - let { newUrl, parsedDestination } = prepareDestination({ + const { newUrl, parsedDestination } = prepareDestination({ appendParamsToQuery: true, - destination: formatUrl({ - ...destination, - pathname: destination.pathname?.replace(/:/g, '__ESC__COLON__'), - }), - params: _params, + destination: formatUrl({ ...destination, pathname: '/:path*' }), + params: { + ..._params, + path: destination.pathname?.split('/').filter(Boolean), + }, query: parsedUrl.query, }) - newUrl = newUrl.replace(/__ESC__COLON__/g, ':') if ( parsedDestination.protocol && From 02c6e75c8457fc17860573a43c428a508bc32a6a Mon Sep 17 00:00:00 2001 From: klarstrup Date: Wed, 15 Dec 2021 20:59:11 +0100 Subject: [PATCH 07/11] obviate prepareDestination for x-middleware-rewrite handling --- packages/next/server/base-server.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 1b6f905eeba0..76a7e29b2d8d 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -90,6 +90,7 @@ import ResponseCache from './response-cache' import { parseNextUrl } from '../shared/lib/router/utils/parse-next-url' import isError from '../lib/is-error' import { getMiddlewareInfo } from './require' +import { parseUrl as simpleParseUrl } from '../shared/lib/router/utils/parse-url' import { MIDDLEWARE_ROUTE } from '../lib/constants' import { run } from './web/sandbox' import { addRequestMeta, getRequestMeta } from './request-meta' @@ -1292,19 +1293,14 @@ export default abstract class Server { } if (result.response.headers.has('x-middleware-rewrite')) { - const destination = parseUrl( - result.response.headers.get('x-middleware-rewrite')! - ) - - const { newUrl, parsedDestination } = prepareDestination({ - appendParamsToQuery: true, - destination: formatUrl({ ...destination, pathname: '/:path*' }), - params: { - ..._params, - path: destination.pathname?.split('/').filter(Boolean), - }, - query: parsedUrl.query, - }) + const parsedDestination: ParsedUrl = { + ...simpleParseUrl( + result.response.headers.get('x-middleware-rewrite')! + ), + query: {}, + search: '', + } + const newUrl = formatUrl(parsedDestination) if ( parsedDestination.protocol && From a3c134a682cb667c3862146ed569e80b8ff23f82 Mon Sep 17 00:00:00 2001 From: klarstrup Date: Wed, 15 Dec 2021 21:18:56 +0100 Subject: [PATCH 08/11] don't clobber rewrite query data --- packages/next/server/base-server.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 76a7e29b2d8d..6f8695440d2d 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1293,14 +1293,14 @@ export default abstract class Server { } if (result.response.headers.has('x-middleware-rewrite')) { - const parsedDestination: ParsedUrl = { - ...simpleParseUrl( - result.response.headers.get('x-middleware-rewrite')! - ), + const parsedDestination: ParsedUrl = simpleParseUrl( + result.response.headers.get('x-middleware-rewrite')! + ) + const newUrl = formatUrl({ + ...parsedDestination, query: {}, search: '', - } - const newUrl = formatUrl(parsedDestination) + }) if ( parsedDestination.protocol && From a5bb9adc55d92a1e66949bfe200a5ffede6df028 Mon Sep 17 00:00:00 2001 From: klarstrup Date: Wed, 15 Dec 2021 22:34:29 +0100 Subject: [PATCH 09/11] omit redundant type --- packages/next/server/base-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 6f8695440d2d..46907b07ff05 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1293,7 +1293,7 @@ export default abstract class Server { } if (result.response.headers.has('x-middleware-rewrite')) { - const parsedDestination: ParsedUrl = simpleParseUrl( + const parsedDestination = simpleParseUrl( result.response.headers.get('x-middleware-rewrite')! ) const newUrl = formatUrl({ From ab229f0e47e9ba912c9a6d6c087489338034abea Mon Sep 17 00:00:00 2001 From: klarstrup Date: Wed, 2 Feb 2022 18:30:53 +0100 Subject: [PATCH 10/11] catch up to main --- packages/next/server/next-server.ts | 10 +++++----- .../middleware/core/pages/rewrites/_middleware.js | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 38ec642e66ce..5473427174a3 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -51,7 +51,7 @@ import { serveStatic } from './serve-static' import { ParsedUrlQuery } from 'querystring' import { apiResolver } from './api-utils' import { RenderOpts, renderToHTML } from './render' -import { ParsedUrl } from '../shared/lib/router/utils/parse-url' +import { ParsedUrl, parseUrl } from '../shared/lib/router/utils/parse-url' import * as Log from '../build/output/log' import BaseServer, { @@ -1009,11 +1009,11 @@ export default class NextNodeServer extends BaseServer { const rewritePath = result.response.headers.get( 'x-middleware-rewrite' )! - const { newUrl, parsedDestination } = prepareDestination({ - appendParamsToQuery: false, - destination: rewritePath, - params: _params, + const parsedDestination = parseUrl(rewritePath) + const newUrl = formatUrl({ + ...parsedDestination, query: {}, + search: '', }) // TODO: remove after next minor version current `v12.0.9` diff --git a/test/integration/middleware/core/pages/rewrites/_middleware.js b/test/integration/middleware/core/pages/rewrites/_middleware.js index 11369425e5d0..446ec5709648 100644 --- a/test/integration/middleware/core/pages/rewrites/_middleware.js +++ b/test/integration/middleware/core/pages/rewrites/_middleware.js @@ -32,11 +32,13 @@ export async function middleware(request) { } if (url.pathname === '/rewrites/rewrite-me-with-a-colon') { - return NextResponse.rewrite('/rewrites/with:colon') + url.pathname = '/rewrites/with:colon' + return NextResponse.rewrite(url) } if (url.pathname === '/rewrites/colon:here') { - return NextResponse.rewrite('/rewrites/no-colon-here') + url.pathname = '/rewrites/no-colon-here' + return NextResponse.rewrite(url) } if (url.pathname === '/rewrites/rewrite-me-to-vercel') { From aef10751769edc9984930cacad2a70256e26163e Mon Sep 17 00:00:00 2001 From: io Date: Thu, 3 Mar 2022 00:49:35 +0100 Subject: [PATCH 11/11] It looks like newUrl should contain only pathname Co-authored-by: Naoyuki Kanezawa --- packages/next/server/next-server.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index ca94a1fa8ff4..65876d9f2fdb 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -1132,11 +1132,7 @@ export default class NextNodeServer extends BaseServer { 'x-middleware-rewrite' )! const parsedDestination = parseUrl(rewritePath) - const newUrl = formatUrl({ - ...parsedDestination, - query: {}, - search: '', - }) + const newUrl = parsedDestination.pathname // TODO: remove after next minor version current `v12.0.9` this.warnIfQueryParametersWereDeleted(