import { NextRequest, NextResponse } from 'next/server'; import { readFile } from 'fs/promises'; import { existsSync } from 'fs'; import path from 'path'; const UPLOAD_DIR = process.env.UPLOAD_DIR || '/app/uploads'; const MIME_TYPES: Record = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml', '.avif': 'image/avif', '.pdf': 'application/pdf', '.txt': 'text/plain', '.md': 'text/markdown', '.csv': 'text/csv', '.json': 'application/json', '.xml': 'application/xml', '.zip': 'application/zip', '.gz': 'application/gzip', '.js': 'text/javascript', '.ts': 'text/typescript', '.html': 'text/html', '.css': 'text/css', '.py': 'text/x-python', }; export async function GET( _request: NextRequest, { params }: { params: { filename: string } } ) { try { const filename = params.filename; // Validate filename (no path traversal) if (filename.includes('/') || filename.includes('\\') || filename.includes('..')) { return NextResponse.json({ error: 'Invalid filename' }, { status: 400 }); } const filePath = path.join(UPLOAD_DIR, filename); // Validate resolved path stays within UPLOAD_DIR const resolvedPath = path.resolve(filePath); if (!resolvedPath.startsWith(path.resolve(UPLOAD_DIR))) { return NextResponse.json({ error: 'Invalid filename' }, { status: 400 }); } if (!existsSync(filePath)) { return NextResponse.json({ error: 'File not found' }, { status: 404 }); } const data = await readFile(filePath); const ext = path.extname(filename).toLowerCase(); const contentType = MIME_TYPES[ext] || 'application/octet-stream'; return new NextResponse(data, { headers: { 'Content-Type': contentType, 'Cache-Control': 'public, max-age=31536000, immutable', 'Content-Length': data.length.toString(), }, }); } catch (error) { console.error('Serve file error:', error); return NextResponse.json({ error: 'Failed to serve file' }, { status: 500 }); } }