Download de arquivos (/file)
Download de arquivos
O endpoint /file serve arquivos binários gerados por uma função PostgreSQL — sem base64 dentro do JSON-RPC. Um único arquivo retornado é enviado como está; vários arquivos são empacotados em um arquivo ZIP em tempo real.
POST /{prefixo}/{banco_de_dados}/fileAutenticação, troca de papel (SET LOCAL ROLE), rate limiting e limite de tamanho da requisição são idênticos aos de /jsonrpc. O chamador precisa de EXECUTE na função.
Requisição
{
"method": "api.export_documents",
"params": { "count": 3 },
"options": { "filename": "export.zip", "force_zip": false, "compression_level": 6 },
"idempotencyKey": "optional"
}| Campo | Descrição |
|---|---|
method | Obrigatório. schema.function, chamada como fn(params::jsonb). |
params | Passado à função como jsonb (padrão {}). |
options.filename | Nome do arquivo/ZIP baixado. Padrão: o nome do próprio arquivo, ou export-YYYYMMDD-HHMMSS.zip. |
options.force_zip | Sempre retorna um ZIP, mesmo para um único arquivo. |
options.compression_level | 0 = apenas armazenar, 1–9; padrão 6. |
Contrato com o banco de dados
A função recebe um argumento jsonb e retorna um conjunto de linhas. path e content são obrigatórios; mime_type e store_only são opcionais.
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;
$$;pathdeve ser um caminho relativo simples que possa ser extraído com segurança em qualquer sistema operacional. Um caminho é rejeitado (a requisição falha com 500 e o problema é registrado no log) se:- for vazio, tiver mais de 512 bytes, não for UTF-8 válido ou contiver caracteres de controle;
- for absoluto (
/…,C:…) ou contiver uma barra invertida; - tiver um segmento vazio,
.ou..; - tiver um segmento terminando em ponto ou espaço (o Windows os remove, então
".. "viraria..); - contiver
:(alternate data stream do NTFS) ou algum de< > " | ? *; - usar como segmento um nome de dispositivo reservado do Windows, com ou sem extensão (
CON,PRN,AUX,NUL,COM0–COM9,LPT0–LPT9); - colidir com outro caminho na mesma resposta: os nomes são comparados sem diferenciar maiúsculas de minúsculas (
a.txteA.TXTentram em conflito no macOS/Windows), e um arquivo não pode ser também um diretório (aea/b).
contentNULLé um arquivo vazio.mime_typeé usado apenas se for um media type bem formado; caso contrário, é deduzido da extensão (fallbackapplication/octet-stream).store_only = truearmazena a entrada do ZIP sem compressão (necessário, por exemplo, para o arquivomimetypedo EPUB).
Resposta
| Linhas | force_zip | Resultado |
|---|---|---|
| 0 | qualquer | 404 (erro JSON); a transação sofre rollback, portanto nada do que a função fez é persistido e um idempotencyKey não é consumido |
| 1 | false | o próprio arquivo, com seu media type |
| 1 | true | ZIP com uma entrada |
| ≥ 2 | qualquer | 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}}' -OJO nome de download (options.filename ou o nome do próprio arquivo) é sanitizado: separadores de caminho, aspas
e caracteres reservados viram _, e caracteres de controle, de substituição bidirecional, de largura zero
e separadores de linha são removidos, de modo que um nome não possa ser disfarçado de outro tipo de arquivo.
Os erros são sempre JSON, nunca binário: 400 requisição inválida, 401 não autenticado, 403 sem permissão, 404 função desconhecida ou nenhuma linha, 413 a resposta excede FILE_MAX_BYTES / FILE_MAX_ENTRIES, 500 a função falhou ou retornou uma estrutura inválida.
Seguro por construção
O banco de dados controla apenas nomes de arquivos, conteúdos e media types — nunca cabeçalhos HTTP. As respostas são sempre Content-Disposition: attachment com X-Content-Type-Options: nosniff, Content-Security-Policy: sandbox e Cache-Control: no-store, de modo que uma função que retorna text/html ou SVG não pode executar script na origem da API. Os nomes de arquivo são sanitizados. O resultado inteiro é mantido em memória: o limite real por requisição é FILE_MAX_BYTES mais a maior linha individual, porque o driver do banco de dados lê uma linha por completo antes que o limite possa ser verificado — tenha em mente o número de downloads simultâneos. O ZIP em si é transmitido por streaming.
Descoberta
Uma função set-returning declarada com RETURNS TABLE (…) ou parâmetros OUT que
inclua as colunas path e content é reportada por capabilities com "kind": "file" e endpoint /file. A especificação OpenAPI documenta uma operação real POST /{prefixo}/{banco_de_dados}/file (apenas para papéis que podem executar uma função de arquivo); as funções de arquivo não são expostas como ferramentas MCP.