Skip to content

Commit 01395b8

Browse files
astro-factory[bot]factory[bot]matthewp
authored
Fix trailing-slash redirect truncating query strings that contain a second ? (#17964)
* fix(node): preserve full query string in trailing-slash redirects * test(node): cover query-string preservation on trailing-slash redirects with base (#17962) * test(node): parse Location header with exec() to satisfy lint (#17962) --------- Co-authored-by: factory[bot] <factory[bot]@users.noreply.github.com> Co-authored-by: Matthew Phillips <matthew@matthewphillips.info>
1 parent 37fd0cd commit 01395b8

3 files changed

Lines changed: 52 additions & 1 deletion

File tree

‎.changeset/tricky-sheep-relate.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@astrojs/node': patch
3+
---
4+
5+
Fixes trailing-slash redirects truncating query strings that contain a second `?` character

‎packages/integrations/node/src/serve-static.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ export function createStaticHandler(
5757
fullUrl = fullUrl.slice(0, req.url.indexOf('#'));
5858
}
5959

60-
const [urlPath, urlQuery] = fullUrl.split('?');
60+
const qIndex = fullUrl.indexOf('?');
61+
const urlPath = qIndex >= 0 ? fullUrl.slice(0, qIndex) : fullUrl;
62+
const urlQuery = qIndex >= 0 ? fullUrl.slice(qIndex + 1) : '';
6163
let fsPath = app.removeBase(urlPath);
6264
try {
6365
fsPath = decodeURI(fsPath);

‎packages/integrations/node/test/trailing-slash.test.ts‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,17 @@ describe('Trailing slash', () => {
8888
assert.equal(res.headers.get('location'), '/some-base/one/?foo=bar');
8989
});
9090

91+
it('Preserves query string containing a second ? in redirect', async () => {
92+
const res = await rawRequest(
93+
server.host ?? 'localhost',
94+
server.port,
95+
'/some-base/one?redirect=/x?y=1&z=2',
96+
);
97+
assert.match(res.statusLine, /301/);
98+
const location = /^location:(.*)$/im.exec(res.head)?.[1]?.trim();
99+
assert.equal(location, '/some-base/one/?redirect=/x?y=1&z=2');
100+
});
101+
91102
it('Can render prerendered route with query params', async () => {
92103
const res = await fetch(`http://${server.host}:${server.port}/some-base/one/?foo=bar`);
93104
const html = await res.text();
@@ -164,6 +175,17 @@ describe('Trailing slash', () => {
164175
assert.equal(res.headers.get('location'), '/one/?foo=bar');
165176
});
166177

178+
it('Preserves query string containing a second ? in redirect', async () => {
179+
const res = await rawRequest(
180+
server.host ?? 'localhost',
181+
server.port,
182+
'/one?redirect=/x?y=1&z=2',
183+
);
184+
assert.match(res.statusLine, /301/);
185+
const location = /^location:(.*)$/im.exec(res.head)?.[1]?.trim();
186+
assert.equal(location, '/one/?redirect=/x?y=1&z=2');
187+
});
188+
167189
it('Can render prerendered route with query params', async () => {
168190
const res = await fetch(`http://${server.host}:${server.port}/one/?foo=bar`);
169191
const html = await res.text();
@@ -296,6 +318,17 @@ describe('Trailing slash', () => {
296318
assert.equal(res.headers.get('location'), '/some-base/one?foo=bar');
297319
});
298320

321+
it('Preserves query string containing a second ? in redirect', async () => {
322+
const res = await rawRequest(
323+
server.host ?? 'localhost',
324+
server.port,
325+
'/some-base/one/?redirect=/x?y=1&z=2',
326+
);
327+
assert.match(res.statusLine, /301/);
328+
const location = /^location:(.*)$/im.exec(res.head)?.[1]?.trim();
329+
assert.equal(location, '/some-base/one?redirect=/x?y=1&z=2');
330+
});
331+
299332
it('Can render prerendered route with query params', async () => {
300333
const res = await fetch(`http://${server.host}:${server.port}/some-base/one?foo=bar`);
301334
const html = await res.text();
@@ -354,6 +387,17 @@ describe('Trailing slash', () => {
354387
assert.equal(res.headers.get('location'), '/one?foo=bar');
355388
});
356389

390+
it('Preserves query string containing a second ? in redirect', async () => {
391+
const res = await rawRequest(
392+
server.host ?? 'localhost',
393+
server.port,
394+
'/one/?redirect=/x?y=1&z=2',
395+
);
396+
assert.match(res.statusLine, /301/);
397+
const location = /^location:(.*)$/im.exec(res.head)?.[1]?.trim();
398+
assert.equal(location, '/one?redirect=/x?y=1&z=2');
399+
});
400+
357401
it('Can render prerendered route and query params', async () => {
358402
const res = await fetch(`http://${server.host}:${server.port}/one?foo=bar`);
359403
const html = await res.text();

0 commit comments

Comments
 (0)