feat(clickTtHttpPageRoutes): add meta-refresh URL rewriting for proxy handling

- Implemented a new function to rewrite meta-refresh URLs in HTML, ensuring redirects after login are routed through the proxy while maintaining session integrity.
- Updated the proxy GET and POST endpoints to include the new meta-refresh handling, enhancing the overall functionality of the proxy interactions.
- Improved error handling in the new function to ensure robustness in processing HTML content.
This commit is contained in:
Torsten Schulz (local)
2026-03-10 22:15:37 +01:00
parent bacc6b994d
commit 4f3a1829ca
3 changed files with 54 additions and 2 deletions

View File

@@ -85,6 +85,38 @@ function rewriteLinksInHtml(html, proxyBaseUrl, pageBaseUrl, sid) {
}
}
/**
* Schreibt meta-refresh-URLs um, damit Redirects nach Login über unseren Proxy laufen (Session bleibt erhalten).
*/
function rewriteMetaRefreshInHtml(html, proxyBaseUrl, pageBaseUrl, sid) {
if (!html || !proxyBaseUrl || !pageBaseUrl || !sid) return html;
try {
const base = new URL(pageBaseUrl);
return html.replace(
/<meta\s[^>]*(?: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);
}
);
} catch {
return html;
}
}
/**
* Schreibt Formular-Actions um, sodass Submissions über unseren Proxy laufen (Login etc. wird geloggt).
*/
@@ -198,6 +230,7 @@ router.get('/proxy', async (req, res, next) => {
const proxyBase = baseUrl.replace(/\/$/, '') + '/api/clicktt/proxy';
html = rewriteLinksInHtml(html, proxyBase, targetUrl, sid);
html = rewriteFormActionsInHtml(html, proxyBase, targetUrl, sid);
html = rewriteMetaRefreshInHtml(html, proxyBase, targetUrl, sid);
res.set({
'Content-Type': 'text/html; charset=utf-8',
@@ -258,8 +291,14 @@ router.post('/proxy', async (req, res, next) => {
}
(Array.isArray(setCookies) ? setCookies : [setCookies]).forEach(c => res.append('Set-Cookie', c));
}
const location = responseHeaders.get?.('location') ?? responseHeaders['location'];
let location = responseHeaders.get?.('location') ?? responseHeaders['location'];
if (location && (result.status >= 301 && result.status <= 308)) {
if (location.startsWith('/')) {
try {
const base = new URL(targetUrl);
location = base.origin + location;
} catch { /* ignore */ }
}
const baseUrl = process.env.BACKEND_BASE_URL || process.env.BASE_URL
|| `${req.protocol || 'http'}://${req.get('host') || 'localhost:' + (process.env.PORT || 3005)}`;
const proxyBase = baseUrl.replace(/\/$/, '') + '/api/clicktt/proxy';
@@ -288,6 +327,7 @@ router.post('/proxy', async (req, res, next) => {
}
responseBody = rewriteLinksInHtml(responseBody, proxyBase, targetUrl, sid);
responseBody = rewriteFormActionsInHtml(responseBody, proxyBase, targetUrl, sid);
responseBody = rewriteMetaRefreshInHtml(responseBody, proxyBase, targetUrl, sid);
}
res.set({

View File

@@ -106,15 +106,24 @@ async function fetchWithLogging(options) {
const baseDomain = extractBaseDomain(url);
const startTime = Date.now();
let origin;
try { origin = new URL(url).origin + '/'; } catch { origin = 'https://httv.click-tt.de/'; }
const headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
'Referer': origin,
'Origin': origin.replace(/\/$/, ''),
...customHeaders,
};
try {
const fetchOpts = { method, headers, timeout: 30000 };
const fetchOpts = {
method,
headers,
timeout: 30000,
redirect: method === 'POST' ? 'manual' : 'follow',
};
if (body && (method === 'POST' || method === 'PUT')) {
fetchOpts.body = body;
}

View File

@@ -97,6 +97,7 @@ export default {
const clubId = ref('');
const directUrl = ref('');
const iframeSrc = ref('');
const sessionId = ref('');
const canLoad = computed(() => {
if (pageType.value === 'url') {
@@ -124,11 +125,13 @@ export default {
params.set('clubId', clubId.value.trim());
}
}
if (sessionId.value) params.set('sid', sessionId.value);
return `${base}?${params.toString()}`;
}
function loadPage() {
if (!canLoad.value) return;
sessionId.value = crypto.randomUUID?.() || `s${Date.now()}-${Math.random().toString(36).slice(2)}`;
iframeSrc.value = buildProxyUrl();
}