From 03365886f1b8a87cc14c825ac8cf3829a05e472d Mon Sep 17 00:00:00 2001 From: Seth Falco Date: Tue, 29 Mar 2022 18:06:39 +0100 Subject: [PATCH] bug: allow scheme-relative url in links --- packages/next/shared/lib/router/router.ts | 16 +++++---- .../repeated-slashes/app/pages/invalid.js | 36 ++++++++++--------- .../repeated-slashes/test/index.test.js | 28 +++++++-------- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts index 2816ab180e804a..7c78d209082177 100644 --- a/packages/next/shared/lib/router/router.ts +++ b/packages/next/shared/lib/router/router.ts @@ -214,10 +214,14 @@ export function delBasePath(path: string): string { */ export function isLocalURL(url: string): boolean { // prevent a hydration mismatch on href for url with anchor refs - if (url.startsWith('/') || url.startsWith('#') || url.startsWith('?')) + if ( + url.match(/^\/(?!\/)/) !== null || + url.startsWith('#') || + url.startsWith('?') + ) return true try { - // absolute urls can be local if they are on the same origin + // absolute and scheme-relative urls can be local if they are on the same origin const locationOrigin = getLocationOrigin() const resolved = new URL(url, locationOrigin) return resolved.origin === locationOrigin && hasBasePath(resolved.pathname) @@ -318,9 +322,9 @@ export function resolveHref( // repeated slashes and backslashes in the URL are considered // invalid and will never match a Next.js page/file - const urlProtoMatch = urlAsString.match(/^[a-zA-Z]{1,}:\/\//) - const urlAsStringNoProto = urlProtoMatch - ? urlAsString.slice(urlProtoMatch[0].length) + const urlSchemeMatch = urlAsString.match(/^(?:[a-zA-Z]+:)?\/\//) + const urlAsStringNoProto = urlSchemeMatch + ? urlAsString.slice(urlSchemeMatch[0].length) : urlAsString const urlParts = urlAsStringNoProto.split('?') @@ -330,7 +334,7 @@ export function resolveHref( `Invalid href passed to next/router: ${urlAsString}, repeated forward-slashes (//) or backslashes \\ are not valid in the href` ) const normalizedUrl = normalizeRepeatedSlashes(urlAsStringNoProto) - urlAsString = (urlProtoMatch ? urlProtoMatch[0] : '') + normalizedUrl + urlAsString = (urlSchemeMatch ? urlSchemeMatch[0] : '') + normalizedUrl } // Return because it cannot be routed by the Next.js router diff --git a/test/integration/repeated-slashes/app/pages/invalid.js b/test/integration/repeated-slashes/app/pages/invalid.js index 907fd59f457a43..8586781ca2f791 100644 --- a/test/integration/repeated-slashes/app/pages/invalid.js +++ b/test/integration/repeated-slashes/app/pages/invalid.js @@ -14,43 +14,47 @@ export default function Invalid() { return ( <>

invalid page

- - to /another as //google.com + + to /another as /google.com//maps
- - to //google.com + + to /google.com//maps
- - to //google.com?hello=1 + + to /google.com//maps?hello=1
- - to //google.com#hello + + to /google.com//maps#hello
- - to /another as \\/\\/google.com + + + to /another as \\/google.com\\/\\/maps +
- - to \\/\\/google.com + + to \\/google.com\\/\\/maps
- - to \\/\\/google.com?hello=1 + + + to \\/google.com\\/\\/maps?hello=1 +
- - to \\/\\/google.com#hello + + to \\/google.com\\/\\/maps#hello
diff --git a/test/integration/repeated-slashes/test/index.test.js b/test/integration/repeated-slashes/test/index.test.js index 347550441580a6..8d68e326e4898b 100644 --- a/test/integration/repeated-slashes/test/index.test.js +++ b/test/integration/repeated-slashes/test/index.test.js @@ -289,12 +289,12 @@ function runTests({ isDev = false, isExport = false, isPages404 = false }) { `/invalid${isExport ? '.html' : ''}` ) const invalidHrefs = [ - '//google.com', - '//google.com?hello=1', - '//google.com#hello', - '\\/\\/google.com', - '\\/\\/google.com?hello=1', - '\\/\\/google.com#hello', + '/google.com//maps', + '/google.com//maps?hello=1', + '/google.com//maps#hello', + '\\/google.com\\/\\/maps', + '\\/google.com\\/\\/maps?hello=1', + '\\/google.com\\/\\/maps#hello', ] for (const href of invalidHrefs) { @@ -313,24 +313,24 @@ function runTests({ isDev = false, isExport = false, isPages404 = false }) { { page: '/another', href: '/another', - as: '//google.com', - pathname: '/google.com', + as: '/google.com//maps', + pathname: '/google.com/maps', }, { page: isPages404 ? '/404' : '/_error', - href: '//google.com', - pathname: '/google.com', + href: '/google.com//maps', + pathname: '/google.com/maps', }, { page: isPages404 ? '/404' : '/_error', - href: '//google.com?hello=1', - pathname: '/google.com', + href: '/google.com//maps?hello=1', + pathname: '/google.com/maps', search: '?hello=1', }, { page: isPages404 ? '/404' : '/_error', - href: '//google.com#hello', - pathname: '/google.com', + href: '/google.com//maps#hello', + pathname: '/google.com/maps', hash: '#hello', }, ]) {