diff --git a/apps/meteor/app/ui-master/server/index.ts b/apps/meteor/app/ui-master/server/index.ts index 7efdd94c25159..24a673b3b9b85 100644 --- a/apps/meteor/app/ui-master/server/index.ts +++ b/apps/meteor/app/ui-master/server/index.ts @@ -6,6 +6,7 @@ import { Inject } from 'meteor/meteorhacks:inject-initial'; import { Tracker } from 'meteor/tracker'; import { applyHeadInjections, headInjections, injectIntoBody, injectIntoHead } from './inject'; +import { getMessageMaxParseLength } from '../../../lib/getMessageMaxParseLength'; import { withDebouncing } from '../../../lib/utils/highOrderFunctions'; import { settings } from '../../settings/server'; import { getURL } from '../../utils/server/getURL'; @@ -126,6 +127,9 @@ Meteor.startup(() => { })(__meteor_runtime_config__.ROOT_URL_PATH_PREFIX); injectIntoHead('base', ``); + + const escapedMessageMaxParseLength = escapeHTML(String(getMessageMaxParseLength())); + injectIntoHead('MESSAGE_MAX_PARSE_LENGTH', ``); }); const renderDynamicCssList = withDebouncing({ wait: 500 })(async () => { diff --git a/apps/meteor/lib/constants.ts b/apps/meteor/lib/constants.ts index 6d258a8876d36..23807a3503f46 100644 --- a/apps/meteor/lib/constants.ts +++ b/apps/meteor/lib/constants.ts @@ -2,3 +2,4 @@ export const NOTIFICATION_ATTACHMENT_COLOR = '#FD745E'; export const MAX_MULTIPLE_UPLOADED_FILES = 10; export const MAX_CUSTOM_SOUND_SIZE_BYTES = 5242880; export const CUSTOM_SOUND_ALLOWED_MIME_TYPES = ['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/x-wav']; +export const MESSAGE_MAX_PARSE_LENGTH_DEFAULT = 0; diff --git a/apps/meteor/lib/getMessageMaxParseLength.ts b/apps/meteor/lib/getMessageMaxParseLength.ts new file mode 100644 index 0000000000000..941fa9be61809 --- /dev/null +++ b/apps/meteor/lib/getMessageMaxParseLength.ts @@ -0,0 +1,6 @@ +import { MESSAGE_MAX_PARSE_LENGTH_DEFAULT } from './constants'; + +export function getMessageMaxParseLength(): number { + const parsed = Number.parseInt(process.env.MESSAGE_MAX_PARSE_LENGTH ?? '', 10); + return Number.isFinite(parsed) ? parsed : MESSAGE_MAX_PARSE_LENGTH_DEFAULT; +} diff --git a/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts b/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts index f4e26cdb97ee5..b2b47ad0b9228 100644 --- a/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts +++ b/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts @@ -2,6 +2,8 @@ import { isE2EEMessage } from '@rocket.chat/core-typings'; import type { IMessage } from '@rocket.chat/core-typings'; import { parse } from '@rocket.chat/message-parser'; +import { getMessageMaxParseLength } from '../../../../lib/getMessageMaxParseLength'; + type ParserConfig = { colors?: boolean; emoticons?: boolean; @@ -26,6 +28,12 @@ export class BeforeSaveMarkdownParser { return message; } + const messageMaxParseLength = getMessageMaxParseLength(); + if (messageMaxParseLength > 0 && message.msg && message.msg.length > messageMaxParseLength) { + delete message.md; + return message; + } + try { if (message.msg) { message.md = parse(message.msg, config); diff --git a/apps/meteor/tests/unit/lib/getMessageMaxParseLength.spec.ts b/apps/meteor/tests/unit/lib/getMessageMaxParseLength.spec.ts new file mode 100644 index 0000000000000..b9173ccd133cd --- /dev/null +++ b/apps/meteor/tests/unit/lib/getMessageMaxParseLength.spec.ts @@ -0,0 +1,39 @@ +import { expect } from 'chai'; + +import { getMessageMaxParseLength } from '../../../lib/getMessageMaxParseLength'; + +describe('getMessageMaxParseLength', () => { + afterEach(() => { + delete process.env.MESSAGE_MAX_PARSE_LENGTH; + }); + + it('should return 0 (default) when env var is not set', () => { + delete process.env.MESSAGE_MAX_PARSE_LENGTH; + expect(getMessageMaxParseLength()).to.equal(0); + }); + + it('should return 0 (default) when env var is empty string', () => { + process.env.MESSAGE_MAX_PARSE_LENGTH = ''; + expect(getMessageMaxParseLength()).to.equal(0); + }); + + it('should return 0 (default) when env var is not a number', () => { + process.env.MESSAGE_MAX_PARSE_LENGTH = 'abc'; + expect(getMessageMaxParseLength()).to.equal(0); + }); + + it('should return the parsed number when env var is a valid integer', () => { + process.env.MESSAGE_MAX_PARSE_LENGTH = '5000'; + expect(getMessageMaxParseLength()).to.equal(5000); + }); + + it('should return 0 when env var is "0"', () => { + process.env.MESSAGE_MAX_PARSE_LENGTH = '0'; + expect(getMessageMaxParseLength()).to.equal(0); + }); + + it('should return 0 (default) when env var is Infinity', () => { + process.env.MESSAGE_MAX_PARSE_LENGTH = 'Infinity'; + expect(getMessageMaxParseLength()).to.equal(0); + }); +}); diff --git a/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveMarkdownParser.tests.ts b/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveMarkdownParser.tests.ts index ae0f3ed284a7a..c744883ce9e3c 100644 --- a/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveMarkdownParser.tests.ts +++ b/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveMarkdownParser.tests.ts @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import { beforeEach } from 'mocha'; import { BeforeSaveMarkdownParser } from '../../../../../../server/services/messages/hooks/BeforeSaveMarkdownParser'; @@ -16,6 +17,14 @@ const createMessage = (msg?: string, extra: any = {}) => ({ }); describe('Markdown parser', () => { + beforeEach(() => { + delete process.env.MESSAGE_MAX_PARSE_LENGTH; + }); + + afterEach(() => { + delete process.env.MESSAGE_MAX_PARSE_LENGTH; + }); + it('should do nothing if markdown parser is disabled', async () => { const markdownParser = new BeforeSaveMarkdownParser(false); @@ -38,11 +47,36 @@ describe('Markdown parser', () => { expect(message).to.not.have.property('md'); }); - it('should parse markdown', async () => { + it('should skip parsing when msg exceeds MESSAGE_MAX_PARSE_LENGTH', async () => { + process.env.MESSAGE_MAX_PARSE_LENGTH = '10'; const markdownParser = new BeforeSaveMarkdownParser(true); const message = await markdownParser.parseMarkdown({ - message: createMessage('hey'), + message: createMessage('a'.repeat(11)), + config: {}, + }); + + expect(message).to.not.have.property('md'); + }); + + it('should parse normally when msg is within MESSAGE_MAX_PARSE_LENGTH', async () => { + process.env.MESSAGE_MAX_PARSE_LENGTH = '100'; + const markdownParser = new BeforeSaveMarkdownParser(true); + + const message = await markdownParser.parseMarkdown({ + message: createMessage('short msg'), + config: {}, + }); + + expect(message).to.have.property('md'); + }); + + it('should parse normally when MESSAGE_MAX_PARSE_LENGTH is 0', async () => { + process.env.MESSAGE_MAX_PARSE_LENGTH = '0'; + const markdownParser = new BeforeSaveMarkdownParser(true); + + const message = await markdownParser.parseMarkdown({ + message: createMessage('short msg'), config: {}, });