From 936afe782c3196eb26d133b2fcbab0a71ed25e07 Mon Sep 17 00:00:00 2001 From: jin Date: Tue, 17 Dec 2024 01:31:02 +0300 Subject: [PATCH] $mol_dom_safe - sanitize wild dom by white list --- dom/safe/safe.test.tsx | 49 ++++++++++++++++++++++ dom/safe/safe.ts | 92 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 dom/safe/safe.test.tsx create mode 100644 dom/safe/safe.ts diff --git a/dom/safe/safe.test.tsx b/dom/safe/safe.test.tsx new file mode 100644 index 0000000000..2384bf1721 --- /dev/null +++ b/dom/safe/safe.test.tsx @@ -0,0 +1,49 @@ +/** @jsx $mol_jsx */ +/** @jsxFrag $mol_jsx_frag */ +namespace $ { + $mol_test({ + + 'safe tag'() { + $mol_assert_equal( + $$.$mol_dom_safe([
foo
])[0], +
foo
, + ) + }, + + 'bad tag'() { + $mol_assert_equal( + $mol_dom_serialize( $$.$mol_dom_safe([ ])[0] ), + $mol_dom_serialize( <>alert('ahtung!') ), + ) + }, + + 'common attr'() { + $mol_assert_equal( + $$.$mol_dom_safe([ foo ])[0], + foo, + ) + }, + + 'safe attr'() { + $mol_assert_equal( + $$.$mol_dom_safe([ foo ])[0], + foo, + ) + }, + + 'bad attr'() { + $mol_assert_equal( + $$.$mol_dom_safe([ foo ])[0], + foo, + ) + }, + + 'danger attr'() { + $mol_assert_equal( + $$.$mol_dom_safe([ foo ])[0], + foo, + ) + }, + + }) +} diff --git a/dom/safe/safe.ts b/dom/safe/safe.ts new file mode 100644 index 0000000000..5a8d345cd0 --- /dev/null +++ b/dom/safe/safe.ts @@ -0,0 +1,92 @@ +namespace $ { + + export function $mol_dom_safe_uri( uri: string ) { + return uri.replace( /^(?=\w+script+:)/, 'about:blank#' ) + } + + export function $mol_dom_safe_attr( val: string ) { + return val + } + + export let $mol_dom_safe_rules: Record< string, Record< string, ( val: string )=> string > > = { + + // defaults + '': { id: $mol_dom_safe_attr }, + + // special + '': { href: $mol_dom_safe_uri }, + img: { src: $mol_dom_safe_uri }, + object: { src: $mol_dom_safe_uri }, + + // blocks + div: {}, + p: {}, + h1: {}, + h2: {}, + h3: {}, + h4: {}, + h5: {}, + h6: {}, + blockquote: {}, + pre: {}, + ul: {}, + ol: {}, + li: {}, + details: {}, + summary: {}, + hr: {}, + table: {}, + tr: {}, + td: {}, + + // inlines + span: {}, + strong: {}, + em: {}, + br: {}, + ins: {}, + del: {}, + code: {}, + + } + + export function $mol_dom_safe( this: $, nodes: ChildNode[] ) { + + const res = [] as ChildNode[] + + for( const node of nodes ) { + + if( node.nodeType === node.TEXT_NODE ) { + res.push( node ) + continue + } + + if( node.nodeType === node.ELEMENT_NODE ) { + + const kids = this.$mol_dom_safe([ ... node.childNodes ]) + + const allowed = this.$mol_dom_safe_rules[ ( node as Element ).localName ] + if( !allowed ) { + res.push( ... kids ) + continue + } + + for( const attr of [ ... ( node as Element ).attributes ] ) { + const proc = allowed[ attr.localName ] ?? this.$mol_dom_safe_rules[''][ attr.localName ] + if( proc ) attr.nodeValue = proc( attr.nodeValue! ) + else ( node as Element ).removeAttribute( attr.nodeName ) + } + + $mol_dom_render_children( node as Element, kids ) + res.push( node ) + continue + + } + + } + + return res + } + + +}