Skip to content

Commit c9d8db1

Browse files
committed
Preserve slashes when custom URL schemes are used in redirects
1 parent 86fef73 commit c9d8db1

File tree

6 files changed

+48
-1
lines changed

6 files changed

+48
-1
lines changed

packages/next/src/shared/lib/router/utils/parse-relative-url.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface ParsedRelativeUrl {
88
pathname: string
99
query: ParsedUrlQuery
1010
search: string
11+
slashes: undefined
1112
}
1213

1314
/**
@@ -58,5 +59,8 @@ export function parseRelativeUrl(
5859
search,
5960
hash,
6061
href: href.slice(origin.length),
62+
// We don't know for relative URLs at this point since we set a custom, internal
63+
// base that isn't surfaced to users.
64+
slashes: undefined,
6165
}
6266
}

packages/next/src/shared/lib/router/utils/parse-url.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface ParsedUrl {
1212
protocol?: string | null
1313
query: ParsedUrlQuery
1414
search: string
15+
slashes: boolean | undefined
1516
}
1617

1718
export function parseUrl(url: string): ParsedUrl {
@@ -29,5 +30,10 @@ export function parseUrl(url: string): ParsedUrl {
2930
protocol: parsedURL.protocol,
3031
query: searchParamsToUrlQuery(parsedURL.searchParams),
3132
search: parsedURL.search,
33+
slashes:
34+
parsedURL.href.slice(
35+
parsedURL.protocol.length,
36+
parsedURL.protocol.length + 2
37+
) === '//',
3238
}
3339
}

packages/next/src/shared/lib/router/utils/prepare-destination.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('parseDestination', () => {
2020
"pathname": "/hello/:name",
2121
"query": {},
2222
"search": "",
23+
"slashes": undefined,
2324
}
2425
`)
2526
})
@@ -45,6 +46,7 @@ describe('parseDestination', () => {
4546
"protocol": "https:",
4647
"query": {},
4748
"search": "",
49+
"slashes": true,
4850
}
4951
`)
5052
})
@@ -72,6 +74,7 @@ describe('parseDestination', () => {
7274
"foo": ":bar",
7375
},
7476
"search": "?foo=:bar",
77+
"slashes": true,
7578
}
7679
`)
7780
})

test/development/basic/misc.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe.each([[''], ['/docs']])(
5959
}
6060
})
6161

62-
it('should handle encoded / value for trailing slash correctly', async () => {
62+
fit('should handle encoded / value for trailing slash correctly', async () => {
6363
const res = await fetchViaHTTP(
6464
next.url,
6565
basePath + '/%2fexample.com/',

test/e2e/app-dir/rewrites-redirects/next.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@ module.exports = {
2020
destination: '/config-redirect-catchall-after/:path*',
2121
permanent: true,
2222
},
23+
{
24+
source: '/config-redirect-itms-apps-slashes',
25+
destination:
26+
'itms-apps://apps.apple.com/de/app/xcode/id497799835?l=en-GB&mt=12',
27+
permanent: true,
28+
},
29+
{
30+
source: '/config-redirect-itms-apps-no-slashes',
31+
destination:
32+
'itms-apps:apps.apple.com/de/app/xcode/id497799835?l=en-GB&mt=12',
33+
permanent: true,
34+
},
2335
]
2436
},
2537
}

test/e2e/app-dir/rewrites-redirects/rewrites-redirects.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,26 @@ describe('redirects and rewrites', () => {
7878
expect(url.pathname).toEndWith('-after')
7979
})
8080
})
81+
82+
it('redirects to exotic url schemes preserving slashes', async () => {
83+
const response = await next.fetch('/config-redirect-itms-apps-slashes', {
84+
redirect: 'manual',
85+
})
86+
87+
expect(response.headers.get('location')).toEqual(
88+
'itms-apps://apps.apple.com/de/app/xcode/id497799835?l=en-GB&mt=12'
89+
)
90+
expect(response.status).toBe(308)
91+
})
92+
93+
it('redirects to exotic url schemes without adding unwanted slashes', async () => {
94+
const response = await next.fetch('/config-redirect-itms-apps-no-slashes', {
95+
redirect: 'manual',
96+
})
97+
98+
expect(response.headers.get('location')).toEqual(
99+
'itms-apps:apps.apple.com/de/app/xcode/id497799835?l=en-GB&mt=12'
100+
)
101+
expect(response.status).toBe(308)
102+
})
81103
})

0 commit comments

Comments
 (0)