fix: escape setting labels in innerHTML for defense-in-depth
This commit is contained in:
+12
-50
@@ -232,9 +232,9 @@ function createKeybindRow(label: string, desc: string, currentBind: Keybind, onB
|
|||||||
row.className = 'setting settName safety-' + s + ' keybind';
|
row.className = 'setting settName safety-' + s + ' keybind';
|
||||||
row.innerHTML =
|
row.innerHTML =
|
||||||
settingIcon(s, instant) +
|
settingIcon(s, instant) +
|
||||||
'<span class="setting-title">' + label + '</span>' +
|
'<span class="setting-title">' + escapeHtml(label) + '</span>' +
|
||||||
'<span class="keyIcon kpc-keyIcon">' + keybindDisplayString(currentBind) + '</span>' +
|
'<span class="keyIcon kpc-keyIcon">' + escapeHtml(keybindDisplayString(currentBind)) + '</span>' +
|
||||||
'<div class="setting-desc-new">' + desc + '</div>';
|
'<div class="setting-desc-new">' + escapeHtml(desc) + '</div>';
|
||||||
const keyEl = row.querySelector('.kpc-keyIcon') as HTMLElement;
|
const keyEl = row.querySelector('.kpc-keyIcon') as HTMLElement;
|
||||||
keyEl.addEventListener('click', () => {
|
keyEl.addEventListener('click', () => {
|
||||||
openKeybindDialog(label).then((newBind) => {
|
openKeybindDialog(label).then((newBind) => {
|
||||||
@@ -261,12 +261,12 @@ function createToggleRow(opts: {
|
|||||||
row.className = 'setting settName safety-' + s + ' bool';
|
row.className = 'setting settName safety-' + s + ' bool';
|
||||||
row.innerHTML =
|
row.innerHTML =
|
||||||
settingIcon(s, opts.instant, opts.refreshOnly, opts.restart) +
|
settingIcon(s, opts.instant, opts.refreshOnly, opts.restart) +
|
||||||
'<span class="setting-title">' + opts.label + '</span>' +
|
'<span class="setting-title">' + escapeHtml(opts.label) + '</span>' +
|
||||||
'<label class="switch">' +
|
'<label class="switch">' +
|
||||||
'<input type="checkbox" class="s-update"' + (opts.checked ? ' checked' : '') + (opts.disabled ? ' disabled' : '') + '>' +
|
'<input type="checkbox" class="s-update"' + (opts.checked ? ' checked' : '') + (opts.disabled ? ' disabled' : '') + '>' +
|
||||||
'<div class="slider round"></div>' +
|
'<div class="slider round"></div>' +
|
||||||
'</label>' +
|
'</label>' +
|
||||||
'<div class="setting-desc-new">' + opts.desc + '</div>';
|
'<div class="setting-desc-new">' + escapeHtml(opts.desc) + '</div>';
|
||||||
if (!opts.disabled) {
|
if (!opts.disabled) {
|
||||||
const cb = row.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
const cb = row.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
||||||
cb.addEventListener('change', () => {
|
cb.addEventListener('change', () => {
|
||||||
@@ -294,8 +294,8 @@ function createSelectRow(opts: {
|
|||||||
row.className = 'setting settName safety-' + s + ' sel';
|
row.className = 'setting settName safety-' + s + ' sel';
|
||||||
row.innerHTML =
|
row.innerHTML =
|
||||||
settingIcon(s, opts.instant, opts.refreshOnly, opts.restart) +
|
settingIcon(s, opts.instant, opts.refreshOnly, opts.restart) +
|
||||||
'<span class="setting-title">' + opts.label + '</span>' +
|
'<span class="setting-title">' + escapeHtml(opts.label) + '</span>' +
|
||||||
'<div class="setting-desc-new">' + opts.desc + '</div>';
|
'<div class="setting-desc-new">' + escapeHtml(opts.desc) + '</div>';
|
||||||
const select = document.createElement('select');
|
const select = document.createElement('select');
|
||||||
select.className = 's-update inputGrey2';
|
select.className = 's-update inputGrey2';
|
||||||
for (const o of opts.options) {
|
for (const o of opts.options) {
|
||||||
@@ -331,12 +331,12 @@ function createNumberRow(opts: {
|
|||||||
row.className = 'setting settName safety-' + s + ' num';
|
row.className = 'setting settName safety-' + s + ' num';
|
||||||
row.innerHTML =
|
row.innerHTML =
|
||||||
settingIcon(s, opts.instant, opts.refreshOnly, opts.restart) +
|
settingIcon(s, opts.instant, opts.refreshOnly, opts.restart) +
|
||||||
'<span class="setting-title">' + opts.label + '</span>' +
|
'<span class="setting-title">' + escapeHtml(opts.label) + '</span>' +
|
||||||
'<span class="setting-input-wrapper">' +
|
'<span class="setting-input-wrapper">' +
|
||||||
'<div class="slidecontainer"><input type="range" class="sliderM s-update-secondary" min="' + opts.min + '" max="' + opts.max + '" value="' + opts.value + '"></div>' +
|
'<div class="slidecontainer"><input type="range" class="sliderM s-update-secondary" min="' + opts.min + '" max="' + opts.max + '" value="' + opts.value + '"></div>' +
|
||||||
'<input type="number" class="rb-input s-update sliderVal" min="' + opts.min + '" max="' + opts.max + '" value="' + opts.value + '">' +
|
'<input type="number" class="rb-input s-update sliderVal" min="' + opts.min + '" max="' + opts.max + '" value="' + opts.value + '">' +
|
||||||
'</span>' +
|
'</span>' +
|
||||||
'<div class="setting-desc-new">' + opts.desc + '</div>';
|
'<div class="setting-desc-new">' + escapeHtml(opts.desc) + '</div>';
|
||||||
const rangeInput = row.querySelector('input[type="range"]') as HTMLInputElement;
|
const rangeInput = row.querySelector('input[type="range"]') as HTMLInputElement;
|
||||||
const numInput = row.querySelector('input[type="number"]') as HTMLInputElement;
|
const numInput = row.querySelector('input[type="number"]') as HTMLInputElement;
|
||||||
rangeInput.addEventListener('input', () => {
|
rangeInput.addEventListener('input', () => {
|
||||||
@@ -369,7 +369,7 @@ function createCheckboxGrid(opts: {
|
|||||||
}): HTMLElement {
|
}): HTMLElement {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'setting settName safety-0 multisel';
|
row.className = 'setting settName safety-0 multisel';
|
||||||
row.innerHTML = '<span class="setting-title">' + opts.header + '</span>';
|
row.innerHTML = '<span class="setting-title">' + escapeHtml(opts.header) + '</span>';
|
||||||
const grid = document.createElement('div');
|
const grid = document.createElement('div');
|
||||||
grid.className = 'kpc-multisel-parent';
|
grid.className = 'kpc-multisel-parent';
|
||||||
for (const item of opts.items) {
|
for (const item of opts.items) {
|
||||||
@@ -992,7 +992,7 @@ function buildAccountsSection(body: HTMLElement, accountsArr: any[]): void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildChatSection(body: HTMLElement, gameConf: any, translatorConf: any, bag: SettingsBag): void {
|
function buildChatSection(body: HTMLElement, gameConf: any, translatorConf: any): void {
|
||||||
const game = { betterChat: true, chatHistorySize: 200, ...gameConf };
|
const game = { betterChat: true, chatHistorySize: 200, ...gameConf };
|
||||||
|
|
||||||
function saveGame(): void {
|
function saveGame(): void {
|
||||||
@@ -1012,11 +1012,6 @@ function buildChatSection(body: HTMLElement, gameConf: any, translatorConf: any,
|
|||||||
onChange: (v) => { game.chatHistorySize = v; saveGame(); setChatHistorySize(v); },
|
onChange: (v) => { game.chatHistorySize = v; saveGame(); setChatHistorySize(v); },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
body.appendChild(createKeybindRow('Pause Chat', 'Freeze chat auto-scroll to read history (default F10)', bag.binds.pauseChat, (b) => {
|
|
||||||
bag.binds.pauseChat = b;
|
|
||||||
bag.saveBinds();
|
|
||||||
}, undefined, true));
|
|
||||||
|
|
||||||
// Translator settings inline
|
// Translator settings inline
|
||||||
const tl = { enabled: true, targetLanguage: 'en', showLanguageTag: true, ...translatorConf };
|
const tl = { enabled: true, targetLanguage: 'en', showLanguageTag: true, ...translatorConf };
|
||||||
|
|
||||||
@@ -1307,7 +1302,6 @@ function renderSettings(searchQuery?: string): void {
|
|||||||
matchmaker: { key: 'F6', ctrl: false, shift: false, alt: false },
|
matchmaker: { key: 'F6', ctrl: false, shift: false, alt: false },
|
||||||
matchmakerAccept: { key: 'Enter', ctrl: false, shift: false, alt: false },
|
matchmakerAccept: { key: 'Enter', ctrl: false, shift: false, alt: false },
|
||||||
matchmakerCancel: { key: 'Escape', ctrl: false, shift: false, alt: false },
|
matchmakerCancel: { key: 'Escape', ctrl: false, shift: false, alt: false },
|
||||||
pauseChat: { key: 'F10', ctrl: false, shift: false, alt: false },
|
|
||||||
fullscreenToggle: { key: 'F11', ctrl: false, shift: false, alt: false },
|
fullscreenToggle: { key: 'F11', ctrl: false, shift: false, alt: false },
|
||||||
};
|
};
|
||||||
const binds = { ...defaultBinds, ...keybindsConf };
|
const binds = { ...defaultBinds, ...keybindsConf };
|
||||||
@@ -1323,7 +1317,7 @@ function renderSettings(searchQuery?: string): void {
|
|||||||
buildGeneralSection(genSec.body, gameConf, uiConfRaw, allConf.performance, bag);
|
buildGeneralSection(genSec.body, gameConf, uiConfRaw, allConf.performance, bag);
|
||||||
buildSwapperSection(swapSec.body, swapperConf, uiConfRaw);
|
buildSwapperSection(swapSec.body, swapperConf, uiConfRaw);
|
||||||
buildMatchmakerSection(mmSec.body, mmConf, bag);
|
buildMatchmakerSection(mmSec.body, mmConf, bag);
|
||||||
buildChatSection(chatSec.body, gameConf, translatorConf, bag);
|
buildChatSection(chatSec.body, gameConf, translatorConf);
|
||||||
buildDiscordSection(discordSec.body, discordConf);
|
buildDiscordSection(discordSec.body, discordConf);
|
||||||
buildAccountsSection(accSec.body, allConf.accounts);
|
buildAccountsSection(accSec.body, allConf.accounts);
|
||||||
buildAdvancedSection(advSec.body, advConf, allConf.performance, isWindows);
|
buildAdvancedSection(advSec.body, advConf, allConf.performance, isWindows);
|
||||||
@@ -1570,38 +1564,6 @@ ipcRenderer.on('matchmaker-find', (_e, mmConfig: MatchmakerConfig) => {
|
|||||||
fetchGame(mmConfig, _console).catch((err) => _console.error('[KCC] Matchmaker error:', err));
|
fetchGame(mmConfig, _console).catch((err) => _console.error('[KCC] Matchmaker error:', err));
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Chat pause ──
|
|
||||||
let chatPaused = false;
|
|
||||||
let chatSavedScrollTop = 0;
|
|
||||||
|
|
||||||
function onChatWheel(e: WheelEvent): void {
|
|
||||||
const chatList = document.getElementById('chatList');
|
|
||||||
if (!chatList) return;
|
|
||||||
chatSavedScrollTop = Math.max(0, Math.min(
|
|
||||||
chatSavedScrollTop + e.deltaY,
|
|
||||||
chatList.scrollHeight - chatList.clientHeight,
|
|
||||||
));
|
|
||||||
chatList.scrollTop = chatSavedScrollTop;
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcRenderer.on('toggle-chat-pause', () => {
|
|
||||||
const chatList = document.getElementById('chatList');
|
|
||||||
if (!chatList) return;
|
|
||||||
|
|
||||||
chatPaused = !chatPaused;
|
|
||||||
|
|
||||||
if (chatPaused) {
|
|
||||||
chatSavedScrollTop = chatList.scrollTop;
|
|
||||||
chatList.classList.add('kpc-chat-paused');
|
|
||||||
chatList.style.overflow = 'hidden';
|
|
||||||
chatList.addEventListener('wheel', onChatWheel, { passive: true });
|
|
||||||
} else {
|
|
||||||
chatList.classList.remove('kpc-chat-paused');
|
|
||||||
chatList.style.overflow = '';
|
|
||||||
chatList.removeEventListener('wheel', onChatWheel);
|
|
||||||
chatList.scrollTop = chatList.scrollHeight;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Wait for main process to signal page load, then poll for settings window ──
|
// ── Wait for main process to signal page load, then poll for settings window ──
|
||||||
ipcRenderer.on('main_did-finish-load', () => {
|
ipcRenderer.on('main_did-finish-load', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user