From 29af81e827cf863611efcbb9f14106434ea652be Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Fri, 20 Nov 2015 18:58:05 +0000
Subject: [PATCH] Refactor HTML markup stuff into its own class, and whitelist
 h1 and h2

---
 src/HtmlUtils.js                              | 79 +++++++++++++++++++
 .../vector/views/molecules/MNoticeTile.js     | 55 +------------
 src/skins/vector/views/molecules/MTextTile.js | 54 +------------
 3 files changed, 84 insertions(+), 104 deletions(-)
 create mode 100644 src/HtmlUtils.js

diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js
new file mode 100644
index 00000000..ff7af121
--- /dev/null
+++ b/src/HtmlUtils.js
@@ -0,0 +1,79 @@
+/*
+Copyright 2015 OpenMarket Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict';
+
+var React = require('react');
+var sanitizeHtml = require('sanitize-html');
+
+var allowedAttributes = sanitizeHtml.defaults.allowedAttributes;
+allowedAttributes['font'] = ['color'];
+var sanitizeHtmlParams = {
+    allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'font', 'h1', 'h2' ]),
+    allowedAttributes: allowedAttributes,
+};
+
+module.exports = {
+    bodyToHtml: function(content, searchTerm) {
+        var originalBody = content.body;
+        var body;
+
+        if (searchTerm) {
+            var lastOffset = 0;
+            var bodyList = [];
+            var k = 0;
+            var offset;
+
+            // XXX: rather than searching for the search term in the body,
+            // 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(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
+                    // 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.
+                    bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset, offset) }} />);
+                    bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeSearchTerm }} className="mx_MessageTile_searchHighlight" />);
+                    lastOffset = offset + safeSearchTerm.length;
+                }
+                bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset) }} />);
+            }
+            else {
+                while ((offset = originalBody.indexOf(searchTerm, lastOffset)) >= 0) {
+                    bodyList.push(<span key={ k++ } >{ originalBody.substring(lastOffset, offset) }</span>);
+                    bodyList.push(<span key={ k++ } className="mx_MessageTile_searchHighlight">{ searchTerm }</span>);
+                    lastOffset = offset + searchTerm.length;
+                }
+                bodyList.push(<span key={ k++ }>{ originalBody.substring(lastOffset) }</span>);
+            }
+            body = bodyList;
+        }
+        else {
+            if (content.format === "org.matrix.custom.html") {
+                var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
+                body = <span dangerouslySetInnerHTML={{ __html: safeBody }} />;
+            }
+            else {
+                body = originalBody;
+            }
+        }
+
+        return body;
+    }
+}
+
diff --git a/src/skins/vector/views/molecules/MNoticeTile.js b/src/skins/vector/views/molecules/MNoticeTile.js
index a0cedb1d..8d8c7792 100644
--- a/src/skins/vector/views/molecules/MNoticeTile.js
+++ b/src/skins/vector/views/molecules/MNoticeTile.js
@@ -17,67 +17,18 @@ limitations under the License.
 'use strict';
 
 var React = require('react');
-var sanitizeHtml = require('sanitize-html');
+var HtmlUtils = require('../../../../HtmlUtils');
 
 var MNoticeTileController = require('matrix-react-sdk/lib/controllers/molecules/MNoticeTile')
 
-var allowedAttributes = sanitizeHtml.defaults.allowedAttributes;
-allowedAttributes['font'] = ['color'];
-var sanitizeHtmlParams = {
-    allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'font' ]),
-    allowedAttributes: allowedAttributes,
-};
-
 module.exports = React.createClass({
     displayName: 'MNoticeTile',
     mixins: [MNoticeTileController],
 
-    // FIXME: this entire class is copy-pasted from MTextTile :(        
+    // XXX: fix horrible duplication with MTextTile
     render: function() {
         var content = this.props.mxEvent.getContent();
-        var originalBody = content.body;
-        var body;
-
-        if (this.props.searchTerm) {
-            var lastOffset = 0;
-            var bodyList = [];
-            var k = 0;
-            var offset;
-
-            // XXX: rather than searching for the search term in the body,
-            // 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
-                    // 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.
-                    bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset, offset) }} />);
-                    bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeSearchTerm }} className="mx_MessageTile_searchHighlight" />);
-                    lastOffset = offset + safeSearchTerm.length;
-                }
-                bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset) }} />);
-            }
-            else {
-                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;
-        }
-        else {
-            if (content.format === "org.matrix.custom.html") {
-                var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
-                body = <span dangerouslySetInnerHTML={{ __html: safeBody }} />;
-            }
-            else {
-                body = originalBody;
-            }
-        }
+        var body = HtmlUtils.bodyToHtml(content, this.props.searchTerm);
 
         return (
             <span ref="content" className="mx_MNoticeTile mx_MessageTile_content">
diff --git a/src/skins/vector/views/molecules/MTextTile.js b/src/skins/vector/views/molecules/MTextTile.js
index 12bafa37..aa5291d7 100644
--- a/src/skins/vector/views/molecules/MTextTile.js
+++ b/src/skins/vector/views/molecules/MTextTile.js
@@ -17,67 +17,17 @@ limitations under the License.
 'use strict';
 
 var React = require('react');
-var sanitizeHtml = require('sanitize-html');
+var HtmlUtils = require('../../../../HtmlUtils');
 
 var MTextTileController = require('matrix-react-sdk/lib/controllers/molecules/MTextTile')
 
-var allowedAttributes = sanitizeHtml.defaults.allowedAttributes;
-allowedAttributes['font'] = ['color'];
-var sanitizeHtmlParams = {
-    allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'font' ]),
-    allowedAttributes: allowedAttributes,
-};
-
 module.exports = React.createClass({
     displayName: 'MTextTile',
     mixins: [MTextTileController],
 
-    // FIXME: this entire class is copy-pasted from MTextTile :(        
     render: function() {
         var content = this.props.mxEvent.getContent();
-        var originalBody = content.body;
-        var body;
-
-        if (this.props.searchTerm) {
-            var lastOffset = 0;
-            var bodyList = [];
-            var k = 0;
-            var offset;
-
-            // XXX: rather than searching for the search term in the body,
-            // 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
-                    // 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.
-                    bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset, offset) }} />);
-                    bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeSearchTerm }} className="mx_MessageTile_searchHighlight" />);
-                    lastOffset = offset + safeSearchTerm.length;
-                }
-                bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset) }} />);
-            }
-            else {
-                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;
-        }
-        else {
-            if (content.format === "org.matrix.custom.html") {
-                var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
-                body = <span dangerouslySetInnerHTML={{ __html: safeBody }} />;
-            }
-            else {
-                body = originalBody;
-            }
-        }
+        var body = HtmlUtils.bodyToHtml(content, this.props.searchTerm);
 
         return (
             <span ref="content" className="mx_MTextTile mx_MessageTile_content">