Git init
[profile/ivi/libsoup2.4.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[4];
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         /****************************/
181         /*** RECOVERABLE REQUESTS ***/
182         /****************************/
183
184         /* RFC 2616 section 4.1 says we SHOULD accept this */
185
186         { "Spurious leading CRLF",
187           "\r\nGET / HTTP/1.1\r\nHost: example.com\r\n", -1,
188           SOUP_STATUS_OK,
189           "GET", "/", SOUP_HTTP_1_1,
190           { { "Host", "example.com" },
191             { NULL }
192           }
193         },
194
195         /* RFC 2616 section 3.1 says we MUST accept this */
196
197         { "HTTP/01.01 request",
198           "GET / HTTP/01.01\r\nHost: example.com\r\n", -1,
199           SOUP_STATUS_OK,
200           "GET", "/", SOUP_HTTP_1_1,
201           { { "Host", "example.com" },
202             { NULL }
203           }
204         },
205
206         /* RFC 2616 section 19.3 says we SHOULD accept these */
207
208         { "LF instead of CRLF after header",
209           "GET / HTTP/1.1\nHost: example.com\nConnection: close\n", -1,
210           SOUP_STATUS_OK,
211           "GET", "/", SOUP_HTTP_1_1,
212           { { "Host", "example.com" },
213             { "Connection", "close" },
214             { NULL }
215           }
216         },
217
218         { "LF instead of CRLF after Request-Line",
219           "GET / HTTP/1.1\nHost: example.com\r\n", -1,
220           SOUP_STATUS_OK,
221           "GET", "/", SOUP_HTTP_1_1,
222           { { "Host", "example.com" },
223             { NULL }
224           }
225         },
226
227         { "Req w/ incorrect whitespace in Request-Line",
228           "GET  /\tHTTP/1.1\r\nHost: example.com\r\n", -1,
229           SOUP_STATUS_OK,
230           "GET", "/", SOUP_HTTP_1_1,
231           { { "Host", "example.com" },
232             { NULL }
233           }
234         },
235
236         { "Req w/ incorrect whitespace after Request-Line",
237           "GET / HTTP/1.1 \r\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         /* qv bug 579318, do_bad_header_tests() below */
246         { "Req w/ mangled header",
247           "GET / HTTP/1.1\r\nHost: example.com\r\nFoo one\r\nBar: two\r\n", -1,
248           SOUP_STATUS_OK,
249           "GET", "/", SOUP_HTTP_1_1,
250           { { "Host", "example.com" },
251             { "Bar", "two" },
252             { NULL }
253           }
254         },
255
256         /************************/
257         /*** INVALID REQUESTS ***/
258         /************************/
259
260         { "HTTP 0.9 request; not supported",
261           "GET /\r\n", -1,
262           SOUP_STATUS_BAD_REQUEST,
263           NULL, NULL, -1,
264           { { NULL } }
265         },
266
267         { "HTTP 1.2 request (no such thing)",
268           "GET / HTTP/1.2\r\n", -1,
269           SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
270           NULL, NULL, -1,
271           { { NULL } }
272         },
273
274         { "HTTP 2000 request (no such thing)",
275           "GET / HTTP/2000.0\r\n", -1,
276           SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
277           NULL, NULL, -1,
278           { { NULL } }
279         },
280
281         { "Non-HTTP request",
282           "GET / SOUP/1.1\r\nHost: example.com\r\n", -1,
283           SOUP_STATUS_BAD_REQUEST,
284           NULL, NULL, -1,
285           { { NULL } }
286         },
287
288         { "Junk after Request-Line",
289           "GET / HTTP/1.1 blah\r\nHost: example.com\r\n", -1,
290           SOUP_STATUS_BAD_REQUEST,
291           NULL, NULL, -1,
292           { { NULL } }
293         },
294
295         { "NUL in Method",
296           "G\x00T / HTTP/1.1\r\nHost: example.com\r\n", 37,
297           SOUP_STATUS_BAD_REQUEST,
298           NULL, NULL, -1,
299           { { NULL } }
300         },
301
302         { "NUL in Path",
303           "GET /\x00 HTTP/1.1\r\nHost: example.com\r\n", 38,
304           SOUP_STATUS_BAD_REQUEST,
305           NULL, NULL, -1,
306           { { NULL } }
307         },
308
309         { "NUL in Header",
310           "GET / HTTP/1.1\r\nHost: example\x00com\r\n", 37,
311           SOUP_STATUS_BAD_REQUEST,
312           NULL, NULL, -1,
313           { { NULL } }
314         },
315
316         { "No terminating CRLF",
317           "GET / HTTP/1.1\r\nHost: example.com", -1,
318           SOUP_STATUS_BAD_REQUEST,
319           NULL, NULL, -1,
320           { { NULL } }
321         },
322
323         { "Unrecognized expectation",
324           "GET / HTTP/1.1\r\nHost: example.com\r\nExpect: the-impossible\r\n", -1,
325           SOUP_STATUS_EXPECTATION_FAILED,
326           NULL, NULL, -1,
327           { { NULL } }
328         }
329 };
330 static const int num_reqtests = G_N_ELEMENTS (reqtests);
331
332 static struct ResponseTest {
333         const char *description;
334         const char *response;
335         int length;
336         SoupHTTPVersion version;
337         guint status_code;
338         const char *reason_phrase;
339         Header headers[4];
340 } resptests[] = {
341         /***********************/
342         /*** VALID RESPONSES ***/
343         /***********************/
344
345         { "HTTP 1.0 response w/ no headers",
346           "HTTP/1.0 200 ok\r\n", -1,
347           SOUP_HTTP_1_0, SOUP_STATUS_OK, "ok",
348           { { NULL } }
349         },
350
351         { "HTTP 1.1 response w/ no headers",
352           "HTTP/1.1 200 ok\r\n", -1,
353           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
354           { { NULL } }
355         },
356
357         { "Response w/ multi-word Reason-Phrase",
358           "HTTP/1.1 400 bad request\r\n", -1,
359           SOUP_HTTP_1_1, SOUP_STATUS_BAD_REQUEST, "bad request",
360           { { NULL } }
361         },
362
363         { "Response w/ 1 header",
364           "HTTP/1.1 200 ok\r\nFoo: bar\r\n", -1,
365           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
366           { { "Foo", "bar" },
367             { NULL }
368           }
369         },
370
371         { "Response w/ 2 headers",
372           "HTTP/1.1 200 ok\r\nFoo: bar\r\nBaz: quux\r\n", -1,
373           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
374           { { "Foo", "bar" },
375             { "Baz", "quux" },
376             { NULL }
377           }
378         },
379
380         { "Response w/ same header multiple times",
381           "HTTP/1.1 200 ok\r\nFoo: bar\r\nFoo: baz\r\nFoo: quux\r\n", -1,
382           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
383           { { "Foo", "bar, baz, quux" },
384             { NULL }
385           }
386         },
387
388         { "Response w/ no reason phrase",
389           "HTTP/1.1 200 \r\nFoo: bar\r\n", -1,
390           SOUP_HTTP_1_1, SOUP_STATUS_OK, "",
391           { { "Foo", "bar" },
392             { NULL }
393           }
394         },
395
396         { "Connection header on HTTP/1.0 message",
397           "HTTP/1.0 200 ok\r\nFoo: bar\r\nConnection: Bar\r\nBar: quux\r\n", -1,
398           SOUP_HTTP_1_0, SOUP_STATUS_OK, "ok",
399           { { "Foo", "bar" },
400             { "Connection", "Bar" },
401             { NULL }
402           }
403         },
404
405         /*****************************/
406         /*** RECOVERABLE RESPONSES ***/
407         /*****************************/
408
409         /* RFC 2616 section 3.1 says we MUST accept this */
410
411         { "HTTP/01.01 response",
412           "HTTP/01.01 200 ok\r\nFoo: bar\r\n", -1,
413           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
414           { { "Foo", "bar" },
415             { NULL }
416           }
417         },
418
419         /* RFC 2616 section 19.3 says we SHOULD accept these */
420
421         { "Response w/ LF instead of CRLF after Status-Line",
422           "HTTP/1.1 200 ok\nFoo: bar\r\n", -1,
423           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
424           { { "Foo", "bar" },
425             { NULL }
426           }
427         },
428
429         { "Response w/ incorrect spacing in Status-Line",
430           "HTTP/1.1  200\tok\r\nFoo: bar\r\n", -1,
431           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
432           { { "Foo", "bar" },
433             { NULL }
434           }
435         },
436
437         { "Response w/ no reason phrase or preceding SP",
438           "HTTP/1.1 200\r\nFoo: bar\r\n", -1,
439           SOUP_HTTP_1_1, SOUP_STATUS_OK, "",
440           { { "Foo", "bar" },
441             { NULL }
442           }
443         },
444
445         { "Response w/ no whitespace after status code",
446           "HTTP/1.1 200ok\r\nFoo: bar\r\n", -1,
447           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
448           { { "Foo", "bar" },
449             { NULL }
450           }
451         },
452
453         /* Shoutcast support */
454         { "Shoutcast server not-quite-HTTP",
455           "ICY 200 OK\r\nFoo: bar\r\n", -1,
456           SOUP_HTTP_1_0, SOUP_STATUS_OK, "OK",
457           { { "Foo", "bar" },
458             { NULL }
459           }
460         },
461
462         /* qv bug 579318, do_bad_header_tests() below */
463         { "Response w/ mangled header",
464           "HTTP/1.1 200 ok\r\nFoo: one\r\nBar two:2\r\nBaz: three\r\n", -1,
465           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
466           { { "Foo", "one" },
467             { "Baz", "three" },
468             { NULL }
469           }
470         },
471
472         /* qv bug 602863 */
473         { "HTTP 1.1 response with leading line break",
474           "\nHTTP/1.1 200 ok\r\nFoo: bar\r\n", -1,
475           SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok",
476           { { "Foo", "bar" },
477             { NULL } }
478         },
479
480         /*************************/
481         /*** INVALID RESPONSES ***/
482         /*************************/
483
484         { "Invalid HTTP version",
485           "HTTP/1.2 200 OK\r\nFoo: bar\r\n", -1,
486           -1, 0, NULL,
487           { { NULL } }
488         },
489
490         { "Non-HTTP response",
491           "SOUP/1.1 200 OK\r\nFoo: bar\r\n", -1,
492           -1, 0, NULL,
493           { { NULL } }
494         },
495
496         { "Non-numeric status code",
497           "HTTP/1.1 XXX OK\r\nFoo: bar\r\n", -1,
498           -1, 0, NULL,
499           { { NULL } }
500         },
501
502         { "No status code",
503           "HTTP/1.1 OK\r\nFoo: bar\r\n", -1,
504           -1, 0, NULL,
505           { { NULL } }
506         },
507
508         { "One-digit status code",
509           "HTTP/1.1 2 OK\r\nFoo: bar\r\n", -1,
510           -1, 0, NULL,
511           { { NULL } }
512         },
513
514         { "Two-digit status code",
515           "HTTP/1.1 20 OK\r\nFoo: bar\r\n", -1,
516           -1, 0, NULL,
517           { { NULL } }
518         },
519
520         { "Four-digit status code",
521           "HTTP/1.1 2000 OK\r\nFoo: bar\r\n", -1,
522           -1, 0, NULL,
523           { { NULL } }
524         },
525
526         { "Status code < 100",
527           "HTTP/1.1 001 OK\r\nFoo: bar\r\n", -1,
528           -1, 0, NULL,
529           { { NULL } }
530         },
531
532         { "Status code > 599",
533           "HTTP/1.1 600 OK\r\nFoo: bar\r\n", -1,
534           -1, 0, NULL,
535           { { NULL } }
536         },
537
538         { "NUL in Reason Phrase",
539           "HTTP/1.1 200 O\x00K\r\nFoo: bar\r\n", 28,
540           -1, 0, NULL,
541           { { NULL } }
542         },
543
544         { "NUL in Header",
545           "HTTP/1.1 200 OK\r\nFoo: b\x00ar\r\n", 28,
546           -1, 0, NULL,
547           { { NULL } }
548         },
549 };
550 static const int num_resptests = G_N_ELEMENTS (resptests);
551
552 static struct QValueTest {
553         const char *header_value;
554         const char *acceptable[7];
555         const char *unacceptable[2];
556 } qvaluetests[] = {
557         { "text/plain; q=0.5, text/html,\t  text/x-dvi; q=0.8, text/x-c",
558           { "text/html", "text/x-c", "text/x-dvi", "text/plain", NULL },
559           { NULL },
560         },
561
562         { "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5",
563           { "text/html;level=1", "text/html", "*/*", "text/html;level=2",
564             "text/*", NULL },
565           { NULL }
566         },
567
568         { "gzip;q=1.0, identity; q=0.5, *;q=0",
569           { "gzip", "identity", NULL },
570           { "*", NULL },
571         }
572 };
573 static const int num_qvaluetests = G_N_ELEMENTS (qvaluetests);
574
575 static void
576 print_header (const char *name, const char *value, gpointer data)
577 {
578         debug_printf (1, "              '%s': '%s'\n", name, value);
579 }
580
581 static gboolean
582 check_headers (Header *headers, SoupMessageHeaders *hdrs)
583 {
584         GSList *header_names, *h;
585         SoupMessageHeadersIter iter;
586         const char *name, *value;
587         gboolean ok = TRUE;
588         int i;
589
590         header_names = NULL;
591         soup_message_headers_iter_init (&iter, hdrs);
592         while (soup_message_headers_iter_next (&iter, &name, &value)) {
593                 if (!g_slist_find_custom (header_names, name,
594                                           (GCompareFunc)strcmp))
595                         header_names = g_slist_append (header_names, (char *)name);
596         }
597
598         for (i = 0, h = header_names; headers[i].name && h; i++, h = h->next) {
599                 if (strcmp (h->data, headers[i].name) != 0) {
600                         ok = FALSE;
601                         break;
602                 }
603                 value = soup_message_headers_get_list (hdrs, headers[i].name);
604                 if (strcmp (value, headers[i].value) != 0) {
605                         ok = FALSE;
606                         break;
607                 }
608         }
609         if (headers[i].name || h)
610                 ok = FALSE;
611         g_slist_free (header_names);
612         return ok;
613 }
614
615 static void
616 do_request_tests (void)
617 {
618         int i, len, h;
619         char *method, *path;
620         SoupHTTPVersion version;
621         SoupMessageHeaders *headers;
622         guint status;
623
624         debug_printf (1, "Request tests\n");
625         for (i = 0; i < num_reqtests; i++) {
626                 gboolean ok = TRUE;
627
628                 debug_printf (1, "%2d. %s (%s): ", i + 1, reqtests[i].description,
629                               soup_status_get_phrase (reqtests[i].status));
630
631                 headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_REQUEST);
632                 method = path = NULL;
633
634                 if (reqtests[i].length == -1)
635                         len = strlen (reqtests[i].request);
636                 else
637                         len = reqtests[i].length;
638                 status = soup_headers_parse_request (reqtests[i].request, len,
639                                                      headers, &method, &path,
640                                                      &version);
641                 if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
642                         if ((reqtests[i].method && strcmp (reqtests[i].method, method) != 0) || !reqtests[i].method)
643                                 ok = FALSE;
644                         if ((reqtests[i].path && strcmp (reqtests[i].path, path) != 0) || !reqtests[i].path)
645                                 ok = FALSE;
646                         if (reqtests[i].version != version)
647                                 ok = FALSE;
648
649                         if (!check_headers (reqtests[i].headers, headers))
650                                 ok = FALSE;
651                 } else {
652                         if (status != reqtests[i].status)
653                                 ok = FALSE;
654                 }
655
656                 if (ok)
657                         debug_printf (1, "OK!\n");
658                 else {
659                         debug_printf (1, "BAD!\n");
660                         errors++;
661                         if (reqtests[i].method) {
662                                 debug_printf (1, "    expected: '%s' '%s' 'HTTP/1.%d'\n",
663                                               reqtests[i].method,
664                                               reqtests[i].path,
665                                               reqtests[i].version);
666                                 for (h = 0; reqtests[i].headers[h].name; h++) {
667                                         debug_printf (1, "              '%s': '%s'\n",
668                                                       reqtests[i].headers[h].name,
669                                                       reqtests[i].headers[h].value);
670                                 }
671                         } else {
672                                 debug_printf (1, "    expected: %s\n",
673                                               soup_status_get_phrase (reqtests[i].status));
674                         }
675                         if (method) {
676                                 debug_printf (1, "         got: '%s' '%s' 'HTTP/1.%d'\n",
677                                               method, path, version);
678                                 soup_message_headers_foreach (headers, print_header, NULL);
679                         } else {
680                                 debug_printf (1, "         got: %s\n",
681                                               soup_status_get_phrase (status));
682                         }
683                 }
684
685                 g_free (method);
686                 g_free (path);
687                 soup_message_headers_free (headers);
688         }
689         debug_printf (1, "\n");
690 }
691
692 static void
693 do_response_tests (void)
694 {
695         int i, len, h;
696         guint status_code;
697         char *reason_phrase;
698         SoupHTTPVersion version;
699         SoupMessageHeaders *headers;
700
701         debug_printf (1, "Response tests\n");
702         for (i = 0; i < num_resptests; i++) {
703                 gboolean ok = TRUE;
704
705                 debug_printf (1, "%2d. %s (%s): ", i + 1, resptests[i].description,
706                               resptests[i].reason_phrase ? "should parse" : "should NOT parse");
707
708                 headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
709                 reason_phrase = NULL;
710
711                 if (resptests[i].length == -1)
712                         len = strlen (resptests[i].response);
713                 else
714                         len = resptests[i].length;
715                 if (soup_headers_parse_response (resptests[i].response, len,
716                                                  headers, &version,
717                                                  &status_code, &reason_phrase)) {
718                         if (resptests[i].version != version)
719                                 ok = FALSE;
720                         if (resptests[i].status_code != status_code)
721                                 ok = FALSE;
722                         if ((resptests[i].reason_phrase && strcmp (resptests[i].reason_phrase, reason_phrase) != 0) || !resptests[i].reason_phrase)
723                                 ok = FALSE;
724
725                         if (!check_headers (resptests[i].headers, headers))
726                                 ok = FALSE;
727                 } else {
728                         if (resptests[i].reason_phrase)
729                                 ok = FALSE;
730                 }
731
732                 if (ok)
733                         debug_printf (1, "OK!\n");
734                 else {
735                         debug_printf (1, "BAD!\n");
736                         errors++;
737                         if (resptests[i].reason_phrase) {
738                                 debug_printf (1, "    expected: 'HTTP/1.%d' '%03d' '%s'\n",
739                                               resptests[i].version,
740                                               resptests[i].status_code,
741                                               resptests[i].reason_phrase);
742                                 for (h = 0; resptests[i].headers[h].name; h++) {
743                                         debug_printf (1, "              '%s': '%s'\n",
744                                                       resptests[i].headers[h].name,
745                                                       resptests[i].headers[h].value);
746                                 }
747                         } else
748                                 debug_printf (1, "    expected: parse error\n");
749                         if (reason_phrase) {
750                                 debug_printf (1, "         got: 'HTTP/1.%d' '%03d' '%s'\n",
751                                               version, status_code, reason_phrase);
752                                 soup_message_headers_foreach (headers, print_header, NULL);
753                         } else
754                                 debug_printf (1, "         got: parse error\n");
755                 }
756
757                 g_free (reason_phrase);
758                 soup_message_headers_free (headers);
759         }
760         debug_printf (1, "\n");
761 }
762
763 static void
764 do_qvalue_tests (void)
765 {
766         int i, j;
767         GSList *acceptable, *unacceptable, *iter;
768         gboolean wrong;
769
770         debug_printf (1, "qvalue tests\n");
771         for (i = 0; i < num_qvaluetests; i++) {
772                 debug_printf (1, "%2d. %s:\n", i + 1, qvaluetests[i].header_value);
773
774                 unacceptable = NULL;
775                 acceptable = soup_header_parse_quality_list (qvaluetests[i].header_value,
776                                                              &unacceptable);
777
778                 debug_printf (1, "    acceptable: ");
779                 wrong = FALSE;
780                 if (acceptable) {
781                         for (iter = acceptable, j = 0; iter; iter = iter->next, j++) {
782                                 debug_printf (1, "%s ", (char *)iter->data);
783                                 if (!qvaluetests[i].acceptable[j] ||
784                                     strcmp (iter->data, qvaluetests[i].acceptable[j]) != 0)
785                                         wrong = TRUE;
786                         }
787                         debug_printf (1, "\n");
788                         soup_header_free_list (acceptable);
789                 } else
790                         debug_printf (1, "(none)\n");
791                 if (wrong) {
792                         debug_printf (1, "    WRONG! expected: ");
793                         for (j = 0; qvaluetests[i].acceptable[j]; j++)
794                                 debug_printf (1, "%s ", qvaluetests[i].acceptable[j]);
795                         debug_printf (1, "\n");
796                         errors++;
797                 }
798
799                 debug_printf (1, "  unacceptable: ");
800                 wrong = FALSE;
801                 if (unacceptable) {
802                         for (iter = unacceptable, j = 0; iter; iter = iter->next, j++) {
803                                 debug_printf (1, "%s ", (char *)iter->data);
804                                 if (!qvaluetests[i].unacceptable[j] ||
805                                     strcmp (iter->data, qvaluetests[i].unacceptable[j]) != 0)
806                                         wrong = TRUE;
807                         }
808                         debug_printf (1, "\n");
809                         soup_header_free_list (unacceptable);
810                 } else
811                         debug_printf (1, "(none)\n");
812                 if (wrong) {
813                         debug_printf (1, "    WRONG! expected: ");
814                         for (j = 0; qvaluetests[i].unacceptable[j]; j++)
815                                 debug_printf (1, "%s ", qvaluetests[i].unacceptable[j]);
816                         debug_printf (1, "\n");
817                         errors++;
818                 }
819
820                 debug_printf (1, "\n");
821         }
822 }
823
824 #define RFC5987_TEST_FILENAME "t\xC3\xA9st.txt"
825 #define RFC5987_TEST_FALLBACK_FILENAME "test.txt"
826
827 #define RFC5987_TEST_HEADER_ENCODED  "attachment; filename*=UTF-8''t%C3%A9st.txt"
828
829 #define RFC5987_TEST_HEADER_UTF8     "attachment; filename*=UTF-8''t%C3%A9st.txt; filename=\"test.txt\""
830 #define RFC5987_TEST_HEADER_ISO      "attachment; filename=\"test.txt\"; filename*=iso-8859-1''t%E9st.txt"
831 #define RFC5987_TEST_HEADER_FALLBACK "attachment; filename*=Unknown''t%FF%FF%FFst.txt; filename=\"test.txt\""
832
833 static void
834 do_content_disposition_tests (void)
835 {
836         SoupMessageHeaders *hdrs;
837         GHashTable *params;
838         const char *header, *filename;
839         char *disposition;
840         SoupBuffer *buffer;
841         SoupMultipart *multipart;
842         SoupMessageBody *body;
843
844         debug_printf (1, "Content-Disposition tests\n");
845
846         hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
847         params = g_hash_table_new (g_str_hash, g_str_equal);
848         g_hash_table_insert (params, "filename", RFC5987_TEST_FILENAME);
849         soup_message_headers_set_content_disposition (hdrs, "attachment", params);
850         g_hash_table_destroy (params);
851
852         header = soup_message_headers_get_one (hdrs, "Content-Disposition");
853         if (!strcmp (header, RFC5987_TEST_HEADER_ENCODED))
854                 debug_printf (1, "  encoded OK\n");
855         else {
856                 debug_printf (1, "  encoding FAILED!\n    expected: %s\n    got:      %s\n",
857                               RFC5987_TEST_HEADER_ENCODED, header);
858                 errors++;
859         }
860
861         /* UTF-8 decoding */
862         soup_message_headers_clear (hdrs);
863         soup_message_headers_append (hdrs, "Content-Disposition",
864                                      RFC5987_TEST_HEADER_UTF8);
865         if (!soup_message_headers_get_content_disposition (hdrs,
866                                                            &disposition,
867                                                            &params)) {
868                 debug_printf (1, "  UTF-8 decoding FAILED!\n    could not parse\n");
869                 errors++;
870                 return;
871         }
872         g_free (disposition);
873
874         filename = g_hash_table_lookup (params, "filename");
875         if (!filename) {
876                 debug_printf (1, "  UTF-8 decoding FAILED!\n    could not find filename\n");
877                 errors++;
878         } else if (strcmp (filename, RFC5987_TEST_FILENAME) != 0) {
879                 debug_printf (1, "  UTF-8 decoding FAILED!\n    expected: %s\n    got:      %s\n",
880                               RFC5987_TEST_FILENAME, filename);
881                 errors++;
882         } else
883                 debug_printf (1, "  UTF-8 decoded OK\n");
884         g_hash_table_destroy (params);
885
886         /* ISO-8859-1 decoding */
887         soup_message_headers_clear (hdrs);
888         soup_message_headers_append (hdrs, "Content-Disposition",
889                                      RFC5987_TEST_HEADER_ISO);
890         if (!soup_message_headers_get_content_disposition (hdrs,
891                                                            &disposition,
892                                                            &params)) {
893                 debug_printf (1, "  iso-8859-1 decoding FAILED!\n    could not parse\n");
894                 errors++;
895                 return;
896         }
897         g_free (disposition);
898
899         filename = g_hash_table_lookup (params, "filename");
900         if (!filename) {
901                 debug_printf (1, "  iso-8859-1 decoding FAILED!\n    could not find filename\n");
902                 errors++;
903         } else if (strcmp (filename, RFC5987_TEST_FILENAME) != 0) {
904                 debug_printf (1, "  iso-8859-1 decoding FAILED!\n    expected: %s\n    got:      %s\n",
905                               RFC5987_TEST_FILENAME, filename);
906                 errors++;
907         } else
908                 debug_printf (1, "  iso-8859-1 decoded OK\n");
909         g_hash_table_destroy (params);
910
911         /* Fallback */
912         soup_message_headers_clear (hdrs);
913         soup_message_headers_append (hdrs, "Content-Disposition",
914                                      RFC5987_TEST_HEADER_FALLBACK);
915         if (!soup_message_headers_get_content_disposition (hdrs,
916                                                            &disposition,
917                                                            &params)) {
918                 debug_printf (1, "  fallback decoding FAILED!\n    could not parse\n");
919                 errors++;
920                 return;
921         }
922         g_free (disposition);
923
924         filename = g_hash_table_lookup (params, "filename");
925         if (!filename) {
926                 debug_printf (1, "  fallback decoding FAILED!\n    could not find filename\n");
927                 errors++;
928         } else if (strcmp (filename, RFC5987_TEST_FALLBACK_FILENAME) != 0) {
929                 debug_printf (1, "  fallback decoding FAILED!\n    expected: %s\n    got:      %s\n",
930                               RFC5987_TEST_FALLBACK_FILENAME, filename);
931                 errors++;
932         } else
933                 debug_printf (1, "  fallback decoded OK\n");
934         g_hash_table_destroy (params);
935
936         soup_message_headers_free (hdrs);
937
938         /* Ensure that soup-multipart always quotes filename (bug 641280) */
939         multipart = soup_multipart_new (SOUP_FORM_MIME_TYPE_MULTIPART);
940         buffer = soup_buffer_new (SOUP_MEMORY_STATIC, "foo", 3);
941         soup_multipart_append_form_file (multipart, "test", "token",
942                                          "text/plain", buffer);
943         soup_buffer_free (buffer);
944
945         hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
946         body = soup_message_body_new ();
947         soup_multipart_to_message (multipart, hdrs, body);
948         soup_message_headers_free (hdrs);
949         soup_multipart_free (multipart);
950
951         buffer = soup_message_body_flatten (body);
952         soup_message_body_free (body);
953
954         if (strstr (buffer->data, "filename=\"token\""))
955                 debug_printf (1, "  SoupMultipart encoded filename correctly\n");
956         else {
957                 debug_printf (1, "  SoupMultipart encoded filename incorrectly!\n");
958                 errors++;
959         }
960         soup_buffer_free (buffer);
961
962         debug_printf (1, "\n");
963 }
964
965 #define CONTENT_TYPE_TEST_MIME_TYPE "text/plain"
966 #define CONTENT_TYPE_TEST_ATTRIBUTE "charset"
967 #define CONTENT_TYPE_TEST_VALUE     "US-ASCII"
968 #define CONTENT_TYPE_TEST_HEADER    "text/plain; charset=US-ASCII"
969
970 #define CONTENT_TYPE_BAD_HEADER     "plain text, not text/html"
971
972 static void
973 do_content_type_tests (void)
974 {
975         SoupMessageHeaders *hdrs;
976         GHashTable *params;
977         const char *header, *mime_type;
978
979         debug_printf (1, "Content-Type tests\n");
980
981         hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
982         params = g_hash_table_new (g_str_hash, g_str_equal);
983         g_hash_table_insert (params, CONTENT_TYPE_TEST_ATTRIBUTE,
984                              CONTENT_TYPE_TEST_VALUE);
985         soup_message_headers_set_content_type (hdrs, CONTENT_TYPE_TEST_MIME_TYPE, params);
986         g_hash_table_destroy (params);
987
988         header = soup_message_headers_get_one (hdrs, "Content-Type");
989         if (!strcmp (header, CONTENT_TYPE_TEST_HEADER))
990                 debug_printf (1, "  encoded OK\n");
991         else {
992                 debug_printf (1, "  encoding FAILED!\n    expected: %s\n    got:      %s\n",
993                               CONTENT_TYPE_TEST_HEADER, header);
994                 errors++;
995         }
996
997         soup_message_headers_clear (hdrs);
998         soup_message_headers_append (hdrs, "Content-Type",
999                                      CONTENT_TYPE_TEST_MIME_TYPE);
1000         /* Add a second Content-Type header: should be ignored */
1001         soup_message_headers_append (hdrs, "Content-Type",
1002                                      CONTENT_TYPE_TEST_MIME_TYPE);
1003
1004         mime_type = soup_message_headers_get_content_type (hdrs, &params);
1005         if (!mime_type) {
1006                 debug_printf (1, "  decoding FAILED!\n    could not parse\n");
1007                 errors++;
1008         }
1009
1010         if (mime_type && strcmp (mime_type, CONTENT_TYPE_TEST_MIME_TYPE) != 0) {
1011                 debug_printf (1, "  decoding FAILED!\n    bad returned MIME type: %s\n",
1012                               mime_type);
1013                 errors++;
1014         } else if (params && g_hash_table_size (params) != 0) {
1015                 debug_printf (1, "  decoding FAILED!\n    params contained %d params (should be 0)\n",
1016                               g_hash_table_size (params));
1017                 errors++;
1018         } else
1019                 debug_printf (1, "  decoded OK\n");
1020
1021         if (params)
1022                 g_hash_table_destroy (params);
1023
1024         soup_message_headers_clear (hdrs);
1025         soup_message_headers_append (hdrs, "Content-Type",
1026                                      CONTENT_TYPE_BAD_HEADER);
1027         mime_type = soup_message_headers_get_content_type (hdrs, &params);
1028         if (mime_type) {
1029                 debug_printf (1, "  Bad content rejection FAILED!\n");
1030                 errors++;
1031         } else
1032                 debug_printf (1, "  Bad content rejection OK\n");
1033
1034         soup_message_headers_free (hdrs);
1035
1036         debug_printf (1, "\n");
1037 }
1038
1039 struct {
1040         const char *name, *value;
1041 } test_params[] = {
1042         { "one", "foo" },
1043         { "two", "test with spaces" },
1044         { "three", "test with \"quotes\" and \\s" },
1045         { "four", NULL },
1046         { "five", "test with \xC3\xA1\xC3\xA7\xC4\x89\xC3\xA8\xC3\xB1\xC5\xA3\xC5\xA1" }
1047 };
1048
1049 #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"
1050
1051 static void
1052 do_append_param_tests (void)
1053 {
1054         GString *params;
1055         int i;
1056
1057         debug_printf (1, "soup_header_g_string_append_param() tests\n");
1058
1059         params = g_string_new (NULL);
1060         for (i = 0; i < G_N_ELEMENTS (test_params); i++) {
1061                 if (i > 0)
1062                         g_string_append (params, ", ");
1063                 soup_header_g_string_append_param (params,
1064                                                    test_params[i].name,
1065                                                    test_params[i].value);
1066         }
1067         if (strcmp (params->str, TEST_PARAMS_RESULT) != 0) {
1068                 debug_printf (1, "  FAILED!\n    expected: %s\n    got: %s\n",
1069                               TEST_PARAMS_RESULT, params->str);
1070                 errors++;
1071         } else
1072                 debug_printf (1, "  OK\n");
1073         g_string_free (params, TRUE);
1074
1075         debug_printf (1, "\n");
1076 }
1077
1078 static const struct {
1079         const char *description, *name, *value;
1080 } bad_headers[] = {
1081         { "Empty name", "", "value" },
1082         { "Name with spaces", "na me", "value" },
1083         { "Name with colon", "na:me", "value" },
1084         { "Name with CR", "na\rme", "value" },
1085         { "Name with LF", "na\nme", "value" },
1086         { "Name with tab", "na\tme", "value" },
1087         { "Value with CR", "name", "val\rue" },
1088         { "Value with LF", "name", "val\nue" },
1089         { "Value with LWS", "name", "val\r\n ue" }
1090 };
1091
1092 static void
1093 do_bad_header_tests (void)
1094 {
1095         SoupMessageHeaders *hdrs;
1096         int i;
1097
1098         debug_printf (1, "bad header rejection tests\n");
1099
1100         hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
1101         for (i = 0; i < G_N_ELEMENTS (bad_headers); i++) {
1102                 debug_printf (1, "  %s\n", bad_headers[i].description);
1103                 expect_warning = TRUE;
1104                 soup_message_headers_append (hdrs, bad_headers[i].name,
1105                                              bad_headers[i].value);
1106                 if (expect_warning) {
1107                         expect_warning = FALSE;
1108                         debug_printf (1, "    FAILED: soup_message_headers_append() did not reject it\n");
1109                         errors++;
1110                 }
1111         }
1112         soup_message_headers_free (hdrs);
1113 }
1114
1115 int
1116 main (int argc, char **argv)
1117 {
1118         test_init (argc, argv, NULL);
1119
1120         do_request_tests ();
1121         do_response_tests ();
1122         do_qvalue_tests ();
1123         do_content_disposition_tests ();
1124         do_content_type_tests ();
1125         do_append_param_tests ();
1126         do_bad_header_tests ();
1127
1128         test_cleanup ();
1129         return errors != 0;
1130 }