diff --git a/Plugin/unraid/pages/scheduler.php b/Plugin/unraid/pages/scheduler.php
index 2970178..6c26f37 100644
--- a/Plugin/unraid/pages/scheduler.php
+++ b/Plugin/unraid/pages/scheduler.php
@@ -1159,6 +1159,15 @@ function vvEscHtml(s) {
return s.replace(/&/g,'&').replace(//g,'>');
}
+// For text going INSIDE a double-quoted attribute, which vvEscHtml does not cover: it leaves "
+// alone, and a quote there ends the attribute early and silently destroys the handler after it.
+// That is not a theoretical hazard — it shipped, and an onclick built from JSON.stringify() output
+// was truncated to "vvErrOpenAtLine(" and did nothing at all when clicked.
+function vvEscAttr(s) {
+ return String(s).replace(/&/g,'&').replace(/"/g,'"')
+ .replace(//g,'>');
+}
+
function vvPost(url, data) {
const params = new URLSearchParams({csrf_token, ...data});
return fetch(url, {
@@ -2787,16 +2796,20 @@ function vvUpdateErrors(errors) {
const now = Math.floor(Date.now() / 1000);
let html = '
';
for (const e of unacked) {
- const jobId = "'" + (e.script + '.sh').replace(/\\/g,"\\\\").replace(/'/g,"\\'") + "'";
- // JSON.stringify rather than the hand-rolled quote escaping above: this one is embedded in an
- // attribute alongside the error text, which is arbitrary log output and not to be trusted to
- // contain no quotes.
- const scriptJs = vvEscHtml(JSON.stringify(e.script));
- const lineJs = vvEscHtml(JSON.stringify(e.line));
+ // JSON.stringify for correct JS literals, then attribute-escaped: the error text is arbitrary
+ // log output and absolutely will contain quotes eventually.
+ const scriptJs = vvEscAttr(JSON.stringify(e.script));
+ const lineJs = vvEscAttr(JSON.stringify(e.line));
const label = e.script.split('/').pop().replace(/_/g, ' ');
html += '
'
+ '
'
- + '' + vvEscHtml(label) + ''
+ // The name goes to the same place as the text below it. Two clickable halves of one row
+ // behaving differently — one acking, one not — is how an error looks acknowledged when
+ // it is not, which is worse than either behaviour on its own.
+ + ''
+ + vvEscHtml(label) + ''
+ '' + vvFmtAge(now - e.ts) + ''
+ ''
+ '