Added feature to allow <style> in messages

This commit is contained in:
the-djmaze 2023-02-14 14:54:38 +01:00
parent aae3072209
commit 64818025e8
46 changed files with 154 additions and 6 deletions

82
dev/Common/CSS.js Normal file
View file

@ -0,0 +1,82 @@
export class CSS
{
/*
Parses given css string, and returns css object
keys as selectors and values are css rules
eliminates all css comments before parsing
@param source css string to be parsed
@return object css
*/
parse(source) {
const css = [];
css.toString = () => css.reduce(
(ret, tmp) =>
ret + tmp.selector + ' {\n'
+ (tmp.type === 'media' ? tmp.subStyles.toString() : tmp.rules)
+ '}\n'
,
''
);
/**
* Given css array, parses it and then for every selector,
* prepends namespace to prevent css collision issues
*/
css.applyNamespace = (namespace) => css.forEach(obj => {
if (obj.type === 'media') {
obj.subStyles.applyNamespace(namespace);
} else {
obj.selector = obj.selector.split(',').map(selector => namespace + ' ' + selector).join(',');
}
});
if (source) {
source = source
// strip comments
.replace(/\/\*[\s\S]*?\*\/|<!--|-->/gi, '')
// strip import statements
.replace(/@import .*?;/gi , '')
// strip keyframe statements
.replace(/((@.*?keyframes [\s\S]*?){([\s\S]*?}\s*?)})/gi, '');
// 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,
arr;
while (true) {
arr = unified.exec(source);
if (arr === null) {
break;
}
let selector = arr[arr[2] === undefined ? 5 : 2].split('\r\n').join('\n').trim()
// Never have more than a single line break in a row
.replace(/\n+/, "\n");
// determine the type
if (selector.includes('@media')) {
// we have a media query
css.push({
selector: selector,
type: 'media',
subStyles: this.parse(arr[3] + '\n}') //recursively parse media query inner css
});
} else if (!selector.includes('@') && ![':root','html','body'].includes(selector)) {
// we have standard css
css.push({
selector: selector,
rules: arr[6]
});
}
}
}
return css;
}
}

View file

@ -1,4 +1,5 @@
import { createElement } from 'Common/Globals';
import { CSS } from 'Common/CSS';
import { forEachObjectEntry, pInt } from 'Common/Utils';
import { SettingsUserStore } from 'Stores/User/Settings';
@ -103,7 +104,7 @@ export const
* @param {string} text
* @returns {string}
*/
cleanHtml = (html, oAttachments) => {
cleanHtml = (html, oAttachments, msgId) => {
let aColor;
const
debug = false, // Config()->Get('debug', 'enable', false);
@ -132,7 +133,7 @@ export const
},
allowedAttributes = [
// defaults
'name',
'name', 'class',
'dir', 'lang', 'style', 'title',
'background', 'bgcolor', 'alt', 'height', 'width', 'src', 'href',
'border', 'bordercolor', 'charset', 'direction',
@ -160,7 +161,7 @@ export const
'colspan', 'rowspan', 'headers'
],
disallowedTags = [
'STYLE','SVG','SCRIPT','TITLE','LINK','BASE','META',
'SVG','SCRIPT','TITLE','LINK','BASE','META',
'INPUT','OUTPUT','SELECT','BUTTON','TEXTAREA',
'BGSOUND','KEYGEN','SOURCE','OBJECT','EMBED','APPLET','IFRAME','FRAME','FRAMESET','VIDEO','AUDIO','AREA','MAP'
// Not supported by <template> element
@ -171,6 +172,8 @@ export const
];
tpl.innerHTML = html
// Strip Microsoft comments
.replace(/<!--\[if[\s\S]*?endif\]-->/gi, '')
// .replace(/<pre[^>]*>[\s\S]*?<\/pre>/gi, pre => pre.replace(/\n/g, '\n<br>'))
// Not supported by <template> element
// .replace(/<!doctype[^>]*>/gi, '')
@ -199,6 +202,17 @@ export const
const name = oElement.tagName,
oStyle = oElement.style;
if ('STYLE' === name) {
if (msgId) {
let css = new CSS().parse(oElement.textContent);
css.applyNamespace(msgId);
oElement.textContent = css;
} else {
oElement.remove();
}
return;
}
// \MailSo\Base\HtmlUtils::ClearTags()
if (disallowedTags.includes(name)
|| 'none' == oStyle.display

View file

@ -25,7 +25,9 @@ import { LanguageStore } from 'Stores/Language';
import Remote from 'Remote/User/Fetch';
const
msgHtml = msg => cleanHtml(msg.html(), msg.attachments()),
msgHtml = msg => cleanHtml(msg.html(), msg.attachments(),
SettingsUserStore.allowStyles() ? '#rl-msg-' + msg.hash : ''
),
toggleTag = (message, keyword) => {
const lower = keyword.toLowerCase(),

View file

@ -44,7 +44,7 @@ export class UserSettingsGeneral extends AbstractViewSettings {
['layout', 'messageReadDelay', 'messagesPerPage', 'checkMailInterval',
'editorDefaultType', 'requestReadReceipt', 'requestDsn', 'requireTLS', 'pgpSign', 'pgpEncrypt',
'viewHTML', 'viewImages', 'viewImagesWhitelist', 'removeColors',
'viewHTML', 'viewImages', 'viewImagesWhitelist', 'removeColors', 'allowStyles',
'hideDeleted', 'listInlineAttachments', 'simpleAttachmentsList', 'collapseBlockquotes', 'maxBlockquotesLevel',
'useCheckboxesInList', 'listGrouped', 'useThreads', 'replySameFolder', 'msgDefaultAction', 'allowSpellcheck'
].forEach(name => this[name] = SettingsUserStore[name]);
@ -102,7 +102,7 @@ export class UserSettingsGeneral extends AbstractViewSettings {
this.addSetting('Layout');
this.addSetting('MaxBlockquotesLevel');
this.addSettings(['ViewHTML', 'ViewImages', 'ViewImagesWhitelist', 'HideDeleted',
this.addSettings(['ViewHTML', 'ViewImages', 'ViewImagesWhitelist', 'HideDeleted', 'AllowStyles',
'ListInlineAttachments', 'simpleAttachmentsList', 'UseCheckboxesInList', 'listGrouped', 'ReplySameFolder',
'requestReadReceipt', 'requestDsn', 'requireTLS', 'pgpSign', 'pgpEncrypt', 'allowSpellcheck',
'DesktopNotifications', 'SoundNotification', 'CollapseBlockquotes']);

View file

@ -19,6 +19,7 @@ export const SettingsUserStore = new class {
viewImages: 0,
viewImagesWhitelist: '',
removeColors: 0,
allowStyles: 0,
collapseBlockquotes: 1,
maxBlockquotesLevel: 0,
listInlineAttachments: 0,
@ -88,6 +89,7 @@ export const SettingsUserStore = new class {
self.viewImages(SettingsGet('ViewImages'));
self.viewImagesWhitelist(SettingsGet('ViewImagesWhitelist'));
self.removeColors(SettingsGet('RemoveColors'));
self.allowStyles(SettingsGet('AllowStyles'));
self.collapseBlockquotes(SettingsGet('CollapseBlockquotes'));
self.maxBlockquotesLevel(SettingsGet('MaxBlockquotesLevel'));
self.listInlineAttachments(SettingsGet('ListInlineAttachments'));