Dateien nach "backend/src/routes" hochladen
This commit is contained in:
@@ -4,26 +4,60 @@ const http = require('http');
|
|||||||
const { authenticate } = require('../middleware/auth');
|
const { authenticate } = require('../middleware/auth');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Fetch iCal feed server-side (avoids CORS)
|
// Fetch mit Redirect-Unterstützung (bis 5 Hops)
|
||||||
router.get('/fetch', authenticate, (req, res) => {
|
function fetchWithRedirects(url, hops = 0) {
|
||||||
const { url } = req.query;
|
return new Promise((resolve, reject) => {
|
||||||
if (!url) return res.status(400).json({ error: 'URL fehlt' });
|
if (hops > 5) return reject(new Error('Zu viele Redirects'));
|
||||||
let parsed;
|
let parsed;
|
||||||
try { parsed = new URL(url); }
|
try { parsed = new URL(url); } catch { return reject(new Error('Ungültige 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' });
|
|
||||||
|
|
||||||
const lib = parsed.protocol === 'https:' ? https : http;
|
const lib = parsed.protocol === 'https:' ? https : http;
|
||||||
const req2 = lib.get(url, { headers:{ 'User-Agent':'DickenDock/1.0' } }, r2 => {
|
const req = lib.get(url, {
|
||||||
if (r2.statusCode !== 200)
|
headers: {
|
||||||
return res.status(502).json({ error: `Feed antwortet mit ${r2.statusCode}` });
|
'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 = '';
|
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)' }); } });
|
res.setEncoding('utf8');
|
||||||
r2.on('end', () => res.type('text/calendar').send(data));
|
res.on('data', d => {
|
||||||
|
data += d;
|
||||||
|
if (data.length > 3 * 1024 * 1024) { res.destroy(); reject(new Error('Feed zu groß (max 3MB)')); }
|
||||||
});
|
});
|
||||||
req2.on('error', e => res.status(502).json({ error: e.message }));
|
res.on('end', () => resolve(data));
|
||||||
req2.setTimeout(8000, () => { req2.destroy(); res.status(504).json({ error: 'Timeout' }); });
|
});
|
||||||
|
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' });
|
||||||
|
try {
|
||||||
|
new URL(url); // validate
|
||||||
|
} catch { return res.status(400).json({ error: 'Ungültige URL' }); }
|
||||||
|
|
||||||
|
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;
|
module.exports = router;
|
||||||
|
|||||||
Reference in New Issue
Block a user