xref: /lighttpd1.4/src/t/test_request.c (revision 3392e8fb)
1 #include "first.h"
2 
3 #undef NDEBUG
4 #include <assert.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <string.h>
8 
9 #include "request.c"
10 
11 static void test_request_reset(request_st * const r)
12 {
13     r->http_method = HTTP_METHOD_UNSET;
14     r->http_version = HTTP_VERSION_UNSET;
15     r->http_host = NULL;
16     r->rqst_htags = 0;
17     r->reqbody_length = 0;
18     buffer_clear(&r->target_orig);
19     buffer_clear(&r->target);
20     array_reset_data_strings(&r->rqst_headers);
21 }
22 
23 static void run_http_request_parse(request_st * const r, int line, int status, const char *desc, const char *req, size_t reqlen)
24 {
25     unsigned short hloffsets[32];
26     char hdrs[1024];
27     test_request_reset(r);
28     assert(reqlen < sizeof(hdrs));
29     memcpy(hdrs, req, reqlen);
30     hloffsets[0] = 1;
31     hloffsets[1] = 0;
32     hloffsets[2] = 0;
33     for (const char *n=req, *end=req+reqlen; (n=memchr(n,'\n',end-n)); ++n) {
34         if (++hloffsets[0] >= sizeof(hloffsets)/sizeof(*hloffsets)) break;
35         hloffsets[hloffsets[0]] = n - req + 1;
36     }
37     --hloffsets[0]; /*(ignore final blank line "\r\n" ending headers)*/
38     const int proto_default_port = 80;
39     int http_status =
40       http_request_parse_hoff(r, hdrs, hloffsets, proto_default_port);
41     if (http_status != status) {
42         fprintf(stderr,
43                 "%s.%d: %s() failed: expected '%d', got '%d' for test %s\n",
44                 __FILE__, line, "http_request_parse_hoff", status, http_status,
45                 desc);
46         fflush(stderr);
47         abort();
48     }
49 }
50 
51 static void test_request_http_request_parse(request_st * const r)
52 {
53     data_string *ds;
54 
55     run_http_request_parse(r, __LINE__, 400,
56       "invalid request-line: space",
57       CONST_STR_LEN(" \r\n"
58                     "Host: www.example.org\r\n"
59                     "\r\n"));
60 
61     run_http_request_parse(r, __LINE__, 400,
62       "invalid request-line: space, char",
63       CONST_STR_LEN(" a\r\n"
64                     "Host: www.example.org\r\n"
65                     "\r\n"));
66 
67     run_http_request_parse(r, __LINE__, 400,
68       "invalid request-line: dot",
69       CONST_STR_LEN(".\r\n"
70                     "Host: www.example.org\r\n"
71                     "\r\n"));
72 
73     run_http_request_parse(r, __LINE__, 400,
74       "invalid request-line: single char",
75       CONST_STR_LEN("a\r\n"
76                     "Host: www.example.org\r\n"
77                     "\r\n"));
78 
79     run_http_request_parse(r, __LINE__, 400,
80       "invalid request-line: char, space",
81       CONST_STR_LEN("a \r\n"
82                     "Host: www.example.org\r\n"
83                     "\r\n"));
84 
85     run_http_request_parse(r, __LINE__, 400,
86       "invalid request-line: method only",
87       CONST_STR_LEN("GET\r\n"
88                     "Host: www.example.org\r\n"
89                     "\r\n"));
90 
91     run_http_request_parse(r, __LINE__, 400,
92       "invalid request-line: method space",
93       CONST_STR_LEN("GET \r\n"
94                     "Host: www.example.org\r\n"
95                     "\r\n"));
96 
97     run_http_request_parse(r, __LINE__, 400,
98       "invalid request-line: method space space",
99       CONST_STR_LEN("GET  \r\n"
100                     "Host: www.example.org\r\n"
101                     "\r\n"));
102 
103     run_http_request_parse(r, __LINE__, 400,
104       "invalid request-line: method space proto",
105       CONST_STR_LEN("GET HTTP/1.0\r\n"
106                     "Host: www.example.org\r\n"
107                     "\r\n"));
108 
109     run_http_request_parse(r, __LINE__, 400,
110       "invalid request-line: method space space proto",
111       CONST_STR_LEN("GET  HTTP/1.0\r\n"
112                     "Host: www.example.org\r\n"
113                     "\r\n"));
114 
115     run_http_request_parse(r, __LINE__, 400,
116       "invalid request-line: method space space space proto",
117       CONST_STR_LEN("GET   HTTP/1.0\r\n"
118                     "Host: www.example.org\r\n"
119                     "\r\n"));
120 
121     run_http_request_parse(r, __LINE__, 400,
122       "invalid request-line: method slash proto, no spaces",
123       CONST_STR_LEN("GET/HTTP/1.0\r\n"
124                     "Host: www.example.org\r\n"
125                     "\r\n"));
126 
127     run_http_request_parse(r, __LINE__, 400,
128       "invalid request-line: method space slash proto",
129       CONST_STR_LEN("GET /HTTP/1.0\r\n"
130                     "Host: www.example.org\r\n"
131                     "\r\n"));
132 
133     run_http_request_parse(r, __LINE__, 400,
134       "invalid request-line: method space space slash proto",
135       CONST_STR_LEN("GET  /HTTP/1.0\r\n"
136                     "Host: www.example.org\r\n"
137                     "\r\n"));
138 
139     run_http_request_parse(r, __LINE__, 501,
140       "invalid request-line: method slash space proto",
141       CONST_STR_LEN("GET/ HTTP/1.0\r\n"
142                     "Host: www.example.org\r\n"
143                     "\r\n"));
144 
145     run_http_request_parse(r, __LINE__, 501,
146       "invalid request-line: method slash space space proto",
147       CONST_STR_LEN("GET/  HTTP/1.0\r\n"
148                     "Host: www.example.org\r\n"
149                     "\r\n"));
150 
151     run_http_request_parse(r, __LINE__, 0,
152       "hostname",
153       CONST_STR_LEN("GET / HTTP/1.0\r\n"
154                     "Host: www.example.org\r\n"
155                     "\r\n"));
156     assert(buffer_eq_slen(r->http_host, CONST_STR_LEN("www.example.org")));
157 
158     run_http_request_parse(r, __LINE__, 0,
159       "IPv4 address",
160       CONST_STR_LEN("GET / HTTP/1.0\r\n"
161                     "Host: 127.0.0.1\r\n"
162                     "\r\n"));
163     assert(buffer_eq_slen(r->http_host, CONST_STR_LEN("127.0.0.1")));
164 
165     run_http_request_parse(r, __LINE__, 0,
166       "IPv6 address",
167       CONST_STR_LEN("GET / HTTP/1.0\r\n"
168                     "Host: [::1]\r\n"
169                     "\r\n"));
170     assert(buffer_eq_slen(r->http_host, CONST_STR_LEN("[::1]")));
171 
172     run_http_request_parse(r, __LINE__, 0,
173       "hostname + port",
174       CONST_STR_LEN("GET / HTTP/1.0\r\n"
175                     "Host: www.example.org:80\r\n"
176                     "\r\n"));
177     assert(buffer_eq_slen(r->http_host, CONST_STR_LEN("www.example.org")));
178 
179     run_http_request_parse(r, __LINE__, 0,
180       "IPv4 address + port",
181       CONST_STR_LEN("GET / HTTP/1.0\r\n"
182                     "Host: 127.0.0.1:80\r\n"
183                     "\r\n"));
184     assert(buffer_eq_slen(r->http_host, CONST_STR_LEN("127.0.0.1")));
185 
186     run_http_request_parse(r, __LINE__, 0,
187       "IPv6 address + port",
188       CONST_STR_LEN("GET / HTTP/1.0\r\n"
189                     "Host: [::1]:80\r\n"
190                     "\r\n"));
191     assert(buffer_eq_slen(r->http_host, CONST_STR_LEN("[::1]")));
192 
193     run_http_request_parse(r, __LINE__, 400,
194       "directory traversal",
195       CONST_STR_LEN("GET / HTTP/1.0\r\n"
196                     "Host: ../123.org\r\n"
197                     "\r\n"));
198 
199     run_http_request_parse(r, __LINE__, 400,
200       "leading and trailing dot",
201       CONST_STR_LEN("GET / HTTP/1.0\r\n"
202                     "Host: .jsdh.sfdg.sdfg.\r\n"
203                     "\r\n"));
204 
205     run_http_request_parse(r, __LINE__, 0,
206       "trailing dot is ok",
207       CONST_STR_LEN("GET / HTTP/1.0\r\n"
208                     "Host: jsdh.sfdg.sdfg.\r\n"
209                     "\r\n"));
210     assert(buffer_eq_slen(r->http_host, CONST_STR_LEN("jsdh.sfdg.sdfg")));
211 
212     run_http_request_parse(r, __LINE__, 400,
213       "leading dot",
214       CONST_STR_LEN("GET / HTTP/1.0\r\n"
215                     "Host: .jsdh.sfdg.sdfg\r\n"
216                     "\r\n"));
217 
218     run_http_request_parse(r, __LINE__, 400,
219       "two dots",
220       CONST_STR_LEN("GET / HTTP/1.0\r\n"
221                     "Host: jsdh..sfdg.sdfg\r\n"
222                     "\r\n"));
223 
224     run_http_request_parse(r, __LINE__, 400,
225       "broken port-number",
226       CONST_STR_LEN("GET / HTTP/1.0\r\n"
227                     "Host: jsdh.sfdg.sdfg:asd\r\n"
228                     "\r\n"));
229 
230     run_http_request_parse(r, __LINE__, 400,
231       "negative port-number",
232       CONST_STR_LEN("GET / HTTP/1.0\r\n"
233                     "Host: jsdh.sfdg.sdfg:-1\r\n"
234                     "\r\n"));
235 
236     run_http_request_parse(r, __LINE__, 400,
237       "port given but host missing",
238       CONST_STR_LEN("GET / HTTP/1.0\r\n"
239                     "Host: :80\r\n"
240                     "\r\n"));
241 
242     run_http_request_parse(r, __LINE__, 400,
243       "port and host are broken",
244       CONST_STR_LEN("GET / HTTP/1.0\r\n"
245                     "Host: .jsdh.sfdg.:sdfg.\r\n"
246                     "\r\n"));
247 
248     run_http_request_parse(r, __LINE__, 0,
249       "allowed characters in host-name",
250       CONST_STR_LEN("GET / HTTP/1.0\r\n"
251                     "Host: a.b-c.d123\r\n"
252                     "\r\n"));
253     assert(buffer_eq_slen(r->http_host, CONST_STR_LEN("a.b-c.d123")));
254 
255     run_http_request_parse(r, __LINE__, 400,
256       "leading dash",
257       CONST_STR_LEN("GET / HTTP/1.0\r\n"
258                     "Host: -a.c\r\n"
259                     "\r\n"));
260 
261     run_http_request_parse(r, __LINE__, 400,
262       "dot only",
263       CONST_STR_LEN("GET / HTTP/1.0\r\n"
264                     "Host: .\r\n"
265                     "\r\n"));
266 
267     run_http_request_parse(r, __LINE__, 400,
268       "broken IPv4 address - non-digit",
269       CONST_STR_LEN("GET / HTTP/1.0\r\n"
270                     "Host: a192.168.2.10:1234\r\n"
271                     "\r\n"));
272 
273     run_http_request_parse(r, __LINE__, 400,
274       "broken IPv4 address - too short",
275       CONST_STR_LEN("GET / HTTP/1.0\r\n"
276                     "Host: 192.168.2:1234\r\n"
277                     "\r\n"));
278 
279     run_http_request_parse(r, __LINE__, 400,
280       "IPv6 address + SQL injection",
281       CONST_STR_LEN("GET / HTTP/1.0\r\n"
282                     "Host: [::1]' UNION SELECT '/\r\n"
283                     "\r\n"));
284 
285     run_http_request_parse(r, __LINE__, 400,
286       "IPv6 address + path traversal",
287       CONST_STR_LEN("GET / HTTP/1.0\r\n"
288                     "Host: [::1]/../../../\r\n"
289                     "\r\n"));
290 
291     run_http_request_parse(r, __LINE__, 400,
292       "negative Content-Length",
293       CONST_STR_LEN("POST /12345.txt HTTP/1.0\r\n"
294                     "Content-Length: -2\r\n"
295                     "\r\n"));
296 
297     run_http_request_parse(r, __LINE__, 411,
298       "Content-Length is empty",
299       CONST_STR_LEN("POST /12345.txt HTTP/1.0\r\n"
300                     "Host: 123.example.org\r\n"
301                     "Content-Length:\r\n"
302                     "\r\n"));
303 
304     run_http_request_parse(r, __LINE__, 400,
305       "Host missing",
306       CONST_STR_LEN("GET / HTTP/1.1\r\n"
307                     "\r\n"));
308 
309     run_http_request_parse(r, __LINE__, 400,
310       "empty request-URI",
311       CONST_STR_LEN("GET  HTTP/1.0\r\n"
312                     "\r\n"));
313 
314     run_http_request_parse(r, __LINE__, 0,
315       "#1232 - duplicate headers with line-wrapping",
316       CONST_STR_LEN("GET / HTTP/1.0\r\n"
317                     "Location: foo\r\n"
318                     "Location: foobar\r\n"
319                     "  baz\r\n"
320                     "\r\n"));
321     ds = (data_string *)
322       array_get_element_klen(&r->rqst_headers, CONST_STR_LEN("Location"));
323     assert(ds
324            && buffer_eq_slen(&ds->value, CONST_STR_LEN("foo, foobar    baz")));
325 
326     run_http_request_parse(r, __LINE__, 0,
327       "#1232 - duplicate headers with line-wrapping - test 2",
328       CONST_STR_LEN("GET / HTTP/1.0\r\n"
329                     "Location: \r\n"
330                     "Location: foobar\r\n"
331                     "  baz\r\n"
332                     "\r\n"));
333     ds = (data_string *)
334       array_get_element_klen(&r->rqst_headers, CONST_STR_LEN("Location"));
335     assert(ds && buffer_eq_slen(&ds->value, CONST_STR_LEN("foobar    baz")));
336 
337     run_http_request_parse(r, __LINE__, 0,
338       "#1232 - duplicate headers with line-wrapping - test 3",
339       CONST_STR_LEN("GET / HTTP/1.0\r\n"
340                     "A: \r\n"
341                     "Location: foobar\r\n"
342                     "  baz\r\n"
343                     "\r\n"));
344     ds = (data_string *)
345       array_get_element_klen(&r->rqst_headers, CONST_STR_LEN("Location"));
346     assert(ds && buffer_eq_slen(&ds->value, CONST_STR_LEN("foobar    baz")));
347 
348     run_http_request_parse(r, __LINE__, 400,
349       "missing protocol",
350       CONST_STR_LEN("GET /\r\n"
351                     "\r\n"));
352 
353     run_http_request_parse(r, __LINE__, 505,
354       "zeros in protocol version",
355       CONST_STR_LEN("GET / HTTP/01.01\r\n"
356                     "Host: foo\r\n"
357                     "\r\n"));
358 
359     run_http_request_parse(r, __LINE__, 505,
360       "missing major version",
361       CONST_STR_LEN("GET / HTTP/.01\r\n"
362                     "\r\n"));
363 
364     run_http_request_parse(r, __LINE__, 505,
365       "missing minor version",
366       CONST_STR_LEN("GET / HTTP/01.\r\n"
367                     "\r\n"));
368 
369     run_http_request_parse(r, __LINE__, 505,
370       "strings as version",
371       CONST_STR_LEN("GET / HTTP/a.b\r\n"
372                     "\r\n"));
373 
374     run_http_request_parse(r, __LINE__, 400,
375       "missing protocol + unknown method",
376       CONST_STR_LEN("BC /\r\n"
377                     "\r\n"));
378 
379     run_http_request_parse(r, __LINE__, 400,
380       "missing protocol + unknown method + missing URI",
381       CONST_STR_LEN("ABC\r\n"
382                     "\r\n"));
383 
384     run_http_request_parse(r, __LINE__, 501,
385       "unknown method",
386       CONST_STR_LEN("ABC / HTTP/1.0\r\n"
387                     "\r\n"));
388 
389     run_http_request_parse(r, __LINE__, 505,
390       "unknown protocol",
391       CONST_STR_LEN("GET / HTTP/1.3\r\n"
392                     "\r\n"));
393 
394     run_http_request_parse(r, __LINE__, 0,
395       "absolute URI",
396       CONST_STR_LEN("GET http://www.example.org/ HTTP/1.0\r\n"
397                     "\r\n"));
398     assert(buffer_eq_slen(r->http_host, CONST_STR_LEN("www.example.org")));
399     assert(buffer_eq_slen(&r->target, CONST_STR_LEN("/")));
400 
401     run_http_request_parse(r, __LINE__, 400,
402       "whitespace after key",
403       CONST_STR_LEN("GET / HTTP/1.0\r\n"
404                     "ABC : foo\r\n"
405                     "\r\n"));
406 
407     run_http_request_parse(r, __LINE__, 400,
408       "whitespace within key",
409       CONST_STR_LEN("GET / HTTP/1.0\r\n"
410                     "ABC a: foo\r\n"
411                     "\r\n"));
412 
413     run_http_request_parse(r, __LINE__, 0,
414       "no whitespace",
415       CONST_STR_LEN("GET / HTTP/1.0\r\n"
416                     "ABC:foo\r\n"
417                     "\r\n"));
418     ds = (data_string *)
419       array_get_element_klen(&r->rqst_headers, CONST_STR_LEN("ABC"));
420     assert(ds && buffer_eq_slen(&ds->value, CONST_STR_LEN("foo")));
421 
422     run_http_request_parse(r, __LINE__, 0,
423       "line-folding",
424       CONST_STR_LEN("GET / HTTP/1.0\r\n"
425                     "ABC:foo\r\n"
426                     "  bc\r\n"
427                     "\r\n"));
428     ds = (data_string *)
429       array_get_element_klen(&r->rqst_headers, CONST_STR_LEN("ABC"));
430     assert(ds && buffer_eq_slen(&ds->value, CONST_STR_LEN("foo    bc")));
431 
432     run_http_request_parse(r, __LINE__, 411,
433       "POST request, no Content-Length",
434       CONST_STR_LEN("POST / HTTP/1.0\r\n"
435                     "\r\n"));
436 
437     run_http_request_parse(r, __LINE__, 400,
438       "Duplicate Host headers, Bug #25",
439       CONST_STR_LEN("GET / HTTP/1.0\r\n"
440                     "Host: www.example.org\r\n"
441                     "Host: 123.example.org\r\n"
442                     "\r\n"));
443 
444     run_http_request_parse(r, __LINE__, 400,
445       "Duplicate Content-Length headers",
446       CONST_STR_LEN("GET / HTTP/1.0\r\n"
447                     "Content-Length: 5\r\n"
448                     "Content-Length: 4\r\n"
449                     "\r\n"));
450 
451     run_http_request_parse(r, __LINE__, 400,
452       "Duplicate Content-Type headers",
453       CONST_STR_LEN("GET / HTTP/1.0\r\n"
454                     "Content-Type: 5\r\n"
455                     "Content-Type: 4\r\n"
456                     "\r\n"));
457 
458     /* (not actually testing Range here anymore; parsing deferred until use) */
459 
460     run_http_request_parse(r, __LINE__, 0,
461       "Duplicate Range headers (get appended)",
462       CONST_STR_LEN("GET / HTTP/1.0\r\n"
463                     "Range: bytes=5-6\r\n"
464                     "Range: bytes=5-9\r\n"
465                     "\r\n"));
466 
467     run_http_request_parse(r, __LINE__, 0,
468       "Duplicate Range headers with invalid range (a)",
469       CONST_STR_LEN("GET / HTTP/1.0\r\n"
470                     "Range: bytes=0\r\n"
471                     "Range: bytes=5-9\r\n"
472                     "\r\n"));
473     run_http_request_parse(r, __LINE__, 0,
474       "Duplicate Range headers with invalid range (b)",
475       CONST_STR_LEN("GET / HTTP/1.0\r\n"
476                     "Range: bytes=5-9\r\n"
477                     "Range: bytes=0\r\n"
478                     "\r\n"));
479     run_http_request_parse(r, __LINE__, 0,
480       "Duplicate Range headers with invalid range (c)",
481       CONST_STR_LEN("GET / HTTP/1.0\r\n"
482                     "Range: 0\r\n"
483                     "Range: bytes=5-9\r\n"
484                     "\r\n"));
485     run_http_request_parse(r, __LINE__, 0,
486       "Duplicate Range headers with invalid range (d)",
487       CONST_STR_LEN("GET / HTTP/1.0\r\n"
488                     "Range: bytes=5-9\r\n"
489                     "Range: 0\r\n"
490                     "\r\n"));
491 
492     run_http_request_parse(r, __LINE__, 0,
493       "Duplicate If-None-Match headers",
494       CONST_STR_LEN("GET / HTTP/1.0\r\n"
495                     "If-None-Match: 5\r\n"
496                     "If-None-Match: 4\r\n"
497                     "\r\n"));
498 
499     run_http_request_parse(r, __LINE__, 400,
500       "Duplicate If-Modified-Since headers",
501       CONST_STR_LEN("GET / HTTP/1.0\r\n"
502                     "If-Modified-Since: 5\r\n"
503                     "If-Modified-Since: 4\r\n"
504                     "\r\n"));
505 
506     run_http_request_parse(r, __LINE__, 400,
507       "GET with Content-Length",
508       CONST_STR_LEN("GET / HTTP/1.0\r\n"
509                     "Content-Length: 4\r\n"
510                     "\r\n"
511                     "1234"));
512 
513     run_http_request_parse(r, __LINE__, 400,
514       "HEAD with Content-Length",
515       CONST_STR_LEN("HEAD / HTTP/1.0\r\n"
516                     "Content-Length: 4\r\n"
517                     "\r\n"
518                     "1234"));
519 
520     run_http_request_parse(r, __LINE__, 400,
521       "invalid chars in Header values (bug #1286)",
522       CONST_STR_LEN("GET / HTTP/1.0\r\n"
523                     "If-Modified-Since: \0\r\n"
524                     "\r\n"));
525 
526     run_http_request_parse(r, __LINE__, 0,
527       "absolute-uri in request-line (without Host)",
528       CONST_STR_LEN("GET http://zzz.example.org/ HTTP/1.1\r\n"
529                     "Connection: close\r\n"
530                     "\r\n"));
531     ds = (data_string *)
532       array_get_element_klen(&r->rqst_headers, CONST_STR_LEN("Host"));
533     assert(ds && buffer_eq_slen(&ds->value, CONST_STR_LEN("zzz.example.org")));
534 
535     run_http_request_parse(r, __LINE__, 0,
536       "absolute-uri in request-line (with Host match)",
537       CONST_STR_LEN("GET http://zzz.example.org/ HTTP/1.1\r\n"
538                     "Host: zzz.example.org\r\n"
539                     "Connection: close\r\n"
540                     "\r\n"));
541     ds = (data_string *)
542       array_get_element_klen(&r->rqst_headers, CONST_STR_LEN("Host"));
543     assert(ds && buffer_eq_slen(&ds->value, CONST_STR_LEN("zzz.example.org")));
544 
545     run_http_request_parse(r, __LINE__, 400,
546       "absolute-uri in request-line (with Host mismatch)",
547       CONST_STR_LEN("GET http://zzz.example.org/ HTTP/1.1\r\n"
548                     "Host: aaa.example.org\r\n"
549                     "Connection: close\r\n"
550                     "\r\n"));
551 
552     /* (quick check that none of above tests were left in a state
553      *  which resulted in subsequent tests returning 400 for other
554      *  reasons) */
555     run_http_request_parse(r, __LINE__, 0,
556       "valid",
557       CONST_STR_LEN("GET / HTTP/1.0\r\n"
558                     "Host: www.example.org\r\n"
559                     "\r\n"));
560 }
561 
562 #include "base.h"
563 #include "burl.h"
564 #include "log.h"
565 
566 int main (void)
567 {
568     request_st r;
569 
570     memset(&r, 0, sizeof(request_st));
571     r.conf.errh              = log_error_st_init();
572     r.conf.errh->errorlog_fd = -1; /* (disable) */
573     r.conf.allow_http11      = 1;
574     r.conf.http_parseopts    = HTTP_PARSEOPT_HEADER_STRICT
575                              | HTTP_PARSEOPT_HOST_STRICT
576                              | HTTP_PARSEOPT_HOST_NORMALIZE;
577 
578     test_request_http_request_parse(&r);
579 
580     free(r.target_orig.ptr);
581     free(r.target.ptr);
582     array_free_data(&r.rqst_headers);
583 
584     log_error_st_free(r.conf.errh);
585 
586     return 0;
587 }
588