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 @@
-
-
+
-
+
-
+
@@ -287,7 +287,7 @@
-
+
@@ -301,7 +301,7 @@
@@ -318,34 +318,34 @@
@@ -746,7 +746,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();