Skip to content

Commit da94af3

Browse files
authored
[fix] handle paths consistently between dev and various production adapters (#2171)
* [fix] handle paths consistently between dev and various production adapters * decode only once
1 parent 2f8f518 commit da94af3

File tree

10 files changed

+45
-13
lines changed

10 files changed

+45
-13
lines changed

.changeset/brave-seas-invent.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@sveltejs/adapter-node': patch
3+
'@sveltejs/kit': patch
4+
---
5+
6+
[fix] handle paths consistently between dev and various production adapters

packages/adapter-node/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"tiny-glob": "^0.2.9"
2626
},
2727
"devDependencies": {
28+
"@polka/url": "^1.0.0-next.15",
2829
"@rollup/plugin-json": "^4.1.0",
2930
"@sveltejs/kit": "workspace:*",
3031
"c8": "^7.7.2",

packages/adapter-node/src/server.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import compression from 'compression';
33
import fs from 'fs';
44
import { dirname, join } from 'path';
55
import polka from 'polka';
6+
import { parse } from '@polka/url';
67
import sirv from 'sirv';
78
import { fileURLToPath } from 'url';
89

@@ -38,7 +39,14 @@ export function createServer({ render }) {
3839
})
3940
: noop_handler;
4041

41-
const server = polka().use(
42+
const server = polka();
43+
// Polka has a non-standard behavior of decoding the request path
44+
// Disable it so that adapter-node works just like the rest
45+
// SvelteKit will handle decoding URI components into req.params
46+
server.parse = (req) => {
47+
return parse(req, false);
48+
};
49+
server.use(
4250
compression({ threshold: 0 }),
4351
assets_handler,
4452
prerendered_handler,

packages/adapter-node/tests/smoke.js

+14
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,18 @@ test('responses with the rendered status code', async () => {
4646
server.server.close();
4747
});
4848

49+
test('passes through umlaut as encoded path', async () => {
50+
const server = await startServer({
51+
render: (incoming) => {
52+
return {
53+
status: 200,
54+
body: incoming.path
55+
};
56+
}
57+
});
58+
const res = await fetch(`http://localhost:${PORT}/%C3%BCber-uns`);
59+
assert.equal(await res.text(), '/%C3%BCber-uns');
60+
server.server.close();
61+
});
62+
4963
test.run();

packages/kit/src/core/dev/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ async function create_handler(vite, config, dir, cwd, manifest) {
330330
headers: /** @type {import('types/helper').Headers} */ (req.headers),
331331
method: req.method,
332332
host,
333-
path: decodeURI(parsed.pathname),
333+
path: parsed.pathname,
334334
query: parsed.searchParams,
335335
rawBody: body
336336
},

packages/kit/src/core/preview/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export async function preview({
8383
req.headers[config.kit.hostHeader || 'host']),
8484
method: req.method,
8585
headers: /** @type {import('types/helper').Headers} */ (req.headers),
86-
path: parsed.pathname ? decodeURIComponent(parsed.pathname) : '',
86+
path: parsed.pathname ? parsed.pathname : '',
8787
query: new URLSearchParams(parsed.query || ''),
8888
rawBody: body
8989
});

packages/kit/src/runtime/client/router.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,10 @@ export class Router {
168168
*/
169169
parse(url) {
170170
if (this.owns(url)) {
171-
const path = decodeURIComponent(url.pathname.slice(this.base.length) || '/');
171+
const path = url.pathname.slice(this.base.length) || '/';
172172

173-
const routes = this.routes.filter(([pattern]) => pattern.test(path));
173+
const decoded = decodeURI(path);
174+
const routes = this.routes.filter(([pattern]) => pattern.test(decoded));
174175

175176
const query = new URLSearchParams(url.search);
176177
const id = `${path}?${query}`;

packages/kit/src/runtime/server/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export async function respond(incoming, options, state = {}) {
2828
return {
2929
status: 301,
3030
headers: {
31-
location: encodeURI(path + (q ? `?${q}` : ''))
31+
location: path + (q ? `?${q}` : '')
3232
}
3333
};
3434
}
@@ -57,7 +57,7 @@ export async function respond(incoming, options, state = {}) {
5757
}
5858

5959
for (const route of options.manifest.routes) {
60-
if (!route.pattern.test(request.path)) continue;
60+
if (!route.pattern.test(decodeURI(request.path))) continue;
6161

6262
const response =
6363
route.type === 'endpoint'

packages/kit/test/apps/basics/src/routes/encoded/_tests.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ export default function (test) {
55
test('visits a route with non-ASCII character', '/encoded', async ({ page, clicknav }) => {
66
await clicknav('[href="/encoded/苗条"]');
77
assert.equal(await page.innerHTML('h1'), 'static');
8-
assert.equal(await page.innerHTML('h2'), '/encoded/苗条');
9-
assert.equal(await page.innerHTML('h3'), '/encoded/苗条');
8+
assert.equal(decodeURI(await page.innerHTML('h2')), '/encoded/苗条');
9+
assert.equal(decodeURI(await page.innerHTML('h3')), '/encoded/苗条');
1010
});
1111

1212
test(
@@ -15,17 +15,17 @@ export default function (test) {
1515
async ({ page, clicknav }) => {
1616
await clicknav('[href="/encoded/土豆"]');
1717
assert.equal(await page.innerHTML('h1'), 'dynamic');
18-
assert.equal(await page.innerHTML('h2'), '/encoded/土豆: 土豆');
19-
assert.equal(await page.innerHTML('h3'), '/encoded/土豆: 土豆');
18+
assert.equal(decodeURI(await page.innerHTML('h2')), '/encoded/土豆: 土豆');
19+
assert.equal(decodeURI(await page.innerHTML('h3')), '/encoded/土豆: 土豆');
2020
}
2121
);
2222

2323
test('redirects correctly with non-ASCII location', '/encoded', async ({ page, clicknav }) => {
2424
await clicknav('[href="/encoded/反应"]');
2525

2626
assert.equal(await page.innerHTML('h1'), 'static');
27-
assert.equal(await page.innerHTML('h2'), '/encoded/苗条');
28-
assert.equal(await page.innerHTML('h3'), '/encoded/苗条');
27+
assert.equal(decodeURI(await page.innerHTML('h2')), '/encoded/苗条');
28+
assert.equal(decodeURI(await page.innerHTML('h3')), '/encoded/苗条');
2929
});
3030

3131
test('sets charset on JSON Content-Type', null, async ({ fetch }) => {

pnpm-lock.yaml

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)