mirror of
https://github.com/withastro/astro.git
synced 2025-03-10 23:01:26 -05:00
fix: correct handling of collapsing slashes (#13130)
This commit is contained in:
parent
c497491cfe
commit
b71bd10989
7 changed files with 57 additions and 9 deletions
5
.changeset/afraid-kangaroos-hope.md
Normal file
5
.changeset/afraid-kangaroos-hope.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@astrojs/internal-helpers': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixes a bug that meant that internal as well as trailing duplicate slashes were collapsed
|
5
.changeset/pink-apes-invite.md
Normal file
5
.changeset/pink-apes-invite.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixes a bug that caused duplicate slashes inside query params to be collapsed
|
|
@ -9,15 +9,10 @@ export function trailingSlashMiddleware(settings: AstroSettings): vite.Connect.N
|
||||||
const { trailingSlash } = settings.config;
|
const { trailingSlash } = settings.config;
|
||||||
|
|
||||||
return function devTrailingSlash(req, res, next) {
|
return function devTrailingSlash(req, res, next) {
|
||||||
const url = req.url!;
|
const url = new URL(`http://localhost${req.url}`);
|
||||||
|
|
||||||
const destination = collapseDuplicateTrailingSlashes(url, true);
|
|
||||||
if (url && destination !== url) {
|
|
||||||
return writeRedirectResponse(res, 301, destination);
|
|
||||||
}
|
|
||||||
let pathname: string;
|
let pathname: string;
|
||||||
try {
|
try {
|
||||||
pathname = decodeURI(new URL(url, 'http://localhost').pathname);
|
pathname = decodeURI(url.pathname);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
/* malformed uri */
|
/* malformed uri */
|
||||||
return next(e);
|
return next(e);
|
||||||
|
@ -25,6 +20,12 @@ export function trailingSlashMiddleware(settings: AstroSettings): vite.Connect.N
|
||||||
if (pathname.startsWith('/_') || pathname.startsWith('/@')) {
|
if (pathname.startsWith('/_') || pathname.startsWith('/@')) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const destination = collapseDuplicateTrailingSlashes(pathname, true);
|
||||||
|
if (pathname && destination !== pathname) {
|
||||||
|
return writeRedirectResponse(res, 301, `${destination}${url.search}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(trailingSlash === 'never' && pathname.endsWith('/') && pathname !== '/') ||
|
(trailingSlash === 'never' && pathname.endsWith('/') && pathname !== '/') ||
|
||||||
(trailingSlash === 'always' && !pathname.endsWith('/') && !hasFileExtension(pathname))
|
(trailingSlash === 'always' && !pathname.endsWith('/') && !hasFileExtension(pathname))
|
||||||
|
|
|
@ -60,6 +60,22 @@ describe('Development Routing', () => {
|
||||||
assert.equal(response.headers.get('Location'), '/');
|
assert.equal(response.headers.get('Location'), '/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not redirect multiple internal slashes', async () => {
|
||||||
|
const response = await fixture.fetch('/another///here', { redirect: 'manual' });
|
||||||
|
assert.equal(response.status, 404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not redirect slashes on query params', async () => {
|
||||||
|
const response = await fixture.fetch('/another?foo=bar///', { redirect: 'manual' });
|
||||||
|
assert.equal(response.status, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does redirect multiple trailing slashes with query params', async () => {
|
||||||
|
const response = await fixture.fetch('/another///?foo=bar///', { redirect: 'manual' });
|
||||||
|
assert.equal(response.status, 301);
|
||||||
|
assert.equal(response.headers.get('Location'), '/another/?foo=bar///');
|
||||||
|
});
|
||||||
|
|
||||||
it('404 when loading invalid dynamic route', async () => {
|
it('404 when loading invalid dynamic route', async () => {
|
||||||
const response = await fixture.fetch('/2');
|
const response = await fixture.fetch('/2');
|
||||||
assert.equal(response.status, 404);
|
assert.equal(response.status, 404);
|
||||||
|
|
|
@ -33,6 +33,28 @@ describe('Redirecting trailing slashes in SSR', () => {
|
||||||
assert.equal(response.headers.get('Location'), '/another/');
|
assert.equal(response.headers.get('Location'), '/another/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Redirects to collapse multiple trailing slashes with query param', async () => {
|
||||||
|
const app = await fixture.loadTestAdapterApp();
|
||||||
|
const request = new Request('http://example.com/another///?hello=world');
|
||||||
|
const response = await app.render(request);
|
||||||
|
assert.equal(response.status, 301);
|
||||||
|
assert.equal(response.headers.get('Location'), '/another/?hello=world');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Does not redirect to collapse multiple internal slashes', async () => {
|
||||||
|
const app = await fixture.loadTestAdapterApp();
|
||||||
|
const request = new Request('http://example.com/another///path/');
|
||||||
|
const response = await app.render(request);
|
||||||
|
assert.equal(response.status, 404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Does not redirect trailing slashes on query params', async () => {
|
||||||
|
const app = await fixture.loadTestAdapterApp();
|
||||||
|
const request = new Request('http://example.com/another/?hello=world///');
|
||||||
|
const response = await app.render(request);
|
||||||
|
assert.equal(response.status, 200);
|
||||||
|
});
|
||||||
|
|
||||||
it('Does not redirect when trailing slash is present', async () => {
|
it('Does not redirect when trailing slash is present', async () => {
|
||||||
const app = await fixture.loadTestAdapterApp();
|
const app = await fixture.loadTestAdapterApp();
|
||||||
const request = new Request('http://example.com/another/');
|
const request = new Request('http://example.com/another/');
|
|
@ -19,7 +19,7 @@ export function collapseDuplicateSlashes(path: string) {
|
||||||
return path.replace(/(?<!:)\/{2,}/g, '/');
|
return path.replace(/(?<!:)\/{2,}/g, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MANY_TRAILING_SLASHES = /\/{2,}/g;
|
export const MANY_TRAILING_SLASHES = /\/{2,}$/g;
|
||||||
|
|
||||||
export function collapseDuplicateTrailingSlashes(path: string, trailingSlash: boolean) {
|
export function collapseDuplicateTrailingSlashes(path: string, trailingSlash: boolean) {
|
||||||
if (!path) {
|
if (!path) {
|
||||||
|
|
1
pnpm-lock.yaml
generated
1
pnpm-lock.yaml
generated
|
@ -8938,7 +8938,6 @@ packages:
|
||||||
|
|
||||||
libsql@0.4.5:
|
libsql@0.4.5:
|
||||||
resolution: {integrity: sha512-sorTJV6PNt94Wap27Sai5gtVLIea4Otb2LUiAUyr3p6BPOScGMKGt5F1b5X/XgkNtcsDKeX5qfeBDj+PdShclQ==}
|
resolution: {integrity: sha512-sorTJV6PNt94Wap27Sai5gtVLIea4Otb2LUiAUyr3p6BPOScGMKGt5F1b5X/XgkNtcsDKeX5qfeBDj+PdShclQ==}
|
||||||
cpu: [x64, arm64, wasm32]
|
|
||||||
os: [darwin, linux, win32]
|
os: [darwin, linux, win32]
|
||||||
|
|
||||||
lightningcss-darwin-arm64@1.29.1:
|
lightningcss-darwin-arm64@1.29.1:
|
||||||
|
|
Loading…
Add table
Reference in a new issue