mirror of
https://github.com/postgres/postgres.git
synced 2026-03-22 10:30:21 -04:00
test_saslprep: Test module for SASLprep()
This module includes two functions:
- test_saslprep(), that performs pg_saslprep on a bytea.
- test_saslprep_ranges(), able to check for all valid ranges of UTF-8
codepoints pg_saslprep() handles each one of them.
This provides a detailed coverage of our implementation of SASLprep()
used for SCRAM, with:
- ASCII characters.
- Incomplete UTF-8 sequences, for 390b3cbbb2 (later backpatched).
- A more advanced check for all the valid UTF-8 ranges of codepoints, to
check for cases where these generate an empty password, based on an
original suggestion from Heikki Linnakangas. This part consumes
resources and time, so it is implemented as a TAP test under a
new PG_TEST_EXTRA value.
A different patch is still under discussion to tweak our internal
SASLprep() implementation, and this module can be used to track any
changes in behavior.
Author: Michael Paquier <michael@paquier.xyz>
Reviewed-by: John Naylor <johncnaylorls@gmail.com>
Discussion: https://postgr.es/m/aaEJ-El2seZHeFcG@paquier.xyz
This commit is contained in:
parent
79a5911fe6
commit
aa73838a5c
13 changed files with 625 additions and 0 deletions
|
|
@ -342,6 +342,16 @@ make check-world PG_TEST_EXTRA='kerberos ldap ssl load_balance libpq_encryption'
|
|||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>saslprep</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Runs the TAP test suite under <filename>src/test/modules/test_saslprep</filename>.
|
||||
Not enabled by default because it is resource-intensive.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>sepgsql</literal></term>
|
||||
<listitem>
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ SUBDIRS = \
|
|||
test_regex \
|
||||
test_resowner \
|
||||
test_rls_hooks \
|
||||
test_saslprep \
|
||||
test_shm_mq \
|
||||
test_slru \
|
||||
test_tidstore \
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ subdir('test_rbtree')
|
|||
subdir('test_regex')
|
||||
subdir('test_resowner')
|
||||
subdir('test_rls_hooks')
|
||||
subdir('test_saslprep')
|
||||
subdir('test_shm_mq')
|
||||
subdir('test_slru')
|
||||
subdir('test_tidstore')
|
||||
|
|
|
|||
4
src/test/modules/test_saslprep/.gitignore
vendored
Normal file
4
src/test/modules/test_saslprep/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Generated subdirectories
|
||||
/log/
|
||||
/results/
|
||||
/tmp_check/
|
||||
25
src/test/modules/test_saslprep/Makefile
Normal file
25
src/test/modules/test_saslprep/Makefile
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# src/test/modules/test_saslprep/Makefile
|
||||
|
||||
MODULE_big = test_saslprep
|
||||
OBJS = \
|
||||
$(WIN32RES) \
|
||||
test_saslprep.o
|
||||
PGFILEDESC = "test_saslprep - test SASLprep implementation"
|
||||
|
||||
EXTENSION = test_saslprep
|
||||
DATA = test_saslprep--1.0.sql
|
||||
|
||||
REGRESS = test_saslprep
|
||||
|
||||
TAP_TESTS = 1
|
||||
|
||||
ifdef USE_PGXS
|
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS)
|
||||
else
|
||||
subdir = src/test/modules/test_saslprep
|
||||
top_builddir = ../../../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
include $(top_srcdir)/contrib/contrib-global.mk
|
||||
endif
|
||||
25
src/test/modules/test_saslprep/README
Normal file
25
src/test/modules/test_saslprep/README
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
src/test/modules/test_saslprep
|
||||
|
||||
Tests for SASLprep
|
||||
==================
|
||||
|
||||
This repository contains a test suite for stressing the SASLprep
|
||||
implementation internal to PostgreSQL.
|
||||
|
||||
It provides a set of functions able to check the validity of a SASLprep
|
||||
operation for a single byte as well as a range of these, acting as
|
||||
wrappers around pg_saslprep().
|
||||
|
||||
Running the tests
|
||||
=================
|
||||
|
||||
NOTE: A portion of the tests requires --enable-tap-tests, with
|
||||
PG_TEST_EXTRA=saslprep set to run the TAP test suite.
|
||||
|
||||
Run
|
||||
make check PG_TEST_EXTRA=saslprep
|
||||
or
|
||||
make installcheck PG_TEST_EXTRA=saslprep
|
||||
|
||||
The SQL test suite can run with or without PG_TEST_EXTRA=saslprep
|
||||
set.
|
||||
152
src/test/modules/test_saslprep/expected/test_saslprep.out
Normal file
152
src/test/modules/test_saslprep/expected/test_saslprep.out
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
-- Tests for SASLprep
|
||||
CREATE EXTENSION test_saslprep;
|
||||
-- Incomplete UTF-8 sequence.
|
||||
SELECT test_saslprep('\xef');
|
||||
test_saslprep
|
||||
-----------------
|
||||
(,INVALID_UTF8)
|
||||
(1 row)
|
||||
|
||||
-- Range of ASCII characters.
|
||||
SELECT
|
||||
CASE
|
||||
WHEN a = 0 THEN '<NUL>'
|
||||
WHEN a < 32 THEN '<CTL_' || a::text || '>'
|
||||
WHEN a = 127 THEN '<DEL>'
|
||||
ELSE chr(a) END AS dat,
|
||||
set_byte('\x00'::bytea, 0, a) AS byt,
|
||||
test_saslprep(set_byte('\x00'::bytea, 0, a)) AS saslprep
|
||||
FROM generate_series(0,127) AS a;
|
||||
dat | byt | saslprep
|
||||
----------+------+-------------------
|
||||
<NUL> | \x00 | ("\\x",SUCCESS)
|
||||
<CTL_1> | \x01 | ("\\x01",SUCCESS)
|
||||
<CTL_2> | \x02 | ("\\x02",SUCCESS)
|
||||
<CTL_3> | \x03 | ("\\x03",SUCCESS)
|
||||
<CTL_4> | \x04 | ("\\x04",SUCCESS)
|
||||
<CTL_5> | \x05 | ("\\x05",SUCCESS)
|
||||
<CTL_6> | \x06 | ("\\x06",SUCCESS)
|
||||
<CTL_7> | \x07 | ("\\x07",SUCCESS)
|
||||
<CTL_8> | \x08 | ("\\x08",SUCCESS)
|
||||
<CTL_9> | \x09 | ("\\x09",SUCCESS)
|
||||
<CTL_10> | \x0a | ("\\x0a",SUCCESS)
|
||||
<CTL_11> | \x0b | ("\\x0b",SUCCESS)
|
||||
<CTL_12> | \x0c | ("\\x0c",SUCCESS)
|
||||
<CTL_13> | \x0d | ("\\x0d",SUCCESS)
|
||||
<CTL_14> | \x0e | ("\\x0e",SUCCESS)
|
||||
<CTL_15> | \x0f | ("\\x0f",SUCCESS)
|
||||
<CTL_16> | \x10 | ("\\x10",SUCCESS)
|
||||
<CTL_17> | \x11 | ("\\x11",SUCCESS)
|
||||
<CTL_18> | \x12 | ("\\x12",SUCCESS)
|
||||
<CTL_19> | \x13 | ("\\x13",SUCCESS)
|
||||
<CTL_20> | \x14 | ("\\x14",SUCCESS)
|
||||
<CTL_21> | \x15 | ("\\x15",SUCCESS)
|
||||
<CTL_22> | \x16 | ("\\x16",SUCCESS)
|
||||
<CTL_23> | \x17 | ("\\x17",SUCCESS)
|
||||
<CTL_24> | \x18 | ("\\x18",SUCCESS)
|
||||
<CTL_25> | \x19 | ("\\x19",SUCCESS)
|
||||
<CTL_26> | \x1a | ("\\x1a",SUCCESS)
|
||||
<CTL_27> | \x1b | ("\\x1b",SUCCESS)
|
||||
<CTL_28> | \x1c | ("\\x1c",SUCCESS)
|
||||
<CTL_29> | \x1d | ("\\x1d",SUCCESS)
|
||||
<CTL_30> | \x1e | ("\\x1e",SUCCESS)
|
||||
<CTL_31> | \x1f | ("\\x1f",SUCCESS)
|
||||
| \x20 | ("\\x20",SUCCESS)
|
||||
! | \x21 | ("\\x21",SUCCESS)
|
||||
" | \x22 | ("\\x22",SUCCESS)
|
||||
# | \x23 | ("\\x23",SUCCESS)
|
||||
$ | \x24 | ("\\x24",SUCCESS)
|
||||
% | \x25 | ("\\x25",SUCCESS)
|
||||
& | \x26 | ("\\x26",SUCCESS)
|
||||
' | \x27 | ("\\x27",SUCCESS)
|
||||
( | \x28 | ("\\x28",SUCCESS)
|
||||
) | \x29 | ("\\x29",SUCCESS)
|
||||
* | \x2a | ("\\x2a",SUCCESS)
|
||||
+ | \x2b | ("\\x2b",SUCCESS)
|
||||
, | \x2c | ("\\x2c",SUCCESS)
|
||||
- | \x2d | ("\\x2d",SUCCESS)
|
||||
. | \x2e | ("\\x2e",SUCCESS)
|
||||
/ | \x2f | ("\\x2f",SUCCESS)
|
||||
0 | \x30 | ("\\x30",SUCCESS)
|
||||
1 | \x31 | ("\\x31",SUCCESS)
|
||||
2 | \x32 | ("\\x32",SUCCESS)
|
||||
3 | \x33 | ("\\x33",SUCCESS)
|
||||
4 | \x34 | ("\\x34",SUCCESS)
|
||||
5 | \x35 | ("\\x35",SUCCESS)
|
||||
6 | \x36 | ("\\x36",SUCCESS)
|
||||
7 | \x37 | ("\\x37",SUCCESS)
|
||||
8 | \x38 | ("\\x38",SUCCESS)
|
||||
9 | \x39 | ("\\x39",SUCCESS)
|
||||
: | \x3a | ("\\x3a",SUCCESS)
|
||||
; | \x3b | ("\\x3b",SUCCESS)
|
||||
< | \x3c | ("\\x3c",SUCCESS)
|
||||
= | \x3d | ("\\x3d",SUCCESS)
|
||||
> | \x3e | ("\\x3e",SUCCESS)
|
||||
? | \x3f | ("\\x3f",SUCCESS)
|
||||
@ | \x40 | ("\\x40",SUCCESS)
|
||||
A | \x41 | ("\\x41",SUCCESS)
|
||||
B | \x42 | ("\\x42",SUCCESS)
|
||||
C | \x43 | ("\\x43",SUCCESS)
|
||||
D | \x44 | ("\\x44",SUCCESS)
|
||||
E | \x45 | ("\\x45",SUCCESS)
|
||||
F | \x46 | ("\\x46",SUCCESS)
|
||||
G | \x47 | ("\\x47",SUCCESS)
|
||||
H | \x48 | ("\\x48",SUCCESS)
|
||||
I | \x49 | ("\\x49",SUCCESS)
|
||||
J | \x4a | ("\\x4a",SUCCESS)
|
||||
K | \x4b | ("\\x4b",SUCCESS)
|
||||
L | \x4c | ("\\x4c",SUCCESS)
|
||||
M | \x4d | ("\\x4d",SUCCESS)
|
||||
N | \x4e | ("\\x4e",SUCCESS)
|
||||
O | \x4f | ("\\x4f",SUCCESS)
|
||||
P | \x50 | ("\\x50",SUCCESS)
|
||||
Q | \x51 | ("\\x51",SUCCESS)
|
||||
R | \x52 | ("\\x52",SUCCESS)
|
||||
S | \x53 | ("\\x53",SUCCESS)
|
||||
T | \x54 | ("\\x54",SUCCESS)
|
||||
U | \x55 | ("\\x55",SUCCESS)
|
||||
V | \x56 | ("\\x56",SUCCESS)
|
||||
W | \x57 | ("\\x57",SUCCESS)
|
||||
X | \x58 | ("\\x58",SUCCESS)
|
||||
Y | \x59 | ("\\x59",SUCCESS)
|
||||
Z | \x5a | ("\\x5a",SUCCESS)
|
||||
[ | \x5b | ("\\x5b",SUCCESS)
|
||||
\ | \x5c | ("\\x5c",SUCCESS)
|
||||
] | \x5d | ("\\x5d",SUCCESS)
|
||||
^ | \x5e | ("\\x5e",SUCCESS)
|
||||
_ | \x5f | ("\\x5f",SUCCESS)
|
||||
` | \x60 | ("\\x60",SUCCESS)
|
||||
a | \x61 | ("\\x61",SUCCESS)
|
||||
b | \x62 | ("\\x62",SUCCESS)
|
||||
c | \x63 | ("\\x63",SUCCESS)
|
||||
d | \x64 | ("\\x64",SUCCESS)
|
||||
e | \x65 | ("\\x65",SUCCESS)
|
||||
f | \x66 | ("\\x66",SUCCESS)
|
||||
g | \x67 | ("\\x67",SUCCESS)
|
||||
h | \x68 | ("\\x68",SUCCESS)
|
||||
i | \x69 | ("\\x69",SUCCESS)
|
||||
j | \x6a | ("\\x6a",SUCCESS)
|
||||
k | \x6b | ("\\x6b",SUCCESS)
|
||||
l | \x6c | ("\\x6c",SUCCESS)
|
||||
m | \x6d | ("\\x6d",SUCCESS)
|
||||
n | \x6e | ("\\x6e",SUCCESS)
|
||||
o | \x6f | ("\\x6f",SUCCESS)
|
||||
p | \x70 | ("\\x70",SUCCESS)
|
||||
q | \x71 | ("\\x71",SUCCESS)
|
||||
r | \x72 | ("\\x72",SUCCESS)
|
||||
s | \x73 | ("\\x73",SUCCESS)
|
||||
t | \x74 | ("\\x74",SUCCESS)
|
||||
u | \x75 | ("\\x75",SUCCESS)
|
||||
v | \x76 | ("\\x76",SUCCESS)
|
||||
w | \x77 | ("\\x77",SUCCESS)
|
||||
x | \x78 | ("\\x78",SUCCESS)
|
||||
y | \x79 | ("\\x79",SUCCESS)
|
||||
z | \x7a | ("\\x7a",SUCCESS)
|
||||
{ | \x7b | ("\\x7b",SUCCESS)
|
||||
| | \x7c | ("\\x7c",SUCCESS)
|
||||
} | \x7d | ("\\x7d",SUCCESS)
|
||||
~ | \x7e | ("\\x7e",SUCCESS)
|
||||
<DEL> | \x7f | ("\\x7f",SUCCESS)
|
||||
(128 rows)
|
||||
|
||||
DROP EXTENSION test_saslprep;
|
||||
38
src/test/modules/test_saslprep/meson.build
Normal file
38
src/test/modules/test_saslprep/meson.build
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) 2026, PostgreSQL Global Development Group
|
||||
|
||||
test_saslprep_sources = files(
|
||||
'test_saslprep.c',
|
||||
)
|
||||
|
||||
if host_system == 'windows'
|
||||
test_saslprep_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
|
||||
'--NAME', 'test_saslprep',
|
||||
'--FILEDESC', 'test_saslprep - test SASLprep implementation',])
|
||||
endif
|
||||
|
||||
test_saslprep = shared_module('test_saslprep',
|
||||
test_saslprep_sources,
|
||||
kwargs: pg_test_mod_args,
|
||||
)
|
||||
test_install_libs += test_saslprep
|
||||
|
||||
test_install_data += files(
|
||||
'test_saslprep.control',
|
||||
'test_saslprep--1.0.sql',
|
||||
)
|
||||
|
||||
tests += {
|
||||
'name': 'test_saslprep',
|
||||
'sd': meson.current_source_dir(),
|
||||
'bd': meson.current_build_dir(),
|
||||
'regress': {
|
||||
'sql': [
|
||||
'test_saslprep',
|
||||
],
|
||||
},
|
||||
'tap': {
|
||||
'tests': [
|
||||
't/001_saslprep_ranges.pl',
|
||||
],
|
||||
},
|
||||
}
|
||||
19
src/test/modules/test_saslprep/sql/test_saslprep.sql
Normal file
19
src/test/modules/test_saslprep/sql/test_saslprep.sql
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
-- Tests for SASLprep
|
||||
|
||||
CREATE EXTENSION test_saslprep;
|
||||
|
||||
-- Incomplete UTF-8 sequence.
|
||||
SELECT test_saslprep('\xef');
|
||||
|
||||
-- Range of ASCII characters.
|
||||
SELECT
|
||||
CASE
|
||||
WHEN a = 0 THEN '<NUL>'
|
||||
WHEN a < 32 THEN '<CTL_' || a::text || '>'
|
||||
WHEN a = 127 THEN '<DEL>'
|
||||
ELSE chr(a) END AS dat,
|
||||
set_byte('\x00'::bytea, 0, a) AS byt,
|
||||
test_saslprep(set_byte('\x00'::bytea, 0, a)) AS saslprep
|
||||
FROM generate_series(0,127) AS a;
|
||||
|
||||
DROP EXTENSION test_saslprep;
|
||||
38
src/test/modules/test_saslprep/t/001_saslprep_ranges.pl
Normal file
38
src/test/modules/test_saslprep/t/001_saslprep_ranges.pl
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) 2026, PostgreSQL Global Development Group
|
||||
|
||||
# Test all ranges of valid UTF-8 codepoints under SASLprep.
|
||||
#
|
||||
# This test is expensive and enabled with PG_TEST_EXTRA, which is
|
||||
# why it exists as a TAP test.
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use PostgreSQL::Test::Cluster;
|
||||
use Test::More;
|
||||
|
||||
if (!$ENV{PG_TEST_EXTRA} || $ENV{PG_TEST_EXTRA} !~ /\bsaslprep\b/)
|
||||
{
|
||||
plan skip_all => "test saslprep not enabled in PG_TEST_EXTRA";
|
||||
}
|
||||
|
||||
# Initialize node
|
||||
my $node = PostgreSQL::Test::Cluster->new('main');
|
||||
|
||||
$node->init;
|
||||
$node->start;
|
||||
$node->safe_psql('postgres', 'CREATE EXTENSION test_saslprep;');
|
||||
|
||||
# Among all the valid UTF-8 codepoint ranges, our implementation of
|
||||
# SASLprep should never return an empty password if the operation is
|
||||
# considered a success.
|
||||
# The only exception is currently the nul character, prohibited in
|
||||
# input of CREATE/ALTER ROLE.
|
||||
my $result = $node->safe_psql(
|
||||
'postgres', qq[SELECT * FROM test_saslprep_ranges()
|
||||
WHERE status = 'SUCCESS' AND res IN (NULL, '')
|
||||
]);
|
||||
|
||||
is($result, 'U+0000|SUCCESS|\x00|\x', "valid codepoints returning an empty password");
|
||||
|
||||
$node->stop;
|
||||
done_testing();
|
||||
30
src/test/modules/test_saslprep/test_saslprep--1.0.sql
Normal file
30
src/test/modules/test_saslprep/test_saslprep--1.0.sql
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* src/test/modules/test_saslprep/test_saslprep--1.0.sql */
|
||||
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "CREATE EXTENSION test_saslprep" to load this file. \quit
|
||||
|
||||
--
|
||||
-- test_saslprep(bytea)
|
||||
--
|
||||
-- Tests single byte sequence in SASLprep.
|
||||
--
|
||||
CREATE FUNCTION test_saslprep(IN src bytea,
|
||||
OUT res bytea,
|
||||
OUT status text)
|
||||
RETURNS record
|
||||
AS 'MODULE_PATHNAME'
|
||||
LANGUAGE C STRICT;
|
||||
|
||||
--
|
||||
-- test_saslprep_ranges
|
||||
--
|
||||
-- Tests all possible ranges of byte sequences in SASLprep.
|
||||
--
|
||||
CREATE FUNCTION test_saslprep_ranges(
|
||||
OUT codepoint text,
|
||||
OUT status text,
|
||||
OUT src bytea,
|
||||
OUT res bytea)
|
||||
RETURNS SETOF record
|
||||
AS 'MODULE_PATHNAME'
|
||||
LANGUAGE C STRICT;
|
||||
277
src/test/modules/test_saslprep/test_saslprep.c
Normal file
277
src/test/modules/test_saslprep/test_saslprep.c
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
/*--------------------------------------------------------------------------
|
||||
*
|
||||
* test_saslprep.c
|
||||
* Test harness for the SASLprep implementation.
|
||||
*
|
||||
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* src/test/modules/test_saslprep/test_saslprep.c
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/htup_details.h"
|
||||
#include "common/saslprep.h"
|
||||
#include "fmgr.h"
|
||||
#include "funcapi.h"
|
||||
#include "mb/pg_wchar.h"
|
||||
#include "miscadmin.h"
|
||||
#include "utils/builtins.h"
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
|
||||
static const char *
|
||||
saslprep_status_to_text(pg_saslprep_rc rc)
|
||||
{
|
||||
const char *status = "???";
|
||||
|
||||
switch (rc)
|
||||
{
|
||||
case SASLPREP_OOM:
|
||||
status = "OOM";
|
||||
break;
|
||||
case SASLPREP_SUCCESS:
|
||||
status = "SUCCESS";
|
||||
break;
|
||||
case SASLPREP_INVALID_UTF8:
|
||||
status = "INVALID_UTF8";
|
||||
break;
|
||||
case SASLPREP_PROHIBITED:
|
||||
status = "PROHIBITED";
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Simple function to test SASLprep with arbitrary bytes as input.
|
||||
*
|
||||
* This takes a bytea in input, returning in output the generating data as
|
||||
* bytea with the status returned by pg_saslprep().
|
||||
*/
|
||||
PG_FUNCTION_INFO_V1(test_saslprep);
|
||||
Datum
|
||||
test_saslprep(PG_FUNCTION_ARGS)
|
||||
{
|
||||
bytea *string = PG_GETARG_BYTEA_PP(0);
|
||||
char *src;
|
||||
Size src_len;
|
||||
char *input_data;
|
||||
char *result;
|
||||
Size result_len;
|
||||
bytea *result_bytea = NULL;
|
||||
const char *status = NULL;
|
||||
Datum *values;
|
||||
bool *nulls;
|
||||
TupleDesc tupdesc;
|
||||
pg_saslprep_rc rc;
|
||||
|
||||
/* determine result type */
|
||||
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
||||
elog(ERROR, "return type must be a row type");
|
||||
|
||||
values = palloc0_array(Datum, tupdesc->natts);
|
||||
nulls = palloc0_array(bool, tupdesc->natts);
|
||||
|
||||
src_len = VARSIZE_ANY_EXHDR(string);
|
||||
src = VARDATA_ANY(string);
|
||||
|
||||
/*
|
||||
* Copy the input given, to make SASLprep() act on a sanitized string.
|
||||
*/
|
||||
input_data = palloc0(src_len + 1);
|
||||
strlcpy(input_data, src, src_len + 1);
|
||||
|
||||
rc = pg_saslprep(input_data, &result);
|
||||
status = saslprep_status_to_text(rc);
|
||||
|
||||
if (result)
|
||||
{
|
||||
result_len = strlen(result);
|
||||
result_bytea = palloc(result_len + VARHDRSZ);
|
||||
SET_VARSIZE(result_bytea, result_len + VARHDRSZ);
|
||||
memcpy(VARDATA(result_bytea), result, result_len);
|
||||
values[0] = PointerGetDatum(result_bytea);
|
||||
}
|
||||
else
|
||||
nulls[0] = true;
|
||||
|
||||
values[1] = CStringGetTextDatum(status);
|
||||
|
||||
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
|
||||
}
|
||||
|
||||
/* Context structure for set-returning function with ranges */
|
||||
typedef struct
|
||||
{
|
||||
int current_range;
|
||||
char32_t current_codepoint;
|
||||
} pg_saslprep_test_context;
|
||||
|
||||
/*
|
||||
* UTF-8 code point ranges.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
char32_t start_codepoint;
|
||||
char32_t end_codepoint;
|
||||
} pg_utf8_codepoint_range;
|
||||
|
||||
static const pg_utf8_codepoint_range pg_utf8_test_ranges[] = {
|
||||
/* 1, 2, 3 bytes */
|
||||
{0x0000, 0xD7FF}, /* Basic Multilingual Plane, before surrogates */
|
||||
{0xE000, 0xFFFF}, /* Basic Multilingual Plane, after surrogates */
|
||||
/* 4 bytes */
|
||||
{0x10000, 0x1FFFF}, /* Supplementary Multilingual Plane */
|
||||
{0x20000, 0x2FFFF}, /* Supplementary Ideographic Plane */
|
||||
{0x30000, 0x3FFFF}, /* Tertiary Ideographic Plane */
|
||||
{0x40000, 0xDFFFF}, /* Unassigned planes */
|
||||
{0xE0000, 0xEFFFF}, /* Supplementary Special-purpose Plane */
|
||||
{0xF0000, 0xFFFFF}, /* Private Use Area A */
|
||||
{0x100000, 0x10FFFF}, /* Private Use Area B */
|
||||
};
|
||||
|
||||
#define PG_UTF8_TEST_RANGES_LEN \
|
||||
(sizeof(pg_utf8_test_ranges) / sizeof(pg_utf8_test_ranges[0]))
|
||||
|
||||
|
||||
/*
|
||||
* test_saslprep_ranges
|
||||
*
|
||||
* Test SASLprep across various UTF-8 ranges.
|
||||
*/
|
||||
PG_FUNCTION_INFO_V1(test_saslprep_ranges);
|
||||
Datum
|
||||
test_saslprep_ranges(PG_FUNCTION_ARGS)
|
||||
{
|
||||
FuncCallContext *funcctx;
|
||||
pg_saslprep_test_context *ctx;
|
||||
HeapTuple tuple;
|
||||
Datum result;
|
||||
|
||||
/* First call setup */
|
||||
if (SRF_IS_FIRSTCALL())
|
||||
{
|
||||
MemoryContext oldcontext;
|
||||
TupleDesc tupdesc;
|
||||
|
||||
funcctx = SRF_FIRSTCALL_INIT();
|
||||
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
||||
|
||||
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
||||
elog(ERROR, "return type must be a row type");
|
||||
funcctx->tuple_desc = tupdesc;
|
||||
|
||||
/* Allocate context with range setup */
|
||||
ctx = (pg_saslprep_test_context *) palloc(sizeof(pg_saslprep_test_context));
|
||||
ctx->current_range = 0;
|
||||
ctx->current_codepoint = pg_utf8_test_ranges[0].start_codepoint;
|
||||
funcctx->user_fctx = ctx;
|
||||
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
}
|
||||
|
||||
funcctx = SRF_PERCALL_SETUP();
|
||||
ctx = (pg_saslprep_test_context *) funcctx->user_fctx;
|
||||
|
||||
while (ctx->current_range < PG_UTF8_TEST_RANGES_LEN)
|
||||
{
|
||||
char32_t codepoint = ctx->current_codepoint;
|
||||
unsigned char utf8_buf[5];
|
||||
char input_str[6];
|
||||
char *output = NULL;
|
||||
pg_saslprep_rc rc;
|
||||
int utf8_len;
|
||||
const char *status;
|
||||
bytea *input_bytea;
|
||||
bytea *output_bytea;
|
||||
char codepoint_str[16];
|
||||
Datum values[4] = {0};
|
||||
bool nulls[4] = {0};
|
||||
const pg_utf8_codepoint_range *range =
|
||||
&pg_utf8_test_ranges[ctx->current_range];
|
||||
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
/* Switch to next range if finished with the previous one */
|
||||
if (ctx->current_codepoint > range->end_codepoint)
|
||||
{
|
||||
ctx->current_range++;
|
||||
if (ctx->current_range < PG_UTF8_TEST_RANGES_LEN)
|
||||
ctx->current_codepoint =
|
||||
pg_utf8_test_ranges[ctx->current_range].start_codepoint;
|
||||
continue;
|
||||
}
|
||||
|
||||
codepoint = ctx->current_codepoint;
|
||||
|
||||
/* Convert code point to UTF-8 */
|
||||
utf8_len = unicode_utf8len(codepoint);
|
||||
if (utf8_len == 0)
|
||||
{
|
||||
ctx->current_codepoint++;
|
||||
continue;
|
||||
}
|
||||
unicode_to_utf8(codepoint, utf8_buf);
|
||||
|
||||
/* Create null-terminated string */
|
||||
memcpy(input_str, utf8_buf, utf8_len);
|
||||
input_str[utf8_len] = '\0';
|
||||
|
||||
/* Test with pg_saslprep */
|
||||
rc = pg_saslprep(input_str, &output);
|
||||
|
||||
/* Prepare output values */
|
||||
memset(nulls, false, sizeof(nulls));
|
||||
|
||||
/* codepoint as text U+XXXX format */
|
||||
if (codepoint <= 0xFFFF)
|
||||
snprintf(codepoint_str, sizeof(codepoint_str), "U+%04X", codepoint);
|
||||
else
|
||||
snprintf(codepoint_str, sizeof(codepoint_str), "U+%06X", codepoint);
|
||||
values[0] = CStringGetTextDatum(codepoint_str);
|
||||
|
||||
/* status */
|
||||
status = saslprep_status_to_text(rc);
|
||||
values[1] = CStringGetTextDatum(status);
|
||||
|
||||
/* input_bytes */
|
||||
input_bytea = (bytea *) palloc(VARHDRSZ + utf8_len);
|
||||
SET_VARSIZE(input_bytea, VARHDRSZ + utf8_len);
|
||||
memcpy(VARDATA(input_bytea), utf8_buf, utf8_len);
|
||||
values[2] = PointerGetDatum(input_bytea);
|
||||
|
||||
/* output_bytes */
|
||||
if (output != NULL)
|
||||
{
|
||||
int output_len = strlen(output);
|
||||
|
||||
output_bytea = (bytea *) palloc(VARHDRSZ + output_len);
|
||||
SET_VARSIZE(output_bytea, VARHDRSZ + output_len);
|
||||
memcpy(VARDATA(output_bytea), output, output_len);
|
||||
values[3] = PointerGetDatum(output_bytea);
|
||||
pfree(output);
|
||||
}
|
||||
else
|
||||
{
|
||||
nulls[3] = true;
|
||||
values[3] = (Datum) 0;
|
||||
}
|
||||
|
||||
/* Build and return tuple */
|
||||
tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
|
||||
result = HeapTupleGetDatum(tuple);
|
||||
|
||||
/* Move to next code point */
|
||||
ctx->current_codepoint++;
|
||||
|
||||
SRF_RETURN_NEXT(funcctx, result);
|
||||
}
|
||||
|
||||
/* All done */
|
||||
SRF_RETURN_DONE(funcctx);
|
||||
}
|
||||
5
src/test/modules/test_saslprep/test_saslprep.control
Normal file
5
src/test/modules/test_saslprep/test_saslprep.control
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# test_saslprep extension
|
||||
comment = 'Test SASLprep implementation'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/test_saslprep'
|
||||
relocatable = true
|
||||
Loading…
Reference in a new issue