1 /* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "net.h"
5 #include "http-url.h"
6 #include "test-common.h"
7 
8 struct valid_http_url_test {
9 	const char *url;
10 	enum http_url_parse_flags flags;
11 	struct http_url url_base;
12 
13 	struct http_url url_parsed;
14 };
15 
16 /* Valid HTTP URL tests */
17 static struct valid_http_url_test valid_url_tests[] = {
18 	/* Generic tests */
19 	{
20 		.url = "http://localhost",
21 		.url_parsed = {
22 			.host = { .name = "localhost" },
23 		},
24 	},
25 	{
26 		.url = "http://www.%65%78%61%6d%70%6c%65.com",
27 		.url_parsed = {
28 			.host = { .name = "www.example.com" },
29 		},
30 	},
31 	{
32 		.url = "http://www.dovecot.org:8080",
33 		.url_parsed = {
34 			.host = { .name = "www.dovecot.org" },
35 			.port = 8080,
36 		},
37 	},
38 	{
39 		.url = "http://127.0.0.1",
40 		.url_parsed = {
41 			.host = {
42 				.name = "127.0.0.1",
43 				.ip = { .family = AF_INET },
44 			},
45 		},
46 	},
47 	{
48 		.url = "http://[::1]",
49 		.url_parsed = {
50 			.host = {
51 				.name = "[::1]",
52 				.ip = { .family = AF_INET6 },
53 			},
54 		},
55 	},
56 	{
57 		.url = "http://[::1]:8080",
58 		.url_parsed = {
59 			.host = {
60 				.name = "[::1]",
61 				.ip = { .family = AF_INET6 },
62 			},
63 			.port = 8080,
64 		},
65 	},
66 	{
67 		.url = "http://user@api.dovecot.org",
68 		.flags = HTTP_URL_ALLOW_USERINFO_PART,
69 		.url_parsed = {
70 			.host = { .name = "api.dovecot.org" },
71 			.user = "user",
72 		},
73 	},
74 	{
75 		.url = "http://userid:secret@api.dovecot.org",
76 		.flags = HTTP_URL_ALLOW_USERINFO_PART,
77 		.url_parsed = {
78 			.host = { .name = "api.dovecot.org" },
79 			.user = "userid",
80 			.password = "secret",
81 		},
82 	},
83 	{
84 		.url = "http://su%3auserid:secret@api.dovecot.org",
85 		.flags = HTTP_URL_ALLOW_USERINFO_PART,
86 		.url_parsed = {
87 			.host = { .name = "api.dovecot.org" },
88 			.user = "su:userid",
89 			.password = "secret",
90 		},
91 	},
92 	{
93 		.url = "http://www.example.com/"
94 			"?question=What%20are%20you%20doing%3f&answer=Nothing.",
95 		.url_parsed = {
96 			.path = "/",
97 			.host = { .name = "www.example.com" },
98 			.enc_query = "question=What%20are%20you%20doing%3f&answer=Nothing.",
99 		},
100 	},
101 	/* Empty path segments */
102 	{
103 		.url = "http://target//index.php",
104 		.url_parsed = {
105 			.path = "//index.php",
106 			.host = { .name = "target" },
107 		},
108 	},
109 	{
110 		.url = "http://target//path//index.php",
111 		.url_parsed = {
112 			.path = "//path//index.php",
113 			.host = { .name = "target" },
114 		},
115 	},
116 	{
117 		.url = "http://target//path/",
118 		.url_parsed = {
119 			.path = "//path/",
120 			.host = { .name = "target" },
121 		},
122 	},
123 	{
124 		.url = "http://target//path//",
125 		.url_parsed = {
126 			.path = "//path//",
127 			.host = { .name = "target" },
128 		},
129 	},
130 	{
131 		.url = "http://target//path//to//./index.php",
132 		.url_parsed = {
133 			.path = "//path//to//index.php",
134 			.host = { .name = "target" },
135 		},
136 	},
137 	{
138 		.url = "http://target//path//to//../index.php",
139 		.url_parsed = {
140 			.path = "//path//to/index.php",
141 			.host = { .name = "target" },
142 		},
143 	},
144 	{
145 		.url = "/index.php",
146 		.url_base = {
147 			.host = { .name = "target" },
148 		},
149 		.url_parsed = {
150 			.host = { .name = "target" },
151 			.path = "/index.php",
152 		},
153 	},
154 	{
155 		.url = "//index.php",
156 		.url_base = {
157 			.host = { .name = "target" },
158 		},
159 		.url_parsed = {
160 			.host = { .name = "index.php" },
161 		},
162 	},
163 	{
164 		.url = "/path/to/index.php",
165 		.url_base = {
166 			.host = { .name = "target" },
167 		},
168 		.url_parsed = {
169 			.host = { .name = "target" },
170 			.path = "/path/to/index.php",
171 		},
172 	},
173 	{
174 		.url = "//path//to//index.php",
175 		.url_base = {
176 			.host = { .name = "target" },
177 		},
178 		.url_parsed = {
179 			.host = { .name = "path" },
180 			.path = "//to//index.php",
181 		},
182 	},
183 	/* These next 2 URLs don't follow the recommendations in
184 	   http://tools.ietf.org/html/rfc1034#section-3.5 and
185 	   http://tools.ietf.org/html/rfc3696
186 	   However they satisfy the grammar in
187 	   http://tools.ietf.org/html/rfc1123#section-2 and
188 	   http://tools.ietf.org/html/rfc952
189 	   so we should parse them.
190 	*/
191 	{
192 		.url = "http://256.0.0.1/that/reverts/to/DNS",
193 		.url_parsed = {
194 			.path = "/that/reverts/to/DNS",
195 			.host = { .name = "256.0.0.1" },
196 		},
197 	},
198 	{
199 		.url = "http://127.0.0.284/this/also/reverts/to/DNS",
200 		.url_parsed = {
201 			.path = "/this/also/reverts/to/DNS",
202 			.host = { .name = "127.0.0.284" },
203 		},
204 	},
205 	{
206 		.url = "http://www.example.com/#Status%20of%20development",
207 		.flags = HTTP_URL_ALLOW_FRAGMENT_PART,
208 		.url_parsed = {
209 			.path = "/",
210 			.host = { .name = "www.example.com" },
211 			.enc_fragment = "Status%20of%20development",
212 		},
213 	},
214 	/* RFC 3986, Section 5.4. Reference Resolution Examples
215 	 *
216 	 * Within a representation with a well defined base URI of
217 	 *
218 	 *  http://a/b/c/d;p?q
219 	 *
220 	 * a relative reference is transformed to its target URI as follows.
221 	 *
222 	 * 5.4.1. Normal Examples
223 	 */
224 	{ // "g"             =  "http://a/b/c/g"
225 		.url = "g",
226 		.url_base = {
227 			.host = { .name = "a" },
228 			.path = "/b/c/d;p",
229 			.enc_query = "q",
230 		},
231 		.url_parsed = {
232 			.host = { .name = "a" },
233 			.path = "/b/c/g",
234 		},
235 	},
236 	{ // "./g"           =  "http://a/b/c/g"
237 		.url = "./g",
238 		.url_base = {
239 			.host = { .name = "a" },
240 			.path = "/b/c/d;p",
241 			.enc_query = "q",
242 		},
243 		.url_parsed = {
244 			.host = { .name = "a" },
245 			.path = "/b/c/g",
246 		},
247 	},
248 	{ // "g/"            =  "http://a/b/c/g/"
249 		.url = "g/",
250 		.url_base = {
251 			.host = { .name = "a" },
252 			.path = "/b/c/d;p",
253 			.enc_query = "q",
254 		},
255 		.url_parsed = {
256 			.host = { .name = "a" },
257 			.path = "/b/c/g/",
258 		},
259 	},
260 	{ // "/g"            =  "http://a/g"
261 		.url = "/g",
262 		.url_base = {
263 			.host = { .name = "a" },
264 			.path = "/b/c/d;p",
265 			.enc_query = "q",
266 		},
267 		.url_parsed = {
268 			.host = { .name = "a" },
269 			.path = "/g",
270 		},
271 	},
272 	{ // "//g"           =  "http://g"
273 		.url = "//g",
274 		.url_base = {
275 			.host = { .name = "a" },
276 			.path = "/b/c/d;p",
277 			.enc_query = "q",
278 		},
279 		.url_parsed = {
280 			.host = { .name = "g" },
281 		},
282 	},
283 	{ // "?y"            =  "http://a/b/c/d;p?y"
284 		.url = "?y",
285 		.url_base = {
286 			.host = { .name = "a" },
287 			.path = "/b/c/d;p",
288 			.enc_query = "q",
289 		},
290 		.url_parsed = {
291 			.host = { .name = "a" },
292 			.path = "/b/c/d;p",
293 			.enc_query = "y",
294 		},
295 	},
296 	{ // "g?y"           =  "http://a/b/c/g?y"
297 		.url = "g?y",
298 		.url_base = {
299 			.host = { .name = "a" },
300 			.path = "/b/c/d;p",
301 			.enc_query = "q",
302 		},
303 		.url_parsed = {
304 			.host = { .name = "a" },
305 			.path = "/b/c/g",
306 			.enc_query = "y",
307 		},
308 	},
309 	{ // "#s"            =  "http://a/b/c/d;p?q#s"
310 		.url = "#s",
311 		.flags = HTTP_URL_ALLOW_FRAGMENT_PART,
312 		.url_base = {
313 			.host = { .name = "a" },
314 			.path = "/b/c/d;p",
315 			.enc_query = "q",
316 		},
317 		.url_parsed = {
318 			.host = { .name = "a" },
319 			.path = "/b/c/d;p",
320 			.enc_query = "q",
321 			.enc_fragment = "s",
322 		},
323 	},
324 	{ // "g#s"           =  "http://a/b/c/g#s"
325 		.url = "g#s",
326 		.flags = HTTP_URL_ALLOW_FRAGMENT_PART,
327 		.url_base = {
328 			.host = { .name = "a" },
329 			.path = "/b/c/d;p",
330 			.enc_query = "q",
331 		},
332 		.url_parsed = {
333 			.host = { .name = "a" },
334 			.path = "/b/c/g",
335 			.enc_fragment = "s",
336 		},
337 	},
338 	{ // "g?y#s"         =  "http://a/b/c/g?y#s"
339 		.url = "g?y#s",
340 		.flags = HTTP_URL_ALLOW_FRAGMENT_PART,
341 		.url_base = {
342 			.host = { .name = "a" },
343 			.path = "/b/c/d;p",
344 			.enc_query = "q",
345 		},
346 		.url_parsed = {
347 			.host = { .name = "a" },
348 			.path = "/b/c/g",
349 			.enc_query = "y",
350 			.enc_fragment = "s",
351 		},
352 	},
353 	{ // ";x"            =  "http://a/b/c/;x"
354 		.url = ";x",
355 		.url_base = {
356 			.host = { .name = "a" },
357 			.path = "/b/c/d;p",
358 			.enc_query = "q",
359 		},
360 		.url_parsed = {
361 			.host = { .name = "a" },
362 			.path = "/b/c/;x",
363 		},
364 	},
365 	{ // "g;x"           =  "http://a/b/c/g;x"
366 		.url = "g;x",
367 		.url_base = {
368 			.host = { .name = "a" },
369 			.path = "/b/c/d;p",
370 			.enc_query = "q",
371 		},
372 		.url_parsed = {
373 			.host = { .name = "a" },
374 			.path = "/b/c/g;x",
375 		},
376 
377 	},
378 	{ // "g;x?y#s"       =  "http://a/b/c/g;x?y#s"
379 		.url = "g;x?y#s",
380 		.flags = HTTP_URL_ALLOW_FRAGMENT_PART,
381 		.url_base = {
382 			.host = { .name = "a" },
383 			.path = "/b/c/d;p",
384 			.enc_query = "q",
385 		},
386 		.url_parsed = {
387 			.host = { .name = "a" },
388 			.path = "/b/c/g;x",
389 			.enc_query = "y",
390 			.enc_fragment = "s",
391 		},
392 	},
393 	{ // ""              =  "http://a/b/c/d;p?q"
394 		.url = "",
395 		.url_base = {
396 			.host = { .name = "a" },
397 			.path = "/b/c/d;p",
398 			.enc_query = "q",
399 		},
400 		.url_parsed = {
401 			.host = { .name = "a" },
402 			.path = "/b/c/d;p",
403 			.enc_query = "q",
404 		},
405 	},
406 	{ // "."             =  "http://a/b/c/"
407 		.url = ".",
408 		.url_base = {
409 			.host = { .name = "a" },
410 			.path = "/b/c/d;p",
411 			.enc_query = "q",
412 		},
413 		.url_parsed = {
414 			.host = { .name = "a" },
415 			.path = "/b/c/",
416 		},
417 	},
418 	{ // "./"            =  "http://a/b/c/"
419 		.url = "./",
420 		.url_base = {
421 			.host = { .name = "a" },
422 			.path = "/b/c/d;p",
423 			.enc_query = "q",
424 		},
425 		.url_parsed = {
426 			.host = { .name = "a" },
427 			.path = "/b/c/",
428 		},
429 	},
430 	{ // ".."            =  "http://a/b/"
431 		.url = "..",
432 		.url_base = {
433 			.host = { .name = "a" },
434 			.path = "/b/c/d;p",
435 			.enc_query = "q",
436 		},
437 		.url_parsed = {
438 			.host = { .name = "a" },
439 			.path = "/b/",
440 		},
441 	},
442 	{ // "../"           =  "http://a/b/"
443 		.url = "../",
444 		.url_base = {
445 			.host = { .name = "a" },
446 			.path = "/b/c/d;p",
447 			.enc_query = "q",
448 		},
449 		.url_parsed = {
450 			.host = { .name = "a" },
451 			.path = "/b/",
452 		},
453 	},
454 	{ // "../g"          =  "http://a/b/g"
455 		.url = "../g",
456 		.url_base = {
457 			.host = { .name = "a" },
458 			.path = "/b/c/d;p",
459 			.enc_query = "q",
460 		},
461 		.url_parsed = {
462 			.host = { .name = "a" },
463 			.path = "/b/g",
464 		},
465 	},
466 	{ // "../.."         =  "http://a/"
467 		.url = "../..",
468 		.url_base = {
469 			.host = { .name = "a" },
470 			.path = "/b/c/d;p",
471 			.enc_query = "q",
472 		},
473 		.url_parsed = {
474 			.host = { .name = "a" },
475 			.path = "/",
476 		},
477 	},
478 	{ // "../../"        =  "http://a/"
479 		.url = "../../",
480 		.url_base = {
481 			.host = { .name = "a" },
482 			.path = "/b/c/d;p",
483 			.enc_query = "q",
484 		},
485 		.url_parsed = {
486 			.host = { .name = "a" },
487 			.path = "/",
488 		},
489 	},
490 	{ // "../../g"       =  "http://a/g"
491 		.url = "../../g",
492 		.url_base = {
493 			.host = { .name = "a" },
494 			.path = "/b/c/d;p",
495 			.enc_query = "q",
496 		},
497 		.url_parsed = {
498 			.host = { .name = "a" },
499 			.path = "/g",
500 		},
501 	},
502 	/* 5.4.2. Abnormal Examples
503 	 */
504 	{ // "../../../g"    =  "http://a/g"
505 		.url = "../../../g",
506 		.url_base = {
507 			.host = { .name = "a" },
508 			.path = "/b/c/d;p",
509 			.enc_query = "q",
510 		},
511 		.url_parsed = {
512 			.host = { .name = "a" },
513 			.path = "/g",
514 		},
515 	},
516 	{ // "../../../../g" =  "http://a/g"
517 		.url = "../../../../g",
518 		.url_base = {
519 			.host = { .name = "a" },
520 			.path = "/b/c/d;p",
521 			.enc_query = "q",
522 		},
523 		.url_parsed = {
524 			.host = { .name = "a" },
525 			.path = "/g",
526 		},
527 	},
528 	{ // "/./g"          =  "http://a/g"
529 		.url = "/./g",
530 		.url_base = {
531 			.host = {.name = "a"},
532 			.path = "/b/c/d;p",
533 			.enc_query = "q",
534 		},
535 		.url_parsed = {
536 			.host = {.name = "a"},
537 			.path = "/g",
538 		},
539 	},
540 	{ // "/../g"         =  "http://a/g"
541 		.url = "/../g",
542 		.url_base = {
543 			.host = { .name = "a" },
544 			.path = "/b/c/d;p",
545 			.enc_query = "q",
546 		},
547 		.url_parsed = {
548 			.host = { .name = "a" },
549 			.path = "/g",
550 		},
551 	},
552 	{ // "g."            =  "http://a/b/c/g."
553 		.url = "g.",
554 		.url_base = {
555 			.host = { .name = "a" },
556 			.path = "/b/c/d;p",
557 			.enc_query = "q",
558 		},
559 		.url_parsed = {
560 			.host = { .name = "a" },
561 			.path = "/b/c/g.",
562 		},
563 	},
564 	{ // ".g"            =  "http://a/b/c/.g"
565 		.url = ".g",
566 		.url_base = {
567 			.host = { .name = "a" },
568 			.path = "/b/c/d;p",
569 			.enc_query = "q",
570 		},
571 		.url_parsed = {
572 			.host = { .name = "a" },
573 			.path = "/b/c/.g",
574 		},
575 	},
576 	{ // "g.."           =  "http://a/b/c/g.."
577 		.url = "g..",
578 		.url_base = {
579 			.host = { .name = "a" },
580 			.path = "/b/c/d;p",
581 			.enc_query = "q",
582 		},
583 		.url_parsed = {
584 			.host = { .name = "a" },
585 			.path = "/b/c/g..",
586 		},
587 	},
588 	{ // "..g"           =  "http://a/b/c/..g"
589 		.url = "..g",
590 		.url_base = {
591 			.host = { .name = "a" },
592 			.path = "/b/c/d;p",
593 			.enc_query = "q",
594 		},
595 		.url_parsed = {
596 			.host = { .name = "a" },
597 			.path = "/b/c/..g",
598 		},
599 	},
600 	{ // "./../g"        =  "http://a/b/g"
601 		.url = "./../g",
602 		.url_base = {
603 			.host = {.name = "a"},
604 			.path = "/b/c/d;p",
605 			.enc_query = "q",
606 		},
607 		.url_parsed = {
608 			.host = {.name = "a"},
609 			.path = "/b/g",
610 		},
611 	},
612 	{ // "./g/."         =  "http://a/b/c/g/"
613 		.url = "./g/.",
614 		.url_base = {
615 			.host = { .name = "a" },
616 			.path = "/b/c/d;p",
617 			.enc_query = "q",
618 		},
619 		.url_parsed = {
620 			.host = { .name = "a" },
621 			.path = "/b/c/g/",
622 		},
623 	},
624 	{ // "g/./h"         =  "http://a/b/c/g/h"
625 		.url = "g/./h",
626 		.url_base = {
627 			.host = { .name = "a" },
628 			.path = "/b/c/d;p",
629 			.enc_query = "q",
630 		},
631 		.url_parsed = {
632 			.host = { .name = "a" },
633 			.path = "/b/c/g/h",
634 		},
635 	},
636 	{ // "g/../h"        =  "http://a/b/c/h"
637 		.url = "g/../h",
638 		.url_base = {
639 			.host = { .name = "a" },
640 			.path = "/b/c/d;p",
641 			.enc_query = "q",
642 		},
643 		.url_parsed = {
644 			.host = { .name = "a" },
645 			.path = "/b/c/h",
646 		},
647 	},
648 	{ // "g;x=1/./y"     =  "http://a/b/c/g;x=1/y"
649 		.url = "g;x=1/./y",
650 		.url_base = {
651 			.host = { .name = "a" },
652 			.path = "/b/c/d;p",
653 			.enc_query = "q",
654 		},
655 		.url_parsed = {
656 			.host = { .name = "a" },
657 			.path = "/b/c/g;x=1/y",
658 		},
659 	},
660 	{ // "g;x=1/../y"    =  "http://a/b/c/y"
661 		.url = "g;x=1/../y",
662 		.url_base = {
663 			.host = { .name = "a" },
664 			.path = "/b/c/d;p",
665 			.enc_query = "q",
666 		},
667 		.url_parsed = {
668 			.host = { .name = "a" },
669 			.path = "/b/c/y",
670 		},
671 	},
672 	{ // "g?y/./x"       =  "http://a/b/c/g?y/./x"
673 		.url = "g?y/./x",
674 		.url_base = {
675 			.host = { .name = "a" },
676 			.path = "/b/c/d;p",
677 			.enc_query = "q",
678 		},
679 		.url_parsed = {
680 			.host = { .name = "a" },
681 			.path = "/b/c/g",
682 			.enc_query = "y/./x",
683 		},
684 	},
685 	{ // "g?y/../x"      =  "http://a/b/c/g?y/../x"
686 		.url = "g?y/../x",
687 		.url_base = {
688 			.host = { .name = "a" },
689 			.path = "/b/c/d;p",
690 			.enc_query = "q",
691 		},
692 		.url_parsed = {
693 			.host = { .name = "a" },
694 			.path = "/b/c/g",
695 			.enc_query = "y/../x",
696 		},
697 	},
698 	{ // "g#s/./x"       =  "http://a/b/c/g#s/./x"
699 		.url = "g#s/./x",
700 		.flags = HTTP_URL_ALLOW_FRAGMENT_PART,
701 		.url_base = {
702 			.host = { .name = "a" },
703 			.path = "/b/c/d;p",
704 			.enc_query = "q",
705 		},
706 		.url_parsed =
707 			{
708 			.host = { .name = "a" },
709 			.path = "/b/c/g",
710 			.enc_fragment = "s/./x",
711 		},
712 	},
713 	{ // "g#s/../x"      =  "http://a/b/c/g#s/../x"
714 		.url = "g#s/../x",
715 		.flags = HTTP_URL_ALLOW_FRAGMENT_PART,
716 		.url_base = {
717 			.host = { .name = "a" },
718 			.path = "/b/c/d;p",
719 			.enc_query = "q",
720 		},
721 		.url_parsed =
722 			{
723 			.host = { .name = "a" },
724 			.path = "/b/c/g",
725 			.enc_fragment = "s/../x",
726 		},
727 	}
728 };
729 
730 static unsigned int valid_url_test_count = N_ELEMENTS(valid_url_tests);
731 
732 static void
test_http_url_equal(struct http_url * urlt,struct http_url * urlp)733 test_http_url_equal(struct http_url *urlt, struct http_url *urlp)
734 {
735 	if (urlp->host.name == NULL || urlt->host.name == NULL) {
736 		test_assert(urlp->host.name == urlt->host.name);
737 	} else {
738 		test_assert(strcmp(urlp->host.name, urlt->host.name) == 0);
739 	}
740 	test_assert(urlp->port == urlt->port);
741 	test_assert(urlp->host.ip.family == urlt->host.ip.family);
742 	if (urlp->user == NULL || urlt->user == NULL) {
743 		test_assert(urlp->user == urlt->user);
744 	} else {
745 		test_assert(strcmp(urlp->user, urlt->user) == 0);
746 	}
747 	if (urlp->password == NULL || urlt->password == NULL) {
748 		test_assert(urlp->password == urlt->password);
749 	} else {
750 		test_assert(strcmp(urlp->password, urlt->password) == 0);
751 	}
752 	if (urlp->path == NULL || urlt->path == NULL) {
753 		test_assert(urlp->path == urlt->path);
754 	} else {
755 		test_assert(strcmp(urlp->path, urlt->path) == 0);
756 	}
757 	if (urlp->enc_query == NULL || urlt->enc_query == NULL) {
758 		test_assert(urlp->enc_query == urlt->enc_query);
759 	} else {
760 		test_assert(strcmp(urlp->enc_query, urlt->enc_query) == 0);
761 	}
762 	if (urlp->enc_fragment == NULL || urlt->enc_fragment == NULL) {
763 		test_assert(urlp->enc_fragment == urlt->enc_fragment);
764 	} else {
765 		test_assert(strcmp(urlp->enc_fragment,
766 				   urlt->enc_fragment) == 0);
767 	}
768 }
769 
test_http_url_valid(void)770 static void test_http_url_valid(void)
771 {
772 	unsigned int i;
773 
774 	for (i = 0; i < valid_url_test_count; i++) T_BEGIN {
775 		const char *url = valid_url_tests[i].url;
776 		enum http_url_parse_flags flags = valid_url_tests[i].flags;
777 		struct http_url *urlt = &valid_url_tests[i].url_parsed;
778 		struct http_url *urlb = &valid_url_tests[i].url_base;
779 		struct http_url *urlp;
780 		const char *error = NULL;
781 
782 		test_begin(t_strdup_printf("http url valid [%d]", i));
783 
784 		if (urlb->host.name == NULL) urlb = NULL;
785 		if (http_url_parse(url, urlb, flags, pool_datastack_create(),
786 				   &urlp, &error) < 0)
787 			urlp = NULL;
788 
789 		test_out_reason(t_strdup_printf("http_url_parse(%s)",
790 			valid_url_tests[i].url), urlp != NULL, error);
791 		if (urlp != NULL)
792 			test_http_url_equal(urlt, urlp);
793 
794 		test_end();
795 	} T_END;
796 }
797 
798 struct invalid_http_url_test {
799 	const char *url;
800 	enum http_url_parse_flags flags;
801 	struct http_url url_base;
802 };
803 
804 static struct invalid_http_url_test invalid_url_tests[] = {
805 	{
806 		.url = "imap://example.com/INBOX"
807 	},
808 	{
809 		.url = "http:/www.example.com"
810 	},
811 	{
812 		.url = ""
813 	},
814 	{
815 		.url = "/index.html"
816 	},
817 	{
818 		.url = "http://www.example.com/index.html\""
819 	},
820 	{
821 		.url = "http:///dovecot.org"
822 	},
823 	{
824 		.url = "http://[]/index.html"
825 	},
826 	{
827 		.url = "http://[v08.234:232:234:234:2221]/index.html"
828 	},
829 	{
830 		.url = "http://[1::34a:34:234::6]/index.html"
831 	},
832 	{
833 		.url = "http://example%a.com/index.html"
834 	},
835 	{
836 		.url = "http://example.com%/index.html"
837 	},
838 	{
839 		.url = "http://example%00.com/index.html"
840 	},
841 	{
842 		.url = "http://example.com:65536/index.html"
843 	},
844 	{
845 		.url = "http://example.com:72817/index.html"
846 	},
847 	{
848 		.url = "http://example.com/settings/%00/"
849 	},
850 	{
851 		.url = "http://example.com/settings/%0r/"
852 	},
853 	{
854 		.url = "http://example.com/settings/misc/%/"
855 	},
856 	{
857 		.url = "http://example.com/?%00"
858 	},
859 	{
860 		.url = "http://www.example.com/network.html#IMAP_Server"
861 	},
862 	{
863 		.url = "http://example.com/#%00",
864 		.flags = HTTP_URL_ALLOW_FRAGMENT_PART
865 	}
866 };
867 
868 static unsigned int invalid_url_test_count = N_ELEMENTS(invalid_url_tests);
869 
test_http_url_invalid(void)870 static void test_http_url_invalid(void)
871 {
872 	unsigned int i;
873 
874 	for (i = 0; i < invalid_url_test_count; i++) T_BEGIN {
875 		const char *url = invalid_url_tests[i].url;
876 		enum http_url_parse_flags flags = invalid_url_tests[i].flags;
877 		struct http_url *urlb = &invalid_url_tests[i].url_base;
878 		struct http_url *urlp;
879 		const char *error = NULL;
880 
881 		if (urlb->host.name == NULL)
882 			urlb = NULL;
883 
884 		test_begin(t_strdup_printf("http url invalid [%d]", i));
885 
886 		if (http_url_parse(url, urlb, flags,
887 				   pool_datastack_create(), &urlp, &error) < 0)
888 			urlp = NULL;
889 		test_out_reason(t_strdup_printf("parse %s", url),
890 				urlp == NULL, error);
891 
892 		test_end();
893 	} T_END;
894 
895 }
896 
897 static const char *parse_create_url_tests[] = {
898 	"http://www.example.com/",
899 	"http://10.0.0.1/",
900 	"http://[::1]/",
901 	"http://www.example.com:993/",
902 	"http://www.example.com/index.html",
903 	"http://www.example.com/settings/index.html",
904 	"http://www.example.com/%23shared/news",
905 	"http://www.example.com/query.php?name=Hendrik%20Visser",
906 	"http://www.example.com/network.html#IMAP%20Server",
907 };
908 
909 static unsigned int
910 parse_create_url_test_count = N_ELEMENTS(parse_create_url_tests);
911 
test_http_url_parse_create(void)912 static void test_http_url_parse_create(void)
913 {
914 	unsigned int i;
915 
916 	for (i = 0; i < parse_create_url_test_count; i++) T_BEGIN {
917 		const char *url = parse_create_url_tests[i];
918 		struct http_url *urlp;
919 		const char *error = NULL;
920 
921 		test_begin(t_strdup_printf("http url parse/create [%d]", i));
922 
923 		if (http_url_parse
924 			(url, NULL, HTTP_URL_ALLOW_FRAGMENT_PART,
925 			 pool_datastack_create(), &urlp, &error) < 0)
926 			urlp = NULL;
927 		test_out_reason(t_strdup_printf("parse  %s", url),
928 				urlp != NULL, error);
929 		if (urlp != NULL) {
930 			const char *urlnew = http_url_create(urlp);
931 			test_out(t_strdup_printf("create %s", urlnew),
932 				 strcmp(url, urlnew) == 0);
933 		}
934 
935 		test_end();
936 	} T_END;
937 
938 }
939 
main(void)940 int main(void)
941 {
942 	static void (*const test_functions[])(void) = {
943 		test_http_url_valid,
944 		test_http_url_invalid,
945 		test_http_url_parse_create,
946 		NULL
947 	};
948 	return test_run(test_functions);
949 }
950 
951