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