|
- import { NextResponse } from 'next/server';
- import { writeFile, mkdir, unlink } from 'fs/promises';
- import path from 'path';
- import { fromBuffer as fileTypeFromBuffer } from 'file-type';
- import { UPLOAD_LIMITS } from '@/lib/config';
-
- export const dynamic = 'force-dynamic';
- export const maxDuration = 60;
-
- const FONTS_DIR = path.join(process.cwd(), 'data', 'fonts');
- const ALLOWED_EXT = new Set(['.woff2', '.woff', '.ttf', '.otf']);
-
- // Magic-bytes: ttf e otf condividono il container SFNT, quindi accettiamo entrambi
- // per entrambe le estensioni. woff/woff2 hanno header dedicati.
- const ALLOWED_DETECTED: Record<string, string[]> = {
- '.woff2': ['woff2'],
- '.woff': ['woff'],
- '.ttf': ['ttf', 'otf'],
- '.otf': ['otf', 'ttf'],
- };
-
- // Sanitizza il nome del file: solo basename, solo caratteri sicuri, niente path traversal.
- // Per i font conviene preservare il nome originale (la logica di lookup italic/bold in
- // app/layout.tsx si basa sui pattern `Name-Italic.woff2`, `Name-Bold.woff2`, ecc.).
- function sanitizeFontName(rawName: string): string {
- const base = path.basename(rawName);
- // Mantieni lettere, cifre, underscore, hyphen, punto. Tutto il resto → underscore.
- const sanitized = base.replace(/[^a-zA-Z0-9_\-.]/g, '_');
- // Niente percorsi nascosti / nomi vuoti
- if (sanitized.startsWith('.') || sanitized.length === 0) return '';
- return sanitized;
- }
-
- // POST — upload font
- export async function POST(request: Request) {
- try {
- const formData = await request.formData();
- const file = formData.get('file') as File | null;
- if (!file) {
- return NextResponse.json({ error: 'No file received.' }, { status: 400 });
- }
-
- const safeName = sanitizeFontName(file.name);
- const ext = path.extname(safeName).toLowerCase();
- if (!ALLOWED_EXT.has(ext)) {
- return NextResponse.json(
- { error: `Unsupported font extension. Allowed: ${[...ALLOWED_EXT].join(', ')}` },
- { status: 400 },
- );
- }
- if (!safeName || safeName === ext) {
- return NextResponse.json({ error: 'Invalid font filename.' }, { status: 400 });
- }
- if (file.size > UPLOAD_LIMITS.font) {
- const mb = (UPLOAD_LIMITS.font / (1024 * 1024)).toFixed(0);
- return NextResponse.json(
- { error: `Font too large (max ${mb} MB).` },
- { status: 413 },
- );
- }
-
- const buffer = Buffer.from(await file.arrayBuffer());
-
- // Magic-bytes: rifiuta se il contenuto non è davvero un font del tipo dichiarato.
- const detected = await fileTypeFromBuffer(buffer);
- if (!detected) {
- return NextResponse.json(
- { error: 'Font content not recognized (unknown format).' },
- { status: 400 },
- );
- }
- const allowed = ALLOWED_DETECTED[ext] ?? [];
- if (!allowed.includes(detected.ext)) {
- return NextResponse.json(
- { error: `Font content does not match extension (${ext} declared, detected ${detected.ext}).` },
- { status: 400 },
- );
- }
-
- await mkdir(FONTS_DIR, { recursive: true });
- await writeFile(path.join(FONTS_DIR, safeName), buffer);
-
- return NextResponse.json({ ok: true, name: safeName }, { status: 201 });
- } catch (error) {
- console.error('Font upload error:', error);
- return NextResponse.json({ error: 'Failed to upload font.' }, { status: 500 });
- }
- }
-
- // DELETE — rimuove un font (query ?name=<filename>)
- export async function DELETE(request: Request) {
- try {
- const { searchParams } = new URL(request.url);
- const rawName = searchParams.get('name');
- if (!rawName) {
- return NextResponse.json({ error: 'Missing name parameter.' }, { status: 400 });
- }
- const safeName = sanitizeFontName(rawName);
- const ext = path.extname(safeName).toLowerCase();
- if (!ALLOWED_EXT.has(ext) || !safeName) {
- return NextResponse.json({ error: 'Invalid font name.' }, { status: 400 });
- }
- try {
- await unlink(path.join(FONTS_DIR, safeName));
- } catch (err) {
- const e = err as NodeJS.ErrnoException;
- if (e.code === 'ENOENT') {
- return NextResponse.json({ error: 'Font not found.' }, { status: 404 });
- }
- throw err;
- }
- return NextResponse.json({ ok: true });
- } catch (error) {
- console.error('Font delete error:', error);
- return NextResponse.json({ error: 'Failed to delete font.' }, { status: 500 });
- }
- }
|