Skip to content

Commit 41a2a8d

Browse files

File tree

4 files changed

+80
-8
lines changed

4 files changed

+80
-8
lines changed

src/ui/MessageInput/__tests__/utils.spec.ts

+70-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import jsdom from 'jsdom';
2-
3-
import { nodeListToArray } from '../utils';
2+
import { nodeListToArray, sanitizeString } from '../utils';
43

54
describe('MessageInputUtils/nodeListToArray', () => {
65
it('should convert node list to array', () => {
@@ -19,7 +18,75 @@ describe('MessageInputUtils/nodeListToArray', () => {
1918
});
2019

2120
it('should return empty array if nodelist is undefined', () => {
22-
const nodes = nodeListToArray(null);
21+
const nodes = nodeListToArray(undefined);
2322
expect(nodes.length).toBe(0);
2423
});
2524
});
25+
26+
describe('Utils/sanitizeString', () => {
27+
it('should encode special HTML characters', () => {
28+
const input = '<div>Hello & "world"!</div>';
29+
const expectedOutput = '&#60;div&#62;Hello & \"world\"!&#60;/div&#62;';
30+
expect(sanitizeString(input)).toBe(expectedOutput);
31+
});
32+
33+
it('should encode non-English characters correctly', () => {
34+
const input = '안녕하세요';
35+
const expectedOutput = '안녕하세요';
36+
expect(sanitizeString(input)).toBe(expectedOutput);
37+
});
38+
39+
it('should encode emojis as HTML entities', () => {
40+
const input = '🙂';
41+
const expectedOutput = '🙂';
42+
expect(sanitizeString(input)).toBe(expectedOutput);
43+
});
44+
45+
it('should handle mixed content with HTML tags and non-English characters', () => {
46+
const input = '<p>안녕 & Hello 🙂</p>';
47+
const expectedOutput = '&#60;p&#62;안녕 & Hello 🙂&#60;/p&#62;';
48+
expect(sanitizeString(input)).toBe(expectedOutput);
49+
});
50+
51+
it('should return an empty string if input is undefined', () => {
52+
expect(sanitizeString(undefined)).toBe('');
53+
});
54+
55+
it('should return an empty string if input is null', () => {
56+
expect(sanitizeString(null as any)).toBe('');
57+
});
58+
59+
it('should return an empty string if input is empty', () => {
60+
expect(sanitizeString('')).toBe('');
61+
});
62+
63+
it('should encode spaces as non-breaking spaces', () => {
64+
const input = 'Hello world!'; // Note: The space here is a non-breaking space (U+00A0)
65+
const expectedOutput = 'Hello world!';
66+
expect(sanitizeString(input)).toBe(expectedOutput);
67+
});
68+
69+
it('should not double encode already encoded HTML entities', () => {
70+
const input = '&#60;div&#62;Hello&#60;/div&#62;';
71+
const expectedOutput = '&#60;div&#62;Hello&#60;/div&#62;';
72+
expect(sanitizeString(input)).toBe(expectedOutput);
73+
});
74+
75+
it('should handle long strings without performance issues', () => {
76+
const input = '<div>'.repeat(1000);
77+
const expectedOutput = '&#60;div&#62;'.repeat(1000);
78+
expect(sanitizeString(input)).toBe(expectedOutput);
79+
});
80+
81+
it('should handle mixed types of spaces', () => {
82+
const input = 'Hello\u0020world\u00A0!';
83+
const expectedOutput = 'Hello world !';
84+
expect(sanitizeString(input)).toBe(expectedOutput);
85+
});
86+
87+
it('should handle special Unicode control characters', () => {
88+
const input = 'Hello\u200BWorld'; // Zero-width space (U+200B)
89+
const expectedOutput = 'Hello\u200BWorld';
90+
expect(sanitizeString(input)).toBe(expectedOutput);
91+
});
92+
});

src/ui/MessageInput/hooks/usePaste/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useCallback } from 'react';
22
import DOMPurify from 'dompurify';
33

4-
import { inserTemplateToDOM } from './insertTemplate';
4+
import { insertTemplateToDOM } from './insertTemplate';
55
import { sanitizeString } from '../../utils';
66
import { DynamicProps } from './types';
77
import { domToMessageTemplate, extractTextFromNodes, getLeafNodes, getUsersFromWords, hasMention } from './utils';
@@ -73,7 +73,7 @@ export function usePaste({
7373
const mentionedUsers = channel.isGroupChannel() ? getUsersFromWords(words, channel) : [];
7474

7575
setMentionedUsers(mentionedUsers); // Update mentioned users state
76-
inserTemplateToDOM(words); // Insert mentions and content into the DOM
76+
insertTemplateToDOM(words); // Insert mentions and content into the DOM
7777
pasteNode.remove();
7878

7979
setIsInput(true);

src/ui/MessageInput/hooks/usePaste/insertTemplate.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Word } from './types';
22
import { sanitizeString } from '../../utils';
33
import renderMentionLabelToString from '../../../MentionUserLabel/renderToString';
44

5-
export function inserTemplateToDOM(templateList: Word[]): void {
5+
export function insertTemplateToDOM(templateList: Word[]): void {
66
const nodes = templateList.map((template) => {
77
const { text, userId } = template;
88
if (userId) {

src/ui/MessageInput/utils.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ import { BaseChannel } from '@sendbird/chat';
33
import { NodeNames, NodeTypes } from './const';
44
import { USER_MENTION_TEMP_CHAR } from '../../modules/GroupChannel/context/const';
55

6-
export const sanitizeString = (str?: string) => {
7-
return str?.replace(/[\u00A0-\u9999<>]/gim, (i) => ''.concat('&#', String(i.charCodeAt(0)), ';'));
6+
/**
7+
* - Converts `<` and `>` characters to their HTML entities (`&#60;` and `&#62;`).
8+
* - All other characters (including special symbols, emojis, and non-English text) remain unchanged.
9+
*/
10+
export const sanitizeString = (str: string = ''): string => {
11+
if (!str) return '';
12+
return str.replace(/[<>]/g, (char) => (char === '<' ? '&#60;' : '&#62;'));
813
};
914

1015
/**

0 commit comments

Comments
 (0)