diff --git a/frontend/3rdparty/js/HtmlSanitizer.js b/frontend/3rdparty/js/HtmlSanitizer.js
new file mode 100644
index 0000000..2ad8388
--- /dev/null
+++ b/frontend/3rdparty/js/HtmlSanitizer.js
@@ -0,0 +1,111 @@
+//JavaScript HTML Sanitizer, (c) Alexander Yumashev, Jitbit Software.
+
+//homepage https://github.com/jitbit/HtmlSanitizer
+
+//License: MIT https://github.com/jitbit/HtmlSanitizer/blob/master/LICENSE
+
+console.log('Sanitizer loading');
+
+var HtmlSanitizer = new (function () {
+
+ var tagWhitelist_ = {
+ 'A': true, 'ABBR': true, 'B': true, 'BLOCKQUOTE': true, 'BODY': true, 'BR': true, 'CENTER': true, 'CODE': true, 'DIV': true, 'EM': true, 'FONT': true,
+ 'H1': true, 'H2': true, 'H3': true, 'H4': true, 'H5': true, 'H6': true, 'HR': true, 'I': true, 'IMG': true, 'LABEL': true, 'LI': true, 'OL': true, 'P': true, 'PRE': true,
+ 'SMALL': true, 'SOURCE': true, 'SPAN': true, 'STRONG': true, 'TABLE': true, 'TBODY': true, 'TR': true, 'TD': true, 'TH': true, 'THEAD': true, 'UL': true, 'U': true, 'VIDEO': true
+ };
+
+ var contentTagWhiteList_ = { 'FORM': true }; //tags that will be converted to DIVs
+
+ var attributeWhitelist_ = { 'align': true, 'color': true, 'controls': true, 'height': true, 'href': true, 'src': true, 'style': true, 'target': true, 'title': true, 'type': true, 'width': true };
+
+ var cssWhitelist_ = { 'color': true, 'background-color': true, 'font-size': true, 'text-align': true, 'text-decoration': true, 'font-weight': true };
+
+ var schemaWhiteList_ = [ 'http:', 'https:', 'data:', 'm-files:', 'file:', 'ftp:' ]; //which "protocols" are allowed in "href", "src" etc
+
+ var uriAttributes_ = { 'href': true, 'action': true };
+
+ this.SanitizeHtml = function(input) {
+ input = input.trim();
+ if (input == "") return ""; //to save performance and not create iframe
+
+ //firefox "bogus node" workaround
+ if (input == "
") return "";
+
+ var iframe = document.createElement('iframe');
+ if (iframe['sandbox'] === undefined) {
+ alert('Your browser does not support sandboxed iframes. Please upgrade to a modern browser.');
+ return '';
+ }
+ iframe['sandbox'] = 'allow-same-origin';
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe); // necessary so the iframe contains a document
+ var iframedoc = iframe.contentDocument || iframe.contentWindow.document;
+ if (iframedoc.body == null) iframedoc.write("
"); // null in IE
+ iframedoc.body.innerHTML = input;
+
+ function makeSanitizedCopy(node) {
+ if (node.nodeType == Node.TEXT_NODE) {
+ var newNode = node.cloneNode(true);
+ } else if (node.nodeType == Node.ELEMENT_NODE && (tagWhitelist_[node.tagName] || contentTagWhiteList_[node.tagName])) {
+
+ //remove useless empty spans (lots of those when pasting from MS Outlook)
+ if ((node.tagName == "SPAN" || node.tagName == "B" || node.tagName == "I" || node.tagName == "U")
+ && node.innerHTML.trim() == "") {
+ return document.createDocumentFragment();
+ }
+
+ if (contentTagWhiteList_[node.tagName])
+ newNode = iframedoc.createElement('DIV'); //convert to DIV
+ else
+ newNode = iframedoc.createElement(node.tagName);
+
+ for (var i = 0; i < node.attributes.length; i++) {
+ var attr = node.attributes[i];
+ if (attributeWhitelist_[attr.name]) {
+ if (attr.name == "style") {
+ for (s = 0; s < node.style.length; s++) {
+ var styleName = node.style[s];
+ if (cssWhitelist_[styleName])
+ newNode.style.setProperty(styleName, node.style.getPropertyValue(styleName));
+ }
+ }
+ else {
+ if (uriAttributes_[attr.name]) { //if this is a "uri" attribute, that can have "javascript:" or something
+ if (attr.value.indexOf(":") > -1 && !startsWithAny(attr.value, schemaWhiteList_))
+ continue;
+ }
+ newNode.setAttribute(attr.name, attr.value);
+ }
+ }
+ }
+ for (i = 0; i < node.childNodes.length; i++) {
+ var subCopy = makeSanitizedCopy(node.childNodes[i]);
+ newNode.appendChild(subCopy, false);
+ }
+ } else {
+ newNode = document.createDocumentFragment();
+ }
+ return newNode;
+ };
+
+ var resultElement = makeSanitizedCopy(iframedoc.body);
+ document.body.removeChild(iframe);
+ return resultElement.innerHTML
+ .replace(/
]*>(\S)/g, "
\n$1")
+ .replace(/div>\n
Archive
+
+
+
<%- include('templates/navigation-bar.html') %>
diff --git a/frontend/js/util.js b/frontend/js/util.js
index 9c4dd08..05254f8 100644
--- a/frontend/js/util.js
+++ b/frontend/js/util.js
@@ -181,7 +181,7 @@ md.renderer.rules.emoji = function(token, idx) {
Vue.filter('markdown', function (value) {
if (!value) return '';
- return md.render(value);
+ return HtmlSanitizer.SanitizeHtml(md.render(value));
});
Vue.filter('prettyDateOffset', function (time) {
diff --git a/frontend/shared.html b/frontend/shared.html
index 0b6592b..542ccf0 100644
--- a/frontend/shared.html
+++ b/frontend/shared.html
@@ -77,6 +77,9 @@
{{ publicProfile.username }}
+
+
+
diff --git a/frontend/stream.html b/frontend/stream.html
index 2d5f32d..892db59 100644
--- a/frontend/stream.html
+++ b/frontend/stream.html
@@ -78,6 +78,9 @@ {{ publicProfile.username }}
+
+
+