An unescaped quote ended the onclick attribute early, so clicking an error line did nothing

This commit is contained in:
Gmer4Lfe
2026-08-06 22:08:10 -04:00
parent 62a838ebdd
commit d135dff5cd
+20 -7
View File
@@ -1159,6 +1159,15 @@ function vvEscHtml(s) {
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
// 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,'&amp;').replace(/"/g,'&quot;')
.replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
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 = '<div class="vv-errors-list">';
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 += '<div class="vv-err-row">'
+ '<div class="vv-err-top">'
+ '<span class="vv-err-script" onclick="vvOpenRight(' + jobId + ')" style="cursor:pointer" title="' + vvEscHtml(e.script) + '">' + vvEscHtml(label) + '</span>'
// 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.
+ '<span class="vv-err-script" style="cursor:pointer"'
+ ' title="' + vvEscAttr(e.script) + ' — opens the log at this line and acknowledges it"'
+ ' onclick="vvErrOpenAtLine(' + scriptJs + ',' + e.ts + ',' + lineJs + ',this)">'
+ vvEscHtml(label) + '</span>'
+ '<span class="vv-err-age">' + vvFmtAge(now - e.ts) + '</span>'
+ '<button class="vv-btn-sm vv-ack-btn" onclick="vvAckError(\'' + e.script.replace(/\\/g,"\\\\").replace(/'/g,"\\'") + "'," + e.ts + ',this)">Ack</button>'
+ '</div>'