Merge commit from fork

* fix: check to make sure `svelte:element` tags are valid during SSR

* fix: error instead of warn

* better sharing

* nit
pull/17741/head
Elliott Johnson 1 day ago committed by GitHub
parent f89c7ddd7e
commit 73098bb26c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: check to make sure `svelte:element` tags are valid during SSR

@ -16,6 +16,14 @@ Encountered asynchronous work while rendering synchronously.
You (or the framework you're using) called [`render(...)`](svelte-server#render) with a component containing an `await` expression. Either `await` the result of `render` or wrap the `await` (or the component containing it) in a [`<svelte:boundary>`](svelte-boundary) with a `pending` snippet.
### dynamic_element_invalid_tag
```
`<svelte:element this="%tag%">` is not a valid element name — the element will not be rendered
```
The value passed to the `this` prop of `<svelte:element>` must be a valid HTML element, SVG element, MathML element, or custom element name. A value containing invalid characters (such as whitespace or special characters) was provided, which could be a security risk. Ensure only valid tag names are passed.
### html_deprecated
```

@ -10,6 +10,12 @@ Some platforms require configuration flags to enable this API. Consult your plat
You (or the framework you're using) called [`render(...)`](svelte-server#render) with a component containing an `await` expression. Either `await` the result of `render` or wrap the `await` (or the component containing it) in a [`<svelte:boundary>`](svelte-boundary) with a `pending` snippet.
## dynamic_element_invalid_tag
> `<svelte:element this="%tag%">` is not a valid element name — the element will not be rendered
The value passed to the `this` prop of `<svelte:element>` must be a valid HTML element, SVG element, MathML element, or custom element name. A value containing invalid characters (such as whitespace or special characters) was provided, which could be a security risk. Ensure only valid tag names are passed.
## html_deprecated
> The `html` property of server render results has been deprecated. Use `body` instead.

@ -2,7 +2,7 @@
/** @import { Location } from 'locate-character' */
/** @import { AST } from '#compiler' */
/** @import { Parser } from '../index.js' */
import { is_void } from '../../../../utils.js';
import { is_void, REGEX_VALID_TAG_NAME } from '../../../../utils.js';
import read_expression from '../read/expression.js';
import { read_script } from '../read/script.js';
import read_style from '../read/style.js';
@ -24,8 +24,15 @@ const regex_whitespace_or_slash_or_closing_tag = /(\s|\/|>)/;
const regex_token_ending_character = /[\s=/>"']/;
const regex_starts_with_quote_characters = /^["']/;
const regex_attribute_value = /^(?:"([^"]*)"|'([^'])*'|([^>\s]+))/;
const regex_valid_element_name =
/^(?:![a-zA-Z]+|[a-zA-Z](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?|[a-zA-Z][a-zA-Z0-9]*:[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9])$/;
/** @param {string} name */
function is_valid_element_name(name) {
// DOCTYPE (e.g. !DOCTYPE)
if (/^![a-zA-Z]+$/.test(name)) return true;
// svelte:* meta tags (e.g. svelte:element, svelte:head)
if (/^[a-zA-Z][a-zA-Z0-9]*:[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9]$/.test(name)) return true;
// standard HTML/SVG/MathML elements and custom elements
return REGEX_VALID_TAG_NAME.test(name);
}
export const regex_valid_component_name =
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers adjusted for our needs
// (must start with uppercase letter if no dots, can contain dots)
@ -134,7 +141,7 @@ export default function element(parser) {
e.svelte_meta_invalid_tag(bounds, list(Array.from(meta_tags.keys())));
}
if (!regex_valid_element_name.test(tag.name) && !regex_valid_component_name.test(tag.name)) {
if (!is_valid_element_name(tag.name) && !regex_valid_component_name.test(tag.name)) {
// <div. -> in the middle of typing -> allow in loose mode
if (!parser.loose || !tag.name.endsWith('.')) {
const bounds = { start: start + 1, end: start + 1 + tag.name.length };

@ -14,6 +14,19 @@ export function async_local_storage_unavailable() {
throw error;
}
/**
* `<svelte:element this="%tag%">` is not a valid element name the element will not be rendered
* @param {string} tag
* @returns {never}
*/
export function dynamic_element_invalid_tag(tag) {
const error = new Error(`dynamic_element_invalid_tag\n\`<svelte:element this="${tag}">\` is not a valid element name — the element will not be rendered\nhttps://svelte.dev/e/dynamic_element_invalid_tag`);
error.name = 'Svelte error';
throw error;
}
/**
* Encountered asynchronous work while rendering synchronously.
* @returns {never}

@ -13,9 +13,14 @@ import {
} from '../../constants.js';
import { escape_html } from '../../escaping.js';
import { DEV } from 'esm-env';
import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN, BLOCK_OPEN_ELSE } from './hydration.js';
import { EMPTY_COMMENT, BLOCK_OPEN, BLOCK_OPEN_ELSE } from './hydration.js';
import { validate_store } from '../shared/validate.js';
import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js';
import {
is_boolean_attribute,
is_raw_text_element,
is_void,
REGEX_VALID_TAG_NAME
} from '../../utils.js';
import { Renderer } from './renderer.js';
import * as e from './errors.js';
@ -35,6 +40,9 @@ export function element(renderer, tag, attributes_fn = noop, children_fn = noop)
renderer.push('<!---->');
if (tag) {
if (!REGEX_VALID_TAG_NAME.test(tag)) {
e.dynamic_element_invalid_tag(tag);
}
renderer.push(`<${tag}`);
attributes_fn();
renderer.push(`>`);

@ -480,6 +480,19 @@ export function is_raw_text_element(name) {
return RAW_TEXT_ELEMENTS.includes(/** @type {typeof RAW_TEXT_ELEMENTS[number]} */ (name));
}
// Matches valid HTML/SVG/MathML element names and custom element names.
// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
//
// Standard elements: ASCII alpha start, followed by ASCII alphanumerics.
// Custom elements: ASCII alpha start, followed by any mix of PCENChar (which
// includes ASCII alphanumerics, `-`, `.`, `_`, and specified Unicode ranges),
// with at least one hyphen required somewhere after the first character.
//
// Rejects strings containing whitespace, quotes, angle brackets, slashes, equals,
// or other characters that could break out of a tag-name token and enable markup injection.
export const REGEX_VALID_TAG_NAME =
/^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9.\-_\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}]+)*$/u;
/**
* Prevent devtools trying to make `location` a clickable link by inserting a zero-width space
* @template {string | undefined} T

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
props: {
tag: 'svg onload=alert(1)'
},
error: 'dynamic_element_invalid_tag'
});

@ -0,0 +1,5 @@
<script>
let { tag } = $props();
</script>
<svelte:element this={tag}>ok</svelte:element>
Loading…
Cancel
Save