From da94af3086548c904159f4acb4e1f4fe59eb5e0c Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Thu, 12 Aug 2021 14:51:50 -0700 Subject: [PATCH] [fix] handle paths consistently between dev and various production adapters (#2171) * [fix] handle paths consistently between dev and various production adapters * decode only once --- .changeset/brave-seas-invent.md | 6 ++++++ packages/adapter-node/package.json | 1 + packages/adapter-node/src/server.js | 10 +++++++++- packages/adapter-node/tests/smoke.js | 14 ++++++++++++++ packages/kit/src/core/dev/index.js | 2 +- packages/kit/src/core/preview/index.js | 2 +- packages/kit/src/runtime/client/router.js | 5 +++-- packages/kit/src/runtime/server/index.js | 4 ++-- .../test/apps/basics/src/routes/encoded/_tests.js | 12 ++++++------ pnpm-lock.yaml | 2 ++ 10 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 .changeset/brave-seas-invent.md diff --git a/.changeset/brave-seas-invent.md b/.changeset/brave-seas-invent.md new file mode 100644 index 000000000000..5395d5219a5c --- /dev/null +++ b/.changeset/brave-seas-invent.md @@ -0,0 +1,6 @@ +--- +'@sveltejs/adapter-node': patch +'@sveltejs/kit': patch +--- + +[fix] handle paths consistently between dev and various production adapters diff --git a/packages/adapter-node/package.json b/packages/adapter-node/package.json index e2a801a649f1..c8fbd0892109 100644 --- a/packages/adapter-node/package.json +++ b/packages/adapter-node/package.json @@ -25,6 +25,7 @@ "tiny-glob": "^0.2.9" }, "devDependencies": { + "@polka/url": "^1.0.0-next.15", "@rollup/plugin-json": "^4.1.0", "@sveltejs/kit": "workspace:*", "c8": "^7.7.2", diff --git a/packages/adapter-node/src/server.js b/packages/adapter-node/src/server.js index 4dc6c69aa209..0e538042935c 100644 --- a/packages/adapter-node/src/server.js +++ b/packages/adapter-node/src/server.js @@ -3,6 +3,7 @@ import compression from 'compression'; import fs from 'fs'; import { dirname, join } from 'path'; import polka from 'polka'; +import { parse } from '@polka/url'; import sirv from 'sirv'; import { fileURLToPath } from 'url'; @@ -38,7 +39,14 @@ export function createServer({ render }) { }) : noop_handler; - const server = polka().use( + const server = polka(); + // Polka has a non-standard behavior of decoding the request path + // Disable it so that adapter-node works just like the rest + // SvelteKit will handle decoding URI components into req.params + server.parse = (req) => { + return parse(req, false); + }; + server.use( compression({ threshold: 0 }), assets_handler, prerendered_handler, diff --git a/packages/adapter-node/tests/smoke.js b/packages/adapter-node/tests/smoke.js index c370dccc42b8..6a631ccdc9e8 100644 --- a/packages/adapter-node/tests/smoke.js +++ b/packages/adapter-node/tests/smoke.js @@ -46,4 +46,18 @@ test('responses with the rendered status code', async () => { server.server.close(); }); +test('passes through umlaut as encoded path', async () => { + const server = await startServer({ + render: (incoming) => { + return { + status: 200, + body: incoming.path + }; + } + }); + const res = await fetch(`http://localhost:${PORT}/%C3%BCber-uns`); + assert.equal(await res.text(), '/%C3%BCber-uns'); + server.server.close(); +}); + test.run(); diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index 7f2fc1a5f71d..90ffc1dbb0b7 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -330,7 +330,7 @@ async function create_handler(vite, config, dir, cwd, manifest) { headers: /** @type {import('types/helper').Headers} */ (req.headers), method: req.method, host, - path: decodeURI(parsed.pathname), + path: parsed.pathname, query: parsed.searchParams, rawBody: body }, diff --git a/packages/kit/src/core/preview/index.js b/packages/kit/src/core/preview/index.js index cf965ab938cd..facbbe5cf702 100644 --- a/packages/kit/src/core/preview/index.js +++ b/packages/kit/src/core/preview/index.js @@ -83,7 +83,7 @@ export async function preview({ req.headers[config.kit.hostHeader || 'host']), method: req.method, headers: /** @type {import('types/helper').Headers} */ (req.headers), - path: parsed.pathname ? decodeURIComponent(parsed.pathname) : '', + path: parsed.pathname ? parsed.pathname : '', query: new URLSearchParams(parsed.query || ''), rawBody: body }); diff --git a/packages/kit/src/runtime/client/router.js b/packages/kit/src/runtime/client/router.js index 2df516f4d61e..37a6e59be4da 100644 --- a/packages/kit/src/runtime/client/router.js +++ b/packages/kit/src/runtime/client/router.js @@ -168,9 +168,10 @@ export class Router { */ parse(url) { if (this.owns(url)) { - const path = decodeURIComponent(url.pathname.slice(this.base.length) || '/'); + const path = url.pathname.slice(this.base.length) || '/'; - const routes = this.routes.filter(([pattern]) => pattern.test(path)); + const decoded = decodeURI(path); + const routes = this.routes.filter(([pattern]) => pattern.test(decoded)); const query = new URLSearchParams(url.search); const id = `${path}?${query}`; diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 6eeb18dca236..dfcc1c3acf71 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -28,7 +28,7 @@ export async function respond(incoming, options, state = {}) { return { status: 301, headers: { - location: encodeURI(path + (q ? `?${q}` : '')) + location: path + (q ? `?${q}` : '') } }; } @@ -57,7 +57,7 @@ export async function respond(incoming, options, state = {}) { } for (const route of options.manifest.routes) { - if (!route.pattern.test(request.path)) continue; + if (!route.pattern.test(decodeURI(request.path))) continue; const response = route.type === 'endpoint' diff --git a/packages/kit/test/apps/basics/src/routes/encoded/_tests.js b/packages/kit/test/apps/basics/src/routes/encoded/_tests.js index 2d799cb75cbc..cb33123d204c 100644 --- a/packages/kit/test/apps/basics/src/routes/encoded/_tests.js +++ b/packages/kit/test/apps/basics/src/routes/encoded/_tests.js @@ -5,8 +5,8 @@ export default function (test) { test('visits a route with non-ASCII character', '/encoded', async ({ page, clicknav }) => { await clicknav('[href="/encoded/苗条"]'); assert.equal(await page.innerHTML('h1'), 'static'); - assert.equal(await page.innerHTML('h2'), '/encoded/苗条'); - assert.equal(await page.innerHTML('h3'), '/encoded/苗条'); + assert.equal(decodeURI(await page.innerHTML('h2')), '/encoded/苗条'); + assert.equal(decodeURI(await page.innerHTML('h3')), '/encoded/苗条'); }); test( @@ -15,8 +15,8 @@ export default function (test) { async ({ page, clicknav }) => { await clicknav('[href="/encoded/土豆"]'); assert.equal(await page.innerHTML('h1'), 'dynamic'); - assert.equal(await page.innerHTML('h2'), '/encoded/土豆: 土豆'); - assert.equal(await page.innerHTML('h3'), '/encoded/土豆: 土豆'); + assert.equal(decodeURI(await page.innerHTML('h2')), '/encoded/土豆: 土豆'); + assert.equal(decodeURI(await page.innerHTML('h3')), '/encoded/土豆: 土豆'); } ); @@ -24,8 +24,8 @@ export default function (test) { await clicknav('[href="/encoded/反应"]'); assert.equal(await page.innerHTML('h1'), 'static'); - assert.equal(await page.innerHTML('h2'), '/encoded/苗条'); - assert.equal(await page.innerHTML('h3'), '/encoded/苗条'); + assert.equal(decodeURI(await page.innerHTML('h2')), '/encoded/苗条'); + assert.equal(decodeURI(await page.innerHTML('h3')), '/encoded/苗条'); }); test('sets charset on JSON Content-Type', null, async ({ fetch }) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f57e6c1967a..81ac37d79b99 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,6 +88,7 @@ importers: packages/adapter-node: specifiers: + '@polka/url': ^1.0.0-next.15 '@rollup/plugin-json': ^4.1.0 '@sveltejs/kit': workspace:* c8: ^7.7.2 @@ -103,6 +104,7 @@ importers: esbuild: 0.12.5 tiny-glob: 0.2.9 devDependencies: + '@polka/url': 1.0.0-next.15 '@rollup/plugin-json': 4.1.0_rollup@2.55.0 '@sveltejs/kit': link:../kit c8: 7.7.2