Skip to content

Commit d02f1f2

Browse files
committed
[fix] decode parameters on client
1 parent 470d026 commit d02f1f2

File tree

7 files changed

+46
-38
lines changed

7 files changed

+46
-38
lines changed

.changeset/warm-news-stare.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
[fix] decode parameters on client

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { onMount, tick } from 'svelte';
22
import { writable } from 'svelte/store';
33
import { coalesce_to_error } from '../../utils/error.js';
44
import { normalize } from '../load.js';
5-
import { LoadURL, normalize_path } from '../../utils/url.js';
5+
import { LoadURL, decode_params, normalize_path } from '../../utils/url.js';
66
import {
77
create_updated_store,
88
find_anchor,
@@ -893,7 +893,7 @@ export function create_client({ target, session, base, trailing_slash }) {
893893
if (params) {
894894
const id = normalize_path(url.pathname, trailing_slash) + url.search;
895895
/** @type {import('./types').NavigationIntent} */
896-
const intent = { id, route, params, url };
896+
const intent = { id, route, params: decode_params(params), url };
897897
return intent;
898898
}
899899
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { render_page } from './page/index.js';
33
import { render_response } from './page/render.js';
44
import { respond_with_error } from './page/respond_with_error.js';
55
import { coalesce_to_error } from '../../utils/error.js';
6-
import { decode_params, serialize_error, GENERIC_ERROR } from './utils.js';
7-
import { normalize_path } from '../../utils/url.js';
6+
import { serialize_error, GENERIC_ERROR } from './utils.js';
7+
import { decode_params, normalize_path } from '../../utils/url.js';
88
import { exec } from '../../utils/routing.js';
99
import { negotiate } from '../../utils/http.js';
1010

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

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,6 @@ export function lowercase_keys(obj) {
1010
return clone;
1111
}
1212

13-
/** @param {Record<string, string>} params */
14-
export function decode_params(params) {
15-
for (const key in params) {
16-
// input has already been decoded by decodeURI
17-
// now handle the rest that decodeURIComponent would do
18-
params[key] = params[key]
19-
.replace(/%23/g, '#')
20-
.replace(/%3[Bb]/g, ';')
21-
.replace(/%2[Cc]/g, ',')
22-
.replace(/%2[Ff]/g, '/')
23-
.replace(/%3[Ff]/g, '?')
24-
.replace(/%3[Aa]/g, ':')
25-
.replace(/%40/g, '@')
26-
.replace(/%26/g, '&')
27-
.replace(/%3[Dd]/g, '=')
28-
.replace(/%2[Bb]/g, '+')
29-
.replace(/%24/g, '$');
30-
}
31-
32-
return params;
33-
}
34-
3513
/** @param {any} body */
3614
export function is_pojo(body) {
3715
if (typeof body !== 'object') return false;

packages/kit/src/utils/url.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,28 @@ export function normalize_path(path, trailing_slash) {
5353
return path;
5454
}
5555

56+
/** @param {Record<string, string>} params */
57+
export function decode_params(params) {
58+
for (const key in params) {
59+
// input has already been decoded by decodeURI
60+
// now handle the rest that decodeURIComponent would do
61+
params[key] = params[key]
62+
.replace(/%23/g, '#')
63+
.replace(/%3[Bb]/g, ';')
64+
.replace(/%2[Cc]/g, ',')
65+
.replace(/%2[Ff]/g, '/')
66+
.replace(/%3[Ff]/g, '?')
67+
.replace(/%3[Aa]/g, ':')
68+
.replace(/%40/g, '@')
69+
.replace(/%26/g, '&')
70+
.replace(/%3[Dd]/g, '=')
71+
.replace(/%2[Bb]/g, '+')
72+
.replace(/%24/g, '$');
73+
}
74+
75+
return params;
76+
}
77+
5678
export class LoadURL extends URL {
5779
/** @returns {string} */
5880
get hash() {

packages/kit/test/apps/basics/src/routes/encoded/index.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
<a href="/encoded/redirect">Redirect</a>
55
<a href="/encoded/@svelte">@svelte</a>
66
<a href="/encoded/$SVLT">$SVLT</a>
7+
<a href="/encoded/test%2520me">test%20me</a>
8+
<a href="/encoded/AC%2fDC">AC/DC</a>
9+
<a href="/encoded/%5b">[</a>

packages/kit/test/apps/basics/test/test.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -262,30 +262,30 @@ test.describe('Encoded paths', () => {
262262
expect(decodeURI(await page.innerHTML('h3'))).toBe('/encoded/苗条');
263263
});
264264

265-
test('visits a route with a doubly encoded space', async ({ page }) => {
266-
await page.goto('/encoded/test%2520me');
265+
test('visits a route with a doubly encoded space', async ({ page, clicknav }) => {
266+
await page.goto('/encoded');
267+
await clicknav('[href="/encoded/test%2520me"]');
268+
expect(await page.innerHTML('h1')).toBe('dynamic');
267269
expect(await page.innerHTML('h2')).toBe('/encoded/test%2520me: test%20me');
268270
expect(await page.innerHTML('h3')).toBe('/encoded/test%2520me: test%20me');
269271
});
270272

271-
test('visits a route with an encoded slash', async ({ page }) => {
272-
await page.goto('/encoded/AC%2fDC');
273+
test('visits a route with an encoded slash', async ({ page, clicknav }) => {
274+
await page.goto('/encoded');
275+
await clicknav('[href="/encoded/AC%2fDC"]');
276+
expect(await page.innerHTML('h1')).toBe('dynamic');
273277
expect(await page.innerHTML('h2')).toBe('/encoded/AC%2fDC: AC/DC');
274278
expect(await page.innerHTML('h3')).toBe('/encoded/AC%2fDC: AC/DC');
275279
});
276280

277-
test('visits a route with an encoded bracket', async ({ page }) => {
278-
await page.goto('/encoded/%5b');
281+
test('visits a route with an encoded bracket', async ({ page, clicknav }) => {
282+
await page.goto('/encoded');
283+
await clicknav('[href="/encoded/%5b"]');
284+
expect(await page.innerHTML('h1')).toBe('dynamic');
279285
expect(await page.innerHTML('h2')).toBe('/encoded/%5b: [');
280286
expect(await page.innerHTML('h3')).toBe('/encoded/%5b: [');
281287
});
282288

283-
test('visits a route with an encoded question mark', async ({ page }) => {
284-
await page.goto('/encoded/%3f');
285-
expect(await page.innerHTML('h2')).toBe('/encoded/%3f: ?');
286-
expect(await page.innerHTML('h3')).toBe('/encoded/%3f: ?');
287-
});
288-
289289
test('visits a dynamic route with non-ASCII character', async ({ page, clicknav }) => {
290290
await page.goto('/encoded');
291291
await clicknav('[href="/encoded/土豆"]');

0 commit comments

Comments
 (0)