libmicrohttpd2

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

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:
Mconfigure.ac | 1+
Msrc/tests/Makefile.am | 2+-
Asrc/tests/raw/Makefile.am | 35+++++++++++++++++++++++++++++++++++
Asrc/tests/raw/test_raw.c | 562+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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; + } +}