commit 459fb93fabb14c594826da7b89810a2f9a43d4a6
parent 7719d5d0ece95710f34f11be54289f801aef4646
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 21 Dec 2025 14:04:32 +0100
add tests for handling of incorrect or non-standard requests
Diffstat:
3 files changed, 940 insertions(+), 1 deletion(-)
diff --git a/src/tests/raw/Makefile.am b/src/tests/raw/Makefile.am
@@ -25,11 +25,17 @@ $(top_builddir)/src/mhd2/libmicrohttpd2.la: $(top_builddir)/src/mhd2/Makefile
$(am__cd) $(top_builddir)/src/mhd2 && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd2.la
check_PROGRAMS = \
+ test_incompatible \
test_ram \
test_raw
TESTS = $(check_PROGRAMS)
+test_incompatible_SOURCES = \
+ test_incompatible.c
+test_incompatible_LDADD = \
+ -lmicrohttpd2
+
test_ram_SOURCES = \
test_ram.c
test_ram_LDADD = \
diff --git a/src/tests/raw/test_incompatible.c b/src/tests/raw/test_incompatible.c
@@ -0,0 +1,924 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
+/*
+ This file is part of GNU libmicrohttpd.
+ Copyright (C) 2025 Christian Grothoff
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ Alternatively, you can redistribute GNU libmicrohttpd and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version, together
+ with the eCos exception, as follows:
+
+ As a special exception, if other files instantiate templates or
+ use macros or inline functions from this file, or you compile this
+ file and link it with other works to produce a work based on this
+ file, this file does not by itself cause the resulting work to be
+ covered by the GNU General Public License. However the source code
+ for this file must still be made available in accordance with
+ section (3) of the GNU General Public License v2.
+
+ This exception does not invalidate any other reasons why a work
+ based on this file might be covered by the GNU General Public
+ License.
+
+ You should have received copies of the GNU Lesser General Public
+ License and the GNU General Public License along with this library;
+ if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file test_incompatible.c
+ * @brief tests server rejects incorrect or non-standard requests, either
+ * those that are:
+ * - incompatible to MUST requirements, or
+ * - non-standard and violate SHOULD requirements
+ * @author Christian Grothoff
+ */
+#include <stdio.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+#include "microhttpd2.h"
+
+#define LOG 0
+
+/**
+ * Defines a test.
+ */
+struct Test
+{
+ /**
+ * Human-readable name of the test. NULL to end test array.
+ */
+ const char *name;
+
+ /**
+ * Request to send to the server.
+ */
+ const char *upload;
+
+};
+
+
+/**
+ * Tests with HTTP requests that violate MUST constraints
+ * of the HTTP specifications.
+ *
+ * For example, using a bare LF instead of CRLF is forbidden, and
+ * requests that include both a "Transfer-Encoding:" and a
+ * "Content-Length:" headers are rejected.
+ */
+static struct Test tests_must[] = {
+ {
+ .name = "HTTP 1.1 without Host",
+ .upload = "GET / HTTP/1.1\r\n\r\n",
+ },
+ {
+ .name = "HTTP 1.0 GET without CRLF",
+ .upload = "GET / HTTP/1.0\n\n",
+ },
+ {
+ .name = "POST with both Content-Length and Transfer-Encoding",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n",
+ },
+ {
+ .name = "unsupported Ttransfer-Encoding",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: wild\r\n\r\n0\r\n",
+ },
+ {
+ .name = "Invalid HTTP version format",
+ .upload = "GET / HTTP/1\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 2.3: HTTP-version must be "HTTP/" followed by two digits separated by "."
+ },
+ {
+ .name = "Missing space after method",
+ .upload = "GET/ HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 3: Request-line requires SP between method and request-target
+ },
+ {
+ .name = "Invalid request-target with space",
+ .upload = "GET /path with space HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 3.2: Request-target must not contain unencoded spaces
+ },
+ {
+ .name = "Header field name with space",
+ .upload = "GET / HTTP/1.1\r\nHost Name: example.com\r\n\r\n",
+ // RFC 9110 Section 5.1: Field names must be tokens (no spaces allowed)
+ },
+#if QUESTIONABLE
+ {
+ .name = "Header field name with colon",
+ .upload = "GET / HTTP/1.1\r\nHost:Name: example.com\r\n\r\n",
+ // RFC 9110 Section 5.1: Field names must be tokens (colons not allowed)
+ },
+#endif
+ {
+ .name = "Missing colon after header field name",
+ .upload = "GET / HTTP/1.1\r\nHost example.com\r\n\r\n",
+ // RFC 9112 Section 5: Header field must have name, colon, and value
+ },
+ {
+ .name = "Header line ending with bare CR",
+ .upload = "GET / HTTP/1.1\rHost: example.com\r\n\r\n",
+ // RFC 9112 Section 2.2: Lines must end with CRLF, not bare CR
+ },
+ {
+ .name = "Request line ending with bare LF",
+ .upload = "GET / HTTP/1.1\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 2.2: Request-line must end with CRLF
+ },
+ {
+ .name = "Multiple Host headers",
+ .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nHost: other.com\r\n\r\n",
+ // RFC 9112 Section 3.2: A sender MUST NOT generate multiple Host header fields
+ },
+ {
+ .name = "Negative Content-Length",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: -5\r\n\r\n",
+ // RFC 9110 Section 8.6: Content-Length value must be non-negative decimal integer
+ },
+ {
+ .name = "Non-numeric Content-Length",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: abc\r\n\r\n",
+ // RFC 9110 Section 8.6: Content-Length must be a decimal integer
+ },
+ {
+ .name = "Multiple Content-Length with different values",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 5\r\nContent-Length: 10\r\n\r\n",
+ // RFC 9110 Section 8.6: Multiple Content-Length values must be identical
+ },
+#if BUG
+ {
+ .name = "Invalid method with control character",
+ .upload = "GET\x01 / HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9110 Section 9.1: Method token must not contain control characters
+ },
+#endif
+ {
+ .name = "Request-target starting with space",
+ .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 3: Only single SP allowed between method and request-target
+ },
+ {
+ .name = "HTTP/0.9 simple request with headers",
+ .upload = "GET /\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 2.3: HTTP/0.9 requests must not have headers
+ },
+#if BUG_HANGS
+ {
+ .name = "Missing final CRLF after headers",
+ .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n",
+ // RFC 9112 Section 6.1: Empty line (CRLF) required after headers
+ },
+#endif
+ {
+ .name = "Whitespace before header field name",
+ .upload = "GET / HTTP/1.1\r\n Host: example.com\r\n\r\n",
+ // RFC 9112 Section 5: No whitespace allowed before field name
+ },
+
+
+ {
+ .name = "Empty request line",
+ .upload = "\r\n\r\n",
+ // RFC 9112 Section 3: Request-line is required
+ },
+ {
+ .name = "Request line with only method",
+ .upload = "GET\r\n\r\n",
+ // RFC 9112 Section 3: Request-line must have method, target, and version
+ },
+ {
+ .name = "Request line with only method and target",
+ .upload = "GET /\r\n\r\n",
+ // RFC 9112 Section 3: HTTP-version is required in request-line
+ },
+ {
+ .name = "Request with only CR as line ending",
+ .upload = "GET / HTTP/1.1\rHost: example.com\r\r",
+ // RFC 9112 Section 2.2: CRLF required, not bare CR
+ },
+ {
+ .name = "Missing space before HTTP version",
+ .upload = "GET /HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 3: SP required between request-target and HTTP-version
+ },
+ {
+ .name = "HTTP version with extra dot",
+ .upload = "GET / HTTP/1.1.0\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 2.3: Version format is "HTTP/" DIGIT "." DIGIT
+ },
+ {
+ .name = "HTTP version with letter",
+ .upload = "GET / HTTP/1.A\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 2.3: Version numbers must be digits
+ },
+#if QUESTIONABLE
+ {
+ .name = "Method with lowercase letters",
+ .upload = "get / HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9110 Section 9.1: Method is case-sensitive
+ // Alas, we may consider this a non-standard method? :-)
+ },
+#endif
+#if BUG
+ {
+ .name = "Request-target with fragment identifier",
+ .upload = "GET /path#fragment HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 3.2.1: Fragment must not be sent in request-target
+ },
+#endif
+#if BUG
+ {
+ .name = "Absolute-form with userinfo in HTTP/1.1",
+ .upload =
+ "GET http://user:pass@example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9110 Section 4.2.4: Userinfo (and its "@" delimiter) is now disallowed
+ },
+#endif
+ {
+ .name = "Request-target with bare CR",
+ .upload = "GET /path\r/file HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 3.2: Request-target must not contain CR
+ },
+ {
+ .name = "Request-target with LF",
+ .upload = "GET /path\n/file HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 3.2: Request-target must not contain LF
+ },
+ {
+ .name = "Header colon without field name",
+ .upload = "GET / HTTP/1.1\r\n: value\r\nHost: example.com\r\n\r\n",
+ // RFC 9110 Section 5.1: Field name is required before colon
+ },
+ {
+ .name = "Content-Length with plus sign",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: +10\r\n\r\n0123456789",
+ // RFC 9110 Section 8.6: Content-Length must be 1*DIGIT (no sign allowed)
+ },
+ {
+ .name = "Content-Length with whitespace",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 1 0\r\n\r\n0123456789",
+ // RFC 9110 Section 8.6: Content-Length must be digits only
+ },
+ {
+ .name = "Content-Length with decimal point",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 10.0\r\n\r\n0123456789",
+ // RFC 9110 Section 8.6: Content-Length must be decimal integer, not floating point
+ },
+ {
+ .name = "Content-Length overflow value",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 99999999999999999999999999999\r\n\r\n",
+ // RFC 9110 Section 8.6: Content-Length must be valid decimal integer
+ },
+#if BUG
+ {
+ .name = "Request with vertical tab in header",
+ .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Custom:\vvalue\r\n\r\n",
+ // RFC 9110 Section 5.5: Only HTAB, SP, and VCHAR allowed in field values (VT is 0x0B)
+ },
+#endif
+#if BUG
+ {
+ .name = "Request with form feed in header value",
+ .upload =
+ "GET / HTTP/1.1\r\nHost: example.com\r\nX-Custom: val\fue\r\n\r\n",
+ // RFC 9110 Section 5.5: Form feed (0x0C) not allowed in field values
+ },
+#endif
+ {
+ .name = "Transfer-Encoding with unknown coding",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: gzip\r\n\r\n",
+ // RFC 9112 Section 6.1: Only 'chunked' and specific codings defined
+ },
+ {
+ .name = "Transfer-Encoding chunked not last",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked, gzip\r\n\r\n",
+ // RFC 9112 Section 6.1: 'chunked' must be last when present
+ },
+#if MINOR_BUG
+ {
+ .name = "HTTP/1.0 with Transfer-Encoding",
+ .upload =
+ "POST / HTTP/1.0\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n",
+ // RFC 9112 Section 6.1: Transfer-Encoding must not be sent in HTTP/1.0
+ },
+#endif
+#if BUG
+ {
+ .name = "POST without Content-Length or Transfer-Encoding",
+ .upload = "POST / HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 6.3: Message with body must have Content-Length or Transfer-Encoding
+ },
+#endif
+#if BUG
+ {
+ .name = "Request with CTL character in header name",
+ .upload =
+ "GET / HTTP/1.1\r\nHost: example.com\r\nX-Cu\x01stom: value\r\n\r\n",
+ // RFC 9110 Section 5.1: Field names must be tokens, CTL chars not allowed
+ },
+#endif
+#if BUG
+ {
+ .name = "Request with DEL character in header name",
+ .upload =
+ "GET / HTTP/1.1\r\nHost: example.com\r\nX-Custom\x7F: value\r\n\r\n",
+ // RFC 9110 Section 5.1: DEL (0x7F) not allowed in field names
+ },
+#endif
+#if BUG
+ {
+ .name = "Request with non-ASCII in header name",
+ .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Cüstom: value\r\n\r\n",
+ // RFC 9110 Section 5.1: Field names must be ASCII tokens
+ },
+#endif
+#if BUG
+ {
+ .name = "Request-target as asterisk for non-OPTIONS",
+ .upload = "GET * HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 3.2.4: Asterisk form only valid for OPTIONS
+ },
+#endif
+#if BUG
+ {
+ .name = "Authority-form for non-CONNECT",
+ .upload = "GET example.com:80 HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 3.2.3: Authority-form only valid for CONNECT
+ },
+#endif
+#if BUG
+ {
+ .name = "HTTP/1.1 GET with empty Host header",
+ .upload = "GET / HTTP/1.1\r\nHost: \r\n\r\n",
+ // RFC 9112 Section 3.2: Empty Host header is invalid for this target form
+ },
+#endif
+#if BUG
+ {
+ .name = "Request with quoted string in field name",
+ .upload =
+ "GET / HTTP/1.1\r\nHost: example.com\r\n\"X-Custom\": value\r\n\r\n",
+ // RFC 9110 Section 5.1: Field names must be tokens, not quoted strings
+ },
+#endif
+ {
+ .name = "HTTP/1.1 request with HTTP/1.2 version",
+ .upload = "GET / HTTP/1.2\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 2.3: HTTP/1.2 is not defined
+ },
+ {
+ .name = "HTTP version HTTP/2.0 on HTTP/1.1 connection",
+ .upload = "GET / HTTP/2.0\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 2.3: HTTP/2 uses different framing, not text-based
+ },
+ {
+ .name = "Empty method",
+ .upload = " / HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 3: Method is required
+ },
+#if BUG
+ {
+ .name = "Method with special character",
+ .upload = "GET/ / HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9110 Section 9.1: Method must be a token (no '/' in method name)
+ },
+#endif
+ {
+ .name = "Triple Host headers",
+ .upload =
+ "GET / HTTP/1.1\r\nHost: a.com\r\nHost: b.com\r\nHost: c.com\r\n\r\n",
+ // RFC 9112 Section 3.2: Multiple Host headers forbidden
+ },
+ {
+ .name = "Content-Length with hexadecimal",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0x10\r\n\r\n0123456789012345",
+ // RFC 9110 Section 8.6: Content-Length must be decimal, not hexadecimal
+ },
+ {
+ .name = NULL,
+ }
+};
+
+
+/**
+ * Tests with HTTP requests that violate MUST constraints
+ * of the HTTP specifications during the upload.
+ */
+static struct Test tests_must_upload[] = {
+ {
+ .name = "Chunked encoding with invalid hex",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\nGG\r\n\r\n",
+ // RFC 9112 Section 7.1: Chunk size must be hexadecimal
+ },
+ {
+ .name = "Chunked encoding with negative size",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n-5\r\n\r\n",
+ // RFC 9112 Section 7.1: Chunk size must be non-negative hex
+ },
+ {
+ .name = "Chunked encoding missing CRLF after size",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5hello\r\n0\r\n\r\n",
+ // RFC 9112 Section 7.1: CRLF required after chunk-size
+ },
+ {
+ .name = "Chunked encoding missing CRLF after data",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello0\r\n\r\n",
+ // RFC 9112 Section 7.1: CRLF required after chunk-data
+ },
+#if BAD_HANGS
+ {
+ .name = "Chunked with no final chunk",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n",
+ // RFC 9112 Section 7.1: Last chunk (size 0) is required
+ },
+#endif
+ {
+ .name = "Chunk size with leading whitespace",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n 5\r\nhello\r\n0\r\n\r\n",
+ // RFC 9112 Section 7.1: No whitespace before chunk-size
+ },
+#if BAD_HANGS
+ {
+ .name = "Chunk size exceeding data provided",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\nA\r\nhello\r\n0\r\n\r\n",
+ // RFC 9112 Section 7.1: Chunk-size must match chunk-data length (10 bytes expected, 5 provided)
+ },
+#endif
+ {
+ .name = NULL,
+ }
+};
+
+
+/**
+ * Tests with HTTP requests that violate SHOULD constraints
+ * of the HTTP specifications.
+ *
+ * For example, for chunked encoding, this level (and more restrictive
+ * ones) forbids whitespace in chunk extensions. For cookie parsing,
+ * this level (and more restrictive ones) rejects the entire cookie if
+ * even a single value within it is incorrectly encoded.
+ */
+static struct Test tests_should[] = {
+ {
+ .name = "Obsolete line folding in header",
+ .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n continuation\r\n\r\n",
+ // RFC 9112 Section 5.2: Line folding is obsolete, recipients SHOULD reject or replace with SP
+ },
+#if MINOR_BUG
+ {
+ .name = "Multiple spaces after colon in header",
+ .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9110 Section 5.3: Senders SHOULD NOT generate optional whitespace except as SP
+ },
+#endif
+#if MINOR_BUG
+ {
+ .name = "Trailing whitespace in header value",
+ .upload = "GET / HTTP/1.1\r\nHost: example.com \r\n\r\n",
+ // RFC 9110 Section 5.3: Trailing whitespace should be stripped
+ },
+#endif
+#if MINOR_BUG
+ {
+ .name = "Leading whitespace in header value",
+ .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n",
+ // RFC 9110 Section 5.3: Leading whitespace should be stripped
+ },
+#endif
+#if BUG
+ {
+ .name = "Chunk extension with whitespace",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5 ; ext=val\r\nhello\r\n0\r\n\r\n",
+ // RFC 9112 Section 7.1.1: Whitespace in chunk extensions should be minimal
+ },
+#endif
+#if BUG
+ {
+ .name = "Chunked with uppercase hex digits",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n",
+ // RFC 9112 Section 7.1: Senders SHOULD use lowercase for hex digits (though uppercase is valid)
+ },
+#endif
+#if QUESTIONABLE
+ {
+ .name = "Empty header field value",
+ .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Empty:\r\n\r\n",
+ // RFC 9110 Section 5.3: Empty field values are valid but some implementations may reject
+ },
+#endif
+#if MINOR_BUG
+ {
+ .name = "Header field name with uppercase and lowercase",
+ .upload = "GET / HTTP/1.1\r\nHoSt: example.com\r\n\r\n",
+ // RFC 9110 Section 5.1: Field names are case-insensitive, but conventional capitalization SHOULD be used
+ },
+#endif
+ {
+ .name = "Excessive whitespace in request line",
+ .upload = "GET / HTTP/1.1 \r\nHost: example.com\r\n\r\n",
+ // RFC 9112 Section 3: Trailing whitespace in request-line should not be present
+ },
+#if MINOR_BUG
+ {
+ .name = "Content-Length with leading zeros",
+ .upload =
+ "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0005\r\n\r\nhello",
+ // RFC 9110 Section 8.6: Leading zeros should not be sent
+ },
+#endif
+#if BUG
+ {
+ .name = "Cookie header with invalid encoding in value",
+ .upload =
+ "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: name=val ue\r\n\r\n",
+ // RFC 6265 Section 4.2: Cookie values should be properly encoded, spaces require encoding
+ },
+#endif
+ {
+ .name = NULL,
+ }
+};
+
+
+static struct Test *current;
+
+
+/**
+ * Our port.
+ */
+static uint16_t port;
+
+/**
+ * Set to true if a test failed.
+ */
+static volatile bool failed;
+
+
+/**
+ * Function to process data uploaded by a client.
+ *
+ * Given that we ONLY generate incorrect/malformed upload requests,
+ * this function should never be called.
+ *
+ * @param upload_cls the argument given together with the function
+ * pointer when the handler was registered with MHD
+ * @param request the request is being processed
+ * @param content_data_size the size of the @a content_data,
+ * zero when all data have been processed
+ * @param[in] content_data the uploaded content data,
+ * may be modified in the callback,
+ * valid only until return from the callback,
+ * NULL when all data have been processed
+ * @return action specifying how to proceed:
+ * #MHD_upload_action_continue() to continue upload (for incremental
+ * upload processing only),
+ * #MHD_upload_action_suspend() to stop reading the upload until
+ * the request is resumed,
+ * #MHD_upload_action_abort_request() to close the socket,
+ * or a response to discard the rest of the upload and transmit
+ * the response
+ * @ingroup action
+ */
+static const struct MHD_UploadAction *
+uc_fail (void *upload_cls,
+ struct MHD_Request *request,
+ size_t content_data_size,
+ void *content_data)
+{
+ fprintf (stderr,
+ "Test `%s' failed\n",
+ current->name);
+ failed = true;
+ return NULL;
+}
+
+
+/**
+ * A client has requested the given url using the given method
+ * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
+ * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc).
+ * If @a upload_size is not zero and response action is provided by this
+ * callback, then upload will be discarded and the stream (the connection for
+ * HTTP/1.1) will be closed after sending the response.
+ *
+ * This function is expected to be called, but when we try to
+ * process the upload it should always fail on the MHD side.
+ *
+ * @param cls argument given together with the function
+ * pointer when the handler was registered with MHD
+ * @param request the request object
+ * @param path the requested uri (without arguments after "?")
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ * #MHD_HTTP_METHOD_PUT, etc.)
+ * @param upload_size the size of the message upload content payload,
+ * #MHD_SIZE_UNKNOWN for chunked uploads (if the
+ * final chunk has not been processed yet)
+ * @return action how to proceed, NULL
+ * if the request must be aborted due to a serious
+ * error while handling the request (implies closure
+ * of underling data stream, for HTTP/1.1 it means
+ * socket closure).
+ */
+static const struct MHD_Action *
+server_upload_req_cb (void *cls,
+ struct MHD_Request *MHD_RESTRICT request,
+ const struct MHD_String *MHD_RESTRICT path,
+ enum MHD_HTTP_Method method,
+ uint_fast64_t upload_size)
+{
+ return MHD_action_process_upload (request,
+ 1024 * 1024,
+ &uc_fail,
+ NULL,
+ &uc_fail,
+ NULL);
+}
+
+
+/**
+ * A client has requested the given url using the given method
+ * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
+ * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc).
+ * If @a upload_size is not zero and response action is provided by this
+ * callback, then upload will be discarded and the stream (the connection for
+ * HTTP/1.1) will be closed after sending the response.
+ *
+ * Given that we ONLY generate incorrect/malformed requests,
+ * this function should never be called.
+ *
+ * @param cls argument given together with the function
+ * pointer when the handler was registered with MHD
+ * @param request the request object
+ * @param path the requested uri (without arguments after "?")
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ * #MHD_HTTP_METHOD_PUT, etc.)
+ * @param upload_size the size of the message upload content payload,
+ * #MHD_SIZE_UNKNOWN for chunked uploads (if the
+ * final chunk has not been processed yet)
+ * @return action how to proceed, NULL
+ * if the request must be aborted due to a serious
+ * error while handling the request (implies closure
+ * of underling data stream, for HTTP/1.1 it means
+ * socket closure).
+ */
+static const struct MHD_Action *
+server_req_cb (void *cls,
+ struct MHD_Request *MHD_RESTRICT request,
+ const struct MHD_String *MHD_RESTRICT path,
+ enum MHD_HTTP_Method method,
+ uint_fast64_t upload_size)
+{
+ fprintf (stderr,
+ "Test `%s' failed\n",
+ current->name);
+ failed = true;
+ return NULL;
+}
+
+
+/**
+ * Helper function to deal with partial writes.
+ * Fails hard (calls exit() on failures)!
+ *
+ * @param fd where to write to
+ * @param buf what to write
+ * @param buf_size number of bytes in @a buf
+ */
+static void
+write_all (int fd,
+ const void *buf,
+ size_t buf_size)
+{
+ const char *cbuf = buf;
+ size_t off;
+
+ off = 0;
+ while (off < buf_size)
+ {
+ ssize_t ret;
+
+ ret = write (fd,
+ &cbuf[off],
+ buf_size - off);
+ if (ret <= 0)
+ {
+ fprintf (stderr,
+ "Writing %u bytes to %d failed: %s\n",
+ (unsigned int) (buf_size - off),
+ fd,
+ strerror (errno));
+ exit (1);
+ }
+ off += ret;
+ }
+}
+
+
+static int
+run_test ()
+{
+ int s;
+ struct sockaddr_in sa = {
+ .sin_family = AF_INET,
+ .sin_port = htons (port),
+ };
+ char dummy;
+
+ s = socket (AF_INET, SOCK_STREAM, 0);
+ if (-1 == s)
+ {
+ fprintf (stderr,
+ "socket() failed: %s\n",
+ strerror (errno));
+ return 1;
+ }
+ inet_pton (AF_INET,
+ "127.0.0.1",
+ &sa.sin_addr);
+ if (0 != connect (s,
+ (struct sockaddr *) &sa,
+ sizeof (sa)))
+ {
+ fprintf (stderr,
+ "bind() failed: %s\n",
+ strerror (errno));
+ close (s);
+ return 1;
+ }
+ write_all (s,
+ current->upload,
+ strlen (current->upload));
+ shutdown (s,
+ SHUT_WR);
+ if (sizeof (dummy) !=
+ read (s,
+ &dummy,
+ sizeof (dummy)))
+ {
+#if LOG
+ fprintf (stderr,
+ "Server closed connection\n");
+#endif
+ }
+ close (s);
+ if (failed)
+ return 1;
+ return 0;
+}
+
+
+static int
+run_tests (struct Test *tests)
+{
+ for (unsigned int i = 0;
+ NULL != tests[i].name;
+ i++)
+ {
+ current = &tests[i];
+#if LOG || 1
+ fprintf (stderr,
+ "Running test `%s'\n",
+ current->name);
+#endif
+ if (0 != run_test ())
+ return 1;
+ }
+ return 0;
+}
+
+
+static int
+run (MHD_RequestCallback cb,
+ struct Test *tests,
+ enum MHD_ProtocolStrictLevel psl)
+{
+ struct MHD_Daemon *d;
+
+ d = MHD_daemon_create (cb,
+ NULL);
+ if (MHD_SC_OK !=
+ MHD_DAEMON_SET_OPTIONS (
+ d,
+ MHD_D_OPTION_WM_WORKER_THREADS (2),
+#if ! LOG
+ MHD_D_OPTION_LOG_CALLBACK (NULL,
+ NULL),
+#endif
+ MHD_D_OPTION_PROTOCOL_STRICT_LEVEL (psl,
+ MHD_USL_PRECISE),
+ MHD_D_OPTION_DEFAULT_TIMEOUT (1),
+ MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO,
+ 0)))
+ {
+ fprintf (stderr,
+ "Failed to configure daemon!");
+ return 1;
+ }
+
+ {
+ enum MHD_StatusCode sc;
+
+ sc = MHD_daemon_start (d);
+ if (MHD_SC_OK != sc)
+ {
+#ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED
+ fprintf (stderr,
+ "Failed to start server: %s\n",
+ MHD_status_code_to_string_lazy (sc));
+#else
+ fprintf (stderr,
+ "Failed to start server: %u\n",
+ (unsigned int) sc);
+#endif
+ MHD_daemon_destroy (d);
+ return 1;
+ }
+ }
+
+ {
+ union MHD_DaemonInfoFixedData info;
+ enum MHD_StatusCode sc;
+
+ sc = MHD_daemon_get_info_fixed (
+ d,
+ MHD_DAEMON_INFO_FIXED_BIND_PORT,
+ &info);
+ if (MHD_SC_OK != sc)
+ {
+ fprintf (stderr,
+ "Failed to determine our port: %u\n",
+ (unsigned int) sc);
+ MHD_daemon_destroy (d);
+ return 1;
+ }
+ port = info.v_bind_port_uint16;
+ }
+
+ {
+ int result;
+
+ result = run_tests (tests);
+ MHD_daemon_destroy (d);
+ return result;
+ }
+}
+
+
+int
+main ()
+{
+ if (0 !=
+ run (&server_req_cb,
+ tests_must,
+ MHD_PSL_STRICT))
+ return 1;
+ if (0 !=
+ run (&server_upload_req_cb,
+ tests_must_upload,
+ MHD_PSL_STRICT))
+ return 1;
+ if (0 !=
+ run (&server_req_cb,
+ tests_should,
+ MHD_PSL_VERY_STRICT))
+ return 1;
+ return 0;
+}
diff --git a/src/tests/raw/test_raw.c b/src/tests/raw/test_raw.c
@@ -524,7 +524,16 @@ static struct Test tests[] = {
.expect_path = "/",
.expect_parser = "H-Host:example.com\nQ-a:1",
},
-
+#if BUG
+ {
+ .name = "Absolute form",
+ // RFC 9112 Section 3.2.2
+ .upload = "GET http://example.com/ HTTP/1.1\r\nHost: example.bad\r\n\r\n",
+ .expect_method = MHD_HTTP_METHOD_GET,
+ .expect_path = "/",
+ .expect_parser = "H-Host:example.com",
+ },
+#endif
{
.name = NULL,
}