cleanHtml use allowedTags instead of disallowedTags and improved CSS handling

This commit is contained in:
the-djmaze 2024-09-16 14:37:26 +02:00
parent 05812c6be1
commit cfbc47488a

View file

@ -17,14 +17,34 @@ const
"'": ''' "'": '''
}, },
disallowedTags = [ keepTagContent = 'form,button,data', // font
'svg','script','title','link','base','meta',
'input','output','select','button','textarea', allowedTags = [
'bgsound','keygen','source','object','embed','applet','iframe','frame','frameset','video','audio','area','map' // Structural Elements:
// not supported by <template> element 'blockquote','br','div','figcaption','figure','h1','h2','h3','h4','h5','h6','hgroup','hr','p','wbr',
// ,'html','head','body' 'article','aside','header','footer','main','section',
'details','summary',
// List Elements
'dd','dl','dt','li','ol','ul',
// Text Formatting Elements
'a','abbr','address','b','bdi','bdo','cite','code','del','dfn',
'em','i','ins','kbd','mark','pre','q','rp','rt','ruby','s','samp','small',
'span','strong','sub','sup','time','u','var',
// Deprecated by HTML Standard
'acronym','big','center','dir','font','marquee',
'nobr','noembed','noframes','plaintext','rb','rtc','strike','tt',
// Media Elements
'img',//'picture','source',
// Table Elements
'caption','col','colgroup','table','tbody','td','tfoot','th','thead','tr',
// Disallowed but converted later
'style','xmp'
].join(','), ].join(','),
nonEmptyTags = [
'A','B','EM','I','SPAN','STRONG'
],
blockquoteSwitcher = () => { blockquoteSwitcher = () => {
SettingsUserStore.collapseBlockquotes() && SettingsUserStore.collapseBlockquotes() &&
// tmpl.content.querySelectorAll('blockquote').forEach(node => { // tmpl.content.querySelectorAll('blockquote').forEach(node => {
@ -102,8 +122,10 @@ const
}, },
cleanCSS = source => cleanCSS = source =>
source.trim().replace(/(^|;)\s*-(ms|webkit)-[^;]+(;|$)/g, '') source.trim()
.replace(/white-space[^;]+(;|$)/g, '') .replace(/;\s*-[^;]+/g, '')
.replace(/^\s*-[^;]+(;|$)/g, '')
.replace(/white-space[^;]+/g, '')
// Drop Microsoft Office style properties // Drop Microsoft Office style properties
// .replace(/mso-[^:;]+:[^;]+/gi, '') // .replace(/mso-[^:;]+:[^;]+/gi, '')
, ,
@ -145,14 +167,14 @@ const
if (source) { if (source) {
source = source source = source
// strip comments // strip comments
.replace(/\/\*[\s\S]*?\*\/|<!--|-->/gi, '') .replace(/\/\*[\s\S]*?\*\//gi, '')
// strip import statements // strip MS Word comments
.replace(/@import .*?;/gi , '') .replace(/<!--[\s\S]*?-->/gi, '');
// strip keyframe statements // strip HTML
.replace(/((@.*?keyframes [\s\S]*?){([\s\S]*?}\s*?)})/gi, ''); // .replace(/<\/?[a-z][\s\S]*?>/gi, '');
// unified regex to match css & media queries together // unified regex to match css & media queries together
let unified = /((\s*?(?:\/\*[\s\S]*?\*\/)?\s*?@media[\s\S]*?){([\s\S]*?)}\s*?})|(([\s\S]*?){([\s\S]*?)})/gi, let unified = /(?:(\s*?@(?:media)[\s\S]*?){([\s\S]*?)}\s*?})|(?:([\s\S]*?){([\s\S]*?)})/gi,
arr; arr;
while (true) { while (true) {
@ -161,7 +183,7 @@ const
break; break;
} }
let selector = arr[arr[2] === undefined ? 5 : 2].split('\r\n').join('\n').trim() let selector = arr[arr[2] === undefined ? 3 : 1].split('\r\n').join('\n').trim()
// Never have more than a single line break in a row // Never have more than a single line break in a row
.replace(/\n+/, "\n") .replace(/\n+/, "\n")
// Remove :root and html // Remove :root and html
@ -173,13 +195,14 @@ const
css.push({ css.push({
selector: selector, selector: selector,
type: 'media', type: 'media',
subStyles: parseCSS(arr[3] + '\n}') //recursively parse media query inner css subStyles: parseCSS(arr[2] + '\n}') //recursively parse media query inner css
}); });
} else if (selector && !selector.includes('@')) { } else if (selector && !selector.includes('@')) {
// we have standard css // we have standard css
// ignores @import, @keyframe, @font-face statements
css.push({ css.push({
selector: selector, selector: selector,
rules: cleanCSS(arr[6]) rules: cleanCSS(arr[4])
}); });
} }
} }
@ -258,9 +281,6 @@ export const
'abbr', 'scope', 'abbr', 'scope',
// td // td
'colspan', 'rowspan', 'headers' 'colspan', 'rowspan', 'headers'
],
nonEmptyTags = [
'A','B','EM','I','SPAN','STRONG'
]; ];
if (SettingsUserStore.allowStyles()) { if (SettingsUserStore.allowStyles()) {
@ -307,13 +327,21 @@ export const
} }
}); });
// https://github.com/the-djmaze/snappymail/issues/1125
tmpl.content.querySelectorAll(keepTagContent).forEach(oElement => replaceWithChildren(oElement));
tmpl.content.querySelectorAll( tmpl.content.querySelectorAll(
disallowedTags ':not('+allowedTags+')'
+ (0 < bqLevel ? ',' + (new Array(1 + bqLevel).fill('blockquote').join(' ')) : '') + (0 < bqLevel ? ',' + (new Array(1 + bqLevel).fill('blockquote').join(' ')) : '')
).forEach(oElement => oElement.remove()); ).forEach(oElement => oElement.remove());
/* // Is this slower or faster?
// https://github.com/the-djmaze/snappymail/issues/1125 ).forEach(oElement => {
tmpl.content.querySelectorAll('form,button').forEach(oElement => replaceWithChildren(oElement)); if (!node || !node.contains(oElement)) {
oElement.remove();
node = oElement;
}
});
*/
// https://github.com/the-djmaze/snappymail/issues/1641 // https://github.com/the-djmaze/snappymail/issues/1641
let body = tmpl.content.querySelector('.mail-body'); let body = tmpl.content.querySelector('.mail-body');
@ -346,6 +374,13 @@ export const
return; return;
} }
if ('XMP' === name) {
const pre = createElement('pre');
pre.innerHTML = encodeHtml(oElement.innerHTML);
oElement.replaceWith(pre);
return;
}
// \MailSo\Base\HtmlUtils::ClearTags() // \MailSo\Base\HtmlUtils::ClearTags()
if ('none' == oStyle.display if ('none' == oStyle.display
|| 'hidden' == oStyle.visibility || 'hidden' == oStyle.visibility
@ -432,7 +467,7 @@ export const
} }
// if (['CENTER','FORM'].includes(name)) { // if (['CENTER','FORM'].includes(name)) {
if ('O:P' === name || (nonEmptyTags.includes(name) && ('' == oElement.textContent.trim()))) { if (nonEmptyTags.includes(name) && ('' == oElement.textContent.trim())) {
('A' !== name || !oElement.querySelector('IMG')) && replaceWithChildren(oElement); ('A' !== name || !oElement.querySelector('IMG')) && replaceWithChildren(oElement);
return; return;
} }