diff --git a/source/compose.manager/compose.manager.dashboard.page b/source/compose.manager/compose.manager.dashboard.page index f847c9d..f6cf484 100644 --- a/source/compose.manager/compose.manager.dashboard.page +++ b/source/compose.manager/compose.manager.dashboard.page @@ -80,8 +80,13 @@ EOT; // Debug setting from config $debugEnabled = isset($cfg['DEBUG_TO_LOG']) && $cfg['DEBUG_TO_LOG'] === 'true' ? 'true' : 'false'; $hideComposeContainersJs = $hideDockerComposeContainers ? 'true' : 'false'; +// Icon helpers live in their own file because composeManagerMain.js is not loaded on the Dashboard +$iconsJsSrc = '/plugins/compose.manager/javascript/composeIcons.js'; +$iconsJsVer = @filemtime('/usr/local/emhttp' . $iconsJsSrc); +if ($iconsJsVer) $iconsJsSrc .= '?v=' . $iconsJsVer; // CSS and JavaScript $configScript = << + diff --git a/source/compose.manager/include/Util.php b/source/compose.manager/include/Util.php index ff91366..2aacac8 100644 --- a/source/compose.manager/include/Util.php +++ b/source/compose.manager/include/Util.php @@ -157,7 +157,10 @@ function compose_icon_ext_to_mime(string $ext): string } if (!function_exists('compose_icon_browser_url')) { - /** Return the URL the browser should use: proxy for http(s), passthrough otherwise. */ + /** + * Return the URL the browser should use: proxy for http(s), passthrough otherwise. + * Data URIs are never proxied — the base64 payload would blow past URL length limits. + */ function compose_icon_browser_url(string $src): string { $src = trim($src); @@ -172,10 +175,9 @@ function compose_icon_browser_url(string $src): string } $isRemote = strncasecmp($src, 'http://', 7) === 0 || strncasecmp($src, 'https://', 8) === 0; - $isData = strncasecmp($src, 'data:image/', 11) === 0; $isCacheableLocal = str_starts_with($src, '/mnt/') || str_starts_with($src, '/boot/config/plugins/compose.manager/'); - if ($isRemote || $isData || $isCacheableLocal) { + if ($isRemote || $isCacheableLocal) { return '/plugins/compose.manager/IconCache.php?src=' . urlencode($src); } diff --git a/source/compose.manager/javascript/composeIcons.js b/source/compose.manager/javascript/composeIcons.js new file mode 100644 index 0000000..52ac0fa --- /dev/null +++ b/source/compose.manager/javascript/composeIcons.js @@ -0,0 +1,61 @@ +/** + * Shared icon helpers. + * Loaded by both the Compose page and the Dashboard tile — the tile has no + * access to composeManagerMain.js, so these must live in their own file. + */ + +function composeIconFallback(img) { + if (!img || img.dataset.composeFallbackApplied === 'true') { + return; + } + img.dataset.composeFallbackApplied = 'true'; + img.onerror = null; + img.src = '/plugins/compose.manager/images/question.png'; +} + +// Validate an icon source: http(s) URL, data URI, or local server path +function isValidIconSrc(src) { + if (!src) return false; + var s = src.trim(); + return s.indexOf('http://') === 0 || s.indexOf('https://') === 0 || + s.indexOf('data:image/') === 0 || s.indexOf('/') === 0; +} + +function isCacheEligibleLocalIconPath(src) { + if (!src) return false; + var s = src.trim(); + return s.indexOf('/mnt/') === 0 || s.indexOf('/boot/config/plugins/compose.manager/') === 0; +} + +/** + * Route cache-eligible icons through the local cache proxy; passthrough otherwise. + * Data URIs are never proxied — the base64 payload would blow past URL length limits. + */ +function composeIconSrc(src, containerName) { + if (!src || !isValidIconSrc(src)) { + return '/plugins/compose.manager/images/question.png'; + } + var s = src.trim(); + if (s.indexOf('/plugins/compose.manager/IconCache.php?') === 0) { + return s; + } + + var cacheable = s.indexOf('http://') === 0 || s.indexOf('https://') === 0 || + isCacheEligibleLocalIconPath(s); + if (cacheable) { + var proxied = '/plugins/compose.manager/IconCache.php?src=' + encodeURIComponent(s); + if (containerName && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(containerName)) { + proxied += '&ct=' + encodeURIComponent(containerName); + } + return proxied; + } + + return s; +} + +if (typeof window !== 'undefined') { + window.composeIconFallback = composeIconFallback; + window.isValidIconSrc = isValidIconSrc; + window.isCacheEligibleLocalIconPath = isCacheEligibleLocalIconPath; + window.composeIconSrc = composeIconSrc; +} diff --git a/source/compose.manager/javascript/composeManagerMain.js b/source/compose.manager/javascript/composeManagerMain.js index 1fa5357..5a21464 100644 --- a/source/compose.manager/javascript/composeManagerMain.js +++ b/source/compose.manager/javascript/composeManagerMain.js @@ -2452,51 +2452,8 @@ function isValidWebUIUrl(url) { } } -function composeIconFallback(img) { - if (!img || img.dataset.composeFallbackApplied === 'true') { - return; - } - img.dataset.composeFallbackApplied = 'true'; - img.onerror = null; - img.src = '/plugins/compose.manager/images/question.png'; -} - -// Validate an icon source: http(s) URL, data URI, or local server path -function isValidIconSrc(src) { - if (!src) return false; - var s = src.trim(); - return s.indexOf('http://') === 0 || s.indexOf('https://') === 0 || - s.indexOf('data:image/') === 0 || s.indexOf('/') === 0; -} - -function isCacheEligibleLocalIconPath(src) { - if (!src) return false; - var s = src.trim(); - return s.indexOf('/mnt/') === 0 || s.indexOf('/boot/config/plugins/compose.manager/') === 0; -} - -/** Route cache-eligible icons through the local cache proxy; passthrough otherwise. */ -function composeIconSrc(src, containerName) { - if (!src || !isValidIconSrc(src)) { - return '/plugins/compose.manager/images/question.png'; - } - var s = src.trim(); - if (s.indexOf('/plugins/compose.manager/IconCache.php?') === 0) { - return s; - } - - var cacheable = s.indexOf('http://') === 0 || s.indexOf('https://') === 0 || - s.indexOf('data:image/') === 0 || isCacheEligibleLocalIconPath(s); - if (cacheable) { - var proxied = '/plugins/compose.manager/IconCache.php?src=' + encodeURIComponent(s); - if (containerName && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(containerName)) { - proxied += '&ct=' + encodeURIComponent(containerName); - } - return proxied; - } - - return s; -} +// composeIconFallback/isValidIconSrc/isCacheEligibleLocalIconPath/composeIconSrc +// live in composeIcons.js (shared with the dashboard tile). // Sanitize user-entered icon values before assigning to image src in live preview. function sanitizeIconPreviewSrc(raw) {