Explorar el Código

Various fixes around rendering of URLs

JC Brand hace 5 años
padre
commit
4f76c0f1da

+ 1 - 1
sass/_messages.scss

@@ -196,7 +196,7 @@
                 a {
                     word-wrap: break-word;
                     word-break: break-all;
-                    display: inline-block;
+                    display: inline;
                     &.chat-image__link {
                         display: block;
                     }

+ 1 - 1
spec/chatbox.js

@@ -1620,7 +1620,7 @@ describe("Chatboxes", function () {
             const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
             expect(msg.innerHTML.replace(/\<!----\>/g, '')).toEqual(
                 '<a target="_blank" rel="noopener" href="https://www.openstreetmap.org/?mlat=37.786971&amp;'+
-                'mlon=-122.399677#map=18/37.786971/-122.399677">https://www.openstreetmap.org/?mlat=37.786971&amp;amp;mlon=-122.399677#map=18/37.786971/-122.399677</a>');
+                'mlon=-122.399677#map=18/37.786971/-122.399677">https://www.openstreetmap.org/?mlat=37.786971&amp;mlon=-122.399677#map=18/37.786971/-122.399677</a>');
             done();
         }));
     });

+ 1 - 1
spec/retractions.js

@@ -33,7 +33,7 @@ async function sendAndThenRetractMessage (_converse, view) {
 }
 
 
-fdescribe("Message Retractions", function () {
+describe("Message Retractions", function () {
 
     beforeEach(() => (jasmine.DEFAULT_TIMEOUT_INTERVAL = 7000));
     afterEach(() => (jasmine.DEFAULT_TIMEOUT_INTERVAL = original_timeout));

+ 5 - 0
spec/spoilers.js

@@ -1,7 +1,12 @@
 /* global mock */
 
+const original_timeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
+
 describe("A spoiler message", function () {
 
+    beforeEach(() => (jasmine.DEFAULT_TIMEOUT_INTERVAL = 7000));
+    afterEach(() => (jasmine.DEFAULT_TIMEOUT_INTERVAL = original_timeout));
+
     it("can be received with a hint",
         mock.initConverse(
             ['rosterGroupsFetched', 'chatBoxesFetched'], {},

+ 24 - 24
spec/xss.js

@@ -24,44 +24,44 @@ describe("XSS", function () {
             await mock.sendMessage(view, message);
             let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
             expect(msg.textContent).toEqual(message);
-            expect(msg.innerHTML).toEqual("&lt;img src=x onerror=alert('XSS');&gt;");
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual("&lt;img src=x onerror=alert('XSS');&gt;");
             expect(window.alert).not.toHaveBeenCalled();
 
             message = "<img src=x onerror=alert('XSS')//";
             await mock.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;img src=x onerror=alert('XSS')//");
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual("&lt;img src=x onerror=alert('XSS')//");
 
             message = "<img src=x onerror=alert(String.fromCharCode(88,83,83));>";
             await mock.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;img src=x onerror=alert(String.fromCharCode(88,83,83));&gt;");
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual("&lt;img src=x onerror=alert(String.fromCharCode(88,83,83));&gt;");
 
             message = "<img src=x oneonerrorrror=alert(String.fromCharCode(88,83,83));>";
             await mock.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;img src=x oneonerrorrror=alert(String.fromCharCode(88,83,83));&gt;");
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual("&lt;img src=x oneonerrorrror=alert(String.fromCharCode(88,83,83));&gt;");
 
             message = "<img src=x:alert(alt) onerror=eval(src) alt=xss>";
             await mock.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;img src=x:alert(alt) onerror=eval(src) alt=xss&gt;");
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual("&lt;img src=x:alert(alt) onerror=eval(src) alt=xss&gt;");
 
             message = "><img src=x onerror=alert('XSS');>";
             await mock.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("&gt;&lt;img src=x onerror=alert('XSS');&gt;");
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual("&gt;&lt;img src=x onerror=alert('XSS');&gt;");
 
             message = "><img src=x onerror=alert(String.fromCharCode(88,83,83));>";
             await mock.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("&gt;&lt;img src=x onerror=alert(String.fromCharCode(88,83,83));&gt;");
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual("&gt;&lt;img src=x onerror=alert(String.fromCharCode(88,83,83));&gt;");
 
             expect(window.alert).not.toHaveBeenCalled();
             done();
@@ -84,43 +84,43 @@ describe("XSS", function () {
             await mock.sendMessage(view, message);
             let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
             expect(msg.textContent).toEqual(message);
-            expect(msg.innerHTML).toEqual('&lt;svgonload=alert(1)&gt;');
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual('&lt;svgonload=alert(1)&gt;');
 
             message = "<svg/onload=alert('XSS')>";
             await mock.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;svg/onload=alert('XSS')&gt;");
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual("&lt;svg/onload=alert('XSS')&gt;");
 
             message = "<svg onload=alert(1)//";
             await mock.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;svg onload=alert(1)//");
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual("&lt;svg onload=alert(1)//");
 
             message = "<svg/onload=alert(String.fromCharCode(88,83,83))>";
             await mock.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;svg/onload=alert(String.fromCharCode(88,83,83))&gt;");
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual("&lt;svg/onload=alert(String.fromCharCode(88,83,83))&gt;");
 
             message = "<svg id=alert(1) onload=eval(id)>";
             await mock.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;svg id=alert(1) onload=eval(id)&gt;");
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual("&lt;svg id=alert(1) onload=eval(id)&gt;");
 
             message = '"><svg/onload=alert(String.fromCharCode(88,83,83))>';
             await mock.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('"&gt;&lt;svg/onload=alert(String.fromCharCode(88,83,83))&gt;');
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual('"&gt;&lt;svg/onload=alert(String.fromCharCode(88,83,83))&gt;');
 
             message = '"><svg/onload=alert(/XSS/)';
             await mock.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('"&gt;&lt;svg/onload=alert(/XSS/)');
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual('"&gt;&lt;svg/onload=alert(/XSS/)');
 
             expect(window.alert).not.toHaveBeenCalled();
             done();
@@ -143,7 +143,7 @@ describe("XSS", function () {
 
             let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
             expect(msg.textContent).toEqual(message);
-            expect(msg.innerHTML)
+            expect(msg.innerHTML.replace(/<!---->/g, ''))
                 .toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%27onmouseover=%27alert%281%29%27whatever">http://www.opkode.com/\'onmouseover=\'alert(1)\'whatever</a>');
 
             message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever';
@@ -151,21 +151,21 @@ describe("XSS", function () {
 
             msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
             expect(msg.textContent).toEqual(message);
-            expect(msg.innerHTML).toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>');
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>');
 
             message = "https://en.wikipedia.org/wiki/Ender's_Game";
             await mock.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('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>');
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>');
 
             message = "<https://bugs.documentfoundation.org/show_bug.cgi?id=123737>";
             await mock.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(
+            expect(msg.innerHTML.replace(/<!---->/g, '')).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>';
@@ -173,7 +173,7 @@ describe("XSS", function () {
 
             msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
             expect(msg.textContent).toEqual(message);
-            expect(msg.innerHTML).toEqual(
+            expect(msg.innerHTML.replace(/<!---->/g, '')).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;');
 
             message = `https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=!3m6!1e1!3m4!1sQ7SdHo_bPLPlLlU8GSGWaQ!2e0!7i13312!8i6656!4m5!3m4!1s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08!8m2!3d52.3773668!4d4.5489388!5m1!1e2`
@@ -181,7 +181,7 @@ describe("XSS", function () {
 
             msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
             expect(msg.textContent).toEqual(message);
-            expect(msg.innerHTML).toEqual(
+            expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual(
                 `<a target="_blank" rel="noopener" href="https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=%213m6%211e1%213m4%211sQ7SdHo_bPLPlLlU8GSGWaQ%212e0%217i13312%218i6656%214m5%213m4%211s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08%218m2%213d52.3773668%214d4.5489388%215m1%211e2">https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=!3m6!1e1!3m4!1sQ7SdHo_bPLPlLlU8GSGWaQ!2e0!7i13312!8i6656!4m5!3m4!1s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08!8m2!3d52.3773668!4d4.5489388!5m1!1e2</a>`);
             done();
         }));
@@ -226,19 +226,19 @@ describe("XSS", function () {
             function checkNonParsedURL (url) {
                 const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
                 expect(msg.textContent).toEqual(url);
-                expect(msg.innerHTML).toEqual(url);
+                expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual(url);
             }
 
             function checkParsedURL ({ entered, href }) {
                 const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
                 expect(msg.textContent).toEqual(entered);
-                expect(msg.innerHTML).toEqual(`<a target="_blank" rel="noopener" href="${href}">${entered}</a>`);
+                expect(msg.innerHTML.replace(/<!---->/g, '')).toEqual(`<a target="_blank" rel="noopener" href="${href}">${entered}</a>`);
             }
 
             function checkParsedXMPPURL ({ entered, href }) {
                 const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
-                expect(msg.textContent).toEqual(entered);
-                expect(msg.innerHTML).toEqual(`<a target="_blank" rel="noopener" class="open-chatroom" href="${href}">${entered}</a>`);
+                expect(msg.textContent.trim()).toEqual(entered);
+                expect(msg.innerHTML.replace(/<!---->/g, '').trim()).toEqual(`<a target="_blank" rel="noopener" href="${href}">${entered}</a>`);
             }
 
             await mock.sendMessage(view, bad_urls[0]);

+ 7 - 1
src/converse-headlines-view.js

@@ -132,7 +132,7 @@ converse.plugins.add('converse-headlines-view', {
                 this.initDebounced();
 
                 this.model.disable_mam = true; // Don't do MAM queries for this box
-                this.listenTo(this.model.messages, 'add', this.onMessageAdded);
+                this.listenTo(this.model.messages, 'add', this.renderChatHistory);
                 this.listenTo(this.model, 'show', this.show);
                 this.listenTo(this.model, 'destroy', this.hide);
                 this.listenTo(this.model, 'change:minimized', this.onMinimizedChanged);
@@ -168,6 +168,12 @@ converse.plugins.add('converse-headlines-view', {
                 return this;
             },
 
+            getNotifications () {
+                // Override method in ChatBox. We don't show notifications for
+                // headlines boxes.
+                return [];
+            },
+
             /**
              * Returns a list of objects which represent buttons for the headlines header.
              * @async

+ 1 - 34
src/templates/directives/body.js

@@ -1,5 +1,3 @@
-import URI from "urijs";
-import xss from "xss/dist/xss";
 import { _converse, api, converse } from  "@converse/headless/converse-core";
 import { directive, html } from "lit-html";
 import { isString } from "lodash";
@@ -7,36 +5,6 @@ import { isString } from "lodash";
 const u = converse.env.utils;
 
 
-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 (!["https", "http", "xmpp", "ftp"].includes(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;
-    }
-}
-
-
 class Markup extends String {
 
     constructor (data) {
@@ -79,8 +47,7 @@ class MessageBodyRenderer extends String {
          */
         await api.trigger('beforeMessageBodyTransformed', this.model, this.text, {'Synchronous': true});
 
-        let text = xss.filterXSS(this.text, {'whiteList': {}, 'onTag': onTagFoundDuringXSSFilter});
-        text = this.component.is_me_message ? text.substring(4) : text;
+        let text = this.component.is_me_message ? this.text.substring(4) : this.text;
         text = u.geoUriToHttp(text, _converse.geouri_replacement);
 
         const process = (text) => {

+ 7 - 4
src/utils/html.js

@@ -336,7 +336,7 @@ u.convertToImageTag = async function (url) {
 u.convertURIoHyperlink = function (uri, urlAsTyped) {
     let normalized_url = uri.normalize()._string;
     const pretty_url = uri._parts.urn ? normalized_url : uri.readable();
-    const visibleUrl = u.escapeHTML(urlAsTyped || pretty_url);
+    const visible_url = urlAsTyped || pretty_url;
     if (!uri._parts.protocol && !normalized_url.startsWith('http://') && !normalized_url.startsWith('https://')) {
         normalized_url = 'http://' + normalized_url;
     }
@@ -345,9 +345,9 @@ u.convertURIoHyperlink = function (uri, urlAsTyped) {
             <a target="_blank"
                rel="noopener"
                @click=${ev => api.rooms.open(ev.target.href)}
-               href="${normalized_url}">${visibleUrl}</a>`;
+               href="${normalized_url}">${visible_url}</a>`;
     }
-    return html`<a target="_blank" rel="noopener" href="${normalized_url}">${visibleUrl}</a>`;
+    return html`<a target="_blank" rel="noopener" href="${normalized_url}">${visible_url}</a>`;
 };
 
 function isProtocolApproved (protocol, safeProtocolsList = APPROVED_URL_PROTOCOLS) {
@@ -392,9 +392,12 @@ u.addHyperlinks = function (text) {
     if (objs.length) {
         objs.sort((a, b) => b.start - a.start)
             .forEach(url_obj => {
+                const url_text = text.slice(url_obj.start, url_obj.end);
                 const new_list = [
                     text.slice(0, url_obj.start),
-                    show_images && u.isImageURL(text) ? u.convertToImageTag(text) : u.convertUrlToHyperlink(text),
+                    show_images && u.isImageURL(url_text) ?
+                        u.convertToImageTag(url_text) :
+                        u.convertUrlToHyperlink(url_text),
                     text.slice(url_obj.end),
                     ...list
                 ];