import sanitizeHtml from 'sanitize-html';
import initMatrix from '../../../client/initMatrix';
function sanitizeColorizedTag(tagName, attributes) {
const attribs = { ...attributes };
const styles = [];
if (attributes['data-mx-color']) {
styles.push(`color: ${attributes['data-mx-color']};`);
}
if (attributes['data-mx-bg-color']) {
styles.push(`background-color: ${attributes['data-mx-bg-color']};`);
}
attribs.style = styles.join(' ');
return { tagName, attribs };
}
function sanitizeLinkTag(tagName, attribs) {
const userLink = attribs.href.match(/^https?:\/\/matrix.to\/#\/(@.+:.+)/);
if (userLink !== null) {
// convert user link to pill
const userId = userLink[1];
return {
tagName: 'span',
attribs: {
'data-mx-pill': userId,
},
};
}
return {
tagName,
attribs: {
...attribs,
target: '_blank',
rel: 'noreferrer noopener',
},
};
}
function sanitizeCodeTag(tagName, attributes) {
const attribs = { ...attributes };
let classes = [];
if (attributes.class) {
classes = attributes.class.split(/\s+/).filter((className) => className.match(/^language-(\w+)/));
}
return {
tagName,
attribs: {
...attribs,
class: classes.join(' '),
},
};
}
function sanitizeImgTag(tagName, attributes) {
const mx = initMatrix.matrixClient;
const { src } = attributes;
const attribs = { ...attributes };
delete attribs.src;
if (src.match(/^mxc:\/\//)) {
attribs.src = mx.mxcUrlToHttp(src);
}
return { tagName, attribs };
}
export function sanitizeCustomHtml(body) {
return sanitizeHtml(body, {
allowedTags: [
'font',
'del',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'blockquote',
'p',
'a',
'ul',
'ol',
'sup',
'sub',
'li',
'b',
'i',
'u',
'strong',
'em',
'strike',
'code',
'hr',
'br',
'div',
'table',
'thead',
'tbody',
'tr',
'th',
'td',
'caption',
'pre',
'span',
'img',
'details',
'summary',
],
allowedClasses: {},
allowedAttributes: {
ol: ['start'],
img: ['width', 'height', 'alt', 'title', 'src', 'data-mx-emoticon'],
a: ['name', 'target', 'href', 'rel'],
code: ['class'],
font: ['data-mx-bg-color', 'data-mx-color', 'color', 'style'],
span: ['data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'style', 'data-mx-pill'],
},
allowProtocolRelative: false,
allowedSchemesByTag: {
a: ['https', 'http', 'ftp', 'mailto', 'magnet'],
img: ['https', 'http'],
},
allowedStyles: {
'*': {
color: [/^#(0x)?[0-9a-f]+$/i],
'background-color': [/^#(0x)?[0-9a-f]+$/i],
},
},
nestingLimit: 100,
nonTextTags: [
'style', 'script', 'textarea', 'option', 'mx-reply',
],
transformTags: {
a: sanitizeLinkTag,
img: sanitizeImgTag,
code: sanitizeCodeTag,
font: sanitizeColorizedTag,
span: sanitizeColorizedTag,
},
});
}
export function sanitizeText(body) {
const tagsToReplace = {
'&': '&',
'<': '<',
'>': '>',
};
return body.replace(/[&<>]/g, (tag) => tagsToReplace[tag] || tag);
}