From 952515cbadf46a63befee8847cc60b40a7667df8 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Mon, 13 Apr 2026 16:39:27 +0200 Subject: [PATCH 1/2] CCR feedback on escaping file names --- src/common/utils.ts | 12 ++++++++-- src/test/common/utils.test.ts | 43 +++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/common/utils.ts b/src/common/utils.ts index 9a31423c21..10ee3c7005 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1009,8 +1009,16 @@ export function escapeRegExp(string: string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } -function escapeHtmlAttr(value: string): string { - return value.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(//g, '>'); +export function escapeHtmlAttr(value: string): string { + const escapedCharacters: Record = { + '&': '&', + '"': '"', + '\'': ''', + '<': '<', + '>': '>', + }; + + return value.replace(/[&"'<>]/g, (char) => escapedCharacters[char]); } export function truncate(value: string, maxLength: number, suffix = '...'): string { diff --git a/src/test/common/utils.test.ts b/src/test/common/utils.test.ts index c754eb5c5d..73718f5357 100644 --- a/src/test/common/utils.test.ts +++ b/src/test/common/utils.test.ts @@ -132,6 +132,14 @@ describe('utils', () => { assert(result.includes('link text')); assert(result.includes('data-local-file="src/file.ts"')); }); + + it('should escape HTML special characters in file paths', async () => { + const html = makePermalink('src/file&name"test.ts', 10); + const result = await utils.processPermalinks(html, repoName, authority, async () => true); + + assert(result.includes('data-local-file="src/file&name"test.ts"')); + assert(!result.includes('data-local-file="src/file&name"test.ts"')); + }); }); describe('processDiffLinks', () => { @@ -233,5 +241,40 @@ describe('utils', () => { assert(result.includes('data-local-file="src/found.ts"')); assert(!result.includes('data-local-file="src/other.ts"')); }); + + it('should escape HTML special characters in file names', async () => { + const hashMap: Record = { [diffHash]: 'src/file&name"test.ts' }; + const html = makeDiffLink(diffHash, 10); + const result = await utils.processDiffLinks(html, repoOwner, repoName, authority, hashMap, prNumber); + + assert(result.includes('data-local-file="src/file&name"test.ts"')); + assert(!result.includes('data-local-file="src/file&name"test.ts"')); + }); + }); + + describe('escapeHtmlAttr', () => { + it('should escape ampersands', () => { + assert.strictEqual(utils.escapeHtmlAttr('foo&bar'), 'foo&bar'); + }); + + it('should escape double quotes', () => { + assert.strictEqual(utils.escapeHtmlAttr('foo"bar'), 'foo"bar'); + }); + + it('should escape single quotes', () => { + assert.strictEqual(utils.escapeHtmlAttr('foo\'bar'), 'foo'bar'); + }); + + it('should escape angle brackets', () => { + assert.strictEqual(utils.escapeHtmlAttr('