Files
files/nextjs/app/%5Fshare/[filename]/route.js
2026-03-27 19:50:53 +01:00

81 lines
2.4 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import { Readable } from 'node:stream';
import { NextResponse } from 'next/server';
import { shareDir } from '@/src/lib/config.js';
import { get, logEvent, run, runCleanupIfNeeded } from '@/src/lib/db.js';
import { getRequestMeta } from '@/src/lib/security.js';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
function safeFilename(value) {
const fileName = String(value || '');
if (!fileName) {
return '';
}
if (fileName.includes('/') || fileName.includes('\\') || fileName.includes('..')) {
return '';
}
return fileName;
}
function contentDisposition(filename) {
const fallback = String(filename || 'download')
.replace(/[\r\n]/g, ' ')
.replace(/[\\"]/g, '_')
.replace(/[^ -~]/g, '_');
const encoded = encodeURIComponent(filename || 'download');
return `attachment; filename="${fallback}"; filename*=UTF-8''${encoded}`;
}
export async function GET(request, { params }) {
await runCleanupIfNeeded();
const resolvedParams = await params;
const fileName = safeFilename(resolvedParams.filename);
if (!fileName) {
return new NextResponse('Ungültiger Dateiname', { status: 400 });
}
const row = await get('SELECT id, original_name, stored_path FROM uploads WHERE stored_name = ?', [fileName]);
let filePath;
let downloadName;
if (row) {
filePath = row.stored_path;
downloadName = row.original_name || fileName;
const requestMeta = await getRequestMeta();
run('UPDATE uploads SET downloads = downloads + 1 WHERE id = ?', [row.id]).catch(() => undefined);
logEvent('download', null, { name: fileName, original: downloadName }, requestMeta).catch(() => undefined);
} else {
filePath = path.join(shareDir, fileName);
downloadName = fileName;
}
let fileStat;
try {
fileStat = await fs.promises.stat(filePath);
} catch {
return new NextResponse('Datei nicht gefunden', { status: 404 });
}
const fileStream = fs.createReadStream(filePath);
const webStream = Readable.toWeb(fileStream);
return new NextResponse(webStream, {
status: 200,
headers: {
'Content-Type': 'application/octet-stream',
'Content-Length': String(fileStat.size),
'Content-Disposition': contentDisposition(downloadName),
'Cache-Control': 'private, no-store',
'X-Content-Type-Options': 'nosniff',
},
});
}