rewrite the HTML message stuff to fix XSS and improve clarity

This commit is contained in:
Matthew Hodgson 2015-10-30 18:19:20 +00:00
parent ffb9ce89c7
commit 35cebc56d3
2 changed files with 47 additions and 40 deletions

View file

@ -35,43 +35,46 @@ module.exports = React.createClass({
// FIXME: this entire class is copy-pasted from MTextTile :( // FIXME: this entire class is copy-pasted from MTextTile :(
render: function() { render: function() {
var content = this.props.mxEvent.getContent(); var content = this.props.mxEvent.getContent();
var body = content.body; var originalBody = content.body;
if (content.format === "org.matrix.custom.html") {
body = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
}
if (this.props.searchTerm) { if (this.props.searchTerm) {
var lastOffset = 0; var lastOffset = 0;
var bodyList = []; var bodyList = [];
var k = 0; var k = 0;
var offset; var offset;
// XXX: this probably doesn't handle stemming very well.
while ((offset = body.indexOf(this.props.searchTerm, lastOffset)) >= 0) { // XXX: rather than searching for the search term in the body,
if (content.format === "org.matrix.custom.html") { // we should be looking at the match delimiters returned by the FTS engine
if (content.format === "org.matrix.custom.html") {
var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
var safeSearchTerm = sanitizeHtml(this.props.searchTerm, sanitizeHtmlParams);
while ((offset = safeBody.indexOf(safeSearchTerm, lastOffset)) >= 0) {
// FIXME: we need to apply the search highlighting to only the text elements of HTML, which means // FIXME: we need to apply the search highlighting to only the text elements of HTML, which means
// hooking into the sanitizer parser rather than treating it as a string. Otherwise // hooking into the sanitizer parser rather than treating it as a string. Otherwise
// the act of highlighting a <b/> or whatever will break the HTML badly. // the act of highlighting a <b/> or whatever will break the HTML badly.
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: body.substring(lastOffset, offset) }} />); bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset, offset) }} />);
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: this.props.searchTerm }} className="mx_MessageTile_searchHighlight" />); bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeSearchTerm }} className="mx_MessageTile_searchHighlight" />);
lastOffset = offset + safeSearchTerm.length;
} }
else { bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset) }} />);
bodyList.push(<span key={ k++ } >{ body.substring(lastOffset, offset) }</span>);
bodyList.push(<span key={ k++ } className="mx_MessageTile_searchHighlight">{ this.props.searchTerm }</span>);
}
lastOffset = offset + this.props.searchTerm.length;
}
if (content.format === "org.matrix.custom.html") {
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: body.substring(lastOffset) }} />);
} }
else { else {
bodyList.push(<span key={ k++ }>{ body.substring(lastOffset) }</span>); while ((offset = originalBody.indexOf(this.props.searchTerm, lastOffset)) >= 0) {
bodyList.push(<span key={ k++ } >{ originalBody.substring(lastOffset, offset) }</span>);
bodyList.push(<span key={ k++ } className="mx_MessageTile_searchHighlight">{ this.props.searchTerm }</span>);
lastOffset = offset + this.props.searchTerm.length;
}
bodyList.push(<span key={ k++ }>{ originalBody.substring(lastOffset) }</span>);
} }
body = bodyList; body = bodyList;
} }
else { else {
if (content.format === "org.matrix.custom.html") { if (content.format === "org.matrix.custom.html") {
body = <span dangerouslySetInnerHTML={{ __html: body }} />; var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
body = <span dangerouslySetInnerHTML={{ __html: safeBody }} />;
}
else {
body = originalBody;
} }
} }

View file

@ -32,45 +32,49 @@ module.exports = React.createClass({
displayName: 'MTextTile', displayName: 'MTextTile',
mixins: [MTextTileController], mixins: [MTextTileController],
// FIXME: this entire class is copy-pasted from MTextTile :(
render: function() { render: function() {
var content = this.props.mxEvent.getContent(); var content = this.props.mxEvent.getContent();
var body = content.body; var originalBody = content.body;
if (content.format === "org.matrix.custom.html") {
body = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
}
if (this.props.searchTerm) { if (this.props.searchTerm) {
var lastOffset = 0; var lastOffset = 0;
var bodyList = []; var bodyList = [];
var k = 0; var k = 0;
var offset; var offset;
// XXX: this probably doesn't handle stemming very well.
while ((offset = body.indexOf(this.props.searchTerm, lastOffset)) >= 0) { // XXX: rather than searching for the search term in the body,
if (content.format === "org.matrix.custom.html") { // we should be looking at the match delimiters returned by the FTS engine
if (content.format === "org.matrix.custom.html") {
var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
var safeSearchTerm = sanitizeHtml(this.props.searchTerm, sanitizeHtmlParams);
while ((offset = safeBody.indexOf(safeSearchTerm, lastOffset)) >= 0) {
// FIXME: we need to apply the search highlighting to only the text elements of HTML, which means // FIXME: we need to apply the search highlighting to only the text elements of HTML, which means
// hooking into the sanitizer parser rather than treating it as a string. Otherwise // hooking into the sanitizer parser rather than treating it as a string. Otherwise
// the act of highlighting a <b/> or whatever will break the HTML badly. // the act of highlighting a <b/> or whatever will break the HTML badly.
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: body.substring(lastOffset, offset) }} />); bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset, offset) }} />);
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: this.props.searchTerm }} className="mx_MessageTile_searchHighlight" />); bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeSearchTerm }} className="mx_MessageTile_searchHighlight" />);
lastOffset = offset + safeSearchTerm.length;
} }
else { bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset) }} />);
bodyList.push(<span key={ k++ } >{ body.substring(lastOffset, offset) }</span>);
bodyList.push(<span key={ k++ } className="mx_MessageTile_searchHighlight">{ this.props.searchTerm }</span>);
}
lastOffset = offset + this.props.searchTerm.length;
}
if (content.format === "org.matrix.custom.html") {
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: body.substring(lastOffset) }} />);
} }
else { else {
bodyList.push(<span key={ k++ }>{ body.substring(lastOffset) }</span>); while ((offset = originalBody.indexOf(this.props.searchTerm, lastOffset)) >= 0) {
bodyList.push(<span key={ k++ } >{ originalBody.substring(lastOffset, offset) }</span>);
bodyList.push(<span key={ k++ } className="mx_MessageTile_searchHighlight">{ this.props.searchTerm }</span>);
lastOffset = offset + this.props.searchTerm.length;
}
bodyList.push(<span key={ k++ }>{ originalBody.substring(lastOffset) }</span>);
} }
body = bodyList; body = bodyList;
} }
else { else {
if (content.format === "org.matrix.custom.html") { if (content.format === "org.matrix.custom.html") {
body = <span dangerouslySetInnerHTML={{ __html: body }} />; var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
body = <span dangerouslySetInnerHTML={{ __html: safeBody }} />;
}
else {
body = originalBody;
} }
} }