From fe2e993d69c45e44696224985e558efca79db181 Mon Sep 17 00:00:00 2001 From: Oluwole Adebiyi Date: Wed, 4 Dec 2024 01:10:34 +0100 Subject: [PATCH] fix: enable client parser to retain carriage return characters (#902) * fix: enable client parser to retain carriage return characters * fix: enable client parser to retain carriage return characters * chore: only find \r not \n * chore: add missing tests * chore: add missing tests --- package-lock.json | 14 +++++---- src/client/domparser.ts | 5 ++++ src/client/utilities.ts | 22 +++++++++++++- test/cases/html.js | 22 ++++++++++++++ test/server/client.test.ts | 60 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6e32414..f369cc8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1923,8 +1923,7 @@ "version": "4.3.16", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.16.tgz", "integrity": "sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/conventional-commits-parser": { "version": "5.0.0", @@ -3600,10 +3599,12 @@ } }, "node_modules/define-properties": { - "version": "1.2.0", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, - "license": "MIT", "dependencies": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, @@ -13276,9 +13277,12 @@ } }, "define-properties": { - "version": "1.2.0", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "requires": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } diff --git a/src/client/domparser.ts b/src/client/domparser.ts index ae252909..98513351 100644 --- a/src/client/domparser.ts +++ b/src/client/domparser.ts @@ -1,3 +1,5 @@ +import { escapeSpecialCharacters } from './utilities'; + // constants const HTML = 'html'; const HEAD = 'head'; @@ -116,6 +118,9 @@ if (template && template.content) { * @returns - DOM nodes. */ export default function domparser(html: string): NodeList { + // Escape special characters before parsing + html = escapeSpecialCharacters(html); + const match = html.match(FIRST_TAG_REGEX); const firstTagName = match && match[1] ? match[1].toLowerCase() : ''; diff --git a/src/client/utilities.ts b/src/client/utilities.ts index 7440ac3b..05ed18c9 100644 --- a/src/client/utilities.ts +++ b/src/client/utilities.ts @@ -51,6 +51,26 @@ function formatTagName(tagName: string): string { return tagName; } +/** + * Escapes special characters before parsing. + * + * @param html - The HTML string. + * @returns - HTML string with escaped special characters. + */ +export function escapeSpecialCharacters(html: string): string { + return html.replace(/\r/g, '\\r'); +} + +/** + * Reverts escaped special characters back to actual characters. + * + * @param text - The text with escaped characters. + * @returns - Text with escaped characters reverted. + */ +export function revertEscapedCharacters(text: string): string { + return text.replace(/\\r/g, '\r'); +} + /** * Transforms DOM nodes to `domhandler` nodes. * @@ -95,7 +115,7 @@ export function formatDOM( } case 3: - current = new Text(node.nodeValue!); + current = new Text(revertEscapedCharacters(node.nodeValue!)); break; case 8: diff --git a/test/cases/html.js b/test/cases/html.js index d747d5ad..e946331f 100644 --- a/test/cases/html.js +++ b/test/cases/html.js @@ -305,6 +305,28 @@ module.exports = [ data: ' ', }, + // text with special characters + { + name: 'text with carriage return', + data: 'Hello\rWorld', + }, + { + name: 'text with multiple carriage returns', + data: 'Hello\rDear\rWorld', + }, + { + name: 'text with mixed newlines and carriage returns', + data: 'Hello\rWorld\nNew\rLine', + }, + { + name: 'text with carriage return in tag', + data: '
Hello\rWorld
', + }, + { + name: 'nested tags with carriage returns', + data: '
Hello\rBeautiful\rWorld
', + }, + // custom tag { name: 'custom tag', diff --git a/test/server/client.test.ts b/test/server/client.test.ts index 24635a07..8bc5a1fa 100644 --- a/test/server/client.test.ts +++ b/test/server/client.test.ts @@ -1,6 +1,8 @@ import { expect } from 'chai'; import { formatDOM } from '../../src/client/utilities'; +import { revertEscapedCharacters } from '../../src/client/utilities'; +import { escapeSpecialCharacters } from '../../src/client/utilities'; describe('client utilities', () => { describe('formatDOM', () => { @@ -10,4 +12,62 @@ describe('client utilities', () => { ).to.deep.equal([]); }); }); + + describe('escapeSpecialCharacters', () => { + it('escapes carriage return characters', () => { + const input = 'Hello\rWorld'; + const expected = 'Hello\\rWorld'; + expect(escapeSpecialCharacters(input)).to.equal(expected); + }); + + it('does not modify strings without special characters', () => { + const input = 'Hello World'; + expect(escapeSpecialCharacters(input)).to.equal(input); + }); + + it('handles empty strings', () => { + expect(escapeSpecialCharacters('')).to.equal(''); + }); + + it('handles multiple carriage returns', () => { + const input = 'Hello\rDear\rWorld'; + const expected = 'Hello\\rDear\\rWorld'; + expect(escapeSpecialCharacters(input)).to.equal(expected); + }); + + it('only escapes carriage returns', () => { + const input = 'Hello\rWorld\n'; // \n should not be affected + const expected = 'Hello\\rWorld\n'; + expect(escapeSpecialCharacters(input)).to.equal(expected); + }); + }); + + describe('revertEscapedCharacters', () => { + it('reverts escaped carriage return characters', () => { + const input = 'Hello\\rWorld'; + const expected = 'Hello\rWorld'; + expect(revertEscapedCharacters(input)).to.equal(expected); + }); + + it('does not modify strings without escaped characters', () => { + const input = 'Hello World'; + expect(revertEscapedCharacters(input)).to.equal(input); + }); + + it('handles empty strings', () => { + expect(revertEscapedCharacters('')).to.equal(''); + }); + + it('handles multiple escaped carriage returns', () => { + const input = 'Hello\\rDear\\rWorld'; + const expected = 'Hello\rDear\rWorld'; + expect(revertEscapedCharacters(input)).to.equal(expected); + }); + + it('only reverts escaped carriage returns', () => { + const input = 'Hello\\rWorld\\n'; // \n should not be affected + const expected = 'Hello\rWorld\\n'; + expect(revertEscapedCharacters(input)).to.equal(expected); + }); + }); });