diff --git a/backend/routes/clickTtHttpPageRoutes.js b/backend/routes/clickTtHttpPageRoutes.js index 551779f1..63a805de 100644 --- a/backend/routes/clickTtHttpPageRoutes.js +++ b/backend/routes/clickTtHttpPageRoutes.js @@ -87,28 +87,46 @@ function buildProxyUrl(proxyBase, targetUrl, sid) { return `${proxyBase}${sep}url=${encodeURIComponent(targetUrl)}${sid ? `&sid=${sid}` : ''}`; } +function applyOutsideScriptTags(html, transform) { + if (!html) return html; + const scripts = []; + const placeholderHtml = html.replace(/]*>[\s\S]*?<\/script>/gi, (match) => { + const token = `__CLICKTT_SCRIPT_${scripts.length}__`; + scripts.push(match); + return token; + }); + + let transformed = transform(placeholderHtml); + scripts.forEach((script, index) => { + transformed = transformed.replace(`__CLICKTT_SCRIPT_${index}__`, script); + }); + return transformed; +} + /** * Schreibt Links im HTML um, sodass Klicks im iframe über unseren Proxy laufen (Folge-Logs). */ function rewriteLinksInHtml(html, proxyBaseUrl, pageBaseUrl, sid) { if (!html || !proxyBaseUrl || !pageBaseUrl) return html; - try { - const base = new URL(pageBaseUrl); - return html.replace( - /]*?)href\s*=\s*["']([^"']+)["']([^>]*)>/gi, - (match, before, href, after) => { - let absoluteUrl = href; - if (href.startsWith('/') || !href.startsWith('http')) { - absoluteUrl = new URL(href, base.origin + base.pathname).href; + return applyOutsideScriptTags(html, (safeHtml) => { + try { + const base = new URL(pageBaseUrl); + return safeHtml.replace( + /]*?)href\s*=\s*["']([^"']+)["']([^>]*)>/gi, + (match, before, href, after) => { + let absoluteUrl = href; + if (href.startsWith('/') || !href.startsWith('http')) { + absoluteUrl = new URL(href, base.origin + base.pathname).href; + } + if (!shouldProxyUrl(absoluteUrl)) return match; + const proxyUrl = buildProxyUrl(proxyBaseUrl, absoluteUrl, sid); + return ``; } - if (!shouldProxyUrl(absoluteUrl)) return match; - const proxyUrl = buildProxyUrl(proxyBaseUrl, absoluteUrl, sid); - return ``; - } - ); - } catch { - return html; - } + ); + } catch { + return safeHtml; + } + }); } /** @@ -116,31 +134,33 @@ function rewriteLinksInHtml(html, proxyBaseUrl, pageBaseUrl, sid) { */ function rewriteMetaRefreshInHtml(html, proxyBaseUrl, pageBaseUrl, sid) { if (!html || !proxyBaseUrl || !pageBaseUrl || !sid) return html; - try { - const base = new URL(pageBaseUrl); - return html.replace( - /]*(?:http-equiv\s*=\s*["']refresh["'][^>]*content\s*=\s*["']([^"']+)["']|content\s*=\s*["']([^"']+)["'][^>]*http-equiv\s*=\s*["']refresh["'])[^>]*>/gi, - (match, content1, content2) => { - const content = content1 || content2; - if (!content) return match; - const urlMatch = content.match(/^\s*\d+\s*;\s*url\s*=\s*(.+)$/i); - if (!urlMatch) return match; - let targetUrl = urlMatch[1].trim(); - if ((targetUrl.startsWith("'") && targetUrl.endsWith("'")) || (targetUrl.startsWith('"') && targetUrl.endsWith('"'))) { - targetUrl = targetUrl.slice(1, -1); + return applyOutsideScriptTags(html, (safeHtml) => { + try { + const base = new URL(pageBaseUrl); + return safeHtml.replace( + /]*(?:http-equiv\s*=\s*["']refresh["'][^>]*content\s*=\s*["']([^"']+)["']|content\s*=\s*["']([^"']+)["'][^>]*http-equiv\s*=\s*["']refresh["'])[^>]*>/gi, + (match, content1, content2) => { + const content = content1 || content2; + if (!content) return match; + const urlMatch = content.match(/^\s*\d+\s*;\s*url\s*=\s*(.+)$/i); + if (!urlMatch) return match; + let targetUrl = urlMatch[1].trim(); + if ((targetUrl.startsWith("'") && targetUrl.endsWith("'")) || (targetUrl.startsWith('"') && targetUrl.endsWith('"'))) { + targetUrl = targetUrl.slice(1, -1); + } + if (targetUrl.startsWith('/') || !targetUrl.startsWith('http')) { + targetUrl = new URL(targetUrl, base.origin + base.pathname).href; + } + if (!shouldProxyUrl(targetUrl)) return match; + const proxyUrl = buildProxyUrl(proxyBaseUrl, targetUrl, sid); + const newContent = content.replace(/url\s*=\s*.+$/i, `url=${proxyUrl}`); + return match.replace(content, newContent); } - if (targetUrl.startsWith('/') || !targetUrl.startsWith('http')) { - targetUrl = new URL(targetUrl, base.origin + base.pathname).href; - } - if (!shouldProxyUrl(targetUrl)) return match; - const proxyUrl = buildProxyUrl(proxyBaseUrl, targetUrl, sid); - const newContent = content.replace(/url\s*=\s*.+$/i, `url=${proxyUrl}`); - return match.replace(content, newContent); - } - ); - } catch { - return html; - } + ); + } catch { + return safeHtml; + } + }); } /** @@ -148,25 +168,27 @@ function rewriteMetaRefreshInHtml(html, proxyBaseUrl, pageBaseUrl, sid) { */ function rewriteFormActionsInHtml(html, proxyBaseUrl, pageBaseUrl, sid) { if (!html || !proxyBaseUrl || !pageBaseUrl) return html; - try { - const base = new URL(pageBaseUrl); - return html.replace( - /]*?)action\s*=\s*["']([^"']*)["']([^>]*)>/gi, - (match, before, action, after) => { - const actionTrim = action.trim(); - if (!actionTrim) return match; // action="" = same URL - let absoluteUrl = actionTrim; - if (actionTrim.startsWith('/') || !actionTrim.startsWith('http')) { - absoluteUrl = new URL(actionTrim, base.origin + base.pathname).href; + return applyOutsideScriptTags(html, (safeHtml) => { + try { + const base = new URL(pageBaseUrl); + return safeHtml.replace( + /]*?)action\s*=\s*["']([^"']*)["']([^>]*)>/gi, + (match, before, action, after) => { + const actionTrim = action.trim(); + if (!actionTrim) return match; // action="" = same URL + let absoluteUrl = actionTrim; + if (actionTrim.startsWith('/') || !actionTrim.startsWith('http')) { + absoluteUrl = new URL(actionTrim, base.origin + base.pathname).href; + } + if (!shouldProxyUrl(absoluteUrl)) return match; + const proxyUrl = buildProxyUrl(proxyBaseUrl, absoluteUrl, sid); + return `
`; } - if (!shouldProxyUrl(absoluteUrl)) return match; - const proxyUrl = buildProxyUrl(proxyBaseUrl, absoluteUrl, sid); - return ``; - } - ); - } catch { - return html; - } + ); + } catch { + return safeHtml; + } + }); } function injectProxyNavigationScript(html, proxyBaseUrl, pageBaseUrl, sid) { @@ -216,6 +238,25 @@ function injectProxyNavigationScript(html, proxyBaseUrl, pageBaseUrl, sid) { 'if(!targetUrl||!shouldProxyUrl(targetUrl))return;', "form.setAttribute('action',buildProxyUrl(targetUrl));", '},true);', + 'if(window.fetch){', + 'var nativeFetch=window.fetch.bind(window);', + 'window.fetch=function patchedFetch(input, init){', + 'var rawUrl=typeof input==="string"?input:(input&&input.url?input.url:null);', + 'var targetUrl=normalizeUrl(rawUrl,PAGE_BASE_URL);', + 'if(targetUrl&&shouldProxyUrl(targetUrl)){', + 'if(typeof input==="string"){input=buildProxyUrl(targetUrl);}else if(input&&input.url){input=buildProxyUrl(targetUrl);}', + '}', + 'return nativeFetch(input, init);', + '};', + '}', + 'if(window.XMLHttpRequest&&window.XMLHttpRequest.prototype&&window.XMLHttpRequest.prototype.open){', + 'var nativeOpen=window.XMLHttpRequest.prototype.open;', + 'window.XMLHttpRequest.prototype.open=function patchedOpen(method, url){', + 'var targetUrl=normalizeUrl(url,PAGE_BASE_URL);', + 'if(targetUrl&&shouldProxyUrl(targetUrl)){arguments[1]=buildProxyUrl(targetUrl);}', + 'return nativeOpen.apply(this, arguments);', + '};', + '}', 'if(window.HTMLFormElement&&window.HTMLFormElement.prototype&&window.HTMLFormElement.prototype.submit){', 'var nativeSubmit=window.HTMLFormElement.prototype.submit;', 'window.HTMLFormElement.prototype.submit=function patchedSubmit(){',