Skip to content

Commit fa7dc82

Browse files
authored
Merge pull request #845 from modos189/fix/issue-844
fix: RegionScore tooltip rendering by adding selective escaping
2 parents be2fe58 + 9e3429d commit fa7dc82

File tree

2 files changed

+44
-3
lines changed

2 files changed

+44
-3
lines changed

core/code/utils.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,17 +303,38 @@ const escapeJS = function (str) {
303303
* @memberof IITC.utils
304304
* @function escapeHtml
305305
* @param {string} str - The string to escape.
306+
* @param {string[]} [allowedTags] - Optional array of allowed HTML tags that should not be escaped.
306307
* @returns {string} The escaped string.
307308
*/
308-
const escapeHtml = function (str) {
309+
const escapeHtml = function (str, allowedTags = []) {
309310
const escapeMap = {
310311
'&': '&',
311312
'<': '&lt;',
312313
'>': '&gt;',
313314
'"': '&quot;',
314315
"'": '&#39;',
315316
};
316-
return str.replace(/[&<>"']/g, (char) => escapeMap[char]);
317+
318+
if (allowedTags.length === 0) {
319+
return str.replace(/[&<>"']/g, (char) => escapeMap[char]);
320+
}
321+
322+
// Create pattern for allowed tags (self-closing and paired)
323+
const allowedTagsPattern = new RegExp(`(<\\/?(?:${allowedTags.join('|')})>)`, 'g');
324+
325+
// Split text by allowed tags to preserve them
326+
const parts = str.split(allowedTagsPattern);
327+
328+
return parts
329+
.map((part) => {
330+
// If part matches allowed tags pattern, keep as is
331+
if (allowedTagsPattern.test(part)) {
332+
return part;
333+
}
334+
// Otherwise, escape HTML
335+
return part.replace(/[&<>"']/g, (char) => escapeMap[char]);
336+
})
337+
.join('');
317338
};
318339

319340
/**
@@ -387,7 +408,7 @@ const textToTable = function (text) {
387408
for (const row of rows) {
388409
let rowHtml = '<tr>';
389410
for (let k = 0; k < row.length; k++) {
390-
const cell = IITC.utils.escapeHtml(row[k]);
411+
const cell = IITC.utils.escapeHtml(row[k], ['hr', 'br', 'b', 'i', 'strong', 'em']);
391412
const colspan = k === 0 && row.length < columnCount ? ` colspan="${columnCount - row.length + 1}"` : '';
392413
rowHtml += `<td${colspan}>${cell}</td>`;
393414
}

test/utils.spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,26 @@ describe('IITC.utils.escapeHtml', () => {
453453
const result = IITC.utils.escapeHtml('No special chars');
454454
expect(result).to.equal('No special chars');
455455
});
456+
457+
it('should preserve allowed tags when provided', () => {
458+
const result = IITC.utils.escapeHtml('<b>Bold</b> and <script>alert("XSS")</script>', ['b']);
459+
expect(result).to.equal('<b>Bold</b> and &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;');
460+
});
461+
462+
it('should preserve multiple allowed tags', () => {
463+
const result = IITC.utils.escapeHtml('<b>Bold</b>, <i>Italic</i>, and <script>alert("XSS")</script>', ['b', 'i']);
464+
expect(result).to.equal('<b>Bold</b>, <i>Italic</i>, and &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;');
465+
});
466+
467+
it('should preserve self-closing tags like <hr>', () => {
468+
const result = IITC.utils.escapeHtml('Text <hr> and <script>alert("XSS")</script>', ['hr']);
469+
expect(result).to.equal('Text <hr> and &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;');
470+
});
471+
472+
it('should preserve closing tags correctly', () => {
473+
const result = IITC.utils.escapeHtml('<strong>Strong</strong> and <div>unsafe</div>', ['strong']);
474+
expect(result).to.equal('<strong>Strong</strong> and &lt;div&gt;unsafe&lt;/div&gt;');
475+
});
456476
});
457477

458478
describe('IITC.utils.prettyEnergy', () => {

0 commit comments

Comments
 (0)