PostgreSQL tool for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, POP3, POP3S, RTMP, RTMPS, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS.
pg_curl exposes two GUCs:
-
pg_curl.whitelist(superuser-settable, e.g. viaALTER ROLE ... SET) — comma-separatedfile://andhttp(s)://prefixes the current role is allowed to reach. Afile://entry ending in/allows anything under that directory; without a trailing slash it allows only that exact file. Anhttp(s)://entry allows any URL sharing that prefix.It is checked before every request URL (
curl_easy_setopt_url) and before every option that has curl read a local file from disk:curl_mime_file,curl_easy_setopt_cookiefile,curl_easy_setopt_crlfile,curl_easy_setopt_ssh_private_keyfile,curl_easy_setopt_ssh_public_keyfileandcurl_easy_setopt_random_file.Superusers bypass the whitelist entirely. Every other role is denied by default until a superuser grants specific prefixes:
ALTER ROLE app_user SET pg_curl.whitelist = 'https://api.example.com/,file:///var/lib/postgresql/uploads/';
-
pg_curl.transaction(boolean, defaulttrue) — keep curl handles in the current transaction's memory context instead of the top memory context, so they are cleaned up automatically at transaction end.
CREATE OR REPLACE FUNCTION get(url TEXT) RETURNS TEXT LANGUAGE SQL AS $BODY$
WITH s AS (SELECT
curl_easy_reset(),
curl_easy_setopt_url(url),
curl_easy_perform(),
curl_easy_getinfo_data_in()
) SELECT convert_from(curl_easy_getinfo_data_in, 'utf-8') FROM s;
$BODY$;CREATE OR REPLACE FUNCTION post(url TEXT, request JSON) RETURNS TEXT LANGUAGE SQL AS $BODY$
WITH s AS (SELECT
curl_easy_reset(),
( WITH s AS (
SELECT (json_each_text(request)).*
) SELECT array_agg(curl_postfield_append(key, value)) FROM s),
curl_easy_setopt_url(url),
curl_easy_perform(),
curl_easy_getinfo_data_in()
) SELECT convert_from(curl_easy_getinfo_data_in, 'utf-8') FROM s;
$BODY$;CREATE OR REPLACE FUNCTION post(url TEXT, request JSON) RETURNS TEXT LANGUAGE SQL AS $BODY$
WITH s AS (SELECT
curl_easy_reset(),
( WITH s AS (
SELECT (json_each_text(request)).*
) SELECT array_agg(curl_mime_data(value, name:=key)) FROM s),
curl_easy_setopt_url(url),
curl_easy_perform(),
curl_easy_getinfo_data_in()
) SELECT convert_from(curl_easy_getinfo_data_in, 'utf-8') FROM s;
$BODY$;CREATE OR REPLACE FUNCTION post(url TEXT, request JSON) RETURNS TEXT LANGUAGE SQL AS $BODY$
WITH s AS (SELECT
curl_easy_reset(),
curl_easy_setopt_postfields(convert_to(request::TEXT, 'utf-8')),
curl_easy_setopt_url(url),
curl_header_append('Content-Type', 'application/json; charset=utf-8'),
curl_easy_perform(),
curl_easy_getinfo_data_in()
) SELECT convert_from(curl_easy_getinfo_data_in, 'utf-8') FROM s;
$BODY$;CREATE OR REPLACE FUNCTION email(url TEXT, username TEXT, password TEXT, subject TEXT, sender TEXT, recipient TEXT, body TEXT, type TEXT) RETURNS TEXT LANGUAGE SQL AS $BODY$
WITH s AS (SELECT
curl_easy_reset(),
curl_easy_setopt_mail_from(sender),
curl_easy_setopt_password(password),
curl_easy_setopt_url(url),
curl_easy_setopt_username(username),
curl_header_append('From', sender),
curl_header_append('Subject', subject),
curl_header_append('To', recipient),
curl_mime_data(body, type:=type),
curl_recipient_append(recipient),
curl_easy_perform(),
curl_easy_getinfo_header_in()
) SELECT curl_easy_getinfo_header_in FROM s;
$BODY$;CREATE OR REPLACE FUNCTION upload(url TEXT, username TEXT, password TEXT, file BYTEA) RETURNS TEXT LANGUAGE SQL AS $BODY$
WITH s AS (SELECT
curl_easy_reset(),
curl_easy_setopt_password(password),
curl_easy_setopt_readdata(file),
curl_easy_setopt_url(url),
curl_easy_setopt_username(username),
curl_easy_perform(),
curl_easy_getinfo_header_in()
) SELECT curl_easy_getinfo_header_in FROM s;
$BODY$;CREATE OR REPLACE FUNCTION download(url TEXT, username TEXT, password TEXT) RETURNS BYTEA LANGUAGE SQL AS $BODY$
WITH s AS (SELECT
curl_easy_reset(),
curl_easy_setopt_password(password),
curl_easy_setopt_url(url),
curl_easy_setopt_username(username),
curl_easy_perform(),
curl_easy_getinfo_data_in()
) SELECT curl_easy_getinfo_data_in FROM s;
$BODY$;WITH s AS (
SELECT regexp_matches(curl_easy_getinfo_header_in(), E'([^ \t\r\n\f]+): ?([^\t\r\n\f]+)', 'g') AS s
) SELECT s[1] AS key, s[2] AS value FROM s;