import { NextRequest, NextResponse } from 'next/server'; import { readFile, stat } from 'fs/promises'; import path from 'path'; export async function GET( request: NextRequest, { params }: { params: Promise<{ filename: string }> } ) { try { const { filename } = await params; // Security: Prevent path traversal attacks // Only allow alphanumeric, hyphens, underscores, and dots const safeFilenamePattern = /^[a-zA-Z0-9_\-\.]+\.mp3$/; if (!safeFilenamePattern.test(filename)) { return new NextResponse('Invalid filename', { status: 400 }); } // Additional check: ensure no path separators if (filename.includes('/') || filename.includes('\\') || filename.includes('..')) { return new NextResponse('Invalid filename', { status: 400 }); } const filePath = path.join(process.cwd(), 'public/uploads', filename); // Security: Verify the resolved path is still within uploads directory const uploadsDir = path.join(process.cwd(), 'public/uploads'); const resolvedPath = path.resolve(filePath); if (!resolvedPath.startsWith(uploadsDir)) { return new NextResponse('Forbidden', { status: 403 }); } // Check if file exists try { await stat(filePath); } catch { return new NextResponse('File not found', { status: 404 }); } // Read file const fileBuffer = await readFile(filePath); // Return with proper headers return new NextResponse(fileBuffer, { headers: { 'Content-Type': 'audio/mpeg', 'Accept-Ranges': 'bytes', 'Cache-Control': 'public, max-age=3600, must-revalidate', }, }); } catch (error) { console.error('Error serving audio file:', error); return new NextResponse('Internal Server Error', { status: 500 }); } }