From 52ee406a6ccad8f58d83bd1ffd5d043fa02ef75a Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Sun, 17 May 2026 21:47:56 -0700 Subject: [PATCH] Fix quarantine action button escaping --- webui/static/wishlist-tools.js | 36 ++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/webui/static/wishlist-tools.js b/webui/static/wishlist-tools.js index 864b0825..8e6df5b2 100644 --- a/webui/static/wishlist-tools.js +++ b/webui/static/wishlist-tools.js @@ -3137,21 +3137,17 @@ function renderQuarantineEntry(entry) { const triggerLabel = triggerLabels[entry.trigger] || entry.trigger || 'Unknown'; const triggerColor = triggerColors[entry.trigger] || '#888'; - // Issue #608 (AfonsoG6): the prior code injected `entry.id` raw - // into single-quoted JS string literals inside HTML onclick - // attributes. Filenames containing an apostrophe broke JS - // parsing — Approve and Delete buttons silently no-op'd. - // JSON.stringify wraps the value in valid JS literal syntax - // (handles ', \, " and unicode); escapeHtml then guards the HTML - // attribute boundary so the JS string round-trips intact. - const idJs = escapeHtml(JSON.stringify(entry.id)); + // Keep dynamic filenames/ids out of inline JS. Quarantine ids are derived + // from filenames, so quotes in the filename can break onclick attributes + // if they are interpolated as JS string literals. + const entryIdAttr = escapeHtml(String(entry.id || '')); const approveLabel = entry.has_full_context ? 'Approve' : 'Recover'; const approveTitle = entry.has_full_context ? 'Re-run post-processing with only the failing check skipped' : 'Legacy entry — move to Staging, finish via Import flow'; const approveCall = entry.has_full_context - ? `approveQuarantineEntry(${idJs})` - : `recoverQuarantineEntry(${idJs})`; + ? 'approveQuarantineEntryFromButton(this)' + : 'recoverQuarantineEntryFromButton(this)'; const meta = [entry.expected_artist, entry.original_filename].filter(Boolean).join(' — '); const triggerBadge = `${escapeHtml(triggerLabel)}`; @@ -3179,8 +3175,8 @@ function renderQuarantineEntry(entry) {
${triggerBadge}
${formatHistoryTime(entry.timestamp)}
- - + +
@@ -3191,6 +3187,22 @@ function renderQuarantineEntry(entry) {
`; } +function getQuarantineEntryIdFromButton(button) { + return button?.dataset?.entryId || ''; +} + +function approveQuarantineEntryFromButton(button) { + return approveQuarantineEntry(getQuarantineEntryIdFromButton(button)); +} + +function recoverQuarantineEntryFromButton(button) { + return recoverQuarantineEntry(getQuarantineEntryIdFromButton(button)); +} + +function deleteQuarantineEntryFromButton(button) { + return deleteQuarantineEntry(getQuarantineEntryIdFromButton(button)); +} + async function approveQuarantineEntry(entryId) { const ok = await showConfirmDialog({ title: 'Approve Quarantined File',