Skip to content

Commit 4318c0d

Browse files
committed
fix: self closing tags getting escaped
1 parent 2788499 commit 4318c0d

File tree

2 files changed

+19
-3
lines changed

2 files changed

+19
-3
lines changed

src/string.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function parseBoolean(candidate: string | number) {
3232
* - Complex nested quotes or edge cases may not be handled perfectly
3333
* - For more complex HTML sanitization needs, consider using a proper HTML parser
3434
*
35-
* @param {string} text - The text to sanitize. Can be null or undefined.
35+
* @param {string | null | undefined} text - The text to sanitize. Can be null or undefined.
3636
* @returns {string} The sanitized text safe for HTML rendering, or the original value if null/undefined.
3737
*
3838
* @example
@@ -42,7 +42,7 @@ export function parseBoolean(candidate: string | number) {
4242
* sanitizeTextForRender('Price < $100 <strong>Sale!</strong>') // 'Price &lt; $100 <strong>Sale!</strong>'
4343
*/
4444
export function sanitizeTextForRender(text: string | null | undefined) {
45-
if (!text) return text;
45+
if (!text) return '';
4646

4747
return (
4848
text
@@ -72,8 +72,9 @@ export function sanitizeTextForRender(text: string | null | undefined) {
7272
// \s+ - whitespace before attributes
7373
// [^>]* - any characters except '>' (attribute content)
7474
// )? - attributes are optional
75+
// \/? - optional self-closing slash before >
7576
// ) - end lookbehind
7677
// > - matches '>'
77-
.replace(/(?<!<\/?\w+(?:\s+[^>]*)?)>/g, '&gt;')
78+
.replace(/(?<!<\/?\w+(?:\s+[^>]*)?\/?)>/g, '&gt;')
7879
);
7980
}

test/string.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,19 @@ describe('#sanitizeTextForRender', () => {
148148
const expected = `Original message:<br>&gt; User wrote: x &lt; 5<br><blockquote>Previous reply</blockquote>`;
149149
expect(sanitizeTextForRender(quoted)).toBe(expected);
150150
});
151+
152+
it('should handle self-closing tags correctly', () => {
153+
expect(sanitizeTextForRender('<br />')).toBe('<br />');
154+
expect(sanitizeTextForRender('<img src="test.jpg" />')).toBe('<img src="test.jpg" />');
155+
expect(sanitizeTextForRender('<input type="text" value="test" />')).toBe('<input type="text" value="test" />');
156+
expect(sanitizeTextForRender('<hr/>')).toBe('<hr/>');
157+
expect(sanitizeTextForRender('Text before <br /> text after')).toBe('Text before <br /> text after');
158+
expect(sanitizeTextForRender('<meta charset="UTF-8" />')).toBe('<meta charset="UTF-8" />');
159+
});
160+
161+
it('should handle complex URLs in attributes', () => {
162+
expect(sanitizeTextForRender('<img src="https://example.com/image.jpg?width=100&height=200&format=webp" />')).toBe('<img src="https://example.com/image.jpg?width=100&height=200&format=webp" />');
163+
expect(sanitizeTextForRender('<a href="https://api.example.com/v2/users/123/profile?include=posts&sort=desc">Profile</a>')).toBe('<a href="https://api.example.com/v2/users/123/profile?include=posts&sort=desc">Profile</a>');
164+
expect(sanitizeTextForRender('<iframe src="//cdn.example.com/embed/video/12345?autoplay=1&loop=0" />')).toBe('<iframe src="//cdn.example.com/embed/video/12345?autoplay=1&loop=0" />');
165+
});
151166
});

0 commit comments

Comments
 (0)