1 /* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "str.h"
5 #include "message-address.h"
6 #include "test-common.h"
7 
8 enum test_message_address {
9 	TEST_MESSAGE_ADDRESS_FLAG_SKIP_LIST = BIT(0),
10 };
11 
cmp_addr(const struct message_address * a1,const struct message_address * a2)12 static bool cmp_addr(const struct message_address *a1,
13 		     const struct message_address *a2)
14 {
15 	return null_strcmp(a1->name, a2->name) == 0 &&
16 		null_strcmp(a1->route, a2->route) == 0 &&
17 		null_strcmp(a1->mailbox, a2->mailbox) == 0 &&
18 		null_strcmp(a1->domain, a2->domain) == 0 &&
19 		a1->invalid_syntax == a2->invalid_syntax;
20 }
21 
22 static const struct message_address *
test_parse_address(const char * input,bool fill_missing)23 test_parse_address(const char *input, bool fill_missing)
24 {
25 	const enum message_address_parse_flags flags =
26 		fill_missing ? MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING : 0;
27 	/* duplicate the input (without trailing NUL) so valgrind notices
28 	   if there's any out-of-bounds access */
29 	size_t input_len = strlen(input);
30 	unsigned char *input_dup = i_memdup(input, input_len);
31 	const struct message_address *addr =
32 		message_address_parse(pool_datastack_create(),
33 				      input_dup, input_len, UINT_MAX, flags);
34 	i_free(input_dup);
35 	return addr;
36 }
37 
test_message_address(void)38 static void test_message_address(void)
39 {
40 	static const struct test {
41 		const char *input;
42 		const char *wanted_output;
43 		const char *wanted_filled_output;
44 		struct message_address addr;
45 		struct message_address filled_addr;
46 		enum test_message_address flags;
47 	} tests[] = {
48 		/* user@domain -> <user@domain> */
49 		{ "user@domain", "<user@domain>", NULL,
50 		  { NULL, NULL, NULL, "user", "domain", FALSE },
51 		  { NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
52 		{ "\"user\"@domain", "<user@domain>", NULL,
53 		  { NULL, NULL, NULL, "user", "domain", FALSE },
54 		  { NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
55 		{ "\"user name\"@domain", "<\"user name\"@domain>", NULL,
56 		  { NULL, NULL, NULL, "user name", "domain", FALSE },
57 		  { NULL, NULL, NULL, "user name", "domain", FALSE }, 0 },
58 		{ "\"user@na\\\\me\"@domain", "<\"user@na\\\\me\"@domain>", NULL,
59 		  { NULL, NULL, NULL, "user@na\\me", "domain", FALSE },
60 		  { NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 },
61 		{ "\"user\\\"name\"@domain", "<\"user\\\"name\"@domain>", NULL,
62 		  { NULL, NULL, NULL, "user\"name", "domain", FALSE },
63 		  { NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 },
64 		{ "\"\"@domain", "<\"\"@domain>", NULL,
65 		  { NULL, NULL, NULL, "", "domain", FALSE },
66 		  { NULL, NULL, NULL, "", "domain", FALSE }, 0 },
67 		{ "user", "<user>", "<user@MISSING_DOMAIN>",
68 		  { NULL, NULL, NULL, "user", "", TRUE },
69 		  { NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 },
70 		{ "@domain", "<\"\"@domain>", "<MISSING_MAILBOX@domain>",
71 		  { NULL, NULL, NULL, "", "domain", TRUE },
72 		  { NULL, NULL, NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 },
73 
74 		/* Display Name -> Display Name */
75 		{ "Display Name", "\"Display Name\"", "\"Display Name\" <MISSING_MAILBOX@MISSING_DOMAIN>",
76 		  { NULL, "Display Name", NULL, "", "", TRUE },
77 		  { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
78 		{ "\"Display Name\"", "\"Display Name\"", "\"Display Name\" <MISSING_MAILBOX@MISSING_DOMAIN>",
79 		  { NULL, "Display Name", NULL, "", "", TRUE },
80 		  { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
81 		{ "Display \"Name\"", "\"Display Name\"", "\"Display Name\" <MISSING_MAILBOX@MISSING_DOMAIN>",
82 		  { NULL, "Display Name", NULL, "", "", TRUE },
83 		  { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
84 		{ "\"Display\" \"Name\"", "\"Display Name\"", "\"Display Name\" <MISSING_MAILBOX@MISSING_DOMAIN>",
85 		  { NULL, "Display Name", NULL, "", "", TRUE },
86 		  { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
87 		{ "\"\"", "", "<MISSING_MAILBOX@MISSING_DOMAIN>",
88 		  { NULL, "", NULL, "", "", TRUE },
89 		  { NULL, "", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
90 
91 		/* <user@domain> -> <user@domain> */
92 		{ "<user@domain>", NULL, NULL,
93 		  { NULL, NULL, NULL, "user", "domain", FALSE },
94 		  { NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
95 		{ "<\"user\"@domain>", "<user@domain>", NULL,
96 		  { NULL, NULL, NULL, "user", "domain", FALSE },
97 		  { NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
98 		{ "<\"user name\"@domain>", NULL, NULL,
99 		  { NULL, NULL, NULL, "user name", "domain", FALSE },
100 		  { NULL, NULL, NULL, "user name", "domain", FALSE }, 0 },
101 		{ "<\"user@na\\\\me\"@domain>", NULL, NULL,
102 		  { NULL, NULL, NULL, "user@na\\me", "domain", FALSE },
103 		  { NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 },
104 		{ "<\"user\\\"name\"@domain>", NULL, NULL,
105 		  { NULL, NULL, NULL, "user\"name", "domain", FALSE },
106 		  { NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 },
107 		{ "<\"\"@domain>", NULL, NULL,
108 		  { NULL, NULL, NULL, "", "domain", FALSE },
109 		  { NULL, NULL, NULL, "", "domain", FALSE }, 0 },
110 		{ "<user>", NULL, "<user@MISSING_DOMAIN>",
111 		  { NULL, NULL, NULL, "user", "", TRUE },
112 		  { NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 },
113 		{ "<@route>", "<@route:\"\">", "<INVALID_ROUTE:MISSING_MAILBOX@MISSING_DOMAIN>",
114 		  { NULL, NULL, "@route", "", "", TRUE },
115 		  { NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
116 
117 		/* user@domain (Display Name) -> "Display Name" <user@domain> */
118 		{ "user@domain (DisplayName)", "DisplayName <user@domain>", NULL,
119 		  { NULL, "DisplayName", NULL, "user", "domain", FALSE },
120 		  { NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 },
121 		{ "user@domain (Display Name)", "\"Display Name\" <user@domain>", NULL,
122 		  { NULL, "Display Name", NULL, "user", "domain", FALSE },
123 		  { NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 },
124 		{ "user@domain (Display\"Name)", "\"Display\\\"Name\" <user@domain>", NULL,
125 		  { NULL, "Display\"Name", NULL, "user", "domain", FALSE },
126 		  { NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 },
127 		{ "user (Display Name)", "\"Display Name\" <user>", "\"Display Name\" <user@MISSING_DOMAIN>",
128 		  { NULL, "Display Name", NULL, "user", "", TRUE },
129 		  { NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 },
130 		{ "@domain (Display Name)", "\"Display Name\" <\"\"@domain>", "\"Display Name\" <MISSING_MAILBOX@domain>",
131 		  { NULL, "Display Name", NULL, "", "domain", TRUE },
132 		  { NULL, "Display Name", NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 },
133 		{ "user@domain ()", "<user@domain>", NULL,
134 		  { NULL, NULL, NULL, "user", "domain", FALSE },
135 		  { NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
136 
137 		/* Display Name <user@domain> -> "Display Name" <user@domain> */
138 		{ "DisplayName <user@domain>", NULL, NULL,
139 		  { NULL, "DisplayName", NULL, "user", "domain", FALSE },
140 		  { NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 },
141 		{ "Display Name <user@domain>", "\"Display Name\" <user@domain>", NULL,
142 		  { NULL, "Display Name", NULL, "user", "domain", FALSE },
143 		  { NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 },
144 		{ "\"Display Name\" <user@domain>", NULL, NULL,
145 		  { NULL, "Display Name", NULL, "user", "domain", FALSE },
146 		  { NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 },
147 		{ "\"Display\\\"Name\" <user@domain>", NULL, NULL,
148 		  { NULL, "Display\"Name", NULL, "user", "domain", FALSE },
149 		  { NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 },
150 		{ "Display Name <user>", "\"Display Name\" <user>", "\"Display Name\" <user@MISSING_DOMAIN>",
151 		  { NULL, "Display Name", NULL, "user", "", TRUE },
152 		  { NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 },
153 		{ "\"\" <user@domain>", "<user@domain>", NULL,
154 		  { NULL, NULL, NULL, "user", "domain", FALSE },
155 		  { NULL, NULL, NULL, "user", "domain", FALSE }, 0 },
156 
157 		/* <@route:user@domain> -> <@route:user@domain> */
158 		{ "<@route:user@domain>", NULL, NULL,
159 		  { NULL, NULL, "@route", "user", "domain", FALSE },
160 		  { NULL, NULL, "@route", "user", "domain", FALSE }, 0 },
161 		{ "<@route,@route2:user@domain>", NULL, NULL,
162 		  { NULL, NULL, "@route,@route2", "user", "domain", FALSE },
163 		  { NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 },
164 		{ "<@route@route2:user@domain>", "<@route,@route2:user@domain>", NULL,
165 		  { NULL, NULL, "@route,@route2", "user", "domain", FALSE },
166 		  { NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 },
167 		{ "<@route@route2:user>", "<@route,@route2:user>", "<@route,@route2:user@MISSING_DOMAIN>",
168 		  { NULL, NULL, "@route,@route2", "user", "", TRUE },
169 		  { NULL, NULL, "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 },
170 		{ "<@route@route2:\"\"@domain>", "<@route,@route2:\"\"@domain>", NULL,
171 		  { NULL, NULL, "@route,@route2", "", "domain", FALSE },
172 		  { NULL, NULL, "@route,@route2", "", "domain", FALSE }, 0 },
173 
174 		/* Display Name <@route:user@domain> ->
175 		   "Display Name" <@route:user@domain> */
176 		{ "Display Name <@route:user@domain>", "\"Display Name\" <@route:user@domain>", NULL,
177 		  { NULL, "Display Name", "@route", "user", "domain", FALSE },
178 		  { NULL, "Display Name", "@route", "user", "domain", FALSE }, 0 },
179 		{ "Display Name <@route,@route2:user@domain>", "\"Display Name\" <@route,@route2:user@domain>", NULL,
180 		  { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE },
181 		  { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 },
182 		{ "Display Name <@route@route2:user@domain>", "\"Display Name\" <@route,@route2:user@domain>", NULL,
183 		  { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE },
184 		  { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 },
185 		{ "Display Name <@route@route2:user>", "\"Display Name\" <@route,@route2:user>", "\"Display Name\" <@route,@route2:user@MISSING_DOMAIN>",
186 		  { NULL, "Display Name", "@route,@route2", "user", "", TRUE },
187 		  { NULL, "Display Name", "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 },
188 		{ "Display Name <@route@route2:\"\"@domain>", "\"Display Name\" <@route,@route2:\"\"@domain>", NULL,
189 		  { NULL, "Display Name", "@route,@route2", "", "domain", FALSE },
190 		  { NULL, "Display Name", "@route,@route2", "", "domain", FALSE }, 0 },
191 
192 		/* other tests: */
193 		{ "\"foo: <a@b>;,\" <user@domain>", NULL, NULL,
194 		  { NULL, "foo: <a@b>;,", NULL, "user", "domain", FALSE },
195 		  { NULL, "foo: <a@b>;,", NULL, "user", "domain", FALSE }, 0 },
196 		{ "<>", "", "<MISSING_MAILBOX@MISSING_DOMAIN>",
197 		  { NULL, NULL, NULL, "", "", TRUE },
198 		  { NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
199 		{ "<@>", "", "<INVALID_ROUTE:MISSING_MAILBOX@MISSING_DOMAIN>",
200 		  { NULL, NULL, NULL, "", "", TRUE },
201 		  { NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 },
202 
203 		/* Test against a out-of-bounds read bug - keep these two tests
204 		   together in this same order: */
205 		{ "aaaa@", "<aaaa>", "<aaaa@MISSING_DOMAIN>",
206 		  { NULL, NULL, NULL, "aaaa", "", TRUE },
207 		  { NULL, NULL, NULL, "aaaa", "MISSING_DOMAIN", TRUE }, 0 },
208 		{ "a(aa", "", "<MISSING_MAILBOX@MISSING_DOMAIN>",
209 		  { NULL, NULL, NULL, "", "", TRUE },
210 		  { NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE },
211 		  TEST_MESSAGE_ADDRESS_FLAG_SKIP_LIST },
212 	};
213 	static struct message_address group_prefix = {
214 		NULL, NULL, NULL, "group", NULL, FALSE
215 	};
216 	static struct message_address group_suffix = {
217 		NULL, NULL, NULL, NULL, NULL, FALSE
218 	};
219 	const struct message_address *addr;
220 	string_t *str, *group;
221 	const char *wanted_string;
222 	unsigned int i;
223 
224 	test_begin("message address parsing");
225 	str = t_str_new(128);
226 	group = t_str_new(256);
227 
228 	for (i = 0; i < N_ELEMENTS(tests)*2; i++) {
229 		const struct test *test = &tests[i/2];
230 		const struct message_address *test_wanted_addr;
231 		bool fill_missing = i%2 != 0;
232 
233 		test_wanted_addr = !fill_missing ?
234 			&test->addr : &test->filled_addr;
235 		addr = test_parse_address(test->input, fill_missing);
236 		test_assert_idx(addr != NULL && addr->next == NULL &&
237 				cmp_addr(addr, test_wanted_addr), i);
238 
239 		/* test the address alone */
240 		str_truncate(str, 0);
241 		message_address_write(str, addr);
242 		if (fill_missing && test->wanted_filled_output != NULL)
243 			wanted_string = test->wanted_filled_output;
244 		else if (test->wanted_output != NULL)
245 			wanted_string = test->wanted_output;
246 		else
247 			wanted_string = test->input;
248 		test_assert_idx(strcmp(str_c(str), wanted_string) == 0, i);
249 
250 		if ((test->flags & TEST_MESSAGE_ADDRESS_FLAG_SKIP_LIST) != 0)
251 			continue;
252 
253 		/* test the address as a list of itself */
254 		for (unsigned int list_length = 2; list_length <= 5; list_length++) {
255 			str_truncate(group, 0);
256 			str_append(group, test->input);
257 			for (unsigned int j = 1; j < list_length; j++) {
258 				if ((j % 2) == 0)
259 					str_append(group, ",");
260 				else
261 					str_append(group, " , \n ");
262 				str_append(group, test->input);
263 			}
264 
265 			addr = test_parse_address(str_c(group), fill_missing);
266 			for (unsigned int j = 0; j < list_length; j++) {
267 				test_assert_idx(addr != NULL &&
268 						cmp_addr(addr, test_wanted_addr), i);
269 				if (addr != NULL)
270 					addr = addr->next;
271 			}
272 			test_assert_idx(addr == NULL, i);
273 		}
274 
275 		/* test the address as a group of itself */
276 		for (unsigned int list_length = 1; list_length <= 5; list_length++) {
277 			str_truncate(group, 0);
278 			str_printfa(group, "group: %s", test->input);
279 			for (unsigned int j = 1; j < list_length; j++) {
280 				if ((j % 2) == 0)
281 					str_append(group, ",");
282 				else
283 					str_append(group, " , \n ");
284 				str_append(group, test->input);
285 			}
286 			str_append_c(group, ';');
287 
288 			addr = test_parse_address(str_c(group), fill_missing);
289 			test_assert(addr != NULL && cmp_addr(addr, &group_prefix));
290 			addr = addr->next;
291 			for (unsigned int j = 0; j < list_length; j++) {
292 				test_assert_idx(addr != NULL &&
293 						cmp_addr(addr, test_wanted_addr), i);
294 				if (addr != NULL)
295 					addr = addr->next;
296 			}
297 			test_assert_idx(addr != NULL && addr->next == NULL &&
298 					cmp_addr(addr, &group_suffix), i);
299 		}
300 	}
301 	test_end();
302 
303 	test_begin("message address parsing with empty group");
304 	str_truncate(group, 0);
305 	str_append(group, "group:;");
306 	addr = test_parse_address(str_c(group), FALSE);
307 	str_truncate(str, 0);
308 	message_address_write(str, addr);
309 	test_assert(addr != NULL && cmp_addr(addr, &group_prefix));
310 	addr = addr->next;
311 	test_assert(addr != NULL && addr->next == NULL &&
312 		    cmp_addr(addr, &group_suffix));
313 	test_assert(strcmp(str_c(str), "group:;") == 0);
314 	test_end();
315 
316 	test_begin("message address parsing empty string");
317 	test_assert(message_address_parse(unsafe_data_stack_pool, &uchar_nul, 0, 10,
318 					  MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING) == NULL);
319 	str_truncate(str, 0);
320 	message_address_write(str, NULL);
321 	test_assert(str_len(str) == 0);
322 	test_end();
323 }
324 
test_message_address_nuls(void)325 static void test_message_address_nuls(void)
326 {
327 	const unsigned char input[] =
328 		"\"user\0nuls\\\0-esc\"@[domain\0nuls\\\0-esc] (comment\0nuls\\\0-esc)";
329 	const struct message_address output = {
330 		NULL, "comment\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL,
331 		"user\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc",
332 		"[domain\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc]", FALSE
333 	};
334 	const struct message_address *addr;
335 
336 	test_begin("message address parsing with NULs");
337 	addr = message_address_parse(pool_datastack_create(),
338 				     input, sizeof(input)-1, UINT_MAX, 0);
339 	test_assert(addr != NULL && cmp_addr(addr, &output));
340 	test_end();
341 }
342 
test_message_address_nuls_display_name(void)343 static void test_message_address_nuls_display_name(void)
344 {
345 	const unsigned char input[] =
346 		"\"displayname\0nuls\\\0-esc\" <\"user\0nuls\\\0-esc\"@[domain\0nuls\\\0-esc]>";
347 	const struct message_address output = {
348 		NULL, "displayname\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL,
349 		"user\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc",
350 		"[domain\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc]", FALSE
351 	};
352 	const struct message_address *addr;
353 
354 	test_begin("message address parsing with NULs in display-name");
355 	addr = message_address_parse(pool_datastack_create(),
356 				     input, sizeof(input)-1, UINT_MAX, 0);
357 	test_assert(addr != NULL && cmp_addr(addr, &output));
358 	test_end();
359 }
360 
test_message_address_non_strict_dots(void)361 static void test_message_address_non_strict_dots(void)
362 {
363 	const char *const inputs[] = {
364 		".@example.com",
365 		"..@example.com",
366 		"..foo@example.com",
367 		"..foo..@example.com",
368 		"..foo..bar..@example.com",
369 	};
370 	const struct message_address *addr;
371 	struct message_address output = {
372 		NULL, NULL, NULL, "local-part",
373 		"example.com", FALSE
374 	};
375 
376 	test_begin("message address parsing with non-strict dots");
377 	for (unsigned int i = 0; i < N_ELEMENTS(inputs); i++) {
378 		const unsigned char *addr_input =
379 			(const unsigned char *)inputs[i];
380 		/* invalid with strict-dots flag */
381 		addr = message_address_parse(pool_datastack_create(),
382 			addr_input, strlen(inputs[i]), UINT_MAX,
383 			MESSAGE_ADDRESS_PARSE_FLAG_STRICT_DOTS);
384 		test_assert_idx(addr != NULL && addr->invalid_syntax, i);
385 
386 		/* valid without the strict-dots flag */
387 		addr = message_address_parse(pool_datastack_create(),
388 			addr_input, strlen(inputs[i]), UINT_MAX, 0);
389 		output.mailbox = t_strcut(inputs[i], '@');
390 		test_assert_idx(addr != NULL && cmp_addr(addr, &output), i);
391 	}
392 	test_end();
393 }
394 
395 static int
test_parse_path(const char * input,const struct message_address ** addr_r)396 test_parse_path(const char *input, const struct message_address **addr_r)
397 {
398 	struct message_address *addr;
399 	char *input_dup;
400 	int ret;
401 
402 	/* duplicate the input (without trailing NUL) so valgrind notices
403 	   if there's any out-of-bounds access */
404 	size_t input_len = strlen(input);
405 	if (input_len > 0)
406 		input = input_dup = i_memdup(input, input_len);
407 	ret = message_address_parse_path(pool_datastack_create(),
408 					 (const unsigned char *)input, input_len,
409 					 &addr);
410 	if (input_len > 0)
411 		i_free(input_dup);
412 	*addr_r = addr;
413 	return ret;
414 }
415 
test_message_address_path(void)416 static void test_message_address_path(void)
417 {
418 	static const struct test {
419 		const char *input;
420 		const char *wanted_output;
421 		struct message_address addr;
422 	} tests[] = {
423 		{ "<>", NULL,
424 		  { NULL, NULL, NULL, NULL, NULL, FALSE } },
425 		{ " < > ", "<>",
426 		  { NULL, NULL, NULL, NULL, NULL, FALSE } },
427 		{ "<user@domain>", NULL,
428 		  { NULL, NULL, NULL, "user", "domain", FALSE } },
429 		{ "  <user@domain>  ", "<user@domain>",
430 		  { NULL, NULL, NULL, "user", "domain", FALSE } },
431 		{ "user@domain", "<user@domain>",
432 		  { NULL, NULL, NULL, "user", "domain", FALSE } },
433 		{ "  user@domain  ", "<user@domain>",
434 		  { NULL, NULL, NULL, "user", "domain", FALSE } },
435 		{ "<\"user\"@domain>", "<user@domain>",
436 		  { NULL, NULL, NULL, "user", "domain", FALSE } },
437 		{ "<\"user name\"@domain>", NULL,
438 		  { NULL, NULL, NULL, "user name", "domain", FALSE } },
439 		{ "<\"user@na\\\\me\"@domain>", NULL,
440 		  { NULL, NULL, NULL, "user@na\\me", "domain", FALSE } },
441 		{ "<\"user\\\"name\"@domain>", NULL,
442 		  { NULL, NULL, NULL, "user\"name", "domain", FALSE } },
443 		{ "<\"\"@domain>", NULL,
444 		  { NULL, NULL, NULL, "", "domain", FALSE } },
445 		{ "<@source", "<>",
446 		  { NULL, NULL, NULL, NULL, NULL, TRUE } },
447 	};
448 	const struct message_address *addr;
449 	string_t *str;
450 	const char *wanted_string;
451 	unsigned int i;
452 
453 	test_begin("message address path parsing");
454 	str = t_str_new(128);
455 
456 	for (i = 0; i < N_ELEMENTS(tests); i++) {
457 		const struct test *test = &tests[i];
458 		const struct message_address *test_wanted_addr;
459 		int ret;
460 
461 		test_wanted_addr = &test->addr;
462 		ret = test_parse_path(test->input, &addr);
463 		if (addr->invalid_syntax)
464 			test_assert_idx(ret == -1, i);
465 		else
466 			test_assert_idx(ret == 0, i);
467 		test_assert_idx(addr != NULL && addr->next == NULL &&
468 				cmp_addr(addr, test_wanted_addr), i);
469 
470 		/* test the address alone */
471 		str_truncate(str, 0);
472 		message_address_write(str, addr);
473 		if (test->wanted_output != NULL)
474 			wanted_string = test->wanted_output;
475 		else
476 			wanted_string = test->input;
477 		test_assert_idx(strcmp(str_c(str), wanted_string) == 0, i);
478 	}
479 	test_end();
480 }
481 
test_message_address_path_invalid(void)482 static void test_message_address_path_invalid(void)
483 {
484 	static const char *tests[] = {
485 		"",
486 		"<",
487 		" < ",
488 		">",
489 		" > ",
490 		"<user@domain",
491 		"  <user@domain  ",
492 		"user@domain>",
493 		"  user@domain>  ",
494 		"<user>",
495 		"<@route@route2:user>",
496 		"<@domain>",
497 		"@domain",
498 		"  @domain  ",
499 		"<user@>",
500 		"user@",
501 		"  user@  ",
502 		"<user@domain>bladiebla",
503 		"user@domain@"
504 	};
505 	const struct message_address *addr;
506 	unsigned int i;
507 
508 	test_begin("message address path invalid");
509 
510 	for (i = 0; i < N_ELEMENTS(tests); i++) {
511 		const char *test = tests[i];
512 		int ret;
513 
514 		ret = test_parse_path(test, &addr);
515 		test_assert_idx(ret < 0, i);
516 	}
517 	test_end();
518 }
519 
main(void)520 int main(void)
521 {
522 	static void (*const test_functions[])(void) = {
523 		test_message_address,
524 		test_message_address_nuls,
525 		test_message_address_nuls_display_name,
526 		test_message_address_non_strict_dots,
527 		test_message_address_path,
528 		test_message_address_path_invalid,
529 		NULL
530 	};
531 	return test_run(test_functions);
532 }
533