71 lines
2.1 KiB
TypeScript
71 lines
2.1 KiB
TypeScript
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<string, string> = {
|
|
'.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 });
|
|
}
|
|
}
|