@sveltejs/kit 2.8.2 → 2.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/postbuild/prerender.js +5 -4
- package/src/exports/vite/dev/index.js +10 -13
- package/src/exports/vite/utils.js +9 -2
- package/src/runtime/server/page/csp.js +2 -2
- package/src/runtime/server/page/serialize_data.js +2 -2
- package/src/runtime/server/utils.js +2 -1
- package/src/utils/escape.js +36 -20
- package/src/version.js +1 -1
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
4
4
|
import { installPolyfills } from '../../exports/node/polyfills.js';
|
|
5
5
|
import { mkdirp, posixify, walk } from '../../utils/filesystem.js';
|
|
6
6
|
import { decode_uri, is_root_relative, resolve } from '../../utils/url.js';
|
|
7
|
-
import {
|
|
7
|
+
import { escape_html } from '../../utils/escape.js';
|
|
8
8
|
import { logger } from '../utils.js';
|
|
9
9
|
import { load_config } from '../config/index.js';
|
|
10
10
|
import { get_route_segments } from '../../utils/routing.js';
|
|
@@ -359,9 +359,10 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
|
|
|
359
359
|
dest,
|
|
360
360
|
`<script>location.href=${devalue.uneval(
|
|
361
361
|
location
|
|
362
|
-
)};</script><meta http-equiv="refresh" content
|
|
363
|
-
`0;url=${location}
|
|
364
|
-
|
|
362
|
+
)};</script><meta http-equiv="refresh" content="${escape_html(
|
|
363
|
+
`0;url=${location}`,
|
|
364
|
+
true
|
|
365
|
+
)}">`
|
|
365
366
|
);
|
|
366
367
|
|
|
367
368
|
written.add(file);
|
|
@@ -18,8 +18,11 @@ import { compact } from '../../../utils/array.js';
|
|
|
18
18
|
import { not_found } from '../utils.js';
|
|
19
19
|
import { SCHEME } from '../../../utils/url.js';
|
|
20
20
|
import { check_feature } from '../../../utils/features.js';
|
|
21
|
+
import { escape_html } from '../../../utils/escape.js';
|
|
21
22
|
|
|
22
23
|
const cwd = process.cwd();
|
|
24
|
+
// vite-specifc queries that we should skip handling for css urls
|
|
25
|
+
const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/;
|
|
23
26
|
|
|
24
27
|
/**
|
|
25
28
|
* @param {import('vite').ViteDevServer} vite
|
|
@@ -187,6 +190,7 @@ export async function dev(vite, vite_config, svelte_config) {
|
|
|
187
190
|
// in dev we inline all styles to avoid FOUC. this gets populated lazily so that
|
|
188
191
|
// components/stylesheets loaded via import() during `load` are included
|
|
189
192
|
result.inline_styles = async () => {
|
|
193
|
+
/** @type {Set<import('vite').ModuleNode>} */
|
|
190
194
|
const deps = new Set();
|
|
191
195
|
|
|
192
196
|
for (const module_node of module_nodes) {
|
|
@@ -197,19 +201,12 @@ export async function dev(vite, vite_config, svelte_config) {
|
|
|
197
201
|
const styles = {};
|
|
198
202
|
|
|
199
203
|
for (const dep of deps) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
(isCSSRequest(dep.file) ||
|
|
205
|
-
(query.has('svelte') && query.get('type') === 'style')) &&
|
|
206
|
-
!(query.has('raw') || query.has('url') || query.has('inline'))
|
|
207
|
-
) {
|
|
204
|
+
if (isCSSRequest(dep.url) && !vite_css_query_regex.test(dep.url)) {
|
|
205
|
+
const inlineCssUrl = dep.url.includes('?')
|
|
206
|
+
? dep.url.replace('?', '?inline&')
|
|
207
|
+
: dep.url + '?inline';
|
|
208
208
|
try {
|
|
209
|
-
|
|
210
|
-
const mod = await vite.ssrLoadModule(
|
|
211
|
-
`${decodeURI(url.pathname)}${url.search}${url.hash}`
|
|
212
|
-
);
|
|
209
|
+
const mod = await vite.ssrLoadModule(inlineCssUrl);
|
|
213
210
|
styles[dep.url] = mod.default;
|
|
214
211
|
} catch {
|
|
215
212
|
// this can happen with dynamically imported modules, I think
|
|
@@ -508,7 +505,7 @@ export async function dev(vite, vite_config, svelte_config) {
|
|
|
508
505
|
const error_template = ({ status, message }) => {
|
|
509
506
|
return error_page
|
|
510
507
|
.replace(/%sveltekit\.status%/g, String(status))
|
|
511
|
-
.replace(/%sveltekit\.error\.message%/g, message);
|
|
508
|
+
.replace(/%sveltekit\.error\.message%/g, escape_html(message));
|
|
512
509
|
};
|
|
513
510
|
|
|
514
511
|
res.writeHead(500, {
|
|
@@ -3,6 +3,7 @@ import { loadEnv } from 'vite';
|
|
|
3
3
|
import { posixify } from '../../utils/filesystem.js';
|
|
4
4
|
import { negotiate } from '../../utils/http.js';
|
|
5
5
|
import { filter_private_env, filter_public_env } from '../../utils/env.js';
|
|
6
|
+
import { escape_html } from '../../utils/escape.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Transforms kit.alias to a valid vite.resolve.alias array.
|
|
@@ -89,11 +90,17 @@ export function not_found(req, res, base) {
|
|
|
89
90
|
if (type === 'text/html') {
|
|
90
91
|
res.setHeader('Content-Type', 'text/html');
|
|
91
92
|
res.end(
|
|
92
|
-
`The server is configured with a public base URL of ${
|
|
93
|
+
`The server is configured with a public base URL of ${escape_html(
|
|
94
|
+
base
|
|
95
|
+
)} - did you mean to visit <a href="${escape_html(prefixed, true)}">${escape_html(
|
|
96
|
+
prefixed
|
|
97
|
+
)}</a> instead?`
|
|
93
98
|
);
|
|
94
99
|
} else {
|
|
95
100
|
res.end(
|
|
96
|
-
`The server is configured with a public base URL of ${
|
|
101
|
+
`The server is configured with a public base URL of ${escape_html(
|
|
102
|
+
base
|
|
103
|
+
)} - did you mean to visit ${escape_html(prefixed)} instead?`
|
|
97
104
|
);
|
|
98
105
|
}
|
|
99
106
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { escape_html } from '../../../utils/escape.js';
|
|
2
2
|
import { base64, sha256 } from './crypto.js';
|
|
3
3
|
|
|
4
4
|
const array = new Uint8Array(16);
|
|
@@ -300,7 +300,7 @@ class CspProvider extends BaseProvider {
|
|
|
300
300
|
return;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
return `<meta http-equiv="content-security-policy" content
|
|
303
|
+
return `<meta http-equiv="content-security-policy" content="${escape_html(content, true)}">`;
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
306
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { escape_html } from '../../../utils/escape.js';
|
|
2
2
|
import { hash } from '../../hash.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -70,7 +70,7 @@ export function serialize_data(fetched, filter, prerendering = false) {
|
|
|
70
70
|
const attrs = [
|
|
71
71
|
'type="application/json"',
|
|
72
72
|
'data-sveltekit-fetched',
|
|
73
|
-
`data-url
|
|
73
|
+
`data-url="${escape_html(fetched.url, true)}"`
|
|
74
74
|
];
|
|
75
75
|
|
|
76
76
|
if (fetched.is_b64) {
|
|
@@ -5,6 +5,7 @@ import { negotiate } from '../../utils/http.js';
|
|
|
5
5
|
import { HttpError } from '../control.js';
|
|
6
6
|
import { fix_stack_trace } from '../shared-server.js';
|
|
7
7
|
import { ENDPOINT_METHODS } from '../../constants.js';
|
|
8
|
+
import { escape_html } from '../../utils/escape.js';
|
|
8
9
|
|
|
9
10
|
/** @param {any} body */
|
|
10
11
|
export function is_pojo(body) {
|
|
@@ -50,7 +51,7 @@ export function allowed_methods(mod) {
|
|
|
50
51
|
* @param {string} message
|
|
51
52
|
*/
|
|
52
53
|
export function static_error_page(options, status, message) {
|
|
53
|
-
let page = options.templates.error({ status, message });
|
|
54
|
+
let page = options.templates.error({ status, message: escape_html(message) });
|
|
54
55
|
|
|
55
56
|
if (DEV) {
|
|
56
57
|
// inject Vite HMR client, for easier debugging
|
package/src/utils/escape.js
CHANGED
|
@@ -6,41 +6,57 @@
|
|
|
6
6
|
const escape_html_attr_dict = {
|
|
7
7
|
'&': '&',
|
|
8
8
|
'"': '"'
|
|
9
|
+
// Svelte also escapes < because the escape function could be called inside a `noscript` there
|
|
10
|
+
// https://github.com/sveltejs/svelte/security/advisories/GHSA-8266-84wp-wv5c
|
|
11
|
+
// However, that doesn't apply in SvelteKit
|
|
9
12
|
};
|
|
10
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @type {Record<string, string>}
|
|
16
|
+
*/
|
|
17
|
+
const escape_html_dict = {
|
|
18
|
+
'&': '&',
|
|
19
|
+
'<': '<'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const surrogates = // high surrogate without paired low surrogate
|
|
23
|
+
'[\\ud800-\\udbff](?![\\udc00-\\udfff])|' +
|
|
24
|
+
// a valid surrogate pair, the only match with 2 code units
|
|
25
|
+
// we match it so that we can match unpaired low surrogates in the same pass
|
|
26
|
+
// TODO: use lookbehind assertions once they are widely supported: (?<![\ud800-udbff])[\udc00-\udfff]
|
|
27
|
+
'[\\ud800-\\udbff][\\udc00-\\udfff]|' +
|
|
28
|
+
// unpaired low surrogate (see previous match)
|
|
29
|
+
'[\\udc00-\\udfff]';
|
|
30
|
+
|
|
11
31
|
const escape_html_attr_regex = new RegExp(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// TODO: use lookbehind assertions once they are widely supported: (?<![\ud800-udbff])[\udc00-\udfff]
|
|
19
|
-
'[\\ud800-\\udbff][\\udc00-\\udfff]|' +
|
|
20
|
-
// unpaired low surrogate (see previous match)
|
|
21
|
-
'[\\udc00-\\udfff]',
|
|
32
|
+
`[${Object.keys(escape_html_attr_dict).join('')}]|` + surrogates,
|
|
33
|
+
'g'
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const escape_html_regex = new RegExp(
|
|
37
|
+
`[${Object.keys(escape_html_dict).join('')}]|` + surrogates,
|
|
22
38
|
'g'
|
|
23
39
|
);
|
|
24
40
|
|
|
25
41
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* It escapes unpaired surrogates (which are allowed in js strings but invalid in HTML), escapes
|
|
29
|
-
* characters that are special in attributes, and surrounds the whole string in double-quotes.
|
|
42
|
+
* Escapes unpaired surrogates (which are allowed in js strings but invalid in HTML) and
|
|
43
|
+
* escapes characters that are special.
|
|
30
44
|
*
|
|
31
45
|
* @param {string} str
|
|
32
|
-
* @
|
|
33
|
-
* @
|
|
46
|
+
* @param {boolean} [is_attr]
|
|
47
|
+
* @returns {string} escaped string
|
|
48
|
+
* @example const html = `<tag data-value="${escape_html('value', true)}">...</tag>`;
|
|
34
49
|
*/
|
|
35
|
-
export function
|
|
36
|
-
const
|
|
50
|
+
export function escape_html(str, is_attr) {
|
|
51
|
+
const dict = is_attr ? escape_html_attr_dict : escape_html_dict;
|
|
52
|
+
const escaped_str = str.replace(is_attr ? escape_html_attr_regex : escape_html_regex, (match) => {
|
|
37
53
|
if (match.length === 2) {
|
|
38
54
|
// valid surrogate pair
|
|
39
55
|
return match;
|
|
40
56
|
}
|
|
41
57
|
|
|
42
|
-
return
|
|
58
|
+
return dict[match] ?? `&#${match.charCodeAt(0)};`;
|
|
43
59
|
});
|
|
44
60
|
|
|
45
|
-
return
|
|
61
|
+
return escaped_str;
|
|
46
62
|
}
|
package/src/version.js
CHANGED