Line data Source code
1 : #include "test_helpers.h"
2 : #include "imap_util.h"
3 : #include "raii.h"
4 : #include <string.h>
5 : #include <stdlib.h>
6 :
7 1 : void test_imap_util(void) {
8 :
9 : /* Pure ASCII — no encoding, output identical to input */
10 : {
11 2 : RAII_STRING char *r = imap_utf7_decode("INBOX");
12 1 : ASSERT(r != NULL, "imap_utf7_decode: ASCII should not return NULL");
13 1 : ASSERT(strcmp(r, "INBOX") == 0, "ASCII passthrough mismatch");
14 : }
15 :
16 : /* Literal ampersand: "&-" → "&" */
17 : {
18 2 : RAII_STRING char *r = imap_utf7_decode("foo&-bar");
19 1 : ASSERT(r != NULL, "imap_utf7_decode: literal & should not return NULL");
20 1 : ASSERT(strcmp(r, "foo&bar") == 0, "Literal & decoding mismatch");
21 : }
22 :
23 : /* Single accented character: é = U+00E9 → "&AOk-" */
24 : {
25 2 : RAII_STRING char *r = imap_utf7_decode("&AOk-");
26 1 : ASSERT(r != NULL, "imap_utf7_decode: single char should not return NULL");
27 1 : ASSERT(strcmp(r, "\xC3\xA9") == 0, "é (U+00E9) decoding mismatch");
28 : }
29 :
30 : /* Hungarian pangram: árvíztűrőtükörfúrógép */
31 : {
32 : /* Modified UTF-7 encoded version of the pangram */
33 1 : const char *encoded =
34 : "&AOE-rv&AO0-zt&AXE-r&AVE-t&APw-k&APY-rf&APo-r&APM-g&AOk-p";
35 2 : RAII_STRING char *r = imap_utf7_decode(encoded);
36 1 : ASSERT(r != NULL, "imap_utf7_decode: pangram should not return NULL");
37 1 : ASSERT(strcmp(r, "\xC3\xA1" /* á */
38 : "rv"
39 : "\xC3\xAD" /* í */
40 : "zt"
41 : "\xC5\xB1" /* ű */
42 : "r"
43 : "\xC5\x91" /* ő */
44 : "t"
45 : "\xC3\xBC" /* ü */
46 : "k"
47 : "\xC3\xB6" /* ö */
48 : "rf"
49 : "\xC3\xBA" /* ú */
50 : "r"
51 : "\xC3\xB3" /* ó */
52 : "g"
53 : "\xC3\xA9" /* é */
54 : "p") == 0,
55 : "Hungarian pangram decoding mismatch");
56 : }
57 :
58 : /* Mixed ASCII and encoded segments (folder path) */
59 : {
60 2 : RAII_STRING char *r = imap_utf7_decode("INBOX.&AOk-rtes&AO0-t&AOk-s");
61 1 : ASSERT(r != NULL, "imap_utf7_decode: mixed path should not return NULL");
62 1 : ASSERT(strcmp(r, "INBOX."
63 : "\xC3\xA9" /* é */
64 : "rtes"
65 : "\xC3\xAD" /* í */
66 : "t"
67 : "\xC3\xA9" /* é */
68 : "s") == 0,
69 : "Mixed folder path decoding mismatch");
70 : }
71 :
72 : /* Empty string */
73 : {
74 2 : RAII_STRING char *r = imap_utf7_decode("");
75 1 : ASSERT(r != NULL, "imap_utf7_decode: empty string should not return NULL");
76 1 : ASSERT(strcmp(r, "") == 0, "Empty string result mismatch");
77 : }
78 :
79 : /* NULL input */
80 : {
81 1 : char *r = imap_utf7_decode(NULL);
82 1 : ASSERT(r == NULL, "imap_utf7_decode: NULL input should return NULL");
83 : }
84 :
85 : /* mod64 '+' (62) and ',' (63) characters: U+FBF0 → "&+,A-" */
86 : {
87 : /* UTF-16BE: FB F0 → 6-bit groups: 111110(+) 111111(,) 000000(A) */
88 2 : RAII_STRING char *r = imap_utf7_decode("&+,A-");
89 1 : ASSERT(r != NULL, "imap_utf7_decode: '+' ',' in base64 should not return NULL");
90 1 : ASSERT(strcmp(r, "\xEF\xAF\xB0") == 0, "U+FBF0 via '+' and ',' decoding mismatch");
91 : }
92 :
93 : /* Invalid character in base64 run: covers mod64_value() return -1 path */
94 : {
95 2 : RAII_STRING char *r = imap_utf7_decode("&!-");
96 1 : ASSERT(r != NULL, "imap_utf7_decode: invalid base64 char should not return NULL");
97 1 : ASSERT(strcmp(r, "") == 0, "Invalid base64 segment should produce empty output");
98 : }
99 :
100 : /* ASCII codepoint via encoded path: U+0041 'A' → "&AEE-"
101 : * Tests utf8_encode() cp < 0x80 branch (1-byte output). */
102 : {
103 : /* UTF-16BE: 00 41 → base64: A(0) E(4) E(4), decodes to 0x00 0x41 = U+0041 */
104 2 : RAII_STRING char *r = imap_utf7_decode("&AEE-");
105 1 : ASSERT(r != NULL, "imap_utf7_decode: ASCII-via-encoding should not return NULL");
106 1 : ASSERT(strcmp(r, "A") == 0, "U+0041 via encoded path mismatch");
107 : }
108 :
109 : /* CJK 3-byte UTF-8: U+4E2D (中) → "&Ti0-"
110 : * Tests utf8_encode() 0x800 <= cp < 0x10000 branch. */
111 : {
112 : /* UTF-16BE: 4E 2D → base64: T(19) i(34) 0(52) */
113 2 : RAII_STRING char *r = imap_utf7_decode("&Ti0-");
114 1 : ASSERT(r != NULL, "imap_utf7_decode: CJK should not return NULL");
115 1 : ASSERT(strcmp(r, "\xE4\xB8\xAD") == 0, "U+4E2D (middle) CJK decoding mismatch");
116 : }
117 :
118 : /* UTF-16BE surrogate pair: U+10000 (𐀀) → "&2ADcAA-"
119 : * Tests utf8_encode() cp >= 0x10000 branch and surrogate-pair reassembly. */
120 : {
121 : /* High=0xD800, Low=0xDC00; UTF-16BE: D8 00 DC 00 → 2ADcAA */
122 2 : RAII_STRING char *r = imap_utf7_decode("&2ADcAA-");
123 1 : ASSERT(r != NULL, "imap_utf7_decode: surrogate pair should not return NULL");
124 1 : ASSERT(strcmp(r, "\xF0\x90\x80\x80") == 0, "U+10000 surrogate pair decoding mismatch");
125 : }
126 :
127 : /* Unpaired high surrogate followed by non-surrogate: covers cp=unit pass-through path */
128 : {
129 : /* UTF-16BE: D8 00 (high surrogate) 00 41 (U+0041, not a low surrogate)
130 : * → base64: 2AAAQQ; result is U+D800 (invalid UTF-8) + 'A' */
131 2 : RAII_STRING char *r = imap_utf7_decode("&2AAAQQ-");
132 1 : ASSERT(r != NULL, "imap_utf7_decode: unpaired high surrogate must not crash");
133 1 : ASSERT(strcmp(r, "\xED\xA0\x80" "A") == 0,
134 : "Unpaired high surrogate pass-through mismatch");
135 : }
136 :
137 : /* ── imap_utf7_encode tests ──────────────────────────────────────────── */
138 :
139 : /* NULL input → NULL */
140 : {
141 1 : char *r = imap_utf7_encode(NULL);
142 1 : ASSERT(r == NULL, "imap_utf7_encode: NULL input returns NULL");
143 : }
144 :
145 : /* Empty string → empty string */
146 : {
147 2 : RAII_STRING char *r = imap_utf7_encode("");
148 1 : ASSERT(r != NULL, "imap_utf7_encode: empty string must not return NULL");
149 1 : ASSERT(strcmp(r, "") == 0, "imap_utf7_encode: empty string round-trip");
150 : }
151 :
152 : /* Pure ASCII passthrough (no encoding needed) */
153 : {
154 2 : RAII_STRING char *r = imap_utf7_encode("INBOX");
155 1 : ASSERT(r != NULL, "imap_utf7_encode: ASCII must not return NULL");
156 1 : ASSERT(strcmp(r, "INBOX") == 0, "imap_utf7_encode: ASCII passthrough");
157 : }
158 :
159 : /* Literal '&' → "&-" */
160 : {
161 2 : RAII_STRING char *r = imap_utf7_encode("foo&bar");
162 1 : ASSERT(r != NULL, "imap_utf7_encode: '&' must not return NULL");
163 1 : ASSERT(strcmp(r, "foo&-bar") == 0, "imap_utf7_encode: literal & escaping");
164 : }
165 :
166 : /* Roundtrip: encode then decode → original UTF-8 string */
167 : /* é (U+00E9, 2-byte UTF-8) */
168 : {
169 2 : RAII_STRING char *encoded = imap_utf7_encode("\xC3\xA9");
170 1 : ASSERT(encoded != NULL, "imap_utf7_encode: é must not return NULL");
171 2 : RAII_STRING char *decoded = imap_utf7_decode(encoded);
172 1 : ASSERT(decoded != NULL, "imap_utf7_encode/decode: é roundtrip must not be NULL");
173 1 : ASSERT(strcmp(decoded, "\xC3\xA9") == 0, "imap_utf7_encode/decode: é roundtrip");
174 : }
175 :
176 : /* CJK U+4E2D (中, 3-byte UTF-8) roundtrip */
177 : {
178 2 : RAII_STRING char *encoded = imap_utf7_encode("\xE4\xB8\xAD");
179 1 : ASSERT(encoded != NULL, "imap_utf7_encode: CJK must not return NULL");
180 2 : RAII_STRING char *decoded = imap_utf7_decode(encoded);
181 1 : ASSERT(decoded != NULL, "imap_utf7_encode/decode: CJK roundtrip must not be NULL");
182 1 : ASSERT(strcmp(decoded, "\xE4\xB8\xAD") == 0, "imap_utf7_encode/decode: CJK roundtrip");
183 : }
184 :
185 : /* Supplementary plane U+10000 (4-byte UTF-8) roundtrip (surrogate pair) */
186 : {
187 2 : RAII_STRING char *encoded = imap_utf7_encode("\xF0\x90\x80\x80");
188 1 : ASSERT(encoded != NULL, "imap_utf7_encode: U+10000 must not return NULL");
189 2 : RAII_STRING char *decoded = imap_utf7_decode(encoded);
190 1 : ASSERT(decoded != NULL, "imap_utf7_encode/decode: U+10000 roundtrip must not be NULL");
191 1 : ASSERT(strcmp(decoded, "\xF0\x90\x80\x80") == 0,
192 : "imap_utf7_encode/decode: U+10000 surrogate-pair roundtrip");
193 : }
194 :
195 : /* Mixed ASCII and non-ASCII (folder path with accented chars) */
196 : {
197 : /* "INBOX.étest" — é is U+00E9 */
198 2 : RAII_STRING char *encoded = imap_utf7_encode("INBOX.\xC3\xA9test");
199 1 : ASSERT(encoded != NULL, "imap_utf7_encode: mixed path must not return NULL");
200 2 : RAII_STRING char *decoded = imap_utf7_decode(encoded);
201 1 : ASSERT(decoded != NULL, "imap_utf7_encode/decode: mixed path must not be NULL");
202 1 : ASSERT(strcmp(decoded, "INBOX.\xC3\xA9test") == 0,
203 : "imap_utf7_encode/decode: mixed path roundtrip");
204 : }
205 :
206 : /* Multiple non-ASCII chars in a single run */
207 : {
208 : /* "éü" — two accented chars back to back, one encoded run */
209 2 : RAII_STRING char *encoded = imap_utf7_encode("\xC3\xA9\xC3\xBC");
210 1 : ASSERT(encoded != NULL, "imap_utf7_encode: two-char run must not return NULL");
211 2 : RAII_STRING char *decoded = imap_utf7_decode(encoded);
212 1 : ASSERT(decoded != NULL, "imap_utf7_encode/decode: two-char run roundtrip must not be NULL");
213 1 : ASSERT(strcmp(decoded, "\xC3\xA9\xC3\xBC") == 0,
214 : "imap_utf7_encode/decode: two-char run roundtrip");
215 : }
216 :
217 : /* Non-ASCII immediately followed by '&' (edge: run flush then '&' escape) */
218 : {
219 : /* "é&" */
220 2 : RAII_STRING char *encoded = imap_utf7_encode("\xC3\xA9&");
221 1 : ASSERT(encoded != NULL, "imap_utf7_encode: non-ASCII+'&' must not return NULL");
222 2 : RAII_STRING char *decoded = imap_utf7_decode(encoded);
223 1 : ASSERT(decoded != NULL,
224 : "imap_utf7_encode/decode: non-ASCII+'&' roundtrip must not be NULL");
225 1 : ASSERT(strcmp(decoded, "\xC3\xA9&") == 0,
226 : "imap_utf7_encode/decode: non-ASCII+'&' roundtrip");
227 : }
228 :
229 : /* Invalid UTF-8 continuation byte at start (0x80) → replaced with U+FFFD */
230 : {
231 : /* 0x80 is a bare continuation byte — decoder maps it to U+FFFD */
232 2 : RAII_STRING char *encoded = imap_utf7_encode("\x80");
233 1 : ASSERT(encoded != NULL, "imap_utf7_encode: invalid UTF-8 must not return NULL");
234 : /* encoded should be a non-ASCII run; decode it back and compare with U+FFFD */
235 2 : RAII_STRING char *decoded = imap_utf7_decode(encoded);
236 1 : ASSERT(decoded != NULL,
237 : "imap_utf7_encode/decode: invalid UTF-8 decoded must not be NULL");
238 : /* U+FFFD in UTF-8 is EF BF BD */
239 1 : ASSERT(strcmp(decoded, "\xEF\xBF\xBD") == 0,
240 : "imap_utf7_encode: invalid UTF-8 byte encodes as U+FFFD");
241 : }
242 :
243 : /* Known encode value: é → "&AOk-" (same as decode test, verify direction) */
244 : {
245 2 : RAII_STRING char *r = imap_utf7_encode("\xC3\xA9");
246 1 : ASSERT(r != NULL, "imap_utf7_encode: é known value must not be NULL");
247 1 : ASSERT(strcmp(r, "&AOk-") == 0, "imap_utf7_encode: é encodes to &AOk-");
248 : }
249 :
250 : /* Known encode value: ASCII 'A' is passed through (not encoded) */
251 : {
252 2 : RAII_STRING char *r = imap_utf7_encode("A");
253 1 : ASSERT(r != NULL, "imap_utf7_encode: 'A' must not be NULL");
254 1 : ASSERT(strcmp(r, "A") == 0, "imap_utf7_encode: printable ASCII 'A' passthrough");
255 : }
256 :
257 : /* Printable ASCII boundaries: space (0x20) and tilde (0x7E) pass through */
258 : {
259 2 : RAII_STRING char *r = imap_utf7_encode(" ~");
260 1 : ASSERT(r != NULL, "imap_utf7_encode: space+tilde must not be NULL");
261 1 : ASSERT(strcmp(r, " ~") == 0, "imap_utf7_encode: space+tilde passthrough");
262 : }
263 :
264 : /* ── imap_uid_set_expand tests ──────────────────────────────────── */
265 :
266 : /* NULL input → count 0, NULL array */
267 : {
268 1 : char (*uids)[17] = NULL;
269 1 : int cnt = 0;
270 1 : int rc = imap_uid_set_expand(NULL, &uids, &cnt);
271 1 : ASSERT(rc == 0, "uid_set_expand: NULL set rc=0");
272 1 : ASSERT(cnt == 0, "uid_set_expand: NULL set count=0");
273 1 : ASSERT(uids == NULL, "uid_set_expand: NULL set array=NULL");
274 : }
275 :
276 : /* Empty string → count 0 */
277 : {
278 1 : char (*uids)[17] = NULL;
279 1 : int cnt = 0;
280 1 : int rc = imap_uid_set_expand("", &uids, &cnt);
281 1 : ASSERT(rc == 0, "uid_set_expand: empty set rc=0");
282 1 : ASSERT(cnt == 0, "uid_set_expand: empty set count=0");
283 1 : free(uids);
284 : }
285 :
286 : /* Single UID */
287 : {
288 1 : char (*uids)[17] = NULL;
289 1 : int cnt = 0;
290 1 : int rc = imap_uid_set_expand("5", &uids, &cnt);
291 1 : ASSERT(rc == 0, "uid_set_expand: single UID rc=0");
292 1 : ASSERT(cnt == 1, "uid_set_expand: single UID count=1");
293 1 : ASSERT(strcmp(uids[0], "0000000000000005") == 0,
294 : "uid_set_expand: single UID value");
295 1 : free(uids);
296 : }
297 :
298 : /* Contiguous range 1:3 → 3 entries */
299 : {
300 1 : char (*uids)[17] = NULL;
301 1 : int cnt = 0;
302 1 : int rc = imap_uid_set_expand("1:3", &uids, &cnt);
303 1 : ASSERT(rc == 0, "uid_set_expand: range rc=0");
304 1 : ASSERT(cnt == 3, "uid_set_expand: range count=3");
305 1 : ASSERT(strcmp(uids[0], "0000000000000001") == 0, "uid_set_expand: range[0]");
306 1 : ASSERT(strcmp(uids[2], "0000000000000003") == 0, "uid_set_expand: range[2]");
307 1 : free(uids);
308 : }
309 :
310 : /* Multiple comma-separated UIDs */
311 : {
312 1 : char (*uids)[17] = NULL;
313 1 : int cnt = 0;
314 1 : int rc = imap_uid_set_expand("1,3,7", &uids, &cnt);
315 1 : ASSERT(rc == 0, "uid_set_expand: multi-uid rc=0");
316 1 : ASSERT(cnt == 3, "uid_set_expand: multi-uid count=3");
317 1 : ASSERT(strcmp(uids[1], "0000000000000003") == 0, "uid_set_expand: multi-uid[1]");
318 1 : free(uids);
319 : }
320 :
321 : /* Mix of single and range: "2,5:7" → 4 entries */
322 : {
323 1 : char (*uids)[17] = NULL;
324 1 : int cnt = 0;
325 1 : int rc = imap_uid_set_expand("2,5:7", &uids, &cnt);
326 1 : ASSERT(rc == 0, "uid_set_expand: mixed rc=0");
327 1 : ASSERT(cnt == 4, "uid_set_expand: mixed count=4");
328 1 : free(uids);
329 : }
330 :
331 : /* Large range that triggers realloc (initial cap=32) */
332 : {
333 1 : char (*uids)[17] = NULL;
334 1 : int cnt = 0;
335 1 : int rc = imap_uid_set_expand("1:40", &uids, &cnt);
336 1 : ASSERT(rc == 0, "uid_set_expand: large range rc=0");
337 1 : ASSERT(cnt == 40, "uid_set_expand: large range count=40");
338 1 : free(uids);
339 : }
340 :
341 : /* Non-numeric input → parser stops immediately → 0 UIDs */
342 : {
343 1 : char (*uids)[17] = NULL;
344 1 : int cnt = 0;
345 1 : int rc = imap_uid_set_expand("abc", &uids, &cnt);
346 1 : ASSERT(rc == 0, "uid_set_expand: non-numeric rc=0");
347 1 : ASSERT(cnt == 0, "uid_set_expand: non-numeric count=0");
348 1 : free(uids);
349 : }
350 :
351 : /* Malformed range "5:" → treats hi=lo */
352 : {
353 1 : char (*uids)[17] = NULL;
354 1 : int cnt = 0;
355 1 : int rc = imap_uid_set_expand("5:", &uids, &cnt);
356 1 : ASSERT(rc == 0, "uid_set_expand: malformed range rc=0");
357 1 : ASSERT(cnt == 1, "uid_set_expand: malformed range count=1");
358 1 : ASSERT(strcmp(uids[0], "0000000000000005") == 0,
359 : "uid_set_expand: malformed range value");
360 1 : free(uids);
361 : }
362 : }
|