diff --git a/package.json b/package.json index cde15f42e141..b12fd6cad56e 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "emailreplyparser": "^0.0.5", "file-type": "^8.0.0", "filesize": "^3.6.1", + "grapheme-splitter": "^1.0.2", "gridfs-stream": "^1.1.1", "he": "^1.1.1", "highlight.js": "^9.12.0", diff --git a/packages/rocketchat-lib/lib/MessageProperties.js b/packages/rocketchat-lib/lib/MessageProperties.js new file mode 100644 index 000000000000..1178bcf19237 --- /dev/null +++ b/packages/rocketchat-lib/lib/MessageProperties.js @@ -0,0 +1,24 @@ +import GraphemeSplitter from 'grapheme-splitter'; + +const splitter = new GraphemeSplitter(); + +export const messageProperties = { + + length: (message => { + return splitter.countGraphemes(message); + }), + + messageWithoutEmojiShortnames: (message => { + return message.replace(/:\w+:/gm, (match) => { + if (RocketChat.emoji.list[match] !== undefined) { + return ' '; + } + return match; + }); + }) +}; + +// check for tests +if (typeof RocketChat !== 'undefined') { + RocketChat.messageProperties = messageProperties; +} diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 896775464805..4592252e5633 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -66,6 +66,7 @@ Package.onUse(function(api) { api.addFiles('lib/RoomTypesCommon.js'); api.addFiles('lib/slashCommand.js'); api.addFiles('lib/Message.js'); + api.addFiles('lib/MessageProperties.js'); api.addFiles('lib/messageBox.js'); api.addFiles('lib/MessageTypes.js'); api.addFiles('lib/templateVarHandler.js'); diff --git a/packages/rocketchat-lib/server/methods/sendMessage.js b/packages/rocketchat-lib/server/methods/sendMessage.js index 03b65736150f..d9da792aa050 100644 --- a/packages/rocketchat-lib/server/methods/sendMessage.js +++ b/packages/rocketchat-lib/server/methods/sendMessage.js @@ -29,10 +29,14 @@ Meteor.methods({ message.ts = new Date(); } - if (message.msg && message.msg.length > RocketChat.settings.get('Message_MaxAllowedSize')) { - throw new Meteor.Error('error-message-size-exceeded', 'Message size exceeds Message_MaxAllowedSize', { - method: 'sendMessage' - }); + if (message.msg) { + const adjustedMessage = RocketChat.messageProperties.messageWithoutEmojiShortnames(message.msg); + + if (RocketChat.messageProperties.length(adjustedMessage) > RocketChat.settings.get('Message_MaxAllowedSize')) { + throw new Meteor.Error('error-message-size-exceeded', 'Message size exceeds Message_MaxAllowedSize', { + method: 'sendMessage' + }); + } } const user = RocketChat.models.Users.findOneById(Meteor.userId(), { diff --git a/packages/rocketchat-lib/tests/server.tests.js b/packages/rocketchat-lib/tests/server.tests.js index 4d358b75a088..0cb9a49b0ed3 100644 --- a/packages/rocketchat-lib/tests/server.tests.js +++ b/packages/rocketchat-lib/tests/server.tests.js @@ -3,6 +3,7 @@ import 'babel-polyfill'; import assert from 'assert'; import PasswordPolicyClass from '../server/lib/PasswordPolicyClass'; +import {messageProperties} from '../lib/MessageProperties'; describe('PasswordPolicyClass', () => { describe('Default options', () => { @@ -204,3 +205,20 @@ describe('PasswordPolicyClass', () => { }); }); }); + +const messages = { + 'Sample Message': 14, + 'Sample 1 ⛳': 10, + 'Sample 2 ❤': 10, + 'Sample 3 ⛳❤⛳❤': 13 +}; + +describe('Message Properties', () => { + describe('Check Message Length', () => { + Object.keys(messages).forEach((objectKey) => { + it('should treat emojis as single characters', () => { + assert.equal(messageProperties.length(objectKey), messages[objectKey]); + }); + }); + }); +}); diff --git a/packages/rocketchat-ui/client/lib/chatMessages.js b/packages/rocketchat-ui/client/lib/chatMessages.js index e11058f84684..d1768f9c0dba 100644 --- a/packages/rocketchat-ui/client/lib/chatMessages.js +++ b/packages/rocketchat-ui/client/lib/chatMessages.js @@ -554,7 +554,8 @@ this.ChatMessages = class ChatMessages { } isMessageTooLong(message) { - return message && message.length > this.messageMaxSize; + const adjustedMessage = RocketChat.messageProperties.messageWithoutEmojiShortnames(message); + return RocketChat.messageProperties.length(adjustedMessage) > this.messageMaxSize && message; } isEmpty() {