1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "test-lib.h"
4 #include "str.h"
5 #include "str-sanitize.h"
6 #include "test-common.h"
7 #include "smtp-address.h"
8 
9 /*
10  * Valid mailbox parse tests
11  */
12 
13 struct valid_mailbox_parse_test {
14 	const char *input, *output;
15 	enum smtp_address_parse_flags flags;
16 
17 	struct smtp_address address;
18 };
19 
20 static const struct valid_mailbox_parse_test
21 valid_mailbox_parse_tests[] = {
22 	{
23 		.input = "",
24 		.flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY,
25 		.address = { .localpart = NULL, .domain = NULL },
26 	},
27 	{
28 		.input = "user",
29 		.flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART,
30 		.address = { .localpart = "user", .domain = NULL },
31 	},
32 	{
33 		.input = "user@domain.tld",
34 		.address = { .localpart = "user", .domain = "domain.tld" },
35 	},
36 	{
37 		.input = "1234567890@domain.tld",
38 		.address = {
39 			.localpart = "1234567890",
40 			.domain = "domain.tld" },
41 	},
42 	{
43 		.input = "_______@domain.tld",
44 		.address = {
45 			.localpart = "_______",
46 			.domain = "domain.tld" },
47 	},
48 	{
49 		.input = "firstname.lastname@domain.tld",
50 		.address = {
51 			.localpart = "firstname.lastname",
52 			.domain = "domain.tld" },
53 	},
54 	{
55 		.input = "firstname+lastname@domain.tld",
56 		.address = {
57 			.localpart = "firstname+lastname",
58 			.domain = "domain.tld" },
59 	},
60 	{
61 		.input = "firstname-lastname@domain.tld",
62 		.address = {
63 			.localpart = "firstname-lastname",
64 			.domain = "domain.tld" },
65 	},
66 	{
67 		.input = "\"user\"@domain.tld",
68 		.address = { .localpart = "user", .domain = "domain.tld" },
69 		.output = "user@domain.tld"
70 	},
71 	{
72 		.input = "\"user@frop\"@domain.tld",
73 		.address = { .localpart = "user@frop", .domain = "domain.tld" },
74 		.output = "\"user@frop\"@domain.tld"
75 	},
76 	{
77 		.input = "user@127.0.0.1",
78 		.address = { .localpart = "user", .domain = "127.0.0.1" },
79 	},
80 	{
81 		.input = "user@[127.0.0.1]",
82 		.address = { .localpart = "user", .domain = "[127.0.0.1]" },
83 	},
84 	{
85 		.input = "user@[IPv6:::1]",
86 		.address = { .localpart = "user", .domain = "[IPv6:::1]" },
87 	},
88 	{
89 		.input = "user@[IPv6:::127.0.0.1]",
90 		.address = { .localpart = "user", .domain = "[IPv6:::127.0.0.1]" },
91 	/* Japanese deviations */
92 	},
93 	{
94 		.input = "email@-example.com",
95 		.address = { .localpart = "email", .domain = "-example.com" },
96 	},
97 	{
98 		.input = ".email@example.com",
99 		.output = "\".email\"@example.com",
100 		.address = { .localpart = ".email", .domain = "example.com" },
101 	},
102 	{
103 		.input = "email.@example.com",
104 		.output = "\"email.\"@example.com",
105 		.address = { .localpart = "email.", .domain = "example.com" },
106 	},
107 	{
108 		.input = "email..email@example.com",
109 		.output = "\"email..email\"@example.com",
110 		.address = { .localpart = "email..email", .domain = "example.com" },
111 	},
112 	{
113 		.input = "Abc..123@example.com",
114 		.output = "\"Abc..123\"@example.com",
115 		.address = { .localpart = "Abc..123", .domain = "example.com" },
116 	},
117 	{
118 		.input = "Abc..@example.com",
119 		.output = "\"Abc..\"@example.com",
120 		.address = { .localpart = "Abc..", .domain = "example.com" },
121 	},
122 };
123 
124 unsigned int valid_mailbox_parse_test_count =
125 	N_ELEMENTS(valid_mailbox_parse_tests);
126 
127 static void
test_smtp_mailbox_equal(const struct smtp_address * test,const struct smtp_address * parsed)128 test_smtp_mailbox_equal(const struct smtp_address *test,
129 			const struct smtp_address *parsed)
130 {
131 	if (parsed->localpart == NULL) {
132 		test_out("address->localpart = (null)",
133 			 (parsed->localpart == test->localpart));
134 	} else {
135 		test_out(t_strdup_printf("address->localpart = \"%s\"",
136 					 parsed->localpart),
137 			 null_strcmp(parsed->localpart, test->localpart) == 0);
138 	}
139 	if (parsed->domain == NULL) {
140 		test_out(t_strdup_printf("address->domain = (null)"),
141 			 (parsed->domain == test->domain));
142 	} else {
143 		test_out(t_strdup_printf("address->domain = \"%s\"",
144 					 parsed->domain),
145 			 null_strcmp(parsed->domain, test->domain) == 0);
146 	}
147 }
148 
test_smtp_mailbox_parse_valid(void)149 static void test_smtp_mailbox_parse_valid(void)
150 {
151 	unsigned int i;
152 
153 	for (i = 0; i < valid_mailbox_parse_test_count; i++) T_BEGIN {
154 		const struct valid_mailbox_parse_test *test;
155 		struct smtp_address *address;
156 		const char *error = NULL, *output, *encoded;
157 		int ret;
158 
159 		test = &valid_mailbox_parse_tests[i];
160 		ret = smtp_address_parse_mailbox(pool_datastack_create(),
161 						 test->input, test->flags,
162 						 &address, &error);
163 
164 		test_begin(t_strdup_printf("smtp mailbox valid [%d]", i));
165 		test_out_reason(t_strdup_printf("parse(\"%s\")", test->input),
166 				ret == 0, error);
167 
168 		if (!test_has_failed()) {
169 			test_smtp_mailbox_equal(&test->address, address);
170 
171 			encoded = smtp_address_encode(address);
172 			output = (test->output == NULL ?
173 				  test->input : test->output);
174 			test_out(t_strdup_printf("encode() = \"%s\"", encoded),
175 				 strcmp(encoded, output) == 0);
176 		}
177 		test_end();
178 	} T_END;
179 }
180 
181 /*
182  * Valid path parse tests
183  */
184 
185 struct valid_path_parse_test {
186 	const char *input, *output;
187 	enum smtp_address_parse_flags flags;
188 
189 	struct smtp_address address;
190 };
191 
192 static const struct valid_path_parse_test
193 valid_path_parse_tests[] = {
194 	{
195 		.input = "<>",
196 		.flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY,
197 		.address = { .localpart = NULL, .domain = NULL }
198 	},
199 	{
200 		.input = "<user>",
201 		.flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART,
202 		.address = { .localpart = "user", .domain = NULL }
203 	},
204 	{
205 		.input = "<user@domain.tld>",
206 		.address = { .localpart = "user", .domain = "domain.tld" }
207 	},
208 	{
209 		.input = "<@otherdomain.tld,@yetanotherdomain.tld:user@domain.tld>",
210 		.address = { .localpart = "user", .domain = "domain.tld" },
211 		.output = "<user@domain.tld>"
212 	},
213 	{
214 		.input = "user@domain.tld",
215 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
216 		.address = { .localpart = "user", .domain = "domain.tld" },
217 		.output = "<user@domain.tld>"
218 	},
219 	/* Raw */
220 	{
221 		.input = "<>",
222 		.flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY |
223 			 SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW,
224 		.address = { .localpart = NULL, .domain = NULL, .raw = NULL }
225 	},
226 	{
227 		.input = "<user>",
228 		.flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
229 			 SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW,
230 		.address = { .localpart = "user", .domain = NULL,
231 			     .raw = "user" }
232 	},
233 	{
234 		.input = "<user@domain.tld>",
235 		.flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW,
236 		.address = { .localpart = "user", .domain = "domain.tld",
237 			     .raw = "user@domain.tld" }
238 	},
239 	{
240 		.input = "<@otherdomain.tld,@yetanotherdomain.tld:user@domain.tld>",
241 		.flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW,
242 		.address = { .localpart = "user", .domain = "domain.tld",
243 			     .raw = "@otherdomain.tld,@yetanotherdomain.tld:"
244 				    "user@domain.tld" },
245 		.output = "<user@domain.tld>"
246 	},
247 	{
248 		.input = "user@domain.tld",
249 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL |
250 			 SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW,
251 		.address = { .localpart = "user", .domain = "domain.tld",
252 			     .raw = "user@domain.tld"},
253 		.output = "<user@domain.tld>"
254 	},
255 	/* Broken */
256 	{
257 		.input = "<>",
258 		.flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY |
259 			 SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
260 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
261 		.address = { .localpart = NULL, .domain = NULL, .raw = NULL }
262 	},
263 	{
264 		.input = "<user>",
265 		.flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
266 			 SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
267 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
268 		.address = { .localpart = "user", .domain = NULL,
269 			     .raw = "user" }
270 	},
271 	{
272 		.input = "<user@domain.tld>",
273 		.flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
274 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
275 		.address = { .localpart = "user", .domain = "domain.tld",
276 			     .raw = "user@domain.tld" }
277 	},
278 	{
279 		.input = "<@otherdomain.tld,@yetanotherdomain.tld:user@domain.tld>",
280 		.flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
281 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
282 		.address = { .localpart = "user", .domain = "domain.tld",
283 			     .raw = "@otherdomain.tld,@yetanotherdomain.tld:"
284 				    "user@domain.tld" },
285 		.output = "<user@domain.tld>"
286 	},
287 	{
288 		.input = "user@domain.tld",
289 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL |
290 			 SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
291 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
292 		.address = { .localpart = "user", .domain = "domain.tld",
293 			     .raw = "user@domain.tld"},
294 		.output = "<user@domain.tld>"
295 	},
296 	{
297 		.input = "u\"ser",
298 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL |
299 			 SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
300 			 SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
301 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
302 		.address = { .localpart = NULL, .domain = NULL,
303 			     .raw = "u\"ser" },
304 		.output = "<>",
305 	},
306 	{
307 		.input = "user\"@domain.tld",
308 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL |
309 			 SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
310 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
311 		.address = { .localpart = NULL, .domain = NULL,
312 			     .raw = "user\"@domain.tld" },
313 		.output = "<>",
314 	},
315 	{
316 		.input = "<u\"ser>",
317 		.flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
318 			 SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
319 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
320 		.address = { .localpart = NULL, .domain = NULL,
321 			     .raw = "u\"ser" },
322 		.output = "<>",
323 	},
324 	{
325 		.input = "<user\"@domain.tld>",
326 		.flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
327 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
328 		.address = { .localpart = NULL, .domain = NULL,
329 			     .raw = "user\"@domain.tld" },
330 		.output = "<>",
331 	},
332 	{
333 		.input = "bla$die%bla@die&bla",
334 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL |
335 			 SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
336 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
337 		.address = { .localpart = NULL, .domain = NULL,
338 			     .raw = "bla$die%bla@die&bla" },
339 		.output = "<>",
340 	},
341 	{
342 		.input = "/@)$@)BLAARGH!@#$$",
343 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL |
344 			 SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
345 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
346 		.address = { .localpart = NULL, .domain = NULL,
347 			     .raw = "/@)$@)BLAARGH!@#$$" },
348 		.output = "<>",
349 	},
350 	{
351 		.input = "</@)$@)BLAARGH!@#$$>",
352 		.flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
353 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
354 		.address = { .localpart = NULL, .domain = NULL,
355 			     .raw = "/@)$@)BLAARGH!@#$$" },
356 		.output = "<>",
357 	},
358 	{
359 		.input = "/@)$@)BLAARGH!@#$$",
360 		.flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
361 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN  |
362 			 SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART |
363 			 SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
364 		.address = { .localpart = NULL, .domain = NULL,
365 			     .raw = "/@)$@)BLAARGH!@#$$" },
366 		.output = "<>",
367 	},
368 	{
369 		.input = "</@)$@)BLAARGH!@#$$>",
370 		.flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
371 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN |
372 			 SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART |
373 			 SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
374 		.address = { .localpart = NULL, .domain = NULL,
375 			     .raw = "/@)$@)BLAARGH!@#$$" },
376 		.output = "<>",
377 	},
378 	{
379 		.input = "f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4",
380 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL |
381 			 SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
382 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN |
383 			 SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART |
384 			 SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
385 		.address = { .localpart = NULL, .domain = NULL,
386 			     .raw = "f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4" },
387 		.output = "<>",
388 	},
389 	{
390 		.input = "<f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4>",
391 		.flags = SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW |
392 			 SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN |
393 			 SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART |
394 			 SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
395 		.address = { .localpart = NULL, .domain = NULL,
396 			     .raw = "f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4" },
397 		.output = "<>",
398 	},
399 };
400 
401 unsigned int valid_path_parse_test_count =
402 	N_ELEMENTS(valid_path_parse_tests);
403 
404 static void
test_smtp_path_equal(const struct smtp_address * test,const struct smtp_address * parsed)405 test_smtp_path_equal(const struct smtp_address *test,
406 		     const struct smtp_address *parsed)
407 {
408 	if (smtp_address_isnull(parsed) || smtp_address_isnull(test)) {
409 		test_out("address = <>",
410 			 (smtp_address_isnull(parsed) &&
411 			  smtp_address_isnull(test)));
412 	} else {
413 		test_out(t_strdup_printf("address->localpart = \"%s\"",
414 					 parsed->localpart),
415 			 null_strcmp(parsed->localpart, test->localpart) == 0);
416 	}
417 	if (smtp_address_isnull(parsed)) {
418 		/* nothing */
419 	} else if (parsed->domain == NULL) {
420 		test_out("address->domain = (null)",
421 			 (parsed->domain == test->domain));
422 	} else {
423 		test_out(t_strdup_printf("address->domain = \"%s\"",
424 					 parsed->domain),
425 			 null_strcmp(parsed->domain, test->domain) == 0);
426 	}
427 	if (parsed == NULL) {
428 		test_out_quiet(t_strdup_printf("address = (null)"),
429 			       (test->raw == NULL));
430 	} else if (parsed->raw == NULL) {
431 		test_out_quiet(t_strdup_printf("address->raw = (null)"),
432 			       (parsed->raw == test->raw));
433 	} else {
434 		test_out_quiet(t_strdup_printf("address->raw = \"%s\"",
435 					 parsed->raw),
436 			       null_strcmp(parsed->raw, test->raw) == 0);
437 	}
438 }
439 
test_smtp_path_parse_valid(void)440 static void test_smtp_path_parse_valid(void)
441 {
442 	unsigned int i;
443 
444 	for (i = 0; i < valid_path_parse_test_count; i++) T_BEGIN {
445 		const struct valid_path_parse_test *test;
446 		bool ignore_broken;
447 		struct smtp_address *address;
448 		const char *error = NULL, *output, *encoded;
449 		int ret;
450 
451 		test = &valid_path_parse_tests[i];
452 		ignore_broken = HAS_ALL_BITS(
453 			test->flags, SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN);
454 		ret = smtp_address_parse_path(pool_datastack_create(),
455 					      test->input, test->flags,
456 					      &address, &error);
457 
458 		test_begin(t_strdup_printf("smtp path valid [%d]", i));
459 		test_out_reason(t_strdup_printf("parse(\"%s\")", test->input),
460 				(ret == 0 || ignore_broken), error);
461 
462 		if (!test_has_failed()) {
463 			test_smtp_path_equal(&test->address, address);
464 
465 			encoded = smtp_address_encode_path(address);
466 			output = (test->output == NULL ?
467 				  test->input : test->output);
468 			test_out(t_strdup_printf("encode() = \"%s\"", encoded),
469 				 strcmp(encoded, output) == 0);
470 		}
471 		test_end();
472 	} T_END;
473 }
474 
475 /*
476  * Valid username parse tests
477  */
478 
479 struct valid_username_parse_test {
480 	const char *input, *output;
481 
482 	struct smtp_address address;
483 };
484 
485 static const struct valid_username_parse_test
486 valid_username_parse_tests[] = {
487 	{
488 		.input = "user",
489 		.address = {
490 			.localpart = "user",
491 			.domain = NULL },
492 	},
493 	{
494 		.input = "user@domain.tld",
495 		.address = {
496 			.localpart = "user",
497 			.domain = "domain.tld" },
498 	},
499 	{
500 		.input = "user@domain.tld",
501 		.address = {
502 			.localpart = "user",
503 			.domain = "domain.tld" },
504 	},
505 	{
506 		.input = "1234567890@domain.tld",
507 		.address = {
508 			.localpart = "1234567890",
509 			.domain = "domain.tld" },
510 	},
511 	{
512 		.input = "_______@domain.tld",
513 		.address = {
514 			.localpart = "_______",
515 			.domain = "domain.tld" },
516 	},
517 	{
518 		.input = "firstname.lastname@domain.tld",
519 		.address = {
520 			.localpart = "firstname.lastname",
521 			.domain = "domain.tld" },
522 	},
523 	{
524 		.input = "firstname+lastname@domain.tld",
525 		.address = {
526 			.localpart = "firstname+lastname",
527 			.domain = "domain.tld" },
528 	},
529 	{
530 		.input = "firstname-lastname@domain.tld",
531 		.address = {
532 			.localpart = "firstname-lastname",
533 			.domain = "domain.tld" },
534 	},
535 	{
536 		.input = "\"user\"@domain.tld",
537 		.address = { .localpart = "user", .domain = "domain.tld" },
538 		.output = "user@domain.tld"
539 	},
540 	{
541 		.input = "\"user@frop\"@domain.tld",
542 		.address = { .localpart = "user@frop", .domain = "domain.tld" },
543 		.output = "\"user@frop\"@domain.tld"
544 	},
545 	{
546 		.input = "user@frop@domain.tld",
547 		.address = { .localpart = "user@frop", .domain = "domain.tld" },
548 		.output = "\"user@frop\"@domain.tld"
549 	},
550 	{
551 		.input = "user frop@domain.tld",
552 		.address = { .localpart = "user frop", .domain = "domain.tld" },
553 		.output = "\"user frop\"@domain.tld"
554 	},
555 	{
556 		.input = "user\"frop@domain.tld",
557 		.address = { .localpart = "user\"frop", .domain = "domain.tld" },
558 		.output = "\"user\\\"frop\"@domain.tld"
559 	},
560 	{
561 		.input = "user\\frop@domain.tld",
562 		.address = { .localpart = "user\\frop", .domain = "domain.tld" },
563 		.output = "\"user\\\\frop\"@domain.tld"
564 	},
565 	{
566 		.input = "user@127.0.0.1",
567 		.address = { .localpart = "user", .domain = "127.0.0.1" },
568 	},
569 	{
570 		.input = "user@[127.0.0.1]",
571 		.address = { .localpart = "user", .domain = "[127.0.0.1]" },
572 	},
573 	{
574 		.input = "user@[IPv6:::1]",
575 		.address = { .localpart = "user", .domain = "[IPv6:::1]" },
576 	},
577 	{
578 		.input = "user@[IPv6:::127.0.0.1]",
579 		.address = { .localpart = "user", .domain = "[IPv6:::127.0.0.1]" },
580 	},
581 };
582 
583 unsigned int valid_username_parse_test_count =
584 	N_ELEMENTS(valid_username_parse_tests);
585 
test_smtp_username_parse_valid(void)586 static void test_smtp_username_parse_valid(void)
587 {
588 	unsigned int i;
589 
590 	for (i = 0; i < valid_username_parse_test_count; i++) T_BEGIN {
591 		const struct valid_username_parse_test *test;
592 		struct smtp_address *address;
593 		const char *error = NULL, *output, *encoded;
594 		int ret;
595 
596 		test = &valid_username_parse_tests[i];
597 		ret = smtp_address_parse_username(pool_datastack_create(),
598 						  test->input,
599 						  &address, &error);
600 
601 		test_begin(t_strdup_printf("smtp username valid [%d]", i));
602 		test_out_reason(t_strdup_printf("parse(\"%s\")", test->input),
603 				ret == 0, error);
604 
605 		if (!test_has_failed()) {
606 			test_smtp_path_equal(&test->address, address);
607 
608 			encoded = smtp_address_encode(address);
609 			output = (test->output == NULL ?
610 				  test->input : test->output);
611 			test_out(t_strdup_printf("encode() = \"%s\"", encoded),
612 				 strcmp(encoded, output) == 0);
613 		}
614 		test_end();
615 	} T_END;
616 }
617 
618 /*
619  * Invalid mailbox parse tests
620  */
621 
622 struct invalid_mailbox_parse_test {
623 	const char *input;
624 	enum smtp_address_parse_flags flags;
625 };
626 
627 static const struct invalid_mailbox_parse_test
628 invalid_mailbox_parse_tests[] = {
629 	{
630 		.input = "",
631 	},
632 	{
633 		.input = "user",
634 	},
635 	{
636 		.input = "\"user@domain.tld",
637 	},
638 	{
639 		.input = "us\"er@domain.tld",
640 	},
641 	{
642 		.input = "user@frop@domain.tld",
643 	},
644 	{
645 		.input = "user@.tld",
646 	},
647 	{
648 		.input = "user@a$.tld",
649 	},
650 	{
651 		.input = "user@a..tld",
652 	},
653 	{
654 		.input = "user@[]",
655 	},
656 	{
657 		.input = "user@[",
658 	},
659 	{
660 		.input = "user@[AA]",
661 	},
662 	{
663 		.input = "user@[AA",
664 	},
665 	{
666 		.input = "user@[127.0.0]",
667 	},
668 	{
669 		.input = "user@[256.256.256.256]",
670 	},
671 	{
672 		.input = "user@[127.0.0.1",
673 	},
674 	{
675 		.input = "user@[::1]",
676 	},
677 	{
678 		.input = "user@[IPv6:flierp]",
679 	},
680 	{
681 		.input = "user@[IPv6:aa:bb::cc::dd]",
682 	},
683 	{
684 		.input = "user@[IPv6::1]",
685 	},
686 	{
687 		.input = "user@[IPv6:::1",
688 	},
689 	{
690 		.input = "user@[Gen:]",
691 	},
692 	{
693 		.input = "user@[Gen:Hopsa",
694 	},
695 	{
696 		.input = "user@[Gen-:Hopsa]",
697 	},
698 	{
699 		.input = "#@%^%#$@#$@#.com",
700 	},
701 	{
702 		.input = "@example.com",
703 	},
704 	{
705 		.input = "Eric Mail <email@example.com>",
706 	},
707 	{
708 		.input = "email.example.com",
709 	},
710 	{
711 		.input = "email@example@example.com",
712 	},
713 	{
714 		.input = "あいうえお@example.com",
715 	},
716 	{
717 		.input = "email@example.com (Eric Mail)",
718 	},
719 	{
720 		.input = "email@example..com",
721 #if 0 /* These deviations are allowed (maybe implement strict mode) */
722 	},
723 	{
724 		.input = "email@-example.com",
725 	},
726 	{
727 		.input = ".email@example.com",
728 	},
729 	{
730 		.input = "email.@example.com",
731 	},
732 	{
733 		.input = "email..email@example.com",
734 	},
735 	{
736 		.input = "Abc..123@example.com"
737 #endif
738 	},
739 };
740 
741 unsigned int invalid_mailbox_parse_test_count =
742 	N_ELEMENTS(invalid_mailbox_parse_tests);
743 
test_smtp_mailbox_parse_invalid(void)744 static void test_smtp_mailbox_parse_invalid(void)
745 {
746 	unsigned int i;
747 
748 	for (i = 0; i < invalid_mailbox_parse_test_count; i++) T_BEGIN {
749 		const struct invalid_mailbox_parse_test *test;
750 		struct smtp_address *address;
751 		const char *error = NULL;
752 		int ret;
753 
754 		test = &invalid_mailbox_parse_tests[i];
755 		ret = smtp_address_parse_mailbox(pool_datastack_create(),
756 						 test->input, test->flags,
757 						 &address, &error);
758 
759 		test_begin(t_strdup_printf("smtp mailbox invalid [%d]", i));
760 		test_out_reason(t_strdup_printf("parse(\"%s\")", test->input),
761 				ret < 0, error);
762 		test_end();
763 	} T_END;
764 }
765 
766 /*
767  * Invalid path parse tests
768  */
769 
770 struct invalid_path_parse_test {
771 	const char *input;
772 	enum smtp_address_parse_flags flags;
773 };
774 
775 static const struct invalid_path_parse_test
776 invalid_path_parse_tests[] = {
777 	{
778 		.input = "",
779 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
780 	},
781 	{
782 		.input = "user",
783 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
784 	},
785 	{
786 		.input = "\"user@domain.tld",
787 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
788 	},
789 	{
790 		.input = "us\"er@domain.tld",
791 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
792 	},
793 	{
794 		.input = "user@frop@domain.tld",
795 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
796 	},
797 	{
798 		.input = "user@.tld",
799 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
800 	},
801 	{
802 		.input = "user@a$.tld",
803 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
804 	},
805 	{
806 		.input = "user@a..tld",
807 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
808 	},
809 	{
810 		.input = "user@[]",
811 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
812 	},
813 	{
814 		.input = "user@[",
815 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
816 	},
817 	{
818 		.input = "user@[AA]",
819 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
820 	},
821 	{
822 		.input = "user@[AA",
823 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
824 	},
825 	{
826 		.input = "user@[127.0.0]",
827 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
828 	},
829 	{
830 		.input = "user@[256.256.256.256]",
831 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
832 	},
833 	{
834 		.input = "user@[127.0.0.1",
835 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
836 	},
837 	{
838 		.input = "user@[::1]",
839 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
840 	},
841 	{
842 		.input = "user@[IPv6:flierp]",
843 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
844 	},
845 	{
846 		.input = "user@[IPv6:aa:bb::cc::dd]",
847 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
848 	},
849 	{
850 		.input = "user@[IPv6::1]",
851 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
852 	},
853 	{
854 		.input = "user@[IPv6:::1",
855 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
856 	},
857 	{
858 		.input = "user@[Gen:]",
859 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
860 	},
861 	{
862 		.input = "user@[Gen:Hopsa",
863 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
864 	},
865 	{
866 		.input = "user@[Gen-:Hopsa]",
867 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
868 	},
869 	{
870 		.input = "#@%^%#$@#$@#.com",
871 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
872 	},
873 	{
874 		.input = "@example.com",
875 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
876 	},
877 	{
878 		.input = "Eric Mail <email@example.com>",
879 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
880 	},
881 	{
882 		.input = "email.example.com",
883 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
884 	},
885 	{
886 		.input = "email@example@example.com",
887 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
888 	},
889 	{
890 		.input = "あいうえお@example.com",
891 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
892 	},
893 	{
894 		.input = "email@example.com (Eric Mail)",
895 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
896 	},
897 	{
898 		.input = "email@example..com",
899 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
900 	},
901 	{
902 		.input = "@otherdomain.tld,@yetanotherdomain.tld:user@domain.tld",
903 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
904 	},
905 	{
906 		.input = "user@domain.tld",
907 		.flags = SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
908 	},
909 	{
910 		.input = "<>",
911 	},
912 	{
913 		.input = "<user>",
914 	},
915 	{
916 		.input = "<\"user@domain.tld>",
917 	},
918 	{
919 		.input = "<us\"er@domain.tld>",
920 	},
921 	{
922 		.input = "<user@frop@domain.tld>",
923 	},
924 	{
925 		.input = "<user@.tld>",
926 	},
927 	{
928 		.input = "<user@a$.tld>",
929 	},
930 	{
931 		.input = "<user@a..tld>",
932 	},
933 	{
934 		.input = "<user@[]>",
935 	},
936 	{
937 		.input = "<user@[>",
938 	},
939 	{
940 		.input = "<user@[AA]>",
941 	},
942 	{
943 		.input = "<user@[AA>",
944 	},
945 	{
946 		.input = "<user@[127.0.0]>",
947 	},
948 	{
949 		.input = "<user@[256.256.256.256]>",
950 	},
951 	{
952 		.input = "<user@[127.0.0.1>",
953 	},
954 	{
955 		.input = "<user@[::1]>",
956 	},
957 	{
958 		.input = "<user@[IPv6:flierp]>",
959 	},
960 	{
961 		.input = "<user@[IPv6:aa:bb::cc::dd]>",
962 	},
963 	{
964 		.input = "<user@[IPv6::1]>",
965 	},
966 	{
967 		.input = "<user@[IPv6:::1>",
968 	},
969 	{
970 		.input = "<user@[Gen:]>",
971 	},
972 	{
973 		.input = "<user@[Gen:Hopsa>",
974 	},
975 	{
976 		.input = "<user@[Gen-:Hopsa]>",
977 	},
978 	{
979 		.input = "<#@%^%#$@#$@#.com>",
980 	},
981 	{
982 		.input = "<@example.com>",
983 	},
984 	{
985 		.input = "Eric Mail <email@example.com>",
986 	},
987 	{
988 		.input = "<email.example.com>",
989 	},
990 	{
991 		.input = "<email@example@example.com>",
992 	},
993 	{
994 		.input = "<あいうえお@example.com>",
995 	},
996 	{
997 		.input = "<email@example.com> (Eric Mail)",
998 	},
999 	{
1000 		.input = "<email@example..com>",
1001 	},
1002 	{
1003 		.input = "<email@example.com",
1004 	},
1005 	{
1006 		.input = "email@example.com>",
1007 	},
1008 	{
1009 		.input = "email@example.com>",
1010 		.flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
1011 	},
1012 	{
1013 		.input = "<",
1014 		.flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY,
1015 	},
1016 	{
1017 		.input = "<user",
1018 		.flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART,
1019 	},
1020 	{
1021 		.input = "<@otherdomain.tld,@yetanotherdomain.tld.user@domain.tld>",
1022 	},
1023 	{
1024 		.input = "<@###domain.tld,@yetanotherdomain.tld.user@domain.tld>",
1025 	},
1026 	{
1027 		.input = "f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4",
1028 		.flags = SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN,
1029 	}
1030 };
1031 
1032 unsigned int invalid_path_parse_test_count =
1033 	N_ELEMENTS(invalid_path_parse_tests);
1034 
test_smtp_path_parse_invalid(void)1035 static void test_smtp_path_parse_invalid(void)
1036 {
1037 	unsigned int i;
1038 
1039 	for (i = 0; i < invalid_path_parse_test_count; i++) T_BEGIN {
1040 		const struct invalid_path_parse_test *test;
1041 		struct smtp_address *address;
1042 		const char *error = NULL;
1043 		int ret;
1044 
1045 		test = &invalid_path_parse_tests[i];
1046 		ret = smtp_address_parse_path(pool_datastack_create(),
1047 					      test->input, test->flags,
1048 					      &address, &error);
1049 
1050 		test_begin(t_strdup_printf("smtp path invalid [%d]", i));
1051 		test_out_reason(t_strdup_printf("parse(\"%s\")", test->input),
1052 				(ret < 0 && !smtp_address_is_broken(address)),
1053 				error);
1054 		test_end();
1055 	} T_END;
1056 }
1057 
1058 /*
1059  * Invalid username parse tests
1060  */
1061 
1062 struct invalid_username_parse_test {
1063 	const char *input;
1064 	enum smtp_address_parse_flags flags;
1065 };
1066 
1067 static const struct invalid_username_parse_test
1068 invalid_username_parse_tests[] = {
1069 	{
1070 		.input = "frop@$%^$%^.tld",
1071 	},
1072 	{
1073 		.input = "fr\top@domain.tld",
1074 	}
1075 };
1076 
1077 unsigned int invalid_username_parse_test_count =
1078 	N_ELEMENTS(invalid_username_parse_tests);
1079 
test_smtp_username_parse_invalid(void)1080 static void test_smtp_username_parse_invalid(void)
1081 {
1082 	unsigned int i;
1083 
1084 	for (i = 0; i < invalid_username_parse_test_count; i++) T_BEGIN {
1085 		const struct invalid_username_parse_test *test;
1086 		struct smtp_address *address;
1087 		const char *error = NULL;
1088 		int ret;
1089 
1090 		test = &invalid_username_parse_tests[i];
1091 		ret = smtp_address_parse_username(pool_datastack_create(),
1092 						  test->input,
1093 						  &address, &error);
1094 
1095 		test_begin(t_strdup_printf("smtp username invalid [%d]", i));
1096 		test_out_reason(t_strdup_printf("parse(\"%s\")", test->input),
1097 				ret < 0, error);
1098 		test_end();
1099 	} T_END;
1100 }
1101 
1102 /*
1103  * Address detail parsing
1104  */
1105 
1106 struct address_detail_parse_test {
1107 	const char *delimiters;
1108 	const char *address;
1109 	const char *username;
1110 	const char *detail;
1111 	char delim;
1112 };
1113 
1114 static const struct address_detail_parse_test
1115 address_detail_parse_tests[] = {
1116 	{ "", "test", "test", "", '\0' },
1117 	{ "", "test+address", "test+address", "", '\0' },
1118 	{ "", "\"test:address\"", "test:address", "", '\0' },
1119 	{ "", "\"test-address:another+delim\"", "test-address:another+delim",
1120 	  "", '\0' },
1121 	{ "", "test@domain", "test@domain", "", '\0' },
1122 	{ "", "test+address@domain", "test+address@domain", "", '\0' },
1123 	{ "", "\"test:address\"@domain", "test:address@domain", "", '\0' },
1124 	{ "", "\"test-address:another+delim\"@domain",
1125 	  "test-address:another+delim@domain", "", '\0' },
1126 
1127 	{ "+-:", "test", "test", "", '\0' },
1128 	{ "+-:", "test+address", "test", "address", '+' },
1129 	{ "+-:", "\"test:address\"", "test", "address", ':' },
1130 	{ "+-:", "\"test-address:another+delim\"",
1131 	  "test", "address:another+delim", '-' },
1132 	{ "+-:", "test@domain", "test@domain", "", '\0' },
1133 	{ "+-:", "test+address@domain", "test@domain", "address", '+' },
1134 	{ "+-:", "\"test:address\"@domain", "test@domain", "address", ':' },
1135 	{ "+-:", "\"test-address:another+delim\"@domain", "test@domain",
1136 	  "address:another+delim", '-' },
1137 };
1138 
1139 unsigned int addresss_detail_parse_test_count =
1140 	N_ELEMENTS(address_detail_parse_tests);
1141 
test_smtp_address_detail_parse(void)1142 static void test_smtp_address_detail_parse(void)
1143 {
1144 	unsigned int i;
1145 
1146 
1147 	for (i = 0; i < N_ELEMENTS(address_detail_parse_tests); i++) T_BEGIN {
1148 		const struct address_detail_parse_test *test =
1149 			&address_detail_parse_tests[i];
1150 		struct smtp_address *address;
1151 		const char *username, *detail, *error;
1152 		char delim;
1153 		int ret;
1154 
1155 		test_begin(t_strdup_printf(
1156 			"smtp address detail parsing [%d]", i));
1157 
1158 		ret = smtp_address_parse_path(
1159 			pool_datastack_create(), test->address,
1160 			SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
1161 			SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
1162 			&address, &error);
1163 		test_out_reason("address parse", ret == 0, error);
1164 
1165 		if (!test_has_failed()) {
1166 			smtp_address_detail_parse_temp(test->delimiters,
1167 						       address, &username,
1168 						       &delim, &detail);
1169 			test_assert(strcmp(username, test->username) == 0);
1170 			test_assert(strcmp(detail, test->detail) == 0);
1171 			test_assert(delim == test->delim);
1172 		}
1173 
1174 		test_end();
1175 	} T_END;
1176 }
1177 
1178 /*
1179  * Skip address tests
1180  */
1181 
1182 struct any_address_parse_test {
1183 	const char *input;
1184 	const char *address;
1185 	size_t pos;
1186 	int ret;
1187 };
1188 
1189 static const struct any_address_parse_test
1190 any_address_parse_tests[] = {
1191 	{
1192 		.input = "",
1193 		.address = "",
1194 		.pos = 0,
1195 		.ret = 0,
1196 	},
1197 	{
1198 		.input = " ",
1199 		.address = "",
1200 		.pos = 0,
1201 		.ret = 0,
1202 	},
1203 	{
1204 		.input = "frop@example.com",
1205 		.address = "frop@example.com",
1206 		.pos = 16,
1207 		.ret = 0,
1208 	},
1209 	{
1210 		.input = "frop@example.com ",
1211 		.address = "frop@example.com",
1212 		.pos = 16,
1213 		.ret = 0,
1214 	},
1215 	{
1216 		.input = "<frop@example.com>",
1217 		.address = "frop@example.com",
1218 		.pos = 18,
1219 		.ret = 0,
1220 	},
1221 	{
1222 		.input = "<frop@example.com> ",
1223 		.address = "frop@example.com",
1224 		.pos = 18,
1225 		.ret = 0,
1226 	},
1227 	{
1228 		.input = "<frop@example.com",
1229 		.pos = 0,
1230 		.ret = -1,
1231 	},
1232 	{
1233 		.input = "<frop@example.com ",
1234 		.pos = 0,
1235 		.ret = -1,
1236 	},
1237 	{
1238 		.input = "fr\"op@example.com",
1239 		.address = "fr\"op@example.com",
1240 		.pos = 17,
1241 		.ret = 0,
1242 	},
1243 	{
1244 		.input = "fr\"op@example.com ",
1245 		.address = "fr\"op@example.com",
1246 		.pos = 17,
1247 		.ret = 0,
1248 	},
1249 	{
1250 		.input = "fr<op@example.com",
1251 		.address = "fr<op@example.com",
1252 		.pos = 17,
1253 		.ret = 0,
1254 	},
1255 	{
1256 		.input = "fr<op@example.com ",
1257 		.address = "fr<op@example.com",
1258 		.pos = 17,
1259 		.ret = 0,
1260 	},
1261 	{
1262 		.input = "\"frop\"@example.com",
1263 		.address = "\"frop\"@example.com",
1264 		.pos = 18,
1265 		.ret = 0,
1266 	},
1267 	{
1268 		.input = "\"frop\"@example.com ",
1269 		.address = "\"frop\"@example.com",
1270 		.pos = 18,
1271 		.ret = 0,
1272 	},
1273 	{
1274 		.input = "\"frop\\\"@example.com",
1275 		.pos = 0,
1276 		.ret = -1,
1277 	},
1278 	{
1279 		.input = "\"frop\\\"@example.com ",
1280 		.pos = 0,
1281 		.ret = -1,
1282 	},
1283 	{
1284 		.input = "<\"fr>op\"@example.com>",
1285 		.address = "\"fr>op\"@example.com",
1286 		.pos = 21,
1287 		.ret = 0,
1288 	},
1289 	{
1290 		.input = "<\"fr>op\"@example.com> ",
1291 		.address = "\"fr>op\"@example.com",
1292 		.pos = 21,
1293 		.ret = 0,
1294 	},
1295 	{
1296 		.input = "<\"fr>op\"@example.com",
1297 		.pos = 0,
1298 		.ret = -1,
1299 	},
1300 	{
1301 		.input = "<\"fr>op\"@example.com ",
1302 		.pos = 0,
1303 		.ret = -1,
1304 	},
1305 	{
1306 		.input = "<\"frop\">",
1307 		.address = "\"frop\"",
1308 		.pos = 8,
1309 		.ret = 0,
1310 	},
1311 	{
1312 		.input = "<\"frop\"> ",
1313 		.address = "\"frop\"",
1314 		.pos = 8,
1315 		.ret = 0,
1316 	},
1317 	{
1318 		.input = "<\"frop\"",
1319 		.pos = 0,
1320 		.ret = -1,
1321 	},
1322 	{
1323 		.input = "<\"frop\" ",
1324 		.pos = 0,
1325 		.ret = -1,
1326 	},
1327 	{
1328 		.input = "\"frop\\\" ",
1329 		.pos = 0,
1330 		.ret = -1,
1331 	},
1332 	{
1333 		.input = "\"frop\\\"",
1334 		.pos = 0,
1335 		.ret = -1,
1336 	},
1337 };
1338 
1339 unsigned int any_address_parse_tests_count =
1340 	N_ELEMENTS(any_address_parse_tests);
1341 
test_smtp_parse_any_address(void)1342 static void test_smtp_parse_any_address(void)
1343 {
1344 	unsigned int i;
1345 
1346 	for (i = 0; i < any_address_parse_tests_count; i++) T_BEGIN {
1347 		const struct any_address_parse_test *test;
1348 		const char *address = NULL, *pos = NULL;
1349 		int ret;
1350 
1351 		test = &any_address_parse_tests[i];
1352 		ret = smtp_address_parse_any(test->input, &address, &pos);
1353 
1354 		test_begin(t_strdup_printf("smtp parse any [%d]", i));
1355 		test_out_quiet(t_strdup_printf("parse(\"%s\")",
1356 					       str_sanitize(test->input, 256)),
1357 			       (ret == test->ret) &&
1358 			       ((size_t)(pos - test->input) == test->pos) &&
1359 			       (null_strcmp(test->address, address ) == 0));
1360 		test_end();
1361 	} T_END;
1362 }
1363 
1364 /*
1365  * Tests
1366  */
1367 
main(void)1368 int main(void)
1369 {
1370 	static void (*test_functions[])(void) = {
1371 		test_smtp_mailbox_parse_valid,
1372 		test_smtp_path_parse_valid,
1373 		test_smtp_username_parse_valid,
1374 		test_smtp_mailbox_parse_invalid,
1375 		test_smtp_path_parse_invalid,
1376 		test_smtp_username_parse_invalid,
1377 		test_smtp_address_detail_parse,
1378 		test_smtp_parse_any_address,
1379 		NULL
1380 	};
1381 	return test_run(test_functions);
1382 }
1383