Browse Source

Fixes #1467. Fix rendering of URLs enclosed with sharp brackets

such as <https://example.org>
JC Brand 6 years ago
parent
commit
03f9eb95da
5 changed files with 115 additions and 39 deletions
  1. 1 0
      CHANGES.md
  2. 71 35
      dist/converse.js
  3. 11 2
      spec/messages.js
  4. 32 1
      src/converse-message-view.js
  5. 0 1
      src/utils/html.js

+ 1 - 0
CHANGES.md

@@ -24,6 +24,7 @@
 - #1407: Silent errors when trying to use whitespace as MUC nickname
 - #1407: Silent errors when trying to use whitespace as MUC nickname
 - #1437: List of groupchats in modal doesn't scroll
 - #1437: List of groupchats in modal doesn't scroll
 - #1457: Wrong tooltip shown for "unbookmark" icon
 - #1457: Wrong tooltip shown for "unbookmark" icon
+- #1467: Fix rendering of URLs enclosed with sharp brackets such as <https://example.org>
 - #1479: Allow file upload by drag & drop also in MUCs
 - #1479: Allow file upload by drag & drop also in MUCs
 - #1487: New config option [muc_respect_autojoin](https://conversejs.org/docs/html/configuration.html#muc-respect-autojoin)
 - #1487: New config option [muc_respect_autojoin](https://conversejs.org/docs/html/configuration.html#muc-respect-autojoin)
 - #1501: Don't prompt for a reason if [auto_join_on_invite](https://conversejs.org/docs/html/configuration.html#auto-join-on-invite) is `true`
 - #1501: Don't prompt for a reason if [auto_join_on_invite](https://conversejs.org/docs/html/configuration.html#auto-join-on-invite) is `true`

+ 71 - 35
dist/converse.js

@@ -52577,23 +52577,25 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
 
 
 "use strict";
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 __webpack_require__.r(__webpack_exports__);
-/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js");
-/* harmony import */ var filesize__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! filesize */ "./node_modules/filesize/lib/filesize.js");
-/* harmony import */ var filesize__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(filesize__WEBPACK_IMPORTED_MODULE_1__);
-/* harmony import */ var _utils_html__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils/html */ "./src/utils/html.js");
-/* harmony import */ var templates_csn_html__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! templates/csn.html */ "./src/templates/csn.html");
-/* harmony import */ var templates_csn_html__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(templates_csn_html__WEBPACK_IMPORTED_MODULE_3__);
-/* harmony import */ var templates_file_progress_html__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! templates/file_progress.html */ "./src/templates/file_progress.html");
-/* harmony import */ var templates_file_progress_html__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(templates_file_progress_html__WEBPACK_IMPORTED_MODULE_4__);
-/* harmony import */ var templates_info_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! templates/info.html */ "./src/templates/info.html");
-/* harmony import */ var templates_info_html__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(templates_info_html__WEBPACK_IMPORTED_MODULE_5__);
-/* harmony import */ var templates_message_html__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! templates/message.html */ "./src/templates/message.html");
-/* harmony import */ var templates_message_html__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(templates_message_html__WEBPACK_IMPORTED_MODULE_6__);
-/* harmony import */ var templates_message_versions_modal_html__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! templates/message_versions_modal.html */ "./src/templates/message_versions_modal.html");
-/* harmony import */ var templates_message_versions_modal_html__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(templates_message_versions_modal_html__WEBPACK_IMPORTED_MODULE_7__);
-/* harmony import */ var _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @converse/headless/utils/emoji */ "./src/headless/utils/emoji.js");
-/* harmony import */ var xss__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js");
-/* harmony import */ var xss__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(xss__WEBPACK_IMPORTED_MODULE_9__);
+/* harmony import */ var urijs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! urijs */ "./node_modules/urijs/src/URI.js");
+/* harmony import */ var urijs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(urijs__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js");
+/* harmony import */ var filesize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! filesize */ "./node_modules/filesize/lib/filesize.js");
+/* harmony import */ var filesize__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(filesize__WEBPACK_IMPORTED_MODULE_2__);
+/* harmony import */ var _utils_html__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils/html */ "./src/utils/html.js");
+/* harmony import */ var templates_csn_html__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! templates/csn.html */ "./src/templates/csn.html");
+/* harmony import */ var templates_csn_html__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(templates_csn_html__WEBPACK_IMPORTED_MODULE_4__);
+/* harmony import */ var templates_file_progress_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! templates/file_progress.html */ "./src/templates/file_progress.html");
+/* harmony import */ var templates_file_progress_html__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(templates_file_progress_html__WEBPACK_IMPORTED_MODULE_5__);
+/* harmony import */ var templates_info_html__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! templates/info.html */ "./src/templates/info.html");
+/* harmony import */ var templates_info_html__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(templates_info_html__WEBPACK_IMPORTED_MODULE_6__);
+/* harmony import */ var templates_message_html__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! templates/message.html */ "./src/templates/message.html");
+/* harmony import */ var templates_message_html__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(templates_message_html__WEBPACK_IMPORTED_MODULE_7__);
+/* harmony import */ var templates_message_versions_modal_html__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! templates/message_versions_modal.html */ "./src/templates/message_versions_modal.html");
+/* harmony import */ var templates_message_versions_modal_html__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(templates_message_versions_modal_html__WEBPACK_IMPORTED_MODULE_8__);
+/* harmony import */ var _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @converse/headless/utils/emoji */ "./src/headless/utils/emoji.js");
+/* harmony import */ var xss__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js");
+/* harmony import */ var xss__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(xss__WEBPACK_IMPORTED_MODULE_10__);
 // Converse.js
 // Converse.js
 // https://conversejs.org
 // https://conversejs.org
 //
 //
@@ -52609,11 +52611,12 @@ __webpack_require__.r(__webpack_exports__);
 
 
 
 
 
 
-const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env,
+
+const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].env,
       Backbone = _converse$env.Backbone,
       Backbone = _converse$env.Backbone,
       _ = _converse$env._,
       _ = _converse$env._,
       moment = _converse$env.moment;
       moment = _converse$env.moment;
-_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add('converse-message-view', {
+_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].plugins.add('converse-message-view', {
   dependencies: ["converse-modal", "converse-chatboxviews"],
   dependencies: ["converse-modal", "converse-chatboxviews"],
 
 
   initialize() {
   initialize() {
@@ -52623,13 +52626,45 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
     const _converse = this._converse,
     const _converse = this._converse,
           __ = _converse.__;
           __ = _converse.__;
 
 
+    function onTagFoundDuringXSSFilter(tag, html, options) {
+      /* This function gets called by the XSS library whenever it finds
+       * what it thinks is a new HTML tag.
+       *
+       * It thinks that something like <https://example.com> is an HTML
+       * tag and then escapes the <> chars.
+       *
+       * We want to avoid this, because it prevents these URLs from being
+       * shown properly (whithout the trailing &gt;).
+       *
+       * The URI lib correctly trims a trailing >, but not a trailing &gt;
+       */
+      if (options.isClosing) {
+        // Closing tags don't match our use-case
+        return;
+      }
+
+      const uri = new urijs__WEBPACK_IMPORTED_MODULE_0___default.a(tag);
+      const protocol = uri.protocol().toLowerCase();
+
+      if (!_.includes(["https", "http", "xmpp", "ftp"], protocol)) {
+        // Not a URL, the tag will get filtered as usual
+        return;
+      }
+
+      if (uri.equals(tag) && `<${tag}>` === html.toLocaleLowerCase()) {
+        // We have something like <https://example.com>, and don't want
+        // to filter it.
+        return html;
+      }
+    }
+
     _converse.api.settings.update({
     _converse.api.settings.update({
       'show_images_inline': true
       'show_images_inline': true
     });
     });
 
 
     _converse.MessageVersionsModal = _converse.BootstrapModal.extend({
     _converse.MessageVersionsModal = _converse.BootstrapModal.extend({
       toHTML() {
       toHTML() {
-        return templates_message_versions_modal_html__WEBPACK_IMPORTED_MODULE_7___default()(_.extend(this.model.toJSON(), {
+        return templates_message_versions_modal_html__WEBPACK_IMPORTED_MODULE_8___default()(_.extend(this.model.toJSON(), {
           '__': __
           '__': __
         }));
         }));
       }
       }
@@ -52650,7 +52685,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
       },
       },
 
 
       async render() {
       async render() {
-        const is_followup = _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].hasClass('chat-msg--followup', this.el);
+        const is_followup = _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].hasClass('chat-msg--followup', this.el);
 
 
         if (this.model.isOnlyChatStateNotification()) {
         if (this.model.isOnlyChatStateNotification()) {
           this.renderChatStateNotification();
           this.renderChatStateNotification();
@@ -52669,7 +52704,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
         }
         }
 
 
         if (is_followup) {
         if (is_followup) {
-          _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addClass('chat-msg--followup', this.el);
+          _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].addClass('chat-msg--followup', this.el);
         }
         }
 
 
         return this.el;
         return this.el;
@@ -52699,8 +52734,8 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
           return;
           return;
         }
         }
 
 
-        this.el.addEventListener('animationend', () => _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].removeClass('onload', this.el));
-        _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addClass('onload', this.el);
+        this.el.addEventListener('animationend', () => _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].removeClass('onload', this.el));
+        _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].addClass('onload', this.el);
       },
       },
 
 
       replaceElement(msg) {
       replaceElement(msg) {
@@ -52717,7 +52752,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
               moment_time = moment(this.model.get('time')),
               moment_time = moment(this.model.get('time')),
               role = this.model.vcard ? this.model.vcard.get('role') : null,
               role = this.model.vcard ? this.model.vcard.get('role') : null,
               roles = role ? role.split(',') : [];
               roles = role ? role.split(',') : [];
-        const msg = _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].stringToElement(templates_message_html__WEBPACK_IMPORTED_MODULE_6___default()(_.extend(this.model.toJSON(), {
+        const msg = _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].stringToElement(templates_message_html__WEBPACK_IMPORTED_MODULE_7___default()(_.extend(this.model.toJSON(), {
           '__': __,
           '__': __,
           'is_me_message': is_me_message,
           'is_me_message': is_me_message,
           'roles': roles,
           'roles': roles,
@@ -52730,7 +52765,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
         const url = this.model.get('oob_url');
         const url = this.model.get('oob_url');
 
 
         if (url) {
         if (url) {
-          msg.querySelector('.chat-msg__media').innerHTML = _.flow(_.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderFileURL, _converse), _.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderMovieURL, _converse), _.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderAudioURL, _converse), _.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderImageURL, _converse))(url);
+          msg.querySelector('.chat-msg__media').innerHTML = _.flow(_.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].renderFileURL, _converse), _.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].renderMovieURL, _converse), _.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].renderAudioURL, _converse), _.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].renderImageURL, _converse))(url);
         }
         }
 
 
         let text = this.getMessageText();
         let text = this.getMessageText();
@@ -52741,13 +52776,14 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
             text = text.substring(4);
             text = text.substring(4);
           }
           }
 
 
-          text = xss__WEBPACK_IMPORTED_MODULE_9___default.a.filterXSS(text, {
-            'whiteList': {}
+          text = xss__WEBPACK_IMPORTED_MODULE_10___default.a.filterXSS(text, {
+            'whiteList': {},
+            'onTag': onTagFoundDuringXSSFilter
           });
           });
-          msg_content.innerHTML = _.flow(_.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].geoUriToHttp, _, _converse.geouri_replacement), _.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addMentionsMarkup, _, this.model.get('references'), this.model.collection.chatbox), _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addHyperlinks, _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderNewLines, _.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addEmoji, _converse, _))(text);
+          msg_content.innerHTML = _.flow(_.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].geoUriToHttp, _, _converse.geouri_replacement), _.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].addMentionsMarkup, _, this.model.get('references'), this.model.collection.chatbox), _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].addHyperlinks, _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].renderNewLines, _.partial(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].addEmoji, _converse, _))(text);
         }
         }
 
 
-        const promise = _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderImageURLs(_converse, msg_content);
+        const promise = _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].renderImageURLs(_converse, msg_content);
 
 
         if (this.model.get('type') !== 'headline') {
         if (this.model.get('type') !== 'headline') {
           this.renderAvatar(msg);
           this.renderAvatar(msg);
@@ -52760,7 +52796,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
 
 
       renderErrorMessage() {
       renderErrorMessage() {
         const moment_time = moment(this.model.get('time')),
         const moment_time = moment(this.model.get('time')),
-              msg = _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].stringToElement(templates_info_html__WEBPACK_IMPORTED_MODULE_5___default()(_.extend(this.model.toJSON(), {
+              msg = _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].stringToElement(templates_info_html__WEBPACK_IMPORTED_MODULE_6___default()(_.extend(this.model.toJSON(), {
           'extra_classes': 'chat-error',
           'extra_classes': 'chat-error',
           'isodate': moment_time.format()
           'isodate': moment_time.format()
         })));
         })));
@@ -52791,7 +52827,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
         }
         }
 
 
         const isodate = moment().format();
         const isodate = moment().format();
-        this.replaceElement(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].stringToElement(templates_csn_html__WEBPACK_IMPORTED_MODULE_3___default()({
+        this.replaceElement(_converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].stringToElement(templates_csn_html__WEBPACK_IMPORTED_MODULE_4___default()({
           'message': text,
           'message': text,
           'from': from,
           'from': from,
           'isodate': isodate
           'isodate': isodate
@@ -52799,10 +52835,10 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
       },
       },
 
 
       renderFileUploadProgresBar() {
       renderFileUploadProgresBar() {
-        const msg = _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].stringToElement(templates_file_progress_html__WEBPACK_IMPORTED_MODULE_4___default()(_.extend(this.model.toJSON(), {
+        const msg = _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].stringToElement(templates_file_progress_html__WEBPACK_IMPORTED_MODULE_5___default()(_.extend(this.model.toJSON(), {
           '__': __,
           '__': __,
           'filename': this.model.file.name,
           'filename': this.model.file.name,
-          'filesize': filesize__WEBPACK_IMPORTED_MODULE_1___default()(this.model.file.size)
+          'filesize': filesize__WEBPACK_IMPORTED_MODULE_2___default()(this.model.file.size)
         })));
         })));
         this.replaceElement(msg);
         this.replaceElement(msg);
         this.renderAvatar();
         this.renderAvatar();
@@ -52840,7 +52876,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
 
 
       processMessageText() {
       processMessageText() {
         var text = this.get('message');
         var text = this.get('message');
-        text = _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].geoUriToHttp(text, _converse.geouri_replacement);
+        text = _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_9__["default"].geoUriToHttp(text, _converse.geouri_replacement);
       },
       },
 
 
       getExtraMessageClasses() {
       getExtraMessageClasses() {

+ 11 - 2
spec/messages.js

@@ -897,12 +897,21 @@
             expect(msg.textContent).toEqual(message);
             expect(msg.textContent).toEqual(message);
             expect(msg.innerHTML).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>');
             expect(msg.innerHTML).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>');
 
 
-            message = "https://en.wikipedia.org/wiki/Ender's_Game";
+            message = "<https://bugs.documentfoundation.org/show_bug.cgi?id=123737>";
             await test_utils.sendMessage(view, message);
             await test_utils.sendMessage(view, message);
 
 
             msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
             msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
             expect(msg.textContent).toEqual(message);
             expect(msg.textContent).toEqual(message);
-            expect(msg.innerHTML).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>');
+            expect(msg.innerHTML).toEqual(
+                `&lt;<a target="_blank" rel="noopener" href="https://bugs.documentfoundation.org/show_bug.cgi?id=123737">https://bugs.documentfoundation.org/show_bug.cgi?id=123737</a>&gt;`);
+
+            message = '<http://www.opkode.com/"onmouseover="alert(1)"whatever>';
+            await test_utils.sendMessage(view, message);
+
+            msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
+            expect(msg.textContent).toEqual(message);
+            expect(msg.innerHTML).toEqual(
+                '&lt;<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>&gt;');
             done();
             done();
         }));
         }));
 
 

+ 32 - 1
src/converse-message-view.js

@@ -4,6 +4,7 @@
 // Copyright (c) 2013-2019, the Converse.js developers
 // Copyright (c) 2013-2019, the Converse.js developers
 // Licensed under the Mozilla Public License (MPLv2)
 // Licensed under the Mozilla Public License (MPLv2)
 
 
+import URI from "urijs";
 import converse from  "@converse/headless/converse-core";
 import converse from  "@converse/headless/converse-core";
 import filesize from "filesize";
 import filesize from "filesize";
 import html from "./utils/html";
 import html from "./utils/html";
@@ -30,6 +31,36 @@ converse.plugins.add('converse-message-view', {
             { __ } = _converse;
             { __ } = _converse;
 
 
 
 
+        function onTagFoundDuringXSSFilter (tag, html, options) {
+            /* This function gets called by the XSS library whenever it finds
+             * what it thinks is a new HTML tag.
+             *
+             * It thinks that something like <https://example.com> is an HTML
+             * tag and then escapes the <> chars.
+             *
+             * We want to avoid this, because it prevents these URLs from being
+             * shown properly (whithout the trailing &gt;).
+             *
+             * The URI lib correctly trims a trailing >, but not a trailing &gt;
+             */
+            if (options.isClosing) {
+                // Closing tags don't match our use-case
+                return;
+            }
+            const uri = new URI(tag);
+            const protocol = uri.protocol().toLowerCase();
+            if (!_.includes(["https", "http", "xmpp", "ftp"], protocol)) {
+                // Not a URL, the tag will get filtered as usual
+                return;
+            }
+            if (uri.equals(tag) && `<${tag}>` === html.toLocaleLowerCase()) {
+                // We have something like <https://example.com>, and don't want
+                // to filter it.
+                return html;
+            }
+        }
+
+
         _converse.api.settings.update({
         _converse.api.settings.update({
             'show_images_inline': true
             'show_images_inline': true
         });
         });
@@ -146,7 +177,7 @@ converse.plugins.add('converse-message-view', {
                     if (is_me_message) {
                     if (is_me_message) {
                         text = text.substring(4);
                         text = text.substring(4);
                     }
                     }
-                    text = xss.filterXSS(text, {'whiteList': {}});
+                    text = xss.filterXSS(text, {'whiteList': {}, 'onTag': onTagFoundDuringXSSFilter});
                     msg_content.innerHTML = _.flow(
                     msg_content.innerHTML = _.flow(
                         _.partial(u.geoUriToHttp, _, _converse.geouri_replacement),
                         _.partial(u.geoUriToHttp, _, _converse.geouri_replacement),
                         _.partial(u.addMentionsMarkup, _, this.model.get('references'), this.model.collection.chatbox),
                         _.partial(u.addMentionsMarkup, _, this.model.get('references'), this.model.collection.chatbox),

+ 0 - 1
src/utils/html.js

@@ -138,7 +138,6 @@ u.renderFileURL = function (_converse, url) {
     })
     })
 };
 };
 
 
-
 u.renderImageURL = function (_converse, url) {
 u.renderImageURL = function (_converse, url) {
     if (!_converse.show_images_inline) {
     if (!_converse.show_images_inline) {
         return u.addHyperlinks(url);
         return u.addHyperlinks(url);