Skip to content

Commit c4f64b7

Browse files
committed
fix: replace 100+ unsafe innerHTML assignments with textContent/escapeHTML
1 parent c7549c3 commit c4f64b7

20 files changed

Lines changed: 172 additions & 170 deletions

js/widgets/__tests__/help.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ afterEach(() => {
124124
function createMockActivity(options = {}) {
125125
return {
126126
__keyPressed: jest.fn(),
127+
textMsg: jest.fn(),
127128
blocks: {
128129
activeBlock: options.activeBlock || null,
129130
blockList: options.blockList || {},

js/widgets/__tests__/status.test.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
global._ = msg => msg;
1616

17+
// Mock escapeHTML utility used by sanitized innerHTML
18+
global.escapeHTML = (str) => String(str);
19+
1720
global._THIS_IS_MUSIC_BLOCKS_ = true;
1821
global.MATRIXBUTTONHEIGHT = 40;
1922
global.MATRIXSOLFEHEIGHT = 30;
@@ -440,7 +443,7 @@ describe("StatusMatrix Widget", () => {
440443
});
441444

442445
statusMatrix.updateAll();
443-
expect(statusMatrix._statusTable.rows[1].cells[1].innerHTML).toBe(4);
446+
expect(statusMatrix._statusTable.rows[1].cells[1].textContent).toBe(4);
444447
});
445448

446449
test("handles namedbox with value from logo.boxes", () => {
@@ -479,7 +482,7 @@ describe("StatusMatrix Widget", () => {
479482
};
480483
mockActivity.logo.statusFields = [[0, "unknownblock"]];
481484
statusMatrix.updateAll();
482-
expect(statusMatrix._statusTable.rows[1].cells[1].innerHTML).toBe(99);
485+
expect(statusMatrix._statusTable.rows[1].cells[1].textContent).toBe(99);
483486
});
484487
});
485488

@@ -598,7 +601,7 @@ describe("StatusMatrix Widget", () => {
598601

599602
statusMatrix.updateAll();
600603

601-
expect(mockCell.innerHTML).toContain("♯");
604+
expect(mockCell.textContent).toContain("♯");
602605
});
603606

604607
test("replaces b with ♭ in note display", () => {
@@ -611,7 +614,7 @@ describe("StatusMatrix Widget", () => {
611614
});
612615

613616
statusMatrix.updateAll();
614-
expect(statusMatrix._statusTable.rows[2].cells[1].innerHTML).toContain("♭");
617+
expect(statusMatrix._statusTable.rows[2].cells[1].textContent).toContain("♭");
615618
});
616619

617620
test("handles empty noteStatus array", () => {

js/widgets/__tests__/temperament.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const TemperamentWidget = require("../temperament");
22
describe("TemperamentWidget basic tests", () => {
33
let widget;
44
global._ = jest.fn(text => text);
5-
5+
global.escapeHTML = jest.fn(str => String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;'));
66
beforeEach(() => {
77
document.body.innerHTML = `
88
<table id="temperamentTable"></table>

js/widgets/arpeggio.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ class Arpeggio {
152152
labelCell.style.minWidth = 0;
153153
labelCell.style.maxWidth = 0;
154154
labelCell.className = "headcol";
155-
labelCell.innerHTML = this._rowLabels[j];
155+
labelCell.textContent = this._rowLabels[j];
156156

157157
arpeggioCell = arpeggioTableRow.insertCell();
158158
// Create tables to store individual notes.
@@ -392,7 +392,7 @@ class Arpeggio {
392392
cell.style.lineHeight = 100 + "%";
393393
cell.setAttribute("id", arpeggioIdx);
394394
cell.className = "headcol";
395-
cell.innerHTML = arpeggioName;
395+
cell.textContent = arpeggioName;
396396
cell.style.backgroundColor = platformColor.selectorBackground;
397397
}
398398

js/widgets/jseditor.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ class JSEditor {
363363
helpBtn.style.fontSize = "2rem";
364364
helpBtn.style.background = "#2196f3";
365365
helpBtn.style.cursor = "pointer";
366-
helpBtn.innerHTML = "help_outline";
366+
helpBtn.textContent = "help_outline";
367367
helpBtn.onclick = this._toggleHelp.bind(this);
368368
menuLeft.appendChild(helpBtn);
369369
generateTooltip(helpBtn, _("Help"));
@@ -376,7 +376,7 @@ class JSEditor {
376376
generateBtn.style.fontSize = "2rem";
377377
generateBtn.style.background = "#2196f3";
378378
generateBtn.style.cursor = "pointer";
379-
generateBtn.innerHTML = "autorenew";
379+
generateBtn.textContent = "autorenew";
380380
generateBtn.onclick = this._generateCode.bind(this);
381381
menuLeft.appendChild(generateBtn);
382382
generateTooltip(generateBtn, _("Reset Code"));
@@ -389,7 +389,7 @@ class JSEditor {
389389
runBtn.style.fontSize = "2rem";
390390
runBtn.style.background = "#2196f3";
391391
runBtn.style.cursor = "pointer";
392-
runBtn.innerHTML = "play_arrow";
392+
runBtn.textContent = "play_arrow";
393393
runBtn.onclick = this._runCode.bind(this);
394394
menuLeft.appendChild(runBtn);
395395
menubar.appendChild(menuLeft);
@@ -403,7 +403,7 @@ class JSEditor {
403403
convertBtn.style.fontSize = "2rem";
404404
convertBtn.style.background = "#2196f3";
405405
convertBtn.style.cursor = "pointer";
406-
convertBtn.innerHTML = "transform";
406+
convertBtn.textContent = "transform";
407407
convertBtn.onclick = this._codeToBlocks.bind(this);
408408
menuLeft.appendChild(convertBtn);
409409
menubar.appendChild(menuLeft);
@@ -424,7 +424,7 @@ class JSEditor {
424424
styleBtn.style.fontSize = "2rem";
425425
styleBtn.style.background = "#2196f3";
426426
styleBtn.style.cursor = "pointer";
427-
styleBtn.innerHTML = "invert_colors";
427+
styleBtn.textContent = "invert_colors";
428428
styleBtn.onclick = this._changeStyle.bind(this);
429429
menuRight.appendChild(styleBtn);
430430
menubar.appendChild(menuRight);
@@ -538,7 +538,7 @@ class JSEditor {
538538
arrowBtn.style.cursor = "pointer";
539539
arrowBtn.style.lineHeight = "0.75rem";
540540
arrowBtn.style.marginLeft = "0";
541-
arrowBtn.innerHTML = "keyboard_arrow_down";
541+
arrowBtn.textContent = "keyboard_arrow_down";
542542
arrowBtn.onclick = this._toggleConsole.bind(this);
543543
consolelabel.appendChild(arrowBtn);
544544
generateTooltip(arrowBtn, _("Toggle Console"), "left");
@@ -1161,11 +1161,11 @@ class JSEditor {
11611161
if (this.isOpen) {
11621162
this.isOpen = false;
11631163
editorconsole.style.display = "none";
1164-
if (arrowBtn) arrowBtn.innerHTML = "keyboard_arrow_up";
1164+
if (arrowBtn) arrowBtn.textContent = "keyboard_arrow_up";
11651165
} else {
11661166
this.isOpen = true;
11671167
editorconsole.style.display = "block";
1168-
if (arrowBtn) arrowBtn.innerHTML = "keyboard_arrow_down";
1168+
if (arrowBtn) arrowBtn.textContent = "keyboard_arrow_down";
11691169
}
11701170
}
11711171

js/widgets/meterwidget.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,13 @@ class MeterWidget {
249249

250250
const divInput = document.createElement("div");
251251
divInput.className = "wfbtItem";
252-
divInput.innerHTML = `<input style="float: left;" value="${v1}" type="number" id="beatValue" min="1" max="16" >`;
252+
divInput.innerHTML = `<input style="float: left;" value="${escapeHTML(v1)}" type="number" id="beatValue" min="1" max="16" >`;
253253

254254
const divInput2 = document.createElement("div");
255255
divInput2.className = "wfbtItem";
256-
divInput2.innerHTML = `<input style="float: left;" value="${
256+
divInput2.innerHTML = `<input style="float: left;" value="${escapeHTML(
257257
1 / this._beatValue
258-
}" type="number" id="beatValue" min="1" max="35">`;
258+
)}" type="number" id="beatValue" min="1" max="35">`;
259259

260260
widgetWindow._toolbar.appendChild(divInput);
261261
widgetWindow._toolbar.appendChild(divInput2);
@@ -419,11 +419,11 @@ class MeterWidget {
419419
_addButton(row, icon, iconSize, label) {
420420
const cell = row.insertCell(-1);
421421
cell.innerHTML = `&nbsp;&nbsp;<img
422-
src="header-icons/${icon}"
423-
title="${label}"
424-
alt="${label}"
425-
height="${iconSize}"
426-
width="${iconSize}"
422+
src="header-icons/${escapeHTML(icon)}"
423+
title="${escapeHTML(label)}"
424+
alt="${escapeHTML(label)}"
425+
height="${escapeHTML(iconSize)}"
426+
width="${escapeHTML(iconSize)}"
427427
vertical-align="middle"
428428
align-content="center"
429429
>&nbsp;&nbsp;`;

js/widgets/meterwidget.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
* along with this program. If not, see <https://www.gnu.org/licenses/>.
2121
*/
2222

23+
// Mock escapeHTML utility used by sanitized innerHTML
24+
global.escapeHTML = (str) => String(str);
25+
2326
const MeterWidget = require("./meterwidget.js");
2427

2528
// --- 1. Global Mocks (Fake the Browser Environment) ---

js/widgets/modewidget.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ class ModeWidget {
277277

278278
// console.debug(_(currentModeName[1]));
279279
const name = currentModeName[0] + " " + _(currentModeName[1]);
280-
table.rows[n].cells[0].innerHTML = name;
280+
table.rows[n].cells[0].textContent = name;
281281
this.widgetWindow.updateTitle(name);
282282

283283
// Set the notes for this mode.
@@ -882,14 +882,14 @@ class ModeWidget {
882882
}
883883

884884
const name = currentKey + " " + _(mode);
885-
table.rows[n].cells[0].innerHTML = name;
885+
table.rows[n].cells[0].textContent = name;
886886
this.widgetWindow.updateTitle(name);
887887
return;
888888
}
889889
}
890890

891891
// console.debug('setModeName:' + 'not found');
892-
table.rows[n].cells[0].innerHTML = "";
892+
table.rows[n].cells[0].textContent = "";
893893
this.widgetWindow.updateTitle("");
894894
}
895895

@@ -902,13 +902,13 @@ class ModeWidget {
902902
const n = table.rows.length - 1;
903903

904904
// If the mode is not in the list, save it as the new custom mode.
905-
if (table.rows[n].cells[0].innerHTML === "") {
905+
if (table.rows[n].cells[0].textContent === "") {
906906
const customMode = this._calculateMode();
907907
// console.debug("custom mode: " + customMode);
908908
this.storage.custommode = JSON.stringify(customMode);
909909
}
910910

911-
let modeName = table.rows[n].cells[0].innerHTML;
911+
let modeName = table.rows[n].cells[0].textContent;
912912
if (modeName === "") {
913913
modeName = _("custom");
914914
}

js/widgets/musickeyboard.js

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,13 +1571,13 @@ function MusicKeyboard(activity) {
15711571
cell.style.left = "0px";
15721572
cell.className = "headcol"; // This cell is fixed horizontally.
15731573
if (this.displayLayout[i].noteName === "drum") {
1574-
cell.innerHTML = this.displayLayout[i].voice;
1574+
cell.textContent = this.displayLayout[i].voice;
15751575
} else if (this.displayLayout[i].noteName === "hertz") {
1576-
cell.innerHTML = this.displayLayout[i].noteOctave.toString() + "HZ";
1576+
cell.textContent = this.displayLayout[i].noteOctave.toString() + "HZ";
15771577
} else {
1578-
cell.innerHTML = `${i18nSolfege(
1578+
cell.innerHTML = `${escapeHTML(i18nSolfege(
15791579
this.displayLayout[i].noteName
1580-
)}<sub>${this.displayLayout[i].noteOctave.toString()}</sub>`;
1580+
))}<sub>${escapeHTML(this.displayLayout[i].noteOctave.toString())}</sub>`;
15811581
}
15821582

15831583
cell.setAttribute("id", "labelcol" + (n - i - 1));
@@ -1621,7 +1621,7 @@ function MusicKeyboard(activity) {
16211621
cell.style.minWidth = Math.floor(MATRIXSOLFEWIDTH * this._cellScale) * 1.5 + "px";
16221622
cell.style.maxWidth = cell.style.minWidth;
16231623
cell.className = "headcol"; // This cell is fixed horizontally.
1624-
cell.innerHTML = _("Note value");
1624+
cell.textContent = _("Note value");
16251625
cell.style.position = "sticky";
16261626
cell.style.left = "0px";
16271627
cell.style.zIndex = "1";
@@ -1673,7 +1673,7 @@ function MusicKeyboard(activity) {
16731673
cell.style.maxWidth = cell.style.width;
16741674
cell.style.lineHeight = 60 + "%";
16751675
cell.style.textAlign = "center";
1676-
cell.innerHTML = `${dur[0].toString()}/${dur[1].toString()}`;
1676+
cell.textContent = `${dur[0].toString()}/${dur[1].toString()}`;
16771677
cell.setAttribute("id", "cells-" + j);
16781678
cell.setAttribute("start", selectedNotes[j].startTime);
16791679
cell.setAttribute("dur", maxWidth);
@@ -2531,7 +2531,7 @@ function MusicKeyboard(activity) {
25312531

25322532
const cell = docById("labelcol" + (this.layout.length - index - 1));
25332533
this.layout[index].noteOctave = parseInt(blockValue);
2534-
cell.innerHTML = this.layout[index].noteName + this.layout[index].noteOctave.toString();
2534+
cell.textContent = this.layout[index].noteName + this.layout[index].noteOctave.toString();
25352535
this._notesPlayed.map(function (item) {
25362536
if (item.objId == this.layout[index].blockNumber) {
25372537
item.noteOctave = parseInt(blockValue);
@@ -2592,7 +2592,7 @@ function MusicKeyboard(activity) {
25922592
const cell = docById("labelcol" + (this.layout.length - index - 1));
25932593
this.layout[index].noteName = label;
25942594
this.layout[index].noteOctave = octave;
2595-
cell.innerHTML = this.layout[index].noteName + this.layout[index].noteOctave.toString();
2595+
cell.textContent = this.layout[index].noteName + this.layout[index].noteOctave.toString();
25962596
const temp1 = label;
25972597
let temp2;
25982598
if (temp1 in FIXEDSOLFEGE1) {
@@ -2781,9 +2781,9 @@ function MusicKeyboard(activity) {
27812781
]);
27822782
newel.innerHTML = `${
27832783
myrowId < WHITEKEYS.length
2784-
? `<small>(${String.fromCharCode(WHITEKEYS[myrowId])})</small><br/>`
2784+
? `<small>(${escapeHTML(String.fromCharCode(WHITEKEYS[myrowId]))})</small><br/>`
27852785
: ""
2786-
}${this.displayLayout[p].voice}`;
2786+
}${escapeHTML(this.displayLayout[p].voice)}`;
27872787

27882788
this.displayLayout[p].objId = "whiteRow" + myrowId.toString();
27892789

@@ -2918,17 +2918,17 @@ function MusicKeyboard(activity) {
29182918
nname = this.displayLayout[p].noteName.replace(FLAT, "").replace("b", "");
29192919
if (this.displayLayout[p].blockNumber <= FAKEBLOCKNUMBER) {
29202920
if (SOLFEGENAMES.includes(nname)) {
2921-
newel2.innerHTML = `<small>(${String.fromCharCode(
2921+
newel2.innerHTML = `<small>(${escapeHTML(String.fromCharCode(
29222922
BLACKKEYS[myrow2Id]
2923-
)})</small><br/>${i18nSolfege(nname)}${FLAT}${
2923+
))})</small><br/>${escapeHTML(i18nSolfege(nname))}${FLAT}${escapeHTML(
29242924
this.displayLayout[p].noteOctave
2925-
}`;
2925+
)}`;
29262926
} else {
2927-
newel2.innerHTML = `<small>(${String.fromCharCode(
2927+
newel2.innerHTML = `<small>(${escapeHTML(String.fromCharCode(
29282928
BLACKKEYS[myrow2Id]
2929-
)})</small><br/>${this.displayLayout[p].noteName}${
2929+
))})</small><br/>${escapeHTML(this.displayLayout[p].noteName)}${escapeHTML(
29302930
this.displayLayout[p].noteOctave
2931-
}`;
2931+
)}`;
29322932
}
29332933
}
29342934
if (p < this.layout.length) {
@@ -2964,17 +2964,17 @@ function MusicKeyboard(activity) {
29642964
if (SOLFEGENAMES.includes(this.displayLayout[p].noteName)) {
29652965
newel.innerHTML = `${
29662966
myrowId < WHITEKEYS.length
2967-
? `<small>(${String.fromCharCode(WHITEKEYS[myrowId])})</small><br/>`
2967+
? `<small>(${escapeHTML(String.fromCharCode(WHITEKEYS[myrowId]))})</small><br/>`
29682968
: ""
2969-
}${i18nSolfege(this.displayLayout[p].noteName)}${
2969+
}${escapeHTML(i18nSolfege(this.displayLayout[p].noteName))}${escapeHTML(
29702970
this.displayLayout[p].noteOctave
2971-
}`;
2971+
)}`;
29722972
} else {
29732973
newel.innerHTML = `${
29742974
myrowId < WHITEKEYS.length
2975-
? `<small>(${String.fromCharCode(WHITEKEYS[myrowId])})</small><br/>`
2975+
? `<small>(${escapeHTML(String.fromCharCode(WHITEKEYS[myrowId]))})</small><br/>`
29762976
: ""
2977-
}${this.displayLayout[p].noteName}${this.displayLayout[p].noteOctave}`;
2977+
}${escapeHTML(this.displayLayout[p].noteName)}${escapeHTML(this.displayLayout[p].noteOctave)}`;
29782978
}
29792979
}
29802980
if (p < this.layout.length) {

0 commit comments

Comments
 (0)