do less rewriting for composer quote to prevent breaking pills

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2018-07-18 10:10:42 +01:00
parent 8bb08b1b75
commit 19e5dc5799
No known key found for this signature in database
GPG key ID: 3F879DA5AD802A5E
2 changed files with 113 additions and 101 deletions

View file

@ -141,31 +141,7 @@ export function isUrlPermitted(inputUrl) {
} }
} }
const sanitizeHtmlParams = { const transformTags = { // custom to matrix
allowedTags: [
'font', // custom to matrix for IRC-style font coloring
'del', // for markdown
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub',
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span', 'img',
],
allowedAttributes: {
// custom ones first:
font: ['color', 'data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix
span: ['data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix
a: ['href', 'name', 'target', 'rel'], // remote target: custom to matrix
img: ['src', 'width', 'height', 'alt', 'title'],
ol: ['start'],
code: ['class'], // We don't actually allow all classes, we filter them in transformTags
},
// Lots of these won't come up by default because we don't allow them
selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'],
// URL schemes we permit
allowedSchemes: PERMITTED_URL_SCHEMES,
allowProtocolRelative: false,
transformTags: { // custom to matrix
// add blank targets to all hyperlinks except vector URLs // add blank targets to all hyperlinks except vector URLs
'a': function(tagName, attribs) { 'a': function(tagName, attribs) {
if (attribs.href) { if (attribs.href) {
@ -198,7 +174,7 @@ const sanitizeHtmlParams = {
} }
} }
attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/ attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/
return { tagName: tagName, attribs: attribs }; return { tagName, attribs };
}, },
'img': function(tagName, attribs) { 'img': function(tagName, attribs) {
// Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag // Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag
@ -212,7 +188,7 @@ const sanitizeHtmlParams = {
attribs.width || 800, attribs.width || 800,
attribs.height || 600, attribs.height || 600,
); );
return { tagName: tagName, attribs: attribs }; return { tagName, attribs };
}, },
'code': function(tagName, attribs) { 'code': function(tagName, attribs) {
if (typeof attribs.class !== 'undefined') { if (typeof attribs.class !== 'undefined') {
@ -222,10 +198,7 @@ const sanitizeHtmlParams = {
}); });
attribs.class = classes.join(' '); attribs.class = classes.join(' ');
} }
return { return { tagName, attribs };
tagName: tagName,
attribs: attribs,
};
}, },
'*': function(tagName, attribs) { '*': function(tagName, attribs) {
// Delete any style previously assigned, style is an allowedTag for font and span // Delete any style previously assigned, style is an allowedTag for font and span
@ -257,9 +230,41 @@ const sanitizeHtmlParams = {
attribs.style = style; attribs.style = style;
} }
return { tagName: tagName, attribs: attribs }; return { tagName, attribs };
}, },
};
const sanitizeHtmlParams = {
allowedTags: [
'font', // custom to matrix for IRC-style font coloring
'del', // for markdown
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub',
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span', 'img',
],
allowedAttributes: {
// custom ones first:
font: ['color', 'data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix
span: ['data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix
a: ['href', 'name', 'target', 'rel'], // remote target: custom to matrix
img: ['src', 'width', 'height', 'alt', 'title'],
ol: ['start'],
code: ['class'], // We don't actually allow all classes, we filter them in transformTags
}, },
// Lots of these won't come up by default because we don't allow them
selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'],
// URL schemes we permit
allowedSchemes: PERMITTED_URL_SCHEMES,
allowProtocolRelative: false,
transformTags,
};
// this is the same as the above except with less rewriting
const composerSanitizeHtmlParams = Object.assign({}, sanitizeHtmlParams);
composerSanitizeHtmlParams.transformTags = {
'code': transformTags['code'],
'*': transformTags['*'],
}; };
class BaseHighlighter { class BaseHighlighter {
@ -385,6 +390,7 @@ class TextHighlighter extends BaseHighlighter {
* opts.stripReplyFallback: optional argument specifying the event is a reply and so fallback needs removing * opts.stripReplyFallback: optional argument specifying the event is a reply and so fallback needs removing
* opts.returnString: return an HTML string rather than JSX elements * opts.returnString: return an HTML string rather than JSX elements
* opts.emojiOne: optional param to do emojiOne (default true) * opts.emojiOne: optional param to do emojiOne (default true)
* opts.forComposerQuote: optional param to lessen the url rewriting done by sanitization, for quoting into composer
*/ */
export function bodyToHtml(content, highlights, opts={}) { export function bodyToHtml(content, highlights, opts={}) {
const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body; const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body;
@ -392,6 +398,11 @@ export function bodyToHtml(content, highlights, opts={}) {
const doEmojiOne = opts.emojiOne === undefined ? true : opts.emojiOne; const doEmojiOne = opts.emojiOne === undefined ? true : opts.emojiOne;
let bodyHasEmoji = false; let bodyHasEmoji = false;
let sanitizeParams = sanitizeHtmlParams;
if (opts.forComposerQuote) {
sanitizeParams = composerSanitizeHtmlParams;
}
let strippedBody; let strippedBody;
let safeBody; let safeBody;
let isDisplayedWithHtml; let isDisplayedWithHtml;
@ -403,10 +414,10 @@ export function bodyToHtml(content, highlights, opts={}) {
if (highlights && highlights.length > 0) { if (highlights && highlights.length > 0) {
const highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink); const highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
const safeHighlights = highlights.map(function(highlight) { const safeHighlights = highlights.map(function(highlight) {
return sanitizeHtml(highlight, sanitizeHtmlParams); return sanitizeHtml(highlight, sanitizeParams);
}); });
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeHtmlParams structure. // XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
sanitizeHtmlParams.textFilter = function(safeText) { sanitizeParams.textFilter = function(safeText) {
return highlighter.applyHighlights(safeText, safeHighlights).join(''); return highlighter.applyHighlights(safeText, safeHighlights).join('');
}; };
} }
@ -422,13 +433,13 @@ export function bodyToHtml(content, highlights, opts={}) {
// Only generate safeBody if the message was sent as org.matrix.custom.html // Only generate safeBody if the message was sent as org.matrix.custom.html
if (isHtmlMessage) { if (isHtmlMessage) {
isDisplayedWithHtml = true; isDisplayedWithHtml = true;
safeBody = sanitizeHtml(formattedBody, sanitizeHtmlParams); safeBody = sanitizeHtml(formattedBody, sanitizeParams);
} else { } else {
// ... or if there are emoji, which we insert as HTML alongside the // ... or if there are emoji, which we insert as HTML alongside the
// escaped plaintext body. // escaped plaintext body.
if (bodyHasEmoji) { if (bodyHasEmoji) {
isDisplayedWithHtml = true; isDisplayedWithHtml = true;
safeBody = sanitizeHtml(escape(strippedBody), sanitizeHtmlParams); safeBody = sanitizeHtml(escape(strippedBody), sanitizeParams);
} }
} }
@ -439,7 +450,7 @@ export function bodyToHtml(content, highlights, opts={}) {
safeBody = unicodeToImage(safeBody); safeBody = unicodeToImage(safeBody);
} }
} finally { } finally {
delete sanitizeHtmlParams.textFilter; delete sanitizeParams.textFilter;
} }
if (opts.returnString) { if (opts.returnString) {

View file

@ -373,6 +373,7 @@ export default class MessageComposerInput extends React.Component {
break; break;
case 'quote': { case 'quote': {
const html = HtmlUtils.bodyToHtml(payload.event.getContent(), null, { const html = HtmlUtils.bodyToHtml(payload.event.getContent(), null, {
forComposerQuote: true,
returnString: true, returnString: true,
emojiOne: false, emojiOne: false,
}); });