diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts index 3c904c9..3d260e6 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -63,6 +63,8 @@ function generate_client( import root from '${get_file(path_to_routes, manifest_data.root)}'; import error from '${posixify(`${path_to_routes}/_error.html`)}'; + const d = decodeURIComponent; + ${manifest_data.components.map(component => { const annotation = bundler === 'webpack' ? `/* webpackChunkName: "${component.name}" */ ` @@ -88,7 +90,7 @@ function generate_client( if (part === null) return 'null'; if (part.params.length > 0) { - const props = part.params.map((param, i) => `${param}: match[${i + 1}]`); + const props = part.params.map((param, i) => `${param}: d(match[${i + 1}])`); return `{ component: ${part.component.name}, params: match => ({ ${props.join(', ')} }) }`; } @@ -138,6 +140,8 @@ function generate_server( // This file is generated by Sapper — do not edit it! ${imports.join('\n')} + const d = decodeURIComponent; + export const manifest = { server_routes: [ ${manifest_data.server_routes.map(route => `{ @@ -145,7 +149,7 @@ function generate_server( pattern: ${route.pattern}, handlers: ${route.name}, params: ${route.params.length > 0 - ? `match => ({ ${route.params.map((param, i) => `${param}: match[${i + 1}]`).join(', ')} })` + ? `match => ({ ${route.params.map((param, i) => `${param}: d(match[${i + 1}])`).join(', ')} })` : `() => ({})`} }`).join(',\n\n\t\t\t\t')} ], @@ -165,7 +169,7 @@ function generate_server( ]; if (part.params.length > 0) { - const params = part.params.map((param, i) => `${param}: match[${i + 1}]`); + const params = part.params.map((param, i) => `${param}: d(match[${i + 1}])`); props.push(`params: match => ({ ${params.join(', ')} })`); } diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 83c0a76..9530604 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -67,7 +67,7 @@ function select_route(url: URL): Target { if (url.search.length > 0) { url.search.slice(1).split('&').forEach(searchParam => { const [, key, value] = /([^=]+)=(.*)/.exec(searchParam); - query[key] = value || true; + query[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : true; }); } return { url, path, page, match, query }; diff --git a/test/app/app/client.js b/test/app/app/client.js index 2ca0ebe..d1c2e96 100644 --- a/test/app/app/client.js +++ b/test/app/app/client.js @@ -1,4 +1,4 @@ -import { init, prefetchRoutes } from '../../../runtime.js'; +import { init, goto, prefetchRoutes } from '../../../runtime.js'; import { Store } from 'svelte/store.js'; import { manifest } from './manifest/client.js'; @@ -10,4 +10,5 @@ window.init = () => { }); }; -window.prefetchRoutes = prefetchRoutes; \ No newline at end of file +window.prefetchRoutes = prefetchRoutes; +window.goto = goto; \ No newline at end of file diff --git a/test/app/routes/echo/page/[slug].html b/test/app/routes/echo/page/[slug].html new file mode 100644 index 0000000..7b2f0a8 --- /dev/null +++ b/test/app/routes/echo/page/[slug].html @@ -0,0 +1,12 @@ +

{slug} ({message})

+ + \ No newline at end of file diff --git a/test/app/routes/echo/server-route/[slug].js b/test/app/routes/echo/server-route/[slug].js new file mode 100644 index 0000000..e0e9ef7 --- /dev/null +++ b/test/app/routes/echo/server-route/[slug].js @@ -0,0 +1,15 @@ +export function get(req, res) { + res.writeHead(200, { + 'Content-Type': 'text/html' + }); + + res.end(` + + + + +

${req.params.slug}

+ + + `); +} \ No newline at end of file diff --git a/test/app/routes/index.html b/test/app/routes/index.html index 4678fbd..3b889ef 100644 --- a/test/app/routes/index.html +++ b/test/app/routes/index.html @@ -15,6 +15,7 @@ credentials blog const +echo/page/encöded?message=hëllö+wörld
diff --git a/test/common/test.js b/test/common/test.js index 4db2d30..8a61b59 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -753,6 +753,32 @@ function run({ mode, basepath = '' }) { assert.equal(title, 'reserved words are okay as routes'); }); }); + + it('encodes req.params and req.query for server-rendered pages', () => { + return nightmare.goto(`${base}/echo/page/encöded?message=hëllö+wörld`) + .page.title() + .then(title => { + assert.equal(title, 'encöded (hëllö wörld)'); + }); + }); + + it('encodes req.params and req.query for client-rendered pages', () => { + return nightmare.goto(base).init() + .click('a[href="echo/page/encöded?message=hëllö+wörld"]') + .wait(100) + .page.title() + .then(title => { + assert.equal(title, 'encöded (hëllö wörld)'); + }); + }); + + it('encodes req.params for server routes', () => { + return nightmare.goto(`${base}/echo/server-route/encöded`) + .page.title() + .then(title => { + assert.equal(title, 'encöded'); + }); + }); }); describe('headers', () => {