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 : strlcpy(input_data, src, src_len + 1);
88 :
89 129 : rc = pg_saslprep(input_data, &result);
90 129 : status = saslprep_status_to_text(rc);
91 :
92 129 : if (result)
93 : {
94 95 : result_len = strlen(result);
95 95 : result_bytea = palloc(result_len + VARHDRSZ);
96 95 : SET_VARSIZE(result_bytea, result_len + VARHDRSZ);
97 95 : memcpy(VARDATA(result_bytea), result, result_len);
98 95 : values[0] = PointerGetDatum(result_bytea);
99 : }
100 : else
101 34 : nulls[0] = true;
102 :
103 129 : values[1] = CStringGetTextDatum(status);
104 :
105 129 : PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
106 : }
107 :
108 : /* Context structure for set-returning function with ranges */
109 : typedef struct
110 : {
111 : int current_range;
112 : char32_t current_codepoint;
113 : } pg_saslprep_test_context;
114 :
115 : /*
116 : * UTF-8 code point ranges.
117 : */
118 : typedef struct
119 : {
120 : char32_t start_codepoint;
121 : char32_t end_codepoint;
122 : } pg_utf8_codepoint_range;
123 :
124 : static const pg_utf8_codepoint_range pg_utf8_test_ranges[] = {
125 : /* 1, 2, 3 bytes */
126 : {0x0000, 0xD7FF}, /* Basic Multilingual Plane, before surrogates */
127 : {0xE000, 0xFFFF}, /* Basic Multilingual Plane, after surrogates */
128 : /* 4 bytes */
129 : {0x10000, 0x1FFFF}, /* Supplementary Multilingual Plane */
130 : {0x20000, 0x2FFFF}, /* Supplementary Ideographic Plane */
131 : {0x30000, 0x3FFFF}, /* Tertiary Ideographic Plane */
132 : {0x40000, 0xDFFFF}, /* Unassigned planes */
133 : {0xE0000, 0xEFFFF}, /* Supplementary Special-purpose Plane */
134 : {0xF0000, 0xFFFFF}, /* Private Use Area A */
135 : {0x100000, 0x10FFFF}, /* Private Use Area B */
136 : };
137 :
138 : #define PG_UTF8_TEST_RANGES_LEN \
139 : (sizeof(pg_utf8_test_ranges) / sizeof(pg_utf8_test_ranges[0]))
140 :
141 :
142 : /*
143 : * test_saslprep_ranges
144 : *
145 : * Test SASLprep across various UTF-8 ranges.
146 : */
147 1 : PG_FUNCTION_INFO_V1(test_saslprep_ranges);
148 : Datum
149 0 : test_saslprep_ranges(PG_FUNCTION_ARGS)
150 : {
151 : FuncCallContext *funcctx;
152 : pg_saslprep_test_context *ctx;
153 : HeapTuple tuple;
154 : Datum result;
155 :
156 : /* First call setup */
157 0 : if (SRF_IS_FIRSTCALL())
158 : {
159 : MemoryContext oldcontext;
160 : TupleDesc tupdesc;
161 :
162 0 : funcctx = SRF_FIRSTCALL_INIT();
163 0 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
164 :
165 0 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
166 0 : elog(ERROR, "return type must be a row type");
167 0 : funcctx->tuple_desc = tupdesc;
168 :
169 : /* Allocate context with range setup */
170 0 : ctx = (pg_saslprep_test_context *) palloc(sizeof(pg_saslprep_test_context));
171 0 : ctx->current_range = 0;
172 0 : ctx->current_codepoint = pg_utf8_test_ranges[0].start_codepoint;
173 0 : funcctx->user_fctx = ctx;
174 :
175 0 : MemoryContextSwitchTo(oldcontext);
176 : }
177 :
178 0 : funcctx = SRF_PERCALL_SETUP();
179 0 : ctx = (pg_saslprep_test_context *) funcctx->user_fctx;
180 :
181 0 : while (ctx->current_range < PG_UTF8_TEST_RANGES_LEN)
182 : {
183 0 : char32_t codepoint = ctx->current_codepoint;
184 : unsigned char utf8_buf[5];
185 : char input_str[6];
186 0 : char *output = NULL;
187 : pg_saslprep_rc rc;
188 : int utf8_len;
189 : const char *status;
190 : bytea *input_bytea;
191 : bytea *output_bytea;
192 : char codepoint_str[16];
193 0 : Datum values[4] = {0};
194 0 : bool nulls[4] = {0};
195 0 : const pg_utf8_codepoint_range *range =
196 0 : &pg_utf8_test_ranges[ctx->current_range];
197 :
198 0 : CHECK_FOR_INTERRUPTS();
199 :
200 : /* Switch to next range if finished with the previous one */
201 0 : if (ctx->current_codepoint > range->end_codepoint)
202 : {
203 0 : ctx->current_range++;
204 0 : if (ctx->current_range < PG_UTF8_TEST_RANGES_LEN)
205 0 : ctx->current_codepoint =
206 0 : pg_utf8_test_ranges[ctx->current_range].start_codepoint;
207 0 : continue;
208 : }
209 :
210 0 : codepoint = ctx->current_codepoint;
211 :
212 : /* Convert code point to UTF-8 */
213 0 : utf8_len = unicode_utf8len(codepoint);
214 0 : if (utf8_len == 0)
215 : {
216 0 : ctx->current_codepoint++;
217 0 : continue;
218 : }
219 0 : unicode_to_utf8(codepoint, utf8_buf);
220 :
221 : /* Create null-terminated string */
222 0 : memcpy(input_str, utf8_buf, utf8_len);
223 0 : input_str[utf8_len] = '\0';
224 :
225 : /* Test with pg_saslprep */
226 0 : rc = pg_saslprep(input_str, &output);
227 :
228 : /* Prepare output values */
229 0 : memset(nulls, false, sizeof(nulls));
230 :
231 : /* codepoint as text U+XXXX format */
232 0 : if (codepoint <= 0xFFFF)
233 0 : snprintf(codepoint_str, sizeof(codepoint_str), "U+%04X", codepoint);
234 : else
235 0 : snprintf(codepoint_str, sizeof(codepoint_str), "U+%06X", codepoint);
236 0 : values[0] = CStringGetTextDatum(codepoint_str);
237 :
238 : /* status */
239 0 : status = saslprep_status_to_text(rc);
240 0 : values[1] = CStringGetTextDatum(status);
241 :
242 : /* input_bytes */
243 0 : input_bytea = (bytea *) palloc(VARHDRSZ + utf8_len);
244 0 : SET_VARSIZE(input_bytea, VARHDRSZ + utf8_len);
245 0 : memcpy(VARDATA(input_bytea), utf8_buf, utf8_len);
246 0 : values[2] = PointerGetDatum(input_bytea);
247 :
248 : /* output_bytes */
249 0 : if (output != NULL)
250 : {
251 0 : int output_len = strlen(output);
252 :
253 0 : output_bytea = (bytea *) palloc(VARHDRSZ + output_len);
254 0 : SET_VARSIZE(output_bytea, VARHDRSZ + output_len);
255 0 : memcpy(VARDATA(output_bytea), output, output_len);
256 0 : values[3] = PointerGetDatum(output_bytea);
257 0 : pfree(output);
258 : }
259 : else
260 : {
261 0 : nulls[3] = true;
262 0 : values[3] = (Datum) 0;
263 : }
264 :
265 : /* Build and return tuple */
266 0 : tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
267 0 : result = HeapTupleGetDatum(tuple);
268 :
269 : /* Move to next code point */
270 0 : ctx->current_codepoint++;
271 :
272 0 : SRF_RETURN_NEXT(funcctx, result);
273 : }
274 :
275 : /* All done */
276 0 : SRF_RETURN_DONE(funcctx);
277 : }
|