Skip to content

Commit

Permalink
fix: escape < in attribute strings (#12989)
Browse files Browse the repository at this point in the history
Svelte 4 version of #11411
  • Loading branch information
dummdidumm committed Aug 23, 2024
1 parent 5ec4409 commit 83e96e0
Show file tree
Hide file tree
Showing 13 changed files with 59 additions and 38 deletions.
5 changes: 5 additions & 0 deletions .changeset/itchy-ties-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: escape `<` in attribute strings
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ jobs:
strategy:
matrix:
include:
- node-version: 16
- node-version: 18
os: ubuntu-latest
- node-version: 16
- node-version: 18
os: windows-latest
- node-version: 16
os: macOS-latest
- node-version: 18
os: ubuntu-latest
os: macOS-latest
- node-version: 20
os: ubuntu-latest
- node-version: 22
os: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
Expand All @@ -44,10 +44,10 @@ jobs:
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
cache: pnpm
- name: install
run: pnpm install --frozen-lockfile
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
with:
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
fetch-depth: 0
- uses: pnpm/action-setup@v2.2.4
- uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@
"prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.1"
},
"packageManager": "pnpm@8.6.3"
"packageManager": "pnpm@9.4.0"
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { string_literal } from '../../../utils/stringify.js';
import { x } from 'code-red';
import { regex_double_quotes } from '../../../../utils/patterns.js';
import { escape } from '../../../../../shared/utils/escape.js';

/**
* @param {import('../../../nodes/Attribute.js').default} attribute
Expand Down Expand Up @@ -37,9 +37,7 @@ export function get_attribute_value(attribute) {
return attribute.chunks
.map((chunk) => {
return chunk.type === 'Text'
? /** @type {import('estree').Expression} */ (
string_literal(chunk.data.replace(regex_double_quotes, '&quot;'))
)
? /** @type {import('estree').Expression} */ (string_literal(escape(chunk.data, true)))
: x`@escape(${chunk.node}, ${is_textarea_value ? 'false' : 'true'})`;
})
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
Expand Down
26 changes: 2 additions & 24 deletions packages/svelte/src/runtime/internal/ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { set_current_component, current_component } from './lifecycle.js';
import { run_all, blank_object } from './utils.js';
import { boolean_attributes } from '../../shared/boolean_attributes.js';
import { ensure_array_like } from './each.js';
import { escape } from '../../shared/utils/escape.js';
export { is_void } from '../../shared/utils/names.js';
export { escape };

export const invalid_attribute_name_character =
/[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
Expand Down Expand Up @@ -67,30 +69,6 @@ export function merge_ssr_styles(style_attribute, style_directive) {
return style_object;
}

const ATTR_REGEX = /[&"]/g;
const CONTENT_REGEX = /[&<]/g;

/**
* Note: this method is performance sensitive and has been optimized
* https://github.com/sveltejs/svelte/pull/5701
* @param {unknown} value
* @returns {string}
*/
export function escape(value, is_attr = false) {
const str = String(value);
const pattern = is_attr ? ATTR_REGEX : CONTENT_REGEX;
pattern.lastIndex = 0;
let escaped = '';
let last = 0;
while (pattern.test(str)) {
const i = pattern.lastIndex - 1;
const ch = str[i];
escaped += str.substring(last, i) + (ch === '&' ? '&amp;' : ch === '"' ? '&quot;' : '&lt;');
last = i + 1;
}
return escaped + str.substring(last);
}

export function escape_attribute_value(value) {
// keep booleans, null, and undefined for the sake of `spread`
const should_escape = typeof value === 'string' || (value && typeof value === 'object');
Expand Down
23 changes: 23 additions & 0 deletions packages/svelte/src/shared/utils/escape.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const ATTR_REGEX = /[&"<]/g;
const CONTENT_REGEX = /[&<]/g;

/**
* Note: this method is performance sensitive and has been optimized
* https://github.com/sveltejs/svelte/pull/5701
* @param {unknown} value
* @returns {string}
*/
export function escape(value, is_attr = false) {
const str = String(value);
const pattern = is_attr ? ATTR_REGEX : CONTENT_REGEX;
pattern.lastIndex = 0;
let escaped = '';
let last = 0;
while (pattern.test(str)) {
const i = pattern.lastIndex - 1;
const ch = str[i];
escaped += str.substring(last, i) + (ch === '&' ? '&amp;' : ch === '"' ? '&quot;' : '&lt;');
last = i + 1;
}
return escaped + str.substring(last);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<noscript
><a href="&lt;/noscript>&lt;script>console.log('should not run')&lt;/script>">test</a></noscript
>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script>
const x = `</noscript><script>console.log('should not run')<` + `/script>`
</script>

<noscript>
<a href={x}>test</a>
</noscript>

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div title="&amp;&lt;">blah</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div title="&amp;&lt;">blah</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<noscript><a href="&lt;/noscript>&lt;script>throw new Error('fooo')&lt;/script>">test</a></noscript>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<noscript>
<a href="</noscript><script>throw new Error('fooo')</script>">test</a>
</noscript>

0 comments on commit 83e96e0

Please sign in to comment.