| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { NextRequest, NextResponse } from 'next/server'; |
| import { validateUrlForSSRF } from '@/lib/server/ssrf-guard'; |
| import { apiError } from '@/lib/server/api-response'; |
| import { createLogger } from '@/lib/logger'; |
|
|
| const log = createLogger('ProxyMedia'); |
|
|
| export const maxDuration = 60; |
|
|
| export async function POST(request: NextRequest) { |
| let url: string | undefined; |
| try { |
| ({ url } = await request.json()); |
|
|
| if (!url || typeof url !== 'string') { |
| return apiError('MISSING_REQUIRED_FIELD', 400, 'Missing or invalid url'); |
| } |
|
|
| |
| const ssrfError = await validateUrlForSSRF(url); |
| if (ssrfError) { |
| return apiError('INVALID_URL', 403, ssrfError); |
| } |
|
|
| |
| const response = await fetch(url, { redirect: 'manual' }); |
| if (response.status >= 300 && response.status < 400) { |
| return apiError('REDIRECT_NOT_ALLOWED', 403, 'Redirects are not allowed'); |
| } |
| if (!response.ok) { |
| return apiError('UPSTREAM_ERROR', 502, `Upstream returned ${response.status}`); |
| } |
|
|
| const blob = await response.blob(); |
| const contentType = response.headers.get('content-type') || 'application/octet-stream'; |
|
|
| return new NextResponse(blob, { |
| headers: { |
| 'Content-Type': contentType, |
| 'Content-Length': String(blob.size), |
| 'Cache-Control': 'private, max-age=3600', |
| }, |
| }); |
| } catch (error) { |
| log.error(`Proxy media failed [url="${url?.substring(0, 100) ?? 'unknown'}"]:`, error); |
| return apiError('INTERNAL_ERROR', 500, error instanceof Error ? error.message : String(error)); |
| } |
| } |
|
|