Datei-Download (/file)

3 Min. Lesezeit

Datei-Download

Der Endpunkt /file liefert binäre Dateien, die von einer PostgreSQL-Funktion erzeugt werden — ohne Base64 innerhalb von JSON-RPC. Eine zurückgegebene Datei wird unverändert gesendet; mehrere Dateien werden on the fly in ein ZIP-Archiv gepackt.

POST /{prefix}/{database}/file

Authentifizierung, Rollenwechsel (SET LOCAL ROLE), Rate Limiting und Request-Größenlimit sind identisch mit /jsonrpc. Der Aufrufer benötigt EXECUTE auf der Funktion.

Request

{
  "method": "api.export_documents",
  "params": { "count": 3 },
  "options": { "filename": "export.zip", "force_zip": false, "compression_level": 6 },
  "idempotencyKey": "optional"
}
FeldBeschreibung
methodErforderlich. schema.function, aufgerufen als fn(params::jsonb).
paramsWird als jsonb an die Funktion übergeben (Standard {}).
options.filenameName der heruntergeladenen Datei bzw. des ZIP. Standard: der eigene Name der Datei oder export-YYYYMMDD-HHMMSS.zip.
options.force_zipImmer ein ZIP zurückgeben, auch bei einer einzelnen Datei.
options.compression_level0 = nur speichern, 1–9; Standard 6.

Datenbank-Vertrag

Die Funktion nimmt ein jsonb-Argument entgegen und gibt eine Menge von Zeilen zurück. path und content sind erforderlich; mime_type und store_only sind optional.

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 muss ein einfacher relativer Pfad sein, der auf jedem Betriebssystem sicher entpackt werden kann. Ein Pfad wird abgelehnt (der Request schlägt mit 500 fehl und das Problem wird geloggt), wenn er:
    • leer ist, länger als 512 Bytes, kein gültiges UTF-8 ist oder Steuerzeichen enthält;
    • absolut ist (/…, C:…) oder einen Backslash enthält;
    • ein leeres Segment, ein Segment . oder .. hat;
    • ein Segment hat, das auf einen Punkt oder ein Leerzeichen endet (Windows entfernt sie, sodass aus ".. " dann .. würde);
    • : (NTFS Alternate Data Stream) oder eines der Zeichen < > " | ? * enthält;
    • einen reservierten Windows-Gerätenamen als Segment verwendet, mit oder ohne Erweiterung (CON, PRN, AUX, NUL, COM0–COM9, LPT0–LPT9);
    • mit einem anderen Pfad in derselben Antwort kollidiert: Namen werden ohne Beachtung der Groß-/Kleinschreibung verglichen (a.txt und A.TXT kollidieren unter macOS/Windows), und eine Datei kann nicht zugleich ein Verzeichnis sein (a und a/b).
  • content NULL ist eine leere Datei.
  • mime_type wird nur verwendet, wenn es ein wohlgeformter Media-Type ist, andernfalls wird er anhand der Erweiterung ermittelt (Fallback application/octet-stream).
  • store_only = true speichert den ZIP-Eintrag unkomprimiert (nötig z. B. für die EPUB-Datei mimetype).

Antwort

Zeilenforce_zipErgebnis
0beliebig404 (JSON-Fehler); die Transaktion wird zurückgerollt, sodass nichts, was die Funktion getan hat, persistiert wird und ein idempotencyKey nicht verbraucht wird
1falsedie Datei selbst, mit ihrem Media-Type
1trueZIP mit einem Eintrag
≥ 2beliebigZIP (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

Der Download-Name (options.filename oder der eigene Dateiname) wird bereinigt: Pfadtrenner, Anführungszeichen und reservierte Zeichen werden zu _, Steuerzeichen, Zeichen zur Umkehrung der Schreibrichtung, Zeichen ohne Breite und Zeilentrenner werden entfernt, sodass ein Name nicht als anderer Dateityp getarnt werden kann.

Fehler sind immer JSON, niemals binär: 400 ungültiger Request, 401 nicht authentifiziert, 403 keine Berechtigung, 404 unbekannte Funktion oder keine Zeilen, 413 die Antwort überschreitet FILE_MAX_BYTES / FILE_MAX_ENTRIES, 500 Funktion fehlgeschlagen oder hat eine ungültige Struktur zurückgegeben.

Sicher by Construction

Die Datenbank steuert nur Dateinamen, Inhalte und Media-Types — niemals HTTP-Header. Antworten sind immer Content-Disposition: attachment mit X-Content-Type-Options: nosniff, Content-Security-Policy: sandbox und Cache-Control: no-store, sodass eine Funktion, die text/html oder SVG zurückgibt, im Origin der API keinen Skriptcode ausführen kann. Dateinamen werden bereinigt. Das gesamte Ergebnis wird im Speicher gepuffert: Die reale Obergrenze pro Request ist FILE_MAX_BYTES plus die größte einzelne Zeile, da der Datenbanktreiber eine Zeile vollständig liest, bevor das Limit geprüft werden kann — beachten Sie die Zahl gleichzeitiger Downloads. Das ZIP selbst wird gestreamt.

Discovery

Eine mengenzurückgebende Funktion, deklariert mit RETURNS TABLE (…) oder OUT-Parametern, die die Spalten path und content enthalten, wird von capabilities mit "kind": "file" und dem Endpunkt /file gemeldet. Die OpenAPI-Spezifikation dokumentiert eine echte Operation POST /{prefix}/{database}/file (nur für Rollen, die eine Datei-Funktion ausführen können); Datei-Funktionen werden nicht als MCP-Tools bereitgestellt.