mirror of
https://github.com/postgres/postgres.git
synced 2025-04-18 13:44:19 +03:00
Add functions for dealing with PGP armor header lines to pgcrypto.
This add a new pgp_armor_headers function to extract armor headers from an ASCII-armored blob, and a new overloaded variant of the armor function, for constructing an ASCII-armor with extra headers. Marko Tiikkaja and me.
This commit is contained in:
parent
0ef3c29a4b
commit
32984d8fc3
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -14,6 +14,7 @@ README.* conflict-marker-size=32
|
|||||||
# Certain data files that contain special whitespace, and other special cases
|
# Certain data files that contain special whitespace, and other special cases
|
||||||
*.data -whitespace
|
*.data -whitespace
|
||||||
contrib/tsearch2/sql/tsearch2.sql whitespace=space-before-tab,blank-at-eof,-blank-at-eol
|
contrib/tsearch2/sql/tsearch2.sql whitespace=space-before-tab,blank-at-eof,-blank-at-eol
|
||||||
|
contrib/pgcrypto/sql/pgp-armor.sql whitespace=-blank-at-eol
|
||||||
doc/bug.template whitespace=space-before-tab,-blank-at-eof,blank-at-eol
|
doc/bug.template whitespace=space-before-tab,-blank-at-eof,blank-at-eol
|
||||||
src/backend/catalog/sql_features.txt whitespace=space-before-tab,blank-at-eof,-blank-at-eol
|
src/backend/catalog/sql_features.txt whitespace=space-before-tab,blank-at-eof,-blank-at-eol
|
||||||
src/backend/tsearch/hunspell_sample.affix whitespace=-blank-at-eof
|
src/backend/tsearch/hunspell_sample.affix whitespace=-blank-at-eof
|
||||||
|
@ -26,7 +26,8 @@ MODULE_big = pgcrypto
|
|||||||
OBJS = $(SRCS:.c=.o) $(WIN32RES)
|
OBJS = $(SRCS:.c=.o) $(WIN32RES)
|
||||||
|
|
||||||
EXTENSION = pgcrypto
|
EXTENSION = pgcrypto
|
||||||
DATA = pgcrypto--1.1.sql pgcrypto--1.0--1.1.sql pgcrypto--unpackaged--1.0.sql
|
DATA = pgcrypto--1.2.sql pgcrypto--1.1--1.2.sql pgcrypto--1.0--1.1.sql \
|
||||||
|
pgcrypto--unpackaged--1.0.sql
|
||||||
PGFILEDESC = "pgcrypto - cryptographic functions"
|
PGFILEDESC = "pgcrypto - cryptographic functions"
|
||||||
|
|
||||||
REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \
|
REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \
|
||||||
|
@ -102,3 +102,271 @@ em9va2E=
|
|||||||
-----END PGP MESSAGE-----
|
-----END PGP MESSAGE-----
|
||||||
');
|
');
|
||||||
ERROR: Corrupt ascii-armor
|
ERROR: Corrupt ascii-armor
|
||||||
|
-- corrupt (no space after the colon)
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
foo:
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
ERROR: Corrupt ascii-armor
|
||||||
|
-- corrupt (no empty line)
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
ERROR: Corrupt ascii-armor
|
||||||
|
-- no headers
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
key | value
|
||||||
|
-----+-------
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
-- header with empty value
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
foo:
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
key | value
|
||||||
|
-----+-------
|
||||||
|
foo |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- simple
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
fookey: foovalue
|
||||||
|
barkey: barvalue
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
key | value
|
||||||
|
--------+----------
|
||||||
|
fookey | foovalue
|
||||||
|
barkey | barvalue
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
-- insane keys, part 1
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
insane:key :
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
key | value
|
||||||
|
-------------+-------
|
||||||
|
insane:key |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- insane keys, part 2
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
insane:key : text value here
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
key | value
|
||||||
|
-------------+-----------------
|
||||||
|
insane:key | text value here
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- long value
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
long: this value is more than 76 characters long, but it should still parse correctly as that''s permitted by RFC 4880
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
key | value
|
||||||
|
------+-----------------------------------------------------------------------------------------------------------------
|
||||||
|
long | this value is more than 76 characters long, but it should still parse correctly as that's permitted by RFC 4880
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- long value, split up
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
long: this value is more than 76 characters long, but it should still
|
||||||
|
long: parse correctly as that''s permitted by RFC 4880
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
key | value
|
||||||
|
------+------------------------------------------------------------------
|
||||||
|
long | this value is more than 76 characters long, but it should still
|
||||||
|
long | parse correctly as that's permitted by RFC 4880
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
-- long value, split up, part 2
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
long: this value is more than
|
||||||
|
long: 76 characters long, but it should still
|
||||||
|
long: parse correctly as that''s permitted by RFC 4880
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
key | value
|
||||||
|
------+-------------------------------------------------
|
||||||
|
long | this value is more than
|
||||||
|
long | 76 characters long, but it should still
|
||||||
|
long | parse correctly as that's permitted by RFC 4880
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
-- long value, split up, part 3
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
emptykey:
|
||||||
|
long: this value is more than
|
||||||
|
emptykey:
|
||||||
|
long: 76 characters long, but it should still
|
||||||
|
emptykey:
|
||||||
|
long: parse correctly as that''s permitted by RFC 4880
|
||||||
|
emptykey:
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
key | value
|
||||||
|
----------+-------------------------------------------------
|
||||||
|
emptykey |
|
||||||
|
long | this value is more than
|
||||||
|
emptykey |
|
||||||
|
long | 76 characters long, but it should still
|
||||||
|
emptykey |
|
||||||
|
long | parse correctly as that's permitted by RFC 4880
|
||||||
|
emptykey |
|
||||||
|
(7 rows)
|
||||||
|
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
Comment: dat1.blowfish.sha1.mdc.s2k3.z0
|
||||||
|
|
||||||
|
jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
|
||||||
|
yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
|
||||||
|
=JcP+
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
key | value
|
||||||
|
---------+--------------------------------
|
||||||
|
Comment | dat1.blowfish.sha1.mdc.s2k3.z0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- test CR+LF line endings
|
||||||
|
select * from pgp_armor_headers(replace('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
fookey: foovalue
|
||||||
|
barkey: barvalue
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
', E'\n', E'\r\n'));
|
||||||
|
key | value
|
||||||
|
--------+----------
|
||||||
|
fookey | foovalue
|
||||||
|
barkey | barvalue
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
-- test header generation
|
||||||
|
select armor('zooka', array['foo'], array['bar']);
|
||||||
|
armor
|
||||||
|
-----------------------------
|
||||||
|
-----BEGIN PGP MESSAGE-----+
|
||||||
|
foo: bar +
|
||||||
|
+
|
||||||
|
em9va2E= +
|
||||||
|
=D5cR +
|
||||||
|
-----END PGP MESSAGE----- +
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select armor('zooka', array['Version', 'Comment'], array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']);
|
||||||
|
armor
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
-----BEGIN PGP MESSAGE----- +
|
||||||
|
Version: Created by pgcrypto +
|
||||||
|
Comment: PostgreSQL, the world's most advanced open source database+
|
||||||
|
+
|
||||||
|
em9va2E= +
|
||||||
|
=D5cR +
|
||||||
|
-----END PGP MESSAGE----- +
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select * from pgp_armor_headers(
|
||||||
|
armor('zooka', array['Version', 'Comment'],
|
||||||
|
array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']));
|
||||||
|
key | value
|
||||||
|
---------+------------------------------------------------------------
|
||||||
|
Version | Created by pgcrypto
|
||||||
|
Comment | PostgreSQL, the world's most advanced open source database
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
-- error/corner cases
|
||||||
|
select armor('', array['foo'], array['too', 'many']);
|
||||||
|
ERROR: mismatched array dimensions
|
||||||
|
select armor('', array['too', 'many'], array['foo']);
|
||||||
|
ERROR: mismatched array dimensions
|
||||||
|
select armor('', array[['']], array['foo']);
|
||||||
|
ERROR: wrong number of array subscripts
|
||||||
|
select armor('', array['foo'], array[['']]);
|
||||||
|
ERROR: wrong number of array subscripts
|
||||||
|
select armor('', array[null], array['foo']);
|
||||||
|
ERROR: null value not allowed for header key
|
||||||
|
select armor('', array['foo'], array[null]);
|
||||||
|
ERROR: null value not allowed for header value
|
||||||
|
select armor('', '[0:0]={"foo"}', array['foo']);
|
||||||
|
armor
|
||||||
|
-----------------------------
|
||||||
|
-----BEGIN PGP MESSAGE-----+
|
||||||
|
foo: foo +
|
||||||
|
+
|
||||||
|
=twTO +
|
||||||
|
-----END PGP MESSAGE----- +
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select armor('', array['foo'], '[0:0]={"foo"}');
|
||||||
|
armor
|
||||||
|
-----------------------------
|
||||||
|
-----BEGIN PGP MESSAGE-----+
|
||||||
|
foo: foo +
|
||||||
|
+
|
||||||
|
=twTO +
|
||||||
|
-----END PGP MESSAGE----- +
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select armor('', array[E'embedded\nnewline'], array['foo']);
|
||||||
|
ERROR: header key must not contain newlines
|
||||||
|
select armor('', array['foo'], array[E'embedded\nnewline']);
|
||||||
|
ERROR: header value must not contain newlines
|
||||||
|
select armor('', array['embedded: colon+space'], array['foo']);
|
||||||
|
ERROR: header key must not contain ": "
|
||||||
|
14
contrib/pgcrypto/pgcrypto--1.1--1.2.sql
Normal file
14
contrib/pgcrypto/pgcrypto--1.1--1.2.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* contrib/pgcrypto/pgcrypto--1.1--1.2.sql */
|
||||||
|
|
||||||
|
-- complain if script is sourced in psql, rather than via ALTER EXTENSION
|
||||||
|
\echo Use "ALTER EXTENSION pgcrypto UPDATE TO '1.2'" to load this file. \quit
|
||||||
|
|
||||||
|
CREATE FUNCTION armor(bytea, text[], text[])
|
||||||
|
RETURNS text
|
||||||
|
AS 'MODULE_PATHNAME', 'pg_armor'
|
||||||
|
LANGUAGE C IMMUTABLE STRICT;
|
||||||
|
|
||||||
|
CREATE FUNCTION pgp_armor_headers(text, key OUT text, value OUT text)
|
||||||
|
RETURNS SETOF record
|
||||||
|
AS 'MODULE_PATHNAME', 'pgp_armor_headers'
|
||||||
|
LANGUAGE C IMMUTABLE STRICT;
|
@ -201,7 +201,17 @@ RETURNS text
|
|||||||
AS 'MODULE_PATHNAME', 'pg_armor'
|
AS 'MODULE_PATHNAME', 'pg_armor'
|
||||||
LANGUAGE C IMMUTABLE STRICT;
|
LANGUAGE C IMMUTABLE STRICT;
|
||||||
|
|
||||||
|
CREATE FUNCTION armor(bytea, text[], text[])
|
||||||
|
RETURNS text
|
||||||
|
AS 'MODULE_PATHNAME', 'pg_armor'
|
||||||
|
LANGUAGE C IMMUTABLE STRICT;
|
||||||
|
|
||||||
CREATE FUNCTION dearmor(text)
|
CREATE FUNCTION dearmor(text)
|
||||||
RETURNS bytea
|
RETURNS bytea
|
||||||
AS 'MODULE_PATHNAME', 'pg_dearmor'
|
AS 'MODULE_PATHNAME', 'pg_dearmor'
|
||||||
LANGUAGE C IMMUTABLE STRICT;
|
LANGUAGE C IMMUTABLE STRICT;
|
||||||
|
|
||||||
|
CREATE FUNCTION pgp_armor_headers(text, key OUT text, value OUT text)
|
||||||
|
RETURNS SETOF record
|
||||||
|
AS 'MODULE_PATHNAME', 'pgp_armor_headers'
|
||||||
|
LANGUAGE C IMMUTABLE STRICT;
|
@ -1,5 +1,5 @@
|
|||||||
# pgcrypto extension
|
# pgcrypto extension
|
||||||
comment = 'cryptographic functions'
|
comment = 'cryptographic functions'
|
||||||
default_version = '1.1'
|
default_version = '1.2'
|
||||||
module_pathname = '$libdir/pgcrypto'
|
module_pathname = '$libdir/pgcrypto'
|
||||||
relocatable = true
|
relocatable = true
|
||||||
|
@ -178,7 +178,7 @@ b64_dec_len(unsigned srclen)
|
|||||||
* PGP armor
|
* PGP armor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static const char *armor_header = "-----BEGIN PGP MESSAGE-----\n\n";
|
static const char *armor_header = "-----BEGIN PGP MESSAGE-----\n";
|
||||||
static const char *armor_footer = "\n-----END PGP MESSAGE-----\n";
|
static const char *armor_footer = "\n-----END PGP MESSAGE-----\n";
|
||||||
|
|
||||||
/* CRC24 implementation from rfc2440 */
|
/* CRC24 implementation from rfc2440 */
|
||||||
@ -204,17 +204,24 @@ crc24(const uint8 *data, unsigned len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
pgp_armor_encode(const uint8 *src, int len, StringInfo dst)
|
pgp_armor_encode(const uint8 *src, unsigned len, StringInfo dst,
|
||||||
|
int num_headers, char **keys, char **values)
|
||||||
{
|
{
|
||||||
|
int n;
|
||||||
int res;
|
int res;
|
||||||
unsigned b64len;
|
unsigned b64len;
|
||||||
unsigned crc = crc24(src, len);
|
unsigned crc = crc24(src, len);
|
||||||
|
|
||||||
appendStringInfoString(dst, armor_header);
|
appendStringInfoString(dst, armor_header);
|
||||||
|
|
||||||
|
for (n = 0; n < num_headers; n++)
|
||||||
|
appendStringInfo(dst, "%s: %s\n", keys[n], values[n]);
|
||||||
|
appendStringInfoChar(dst, '\n');
|
||||||
|
|
||||||
/* make sure we have enough room to b64_encode() */
|
/* make sure we have enough room to b64_encode() */
|
||||||
b64len = b64_enc_len(len);
|
b64len = b64_enc_len(len);
|
||||||
enlargeStringInfo(dst, (int) b64len);
|
enlargeStringInfo(dst, (int) b64len);
|
||||||
|
|
||||||
res = b64_encode(src, len, (uint8 *) dst->data + dst->len);
|
res = b64_encode(src, len, (uint8 *) dst->data + dst->len);
|
||||||
if (res > b64len)
|
if (res > b64len)
|
||||||
elog(FATAL, "overflow - encode estimate too small");
|
elog(FATAL, "overflow - encode estimate too small");
|
||||||
@ -371,3 +378,111 @@ pgp_armor_decode(const uint8 *src, int len, StringInfo dst)
|
|||||||
out:
|
out:
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extracts all armor headers from an ASCII-armored input.
|
||||||
|
*
|
||||||
|
* Returns 0 on success, or PXE_* error code on error. On success, the
|
||||||
|
* number of headers and their keys and values are returned in *nheaders,
|
||||||
|
* *nkeys and *nvalues.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
pgp_extract_armor_headers(const uint8 *src, unsigned len,
|
||||||
|
int *nheaders, char ***keys, char ***values)
|
||||||
|
{
|
||||||
|
const uint8 *data_end = src + len;
|
||||||
|
const uint8 *p;
|
||||||
|
const uint8 *base64_start;
|
||||||
|
const uint8 *armor_start;
|
||||||
|
const uint8 *armor_end;
|
||||||
|
Size armor_len;
|
||||||
|
char *line;
|
||||||
|
char *nextline;
|
||||||
|
char *eol,
|
||||||
|
*colon;
|
||||||
|
int hlen;
|
||||||
|
char *buf;
|
||||||
|
int hdrlines;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
/* armor start */
|
||||||
|
hlen = find_header(src, data_end, &armor_start, 0);
|
||||||
|
if (hlen <= 0)
|
||||||
|
return PXE_PGP_CORRUPT_ARMOR;
|
||||||
|
armor_start += hlen;
|
||||||
|
|
||||||
|
/* armor end */
|
||||||
|
hlen = find_header(armor_start, data_end, &armor_end, 1);
|
||||||
|
if (hlen <= 0)
|
||||||
|
return PXE_PGP_CORRUPT_ARMOR;
|
||||||
|
|
||||||
|
/* Count the number of armor header lines. */
|
||||||
|
hdrlines = 0;
|
||||||
|
p = armor_start;
|
||||||
|
while (p < armor_end && *p != '\n' && *p != '\r')
|
||||||
|
{
|
||||||
|
p = memchr(p, '\n', armor_end - p);
|
||||||
|
if (!p)
|
||||||
|
return PXE_PGP_CORRUPT_ARMOR;
|
||||||
|
|
||||||
|
/* step to start of next line */
|
||||||
|
p++;
|
||||||
|
hdrlines++;
|
||||||
|
}
|
||||||
|
base64_start = p;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make a modifiable copy of the part of the input that contains the
|
||||||
|
* headers. The returned key/value pointers will point inside the buffer.
|
||||||
|
*/
|
||||||
|
armor_len = base64_start - armor_start;
|
||||||
|
buf = palloc(armor_len + 1);
|
||||||
|
memcpy(buf, armor_start, armor_len);
|
||||||
|
buf[armor_len] = '\0';
|
||||||
|
|
||||||
|
/* Allocate return arrays */
|
||||||
|
*keys = (char **) palloc(hdrlines * sizeof(char *));
|
||||||
|
*values = (char **) palloc(hdrlines * sizeof(char *));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split the header lines at newlines and ": " separators, and collect
|
||||||
|
* pointers to the keys and values in the return arrays.
|
||||||
|
*/
|
||||||
|
n = 0;
|
||||||
|
line = buf;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
/* find end of line */
|
||||||
|
eol = strchr(line, '\n');
|
||||||
|
if (!eol)
|
||||||
|
break;
|
||||||
|
nextline = eol + 1;
|
||||||
|
/* if the line ends in CR + LF, strip the CR */
|
||||||
|
if (eol > line && *(eol - 1) == '\r')
|
||||||
|
eol--;
|
||||||
|
*eol = '\0';
|
||||||
|
|
||||||
|
/* find colon+space separating the key and value */
|
||||||
|
colon = strstr(line, ": ");
|
||||||
|
if (!colon)
|
||||||
|
return PXE_PGP_CORRUPT_ARMOR;
|
||||||
|
*colon = '\0';
|
||||||
|
|
||||||
|
/* shouldn't happen, we counted the number of lines beforehand */
|
||||||
|
if (n >= hdrlines)
|
||||||
|
elog(ERROR, "unexpected number of armor header lines");
|
||||||
|
|
||||||
|
(*keys)[n] = line;
|
||||||
|
(*values)[n] = colon + 2;
|
||||||
|
n++;
|
||||||
|
|
||||||
|
/* step to start of next line */
|
||||||
|
line = nextline;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n != hdrlines)
|
||||||
|
elog(ERROR, "unexpected number of armor header lines");
|
||||||
|
|
||||||
|
*nheaders = n;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@ -32,8 +32,11 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "lib/stringinfo.h"
|
#include "lib/stringinfo.h"
|
||||||
|
#include "catalog/pg_type.h"
|
||||||
#include "mb/pg_wchar.h"
|
#include "mb/pg_wchar.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/array.h"
|
||||||
|
#include "funcapi.h"
|
||||||
|
|
||||||
#include "mbuf.h"
|
#include "mbuf.h"
|
||||||
#include "px.h"
|
#include "px.h"
|
||||||
@ -56,6 +59,7 @@ PG_FUNCTION_INFO_V1(pgp_key_id_w);
|
|||||||
|
|
||||||
PG_FUNCTION_INFO_V1(pg_armor);
|
PG_FUNCTION_INFO_V1(pg_armor);
|
||||||
PG_FUNCTION_INFO_V1(pg_dearmor);
|
PG_FUNCTION_INFO_V1(pg_dearmor);
|
||||||
|
PG_FUNCTION_INFO_V1(pgp_armor_headers);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mix a block of data into RNG.
|
* Mix a block of data into RNG.
|
||||||
@ -148,6 +152,19 @@ convert_to_utf8(text *src)
|
|||||||
return convert_charset(src, GetDatabaseEncoding(), PG_UTF8);
|
return convert_charset(src, GetDatabaseEncoding(), PG_UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
string_is_ascii(const char *str)
|
||||||
|
{
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
for (p = str; *p; p++)
|
||||||
|
{
|
||||||
|
if (IS_HIGHBIT_SET(*p))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
clear_and_pfree(text *p)
|
clear_and_pfree(text *p)
|
||||||
{
|
{
|
||||||
@ -816,6 +833,100 @@ pgp_pub_decrypt_text(PG_FUNCTION_ARGS)
|
|||||||
* Wrappers for PGP ascii armor
|
* Wrappers for PGP ascii armor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper function for pgp_armor. Converts arrays of keys and values into
|
||||||
|
* plain C arrays, and checks that they don't contain invalid characters.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
parse_key_value_arrays(ArrayType *key_array, ArrayType *val_array,
|
||||||
|
char ***p_keys, char ***p_values)
|
||||||
|
{
|
||||||
|
int nkdims = ARR_NDIM(key_array);
|
||||||
|
int nvdims = ARR_NDIM(val_array);
|
||||||
|
char **keys,
|
||||||
|
**values;
|
||||||
|
Datum *key_datums,
|
||||||
|
*val_datums;
|
||||||
|
bool *key_nulls,
|
||||||
|
*val_nulls;
|
||||||
|
int key_count,
|
||||||
|
val_count;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (nkdims > 1 || nkdims != nvdims)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
||||||
|
errmsg("wrong number of array subscripts")));
|
||||||
|
if (nkdims == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
deconstruct_array(key_array,
|
||||||
|
TEXTOID, -1, false, 'i',
|
||||||
|
&key_datums, &key_nulls, &key_count);
|
||||||
|
|
||||||
|
deconstruct_array(val_array,
|
||||||
|
TEXTOID, -1, false, 'i',
|
||||||
|
&val_datums, &val_nulls, &val_count);
|
||||||
|
|
||||||
|
if (key_count != val_count)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
||||||
|
errmsg("mismatched array dimensions")));
|
||||||
|
|
||||||
|
keys = (char **) palloc(sizeof(char *) * key_count);
|
||||||
|
values = (char **) palloc(sizeof(char *) * val_count);
|
||||||
|
|
||||||
|
for (i = 0; i < key_count; i++)
|
||||||
|
{
|
||||||
|
char *v;
|
||||||
|
|
||||||
|
/* Check that the key doesn't contain anything funny */
|
||||||
|
if (key_nulls[i])
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
||||||
|
errmsg("null value not allowed for header key")));
|
||||||
|
|
||||||
|
v = TextDatumGetCString(key_datums[i]);
|
||||||
|
|
||||||
|
if (!string_is_ascii(v))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("header key must not contain non-ASCII characters")));
|
||||||
|
if (strstr(v, ": "))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("header key must not contain \": \"")));
|
||||||
|
if (strchr(v, '\n'))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("header key must not contain newlines")));
|
||||||
|
keys[i] = v;
|
||||||
|
|
||||||
|
/* And the same for the value */
|
||||||
|
if (val_nulls[i])
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
||||||
|
errmsg("null value not allowed for header value")));
|
||||||
|
|
||||||
|
v = TextDatumGetCString(val_datums[i]);
|
||||||
|
|
||||||
|
if (!string_is_ascii(v))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("header value must not contain non-ASCII characters")));
|
||||||
|
if (strchr(v, '\n'))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("header value must not contain newlines")));
|
||||||
|
|
||||||
|
values[i] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
*p_keys = keys;
|
||||||
|
*p_values = values;
|
||||||
|
return key_count;
|
||||||
|
}
|
||||||
|
|
||||||
Datum
|
Datum
|
||||||
pg_armor(PG_FUNCTION_ARGS)
|
pg_armor(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
@ -823,13 +934,27 @@ pg_armor(PG_FUNCTION_ARGS)
|
|||||||
text *res;
|
text *res;
|
||||||
int data_len;
|
int data_len;
|
||||||
StringInfoData buf;
|
StringInfoData buf;
|
||||||
|
int num_headers;
|
||||||
|
char **keys = NULL,
|
||||||
|
**values = NULL;
|
||||||
|
|
||||||
data = PG_GETARG_BYTEA_P(0);
|
data = PG_GETARG_BYTEA_P(0);
|
||||||
data_len = VARSIZE(data) - VARHDRSZ;
|
data_len = VARSIZE(data) - VARHDRSZ;
|
||||||
|
if (PG_NARGS() == 3)
|
||||||
|
{
|
||||||
|
num_headers = parse_key_value_arrays(PG_GETARG_ARRAYTYPE_P(1),
|
||||||
|
PG_GETARG_ARRAYTYPE_P(2),
|
||||||
|
&keys, &values);
|
||||||
|
}
|
||||||
|
else if (PG_NARGS() == 1)
|
||||||
|
num_headers = 0;
|
||||||
|
else
|
||||||
|
elog(ERROR, "unexpected number of arguments %d", PG_NARGS());
|
||||||
|
|
||||||
initStringInfo(&buf);
|
initStringInfo(&buf);
|
||||||
|
|
||||||
pgp_armor_encode((uint8 *) VARDATA(data), data_len, &buf);
|
pgp_armor_encode((uint8 *) VARDATA(data), data_len, &buf,
|
||||||
|
num_headers, keys, values);
|
||||||
|
|
||||||
res = palloc(VARHDRSZ + buf.len);
|
res = palloc(VARHDRSZ + buf.len);
|
||||||
SET_VARSIZE(res, VARHDRSZ + buf.len);
|
SET_VARSIZE(res, VARHDRSZ + buf.len);
|
||||||
@ -868,6 +993,82 @@ pg_dearmor(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_TEXT_P(res);
|
PG_RETURN_TEXT_P(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* cross-call state for pgp_armor_headers */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int nheaders;
|
||||||
|
char **keys;
|
||||||
|
char **values;
|
||||||
|
} pgp_armor_headers_state;
|
||||||
|
|
||||||
|
Datum
|
||||||
|
pgp_armor_headers(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
FuncCallContext *funcctx;
|
||||||
|
pgp_armor_headers_state *state;
|
||||||
|
char *utf8key;
|
||||||
|
char *utf8val;
|
||||||
|
HeapTuple tuple;
|
||||||
|
TupleDesc tupdesc;
|
||||||
|
AttInMetadata *attinmeta;
|
||||||
|
|
||||||
|
if (SRF_IS_FIRSTCALL())
|
||||||
|
{
|
||||||
|
text *data = PG_GETARG_TEXT_PP(0);
|
||||||
|
int res;
|
||||||
|
MemoryContext oldcontext;
|
||||||
|
|
||||||
|
funcctx = SRF_FIRSTCALL_INIT();
|
||||||
|
|
||||||
|
/* we need the state allocated in the multi call context */
|
||||||
|
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
||||||
|
|
||||||
|
/* Build a tuple descriptor for our result type */
|
||||||
|
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
||||||
|
elog(ERROR, "return type must be a row type");
|
||||||
|
|
||||||
|
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||||
|
funcctx->attinmeta = attinmeta;
|
||||||
|
|
||||||
|
state = (pgp_armor_headers_state *) palloc(sizeof(pgp_armor_headers_state));
|
||||||
|
|
||||||
|
res = pgp_extract_armor_headers((uint8 *) VARDATA_ANY(data),
|
||||||
|
VARSIZE_ANY_EXHDR(data),
|
||||||
|
&state->nheaders, &state->keys,
|
||||||
|
&state->values);
|
||||||
|
if (res < 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
|
||||||
|
errmsg("%s", px_strerror(res))));
|
||||||
|
|
||||||
|
MemoryContextSwitchTo(oldcontext);
|
||||||
|
funcctx->user_fctx = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
funcctx = SRF_PERCALL_SETUP();
|
||||||
|
state = (pgp_armor_headers_state *) funcctx->user_fctx;
|
||||||
|
|
||||||
|
if (funcctx->call_cntr >= state->nheaders)
|
||||||
|
SRF_RETURN_DONE(funcctx);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char *values[2];
|
||||||
|
|
||||||
|
/* we assume that the keys (and values) are in UTF-8. */
|
||||||
|
utf8key = state->keys[funcctx->call_cntr];
|
||||||
|
utf8val = state->values[funcctx->call_cntr];
|
||||||
|
|
||||||
|
values[0] = pg_any_to_server(utf8key, strlen(utf8key), PG_UTF8);
|
||||||
|
values[1] = pg_any_to_server(utf8val, strlen(utf8val), PG_UTF8);
|
||||||
|
|
||||||
|
/* build a tuple */
|
||||||
|
tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
|
||||||
|
SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Wrappers for PGP key id
|
* Wrappers for PGP key id
|
||||||
*/
|
*/
|
||||||
|
@ -276,8 +276,11 @@ void pgp_cfb_free(PGP_CFB *ctx);
|
|||||||
int pgp_cfb_encrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);
|
int pgp_cfb_encrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);
|
||||||
int pgp_cfb_decrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);
|
int pgp_cfb_decrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);
|
||||||
|
|
||||||
void pgp_armor_encode(const uint8 *src, int len, StringInfo dst);
|
void pgp_armor_encode(const uint8 *src, unsigned len, StringInfo dst,
|
||||||
|
int num_headers, char **keys, char **values);
|
||||||
int pgp_armor_decode(const uint8 *src, int len, StringInfo dst);
|
int pgp_armor_decode(const uint8 *src, int len, StringInfo dst);
|
||||||
|
int pgp_extract_armor_headers(const uint8 *src, unsigned len,
|
||||||
|
int *nheaders, char ***keys, char ***values);
|
||||||
|
|
||||||
int pgp_compress_filter(PushFilter **res, PGP_Context *ctx, PushFilter *dst);
|
int pgp_compress_filter(PushFilter **res, PGP_Context *ctx, PushFilter *dst);
|
||||||
int pgp_decompress_filter(PullFilter **res, PGP_Context *ctx, PullFilter *src);
|
int pgp_decompress_filter(PullFilter **res, PGP_Context *ctx, PullFilter *src);
|
||||||
|
@ -56,3 +56,161 @@ em9va2E=
|
|||||||
=ZZZZ
|
=ZZZZ
|
||||||
-----END PGP MESSAGE-----
|
-----END PGP MESSAGE-----
|
||||||
');
|
');
|
||||||
|
|
||||||
|
-- corrupt (no space after the colon)
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
foo:
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
|
||||||
|
-- corrupt (no empty line)
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
|
||||||
|
-- no headers
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
|
||||||
|
-- header with empty value
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
foo:
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
|
||||||
|
-- simple
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
fookey: foovalue
|
||||||
|
barkey: barvalue
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
|
||||||
|
-- insane keys, part 1
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
insane:key :
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
|
||||||
|
-- insane keys, part 2
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
insane:key : text value here
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
|
||||||
|
-- long value
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
long: this value is more than 76 characters long, but it should still parse correctly as that''s permitted by RFC 4880
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
|
||||||
|
-- long value, split up
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
long: this value is more than 76 characters long, but it should still
|
||||||
|
long: parse correctly as that''s permitted by RFC 4880
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
|
||||||
|
-- long value, split up, part 2
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
long: this value is more than
|
||||||
|
long: 76 characters long, but it should still
|
||||||
|
long: parse correctly as that''s permitted by RFC 4880
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
|
||||||
|
-- long value, split up, part 3
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
emptykey:
|
||||||
|
long: this value is more than
|
||||||
|
emptykey:
|
||||||
|
long: 76 characters long, but it should still
|
||||||
|
emptykey:
|
||||||
|
long: parse correctly as that''s permitted by RFC 4880
|
||||||
|
emptykey:
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
|
||||||
|
select * from pgp_armor_headers('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
Comment: dat1.blowfish.sha1.mdc.s2k3.z0
|
||||||
|
|
||||||
|
jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
|
||||||
|
yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
|
||||||
|
=JcP+
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
');
|
||||||
|
|
||||||
|
-- test CR+LF line endings
|
||||||
|
select * from pgp_armor_headers(replace('
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
fookey: foovalue
|
||||||
|
barkey: barvalue
|
||||||
|
|
||||||
|
em9va2E=
|
||||||
|
=ZZZZ
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
', E'\n', E'\r\n'));
|
||||||
|
|
||||||
|
-- test header generation
|
||||||
|
select armor('zooka', array['foo'], array['bar']);
|
||||||
|
select armor('zooka', array['Version', 'Comment'], array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']);
|
||||||
|
select * from pgp_armor_headers(
|
||||||
|
armor('zooka', array['Version', 'Comment'],
|
||||||
|
array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']));
|
||||||
|
|
||||||
|
-- error/corner cases
|
||||||
|
select armor('', array['foo'], array['too', 'many']);
|
||||||
|
select armor('', array['too', 'many'], array['foo']);
|
||||||
|
select armor('', array[['']], array['foo']);
|
||||||
|
select armor('', array['foo'], array[['']]);
|
||||||
|
select armor('', array[null], array['foo']);
|
||||||
|
select armor('', array['foo'], array[null]);
|
||||||
|
select armor('', '[0:0]={"foo"}', array['foo']);
|
||||||
|
select armor('', array['foo'], '[0:0]={"foo"}');
|
||||||
|
select armor('', array[E'embedded\nnewline'], array['foo']);
|
||||||
|
select armor('', array['foo'], array[E'embedded\nnewline']);
|
||||||
|
select armor('', array['embedded: colon+space'], array['foo']);
|
||||||
|
@ -691,13 +691,39 @@ pgp_key_id(bytea) returns text
|
|||||||
</indexterm>
|
</indexterm>
|
||||||
|
|
||||||
<synopsis>
|
<synopsis>
|
||||||
armor(data bytea) returns text
|
armor(data bytea [ , keys text[], values text[] ]) returns text
|
||||||
dearmor(data text) returns bytea
|
dearmor(data text) returns bytea
|
||||||
</synopsis>
|
</synopsis>
|
||||||
<para>
|
<para>
|
||||||
These functions wrap/unwrap binary data into PGP ASCII-armor format,
|
These functions wrap/unwrap binary data into PGP ASCII-armor format,
|
||||||
which is basically Base64 with CRC and additional formatting.
|
which is basically Base64 with CRC and additional formatting.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If the <parameter>keys</> and <parameter>values</> arrays are specified,
|
||||||
|
an <firstterm>armor header</> is added to the armored format for each
|
||||||
|
key/value pair. Both arrays must be single-dimensional, and they must
|
||||||
|
be of the same length. The keys and values cannot contain any non-ASCII
|
||||||
|
characters.
|
||||||
|
</para>
|
||||||
|
</sect3>
|
||||||
|
|
||||||
|
<sect3>
|
||||||
|
<title><function>pgp_armor_headers</function></title>
|
||||||
|
|
||||||
|
<indexterm>
|
||||||
|
<primary>pgp_armor_headers</primary>
|
||||||
|
</indexterm>
|
||||||
|
|
||||||
|
<synopsis>
|
||||||
|
pgp_armor_headers(data text, key out text, value out text) returns setof record
|
||||||
|
</synopsis>
|
||||||
|
<para>
|
||||||
|
<function>pgp_armor_headers()</> extracts the armor headers from
|
||||||
|
<parameter>data</>. The return value is a set of rows with two columns,
|
||||||
|
key and value. If the keys or values contain any non-ASCII characters,
|
||||||
|
they are treated as UTF-8.
|
||||||
|
</para>
|
||||||
</sect3>
|
</sect3>
|
||||||
|
|
||||||
<sect3>
|
<sect3>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user