-
Notifications
You must be signed in to change notification settings - Fork 13.5k
feat: Message_MaxMarkdownParseLength setting
#40306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
nazabucciarelli
wants to merge
17
commits into
develop
Choose a base branch
from
fix/message-parser-max-length-prevention
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
d0d2094
add maxMessageParseSize parameter to parser
nazabucciarelli c8bf7b2
prevent parsing upstream instead of lower level code
nazabucciarelli 1c1c10d
add positive number validation
nazabucciarelli 36d810c
add unit tests
nazabucciarelli bd4a7c2
modify const
nazabucciarelli a5da191
add normalizeAttachment to parse bypass scenario
nazabucciarelli b3bf08d
remove unneeded optional chaining operator
nazabucciarelli cbd1db1
add setting description
nazabucciarelli 8dc377a
fix unit test
nazabucciarelli 43e6955
add changeset
nazabucciarelli 039b964
add text assertion in test
nazabucciarelli 3b4ae96
improve i18n description
nazabucciarelli f331822
deep clone attachments before normalization
nazabucciarelli 0f75d73
change of approach: create a new setting to limit message parsing
nazabucciarelli 32d6517
update changeset
nazabucciarelli dd217aa
improve i18n setting description
nazabucciarelli 27efcb3
improve changeset description
nazabucciarelli File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| '@rocket.chat/i18n': minor | ||
| '@rocket.chat/meteor': minor | ||
| --- | ||
|
|
||
| Adds `Message_MaxMarkdownParseLength` setting to limit the number of characters processed by the Markdown parser. |
45 changes: 45 additions & 0 deletions
45
apps/meteor/client/components/message/content/attachments/QuoteAttachment.spec.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { render, screen } from '@testing-library/react'; | ||
|
|
||
| import { QuoteAttachment } from './QuoteAttachment'; | ||
|
|
||
| jest.mock('../../hooks/useMaxMarkdownParseLength', () => ({ | ||
| useMaxMarkdownParseLength: () => 100, | ||
| })); | ||
|
|
||
| jest.mock('@rocket.chat/ui-contexts', () => ({ | ||
| useUserPreference: () => true, | ||
| })); | ||
|
|
||
| jest.mock('../../../../hooks/useTimeAgo', () => ({ | ||
| useTimeAgo: () => (date: Date) => date.toISOString(), | ||
| })); | ||
|
|
||
| jest.mock('../../MessageContentBody', () => ({ | ||
| __esModule: true, | ||
| default: () => <div data-testid='message-content-body' />, | ||
| })); | ||
|
|
||
| const baseAttachment = { | ||
| author_name: 'User', | ||
| author_icon: '', | ||
| ts: new Date(), | ||
| text: 'short text', | ||
| md: [{ type: 'PARAGRAPH', value: [{ type: 'PLAIN_TEXT', value: 'short text' }] }], | ||
| }; | ||
|
|
||
| describe('QuoteAttachment', () => { | ||
| it('renders MessageContentBody when text length is within maxMarkdownParseLength', () => { | ||
| render(<QuoteAttachment attachment={baseAttachment as any} />); | ||
| expect(screen.getByTestId('message-content-body')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it('renders plain text when text exceeds maxMarkdownParseLength', () => { | ||
| const longText = 'a'.repeat(101); | ||
| const attachment = { ...baseAttachment, text: longText }; | ||
|
|
||
| render(<QuoteAttachment attachment={attachment as any} />); | ||
|
|
||
| expect(screen.queryByTestId('message-content-body')).not.toBeInTheDocument(); | ||
| expect(screen.getByText(longText)).toBeInTheDocument(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
apps/meteor/client/components/message/hooks/useMaxMarkdownParseLength.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { useSetting } from '@rocket.chat/ui-contexts'; | ||
| import { useMemo } from 'react'; | ||
|
|
||
| /** | ||
| * Returns the maximum number of characters a message can have for markdown parsing. | ||
| * Returns Infinity when the setting is 0 or negative, meaning the limit is disabled | ||
| * and all messages will be parsed regardless of length. | ||
| */ | ||
| export const useMaxMarkdownParseLength = (): number => { | ||
| const settingValue = useSetting('Message_MaxMarkdownParseLength', 0); | ||
|
|
||
| return useMemo(() => { | ||
| if (typeof settingValue !== 'number' || settingValue <= 0) { | ||
| return Infinity; | ||
| } | ||
| return settingValue; | ||
| }, [settingValue]); | ||
| }; | ||
68 changes: 68 additions & 0 deletions
68
apps/meteor/client/components/message/hooks/useNormalizedMessage.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import { renderHook } from '@testing-library/react'; | ||
|
|
||
| import { useNormalizedMessage } from './useNormalizedMessage'; | ||
|
|
||
| const mockParseMessageTextToAstMarkdown = jest.fn((msg: any, ..._args: any[]) => msg); | ||
|
|
||
| jest.mock('../list/MessageListContext', () => ({ | ||
| useMessageListKatex: () => null, | ||
| useMessageListAutoTranslate: () => ({ | ||
| showAutoTranslate: () => false, | ||
| autoTranslateLanguage: '', | ||
| }), | ||
| useMessageListShowColors: () => false, | ||
| })); | ||
|
|
||
| jest.mock('../../../lib/parseMessageTextToAstMarkdown', () => ({ | ||
| parseMessageTextToAstMarkdown: (msg: any, ...args: any[]) => mockParseMessageTextToAstMarkdown(msg, ...args), | ||
| })); | ||
|
|
||
| jest.mock('../../../views/room/MessageList/hooks/useAutoLinkDomains', () => ({ useAutoLinkDomains: () => [] })); | ||
|
|
||
| const baseMessage = { | ||
| _id: 'msg1', | ||
| rid: 'room1', | ||
| u: { _id: 'u1', username: 'user', name: 'User' }, | ||
| ts: new Date(), | ||
| _updatedAt: new Date(), | ||
| }; | ||
|
|
||
| describe('useNormalizedMessage', () => { | ||
| beforeEach(() => { | ||
| mockParseMessageTextToAstMarkdown.mockClear(); | ||
| }); | ||
|
|
||
| it('should skip parsing and returns PARAGRAPH node when msg exceeds maxMarkdownParseLength', () => { | ||
| const longMsg = 'a'.repeat(101); | ||
| const message = { ...baseMessage, msg: longMsg }; | ||
|
|
||
| const { result } = renderHook(() => useNormalizedMessage(message as any, 100)); | ||
|
|
||
| expect(mockParseMessageTextToAstMarkdown).not.toHaveBeenCalled(); | ||
| expect(result.current.md).toEqual([ | ||
| { | ||
| type: 'PARAGRAPH', | ||
| value: [{ type: 'PLAIN_TEXT', value: longMsg }], | ||
| }, | ||
| ]); | ||
| }); | ||
|
|
||
| it('should call parseMessageTextToAstMarkdown when msg is within maxMarkdownParseLength', () => { | ||
| const message = { ...baseMessage, msg: 'Hello world' }; | ||
|
|
||
| renderHook(() => useNormalizedMessage(message as any, 100)); | ||
|
|
||
| expect(mockParseMessageTextToAstMarkdown).toHaveBeenCalledWith(message, expect.anything(), expect.anything()); | ||
| }); | ||
|
|
||
| it('should preserve attachments when bypassing parsing due to size', () => { | ||
| const longMsg = 'a'.repeat(101); | ||
| const attachments = [{ type: 'quote', text: 'quoted' }]; | ||
| const message = { ...baseMessage, msg: longMsg, attachments }; | ||
|
|
||
| const { result } = renderHook(() => useNormalizedMessage(message as any, 100)); | ||
|
|
||
| expect(mockParseMessageTextToAstMarkdown).not.toHaveBeenCalled(); | ||
| expect(result.current.attachments).toEqual(attachments); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
apps/meteor/client/views/room/MessageList/hooks/useMessageBody.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import { renderHook } from '@testing-library/react'; | ||
|
|
||
| import { useMessageBody } from './useMessageBody'; | ||
|
|
||
| const mockParseMessageTextToAstMarkdown = jest.fn(); | ||
|
|
||
| jest.mock('./useAutoLinkDomains', () => ({ | ||
| useAutoLinkDomains: () => [], | ||
| })); | ||
|
|
||
| jest.mock('../../../../components/message/list/MessageListContext', () => ({ | ||
| useMessageListAutoTranslate: () => ({ | ||
| showAutoTranslate: () => false, | ||
| autoTranslateLanguage: '', | ||
| }), | ||
| })); | ||
|
|
||
| jest.mock('../../../../lib/parseMessageTextToAstMarkdown', () => ({ | ||
| parseMessageTextToAstMarkdown: (msg: any, ...args: any[]) => mockParseMessageTextToAstMarkdown(msg, ...args), | ||
| })); | ||
|
|
||
| const baseMessage = { | ||
| _id: 'msg1', | ||
| rid: 'room1', | ||
| u: { _id: 'u1', username: 'user', name: 'User' }, | ||
| ts: new Date(), | ||
| _updatedAt: new Date(), | ||
| }; | ||
|
|
||
| describe('useMessageBody', () => { | ||
| beforeEach(() => { | ||
| mockParseMessageTextToAstMarkdown.mockClear(); | ||
| }); | ||
|
|
||
| it('should return raw msg and skips parsing when msg exceeds maxMarkdownParseLength', () => { | ||
| const longMsg = 'a'.repeat(101); | ||
| const message = { ...baseMessage, msg: longMsg, md: [{ type: 'PARAGRAPH', value: [] }] }; | ||
|
|
||
| const { result } = renderHook(() => useMessageBody(message as any, 100)); | ||
|
|
||
| expect(result.current).toBe(longMsg); | ||
| expect(mockParseMessageTextToAstMarkdown).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should call parser when message has md and is within maxMarkdownParseLength', () => { | ||
| const md = [{ type: 'PARAGRAPH', value: [] }]; | ||
| const message = { ...baseMessage, msg: 'Hello world', md }; | ||
| mockParseMessageTextToAstMarkdown.mockReturnValue({ ...message, md }); | ||
|
|
||
| const { result } = renderHook(() => useMessageBody(message as any, 100)); | ||
|
|
||
| expect(mockParseMessageTextToAstMarkdown).toHaveBeenCalledWith(message, expect.anything(), expect.anything()); | ||
| expect(result.current).toBe(md); | ||
| }); | ||
|
|
||
| it('should return raw msg without parsing when message has no md', () => { | ||
| const message = { ...baseMessage, msg: 'Hello world' }; | ||
|
|
||
| const { result } = renderHook(() => useMessageBody(message as any, 100)); | ||
|
|
||
| expect(mockParseMessageTextToAstMarkdown).not.toHaveBeenCalled(); | ||
| expect(result.current).toBe('Hello world'); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.