commit 976c3a99f2a1c89fe5ad944e2dea2a5921b7f53d
parent 911c850839bdd35da4e0481594cae6a1dd913bca
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 6 Dec 2025 23:57:55 +0100
skeleton for header/cookie parser test
Diffstat:
4 files changed, 599 insertions(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
@@ -8765,6 +8765,7 @@ src/tests/Makefile
src/tests/unit/Makefile
src/tests/basic/Makefile
src/tests/upgrade/Makefile
+src/tests/raw/Makefile
src/tests/client_server/Makefile
src/examples2/Makefile
])
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
@@ -1,6 +1,6 @@
# This Makefile.am is in the public domain
-SUBDIRS = unit basic
+SUBDIRS = unit basic raw
if HAVE_POSIX_THREADS
if MHD_SUPPORT_UPGRADE
diff --git a/src/tests/raw/Makefile.am b/src/tests/raw/Makefile.am
@@ -0,0 +1,35 @@
+# This Makefile.am is in the public domain
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/incl_priv \
+ -I$(top_srcdir)/src/include \
+ -I$(top_srcdir)/src/mhd2 \
+ -DMHD_CPU_COUNT=$(CPU_COUNT) \
+ $(CPPFLAGS_ac) $(LIBCURL_CPPFLAGS)
+
+AM_CFLAGS = $(CFLAGS_ac) $(PTHREAD_CFLAGS)
+
+AM_LDFLAGS = $(LDFLAGS_ac)
+
+AM_TESTS_ENVIRONMENT = $(TESTS_ENVIRONMENT_ac)
+
+if USE_COVERAGE
+ AM_CFLAGS += -fprofile-arcs -ftest-coverage
+endif
+
+LIBADD = \
+ $(top_builddir)/src/mhd2/libmicrohttpd2.la $(PTHREAD_LIBS)
+
+$(top_builddir)/src/mhd2/libmicrohttpd2.la: $(top_builddir)/src/mhd2/Makefile
+ @echo ' cd $(top_builddir)/src/mhd2 && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd2.la'; \
+ $(am__cd) $(top_builddir)/src/mhd2 && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd2.la
+
+check_PROGRAMS = \
+ test_raw
+
+TESTS = $(check_PROGRAMS)
+
+test_raw_SOURCES = \
+ test_raw.c
+test_raw_LDADD = \
+ -lmicrohttpd2
diff --git a/src/tests/raw/test_raw.c b/src/tests/raw/test_raw.c
@@ -0,0 +1,562 @@
+/* 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_raw.c
+ * @brief tests streams against server
+ * @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"
+
+
+/**
+ * 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;
+
+ /**
+ * Expected HTTP method.
+ */
+ enum MHD_HTTP_Method expect_method;
+
+ /**
+ * Expected path.
+ */
+ const char *expect_path;
+
+ /**
+ * Expected upload size.
+ */
+ uint_fast64_t expect_upload_size;
+
+ /**
+ * Special string indicating what the MHD parser should give us.
+ * Can be used to encode expected HTTP headers using
+ * "H-$KEY:$VALUE" or HTTP cookies using "C-$KEY:$VALUE".
+ * Multiple entries are separated via "\n".
+ */
+ const char *expect_parser;
+};
+
+
+static struct Test tests[] = {
+ {
+ .name = "Basic GET",
+ .upload = "GET / HTTP/1.0\r\n\r\n",
+ .expect_method = MHD_HTTP_METHOD_GET,
+ .expect_path = "/",
+ },
+ {
+ .name = "Basic HTTP/1.1 GET",
+ .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 = NULL,
+ }
+};
+
+
+static struct Test *current;
+
+
+/**
+ * Our port.
+ */
+static uint16_t port;
+
+
+static bool
+check_headers (struct MHD_Request *MHD_RESTRICT request,
+ const char *spec)
+{
+ bool ok = true;
+ char *hdr = strdup (spec);
+ char *tok;
+ char key[strlen (spec)];
+ char value[strlen (spec)];
+
+ for (tok = strtok (hdr,
+ "\n");
+ NULL != tok;
+ tok = strtok (NULL,
+ "\n"))
+ {
+ if (2 ==
+ sscanf (tok,
+ "H-%[^:]:%s",
+ key,
+ value))
+ {
+ struct MHD_StringNullable have;
+
+ if (! MHD_request_get_value (request,
+ MHD_VK_HEADER,
+ key,
+ &have))
+ {
+ fprintf (stderr,
+ "Expected header `%s' missing\n",
+ key);
+ ok = false;
+ }
+ else if ( (NULL == have.cstr) ||
+ (0 != strcmp (have.cstr,
+ value)) )
+ {
+ fprintf (stderr,
+ "Wrong header value `%s' under key `%s'\n",
+ have.cstr,
+ key);
+ ok = false;
+ }
+ }
+ else if (2 ==
+ sscanf (tok,
+ "C-%[^:]:%s",
+ key,
+ 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,
+ value)) )
+ {
+ fprintf (stderr,
+ "Wrong cookie value `%s' under key `%s'\n",
+ have.cstr,
+ key);
+ ok = false;
+ }
+ }
+ else if (2 ==
+ sscanf (tok,
+ "Q-%[^:]:%s",
+ key,
+ 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,
+ value)) )
+ {
+ fprintf (stderr,
+ "Wrong URI query parameter value `%s' under key `%s'\n",
+ have.cstr,
+ key);
+ ok = false;
+ }
+ }
+ else if (1 ==
+ sscanf (tok,
+ "H-%[^:]:%s",
+ key,
+ value))
+ {
+ struct MHD_StringNullable have;
+
+ if (! MHD_request_get_value (request,
+ MHD_VK_HEADER,
+ key,
+ &have))
+ {
+ fprintf (stderr,
+ "Expected header `%s' missing\n",
+ key);
+ ok = false;
+ }
+ else if (NULL != have.cstr)
+ {
+ fprintf (stderr,
+ "Unexpected non-NULL header value `%s' under key `%s'\n",
+ have.cstr,
+ key);
+ ok = false;
+ }
+ }
+ else if (1 ==
+ sscanf (tok,
+ "C-%[^:]:%s",
+ key,
+ 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)
+ {
+ fprintf (stderr,
+ "Unexpected non-NULL cookie value `%s' under key `%s'\n",
+ have.cstr,
+ key);
+ ok = false;
+ }
+ }
+ else if (1 ==
+ sscanf (tok,
+ "Q-%[^:]:%s",
+ key,
+ 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)
+ {
+ fprintf (stderr,
+ "Unexpected non-NULL URI query parameter value `%s' under key `%s'\n",
+ have.cstr,
+ key);
+ ok = false;
+ }
+ }
+ else
+ {
+ fprintf (stderr,
+ "Invalid token `%s' in test specification\n",
+ tok);
+ ok = false;
+ }
+ }
+ free (hdr);
+ return ok;
+}
+
+
+/**
+ * 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.
+ *
+ * @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)
+{
+ bool failed = false;
+
+ if (current->expect_method != method)
+ {
+ fprintf (stderr,
+ "Wrong HTTP method\n");
+ failed = true;
+ }
+ if (0 != strcmp (current->expect_path,
+ path->cstr))
+ {
+ fprintf (stderr,
+ "Wrong HTTP path %s\n",
+ path->cstr);
+ failed = true;
+ }
+ if (current->expect_upload_size !=
+ upload_size)
+ {
+ fprintf (stderr,
+ "Wrong HTTP path %s\n",
+ path->cstr);
+ failed = true;
+ }
+ if ( (NULL != current->expect_parser) &&
+ (! check_headers (request,
+ current->expect_parser)) )
+ failed = true;
+ if (failed)
+ return NULL;
+ return MHD_action_from_response (
+ request,
+ MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT));
+}
+
+
+/**
+ * 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)))
+ {
+ fprintf (stderr,
+ "Server closed connection due to error!\n");
+ close (s);
+ return 1;
+ }
+ close (s);
+ return 0;
+}
+
+
+static int
+run_tests (void)
+{
+ for (unsigned int i = 0;
+ NULL != tests[i].name;
+ i++)
+ {
+ current = &tests[i];
+ if (0 != run_test ())
+ {
+ fprintf (stderr,
+ "Test %s failed\n",
+ current->name);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+int
+main ()
+{
+ struct MHD_Daemon *d;
+
+ d = MHD_daemon_create (&server_req_cb,
+ NULL);
+ if (MHD_SC_OK !=
+ MHD_DAEMON_SET_OPTIONS (
+ d,
+ MHD_D_OPTION_WM_WORKER_THREADS (2),
+ 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 ();
+ MHD_daemon_destroy (d);
+ return result;
+ }
+}