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