From 7b6bf964cb3ff2aa431c793213e1d5ebf62e1b94 Mon Sep 17 00:00:00 2001 From: Dicken Date: Tue, 26 May 2026 14:49:49 +0200 Subject: [PATCH] Dateien nach "backend/src/routes" hochladen --- backend/src/routes/calendar.js | 68 +++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/backend/src/routes/calendar.js b/backend/src/routes/calendar.js index 7084600..85a7652 100644 --- a/backend/src/routes/calendar.js +++ b/backend/src/routes/calendar.js @@ -4,26 +4,60 @@ const http = require('http'); const { authenticate } = require('../middleware/auth'); const router = express.Router(); -// Fetch iCal feed server-side (avoids CORS) -router.get('/fetch', authenticate, (req, res) => { +// Fetch mit Redirect-Unterstützung (bis 5 Hops) +function fetchWithRedirects(url, hops = 0) { + return new Promise((resolve, reject) => { + if (hops > 5) return reject(new Error('Zu viele Redirects')); + let parsed; + try { parsed = new URL(url); } catch { return reject(new Error('Ungültige URL')); } + + const lib = parsed.protocol === 'https:' ? https : http; + const req = lib.get(url, { + headers: { + 'User-Agent': 'Mozilla/5.0 DickenDock/1.0', + 'Accept': 'text/calendar, text/plain, */*', + } + }, res => { + // Redirect folgen + if ([301,302,303,307,308].includes(res.statusCode) && res.headers.location) { + const next = res.headers.location.startsWith('http') + ? res.headers.location + : new URL(res.headers.location, url).href; + res.destroy(); + return fetchWithRedirects(next, hops + 1).then(resolve).catch(reject); + } + if (res.statusCode !== 200) + return reject(new Error(`HTTP ${res.statusCode}`)); + + let data = ''; + res.setEncoding('utf8'); + res.on('data', d => { + data += d; + if (data.length > 3 * 1024 * 1024) { res.destroy(); reject(new Error('Feed zu groß (max 3MB)')); } + }); + res.on('end', () => resolve(data)); + }); + req.on('error', reject); + req.setTimeout(10000, () => { req.destroy(); reject(new Error('Timeout nach 10s')); }); + }); +} + +router.get('/fetch', authenticate, async (req, res) => { const { url } = req.query; if (!url) return res.status(400).json({ error: 'URL fehlt' }); - let parsed; - try { parsed = new URL(url); } - catch { return res.status(400).json({ error: 'Ungültige URL' }); } - if (!['http:','https:'].includes(parsed.protocol)) - return res.status(400).json({ error: 'Nur HTTP/HTTPS erlaubt' }); + try { + new URL(url); // validate + } catch { return res.status(400).json({ error: 'Ungültige URL' }); } - const lib = parsed.protocol === 'https:' ? https : http; - const req2 = lib.get(url, { headers:{ 'User-Agent':'DickenDock/1.0' } }, r2 => { - if (r2.statusCode !== 200) - return res.status(502).json({ error: `Feed antwortet mit ${r2.statusCode}` }); - let data = ''; - r2.on('data', d => { data += d; if (data.length > 2*1024*1024) { r2.destroy(); res.status(413).json({ error: 'Feed zu groß (max 2MB)' }); } }); - r2.on('end', () => res.type('text/calendar').send(data)); - }); - req2.on('error', e => res.status(502).json({ error: e.message })); - req2.setTimeout(8000, () => { req2.destroy(); res.status(504).json({ error: 'Timeout' }); }); + try { + const data = await fetchWithRedirects(url); + // Prüfen ob es wirklich iCal ist + if (!data.includes('BEGIN:VCALENDAR')) + return res.status(502).json({ error: 'Kein gültiger iCal-Feed (BEGIN:VCALENDAR fehlt)' }); + res.type('text/calendar; charset=utf-8').send(data); + } catch(e) { + res.status(502).json({ error: e.message }); + } }); module.exports = router;