Line data Source code
1 : /*--------------------------------------------------------------------------
2 : *
3 : * test_saslprep.c
4 : * Test harness for the SASLprep implementation.
5 : *
6 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * src/test/modules/test_saslprep/test_saslprep.c
10 : *
11 : * -------------------------------------------------------------------------
12 : */
13 :
14 : #include "postgres.h"
15 :
16 : #include "access/htup_details.h"
17 : #include "common/saslprep.h"
18 : #include "fmgr.h"
19 : #include "funcapi.h"
20 : #include "mb/pg_wchar.h"
21 : #include "miscadmin.h"
22 : #include "utils/builtins.h"
23 :
24 1 : PG_MODULE_MAGIC;
25 :
26 : static const char *
27 129 : saslprep_status_to_text(pg_saslprep_rc rc)
28 : {
29 129 : const char *status = "???";
30 :
31 129 : switch (rc)
32 : {
33 0 : case SASLPREP_OOM:
34 0 : status = "OOM";
35 0 : break;
36 95 : case SASLPREP_SUCCESS:
37 95 : status = "SUCCESS";
38 95 : break;
39 1 : case SASLPREP_INVALID_UTF8:
40 1 : status = "INVALID_UTF8";
41 1 : break;
42 33 : case SASLPREP_PROHIBITED:
43 33 : status = "PROHIBITED";
44 33 : break;
45 : }
46 :
47 129 : return status;
48 : }
49 :
50 : /*
51 : * Simple function to test SASLprep with arbitrary bytes as input.
52 : *
53 : * This takes a bytea in input, returning in output the generating data as
54 : * bytea with the status returned by pg_saslprep().
55 : */
56 2 : PG_FUNCTION_INFO_V1(test_saslprep);
57 : Datum
58 129 : test_saslprep(PG_FUNCTION_ARGS)
59 : {
60 129 : bytea *string = PG_GETARG_BYTEA_PP(0);
61 : char *src;
62 : Size src_len;
63 : char *input_data;
64 : char *result;
65 : Size result_len;
66 129 : bytea *result_bytea = NULL;
67 129 : const char *status = NULL;
68 : Datum *values;
69 : bool *nulls;
70 : TupleDesc tupdesc;
71 : pg_saslprep_rc rc;
72 :
73 : /* determine result type */
74 129 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
75 0 : elog(ERROR, "return type must be a row type");
76 :
77 129 : values = palloc0_array(Datum, tupdesc->natts);
78 129 : nulls = palloc0_array(bool, tupdesc->natts);
79 :
80 129 : src_len = VARSIZE_ANY_EXHDR(string);
81 129 : src = VARDATA_ANY(string);
82 :
83 : /*
84 : * Copy the input given, to make SASLprep() act on a sanitized string.
85 : */
86 129 : input_data = palloc0(src_len + 1);
87 129 : memcpy(input_data, src, src_len);
88 129 : input_data[src_len] = '\0';
89 :
90 129 : rc = pg_saslprep(input_data, &result);
91 129 : status = saslprep_status_to_text(rc);
92 :
93 129 : if (result)
94 : {
95 95 : result_len = strlen(result);
96 95 : result_bytea = palloc(result_len + VARHDRSZ);
97 95 : SET_VARSIZE(result_bytea, result_len + VARHDRSZ);
98 95 : memcpy(VARDATA(result_bytea), result, result_len);
99 95 : values[0] = PointerGetDatum(result_bytea);
100 : }
101 : else
102 34 : nulls[0] = true;
103 :
104 129 : values[1] = CStringGetTextDatum(status);
105 :
106 129 : PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
107 : }
108 :
109 : /* Context structure for set-returning function with ranges */
110 : typedef struct
111 : {
112 : int current_range;
113 : char32_t current_codepoint;
114 : } pg_saslprep_test_context;
115 :
116 : /*
117 : * UTF-8 code point ranges.
118 : */
119 : typedef struct
120 : {
121 : char32_t start_codepoint;
122 : char32_t end_codepoint;
123 : } pg_utf8_codepoint_range;
124 :
125 : static const pg_utf8_codepoint_range pg_utf8_test_ranges[] = {
126 : /* 1, 2, 3 bytes */
127 : {0x0000, 0xD7FF}, /* Basic Multilingual Plane, before surrogates */
128 : {0xE000, 0xFFFF}, /* Basic Multilingual Plane, after surrogates */
129 : /* 4 bytes */
130 : {0x10000, 0x1FFFF}, /* Supplementary Multilingual Plane */
131 : {0x20000, 0x2FFFF}, /* Supplementary Ideographic Plane */
132 : {0x30000, 0x3FFFF}, /* Tertiary Ideographic Plane */
133 : {0x40000, 0xDFFFF}, /* Unassigned planes */
134 : {0xE0000, 0xEFFFF}, /* Supplementary Special-purpose Plane */
135 : {0xF0000, 0xFFFFF}, /* Private Use Area A */
136 : {0x100000, 0x10FFFF}, /* Private Use Area B */
137 : };
138 :
139 : #define PG_UTF8_TEST_RANGES_LEN \
140 : (sizeof(pg_utf8_test_ranges) / sizeof(pg_utf8_test_ranges[0]))
141 :
142 :
143 : /*
144 : * test_saslprep_ranges
145 : *
146 : * Test SASLprep across various UTF-8 ranges.
147 : */
148 1 : PG_FUNCTION_INFO_V1(test_saslprep_ranges);
149 : Datum
150 0 : test_saslprep_ranges(PG_FUNCTION_ARGS)
151 : {
152 : FuncCallContext *funcctx;
153 : pg_saslprep_test_context *ctx;
154 : HeapTuple tuple;
155 : Datum result;
156 :
157 : /* First call setup */
158 0 : if (SRF_IS_FIRSTCALL())
159 : {
160 : MemoryContext oldcontext;
161 : TupleDesc tupdesc;
162 :
163 0 : funcctx = SRF_FIRSTCALL_INIT();
164 0 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
165 :
166 0 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
167 0 : elog(ERROR, "return type must be a row type");
168 0 : funcctx->tuple_desc = tupdesc;
169 :
170 : /* Allocate context with range setup */
171 0 : ctx = (pg_saslprep_test_context *) palloc(sizeof(pg_saslprep_test_context));
172 0 : ctx->current_range = 0;
173 0 : ctx->current_codepoint = pg_utf8_test_ranges[0].start_codepoint;
174 0 : funcctx->user_fctx = ctx;
175 :
176 0 : MemoryContextSwitchTo(oldcontext);
177 : }
178 :
179 0 : funcctx = SRF_PERCALL_SETUP();
180 0 : ctx = (pg_saslprep_test_context *) funcctx->user_fctx;
181 :
182 0 : while (ctx->current_range < PG_UTF8_TEST_RANGES_LEN)
183 : {
184 0 : char32_t codepoint = ctx->current_codepoint;
185 : unsigned char utf8_buf[5];
186 : char input_str[6];
187 0 : char *output = NULL;
188 : pg_saslprep_rc rc;
189 : int utf8_len;
190 : const char *status;
191 : bytea *input_bytea;
192 : bytea *output_bytea;
193 : char codepoint_str[16];
194 0 : Datum values[4] = {0};
195 0 : bool nulls[4] = {0};
196 0 : const pg_utf8_codepoint_range *range =
197 0 : &pg_utf8_test_ranges[ctx->current_range];
198 :
199 0 : CHECK_FOR_INTERRUPTS();
200 :
201 : /* Switch to next range if finished with the previous one */
202 0 : if (ctx->current_codepoint > range->end_codepoint)
203 : {
204 0 : ctx->current_range++;
205 0 : if (ctx->current_range < PG_UTF8_TEST_RANGES_LEN)
206 0 : ctx->current_codepoint =
207 0 : pg_utf8_test_ranges[ctx->current_range].start_codepoint;
208 0 : continue;
209 : }
210 :
211 0 : codepoint = ctx->current_codepoint;
212 :
213 : /* Convert code point to UTF-8 */
214 0 : utf8_len = unicode_utf8len(codepoint);
215 0 : if (utf8_len == 0)
216 : {
217 0 : ctx->current_codepoint++;
218 0 : continue;
219 : }
220 0 : unicode_to_utf8(codepoint, utf8_buf);
221 :
222 : /* Create null-terminated string */
223 0 : memcpy(input_str, utf8_buf, utf8_len);
224 0 : input_str[utf8_len] = '\0';
225 :
226 : /* Test with pg_saslprep */
227 0 : rc = pg_saslprep(input_str, &output);
228 :
229 : /* Prepare output values */
230 0 : memset(nulls, false, sizeof(nulls));
231 :
232 : /* codepoint as text U+XXXX format */
233 0 : if (codepoint <= 0xFFFF)
234 0 : snprintf(codepoint_str, sizeof(codepoint_str), "U+%04X", codepoint);
235 : else
236 0 : snprintf(codepoint_str, sizeof(codepoint_str), "U+%06X", codepoint);
237 0 : values[0] = CStringGetTextDatum(codepoint_str);
238 :
239 : /* status */
240 0 : status = saslprep_status_to_text(rc);
241 0 : values[1] = CStringGetTextDatum(status);
242 :
243 : /* input_bytes */
244 0 : input_bytea = (bytea *) palloc(VARHDRSZ + utf8_len);
245 0 : SET_VARSIZE(input_bytea, VARHDRSZ + utf8_len);
246 0 : memcpy(VARDATA(input_bytea), utf8_buf, utf8_len);
247 0 : values[2] = PointerGetDatum(input_bytea);
248 :
249 : /* output_bytes */
250 0 : if (output != NULL)
251 : {
252 0 : int output_len = strlen(output);
253 :
254 0 : output_bytea = (bytea *) palloc(VARHDRSZ + output_len);
255 0 : SET_VARSIZE(output_bytea, VARHDRSZ + output_len);
256 0 : memcpy(VARDATA(output_bytea), output, output_len);
257 0 : values[3] = PointerGetDatum(output_bytea);
258 0 : pfree(output);
259 : }
260 : else
261 : {
262 0 : nulls[3] = true;
263 0 : values[3] = (Datum) 0;
264 : }
265 :
266 : /* Build and return tuple */
267 0 : tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
268 0 : result = HeapTupleGetDatum(tuple);
269 :
270 : /* Move to next code point */
271 0 : ctx->current_codepoint++;
272 :
273 0 : SRF_RETURN_NEXT(funcctx, result);
274 : }
275 :
276 : /* All done */
277 0 : SRF_RETURN_DONE(funcctx);
278 : }
|