Age Owner Branch data TLA 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 : :
47 michael@paquier.xyz 24 :GNC 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 : : {
47 michael@paquier.xyz 33 :UNC 0 : case SASLPREP_OOM:
34 : 0 : status = "OOM";
35 : 0 : break;
47 michael@paquier.xyz 36 :GNC 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)
47 michael@paquier.xyz 75 [ # # ]:UNC 0 : elog(ERROR, "return type must be a row type");
76 : :
47 michael@paquier.xyz 77 :GNC 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);
22 87 : 129 : memcpy(input_data, src, src_len);
88 : 129 : input_data[src_len] = '\0';
89 : :
47 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
47 michael@paquier.xyz 150 :UNC 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 : : }
|