tests/get: use GProxyResolverDefault
[platform/upstream/libsoup.git] / tests / header-parsing.c
1 #include <config.h>
2
3 #include <stdio.h>
4 #include <string.h>
5 #include <unistd.h>
6
7 #include <libsoup/soup.h>
8
9 #include "test-utils.h"
10
11 typedef struct {
12         const char *name, *value;
13 } Header;
14
15 static struct RequestTest {
16         const char *description;
17         const char *request;
18         int length;
19         guint status;
20         const char *method, *path;
21         SoupHTTPVersion version;
22         Header headers[10];
23 } reqtests[] = {
24         /**********************/
25         /*** VALID REQUESTS ***/
26         /**********************/
27
28         { "HTTP 1.0 request with no headers",
29           "GET / HTTP/1.0\r\n", -1,
30           SOUP_STATUS_OK,
31           "GET", "/", SOUP_HTTP_1_0,
32           { { NULL } }
33         },
34
35         { "Req w/ 1 header",
36           "GET / HTTP/1.1\r\nHost: example.com\r\n", -1,
37           SOUP_STATUS_OK,
38           "GET", "/", SOUP_HTTP_1_1,
39           { { "Host", "example.com" },
40             { NULL }
41           }
42         },
43
44         { "Req w/ 1 header, no leading whitespace",
45           "GET / HTTP/1.1\r\nHost:example.com\r\n", -1,
46           SOUP_STATUS_OK,
47           "GET", "/", SOUP_HTTP_1_1,
48           { { "Host", "example.com" },
49             { NULL }
50           }
51         },
52
53         { "Req w/ 1 header including trailing whitespace",
54           "GET / HTTP/1.1\r\nHost: example.com \r\n", -1,
55           SOUP_STATUS_OK,
56           "GET", "/", SOUP_HTTP_1_1,
57           { { "Host", "example.com" },
58             { NULL }
59           }
60         },
61
62         { "Req w/ 1 header, wrapped",
63           "GET / HTTP/1.1\r\nFoo: bar\r\n baz\r\n", -1,
64           SOUP_STATUS_OK,
65           "GET", "/", SOUP_HTTP_1_1,
66           { { "Foo", "bar baz" },
67             { NULL }
68           }
69         },
70
71         { "Req w/ 1 header, wrapped with additional whitespace",
72           "GET / HTTP/1.1\r\nFoo: bar \r\n  baz\r\n", -1,
73           SOUP_STATUS_OK,
74           "GET", "/", SOUP_HTTP_1_1,
75           { { "Foo", "bar baz" },
76             { NULL }
77           }
78         },
79
80         { "Req w/ 1 header, wrapped with tab",
81           "GET / HTTP/1.1\r\nFoo: bar\r\n\tbaz\r\n", -1,
82           SOUP_STATUS_OK,
83           "GET", "/", SOUP_HTTP_1_1,
84           { { "Foo", "bar baz" },
85             { NULL }
86           }
87         },
88
89         { "Req w/ 1 header, wrapped before value",
90           "GET / HTTP/1.1\r\nFoo:\r\n bar baz\r\n", -1,
91           SOUP_STATUS_OK,
92           "GET", "/", SOUP_HTTP_1_1,
93           { { "Foo", "bar baz" },
94             { NULL }
95           }
96         },
97
98         { "Req w/ 1 header with empty value",
99           "GET / HTTP/1.1\r\nHost:\r\n", -1,
100           SOUP_STATUS_OK,
101           "GET", "/", SOUP_HTTP_1_1,
102           { { "Host", "" },
103             { NULL }
104           }
105         },
106
107         { "Req w/ 2 headers",
108           "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n", -1,
109           SOUP_STATUS_OK,
110           "GET", "/", SOUP_HTTP_1_1,
111           { { "Host", "example.com" },
112             { "Connection", "close" },
113             { NULL }
114           }
115         },
116
117         { "Req w/ 3 headers",
118           "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\nBlah: blah\r\n", -1,
119           SOUP_STATUS_OK,
120           "GET", "/", SOUP_HTTP_1_1,
121           { { "Host", "example.com" },
122             { "Connection", "close" },
123             { "Blah", "blah" },
124             { NULL }
125           }
126         },
127
128         { "Req w/ 3 headers, 1st wrapped",
129           "GET / HTTP/1.1\r\nFoo: bar\r\n baz\r\nConnection: close\r\nBlah: blah\r\n", -1,
130           SOUP_STATUS_OK,
131           "GET", "/", SOUP_HTTP_1_1,
132           { { "Foo", "bar baz" },
133             { "Connection", "close" },
134             { "Blah", "blah" },
135             { NULL }
136           }
137         },
138
139         { "Req w/ 3 headers, 2nd wrapped",
140           "GET / HTTP/1.1\r\nConnection: close\r\nBlah: blah\r\nFoo: bar\r\n baz\r\n", -1,
141           SOUP_STATUS_OK,
142           "GET", "/", SOUP_HTTP_1_1,
143           { { "Connection", "close" },
144             { "Blah", "blah" },
145             { "Foo", "bar baz" },
146             { NULL }
147           }
148         },
149
150         { "Req w/ 3 headers, 3rd wrapped",
151           "GET / HTTP/1.1\r\nConnection: close\r\nBlah: blah\r\nFoo: bar\r\n baz\r\n", -1,
152           SOUP_STATUS_OK,
153           "GET", "/", SOUP_HTTP_1_1,
154           { { "Connection", "close" },
155             { "Blah", "blah" },
156             { "Foo", "bar baz" },
157             { NULL }
158           }
159         },
160
161         { "Req w/ same header multiple times",
162           "GET / HTTP/1.1\r\nFoo: bar\r\nFoo: baz\r\nFoo: quux\r\n", -1,
163           SOUP_STATUS_OK,
164           "GET", "/", SOUP_HTTP_1_1,
165           { { "Foo", "bar, baz, quux" },
166             { NULL }
167           }
168         },
169
170         { "Connection header on HTTP/1.0 message",
171           "GET / HTTP/1.0\r\nFoo: bar\r\nConnection: Bar, Quux\r\nBar: baz\r\nQuux: foo\r\n", -1,
172           SOUP_STATUS_OK,
173           "GET", "/", SOUP_HTTP_1_0,
174           { { "Foo", "bar" },
175             { "Connection", "Bar, Quux" },
176             { NULL }
177           }
178         },
179
180         { "GET with full URI",
181           "GET http://example.com HTTP/1.1\r\n", -1,
182           SOUP_STATUS_OK,
183           "GET", "http://example.com", SOUP_HTTP_1_1,
184           { { NULL } }
185         },
186
187         { "GET with full URI in upper-case",
188           "GET HTTP://example.com HTTP/1.1\r\n", -1,
189           SOUP_STATUS_OK,
190           "GET", "HTTP://example.com", SOUP_HTTP_1_1,
191           { { NULL } }
192         },
193
194         /* It's better for this to be passed through: this means a SoupServer
195          * could implement ftp-over-http proxying, for instance
196          */
197         { "GET with full URI of unrecognised scheme",
198           "GET AbOuT: HTTP/1.1\r\n", -1,
199           SOUP_STATUS_OK,
200           "GET", "AbOuT:", SOUP_HTTP_1_1,
201           { { NULL } }
202         },
203
204         /****************************/
205         /*** RECOVERABLE REQUESTS ***/
206         /****************************/
207
208         /* RFC 2616 section 4.1 says we SHOULD accept this */
209
210         { "Spurious leading CRLF",
211           "\r\nGET / HTTP/1.1\r\nHost: example.com\r\n", -1,
212           SOUP_STATUS_OK,
213           "GET", "/", SOUP_HTTP_1_1,
214           { { "Host", "example.com" },
215             { NULL }
216           }
217         },
218
219         /* RFC 2616 section 3.1 says we MUST accept this */
220
221         { "HTTP/01.01 request",
222           "GET / HTTP/01.01\r\nHost: example.com\r\n", -1,
223           SOUP_STATUS_OK,
224           "GET", "/", SOUP_HTTP_1_1,
225           { { "Host", "example.com" },
226             { NULL }
227           }
228         },
229
230         /* RFC 2616 section 19.3 says we SHOULD accept these */
231
232         { "LF instead of CRLF after header",
233           "GET / HTTP/1.1\r\nHost: example.com\nConnection: close\n", -1,
234           SOUP_STATUS_OK,
235           "GET", "/", SOUP_HTTP_1_1,
236           { { "Host", "example.com" },
237             { "Connection", "close" },
238             { NULL }
239           }
240         },
241
242         { "LF instead of CRLF after Request-Line",
243           "GET / HTTP/1.1\nHost: example.com\r\n", -1,
244           SOUP_STATUS_OK,
245           "GET", "/", SOUP_HTTP_1_1,
246           { { "Host", "example.com" },
247             { NULL }
248           }
249         },
250
251         { "Mixed CRLF/LF",
252           "GET / HTTP/1.1\r\na: b\r\nc: d\ne: f\r\ng: h\n", -1,
253           SOUP_STATUS_OK,
254           "GET", "/", SOUP_HTTP_1_1,
255           { { "a", "b" },
256             { "c", "d" },
257             { "e", "f" },
258             { "g", "h" },
259             { NULL }
260           }
261         },
262
263         { "Req w/ incorrect whitespace in Request-Line",
264           "GET  /\tHTTP/1.1\r\nHost: example.com\r\n", -1,
265           SOUP_STATUS_OK,
266           "GET", "/", SOUP_HTTP_1_1,
267           { { "Host", "example.com" },
268             { NULL }
269           }
270         },
271
272         { "Req w/ incorrect whitespace after Request-Line",
273           "GET / HTTP/1.1 \r\nHost: example.com\r\n", -1,
274           SOUP_STATUS_OK,
275           "GET", "/", SOUP_HTTP_1_1,
276           { { "Host", "example.com" },
277             { NULL }
278           }
279         },
280
281         /* If the request/status line is parseable, then we
282          * just ignore any invalid-looking headers after that.
283          * (qv bug 579318).
284          */
285
286         { "Req w/ mangled header",
287           "GET / HTTP/1.1\r\nHost: example.com\r\nFoo one\r\nBar: two\r\n", -1,
288           SOUP_STATUS_OK,
289           "GET", "/", SOUP_HTTP_1_1,
290           { { "Host", "example.com" },
291             { "Bar", "two" },
292             { NULL }
293           }
294         },
295
296         { "First header line is continuation",
297           "GET / HTTP/1.1\r\n b\r\nHost: example.com\r\nc: d\r\n", -1,
298           SOUP_STATUS_OK,
299           "GET", "/", SOUP_HTTP_1_1,
300           { { "Host", "example.com" },
301             { "c", "d" },
302             { NULL }
303           }
304         },
305
306         { "Zero-length header name",
307           "GET / HTTP/1.1\r\na: b\r\n: example.com\r\nc: d\r\n", -1,
308           SOUP_STATUS_OK,
309           "GET", "/", SOUP_HTTP_1_1,
310           { { "a", "b" },
311             { "c", "d" },
312             { NULL }
313           }
314         },
315
316         { "CR in header name",
317           "GET / HTTP/1.1\r\na: b\r\na\rb: cd\r\nx\r: y\r\n\rz: w\r\nc: d\r\n", -1,
318           SOUP_STATUS_OK,
319           "GET", "/", SOUP_HTTP_1_1,
320           { { "a", "b" },
321             { "c", "d" },
322             { NULL }
323           }
324         },
325
326         { "CR in header value",
327           "GET / HTTP/1.1\r\na: b\r\nHost: example\rcom\r\np: \rq\r\ns: t\r\r\nc: d\r\n", -1,
328           SOUP_STATUS_OK,
329           "GET", "/", SOUP_HTTP_1_1,
330           { { "a", "b" },
331             { "Host", "example com" },  /* CR in the middle turns to space */
332             { "p", "q" },               /* CR at beginning is ignored */
333             { "s", "t" },               /* CR at end is ignored */
334             { "c", "d" },
335             { NULL }
336           }
337         },
338
339         { "Tab in header name",
340           "GET / HTTP/1.1\r\na: b\r\na\tb: cd\r\nx\t: y\r\np: q\r\n\tz: w\r\nc: d\r\n", -1,
341           SOUP_STATUS_OK,
342           "GET", "/", SOUP_HTTP_1_1,
343           { { "a", "b" },
344             /* Tab anywhere in the header name causes it to be
345              * ignored... except at beginning of line where it's a
346              * continuation line
347              */
348             { "p", "q z: w" },
349             { "c", "d" },
350             { NULL }
351           }
352         },
353
354         { "Tab in header value",
355           "GET / HTTP/1.1\r\na: b\r\nab: c\td\r\nx: \ty\r\nz: w\t\r\nc: d\r\n", -1,
356           SOUP_STATUS_OK,
357           "GET", "/", SOUP_HTTP_1_1,
358           { { "a", "b" },
359             { "ab", "c\td" },   /* internal tab preserved */
360             { "x", "y" },       /* leading tab ignored */
361             { "z", "w" },       /* trailing tab ignored */
362             { "c", "d" },
363             { NULL }
364           }
365         },
366
367         /************************/
368         /*** INVALID REQUESTS ***/
369         /************************/
370
371         { "HTTP 0.9 request; not supported",
372           "GET /\r\n", -1,
373           SOUP_STATUS_BAD_REQUEST,
374           NULL, NULL, -1,
375           { { NULL } }
376         },
377
378         { "HTTP 1.2 request (no such thing)",
379           "GET / HTTP/1.2\r\n", -1,
380           SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
381           NULL, NULL, -1,
382           { { NULL } }
383         },
384
385         { "HTTP 2000 request (no such thing)",
386           "GET / HTTP/2000.0\r\n", -1,
387           SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
388           NULL, NULL, -1,
389           { { NULL } }
390         },
391
392         { "Non-HTTP request",
393           "GET / SOUP/1.1\r\nHost: example.com\r\n", -1,
394           SOUP_STATUS_BAD_REQUEST,
395           NULL, NULL, -1,
396           { { NULL } }
397         },
398
399         { "Junk after Request-Line",
400           "GET / HTTP/1.1 blah\r\nHost: example.com\r\n", -1,
401           SOUP_STATUS_BAD_REQUEST,
402           NULL, NULL, -1,
403           { { NULL } }
404         },
405
406         { "NUL in Method",
407           "G\x00T / HTTP/1.1\r\nHost: example.com\r\n", 37,
408           SOUP_STATUS_BAD_REQUEST,
409           NULL, NULL, -1,
410           { { NULL } }
411         },
412
413         { "NUL at beginning of Method",
414           "\x00 / HTTP/1.1\r\nHost: example.com\r\n", 35,
415           SOUP_STATUS_BAD_REQUEST,
416           NULL, NULL, -1,
417           { { NULL } }
418         },
419
420         { "NUL in Path",
421           "GET /\x00 HTTP/1.1\r\nHost: example.com\r\n", 38,
422           SOUP_STATUS_BAD_REQUEST,
423           NULL, NULL, -1,
424           { { NULL } }
425         },
426
427         { "NUL in header name",
428           "GET / HTTP/1.1\r\n\x00: silly\r\n", 37,
429           SOUP_STATUS_BAD_REQUEST,
430           NULL, NULL, -1,
431           { { NULL } }
432         },
433
434         { "NUL in header value",
435           "GET / HTTP/1.1\r\nHost: example\x00com\r\n", 37,
436           SOUP_STATUS_BAD_REQUEST,
437           NULL, NULL, -1,
438           { { NULL } }
439         },
440
441         { "No terminating CRLF",
442           "GET / HTTP/1.1\r\nHost: example.com", -1,
443           SOUP_STATUS_BAD_REQUEST,
444           NULL, NULL, -1,
445           { { NULL } }
446         },
447
448         { "Unrecognized expectation",
449           "GET / HTTP/1.1\r\nHost: example.com\r\nExpect: the-impossible\r\n", -1,
450           SOUP_STATUS_EXPECTATION_FAILED,
451           NULL, NULL, -1,
452           { { NULL } }
453         }
454 };
455 static const int num_reqtests = G_N_ELEMENTS (reqtests);
456
457 static struct ResponseTest {
458         const char *description;
459         const char *response;
460         int length;
461         SoupHTTPVersion version;
462         guint status_code;
463         const char *reason_phrase;
464         Header headers[4];
465 } resptests[] = {
466         /***********************/
467         /*** VALID RESPONSES ***/
468         /***********************/
469
470         { "HTTP 1.0 response w/ no headers",
471           "HTTP/1.0 200 ok\r\n", -1,
472           SOUP_HTTP_1_0, SOUP_STATUS_OK, "ok",
473           { { NULL } }
474         },
475
476         { "HTTP 1.1 response w/ no headers",
477           "HTTP/1.1 200 ok\r\n", -1,
478           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
479           { { NULL } }
480         },
481
482         { "Response w/ multi-word Reason-Phrase",
483           "HTTP/1.1 400 bad request\r\n", -1,
484           SOUP_HTTP_1_1, SOUP_STATUS_BAD_REQUEST, "bad request",
485           { { NULL } }
486         },
487
488         { "Response w/ 1 header",
489           "HTTP/1.1 200 ok\r\nFoo: bar\r\n", -1,
490           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
491           { { "Foo", "bar" },
492             { NULL }
493           }
494         },
495
496         { "Response w/ 2 headers",
497           "HTTP/1.1 200 ok\r\nFoo: bar\r\nBaz: quux\r\n", -1,
498           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
499           { { "Foo", "bar" },
500             { "Baz", "quux" },
501             { NULL }
502           }
503         },
504
505         { "Response w/ same header multiple times",
506           "HTTP/1.1 200 ok\r\nFoo: bar\r\nFoo: baz\r\nFoo: quux\r\n", -1,
507           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
508           { { "Foo", "bar, baz, quux" },
509             { NULL }
510           }
511         },
512
513         { "Response w/ no reason phrase",
514           "HTTP/1.1 200 \r\nFoo: bar\r\n", -1,
515           SOUP_HTTP_1_1, SOUP_STATUS_OK, "",
516           { { "Foo", "bar" },
517             { NULL }
518           }
519         },
520
521         { "Connection header on HTTP/1.0 message",
522           "HTTP/1.0 200 ok\r\nFoo: bar\r\nConnection: Bar\r\nBar: quux\r\n", -1,
523           SOUP_HTTP_1_0, SOUP_STATUS_OK, "ok",
524           { { "Foo", "bar" },
525             { "Connection", "Bar" },
526             { NULL }
527           }
528         },
529
530         /*****************************/
531         /*** RECOVERABLE RESPONSES ***/
532         /*****************************/
533
534         /* RFC 2616 section 3.1 says we MUST accept this */
535
536         { "HTTP/01.01 response",
537           "HTTP/01.01 200 ok\r\nFoo: bar\r\n", -1,
538           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
539           { { "Foo", "bar" },
540             { NULL }
541           }
542         },
543
544         /* RFC 2616 section 19.3 says we SHOULD accept these */
545
546         { "Response w/ LF instead of CRLF after Status-Line",
547           "HTTP/1.1 200 ok\nFoo: bar\r\n", -1,
548           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
549           { { "Foo", "bar" },
550             { NULL }
551           }
552         },
553
554         { "Response w/ incorrect spacing in Status-Line",
555           "HTTP/1.1  200\tok\r\nFoo: bar\r\n", -1,
556           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
557           { { "Foo", "bar" },
558             { NULL }
559           }
560         },
561
562         { "Response w/ no reason phrase or preceding SP",
563           "HTTP/1.1 200\r\nFoo: bar\r\n", -1,
564           SOUP_HTTP_1_1, SOUP_STATUS_OK, "",
565           { { "Foo", "bar" },
566             { NULL }
567           }
568         },
569
570         { "Response w/ no whitespace after status code",
571           "HTTP/1.1 200ok\r\nFoo: bar\r\n", -1,
572           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
573           { { "Foo", "bar" },
574             { NULL }
575           }
576         },
577
578         /* Shoutcast support */
579         { "Shoutcast server not-quite-HTTP",
580           "ICY 200 OK\r\nFoo: bar\r\n", -1,
581           SOUP_HTTP_1_0, SOUP_STATUS_OK, "OK",
582           { { "Foo", "bar" },
583             { NULL }
584           }
585         },
586
587         /* qv bug 579318, do_bad_header_tests() below */
588         { "Response w/ mangled header",
589           "HTTP/1.1 200 ok\r\nFoo: one\r\nBar two:2\r\nBaz: three\r\n", -1,
590           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
591           { { "Foo", "one" },
592             { "Baz", "three" },
593             { NULL }
594           }
595         },
596
597         /* qv bug 602863 */
598         { "HTTP 1.1 response with leading line break",
599           "\nHTTP/1.1 200 ok\r\nFoo: bar\r\n", -1,
600           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
601           { { "Foo", "bar" },
602             { NULL } }
603         },
604
605         /*************************/
606         /*** INVALID RESPONSES ***/
607         /*************************/
608
609         { "Invalid HTTP version",
610           "HTTP/1.2 200 OK\r\nFoo: bar\r\n", -1,
611           -1, 0, NULL,
612           { { NULL } }
613         },
614
615         { "Non-HTTP response",
616           "SOUP/1.1 200 OK\r\nFoo: bar\r\n", -1,
617           -1, 0, NULL,
618           { { NULL } }
619         },
620
621         { "Non-numeric status code",
622           "HTTP/1.1 XXX OK\r\nFoo: bar\r\n", -1,
623           -1, 0, NULL,
624           { { NULL } }
625         },
626
627         { "No status code",
628           "HTTP/1.1 OK\r\nFoo: bar\r\n", -1,
629           -1, 0, NULL,
630           { { NULL } }
631         },
632
633         { "One-digit status code",
634           "HTTP/1.1 2 OK\r\nFoo: bar\r\n", -1,
635           -1, 0, NULL,
636           { { NULL } }
637         },
638
639         { "Two-digit status code",
640           "HTTP/1.1 20 OK\r\nFoo: bar\r\n", -1,
641           -1, 0, NULL,
642           { { NULL } }
643         },
644
645         { "Four-digit status code",
646           "HTTP/1.1 2000 OK\r\nFoo: bar\r\n", -1,
647           -1, 0, NULL,
648           { { NULL } }
649         },
650
651         { "Status code < 100",
652           "HTTP/1.1 001 OK\r\nFoo: bar\r\n", -1,
653           -1, 0, NULL,
654           { { NULL } }
655         },
656
657         { "Status code > 599",
658           "HTTP/1.1 600 OK\r\nFoo: bar\r\n", -1,
659           -1, 0, NULL,
660           { { NULL } }
661         },
662
663         { "NUL at start",
664           "\x00HTTP/1.1 200 OK\r\nFoo: bar\r\n", 28,
665           -1, 0, NULL,
666           { { NULL } }
667         },
668
669         { "NUL in Reason Phrase",
670           "HTTP/1.1 200 O\x00K\r\nFoo: bar\r\n", 28,
671           -1, 0, NULL,
672           { { NULL } }
673         },
674
675         { "NUL in header name",
676           "HTTP/1.1 200 OK\r\nF\x00oo: bar\r\n", 28,
677           -1, 0, NULL,
678           { { NULL } }
679         },
680
681         { "NUL in header value",
682           "HTTP/1.1 200 OK\r\nFoo: b\x00ar\r\n", 28,
683           -1, 0, NULL,
684           { { NULL } }
685         },
686 };
687 static const int num_resptests = G_N_ELEMENTS (resptests);
688
689 static struct QValueTest {
690         const char *header_value;
691         const char *acceptable[7];
692         const char *unacceptable[2];
693 } qvaluetests[] = {
694         { "text/plain; q=0.5, text/html,\t  text/x-dvi; q=0.8, text/x-c",
695           { "text/html", "text/x-c", "text/x-dvi", "text/plain", NULL },
696           { NULL },
697         },
698
699         { "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5",
700           { "text/html;level=1", "text/html", "*/*", "text/html;level=2",
701             "text/*", NULL },
702           { NULL }
703         },
704
705         { "gzip;q=1.0, identity; q=0.5, *;q=0",
706           { "gzip", "identity", NULL },
707           { "*", NULL },
708         }
709 };
710 static const int num_qvaluetests = G_N_ELEMENTS (qvaluetests);
711
712 static void
713 print_header (const char *name, const char *value, gpointer data)
714 {
715         debug_printf (1, "              '%s': '%s'\n", name, value);
716 }
717
718 static gboolean
719 check_headers (Header *headers, SoupMessageHeaders *hdrs)
720 {
721         GSList *header_names, *h;
722         SoupMessageHeadersIter iter;
723         const char *name, *value;
724         gboolean ok = TRUE;
725         int i;
726
727         header_names = NULL;
728         soup_message_headers_iter_init (&iter, hdrs);
729         while (soup_message_headers_iter_next (&iter, &name, &value)) {
730                 if (!g_slist_find_custom (header_names, name,
731                                           (GCompareFunc)strcmp))
732                         header_names = g_slist_append (header_names, (char *)name);
733         }
734
735         for (i = 0, h = header_names; headers[i].name && h; i++, h = h->next) {
736                 if (strcmp (h->data, headers[i].name) != 0) {
737                         ok = FALSE;
738                         break;
739                 }
740                 value = soup_message_headers_get_list (hdrs, headers[i].name);
741                 if (strcmp (value, headers[i].value) != 0) {
742                         ok = FALSE;
743                         break;
744                 }
745         }
746         if (headers[i].name || h)
747                 ok = FALSE;
748         g_slist_free (header_names);
749         return ok;
750 }
751
752 static void
753 do_request_tests (void)
754 {
755         int i, len, h;
756         char *method, *path;
757         SoupHTTPVersion version;
758         SoupMessageHeaders *headers;
759         guint status;
760
761         debug_printf (1, "Request tests\n");
762         for (i = 0; i < num_reqtests; i++) {
763                 gboolean ok = TRUE;
764
765                 debug_printf (1, "%2d. %s (%s): ", i + 1, reqtests[i].description,
766                               soup_status_get_phrase (reqtests[i].status));
767
768                 headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_REQUEST);
769                 method = path = NULL;
770
771                 if (reqtests[i].length == -1)
772                         len = strlen (reqtests[i].request);
773                 else
774                         len = reqtests[i].length;
775                 status = soup_headers_parse_request (reqtests[i].request, len,
776                                                      headers, &method, &path,
777                                                      &version);
778                 if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
779                         if ((reqtests[i].method && strcmp (reqtests[i].method, method) != 0) || !reqtests[i].method)
780                                 ok = FALSE;
781                         if ((reqtests[i].path && strcmp (reqtests[i].path, path) != 0) || !reqtests[i].path)
782                                 ok = FALSE;
783                         if (reqtests[i].version != version)
784                                 ok = FALSE;
785
786                         if (!check_headers (reqtests[i].headers, headers))
787                                 ok = FALSE;
788                 } else {
789                         if (status != reqtests[i].status)
790                                 ok = FALSE;
791                 }
792
793                 if (ok)
794                         debug_printf (1, "OK!\n");
795                 else {
796                         debug_printf (1, "BAD!\n");
797                         errors++;
798                         if (reqtests[i].method) {
799                                 debug_printf (1, "    expected: '%s' '%s' 'HTTP/1.%d'\n",
800                                               reqtests[i].method,
801                                               reqtests[i].path,
802                                               reqtests[i].version);
803                                 for (h = 0; reqtests[i].headers[h].name; h++) {
804                                         debug_printf (1, "              '%s': '%s'\n",
805                                                       reqtests[i].headers[h].name,
806                                                       reqtests[i].headers[h].value);
807                                 }
808                         } else {
809                                 debug_printf (1, "    expected: %s\n",
810                                               soup_status_get_phrase (reqtests[i].status));
811                         }
812                         if (method) {
813                                 debug_printf (1, "         got: '%s' '%s' 'HTTP/1.%d'\n",
814                                               method, path, version);
815                                 soup_message_headers_foreach (headers, print_header, NULL);
816                         } else {
817                                 debug_printf (1, "         got: %s\n",
818                                               soup_status_get_phrase (status));
819                         }
820                 }
821
822                 g_free (method);
823                 g_free (path);
824                 soup_message_headers_free (headers);
825         }
826         debug_printf (1, "\n");
827 }
828
829 static void
830 do_response_tests (void)
831 {
832         int i, len, h;
833         guint status_code;
834         char *reason_phrase;
835         SoupHTTPVersion version;
836         SoupMessageHeaders *headers;
837
838         debug_printf (1, "Response tests\n");
839         for (i = 0; i < num_resptests; i++) {
840                 gboolean ok = TRUE;
841
842                 debug_printf (1, "%2d. %s (%s): ", i + 1, resptests[i].description,
843                               resptests[i].reason_phrase ? "should parse" : "should NOT parse");
844
845                 headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
846                 reason_phrase = NULL;
847
848                 if (resptests[i].length == -1)
849                         len = strlen (resptests[i].response);
850                 else
851                         len = resptests[i].length;
852                 if (soup_headers_parse_response (resptests[i].response, len,
853                                                  headers, &version,
854                                                  &status_code, &reason_phrase)) {
855                         if (resptests[i].version != version)
856                                 ok = FALSE;
857                         if (resptests[i].status_code != status_code)
858                                 ok = FALSE;
859                         if ((resptests[i].reason_phrase && strcmp (resptests[i].reason_phrase, reason_phrase) != 0) || !resptests[i].reason_phrase)
860                                 ok = FALSE;
861
862                         if (!check_headers (resptests[i].headers, headers))
863                                 ok = FALSE;
864                 } else {
865                         if (resptests[i].reason_phrase)
866                                 ok = FALSE;
867                 }
868
869                 if (ok)
870                         debug_printf (1, "OK!\n");
871                 else {
872                         debug_printf (1, "BAD!\n");
873                         errors++;
874                         if (resptests[i].reason_phrase) {
875                                 debug_printf (1, "    expected: 'HTTP/1.%d' '%03d' '%s'\n",
876                                               resptests[i].version,
877                                               resptests[i].status_code,
878                                               resptests[i].reason_phrase);
879                                 for (h = 0; resptests[i].headers[h].name; h++) {
880                                         debug_printf (1, "              '%s': '%s'\n",
881                                                       resptests[i].headers[h].name,
882                                                       resptests[i].headers[h].value);
883                                 }
884                         } else
885                                 debug_printf (1, "    expected: parse error\n");
886                         if (reason_phrase) {
887                                 debug_printf (1, "         got: 'HTTP/1.%d' '%03d' '%s'\n",
888                                               version, status_code, reason_phrase);
889                                 soup_message_headers_foreach (headers, print_header, NULL);
890                         } else
891                                 debug_printf (1, "         got: parse error\n");
892                 }
893
894                 g_free (reason_phrase);
895                 soup_message_headers_free (headers);
896         }
897         debug_printf (1, "\n");
898 }
899
900 static void
901 do_qvalue_tests (void)
902 {
903         int i, j;
904         GSList *acceptable, *unacceptable, *iter;
905         gboolean wrong;
906
907         debug_printf (1, "qvalue tests\n");
908         for (i = 0; i < num_qvaluetests; i++) {
909                 debug_printf (1, "%2d. %s:\n", i + 1, qvaluetests[i].header_value);
910
911                 unacceptable = NULL;
912                 acceptable = soup_header_parse_quality_list (qvaluetests[i].header_value,
913                                                              &unacceptable);
914
915                 debug_printf (1, "    acceptable: ");
916                 wrong = FALSE;
917                 if (acceptable) {
918                         for (iter = acceptable, j = 0; iter; iter = iter->next, j++) {
919                                 debug_printf (1, "%s ", (char *)iter->data);
920                                 if (!qvaluetests[i].acceptable[j] ||
921                                     strcmp (iter->data, qvaluetests[i].acceptable[j]) != 0)
922                                         wrong = TRUE;
923                         }
924                         debug_printf (1, "\n");
925                         soup_header_free_list (acceptable);
926                 } else
927                         debug_printf (1, "(none)\n");
928                 if (wrong) {
929                         debug_printf (1, "    WRONG! expected: ");
930                         for (j = 0; qvaluetests[i].acceptable[j]; j++)
931                                 debug_printf (1, "%s ", qvaluetests[i].acceptable[j]);
932                         debug_printf (1, "\n");
933                         errors++;
934                 }
935
936                 debug_printf (1, "  unacceptable: ");
937                 wrong = FALSE;
938                 if (unacceptable) {
939                         for (iter = unacceptable, j = 0; iter; iter = iter->next, j++) {
940                                 debug_printf (1, "%s ", (char *)iter->data);
941                                 if (!qvaluetests[i].unacceptable[j] ||
942                                     strcmp (iter->data, qvaluetests[i].unacceptable[j]) != 0)
943                                         wrong = TRUE;
944                         }
945                         debug_printf (1, "\n");
946                         soup_header_free_list (unacceptable);
947                 } else
948                         debug_printf (1, "(none)\n");
949                 if (wrong) {
950                         debug_printf (1, "    WRONG! expected: ");
951                         for (j = 0; qvaluetests[i].unacceptable[j]; j++)
952                                 debug_printf (1, "%s ", qvaluetests[i].unacceptable[j]);
953                         debug_printf (1, "\n");
954                         errors++;
955                 }
956
957                 debug_printf (1, "\n");
958         }
959 }
960
961 #define RFC5987_TEST_FILENAME "t\xC3\xA9st.txt"
962 #define RFC5987_TEST_FALLBACK_FILENAME "test.txt"
963
964 #define RFC5987_TEST_HEADER_ENCODED  "attachment; filename*=UTF-8''t%C3%A9st.txt"
965
966 #define RFC5987_TEST_HEADER_UTF8     "attachment; filename*=UTF-8''t%C3%A9st.txt; filename=\"test.txt\""
967 #define RFC5987_TEST_HEADER_ISO      "attachment; filename=\"test.txt\"; filename*=iso-8859-1''t%E9st.txt"
968 #define RFC5987_TEST_HEADER_FALLBACK "attachment; filename*=Unknown''t%FF%FF%FFst.txt; filename=\"test.txt\""
969
970 static void
971 do_content_disposition_tests (void)
972 {
973         SoupMessageHeaders *hdrs;
974         GHashTable *params;
975         const char *header, *filename;
976         char *disposition;
977         SoupBuffer *buffer;
978         SoupMultipart *multipart;
979         SoupMessageBody *body;
980
981         debug_printf (1, "Content-Disposition tests\n");
982
983         hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
984         params = g_hash_table_new (g_str_hash, g_str_equal);
985         g_hash_table_insert (params, "filename", RFC5987_TEST_FILENAME);
986         soup_message_headers_set_content_disposition (hdrs, "attachment", params);
987         g_hash_table_destroy (params);
988
989         header = soup_message_headers_get_one (hdrs, "Content-Disposition");
990         if (!strcmp (header, RFC5987_TEST_HEADER_ENCODED))
991                 debug_printf (1, "  encoded OK\n");
992         else {
993                 debug_printf (1, "  encoding FAILED!\n    expected: %s\n    got:      %s\n",
994                               RFC5987_TEST_HEADER_ENCODED, header);
995                 errors++;
996         }
997
998         /* UTF-8 decoding */
999         soup_message_headers_clear (hdrs);
1000         soup_message_headers_append (hdrs, "Content-Disposition",
1001                                      RFC5987_TEST_HEADER_UTF8);
1002         if (!soup_message_headers_get_content_disposition (hdrs,
1003                                                            &disposition,
1004                                                            &params)) {
1005                 debug_printf (1, "  UTF-8 decoding FAILED!\n    could not parse\n");
1006                 errors++;
1007                 return;
1008         }
1009         g_free (disposition);
1010
1011         filename = g_hash_table_lookup (params, "filename");
1012         if (!filename) {
1013                 debug_printf (1, "  UTF-8 decoding FAILED!\n    could not find filename\n");
1014                 errors++;
1015         } else if (strcmp (filename, RFC5987_TEST_FILENAME) != 0) {
1016                 debug_printf (1, "  UTF-8 decoding FAILED!\n    expected: %s\n    got:      %s\n",
1017                               RFC5987_TEST_FILENAME, filename);
1018                 errors++;
1019         } else
1020                 debug_printf (1, "  UTF-8 decoded OK\n");
1021         g_hash_table_destroy (params);
1022
1023         /* ISO-8859-1 decoding */
1024         soup_message_headers_clear (hdrs);
1025         soup_message_headers_append (hdrs, "Content-Disposition",
1026                                      RFC5987_TEST_HEADER_ISO);
1027         if (!soup_message_headers_get_content_disposition (hdrs,
1028                                                            &disposition,
1029                                                            &params)) {
1030                 debug_printf (1, "  iso-8859-1 decoding FAILED!\n    could not parse\n");
1031                 errors++;
1032                 return;
1033         }
1034         g_free (disposition);
1035
1036         filename = g_hash_table_lookup (params, "filename");
1037         if (!filename) {
1038                 debug_printf (1, "  iso-8859-1 decoding FAILED!\n    could not find filename\n");
1039                 errors++;
1040         } else if (strcmp (filename, RFC5987_TEST_FILENAME) != 0) {
1041                 debug_printf (1, "  iso-8859-1 decoding FAILED!\n    expected: %s\n    got:      %s\n",
1042                               RFC5987_TEST_FILENAME, filename);
1043                 errors++;
1044         } else
1045                 debug_printf (1, "  iso-8859-1 decoded OK\n");
1046         g_hash_table_destroy (params);
1047
1048         /* Fallback */
1049         soup_message_headers_clear (hdrs);
1050         soup_message_headers_append (hdrs, "Content-Disposition",
1051                                      RFC5987_TEST_HEADER_FALLBACK);
1052         if (!soup_message_headers_get_content_disposition (hdrs,
1053                                                            &disposition,
1054                                                            &params)) {
1055                 debug_printf (1, "  fallback decoding FAILED!\n    could not parse\n");
1056                 errors++;
1057                 return;
1058         }
1059         g_free (disposition);
1060
1061         filename = g_hash_table_lookup (params, "filename");
1062         if (!filename) {
1063                 debug_printf (1, "  fallback decoding FAILED!\n    could not find filename\n");
1064                 errors++;
1065         } else if (strcmp (filename, RFC5987_TEST_FALLBACK_FILENAME) != 0) {
1066                 debug_printf (1, "  fallback decoding FAILED!\n    expected: %s\n    got:      %s\n",
1067                               RFC5987_TEST_FALLBACK_FILENAME, filename);
1068                 errors++;
1069         } else
1070                 debug_printf (1, "  fallback decoded OK\n");
1071         g_hash_table_destroy (params);
1072
1073         soup_message_headers_free (hdrs);
1074
1075         /* Ensure that soup-multipart always quotes filename (bug 641280) */
1076         multipart = soup_multipart_new (SOUP_FORM_MIME_TYPE_MULTIPART);
1077         buffer = soup_buffer_new (SOUP_MEMORY_STATIC, "foo", 3);
1078         soup_multipart_append_form_file (multipart, "test", "token",
1079                                          "text/plain", buffer);
1080         soup_buffer_free (buffer);
1081
1082         hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
1083         body = soup_message_body_new ();
1084         soup_multipart_to_message (multipart, hdrs, body);
1085         soup_message_headers_free (hdrs);
1086         soup_multipart_free (multipart);
1087
1088         buffer = soup_message_body_flatten (body);
1089         soup_message_body_free (body);
1090
1091         if (strstr (buffer->data, "filename=\"token\""))
1092                 debug_printf (1, "  SoupMultipart encoded filename correctly\n");
1093         else {
1094                 debug_printf (1, "  SoupMultipart encoded filename incorrectly!\n");
1095                 errors++;
1096         }
1097         soup_buffer_free (buffer);
1098
1099         debug_printf (1, "\n");
1100 }
1101
1102 #define CONTENT_TYPE_TEST_MIME_TYPE "text/plain"
1103 #define CONTENT_TYPE_TEST_ATTRIBUTE "charset"
1104 #define CONTENT_TYPE_TEST_VALUE     "US-ASCII"
1105 #define CONTENT_TYPE_TEST_HEADER    "text/plain; charset=US-ASCII"
1106
1107 #define CONTENT_TYPE_BAD_HEADER     "plain text, not text/html"
1108
1109 static void
1110 do_content_type_tests (void)
1111 {
1112         SoupMessageHeaders *hdrs;
1113         GHashTable *params;
1114         const char *header, *mime_type;
1115
1116         debug_printf (1, "Content-Type tests\n");
1117
1118         hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
1119         params = g_hash_table_new (g_str_hash, g_str_equal);
1120         g_hash_table_insert (params, CONTENT_TYPE_TEST_ATTRIBUTE,
1121                              CONTENT_TYPE_TEST_VALUE);
1122         soup_message_headers_set_content_type (hdrs, CONTENT_TYPE_TEST_MIME_TYPE, params);
1123         g_hash_table_destroy (params);
1124
1125         header = soup_message_headers_get_one (hdrs, "Content-Type");
1126         if (!strcmp (header, CONTENT_TYPE_TEST_HEADER))
1127                 debug_printf (1, "  encoded OK\n");
1128         else {
1129                 debug_printf (1, "  encoding FAILED!\n    expected: %s\n    got:      %s\n",
1130                               CONTENT_TYPE_TEST_HEADER, header);
1131                 errors++;
1132         }
1133
1134         soup_message_headers_clear (hdrs);
1135         soup_message_headers_append (hdrs, "Content-Type",
1136                                      CONTENT_TYPE_TEST_MIME_TYPE);
1137         /* Add a second Content-Type header: should be ignored */
1138         soup_message_headers_append (hdrs, "Content-Type",
1139                                      CONTENT_TYPE_TEST_MIME_TYPE);
1140
1141         mime_type = soup_message_headers_get_content_type (hdrs, &params);
1142         if (!mime_type) {
1143                 debug_printf (1, "  decoding FAILED!\n    could not parse\n");
1144                 errors++;
1145         }
1146
1147         if (mime_type && strcmp (mime_type, CONTENT_TYPE_TEST_MIME_TYPE) != 0) {
1148                 debug_printf (1, "  decoding FAILED!\n    bad returned MIME type: %s\n",
1149                               mime_type);
1150                 errors++;
1151         } else if (params && g_hash_table_size (params) != 0) {
1152                 debug_printf (1, "  decoding FAILED!\n    params contained %d params (should be 0)\n",
1153                               g_hash_table_size (params));
1154                 errors++;
1155         } else
1156                 debug_printf (1, "  decoded OK\n");
1157
1158         if (params)
1159                 g_hash_table_destroy (params);
1160
1161         soup_message_headers_clear (hdrs);
1162         soup_message_headers_append (hdrs, "Content-Type",
1163                                      CONTENT_TYPE_BAD_HEADER);
1164         mime_type = soup_message_headers_get_content_type (hdrs, &params);
1165         if (mime_type) {
1166                 debug_printf (1, "  Bad content rejection FAILED!\n");
1167                 errors++;
1168         } else
1169                 debug_printf (1, "  Bad content rejection OK\n");
1170
1171         soup_message_headers_free (hdrs);
1172
1173         debug_printf (1, "\n");
1174 }
1175
1176 struct {
1177         const char *name, *value;
1178 } test_params[] = {
1179         { "one", "foo" },
1180         { "two", "test with spaces" },
1181         { "three", "test with \"quotes\" and \\s" },
1182         { "four", NULL },
1183         { "five", "test with \xC3\xA1\xC3\xA7\xC4\x89\xC3\xA8\xC3\xB1\xC5\xA3\xC5\xA1" }
1184 };
1185
1186 #define TEST_PARAMS_RESULT "one=foo, two=\"test with spaces\", three=\"test with \\\"quotes\\\" and \\\\s\", four, five*=UTF-8''test%20with%20%C3%A1%C3%A7%C4%89%C3%A8%C3%B1%C5%A3%C5%A1"
1187
1188 static void
1189 do_append_param_tests (void)
1190 {
1191         GString *params;
1192         int i;
1193
1194         debug_printf (1, "soup_header_g_string_append_param() tests\n");
1195
1196         params = g_string_new (NULL);
1197         for (i = 0; i < G_N_ELEMENTS (test_params); i++) {
1198                 if (i > 0)
1199                         g_string_append (params, ", ");
1200                 soup_header_g_string_append_param (params,
1201                                                    test_params[i].name,
1202                                                    test_params[i].value);
1203         }
1204         if (strcmp (params->str, TEST_PARAMS_RESULT) != 0) {
1205                 debug_printf (1, "  FAILED!\n    expected: %s\n    got: %s\n",
1206                               TEST_PARAMS_RESULT, params->str);
1207                 errors++;
1208         } else
1209                 debug_printf (1, "  OK\n");
1210         g_string_free (params, TRUE);
1211
1212         debug_printf (1, "\n");
1213 }
1214
1215 static const struct {
1216         const char *description, *name, *value;
1217 } bad_headers[] = {
1218         { "Empty name", "", "value" },
1219         { "Name with spaces", "na me", "value" },
1220         { "Name with colon", "na:me", "value" },
1221         { "Name with CR", "na\rme", "value" },
1222         { "Name with LF", "na\nme", "value" },
1223         { "Name with tab", "na\tme", "value" },
1224         { "Value with CR", "name", "val\rue" },
1225         { "Value with LF", "name", "val\nue" },
1226         { "Value with LWS", "name", "val\r\n ue" }
1227 };
1228
1229 static void
1230 do_bad_header_tests (void)
1231 {
1232         SoupMessageHeaders *hdrs;
1233         int i;
1234
1235         debug_printf (1, "bad header rejection tests\n");
1236
1237         hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
1238         for (i = 0; i < G_N_ELEMENTS (bad_headers); i++) {
1239                 debug_printf (1, "  %s\n", bad_headers[i].description);
1240                 expect_warning = TRUE;
1241                 soup_message_headers_append (hdrs, bad_headers[i].name,
1242                                              bad_headers[i].value);
1243                 if (expect_warning) {
1244                         expect_warning = FALSE;
1245                         debug_printf (1, "    FAILED: soup_message_headers_append() did not reject it\n");
1246                         errors++;
1247                 }
1248         }
1249         soup_message_headers_free (hdrs);
1250 }
1251
1252 int
1253 main (int argc, char **argv)
1254 {
1255         test_init (argc, argv, NULL);
1256
1257         do_request_tests ();
1258         do_response_tests ();
1259         do_qvalue_tests ();
1260         do_content_disposition_tests ();
1261         do_content_type_tests ();
1262         do_append_param_tests ();
1263         do_bad_header_tests ();
1264
1265         test_cleanup ();
1266         return errors != 0;
1267 }