From d5944c24438823a6c0e3fc95bc5f014468154232 Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Thu, 6 Aug 2026 22:13:10 -0400 Subject: [PATCH] Sweep every onclick built from data: an unescaped quote silently killed nine handlers --- Plugin/unraid/pages/rsync.php | 20 ++++++++++++++++---- Plugin/unraid/pages/scheduler.php | 18 +++++++++--------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/Plugin/unraid/pages/rsync.php b/Plugin/unraid/pages/rsync.php index a4d88e2..86a3a9d 100644 --- a/Plugin/unraid/pages/rsync.php +++ b/Plugin/unraid/pages/rsync.php @@ -591,6 +591,18 @@ function _dur(s) { return sec + 's'; } +// Escapes text going INSIDE a double-quoted HTML attribute. A bare " there ends the attribute +// early and silently destroys everything after it — an onclick built by string concatenation +// becomes a syntax error and the element simply stops responding, with nothing logged. +// +// Duplicated from scheduler.php rather than shared: js/varaverk.js is loaded after the page +// partials, so a page cannot rely on it during its own setup. Four lines in two places beats a +// load-order bug that only shows up on a slow load. +function vvRyEscAttr(s) { + return String(s).replace(/&/g,'&').replace(/"/g,'"') + .replace(//g,'>'); +} + // ── Status + active card ────────────────────────────────────────────────────── function _statusCard(data) { const en = data.enabled; @@ -1207,14 +1219,14 @@ function vvRyEBrowse(key) { let h = ''; if (d.parent) { const pn = d.parent === '/' ? '/' : (d.parent.replace(/^.*\//,'') || d.parent) + '/'; - h += `
↑ ${pn}
`; + h += `
↑ ${pn}
`; } if (!d.dirs.length) { h += '
— empty —
'; } else { for (const dir of d.dirs) { const name = dir.replace(/^.*\//,'') || dir; - h += `
▶ ${name}
`; + h += `
▶ ${name}
`; } } dirsEl.innerHTML = h; @@ -1653,7 +1665,7 @@ function _vvMsRenderDirs(dirsElId, dirs, parent, navFn) { if (parent !== null) { const pname = parent === '/' ? '/' : (parent.replace(/^.*\//, '') || parent) + '/'; - html += `
↑ ${pname}
`; + html += `
↑ ${pname}
`; } if (!dirs.length) { @@ -1661,7 +1673,7 @@ function _vvMsRenderDirs(dirsElId, dirs, parent, navFn) { } else { for (const d of dirs) { const name = d.replace(/^.*\//, '') || d; - html += `
▶ ${name}
`; + html += `
▶ ${name}
`; } } diff --git a/Plugin/unraid/pages/scheduler.php b/Plugin/unraid/pages/scheduler.php index 6c26f37..ab9bec2 100644 --- a/Plugin/unraid/pages/scheduler.php +++ b/Plugin/unraid/pages/scheduler.php @@ -1985,21 +1985,21 @@ function _vvImpRender(d) { if (d.parent !== null) { const pname = d.parent === '/' ? '/' : (d.parent.replace(/^.*\//, '') || d.parent) + '/'; - html += `
↑ ${pname}
`; } for (const dir of d.dirs) { const name = dir.replace(/^.*\//, '') || dir; - html += `
▶ ${_vvImpEsc(name)}
`; } for (const file of d.files) { const name = file.replace(/^.*\//, '') || file; const sel = file === _vvImpSelected; - html += `
📄 ${_vvImpEsc(name)}
`; } @@ -2677,8 +2677,8 @@ function vvUpdateLocks(locks) { html += '
' + '' + vvEscHtml(lk.name) + '' + '' + vvFmtAge(lk.age) + '' - + '' + + '' + '
'; } body.innerHTML = html + ''; @@ -2811,7 +2811,7 @@ function vvUpdateErrors(errors) { + ' onclick="vvErrOpenAtLine(' + scriptJs + ',' + e.ts + ',' + lineJs + ',this)">' + vvEscHtml(label) + '' + '' + vvFmtAge(now - e.ts) + '' - + '' + + '' + '' + '
' + + ' onclick="vvAiAskRun(' + vvEscAttr(JSON.stringify(r.id)) + ',' + bad + ',event)">' + (bad ? 'why?' : 'recap') + '' : ''; - html += '
' + html += '
' + '' + '' + vvEscHtml(r.label) + '' + '' + vvTimeAgo(now - r.start) + ''