libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

commit 914e7d9bda7ee8ed47bd99dffbfcc17917cd7ea7
parent 976c3a99f2a1c89fe5ad944e2dea2a5921b7f53d
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  7 Dec 2025 00:31:03 +0100

tons of header and cookie parsing tests

Diffstat:
Msrc/tests/raw/test_raw.c | 534++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 519 insertions(+), 15 deletions(-)

diff --git a/src/tests/raw/test_raw.c b/src/tests/raw/test_raw.c @@ -107,6 +107,416 @@ static struct Test tests[] = { .expect_parser = "H-Host:example.com", }, { + .name = "Multi-header HTTP/1.1 GET with query parameters", + .upload = "GET /?k=v&a HTTP/1.1\r\nHost: example.com\r\nKey: value\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-Key:value\nQ-k:v\nQ-a", + }, + { + .name = "Empty header value", + .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Empty:\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-X-Empty:", + }, + { + .name = "Header with leading/trailing whitespace", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nX-Space: value \r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-X-Space:value", + }, + { + .name = "Multiple headers with same name", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nX-Dup: first\r\nX-Dup: second\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-X-Dup:first", + }, + { + .name = "Case insensitive header names", + .upload = + "GET / HTTP/1.1\r\nhost: example.com\r\nContent-TYPE: text/plain\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-Content-Type:text/plain", + }, + { + .name = "Query parameter with empty value", + .upload = "GET /?key= HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-key:", + }, + { + .name = "Query parameter without value", + .upload = "GET /?flag HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-flag", + }, + { + .name = "Multiple query parameters", + .upload = "GET /?a=1&b=2&c=3 HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-a:1\nQ-b:2\nQ-c:3", + }, + { + .name = "URL encoded query parameter", + .upload = + "GET /?name=hello%20world&special=%21%40%23 HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-name:hello world\nQ-special:!@#", + }, + { + .name = "Simple cookie", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: session=abc123\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nC-session:abc123", + }, + { + .name = "Multiple cookies in one header", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: a=1; b=2; c=3\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nC-a:1\nC-b:2\nC-c:3", + }, +#if COMPATIBILITY_QUESTION + { + .name = "Cookie with spaces", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: key = value\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nC-key:value", + }, +#endif + { + .name = "POST with Content-Length", + .upload = + "POST /submit HTTP/1.1\r\nHost: example.com\r\nContent-Length: 5\r\n\r\nhello", + .expect_method = MHD_HTTP_METHOD_POST, + .expect_path = "/submit", + .expect_upload_size = 5, + .expect_parser = "H-Host:example.com\nH-Content-Length:5", + }, +#if VERY_BAD_BUG + { + .name = "Path with special characters", + .upload = + "GET /path/to/resource%20file.html HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/path/to/resource file.html", + .expect_parser = "H-Host:example.com", + }, +#endif + { + .name = "Long header value", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nX-Long: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = + "H-Host:example.com\nH-X-Long:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + }, + { + .name = "Query string with equals sign in value", + .upload = "GET /?expr=a=b HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-expr:a=b", + }, + { + .name = "Mixed query parameters with and without values", + .upload = + "GET /?debug&level=5&verbose HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-debug\nQ-level:5\nQ-verbose", + }, +#if PARSER_BUG + { + .name = "Continuation header (folded)", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nX-Multi: line one\r\n line two\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-X-Multi:line one line two", + }, +#endif + { + .name = "Empty query string", + .upload = "GET /? HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com", + }, +#if UNDERSPECIFIED_LIKELY_BAD + { + .name = "Fragment in URI (should probably be stripped!)", + .upload = "GET /page#section HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/page", + .expect_parser = "H-Host:example.com", + }, +#endif + { + .name = "PUT request with headers", + .upload = + "PUT /resource HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/json\r\nContent-Length: 0\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_PUT, + .expect_path = "/resource", + .expect_upload_size = 0, + .expect_parser = + "H-Host:example.com\nH-Content-Type:application/json\nH-Content-Length:0", + }, + { + .name = "DELETE request", + .upload = "DELETE /resource/123 HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_DELETE, + .expect_path = "/resource/123", + .expect_parser = "H-Host:example.com", + }, + + { + .name = "Header with colon in value", + .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Time: 12:34:56\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-X-Time:12:34:56", + }, + { + .name = "Cookie with empty value", + .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: empty=\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nC-empty:", + }, + { + .name = "Query parameter with plus sign encoding", + .upload = "GET /?text=hello+world HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-text:hello world", + }, + { + .name = "Multiple Cookie headers", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: a=1\r\nCookie: b=2\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nC-a:1\nC-b:2", + }, + { + .name = "HEAD request", + .upload = "HEAD /resource HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_HEAD, + .expect_path = "/resource", + .expect_parser = "H-Host:example.com", + }, + { + .name = "OPTIONS request", + .upload = "OPTIONS * HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_OPTIONS, + .expect_path = "*", + .expect_parser = "H-Host:example.com", + }, + { + .name = "Query with ampersand in value", + .upload = "GET /?code=a%26b HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-code:a&b", + }, + { + .name = "Query with percent sign in value", + .upload = "GET /?percent=100%25 HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-percent:100%", + }, + { + .name = "Root path with trailing slash", + .upload = "GET // HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "//", + .expect_parser = "H-Host:example.com", + }, + { + .name = "Deep path nesting", + .upload = "GET /a/b/c/d/e/f/g HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/a/b/c/d/e/f/g", + .expect_parser = "H-Host:example.com", + }, + { + .name = "Authorization header", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nAuthorization: Bearer token123\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-Authorization:Bearer token123", + }, + { + .name = "User-Agent header", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mozilla/5.0\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-User-Agent:Mozilla/5.0", + }, + { + .name = "Accept header with multiple values", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nAccept: text/html, application/json\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-Accept:text/html, application/json", + }, + { + .name = "Connection keep-alive", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-Connection:keep-alive", + }, + { + .name = "Query with duplicate parameters", + .upload = "GET /?id=1&id=2&id=3 HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-id:1", + }, + { + .name = "Cookie with quoted value", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: session=\"abc123\"\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nC-session:abc123", + }, + { + .name = "Query with UTF-8 encoding", + .upload = "GET /?name=%E2%9C%93 HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-name:✓", + }, + { + .name = "Referer header", + .upload = + "GET /page HTTP/1.1\r\nHost: example.com\r\nReferer: https://google.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/page", + .expect_parser = "H-Host:example.com\nH-Referer:https://google.com", + }, + { + .name = "Content-Type with charset", + .upload = + "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 0\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_POST, + .expect_path = "/", + .expect_upload_size = 0, + .expect_parser = + "H-Host:example.com\nH-Content-Type:text/html; charset=utf-8\nH-Content-Length:0", + }, + { + .name = "Cache-Control header", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nCache-Control: no-cache\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-Cache-Control:no-cache", + }, + { + .name = "If-None-Match header", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nIf-None-Match: \"etag123\"\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-If-None-Match:\"etag123\"", + }, + { + .name = "Range header", + .upload = + "GET /file HTTP/1.1\r\nHost: example.com\r\nRange: bytes=0-1023\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/file", + .expect_parser = "H-Host:example.com\nH-Range:bytes=0-1023", + }, + { + .name = "X-Forwarded-For header", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nX-Forwarded-For: 192.168.1.1\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nH-X-Forwarded-For:192.168.1.1", + }, + { + .name = "Query with empty parameter name", + .upload = "GET /?=value HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-:value", + }, +#if QUESTIONABLE_TEST + { + .name = "Absolute URI in request line", + .upload = "GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com", + }, +#endif + { + .name = "Query with semicolon separator", + .upload = "GET /?a=1;b=2 HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-a:1;b=2", + }, + { + .name = "Cookie with special characters", + .upload = + "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: data=value_with-special.chars\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nC-data:value_with-special.chars", + }, + { + .name = "Minimal HTTP/1.0 request with path only", + .upload = "GET /path HTTP/1.0\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/path", + }, + { + .name = "POST with form data Content-Type", + .upload = + "POST /form HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 0\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_POST, + .expect_path = "/form", + .expect_upload_size = 0, + .expect_parser = + "H-Host:example.com\nH-Content-Type:application/x-www-form-urlencoded\nH-Content-Length:0", + }, + { + .name = "Query with trailing ampersand", + .upload = "GET /?a=1& HTTP/1.1\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com\nQ-a:1", + }, + + { .name = NULL, } }; @@ -137,9 +547,11 @@ check_headers (struct MHD_Request *MHD_RESTRICT request, tok = strtok (NULL, "\n")) { + char dummy; + if (2 == sscanf (tok, - "H-%[^:]:%s", + "H-%[^:]:%[^\n]s", key, value)) { @@ -160,15 +572,16 @@ check_headers (struct MHD_Request *MHD_RESTRICT request, value)) ) { fprintf (stderr, - "Wrong header value `%s' under key `%s'\n", + "Wrong header value `%s' under key `%s', expected `%s'\n", have.cstr, - key); + key, + value); ok = false; } } else if (2 == sscanf (tok, - "C-%[^:]:%s", + "C-%[^:]:%[^\n]", key, value)) { @@ -189,15 +602,16 @@ check_headers (struct MHD_Request *MHD_RESTRICT request, value)) ) { fprintf (stderr, - "Wrong cookie value `%s' under key `%s'\n", + "Wrong cookie value `%s' under key `%s', expected `%s'\n", have.cstr, - key); + key, + value); ok = false; } } else if (2 == sscanf (tok, - "Q-%[^:]:%s", + "Q-%[^:]:%[^\n]", key, value)) { @@ -218,15 +632,16 @@ check_headers (struct MHD_Request *MHD_RESTRICT request, value)) ) { fprintf (stderr, - "Wrong URI query parameter value `%s' under key `%s'\n", + "Wrong URI query parameter value `%s' under key `%s', expected `%s'\n", have.cstr, - key); + key, + value); ok = false; } } else if (1 == sscanf (tok, - "H-%[^:]:%s", + "H-%[^:]:%[^\n]", key, value)) { @@ -241,11 +656,43 @@ check_headers (struct MHD_Request *MHD_RESTRICT request, "Expected header `%s' missing\n", key); ok = false; + } /* HTTP header can never be "NULL", only empty string */ + else if ( (NULL == have.cstr) || + (0 != strcmp (have.cstr, + "")) ) + { + fprintf (stderr, + "Unexpected non-empty header value `%s' under key `%s'\n", + have.cstr, + key); + ok = false; } - else if (NULL != have.cstr) + } + else if (2 == + sscanf (tok, + "C-%[^:]%c%[^\n]", + key, + &dummy, + value)) + { + struct MHD_StringNullable have; + + if (! MHD_request_get_value (request, + MHD_VK_COOKIE, + key, + &have)) + { + fprintf (stderr, + "Expected cookie `%s' missing\n", + key); + ok = false; + } + else if ( (NULL == have.cstr) || + (0 != strcmp (have.cstr, + "")) ) { fprintf (stderr, - "Unexpected non-NULL header value `%s' under key `%s'\n", + "Unexpected non-empty cookie value `%s' under key `%s'\n", have.cstr, key); ok = false; @@ -253,7 +700,7 @@ check_headers (struct MHD_Request *MHD_RESTRICT request, } else if (1 == sscanf (tok, - "C-%[^:]:%s", + "C-%[^:]:%[^\n]", key, value)) { @@ -278,9 +725,39 @@ check_headers (struct MHD_Request *MHD_RESTRICT request, ok = false; } } + else if (2 == + sscanf (tok, + "Q-%[^:]%c%[^\n]", + key, + &dummy, + value)) + { + struct MHD_StringNullable have; + + if (! MHD_request_get_value (request, + MHD_VK_URI_QUERY_PARAM, + key, + &have)) + { + fprintf (stderr, + "Expected URI query parameter `%s' missing\n", + key); + ok = false; + } + else if ( (NULL == have.cstr) || + (0 != strcmp (have.cstr, + "")) ) + { + fprintf (stderr, + "Unexpected non-empty URI query parameter value `%s' under key `%s'\n", + have.cstr, + key); + ok = false; + } + } else if (1 == sscanf (tok, - "Q-%[^:]:%s", + "Q-%[^:]:%[^\n]", key, value)) { @@ -305,6 +782,33 @@ check_headers (struct MHD_Request *MHD_RESTRICT request, ok = false; } } + else if (1 == + sscanf (tok, + "Q-:%[^\n]", + value)) + { + struct MHD_StringNullable have; + + if (! MHD_request_get_value (request, + MHD_VK_URI_QUERY_PARAM, + "", + &have)) + { + fprintf (stderr, + "Expected URI query parameter without key missing\n"); + ok = false; + } + else if ( (NULL == have.cstr) || + (0 != strcmp (have.cstr, + value)) ) + { + fprintf (stderr, + "Wrong URI query parameter value `%s' under missing key, expected `%s'\n", + have.cstr, + value); + ok = false; + } + } else { fprintf (stderr, @@ -484,7 +988,7 @@ run_tests (void) if (0 != run_test ()) { fprintf (stderr, - "Test %s failed\n", + "Test `%s' failed\n", current->name); return 1; }