|
- 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 font file was received. Choose a font file before clicking Upload.' },
- { status: 400 },
- );
- }
-
- const safeName = sanitizeFontName(file.name);
- const ext = path.extname(safeName).toLowerCase();
- if (!ALLOWED_EXT.has(ext)) {
- return NextResponse.json(
- { error: `Font extension "${ext || '(none)'}" is not supported. Allowed formats: ${[...ALLOWED_EXT].join(', ')}.` },
- { status: 400 },
- );
- }
- if (!safeName || safeName === ext) {
- return NextResponse.json(
- { error: 'Invalid font filename. The file name must contain at least one letter, digit, hyphen or underscore before the extension.' },
- { status: 400 },
- );
- }
- if (file.size > UPLOAD_LIMITS.font) {
- const limitMB = (UPLOAD_LIMITS.font / (1024 * 1024)).toFixed(0);
- const actualMB = (file.size / (1024 * 1024)).toFixed(1);
- return NextResponse.json(
- { error: `Font is too large: ${actualMB} MB (limit: ${limitMB} MB). Web fonts are typically under 500 KB — convert the file to WOFF2 (e.g. with a font-tools subsetter) before uploading.` },
- { 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: 'Could not identify the font content. The file may be corrupted or not actually a font. Try re-exporting from your font tool.' },
- { status: 400 },
- );
- }
- const allowed = ALLOWED_DETECTED[ext] ?? [];
- if (!allowed.includes(detected.ext)) {
- return NextResponse.json(
- { error: `Font content does not match its extension: the file looks like ${detected.ext.toUpperCase()} but the extension is "${ext}". Rename the file or re-export it in the declared format.` },
- { 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: 'Unexpected error while saving the font. The upload was aborted. Check the server logs for details and try again.' },
- { 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. Specify which font file to delete.' },
- { 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 "${rawName}". Expected a basename ending in ${[...ALLOWED_EXT].join(' / ')}.` },
- { 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 "${safeName}" was not found on the server. It may have already been deleted — refresh the page.` },
- { status: 404 },
- );
- }
- throw err;
- }
- return NextResponse.json({ ok: true });
- } catch (error) {
- console.error('Font delete error:', error);
- return NextResponse.json(
- { error: 'Unexpected error while deleting the font. Check the server logs and try again.' },
- { status: 500 },
- );
- }
- }
|