Jelajahi Sumber

Add `js-xss` and use it to sanitize message HTML

JC Brand 8 tahun lalu
induk
melakukan
284e884766
9 mengubah file dengan 60 tambahan dan 37 penghapusan
  1. 1 0
      css/converse.css
  2. 1 0
      css/inverse.css
  3. 12 0
      package-lock.json
  4. 2 1
      package.json
  5. 1 0
      sass/_chatbox.scss
  6. 16 6
      src/config.js
  7. 5 1
      src/converse-chatview.js
  8. 1 1
      src/converse-core.js
  9. 21 28
      src/utils.js

+ 1 - 0
css/converse.css

@@ -1582,6 +1582,7 @@
       font-style: italic; }
       font-style: italic; }
     #converse-embedded-chat .chatbox .chat-body .chat-message,
     #converse-embedded-chat .chatbox .chat-body .chat-message,
     #conversejs .chatbox .chat-body .chat-message {
     #conversejs .chatbox .chat-body .chat-message {
+      overflow: auto;
       margin: 0.3em; }
       margin: 0.3em; }
       #converse-embedded-chat .chatbox .chat-body .chat-message span.chat-msg-author,
       #converse-embedded-chat .chatbox .chat-body .chat-message span.chat-msg-author,
       #conversejs .chatbox .chat-body .chat-message span.chat-msg-author {
       #conversejs .chatbox .chat-body .chat-message span.chat-msg-author {

+ 1 - 0
css/inverse.css

@@ -1628,6 +1628,7 @@ body {
       font-style: italic; }
       font-style: italic; }
     #converse-embedded-chat .chatbox .chat-body .chat-message,
     #converse-embedded-chat .chatbox .chat-body .chat-message,
     #conversejs .chatbox .chat-body .chat-message {
     #conversejs .chatbox .chat-body .chat-message {
+      overflow: auto;
       margin: 0.3em; }
       margin: 0.3em; }
       #converse-embedded-chat .chatbox .chat-body .chat-message span.chat-msg-author,
       #converse-embedded-chat .chatbox .chat-body .chat-message span.chat-msg-author,
       #conversejs .chatbox .chat-body .chat-message span.chat-msg-author {
       #conversejs .chatbox .chat-body .chat-message span.chat-msg-author {

+ 12 - 0
package-lock.json

@@ -1118,6 +1118,12 @@
       "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
       "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
       "dev": true
       "dev": true
     },
     },
+    "cssfilter": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.9.tgz",
+      "integrity": "sha1-j1zrOqvXaNtTnaRYKyFS1j73cV4=",
+      "dev": true
+    },
     "currently-unhandled": {
     "currently-unhandled": {
       "version": "0.4.1",
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
       "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@@ -6908,6 +6914,12 @@
       "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=",
       "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=",
       "dev": true
       "dev": true
     },
     },
+    "xss": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/xss/-/xss-0.3.3.tgz",
+      "integrity": "sha1-oBQ2De4QMXMx+edCWBQfftA/x4Q=",
+      "dev": true
+    },
     "xtend": {
     "xtend": {
       "version": "4.0.1",
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",

+ 2 - 1
package.json

@@ -78,7 +78,8 @@
     "strophejs-plugin-vcard": "0.0.1",
     "strophejs-plugin-vcard": "0.0.1",
     "text": "requirejs/text#2.0.15",
     "text": "requirejs/text#2.0.15",
     "uglify-es": "^3.0.24",
     "uglify-es": "^3.0.24",
-    "wait-until-promise": "^1.0.0"
+    "wait-until-promise": "^1.0.0",
+    "xss": "^0.3.3"
   },
   },
   "dependencies": {}
   "dependencies": {}
 }
 }

+ 1 - 0
sass/_chatbox.scss

@@ -156,6 +156,7 @@
                 font-style: italic;
                 font-style: italic;
             }
             }
             .chat-message {
             .chat-message {
+                overflow: auto; // Ensures that content stays inside
                 margin: 0.3em;
                 margin: 0.3em;
                 span {
                 span {
                     &.chat-msg-author {
                     &.chat-msg-author {

+ 16 - 6
src/config.js

@@ -19,15 +19,18 @@ require.config({
         "awesomplete":              "node_modules/awesomplete-avoid-xss/awesomplete",
         "awesomplete":              "node_modules/awesomplete-avoid-xss/awesomplete",
         "babel":                    "node_modules/requirejs-babel/babel-5.8.34.min",
         "babel":                    "node_modules/requirejs-babel/babel-5.8.34.min",
         "backbone":                 "node_modules/backbone/backbone",
         "backbone":                 "node_modules/backbone/backbone",
-        "backbone.noconflict":      "src/backbone.noconflict",
         "backbone.browserStorage":  "node_modules/backbone.browserStorage/backbone.browserStorage",
         "backbone.browserStorage":  "node_modules/backbone.browserStorage/backbone.browserStorage",
+        "backbone.noconflict":      "src/backbone.noconflict",
         "backbone.overview":        "node_modules/backbone.overview/backbone.overview",
         "backbone.overview":        "node_modules/backbone.overview/backbone.overview",
         "emojione":                 "node_modules/emojione/lib/js/emojione",
         "emojione":                 "node_modules/emojione/lib/js/emojione",
-        "eventemitter":             "node_modules/otr/build/dep/eventemitter",
         "es6-promise":              "node_modules/es6-promise/dist/es6-promise.auto",
         "es6-promise":              "node_modules/es6-promise/dist/es6-promise.auto",
+        "eventemitter":             "node_modules/otr/build/dep/eventemitter",
         "jquery":                   "node_modules/jquery/dist/jquery",
         "jquery":                   "node_modules/jquery/dist/jquery",
-        "jquery.noconflict":        "src/jquery.noconflict",
         "jquery.browser":           "node_modules/jquery.browser/dist/jquery.browser",
         "jquery.browser":           "node_modules/jquery.browser/dist/jquery.browser",
+        "jquery.noconflict":        "src/jquery.noconflict",
+        "lodash":                   "node_modules/lodash/lodash",
+        "lodash.converter":         "3rdparty/lodash.fp",
+        "lodash.noconflict":        "src/lodash.noconflict",
         "pluggable":                "node_modules/pluggable.js/dist/pluggable",
         "pluggable":                "node_modules/pluggable.js/dist/pluggable",
         "polyfill":                 "src/polyfill",
         "polyfill":                 "src/polyfill",
         "sizzle":                   "node_modules/jquery/sizzle/dist/sizzle",
         "sizzle":                   "node_modules/jquery/sizzle/dist/sizzle",
@@ -39,11 +42,10 @@ require.config({
         "text":                     "node_modules/text/text",
         "text":                     "node_modules/text/text",
         "tpl":                      "node_modules/lodash-template-loader/loader",
         "tpl":                      "node_modules/lodash-template-loader/loader",
         "typeahead":                "components/typeahead.js/index",
         "typeahead":                "components/typeahead.js/index",
-        "lodash":                   "node_modules/lodash/lodash",
-        "lodash.converter":         "3rdparty/lodash.fp",
-        "lodash.noconflict":        "src/lodash.noconflict",
         "underscore":               "src/underscore-shim",
         "underscore":               "src/underscore-shim",
         "utils":                    "src/utils",
         "utils":                    "src/utils",
+        "xss.noconflict":               "src/xss.noconflict",
+        "xss":                      "node_modules/xss/dist/xss",
 
 
         // Converse
         // Converse
         "converse":                 "src/converse",
         "converse":                 "src/converse",
@@ -139,5 +141,13 @@ require.config({
     shim: {
     shim: {
         'awesomplete':          { exports: 'Awesomplete'},
         'awesomplete':          { exports: 'Awesomplete'},
         'emojione':             { exports: 'emojione'},
         'emojione':             { exports: 'emojione'},
+        'xss':                  {
+            init: function (xss_noconflict) {
+                return {
+                    filterXSS: window.filterXSS,
+                    filterCSS: window.filterCSS
+                }
+            }
+        }
     }
     }
 });
 });

+ 5 - 1
src/converse-chatview.js

@@ -11,6 +11,7 @@
             "jquery.noconflict",
             "jquery.noconflict",
             "converse-core",
             "converse-core",
             "emojione",
             "emojione",
+            "xss",
             "tpl!chatbox",
             "tpl!chatbox",
             "tpl!new_day",
             "tpl!new_day",
             "tpl!action",
             "tpl!action",
@@ -25,6 +26,7 @@
             $,
             $,
             converse,
             converse,
             emojione,
             emojione,
+            xss,
             tpl_chatbox,
             tpl_chatbox,
             tpl_new_day,
             tpl_new_day,
             tpl_action,
             tpl_action,
@@ -427,7 +429,9 @@
                     if (_converse.visible_toolbar_buttons.emoji) {
                     if (_converse.visible_toolbar_buttons.emoji) {
                         text = utils.addEmoji(_converse, emojione, text);
                         text = utils.addEmoji(_converse, emojione, text);
                     }
                     }
-                    $msg.find('.chat-msg-content').first().text(text).addHyperlinks();
+                    const msg_content = $msg[0].querySelector('.chat-msg-content');
+                    msg_content.innerHTML = xss.filterXSS(text, {'whiteList': {}});
+                    utils.addHyperlinks(msg_content);
                     return $msg;
                     return $msg;
                 },
                 },
 
 

+ 1 - 1
src/converse-core.js

@@ -136,7 +136,7 @@
             logger.warn(`WARNING: ${txt}`);
             logger.warn(`WARNING: ${txt}`);
         } else if (level === Strophe.LogLevel.FATAL) {
         } else if (level === Strophe.LogLevel.FATAL) {
             if (_converse.debug) {
             if (_converse.debug) {
-                logger.trace(`FATAL: ${txt}`);
+                logger.error(`FATAL: ${txt}`);
             } else {
             } else {
                 logger.error(`FATAL: ${txt}`);
                 logger.error(`FATAL: ${txt}`);
             }
             }

+ 21 - 28
src/utils.js

@@ -101,34 +101,6 @@
         el.innerHTML = html;
         el.innerHTML = html;
     }, 500);
     }, 500);
 
 
-    $.fn.addHyperlinks = function () {
-        if (this.length > 0) {
-            this.each(function (i, obj) {
-                var prot, escaped_url;
-                var x = obj.innerHTML;
-                var list = x.match(/\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<]{2,200}\b/g );
-                if (list) {
-                    for (i=0; i<list.length; i++) {
-                        prot = list[i].indexOf('http://') === 0 || list[i].indexOf('https://') === 0 ? '' : 'http://';
-                        escaped_url = encodeURI(decodeURI(list[i])).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
-                        x = x.replace(list[i], '<a target="_blank" rel="noopener" href="' + prot + escaped_url + '">'+ list[i] + '</a>' );
-                    }
-                }
-                obj.innerHTML = x;
-                _.forEach(list, function (url) {
-                    isImage(unescapeHTML(url)).then(function (img) {
-                        img.className = 'chat-image';
-                        var a = obj.querySelector('a');
-                        if (!_.isNull(a)) {
-                            throttledHTML(a, img.outerHTML);
-                        }
-                    });
-                });
-            });
-        }
-        return this;
-    };
-
     function calculateSlideStep (height) {
     function calculateSlideStep (height) {
         if (height > 100) {
         if (height > 100) {
             return 10;
             return 10;
@@ -185,6 +157,27 @@
         }
         }
     };
     };
 
 
+    utils.addHyperlinks = function (obj) {
+        var x = obj.innerHTML;
+        var list = x.match(/\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<]{2,200}\b/g ) || [];
+        _.each(list, (match) => {
+            const prot = match.indexOf('http://') === 0 || match.indexOf('https://') === 0 ? '' : 'http://';
+            const url = prot + encodeURI(decodeURI(match)).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
+            x = x.replace(match, '<a target="_blank" rel="noopener" href="' + url + '">'+ match + '</a>' );
+        });
+        obj.innerHTML = x;
+        _.forEach(list, function (url) {
+            isImage(unescapeHTML(url)).then(function (img) {
+                img.className = 'chat-image';
+                var a = obj.querySelector('a');
+                if (!_.isNull(a)) {
+                    throttledHTML(a, img.outerHTML);
+                }
+            });
+        });
+        return obj;
+    };
+
     utils.slideInAllElements = function (elements) {
     utils.slideInAllElements = function (elements) {
         return Promise.all(
         return Promise.all(
             _.map(
             _.map(