Browse Source

fix(ChatRoom) filter out bogus reaction emojis

master
Saúl Ibarra Corretgé 4 months ago
parent
commit
b67e3d3fff
4 changed files with 185 additions and 12 deletions
  1. 21
    3
      modules/xmpp/ChatRoom.js
  2. 140
    1
      modules/xmpp/ChatRoom.spec.js
  3. 23
    8
      package-lock.json
  4. 1
    0
      package.json

+ 21
- 3
modules/xmpp/ChatRoom.js View File

1
 import { getLogger } from '@jitsi/logger';
1
 import { getLogger } from '@jitsi/logger';
2
+import emojiRegex from 'emoji-regex';
2
 import $ from 'jquery';
3
 import $ from 'jquery';
3
 import { isEqual } from 'lodash-es';
4
 import { isEqual } from 'lodash-es';
4
 import { $iq, $msg, $pres, Strophe } from 'strophe.js';
5
 import { $iq, $msg, $pres, Strophe } from 'strophe.js';
24
 
25
 
25
 const logger = getLogger('modules/xmpp/ChatRoom');
26
 const logger = getLogger('modules/xmpp/ChatRoom');
26
 
27
 
28
+/**
29
+ * Regex that matches all emojis.
30
+ */
31
+const EMOJI_REGEX = emojiRegex();
32
+
27
 /**
33
 /**
28
  * How long we're going to wait for IQ response, before timeout error is triggered.
34
  * How long we're going to wait for IQ response, before timeout error is triggered.
29
  * @type {number}
35
  * @type {number}
981
      * @param {string} receiverId - The receiver of the message if it is private.
987
      * @param {string} receiverId - The receiver of the message if it is private.
982
      */
988
      */
983
     sendReaction(reaction, messageId, receiverId) {
989
     sendReaction(reaction, messageId, receiverId) {
990
+        const m = reaction.match(EMOJI_REGEX);
991
+
992
+        if (!m || !m[0]) {
993
+            throw new Error(`Invalid reaction: ${reaction}`);
994
+        }
995
+
984
         // Adds the 'to' attribute depending on if the message is private or not.
996
         // Adds the 'to' attribute depending on if the message is private or not.
985
         const msg = receiverId ? $msg({ to: `${this.roomjid}/${receiverId}`,
997
         const msg = receiverId ? $msg({ to: `${this.roomjid}/${receiverId}`,
986
             type: 'chat' }) : $msg({ to: this.roomjid,
998
             type: 'chat' }) : $msg({ to: this.roomjid,
988
 
1000
 
989
         msg.c('reactions', { id: messageId,
1001
         msg.c('reactions', { id: messageId,
990
             xmlns: 'urn:xmpp:reactions:0' })
1002
             xmlns: 'urn:xmpp:reactions:0' })
991
-            .c('reaction', {}, reaction)
1003
+            .c('reaction', {}, m[0])
992
             .up().c('store', { xmlns: 'urn:xmpp:hints' });
1004
             .up().c('store', { xmlns: 'urn:xmpp:hints' });
993
 
1005
 
994
         this.connection.send(msg);
1006
         this.connection.send(msg);
1203
 
1215
 
1204
             reactions.each((_, reactionElem) => {
1216
             reactions.each((_, reactionElem) => {
1205
                 const reaction = $(reactionElem).text();
1217
                 const reaction = $(reactionElem).text();
1218
+                const m = reaction.match(EMOJI_REGEX);
1206
 
1219
 
1207
-                reactionList.push(reaction);
1220
+                // Only allow one reaction per <reaction> element.
1221
+                if (m && m[0]) {
1222
+                    reactionList.push(m[0]);
1223
+                }
1208
             });
1224
             });
1209
 
1225
 
1210
-            this.eventEmitter.emit(XMPPEvents.REACTION_RECEIVED, from, reactionList, messageId);
1226
+            if (reactionList.length > 0) {
1227
+                this.eventEmitter.emit(XMPPEvents.REACTION_RECEIVED, from, reactionList, messageId);
1228
+            }
1211
 
1229
 
1212
             return true;
1230
             return true;
1213
         }
1231
         }

+ 140
- 1
modules/xmpp/ChatRoom.spec.js View File

404
                 '</message>');
404
                 '</message>');
405
         });
405
         });
406
     });
406
     });
407
-});
408
 
407
 
408
+    describe('onMessage - reaction', () => {
409
+        let room;
410
+        let emitterSpy;
411
+
412
+        beforeEach(() => {
413
+            const xmpp = {
414
+                moderator: new Moderator({
415
+                    options: {}
416
+                }),
417
+                options: {},
418
+                addListener: () => {} // eslint-disable-line no-empty-function
419
+            };
420
+
421
+            room = new ChatRoom(
422
+                {} /* connection */,
423
+                'jid',
424
+                'password',
425
+                xmpp,
426
+                {} /* options */);
427
+            emitterSpy = spyOn(room.eventEmitter, 'emit');
428
+        });
429
+
430
+        it('parses reactions correctly', () => {
431
+            const msgStr = '' +
432
+                '<message to="jid" type="groupchat" xmlns="jabber:client">' +
433
+                    '<reactions id="mdgId123" xmlns="urn:xmpp:reactions:0">' +
434
+                        '<reaction>👍</reaction>' +
435
+                    '</reactions>' +
436
+                    '<store xmlns="urn:xmpp:hints"/>' +
437
+                '</message>';
438
+            const msg = new DOMParser().parseFromString(msgStr, 'text/xml').documentElement;
439
+
440
+            room.onMessage(msg, 'fromjid');
441
+            expect(emitterSpy.calls.count()).toEqual(1);
442
+            expect(emitterSpy).toHaveBeenCalledWith(
443
+                XMPPEvents.REACTION_RECEIVED,
444
+                'fromjid',
445
+                ['👍'],
446
+                'mdgId123');
447
+        });
448
+        it('parses multiple reactions correctly', () => {
449
+            const msgStr = '' +
450
+                '<message to="jid" type="groupchat" xmlns="jabber:client">' +
451
+                    '<reactions id="mdgId123" xmlns="urn:xmpp:reactions:0">' +
452
+                        '<reaction>👍</reaction>' +
453
+                        '<reaction>👎</reaction>' +
454
+                    '</reactions>' +
455
+                    '<store xmlns="urn:xmpp:hints"/>' +
456
+                '</message>';
457
+            const msg = new DOMParser().parseFromString(msgStr, 'text/xml').documentElement;
458
+
459
+            room.onMessage(msg, 'fromjid');
460
+            expect(emitterSpy.calls.count()).toEqual(1);
461
+            expect(emitterSpy).toHaveBeenCalledWith(
462
+                XMPPEvents.REACTION_RECEIVED,
463
+                'fromjid',
464
+                ['👍', '👎'],
465
+                'mdgId123');
466
+        });
467
+        it('parses partially bogus reactions correctly', () => {
468
+            const msgStr = '' +
469
+                '<message to="jid" type="groupchat" xmlns="jabber:client">' +
470
+                    '<reactions id="mdgId123" xmlns="urn:xmpp:reactions:0">' +
471
+                        '<reaction>👍 foo bar baz</reaction>' +
472
+                    '</reactions>' +
473
+                    '<store xmlns="urn:xmpp:hints"/>' +
474
+                '</message>';
475
+            const msg = new DOMParser().parseFromString(msgStr, 'text/xml').documentElement;
476
+
477
+            room.onMessage(msg, 'fromjid');
478
+            expect(emitterSpy.calls.count()).toEqual(1);
479
+            expect(emitterSpy).toHaveBeenCalledWith(
480
+                XMPPEvents.REACTION_RECEIVED,
481
+                'fromjid',
482
+                ['👍'],
483
+                'mdgId123');
484
+        });
485
+        it('parses bogus reactions correctly', () => {
486
+            const msgStr = '' +
487
+                '<message to="jid" type="groupchat" xmlns="jabber:client">' +
488
+                    '<reactions id="mdgId123" xmlns="urn:xmpp:reactions:0">' +
489
+                        '<reaction>foo bar baz</reaction>' +
490
+                    '</reactions>' +
491
+                    '<store xmlns="urn:xmpp:hints"/>' +
492
+                '</message>';
493
+            const msg = new DOMParser().parseFromString(msgStr, 'text/xml').documentElement;
494
+
495
+            room.onMessage(msg, 'fromjid');
496
+            expect(emitterSpy.calls.count()).toEqual(0);
497
+        });
498
+    });
499
+
500
+    describe('sendReaction', () => {
501
+        let room;
502
+        let connectionSpy;
503
+
504
+        beforeEach(() => {
505
+            const xmpp = {
506
+                moderator: new Moderator({
507
+                    options: {}
508
+                }),
509
+                options: {},
510
+                addListener: () => {} // eslint-disable-line no-empty-function
511
+            };
512
+
513
+            room = new ChatRoom(
514
+                // eslint-disable-next-line no-empty-function
515
+                { send: () => {} } /* connection */,
516
+                'jid',
517
+                'password',
518
+                xmpp,
519
+                {} /* options */);
520
+            connectionSpy = spyOn(room.connection, 'send');
521
+        });
522
+        it('sends a valid emoji reaction message', () => {
523
+            room.sendReaction('👍', 'mdgId123', 'participant1');
524
+            expect(connectionSpy.calls.argsFor(0).toString()).toBe(
525
+                '<message to="jid/participant1" type="chat" xmlns="jabber:client">' +
526
+                '<reactions id="mdgId123" xmlns="urn:xmpp:reactions:0"><reaction>👍</reaction></reactions>' +
527
+                '<store xmlns="urn:xmpp:hints"/></message>');
528
+        });
529
+        it('sends only valid emoji reaction message', () => {
530
+            room.sendReaction('I like this 👍', 'mdgId123', 'participant1');
531
+            expect(connectionSpy.calls.argsFor(0).toString()).toBe(
532
+                '<message to="jid/participant1" type="chat" xmlns="jabber:client">' +
533
+                '<reactions id="mdgId123" xmlns="urn:xmpp:reactions:0"><reaction>👍</reaction></reactions>' +
534
+                '<store xmlns="urn:xmpp:hints"/></message>');
535
+        });
536
+        it('sends only the first valid emoji reaction message', () => {
537
+            room.sendReaction('👍👎', 'mdgId123', 'participant1');
538
+            expect(connectionSpy.calls.argsFor(0).toString()).toBe(
539
+                '<message to="jid/participant1" type="chat" xmlns="jabber:client">' +
540
+                '<reactions id="mdgId123" xmlns="urn:xmpp:reactions:0"><reaction>👍</reaction></reactions>' +
541
+                '<store xmlns="urn:xmpp:hints"/></message>');
542
+        });
543
+        it('throws in case of invalid or no emoji', () => {
544
+            expect(() => room.sendReaction('foo bar baz', 'mdgId123', 'participant1')).toThrowError(/Invalid reaction/);
545
+        });
546
+    });
547
+});

+ 23
- 8
package-lock.json View File

17
         "async-es": "3.2.4",
17
         "async-es": "3.2.4",
18
         "base64-js": "1.5.1",
18
         "base64-js": "1.5.1",
19
         "current-executing-script": "0.1.3",
19
         "current-executing-script": "0.1.3",
20
+        "emoji-regex": "10.4.0",
20
         "jquery": "3.6.1",
21
         "jquery": "3.6.1",
21
         "lodash-es": "4.17.21",
22
         "lodash-es": "4.17.21",
22
         "sdp-transform": "2.3.0",
23
         "sdp-transform": "2.3.0",
3905
       "license": "ISC"
3906
       "license": "ISC"
3906
     },
3907
     },
3907
     "node_modules/emoji-regex": {
3908
     "node_modules/emoji-regex": {
3908
-      "version": "8.0.0",
3909
-      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
3910
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
3911
-      "dev": true
3909
+      "version": "10.4.0",
3910
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
3911
+      "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
3912
+      "license": "MIT"
3912
     },
3913
     },
3913
     "node_modules/emojis-list": {
3914
     "node_modules/emojis-list": {
3914
       "version": "3.0.0",
3915
       "version": "3.0.0",
7816
         "node": ">=8"
7817
         "node": ">=8"
7817
       }
7818
       }
7818
     },
7819
     },
7820
+    "node_modules/string-width/node_modules/emoji-regex": {
7821
+      "version": "8.0.0",
7822
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
7823
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
7824
+      "dev": true,
7825
+      "license": "MIT"
7826
+    },
7819
     "node_modules/string.prototype.trim": {
7827
     "node_modules/string.prototype.trim": {
7820
       "version": "1.2.10",
7828
       "version": "1.2.10",
7821
       "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
7829
       "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
11796
       "dev": true
11804
       "dev": true
11797
     },
11805
     },
11798
     "emoji-regex": {
11806
     "emoji-regex": {
11799
-      "version": "8.0.0",
11800
-      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
11801
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
11802
-      "dev": true
11807
+      "version": "10.4.0",
11808
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
11809
+      "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="
11803
     },
11810
     },
11804
     "emojis-list": {
11811
     "emojis-list": {
11805
       "version": "3.0.0",
11812
       "version": "3.0.0",
14510
         "emoji-regex": "^8.0.0",
14517
         "emoji-regex": "^8.0.0",
14511
         "is-fullwidth-code-point": "^3.0.0",
14518
         "is-fullwidth-code-point": "^3.0.0",
14512
         "strip-ansi": "^6.0.1"
14519
         "strip-ansi": "^6.0.1"
14520
+      },
14521
+      "dependencies": {
14522
+        "emoji-regex": {
14523
+          "version": "8.0.0",
14524
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
14525
+          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
14526
+          "dev": true
14527
+        }
14513
       }
14528
       }
14514
     },
14529
     },
14515
     "string.prototype.trim": {
14530
     "string.prototype.trim": {

+ 1
- 0
package.json View File

24
     "async-es": "3.2.4",
24
     "async-es": "3.2.4",
25
     "base64-js": "1.5.1",
25
     "base64-js": "1.5.1",
26
     "current-executing-script": "0.1.3",
26
     "current-executing-script": "0.1.3",
27
+    "emoji-regex": "10.4.0",
27
     "jquery": "3.6.1",
28
     "jquery": "3.6.1",
28
     "lodash-es": "4.17.21",
29
     "lodash-es": "4.17.21",
29
     "sdp-transform": "2.3.0",
30
     "sdp-transform": "2.3.0",

Loading…
Cancel
Save