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