#include "first.h" #undef NDEBUG #include #include #include #include #include "request.c" #include "fdlog.h" static void test_request_reset(request_st * const r) { r->http_method = HTTP_METHOD_UNSET; r->http_version = HTTP_VERSION_UNSET; r->http_host = NULL; r->rqst_htags = 0; r->reqbody_length = 0; buffer_clear(&r->target_orig); buffer_clear(&r->target); array_reset_data_strings(&r->rqst_headers); } static void run_http_request_parse(request_st * const r, int line, int status, const char *desc, const char *req, size_t reqlen) { unsigned short hloffsets[32]; char hdrs[1024]; test_request_reset(r); assert(reqlen < sizeof(hdrs)); memcpy(hdrs, req, reqlen); hloffsets[0] = 1; hloffsets[1] = 0; hloffsets[2] = 0; for (const char *n=req, *end=req+reqlen; (n=memchr(n,'\n',end-n)); ++n) { if (++hloffsets[0] >= sizeof(hloffsets)/sizeof(*hloffsets)) break; hloffsets[hloffsets[0]] = n - req + 1; } --hloffsets[0]; /*(ignore final blank line "\r\n" ending headers)*/ const int proto_default_port = 80; int http_status = http_request_parse_hoff(r, hdrs, hloffsets, proto_default_port); if (http_status != status) { fprintf(stderr, "%s.%d: %s() failed: expected '%d', got '%d' for test %s\n", __FILE__, line, "http_request_parse_hoff", status, http_status, desc); fflush(stderr); abort(); } } static void test_request_http_request_parse(request_st * const r) { buffer *b; run_http_request_parse(r, __LINE__, 400, "invalid request-line: space", CONST_STR_LEN(" \r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: space, char", CONST_STR_LEN(" a\r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: dot", CONST_STR_LEN(".\r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: single char", CONST_STR_LEN("a\r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: char, space", CONST_STR_LEN("a \r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: method only", CONST_STR_LEN("GET\r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: method space", CONST_STR_LEN("GET \r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: method space space", CONST_STR_LEN("GET \r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: method space proto", CONST_STR_LEN("GET HTTP/1.0\r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: method space space proto", CONST_STR_LEN("GET HTTP/1.0\r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: method space space space proto", CONST_STR_LEN("GET HTTP/1.0\r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: method slash proto, no spaces", CONST_STR_LEN("GET/HTTP/1.0\r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: method space slash proto", CONST_STR_LEN("GET /HTTP/1.0\r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "invalid request-line: method space space slash proto", CONST_STR_LEN("GET /HTTP/1.0\r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 501, "invalid request-line: method slash space proto", CONST_STR_LEN("GET/ HTTP/1.0\r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 501, "invalid request-line: method slash space space proto", CONST_STR_LEN("GET/ HTTP/1.0\r\n" "Host: www.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "hostname", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: www.example.org\r\n" "\r\n")); assert(r->http_host && buffer_eq_slen(r->http_host, CONST_STR_LEN("www.example.org"))); run_http_request_parse(r, __LINE__, 0, "IPv4 address", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: 127.0.0.1\r\n" "\r\n")); assert(r->http_host && buffer_eq_slen(r->http_host, CONST_STR_LEN("127.0.0.1"))); #ifdef HAVE_IPV6 run_http_request_parse(r, __LINE__, 0, "IPv6 address", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: [::1]\r\n" "\r\n")); assert(r->http_host && buffer_eq_slen(r->http_host, CONST_STR_LEN("[::1]"))); #endif run_http_request_parse(r, __LINE__, 0, "hostname + port", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: www.example.org:80\r\n" "\r\n")); assert(r->http_host && buffer_eq_slen(r->http_host, CONST_STR_LEN("www.example.org"))); run_http_request_parse(r, __LINE__, 0, "IPv4 address + port", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: 127.0.0.1:80\r\n" "\r\n")); assert(r->http_host && buffer_eq_slen(r->http_host, CONST_STR_LEN("127.0.0.1"))); #ifdef HAVE_IPV6 run_http_request_parse(r, __LINE__, 0, "IPv6 address + port", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: [::1]:80\r\n" "\r\n")); assert(r->http_host && buffer_eq_slen(r->http_host, CONST_STR_LEN("[::1]"))); #endif run_http_request_parse(r, __LINE__, 400, "directory traversal", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: ../123.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "leading and trailing dot", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: .jsdh.sfdg.sdfg.\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "trailing dot is ok", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: jsdh.sfdg.sdfg.\r\n" "\r\n")); assert(r->http_host && buffer_eq_slen(r->http_host, CONST_STR_LEN("jsdh.sfdg.sdfg"))); run_http_request_parse(r, __LINE__, 400, "leading dot", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: .jsdh.sfdg.sdfg\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "two dots", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: jsdh..sfdg.sdfg\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "broken port-number", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: jsdh.sfdg.sdfg:asd\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "negative port-number", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: jsdh.sfdg.sdfg:-1\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "port given but host missing", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: :80\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "port and host are broken", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: .jsdh.sfdg.:sdfg.\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "allowed characters in host-name", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: a.b-c.d123\r\n" "\r\n")); assert(r->http_host && buffer_eq_slen(r->http_host, CONST_STR_LEN("a.b-c.d123"))); run_http_request_parse(r, __LINE__, 400, "leading dash", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: -a.c\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "dot only", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: .\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "broken IPv4 address - non-digit", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: a192.168.2.10:1234\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "broken IPv4 address - too short", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: 192.168.2:1234\r\n" "\r\n")); #ifdef HAVE_IPV6 run_http_request_parse(r, __LINE__, 400, "IPv6 address + SQL injection", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: [::1]' UNION SELECT '/\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "IPv6 address + path traversal", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: [::1]/../../../\r\n" "\r\n")); #endif run_http_request_parse(r, __LINE__, 400, "negative Content-Length", CONST_STR_LEN("POST /12345.txt HTTP/1.0\r\n" "Content-Length: -2\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 411, "Content-Length is empty", CONST_STR_LEN("POST /12345.txt HTTP/1.0\r\n" "Host: 123.example.org\r\n" "Content-Length:\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "Host missing", CONST_STR_LEN("GET / HTTP/1.1\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "empty request-URI", CONST_STR_LEN("GET HTTP/1.0\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "URL-decode request-URI", CONST_STR_LEN("GET /index%2ehtml HTTP/1.0\r\n" "\r\n")); assert(buffer_eq_slen(&r->uri.path, CONST_STR_LEN("/index.html"))); run_http_request_parse(r, __LINE__, 0, "#1232 - duplicate headers with line-wrapping", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Location: foo\r\n" "Location: foobar\r\n" " baz\r\n" "\r\n")); b = http_header_request_get(r, HTTP_HEADER_LOCATION, CONST_STR_LEN("Location")); assert(b && buffer_eq_slen(b, CONST_STR_LEN("foo, foobar baz"))); run_http_request_parse(r, __LINE__, 0, "#1232 - duplicate headers with line-wrapping - test 2", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Location: \r\n" "Location: foobar\r\n" " baz\r\n" "\r\n")); b = http_header_request_get(r, HTTP_HEADER_LOCATION, CONST_STR_LEN("Location")); assert(b && buffer_eq_slen(b, CONST_STR_LEN("foobar baz"))); run_http_request_parse(r, __LINE__, 0, "#1232 - duplicate headers with line-wrapping - test 3", CONST_STR_LEN("GET / HTTP/1.0\r\n" "A: \r\n" "Location: foobar\r\n" " baz\r\n" "\r\n")); b = http_header_request_get(r, HTTP_HEADER_LOCATION, CONST_STR_LEN("Location")); assert(b && buffer_eq_slen(b, CONST_STR_LEN("foobar baz"))); run_http_request_parse(r, __LINE__, 400, "missing protocol", CONST_STR_LEN("GET /\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 505, "zeros in protocol version", CONST_STR_LEN("GET / HTTP/01.01\r\n" "Host: foo\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 505, "missing major version", CONST_STR_LEN("GET / HTTP/.01\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 505, "missing minor version", CONST_STR_LEN("GET / HTTP/01.\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 505, "strings as version", CONST_STR_LEN("GET / HTTP/a.b\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "missing protocol + unknown method", CONST_STR_LEN("BC /\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "missing protocol + unknown method + missing URI", CONST_STR_LEN("ABC\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 501, "unknown method", CONST_STR_LEN("ABC / HTTP/1.0\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 505, "unknown protocol", CONST_STR_LEN("GET / HTTP/1.3\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "absolute URI", CONST_STR_LEN("GET http://www.example.org/ HTTP/1.0\r\n" "\r\n")); assert(r->http_host && buffer_eq_slen(r->http_host, CONST_STR_LEN("www.example.org"))); assert(buffer_eq_slen(&r->target, CONST_STR_LEN("/"))); run_http_request_parse(r, __LINE__, 400, "whitespace after key", CONST_STR_LEN("GET / HTTP/1.0\r\n" "ABC : foo\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "whitespace within key", CONST_STR_LEN("GET / HTTP/1.0\r\n" "ABC a: foo\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "no whitespace", CONST_STR_LEN("GET / HTTP/1.0\r\n" "ABC:foo\r\n" "\r\n")); b = http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("ABC")); assert(b && buffer_eq_slen(b, CONST_STR_LEN("foo"))); run_http_request_parse(r, __LINE__, 0, "line-folding", CONST_STR_LEN("GET / HTTP/1.0\r\n" "ABC:foo\r\n" " bc\r\n" "\r\n")); b = http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("ABC")); assert(b && buffer_eq_slen(b, CONST_STR_LEN("foo bc"))); run_http_request_parse(r, __LINE__, 411, "POST request, no Content-Length", CONST_STR_LEN("POST / HTTP/1.0\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "Duplicate Host headers, Bug #25", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: www.example.org\r\n" "Host: 123.example.org\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "Duplicate Content-Length headers", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Content-Length: 5\r\n" "Content-Length: 4\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "Duplicate Content-Type headers", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Content-Type: 5\r\n" "Content-Type: 4\r\n" "\r\n")); /* (not actually testing Range here anymore; parsing deferred until use) */ run_http_request_parse(r, __LINE__, 0, "Duplicate Range headers (get appended)", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Range: bytes=5-6\r\n" "Range: bytes=5-9\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "Duplicate Range headers with invalid range (a)", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Range: bytes=0\r\n" "Range: bytes=5-9\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "Duplicate Range headers with invalid range (b)", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Range: bytes=5-9\r\n" "Range: bytes=0\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "Duplicate Range headers with invalid range (c)", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Range: 0\r\n" "Range: bytes=5-9\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "Duplicate Range headers with invalid range (d)", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Range: bytes=5-9\r\n" "Range: 0\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "Duplicate If-None-Match headers", CONST_STR_LEN("GET / HTTP/1.0\r\n" "If-None-Match: 5\r\n" "If-None-Match: 4\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "Duplicate If-Modified-Since headers", CONST_STR_LEN("GET / HTTP/1.0\r\n" "If-Modified-Since: 5\r\n" "If-Modified-Since: 4\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 400, "GET with Content-Length", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Content-Length: 4\r\n" "\r\n" "1234")); run_http_request_parse(r, __LINE__, 400, "HEAD with Content-Length", CONST_STR_LEN("HEAD / HTTP/1.0\r\n" "Content-Length: 4\r\n" "\r\n" "1234")); run_http_request_parse(r, __LINE__, 400, "invalid chars in Header values (bug #1286)", CONST_STR_LEN("GET / HTTP/1.0\r\n" "If-Modified-Since: \0\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "absolute-uri in request-line (without Host)", CONST_STR_LEN("GET http://zzz.example.org/ HTTP/1.1\r\n" "Connection: close\r\n" "\r\n")); b = http_header_request_get(r, HTTP_HEADER_HOST, CONST_STR_LEN("Host")); assert(b && buffer_eq_slen(b, CONST_STR_LEN("zzz.example.org"))); run_http_request_parse(r, __LINE__, 0, "absolute-uri in request-line (with Host match)", CONST_STR_LEN("GET http://zzz.example.org/ HTTP/1.1\r\n" "Host: zzz.example.org\r\n" "Connection: close\r\n" "\r\n")); b = http_header_request_get(r, HTTP_HEADER_HOST, CONST_STR_LEN("Host")); assert(b && buffer_eq_slen(b, CONST_STR_LEN("zzz.example.org"))); run_http_request_parse(r, __LINE__, 400, "absolute-uri in request-line (with Host mismatch)", CONST_STR_LEN("GET http://zzz.example.org/ HTTP/1.1\r\n" "Host: aaa.example.org\r\n" "Connection: close\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "ignore duplicated If-Modified-Since if matching", CONST_STR_LEN("GET / HTTP/1.1\r\n" "Host: zzz.example.org\r\n" "If-Modified-Since: Sun, 01 Jan 2036 00:00:02 GMT\r\n" "If-Modified-Since: Sun, 01 Jan 2036 00:00:02 GMT\r\n" "Connection: close\r\n" "\r\n")); b = http_header_request_get(r, HTTP_HEADER_IF_MODIFIED_SINCE, CONST_STR_LEN("If-Modified-Since")); assert(b && buffer_eq_slen(b, CONST_STR_LEN("Sun, 01 Jan 2036 00:00:02 GMT"))); run_http_request_parse(r, __LINE__, 400, "reject duplicated If-Modified-Since if not matching", CONST_STR_LEN("GET / HTTP/1.1\r\n" "Host: zzz.example.org\r\n" "If-Modified-Since: Sun, 01 Jan 2036 00:00:02 GMT\r\n" "If-Modified-Since: Sun, 01 Jan 2036 00:00:03 GMT\r\n" "Connection: close\r\n" "\r\n")); run_http_request_parse(r, __LINE__, 0, "large headers", /*(copied from tests/request.t)*/ CONST_STR_LEN("GET / HTTP/1.0\r\n" "Hsgfsdjf: asdfhdf\r\n" "hdhd: shdfhfdasd\r\n" "hfhr: jfghsdfg\r\n" "jfuuehdmn: sfdgjfdg\r\n" "jvcbzufdg: sgfdfg\r\n" "hrnvcnd: jfjdfg\r\n" "jfusfdngmd: gfjgfdusdfg\r\n" "nfj: jgfdjdfg\r\n" "jfue: jfdfdg\r\n" "\r\n")); /* (quick check that none of above tests were left in a state * which resulted in subsequent tests returning 400 for other * reasons) */ run_http_request_parse(r, __LINE__, 0, "valid", CONST_STR_LEN("GET / HTTP/1.0\r\n" "Host: www.example.org\r\n" "\r\n")); } #include "base.h" #include "burl.h" #include "log.h" void test_request (void); void test_request (void) { request_st r; memset(&r, 0, sizeof(request_st)); r.conf.errh = fdlog_init(NULL, -1, FDLOG_FD); r.conf.errh->fd = -1; /* (disable) */ r.conf.allow_http11 = 1; r.conf.http_parseopts = HTTP_PARSEOPT_HEADER_STRICT | HTTP_PARSEOPT_HOST_STRICT | HTTP_PARSEOPT_HOST_NORMALIZE; test_request_http_request_parse(&r); free(r.target_orig.ptr); free(r.target.ptr); free(r.uri.authority.ptr); free(r.uri.path.ptr); free(r.uri.scheme.ptr); array_free_data(&r.rqst_headers); fdlog_free(r.conf.errh); }