fix: misc option escaping and backwards compatibility (#17741)

### Before submitting the PR, please make sure you do the following

- [ ] It's really useful if your PR references an issue where it is
discussed ahead of time. In many cases, features are absent for a
reason. For large changes, please create an RFC:
https://github.com/sveltejs/rfcs
- [ ] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`.
- [ ] This message body should clearly illustrate what problems it
solves.
- [ ] Ideally, include a test that fails without this PR but passes with
it.
- [ ] If this PR changes code within `packages/svelte/src`, add a
changeset (`npx changeset`).

### Tests and linting

- [ ] Run the tests with `pnpm test` and lint the project with `pnpm
lint`
pull/17740/head
Elliott Johnson 5 days ago committed by GitHub
parent 781052eeab
commit f855a0b770
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: misc option escaping and backwards compatibility

@ -15,12 +15,11 @@ export function async_local_storage_unavailable() {
}
/**
* `<svelte:element this="%tag%">` is not a valid element name the element will not be rendered
* @param {string} tag
* Encountered asynchronous work while rendering synchronously.
* @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`);
export function await_invalid() {
const error = new Error(`await_invalid\nEncountered asynchronous work while rendering synchronously.\nhttps://svelte.dev/e/await_invalid`);
error.name = 'Svelte error';
@ -28,11 +27,12 @@ export function dynamic_element_invalid_tag(tag) {
}
/**
* Encountered asynchronous work while rendering synchronously.
* `<svelte:element this="%tag%">` is not a valid element name the element will not be rendered
* @param {string} tag
* @returns {never}
*/
export function await_invalid() {
const error = new Error(`await_invalid\nEncountered asynchronous work while rendering synchronously.\nhttps://svelte.dev/e/await_invalid`);
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';

@ -11,7 +11,7 @@ import { attributes } from './index.js';
import { get_render_context, with_render_context, init_render_context } from './render-context.js';
import { sha256 } from './crypto.js';
import * as devalue from 'devalue';
import { noop } from '../shared/utils.js';
import { has_own_property, noop } from '../shared/utils.js';
import { escape_html } from '../../escaping.js';
/** @typedef {'head' | 'body'} RendererType */
@ -268,7 +268,7 @@ export class Renderer {
* @param {{ head?: string, body: any }} content
*/
const close = (renderer, value, { head, body }) => {
if (Object.hasOwn(attrs, 'value')) {
if (has_own_property.call(attrs, 'value')) {
value = attrs.value;
}
@ -276,7 +276,7 @@ export class Renderer {
renderer.#out.push(' selected=""');
}
renderer.#out.push(`>${escape_html(body)}${is_rich ? '<!>' : ''}</option>`);
renderer.#out.push(`>${body}${is_rich ? '<!>' : ''}</option>`);
// super edge case, but may as well handle it
if (head) {
@ -299,7 +299,7 @@ export class Renderer {
}
});
} else {
close(this, body, { body });
close(this, body, { body: escape_html(body) });
}
}

@ -1,5 +1,6 @@
import { escape_html } from '../../escaping.js';
import { clsx as _clsx } from 'clsx';
import { has_own_property } from './utils.js';
/**
* `<div translate={false}>` should be rendered as `<div translate="no">` and _not_
@ -27,7 +28,8 @@ export function attr(name, value, is_boolean = false) {
is_boolean = true;
}
if (value == null || (!value && is_boolean)) return '';
const normalized = (Object.hasOwn(replacements, name) && replacements[name].get(value)) || value;
const normalized =
(has_own_property.call(replacements, name) && replacements[name].get(value)) || value;
const assignment = is_boolean ? `=""` : `="${escape_html(normalized, true)}"`;
return ` ${name}${assignment}`;
}

@ -12,6 +12,7 @@ export var object_prototype = Object.prototype;
export var array_prototype = Array.prototype;
export var get_prototype_of = Object.getPrototypeOf;
export var is_extensible = Object.isExtensible;
export var has_own_property = Object.prototype.hasOwnProperty;
/**
* @param {any} thing

@ -1 +1 @@
<!--[--><select><option>a&lt;/option>&lt;script>alert("pwnd")&lt;/script>&lt;option>puppa</option></select><!--]-->
<!--[--><select><option>a&lt;/option>&lt;script>alert("pwnd")&lt;/script>&lt;option>puppa</option><option>selected: a&lt;/option>&lt;script>alert("pwnd")&lt;/script>&lt;option>puppa</option></select><!--]-->

@ -1,6 +1,9 @@
<script>
let selectedBook = $state("a</option><sc"+"ript>alert(\"pwnd\")</sc"+"ript><option>puppa");
let {
selectedBook = "a</option><sc" + "ript>alert(\"pwnd\")</sc" + "ript><option>puppa"
} = $props();
</script>
<select>
<option>{selectedBook}</option>
<option>selected: {selectedBook}</option>
</select>

Loading…
Cancel
Save