Téléchargement de fichiers (/file)
Téléchargement de fichiers
L’endpoint /file sert des fichiers binaires générés par une fonction PostgreSQL — sans base64 dans
JSON-RPC. Un seul fichier retourné est envoyé tel quel ; plusieurs fichiers sont empaquetés à la volée dans une archive ZIP.
POST /{prefix}/{database}/fileL’authentification, le changement de rôle (SET LOCAL ROLE), le rate limiting et la limite de taille des requêtes sont
identiques à /jsonrpc. L’appelant doit disposer de EXECUTE sur la fonction.
Requête
{
"method": "api.export_documents",
"params": { "count": 3 },
"options": { "filename": "export.zip", "force_zip": false, "compression_level": 6 },
"idempotencyKey": "optional"
}| Champ | Description |
|---|---|
method | Obligatoire. schema.function, appelée comme fn(params::jsonb). |
params | Transmis à la fonction en jsonb (par défaut {}). |
options.filename | Nom du fichier/ZIP téléchargé. Par défaut : le nom propre du fichier, ou export-YYYYMMDD-HHMMSS.zip. |
options.force_zip | Toujours retourner un ZIP, même pour un seul fichier. |
options.compression_level | 0 = stockage seul, 1–9 ; par défaut 6. |
Contrat avec la base de données
La fonction prend un argument jsonb et retourne un ensemble de lignes. path et
content sont obligatoires ; mime_type et store_only sont optionnels.
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;
$$;pathdoit être un simple chemin relatif pouvant être extrait en toute sécurité sur tous les systèmes d’exploitation. Un chemin est rejeté (la requête échoue avec une erreur 500 et le problème est journalisé) s’il :- est vide, dépasse 512 octets, n’est pas de l’UTF-8 valide ou contient des caractères de contrôle ;
- est absolu (
/…,C:…) ou contient un antislash ; - a un segment vide,
.ou..; - a un segment se terminant par un point ou une espace (Windows les supprime, de sorte que
".. "deviendrait..) ; - contient
:(flux de données alternatif NTFS) ou l’un des caractères< > " | ? *; - utilise comme segment un nom de périphérique Windows réservé, avec ou sans extension (
CON,PRN,AUX,NUL,COM0–COM9,LPT0–LPT9) ; - entre en collision avec un autre chemin de la même réponse : les noms sont comparés sans tenir compte de la casse (
a.txtetA.TXTentrent en conflit sous macOS/Windows), et un fichier ne peut pas être aussi un répertoire (aeta/b).
contentNULLcorrespond à un fichier vide.mime_typen’est utilisé que s’il s’agit d’un type de média bien formé, sinon il est déduit de l’extension (repli surapplication/octet-stream).store_only = truestocke l’entrée ZIP sans compression (nécessaire p. ex. pour le fichiermimetyped’EPUB).
Réponse
| Lignes | force_zip | Résultat |
|---|---|---|
| 0 | indifférent | 404 (erreur JSON) ; la transaction est annulée (rollback), donc rien de ce que la fonction a fait n’est conservé et une idempotencyKey n’est pas consommée |
| 1 | false | le fichier lui-même, avec son type de média |
| 1 | true | ZIP avec une seule entrée |
| ≥ 2 | indifférent | ZIP (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}}' -OJLe nom de téléchargement (options.filename ou le nom propre du fichier) est assaini : les séparateurs de chemin, les guillemets
et les caractères réservés deviennent _, et les caractères de contrôle, de forçage de direction du texte, de largeur nulle
et les séparateurs de ligne sont supprimés, de sorte qu’un nom ne peut pas être déguisé en un autre type de fichier.
Les erreurs sont toujours en JSON, jamais en binaire : 400 requête invalide, 401 non authentifié,
403 permission refusée, 404 fonction inconnue ou aucune ligne, 413 la réponse
dépasse FILE_MAX_BYTES / FILE_MAX_ENTRIES, 500 échec de la fonction ou
structure retournée invalide.
Sûr par conception
La base de données ne contrôle que les noms de fichiers, les contenus et les types de média — jamais les en-têtes HTTP. Les réponses sont toujours
Content-Disposition: attachment avec X-Content-Type-Options: nosniff,
Content-Security-Policy: sandbox et Cache-Control: no-store, de sorte qu’une fonction
retournant text/html ou du SVG ne peut pas exécuter de script dans l’origine de l’API. Les noms de fichiers sont assainis.
Le résultat complet est mis en tampon en mémoire : la borne réelle par requête est FILE_MAX_BYTES plus la plus grande ligne individuelle, car le pilote de base de données lit une ligne entièrement avant que la limite puisse être vérifiée — tenez compte du nombre de téléchargements simultanés. Le ZIP lui-même est diffusé en flux.
Découverte
Une fonction retournant un ensemble, déclarée avec RETURNS TABLE (…) ou des paramètres OUT
incluant les colonnes path et content, est signalée par
capabilities avec "kind": "file" et l’endpoint /file. La spécification OpenAPI
documente une véritable opération POST /{prefix}/{database}/file (uniquement pour les rôles pouvant exécuter une fonction
de fichier) ; les fonctions de fichier ne sont pas exposées comme outils MCP.