From eaeebb64cd9a1590bc07c2358299718fb3c1611d Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sat, 16 May 2026 18:35:51 +0300 Subject: [PATCH] fix(csp): replace inline on* handlers with data-on* + JS wiring The strict `script-src 'self'` CSP blocks inline onclick/onchange/oninput/ onsubmit attribute evaluation, breaking every button and form in the UI. - Rename all 53 inline handler attributes in index.html to data-on* - Add wireInlineHandlers() in app.js that parses each data-on* expression on DOMContentLoaded and attaches a proper addEventListener calling the matching window-global function. Supports no-arg, string/number/bool/null literals, and the `event` token. CSP stays strict; no unsafe-inline or unsafe-hashes needed. --- media_server/static/index.html | 106 ++++++++++++++++----------------- media_server/static/js/app.js | 62 +++++++++++++++++++ 2 files changed, 115 insertions(+), 53 deletions(-) diff --git a/media_server/static/index.html b/media_server/static/index.html index f1b1732..3ebba28 100644 --- a/media_server/static/index.html +++ b/media_server/static/index.html @@ -26,15 +26,15 @@
- - -
@@ -48,7 +48,7 @@
- +

To get your token, run:

media-server --show-token @@ -91,23 +91,23 @@ -
-
- - - - -
@@ -136,29 +136,29 @@
- - - - - @@ -172,7 +172,7 @@ ยท Now Playing
-
- - - @@ -287,7 +287,7 @@
-
@@ -318,34 +318,34 @@
- - -
- -
@@ -398,7 +398,7 @@
@@ -434,7 +434,7 @@ -
+
+
@@ -464,7 +464,7 @@ -
+
+
@@ -496,7 +496,7 @@ -
+
+
@@ -528,7 +528,7 @@ -
+
+
@@ -551,7 +551,7 @@

Add Script

-
+
@@ -593,13 +593,13 @@
Parameters - +
@@ -610,12 +610,12 @@

Execute Script

-
+
@@ -626,7 +626,7 @@

Add Callback

-
+
@@ -664,7 +664,7 @@
@@ -675,7 +675,7 @@

Add Link

-
+
@@ -710,7 +710,7 @@
@@ -737,7 +737,7 @@
@@ -746,7 +746,7 @@

Add Media Folder

-
+
@@ -773,7 +773,7 @@
@@ -813,7 +813,7 @@ diff --git a/media_server/static/js/app.js b/media_server/static/js/app.js index 20d1d2a..ae3125e 100644 --- a/media_server/static/js/app.js +++ b/media_server/static/js/app.js @@ -159,10 +159,72 @@ HTMLDialogElement.prototype.showModal = function (...args) { return result; }; +// CSP-safe replacement for inline on* handlers. HTML uses data-onclick, +// data-onchange, data-oninput, data-onsubmit with simple call expressions +// like "fn()", "fn('arg')", "fn(event)". We parse those at startup and +// attach proper addEventListener calls so script-src 'self' stays strict. +const INLINE_HANDLER_EVENTS = { + 'data-onclick': 'click', + 'data-onchange': 'change', + 'data-oninput': 'input', + 'data-onsubmit': 'submit', +}; + +function parseInlineHandlerArg(token) { + const t = token.trim(); + if (t === '') return { kind: 'empty' }; + if (t === 'event') return { kind: 'event' }; + if (t === 'true') return { kind: 'literal', value: true }; + if (t === 'false') return { kind: 'literal', value: false }; + if (t === 'null') return { kind: 'literal', value: null }; + if (/^-?\d+(\.\d+)?$/.test(t)) return { kind: 'literal', value: Number(t) }; + if ((t.startsWith("'") && t.endsWith("'")) || (t.startsWith('"') && t.endsWith('"'))) { + return { kind: 'literal', value: t.slice(1, -1) }; + } + console.warn('inline-handler: unsupported arg token', token); + return { kind: 'literal', value: undefined }; +} + +function compileInlineHandler(expr) { + const m = expr.match(/^\s*([A-Za-z_$][\w$]*)\s*\((.*)\)\s*;?\s*$/s); + if (!m) { + console.warn('inline-handler: unparsable expression', expr); + return null; + } + const fnName = m[1]; + const argsRaw = m[2].trim(); + const argTokens = argsRaw === '' ? [] : argsRaw.split(',').map(s => s.trim()); + const parsedArgs = argTokens.map(parseInlineHandlerArg); + return function (event) { + const fn = window[fnName]; + if (typeof fn !== 'function') { + console.error('inline-handler: missing global function', fnName); + return; + } + const args = parsedArgs.map(a => a.kind === 'event' ? event : a.value); + return fn.apply(this, args); + }; +} + +function wireInlineHandlers(root) { + for (const [attr, eventName] of Object.entries(INLINE_HANDLER_EVENTS)) { + const nodes = root.querySelectorAll(`[${attr}]`); + for (const el of nodes) { + const expr = el.getAttribute(attr); + const handler = compileInlineHandler(expr); + if (handler) el.addEventListener(eventName, handler); + el.removeAttribute(attr); + } + } +} + window.addEventListener('DOMContentLoaded', async () => { // Cache DOM references cacheDom(); + // Wire CSP-safe inline-handler stand-ins from index.html + wireInlineHandlers(document); + // Initialize theme and accent color initTheme(); initAccentColor();