Download di file (/file)

4 min di lettura

Download di file

L’endpoint /file serve file binari generati da una funzione PostgreSQL — senza base64 dentro JSON-RPC. Un singolo file restituito viene inviato così com’è; più file vengono impacchettati al volo in un archivio ZIP.

POST /{prefisso}/{database}/file

Autenticazione, cambio di ruolo (SET LOCAL ROLE), rate limiting e limite della dimensione della richiesta sono identici a /jsonrpc. Il chiamante deve avere EXECUTE sulla funzione.

Richiesta

{
  "method": "api.export_documents",
  "params": { "count": 3 },
  "options": { "filename": "export.zip", "force_zip": false, "compression_level": 6 },
  "idempotencyKey": "optional"
}
CampoDescrizione
methodObbligatorio. schema.function, chiamata come fn(params::jsonb).
paramsPassato alla funzione come jsonb (predefinito {}).
options.filenameNome del file/ZIP scaricato. Predefinito: il nome del file stesso, oppure export-YYYYMMDD-HHMMSS.zip.
options.force_zipRestituisce sempre uno ZIP, anche per un singolo file.
options.compression_level0 = solo archiviazione, 1–9; predefinito 6.

Contratto con il database

La funzione accetta un argomento jsonb e restituisce un insieme di righe. path e content sono obbligatori; mime_type e store_only sono opzionali.

CREATE FUNCTION api.export_documents(params jsonb)
RETURNS TABLE (path text, content bytea, mime_type text, store_only boolean)
LANGUAGE sql AS $$
  SELECT 'report.csv', convert_to('a,b' || E'\n' || '1,2', 'UTF8'), 'text/csv', false;
$$;
  • path deve essere un semplice percorso relativo che sia sicuro da estrarre su qualsiasi sistema operativo. Un percorso viene rifiutato (la richiesta fallisce con 500 e il problema viene registrato nel log) se:
    • è vuoto, più lungo di 512 byte, non è UTF-8 valido o contiene caratteri di controllo;
    • è assoluto (/…, C:…) o contiene un backslash;
    • ha un segmento vuoto, . o ..;
    • ha un segmento che termina con un punto o uno spazio (Windows li rimuove, quindi ".. " diventerebbe ..);
    • contiene : (alternate data stream di NTFS) o uno tra < > " | ? *;
    • usa come segmento un nome di dispositivo riservato di Windows, con o senza estensione (CON, PRN, AUX, NUL, COM0–COM9, LPT0–LPT9);
    • collide con un altro percorso nella stessa risposta: i nomi vengono confrontati senza distinzione tra maiuscole e minuscole (a.txt e A.TXT entrano in conflitto su macOS/Windows) e un file non può essere anche una directory (a e a/b).
  • content NULL è un file vuoto.
  • mime_type viene usato solo se è un media type ben formato, altrimenti viene dedotto dall’estensione (fallback application/octet-stream).
  • store_only = true memorizza la voce ZIP senza compressione (necessario ad es. per il file mimetype di EPUB).

Risposta

Righeforce_zipRisultato
0qualsiasi404 (errore JSON); la transazione viene annullata (rollback), quindi nulla di ciò che la funzione ha fatto viene mantenuto e un idempotencyKey non viene consumato
1falseil file stesso, con il suo media type
1trueZIP con una voce
≥ 2qualsiasiZIP (application/zip)
curl -X POST http://localhost:8080/db/my_database/file \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"method":"api.export_documents","params":{"count":3}}' -OJ

Il nome di download (options.filename o il nome del file stesso) viene sanificato: i separatori di percorso, le virgolette e i caratteri riservati diventano _, e i caratteri di controllo, di override bidirezionale, a larghezza zero e i separatori di riga vengono rimossi, così un nome non può essere camuffato da un altro tipo di file.

Gli errori sono sempre JSON, mai binari: 400 richiesta non valida, 401 non autenticato, 403 nessun permesso, 404 funzione sconosciuta o nessuna riga, 413 la risposta supera FILE_MAX_BYTES / FILE_MAX_ENTRIES, 500 funzione fallita o struttura restituita non valida.

Sicuro per costruzione

Il database controlla solo i nomi dei file, i contenuti e i media type — mai gli header HTTP. Le risposte hanno sempre Content-Disposition: attachment con X-Content-Type-Options: nosniff, Content-Security-Policy: sandbox e Cache-Control: no-store, quindi una funzione che restituisce text/html o SVG non può eseguire script nell’origine dell’API. I nomi dei file vengono sanificati. L’intero risultato viene tenuto in memoria: il limite reale per richiesta è FILE_MAX_BYTES più la singola riga più grande, perché il driver del database legge una riga per intero prima che il limite possa essere verificato — tenere presente il numero di download simultanei. Lo ZIP stesso viene trasmesso in streaming.

Discovery

Una funzione set-returning dichiarata con RETURNS TABLE (…) o parametri OUT che include le colonne path e content viene riportata da capabilities con "kind": "file" e endpoint /file. La specifica OpenAPI documenta una vera operazione POST /{prefisso}/{database}/file (solo per i ruoli che possono eseguire una funzione file); le funzioni file non sono esposte come tool MCP.