An empty ":method" or ":scheme" is never valid, and an empty ":path" or
":authority" is not valid for the "http" and "https" schemes (RFC 9113
Section 8.3.1, RFC 9110 Section 4.2). An undefined pseudo-header makes
the request malformed (RFC 9113 Section 8.3). The library checked
neither and served the requests.
An empty ":method" was also undefined behaviour: its length is a
variable-length array bound in mhd_parse_http_method(), and UBSan
reports "variable length array bound evaluates to non-positive value 0".
The requests are now rejected, as an empty "content-length" is. The
library does not read the value of ":scheme", so the check applies to
every scheme.
Added test_h2_raw: the request must not reach the application, and any
RST_STREAM or GOAWAY must carry PROTOCOL_ERROR.
---
src/mhd2/h2/h2_conn_streams.h | 2 +
src/mhd2/h2/h2_req_fields.c | 6 +
src/mhd2/stream_process_request.c | 2 +-
src/mhd2/stream_process_request.h | 2 +-
src/tests/raw/Makefile.am | 5 +
src/tests/raw/test_h2_raw.c | 788 ++++++++++++++++++++++++++++++
6 files changed, 803 insertions(+), 2 deletions(-)
create mode 100644 src/tests/raw/test_h2_raw.c
diff --git a/src/mhd2/h2/h2_conn_streams.h b/src/mhd2/h2/h2_conn_streams.h
index 943d86b..8384000 100644
--- a/src/mhd2/h2/h2_conn_streams.h
+++ b/src/mhd2/h2/h2_conn_streams.h
@@ -79,6 +79,8 @@ enum mhd_H2RequestProblemType
,
mhd_H2_REQ_PRBLM_PSEUDOHDR_EXTRA
,
+ mhd_H2_REQ_PRBLM_PSEUDOHDR_VALUE_WRONG
+ ,
mhd_H2_REQ_PRBLM_FLOW_CONTROL
,
diff --git a/src/mhd2/h2/h2_req_fields.c b/src/mhd2/h2/h2_req_fields.c
index 38ad0d9..8107a31 100644
--- a/src/mhd2/h2/h2_req_fields.c
+++ b/src/mhd2/h2/h2_req_fields.c
@@ -222,6 +222,9 @@ req_pseudoheaders_preprocess (struct mhd_H2Stream
*restrict s,
break;
else if (mhd_H2_RIK_PSEUDOHEADER != item->kind)
break;
+ else if (0u == item->val_len)
+ return mhd_h2_stream_req_problem (s,
+
mhd_H2_REQ_PRBLM_PSEUDOHDR_VALUE_WRONG);
else if ((item->name_len == ph_method.len)
&& (0 == memcmp (buff + item->offset,
ph_method.cstr,
@@ -268,6 +271,9 @@ req_pseudoheaders_preprocess (struct mhd_H2Stream
*restrict s,
mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP);
have_scheme = true;
}
+ else
+ return mhd_h2_stream_req_problem (s,
+ mhd_H2_REQ_PRBLM_UNKNOWN_PSEUDOHDR);
mhd_assert (':' == buff[item->offset]);
++(*pos);
}
diff --git a/src/mhd2/stream_process_request.c
b/src/mhd2/stream_process_request.c
index 352c521..5d57ff3 100644
--- a/src/mhd2/stream_process_request.c
+++ b/src/mhd2/stream_process_request.c
@@ -481,7 +481,7 @@
/**
* Parse HTTP method string.
- * @param len the length of the @a mtd string
+ * @param len the length of the @a mtd string, must not be zero
* @param mtd the method string, does not need to be zero-terminated
* @return enum mhd_HTTP_Method value
*/
diff --git a/src/mhd2/stream_process_request.h
b/src/mhd2/stream_process_request.h
index 22b5eba..acbbc22 100644
--- a/src/mhd2/stream_process_request.h
+++ b/src/mhd2/stream_process_request.h
@@ -77,7 +77,7 @@ typedef bool
/**
* Parse HTTP method string.
- * @param len the length of the @a mtd string
+ * @param len the length of the @a mtd string, must not be zero
* @param mtd the method string, does not need to be zero-terminated
* @return enum mhd_HTTP_Method value
*/
diff --git a/src/tests/raw/Makefile.am b/src/tests/raw/Makefile.am
index 2d62fca..9f7bdae 100644
--- a/src/tests/raw/Makefile.am
+++ b/src/tests/raw/Makefile.am
@@ -30,4 +30,9 @@ check_PROGRAMS = \
test_oom \
test_raw
+if MHD_SUPPORT_HTTP2
+check_PROGRAMS += \
+ test_h2_raw
+endif
+
TESTS = $(check_PROGRAMS)
diff --git a/src/tests/raw/test_h2_raw.c b/src/tests/raw/test_h2_raw.c
new file mode 100644
index 0000000..a5f1e49
--- /dev/null
+++ b/src/tests/raw/test_h2_raw.c
@@ -0,0 +1,788 @@
+/* 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
+ Copyright (C) 2026 Sami Jawhar
+
+ 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_h2_raw.c
+ * @brief tests HTTP/2 requests against server
+ * @author Sami Jawhar
+ */
+#include <stdio.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+#include "microhttpd2.h"
+
+
+/**
+ * The start of the connection preface sent by the client, RFC 9113,
+ * Section 3.4. It must be followed by a SETTINGS frame.
+ */
+#define H2_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
+
+/**
+ * The size of the frame header, RFC 9113, Section 4.1.
+ */
+#define H2_FRAME_HDR_SIZE 9
+
+/**
+ * The type of the HEADERS frame, RFC 9113, Section 6.2.
+ */
+#define H2_FRAME_HEADERS 0x1
+
+/**
+ * The type of the RST_STREAM frame, RFC 9113, Section 6.4.
+ */
+#define H2_FRAME_RST_STREAM 0x3
+
+/**
+ * The type of the SETTINGS frame, RFC 9113, Section 6.5.
+ */
+#define H2_FRAME_SETTINGS 0x4
+
+/**
+ * The type of the GOAWAY frame, RFC 9113, Section 6.8.
+ */
+#define H2_FRAME_GOAWAY 0x7
+
+/**
+ * The PROTOCOL_ERROR code, RFC 9113, Section 7.
+ */
+#define H2_ERR_PROTOCOL_ERROR 0x1
+
+/**
+ * The END_STREAM flag of the HEADERS frame, RFC 9113, Section 6.2.
+ */
+#define H2_FLAG_END_STREAM 0x1
+
+/**
+ * The END_HEADERS flag of the HEADERS frame, RFC 9113, Section 6.2.
+ */
+#define H2_FLAG_END_HEADERS 0x4
+
+/**
+ * Defines a test.
+ */
+struct Test
+{
+ /**
+ * Human-readable name of the test. NULL to end test array.
+ */
+ const char *name;
+
+ /**
+ * The value of the ":method" pseudo-header.
+ */
+ const char *method;
+
+ /**
+ * The value of the ":scheme" pseudo-header.
+ */
+ const char *scheme;
+
+ /**
+ * The value of the ":path" pseudo-header.
+ */
+ const char *path;
+
+ /**
+ * The value of the ":authority" pseudo-header.
+ */
+ const char *authority;
+
+ /**
+ * The name of an additional pseudo-header to send after the four
+ * above, NULL to send none.
+ */
+ const char *extra_name;
+
+ /**
+ * True if the server must give the request to the application,
+ * false if the server must reject the request.
+ */
+ bool expect_processed;
+};
+
+
+static const struct Test tests[] = {
+ {
+ .name = "Basic HTTP/2 GET",
+ .method = "GET",
+ .scheme = "http",
+ .path = "/",
+ .authority = "localhost",
+ .expect_processed = true,
+ },
+ {
+ /* RFC 9113, Section 8.3.1: exactly one valid value is required,
+ and an empty value is not a valid method, which is a token
+ (RFC 9110, Section 9.1) */
+ .name = "HTTP/2 GET with empty \":method\"",
+ .method = "",
+ .scheme = "http",
+ .path = "/",
+ .authority = "localhost",
+ },
+ {
+ /* RFC 3986, Section 3.1: a scheme starts with a letter */
+ .name = "HTTP/2 GET with empty \":scheme\"",
+ .method = "GET",
+ .scheme = "",
+ .path = "/",
+ .authority = "localhost",
+ },
+ {
+ /* RFC 9113, Section 8.3.1: ":path" must not be empty for
+ "http" and "https" URIs */
+ .name = "HTTP/2 GET with empty \":path\"",
+ .method = "GET",
+ .scheme = "http",
+ .path = "",
+ .authority = "localhost",
+ },
+ {
+ /* RFC 9110, Section 4.2.1: the host of an "http" URI
+ must not be empty */
+ .name = "HTTP/2 GET with empty \":authority\"",
+ .method = "GET",
+ .scheme = "http",
+ .path = "/",
+ .authority = "",
+ },
+ {
+ /* RFC 9113, Section 8.3: an undefined pseudo-header makes
+ the request malformed */
+ .name = "HTTP/2 GET with undefined pseudo-header",
+ .method = "GET",
+ .scheme = "http",
+ .path = "/",
+ .authority = "localhost",
+ .extra_name = ":forbidden",
+ },
+ {
+ .name = NULL,
+ }
+};
+
+
+/**
+ * True if the request has been given to the application.
+ * Set by a daemon thread, read by the main thread only after the reply
+ * has been received.
+ */
+static volatile bool processed;
+
+/**
+ * Our port.
+ */
+static uint16_t port;
+
+
+/**
+ * Records that the request reached the application and replies.
+ *
+ * @param cls closure
+ * @param request the request object
+ * @param path the requested uri
+ * @param method the HTTP method used
+ * @param upload_size the size of the upload content payload
+ * @return action how to proceed
+ */
+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)
+{
+ (void)cls; /* Unused */
+ (void)path; /* Unused */
+ (void)method; /* Unused */
+ (void)upload_size; /* Unused */
+
+ processed = true;
+ 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;
+ }
+}
+
+
+/**
+ * Read the 24-bit value, RFC 9113, Section 4.1.
+ *
+ * @param buf the buffer to read from
+ * @return the value
+ */
+static size_t
+get_u24 (const unsigned char *buf)
+{
+ return (((size_t)buf[0]) << 16) | (((size_t)buf[1]) << 8)
+ | ((size_t)buf[2]);
+}
+
+
+/**
+ * Read the 32-bit value, RFC 9113, Section 2.2.
+ *
+ * @param buf the buffer to read from
+ * @return the value
+ */
+static uint32_t
+get_u32 (const unsigned char *buf)
+{
+ return (((uint32_t)buf[0]) << 24) | (((uint32_t)buf[1]) << 16)
+ | (((uint32_t)buf[2]) << 8) | ((uint32_t)buf[3]);
+}
+
+
+/**
+ * Write the 32-bit value, RFC 9113, Section 2.2.
+ *
+ * @param[out] buf the buffer to write to
+ * @param val the value to write
+ */
+static void
+put_u32 (unsigned char *buf,
+ uint32_t val)
+{
+ buf[0] = (unsigned char)(val >> 24);
+ buf[1] = (unsigned char)(val >> 16);
+ buf[2] = (unsigned char)(val >> 8);
+ buf[3] = (unsigned char)val;
+}
+
+
+/**
+ * Add the frame header, RFC 9113, Section 4.1.
+ *
+ * @param[out] buf the buffer to add the header to
+ * @param type the type of the frame
+ * @param flags the flags of the frame
+ * @param stream_id the stream identifier
+ * @param size the size of the frame payload
+ */
+static void
+put_frame_header (unsigned char buf[H2_FRAME_HDR_SIZE],
+ unsigned char type,
+ unsigned char flags,
+ uint32_t stream_id,
+ size_t size)
+{
+ buf[0] = (unsigned char)(size >> 16);
+ buf[1] = (unsigned char)(size >> 8);
+ buf[2] = (unsigned char)size;
+ buf[3] = type;
+ buf[4] = flags;
+ put_u32 (buf + 5,
+ stream_id);
+}
+
+
+/**
+ * Add a literal field with incremental indexing, RFC 7541, Section 6.2.1.
+ * The strings are not Huffman-coded.
+ * Fails hard (calls exit() on failures)!
+ * The one-byte forms are used, so @a index must be less than 63 and
+ * the length of @a name and of @a value must be less than 127
+ * (RFC 7541, Sections 5.1 and 5.2).
+ *
+ * @param[out] buf the buffer to add the field to
+ * @param index the index of the name in the static table, zero to add
+ * @a name as a new name
+ * @param name the name of the field, used only if @a index is zero
+ * @param value the value of the field
+ * @return the number of bytes added
+ */
+static size_t
+put_field (unsigned char *buf,
+ unsigned char index,
+ const char *name,
+ const char *value)
+{
+ const size_t name_len = (0 == index) ? strlen (name) : 0;
+ const size_t value_len = strlen (value);
+ size_t pos = 1;
+
+ if ((63 <= index) || (127 <= name_len) || (127 <= value_len))
+ {
+ fprintf (stderr,
+ "The field with index %u cannot be encoded\n",
+ (unsigned int)index);
+ exit (1);
+ }
+ buf[0] = (unsigned char)(0x40u | index);
+ if (0 == index)
+ {
+ buf[pos++] = (unsigned char)name_len;
+ memcpy (buf + pos,
+ name,
+ name_len);
+ pos += name_len;
+ }
+ buf[pos++] = (unsigned char)value_len;
+ memcpy (buf + pos,
+ value,
+ value_len);
+ return pos + value_len;
+}
+
+
+/**
+ * Send the connection preface, the SETTINGS frame required to complete
+ * it, and the request of the test as a single HEADERS frame on stream 1.
+ *
+ * @param s the socket to send to
+ * @param t the test to send the request of
+ */
+static void
+send_request (int s,
+ const struct Test *t)
+{
+ /* Five fields, each within the limits of put_field(), plus the frame
+ header */
+ unsigned char buf[5 * 256 + H2_FRAME_HDR_SIZE];
+ size_t size;
+
+ write_all (s,
+ H2_PREFACE,
+ strlen (H2_PREFACE));
+ put_frame_header (buf,
+ H2_FRAME_SETTINGS,
+ 0,
+ 0,
+ 0);
+ write_all (s,
+ buf,
+ H2_FRAME_HDR_SIZE);
+ size = put_field (buf + H2_FRAME_HDR_SIZE,
+ 2 /* ":method" */,
+ NULL,
+ t->method);
+ size += put_field (buf + H2_FRAME_HDR_SIZE + size,
+ 6 /* ":scheme" */,
+ NULL,
+ t->scheme);
+ size += put_field (buf + H2_FRAME_HDR_SIZE + size,
+ 4 /* ":path" */,
+ NULL,
+ t->path);
+ size += put_field (buf + H2_FRAME_HDR_SIZE + size,
+ 1 /* ":authority" */,
+ NULL,
+ t->authority);
+ if (NULL != t->extra_name)
+ size += put_field (buf + H2_FRAME_HDR_SIZE + size,
+ 0,
+ t->extra_name,
+ "value");
+ put_frame_header (buf,
+ H2_FRAME_HEADERS,
+ H2_FLAG_END_HEADERS | H2_FLAG_END_STREAM,
+ 1,
+ size);
+ write_all (s,
+ buf,
+ H2_FRAME_HDR_SIZE + size);
+}
+
+
+/**
+ * A connection to our server and the data read from it.
+ */
+struct Conn
+{
+ /**
+ * The socket.
+ */
+ int s;
+
+ /**
+ * The data read: one frame of the largest size the client allows,
+ * RFC 9113, Section 4.2.
+ */
+ unsigned char buf[H2_FRAME_HDR_SIZE + 16384];
+
+ /**
+ * The number of bytes in @a buf.
+ */
+ size_t have;
+
+ /**
+ * The size of the frame at the start of @a buf that has been returned
+ * by read_frame(), zero if none.
+ */
+ size_t pos;
+};
+
+
+/**
+ * Open a connection to our server and set the receive timeout.
+ *
+ * @param[out] c the connection
+ * @return 0 if succeed, 1 if the connection cannot be made
+ */
+static int
+connect_to_server (struct Conn *c)
+{
+ int s;
+ struct sockaddr_in sa = {
+ .sin_family = AF_INET,
+ .sin_port = htons (port),
+ };
+ /* Must expire before the timeout of the daemon, so that a server that
+ does not answer fails the test instead of closing the connection */
+ struct timeval tv = {
+ .tv_sec = 5,
+ };
+
+ 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,
+ "connect() failed: %s\n",
+ strerror (errno));
+ close (s);
+ return 1;
+ }
+ if (0 != setsockopt (s,
+ SOL_SOCKET,
+ SO_RCVTIMEO,
+ &tv,
+ sizeof (tv)))
+ {
+ fprintf (stderr,
+ "setsockopt() failed: %s\n",
+ strerror (errno));
+ close (s);
+ return 1;
+ }
+ c->s = s;
+ c->have = 0;
+ c->pos = 0;
+ return 0;
+}
+
+
+/**
+ * Read the next complete frame. Its payload follows the frame header
+ * and stays in the buffer until the next call.
+ *
+ * @param c the connection to read from
+ * @return the frame header, NULL if the server closed the connection
+ * (errno is zero) or the read failed
+ */
+static const unsigned char *
+read_frame (struct Conn *c)
+{
+ memmove (c->buf,
+ c->buf + c->pos,
+ c->have - c->pos);
+ c->have -= c->pos;
+ c->pos = 0;
+ while ((H2_FRAME_HDR_SIZE > c->have)
+ || (H2_FRAME_HDR_SIZE + get_u24 (c->buf) > c->have))
+ {
+ ssize_t ret;
+
+ if (sizeof (c->buf) == c->have)
+ {
+ errno = EMSGSIZE;
+ return NULL;
+ }
+ ret = read (c->s,
+ c->buf + c->have,
+ sizeof (c->buf) - c->have);
+ if (0 == ret)
+ errno = 0;
+ if (0 >= ret)
+ {
+ if (ECONNRESET == errno)
+ errno = 0;
+ return NULL;
+ }
+ c->have += (size_t)ret;
+ }
+ c->pos = H2_FRAME_HDR_SIZE + get_u24 (c->buf);
+ return c->buf;
+}
+
+
+/**
+ * Read the reply until the server answers the request on stream 1,
+ * resets stream 1, sends GOAWAY or closes the connection.
+ *
+ * @param c the connection to read from
+ * @param[out] answer set to the type of the frame that decided the request,
+ * zero if the server closed the connection without one
+ * @param[out] err_code set to the error code of RST_STREAM or GOAWAY
+ * @return 0 if succeed, 1 if the server did not answer in time or the
+ * read failed
+ */
+static int
+read_reply (struct Conn *c,
+ unsigned char *answer,
+ uint32_t *err_code)
+{
+ const unsigned char *f;
+
+ *answer = 0;
+ *err_code = 0;
+ while (NULL != (f = read_frame (c)))
+ {
+ const size_t size = get_u24 (f);
+ const uint32_t stream_id = get_u32 (f + 5) & 0x7fffffffu;
+
+ if ((H2_FRAME_HEADERS == f[3]) && (1 == stream_id))
+ {
+ *answer = f[3];
+ return 0;
+ }
+ if ((H2_FRAME_RST_STREAM == f[3]) && (1 == stream_id)
+ && (4u <= size))
+ {
+ *answer = f[3];
+ *err_code = get_u32 (f + H2_FRAME_HDR_SIZE);
+ return 0;
+ }
+ if ((H2_FRAME_GOAWAY == f[3]) && (8u <= size))
+ {
+ *answer = f[3];
+ *err_code = get_u32 (f + H2_FRAME_HDR_SIZE + 4);
+ return 0;
+ }
+ }
+ if (0 == errno)
+ return 0; /* The server closed the connection without a frame */
+ fprintf (stderr,
+ "The server sent no answer: %s\n",
+ strerror (errno));
+ return 1;
+}
+
+
+static int
+run_test (const struct Test *t)
+{
+ struct Conn c;
+ unsigned char answer;
+ uint32_t err_code;
+ bool was_processed;
+ int ret;
+
+ if (0 != connect_to_server (&c))
+ return 1;
+ processed = false;
+ send_request (c.s,
+ t);
+ ret = read_reply (&c,
+ &answer,
+ &err_code);
+ close (c.s);
+ if (0 != ret)
+ return ret;
+ was_processed = processed;
+ if (t->expect_processed != was_processed)
+ {
+ fprintf (stderr,
+ "The request has%s been given to the application\n",
+ was_processed ? "" : " not");
+ return 1;
+ }
+ if (!t->expect_processed)
+ {
+ /* A rejected request may get an error reply, RST_STREAM, GOAWAY or
+ a closed connection (RFC 9113, Sections 5.4.1 and 8.1.1): check
+ only the error code, if any. */
+ if ((0 != answer)
+ && (H2_FRAME_HEADERS != answer)
+ && (H2_ERR_PROTOCOL_ERROR != err_code))
+ {
+ fprintf (stderr,
+ "The server has used the error code %u, "
+ "expected PROTOCOL_ERROR\n",
+ (unsigned int)err_code);
+ return 1;
+ }
+ }
+ else if (H2_FRAME_HEADERS != answer)
+ {
+ fprintf (stderr,
+ "The server has not replied on the stream\n");
+ return 1;
+ }
+ return 0;
+}
+
+
+static int
+run_tests (void)
+{
+ for (unsigned int i = 0;
+ NULL != tests[i].name;
+ i++)
+ {
+ if (0 != run_test (&tests[i]))
+ {
+ fprintf (stderr,
+ "Test `%s' failed\n",
+ tests[i].name);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+int
+main (void)
+{
+ 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_MILSEC (30000),
+ MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO,
+ 0)))
+ {
+ fprintf (stderr,
+ "Failed to configure daemon!\n");
+ MHD_daemon_destroy (d);
+ return 1;
+ }
+
+ {
+ enum MHD_StatusCode sc;
+
+ sc = MHD_daemon_start (d);
+ if (MHD_SC_OK != sc)
+ {
+ fprintf (stderr,
+ "Failed to start server: %u\n",
+ (unsigned int)sc);
+ 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;
+ }
+}
--
2.43.0