Skip to content

Commit

Permalink
bugfix: Remove invalid pathname slashes in getURL() utility
Browse files Browse the repository at this point in the history
  • Loading branch information
takueof committed Oct 14, 2024
1 parent 719de07 commit 6a78b62
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 1 deletion.
3 changes: 2 additions & 1 deletion packages/next/src/shared/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@ export function getLocationOrigin() {
export function getURL() {
const { href } = window.location
const origin = getLocationOrigin()
return href.substring(origin.length)
const pathname = href.substring(origin.length)
return pathname.replace(/^\/{2,}/, '/')
}

export function getDisplayName<P>(Component: ComponentType<P>) {
Expand Down
28 changes: 28 additions & 0 deletions test/e2e/invalid-url-slash/certificates/localhost-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC2skb3RwQBKtJL
kLfxmPMxAZG4o6R4m9ipcMJtpB6im6di7g+fZzRP+j0AtOnnIaM6ZuT61nCTZKd7
RB/wlrhgEBkIqEGJtJh93Z0An3J2tt7YnBKsJNBP5BqqLD2TDyFGgTjfKdXJObSr
t67IiRtrKdT6VgRtM2gTM+d1lvL5IVc9JiTOeK9QjzVmDI2MIprzkFrQPI7G+UDq
P80IlrRX2mj30g+rIg6ma6GcnG0Wrk0csaghuAQSiLUqoGU3TzG7QK95sMS48xrv
0tpIyIWlWTTr17ntOIWA1cAGKUQadeVc2No/VRJbMxIcjmtDUc1e66c46z/KTgHo
2vSzjWVNAgMBAAECggEACuIT2Cci1e73GAlG691wnzq4s4cMBSNDhNRywJVGPemH
zxzfUV+Ufi8p8yDTzjDyyEfY3BhqHF2inHUyceKImTBcTWe4f7uCWf0ZnS/iYbAD
FmQ1uIt43Ul5TSnVgS0ljk2kVaboVVRaruACSW/hckDLrx3wpZCqYnp1D0wurSh1
hQJsb2Opy5wve+hEUGr8AYlolB2SyQLtdWkwyNTmtDeb/cIM/NKXuWDECBrgEN8y
v6fluRe4YkLqzTOJvMr4xNtA9wshO8qcNWy6T5YaiNRJJmzBq2V3d41UuYRcih2K
jSRfn9xZvGPWZ92+Jce15pZY5+ec9A1PuT6J8304AQKBgQDbyWoPZxE6yEXDWz4+
eFEvPGlVR8KiZhJl78iC1b9gW+FNlmd/LlamRB21IGfpoJgoRyjFP6UJ/tqGx79x
c+7krcBFCOfckYhRGCLxFdK2O0FTQ8ltfqaSYG4jeTSPVDaxhMYh/H3xjls9i7Y7
90sWDjE/uSFuT+ExmNZvvVtjdQKBgQDUzGToK5gqFpVO43pcJbM6odBTVbZSxDMF
cq+KEOT/c7CzKOOQj7UTqiQgXM0odiJ/I4ZvaIlv+s7l/Cr9LyII3gZUQdPnszsM
/N8kMeJC5Oy8O5pboo+p4SOosRTVZhxGB1tTQxqyECZV5Y5ZCH6bGV4cEqO6rjYC
Pkunc6B3eQKBgCLmtg/iFwtVmDZwe87hvkqY9kUTkyXEvbEwRY/5L1222W0/sAmz
KxFWCb2kervPw7nJqwC/nY6byMnUWGNEvK/Vo42S33bYKWRvR8Uu6PoFKNd3ETpw
/TSLWZIKgj0sa07/PZNSDBHawERitjqJh4PmFw3+cP+acbE1iv/NewCtAoGAWOzt
QiRtmzECxgvDp1xN0LOsNhb8cQvyclVhy+WRfLrg3Y25w0B6oDQakreVOFJdyhmT
ZV0fCf+alHtTj6gxpdj6dh1oK0w34g6ORTbfYar+zw5tS9vcA1bFKwqNNTxNlmoe
nOXO8xhSnNSoLsag+bmZHUwgxbNleHyF6v0j0qkCgYAklO2RDc2RDccXYAe6MbT7
EYExvS+K09CF4PVPoFk1QZxENuXfewDli9UCwt+uJvVacJOnUyLjMkkooibTTk+A
xf/dAp5ECrTHny/cMSerFJEgVZYsH06m+0a+RP+zL45skm/EsidOvIS80OkRr+Vz
rgIUqg2F14h2H2vbBlqrqQ==
-----END PRIVATE KEY-----
25 changes: 25 additions & 0 deletions test/e2e/invalid-url-slash/certificates/localhost.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEKDCCApCgAwIBAgIRAOCKMC740uI5i7wASQ65kkgwDQYJKoZIhvcNAQELBQAw
bTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSEwHwYDVQQLDBhqakBz
bGF0ZS5sYW4gKEpKIEthc3BlcikxKDAmBgNVBAMMH21rY2VydCBqakBzbGF0ZS5s
YW4gKEpKIEthc3BlcikwHhcNMjMwMTI2MDQyMjA2WhcNMjUwNDI2MDMyMjA2WjBM
MScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxITAfBgNV
BAsMGGpqQHNsYXRlLmxhbiAoSkogS2FzcGVyKTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBALayRvdHBAEq0kuQt/GY8zEBkbijpHib2Klwwm2kHqKbp2Lu
D59nNE/6PQC06echozpm5PrWcJNkp3tEH/CWuGAQGQioQYm0mH3dnQCfcna23tic
Eqwk0E/kGqosPZMPIUaBON8p1ck5tKu3rsiJG2sp1PpWBG0zaBMz53WW8vkhVz0m
JM54r1CPNWYMjYwimvOQWtA8jsb5QOo/zQiWtFfaaPfSD6siDqZroZycbRauTRyx
qCG4BBKItSqgZTdPMbtAr3mwxLjzGu/S2kjIhaVZNOvXue04hYDVwAYpRBp15VzY
2j9VElszEhyOa0NRzV7rpzjrP8pOAeja9LONZU0CAwEAAaNkMGIwDgYDVR0PAQH/
BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFOCz+XaB1lpu
T6P2UIfjbq7pUX9GMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG
9w0BAQsFAAOCAYEAaIPJdegZqcKys25yF5MYV10OyZ4wpP4gkONaA5oLfE/SeasH
cs1Af85oeGh6fCuszAoDCJHYvSsUqGhg/243Dkl993vGeLVikvC4R/LC/IkiwNKd
91byKnQBDghzeynyJiHO9yEiY4VFxUTThbttfDstDePOxHYxt8bLbIpGNZb0fdeh
opDwD9hhfL90A7sN7MdakBfQajpquyQgWvKGedaQx363WVXkssi4xRAIdm77ZzqA
Cy4WVrnRtvSbJNqYtNT0Ibx3gF7WC+ACh13lnniDjQlfcDJWxOVjYyvPx0k4M0KQ
+Qz/sx4RXy5jI9OO/hxJNNc3HAxU/7fLtnO/VtEb/c9A1m5gPUFU0W/EHL5VPJJ6
A6+/T8wK8hDEY4j+bMrysbOeTrbTyLmFXMGFkkSI7OjX0RHkgYBJclgBZfQYGrh1
PDmdZ26GSgt39k6VCY6ur7dXQuMPvQmRM6IqiPQpmd9SYP+FEiJh5TAOUBinI/Cu
hvseiJ2JQx+4YqxB
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions test/e2e/invalid-url-slash/invalid-url-slash-http.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { join } from 'node:path'
import webdriver from 'next-webdriver'
import { createNext, FileRef, type NextInstance } from 'e2e-utils'

// Pattern that does not cause error
describe('invalid HTTP URL slash', () => {
let next: NextInstance

beforeAll(async () => {
next = await createNext({
files: {
pages: new FileRef(join(__dirname, 'pages')),
},
})
})

afterAll(() => {
next.destroy()
})

it('should navigate from // to / correctly client-side', async () => {
const browser = await webdriver(next.url, '/')
// Change to "//"
await browser.elementByCss('button').click().waitForIdleNetwork()
const text = await browser.waitForElementByCss('h1', 100).text()
expect(text).toBe('index page')
})
})
70 changes: 70 additions & 0 deletions test/e2e/invalid-url-slash/invalid-url-slash-https.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import fs from 'node:fs'
import { join } from 'node:path'
import https from 'https'
import httpProxy from 'http-proxy'
import { findPort } from 'next-test-utils'
import webdriver from 'next-webdriver'
import { FileRef, nextTestSetup } from 'e2e-utils'

// Pattern that may cause error
describe('invalid HTTPS URL slash', () => {
let proxyPort: number
let proxyServer: https.Server

const { next, skipped } = nextTestSetup({
files: {
pages: new FileRef(join(__dirname, 'pages')),
},
// This test is skipped when deployed because it relies on a proxy server
skipDeployment: true,
})

if (skipped) return

beforeAll(async () => {
proxyPort = await findPort()

const ssl = {
key: fs.readFileSync(
join(__dirname, 'certificates/localhost-key.pem'),
'utf8'
),
cert: fs.readFileSync(
join(__dirname, 'certificates/localhost.pem'),
'utf8'
),
}

const proxy = httpProxy.createProxyServer({
target: `http://localhost:${next.appPort}`,
ssl,
secure: false,
})

proxyServer = https.createServer(ssl, async (req, res) => {
proxy.web(req, res)
})

proxy.on('error', (err) => {
throw new Error(`Failed to proxy: ${err.message}`)
})

await new Promise<void>((resolve) => {
proxyServer.listen(proxyPort, () => resolve())
})
})

afterAll(() => {
proxyServer.close()
})

it('should navigate from // to / correctly client-side', async () => {
const browser = await webdriver(`https://localhost:${proxyPort}`, '/', {
ignoreHTTPSErrors: true,
})
// Change to "//"
await browser.elementByCss('button').click().waitForIdleNetwork()
const text = await browser.waitForElementByCss('h1', 100).text()
expect(text).toBe('index page')
})
})
20 changes: 20 additions & 0 deletions test/e2e/invalid-url-slash/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client'

export default function Page() {
const goInvalidUrl = () => {
const { protocol, hostname, port } = location

location.href = `${protocol}//${hostname}${port ? ':' + port : ''}//`
}

return (
<>
<h1>index page</h1>
<p>
<button type="button" onClick={goInvalidUrl}>
Go invalid URL
</button>
</p>
</>
)
}
83 changes: 83 additions & 0 deletions test/unit/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* eslint-env jest */
import { getURL } from 'next/dist/shared/lib/utils'

describe('getURL', () => {
// Convenience function so tests can be aligned neatly
// and easy to eyeball
const check = (url: string, pathname: string) => {
global.window.location = new URL(url)

Check failure on line 8 in test/unit/utils.test.ts

View workflow job for this annotation

GitHub Actions / types and precompiled / build

Type 'URL' is not assignable to type 'Location | (string & Location)'.
const rootRelativeUrl = getURL()
expect(rootRelativeUrl).toBe(pathname)
}

beforeAll(() => {
global.window = {}

Check failure on line 14 in test/unit/utils.test.ts

View workflow job for this annotation

GitHub Actions / types and precompiled / build

Type '{}' is not assignable to type 'Window & typeof globalThis'.
})

afterAll(() => {
delete global.window
})

it('should get valid root path in HTTP', () => {
check('http://example.com:3210/', '/')
})

it('should get converted valid root path from invalid URL in HTTP', () => {
check('http://example.com:3210//', '/')
})

it('should get valid path in HTTP', () => {
check(
'http://example.com:3210/someA/pathB?fooC=barD#hashE',
'/someA/pathB?fooC=barD#hashE'
)
})

it('should get converted valid path from invalid URL in HTTP', () => {
check(
'http://example.com:3210//someA/pathB?fooC=barD#hashE',
'/someA/pathB?fooC=barD#hashE'
)
})

it('should get valid root path in HTTPS', () => {
check('https://example.com:3210/', '/')
})

it('should get converted valid root path from invalid URL in HTTPS', () => {
check('https://example.com:3210//', '/')
})

it('should get valid path in HTTPS', () => {
check(
'https://example.com:3210/someA/pathB?fooC=barD#hashE',
'/someA/pathB?fooC=barD#hashE'
)
})

it('should get converted valid path from invalid URL in HTTPS', () => {
check(
'https://example.com:3210//someA/pathB?fooC=barD#hashE',
'/someA/pathB?fooC=barD#hashE'
)
})

it('should get valid path on special protocol', () => {
check(
'ionic://localhost/someA/pathB?fooC=barD#hashE',
'/someA/pathB?fooC=barD#hashE'
)
check('file:///someA/pathB?fooC=barD#hashE', '/someA/pathB?fooC=barD#hashE')
})

it('should get valid path from invalid URL on special protocol', () => {
check(
'ionic://localhost//someA/pathB?fooC=barD#hashE',
'/someA/pathB?fooC=barD#hashE'
)
check(
'file:////someA/pathB?fooC=barD#hashE',
'/someA/pathB?fooC=barD#hashE'
)
})
})

0 comments on commit 6a78b62

Please sign in to comment.