Skip to content

Commit 757d0e8

Browse files
committed
Immediately sanitize attributes that can cause events to fire while an element is still in memory
1 parent 73e445c commit 757d0e8

File tree

2 files changed

+29
-12
lines changed

2 files changed

+29
-12
lines changed

src/Descriptor.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
import type { AstRule } from 'css-selector-parser';
2+
import type { SanitizationOption } from './Config.js';
23
import { replaceTextNode, sanitizeElement } from './Utility.js';
34

5+
/**
6+
* Attributes that should be immediately sanitized before they are added to any elements.
7+
*/
8+
const immediatelySanitizedAttributes = [
9+
'onerror',
10+
'onload',
11+
'onloadeddata',
12+
'onloadedmetadata',
13+
'onloadstart',
14+
'onstalled',
15+
'onsuspend',
16+
];
17+
418
/**
519
* Valid positioning pseudo classes.
620
*/
@@ -54,10 +68,12 @@ export class Descriptor {
5468
type: 'child' as 'child' | 'type'
5569
};
5670
public invalid = false;
71+
public isImported = false;
5772
private rawContent = '';
58-
private sanitize = false;
73+
private sanitize: SanitizationOption;
5974

60-
constructor (rule: AstRule, content?: string, sanitize = false) {
75+
constructor (rule: AstRule, content?: string, isImported = false, sanitize: SanitizationOption = 'all') {
76+
this.isImported = isImported;
6177
this.rule = rule;
6278
this.sanitize = sanitize;
6379

@@ -90,6 +106,7 @@ export class Descriptor {
90106
// Set the attributes.
91107
if (rule.attributes) {
92108
for (const attribute of rule.attributes) {
109+
if (this.sanitize !== 'off' && immediatelySanitizedAttributes.includes(attribute.name)) continue;
93110
let value = '';
94111
if (attribute.value?.type === 'String') {
95112
value = attribute.value.value;
@@ -99,7 +116,7 @@ export class Descriptor {
99116
}
100117

101118
// Sanitize the element.
102-
if (this.sanitize) {
119+
if (this.isImported && this.sanitize === 'imports') {
103120
this.element = sanitizeElement(this.element);
104121
}
105122

@@ -179,7 +196,7 @@ export class Descriptor {
179196
}
180197

181198
// Sanitize the element.
182-
if (this.sanitize) {
199+
if (this.isImported && this.sanitize === 'imports') {
183200
this.element = sanitizeElement(this.element);
184201
}
185202
}

src/Generator.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import { createCSSOM, elementsAreComparable, mergeElements, sanitizeElement } fr
77
const parse = createParser({ syntax: 'progressive' });
88

99
class Rule {
10+
isImported: boolean;
1011
rule: CSSStyleRule;
11-
sanitize: boolean;
1212
selectorAst: AstSelector;
1313

14-
constructor (rule: CSSStyleRule, sanitize = false) {
14+
constructor (rule: CSSStyleRule, isImported = false) {
15+
this.isImported = isImported;
1516
this.rule = rule;
16-
this.sanitize = sanitize;
1717
this.selectorAst = parse(rule.selectorText);
1818
}
1919
}
@@ -44,12 +44,12 @@ export async function cssToHtml (css: CSSRuleList | string, options: Partial<Opt
4444
const rules = new Array<Rule>();
4545
const importSet = new Set<string>();
4646

47-
async function parseRules (source: CSSRuleList, urlBase: string, sanitize?: boolean): Promise<void> {
47+
async function parseRules (source: CSSRuleList, urlBase: string, isImported = false): Promise<void> {
4848
let seenStyleRule = false;
4949
for (const rule of Object.values(source!)) {
5050
if (rule instanceof CSSStyleRule) {
5151
seenStyleRule = true;
52-
rules.push(new Rule(rule, sanitize));
52+
rules.push(new Rule(rule, isImported));
5353
}
5454
// Fetch the content of imported stylesheets.
5555
else if (rule instanceof CSSImportRule && !seenStyleRule && options.imports === 'include') {
@@ -60,23 +60,23 @@ export async function cssToHtml (css: CSSRuleList | string, options: Partial<Opt
6060
if (resource.status !== 200) throw new Error(`Response status for stylesheet "${url.href}" was ${resource.status}.`);
6161
const text = await resource.text();
6262
const importedRule = createCSSOM(text);
63-
if (importedRule) await parseRules(importedRule, url.href, options.sanitize === 'imports');
63+
if (importedRule) await parseRules(importedRule, url.href, true);
6464
}
6565
}
6666
}
6767
}
6868
await parseRules(styleRules, window.location.href);
6969

7070
// Populate the DOM.
71-
for (const { rule, sanitize, selectorAst } of rules) {
71+
for (const { isImported, rule, selectorAst } of rules) {
7272
// Traverse each rule nest of the selector AST.
7373
for (const r of selectorAst.rules) {
7474
const nest = new Array<Descriptor>();
7575
let invalidNest = false;
7676
// Create a descriptor for each of the nested selectors.
7777
let next: AstRule | undefined = r;
7878
do {
79-
const descriptor = new Descriptor(next, undefined, sanitize);
79+
const descriptor = new Descriptor(next, undefined, isImported, options.sanitize);
8080
if (descriptor.invalid) {
8181
invalidNest = true;
8282
next = undefined;

0 commit comments

Comments
 (0)