diff --git a/app/eslint.config.js b/app/eslint.config.js index 50ea303..35936c4 100644 --- a/app/eslint.config.js +++ b/app/eslint.config.js @@ -21,6 +21,7 @@ const SHARED = { openDynamicPopup: 'readonly', closeAllModals: 'readonly', isSafeUrl: 'readonly', + safeUrl: 'readonly', openIframe: 'readonly', enterFullscreen: 'readonly', exitFullscreen: 'readonly', diff --git a/app/static/js/iframe.js b/app/static/js/iframe.js index 17f96dd..efe9e87 100644 --- a/app/static/js/iframe.js +++ b/app/static/js/iframe.js @@ -2,19 +2,20 @@ let mainElement, originalContent, originalMainStyle, container, customScrollbar, scrollbarContainer; let currentIframeUrl = null; -function isAllowedIframeUrl(url) { - if (!isSafeUrl(url)) { - return false; +function allowedIframeUrl(url) { + const candidate = safeUrl(url); + if (candidate === null) { + return null; } const allowedOrigins = new Set([window.location.origin]); document.querySelectorAll('a.iframe-link[href]').forEach((link) => allowedOrigins.add(link.origin)); - return allowedOrigins.has(new URL(url, window.location.href).origin); + return allowedOrigins.has(new URL(candidate).origin) ? candidate : null; } // === Auto-open iframe if URL parameter is present === window.addEventListener('DOMContentLoaded', () => { - const paramUrl = new URLSearchParams(window.location.search).get('iframe'); - if (paramUrl && isAllowedIframeUrl(paramUrl)) { + const paramUrl = allowedIframeUrl(new URLSearchParams(window.location.search).get('iframe')); + if (paramUrl) { currentIframeUrl = paramUrl; enterFullscreen(); openIframe(paramUrl); @@ -43,7 +44,8 @@ function syncIframeHeight() { // Function to open a URL in an iframe (jQuery version mit 1500 ms Fade) function openIframe(url) { - if (!isSafeUrl(url)) { + const target = safeUrl(url); + if (target === null) { return; } @@ -73,7 +75,7 @@ function openIframe(url) { // Quelle setzen und mit 1500 ms einblenden $iframe - .attr('src', url) + .attr('src', target) .fadeIn(1500, function() { syncIframeHeight(); observeIframeNavigation(); @@ -81,8 +83,8 @@ function openIframe(url) { // URL-State pushen var newUrl = new URL(window.location); - newUrl.searchParams.set('iframe', url); - window.history.pushState({ iframe: url }, '', newUrl); + newUrl.searchParams.set('iframe', target); + window.history.pushState({ iframe: target }, '', newUrl); }); } @@ -148,8 +150,8 @@ document.addEventListener("DOMContentLoaded", function() { */ function openIframeInNewTab() { const params = new URLSearchParams(window.location.search); - const iframeUrl = params.get('iframe'); - if (iframeUrl && isAllowedIframeUrl(iframeUrl)) { + const iframeUrl = allowedIframeUrl(params.get('iframe')); + if (iframeUrl) { window.open(iframeUrl, '_blank'); } else { alert('No iframe is currently open.'); diff --git a/app/static/js/modal.js b/app/static/js/modal.js index 013d143..ea928f4 100644 --- a/app/static/js/modal.js +++ b/app/static/js/modal.js @@ -7,15 +7,19 @@ function t(source) { const SAFE_URL_SCHEMES = ['http:', 'https:', 'mailto:']; -function isSafeUrl(url) { +function safeUrl(url) { try { const parsed = new URL(String(url == null ? '' : url), window.location.href); - return SAFE_URL_SCHEMES.includes(parsed.protocol); + return SAFE_URL_SCHEMES.includes(parsed.protocol) ? parsed.href : null; } catch (error) { - return false; + return null; } } +function isSafeUrl(url) { + return safeUrl(url) !== null; +} + function iconAndName(item) { const nodes = []; if (item.icon && item.icon.class) {