libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit ec6e6d288876606af964747f97379aa9371c4206
parent db3d2d32f56fadc0f668d3113a9b3b78a9ab0c96
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
Date:   Sat, 28 Feb 2026 23:38:52 +0100

Added MHD_OPTION_ALLOW_BIN_ZERO_IN_URI_PATH and MHD_get_connection_URI_path_n()

Diffstat:
MChangeLog | 4++++
Msrc/include/microhttpd.h | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/microhttpd/connection.c | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/microhttpd/daemon.c | 7+++++++
Msrc/microhttpd/internal.h | 13+++++++++++++
5 files changed, 140 insertions(+), 13 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,7 @@ +Sat Feb 28 23:38:39 CET 2026 + Added MHD_OPTION_ALLOW_BIN_ZERO_IN_URI_PATH daemon option. + Added new function MHD_get_connection_URI_path_n(). -EG + Mon Jul 14 05:03:07 PM CEST 2025 Fix double-close bugs on bind() errors reported by MC on the list. Removed MHD2 draft code, now in libmicrohttpd2.git. diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -101,7 +101,7 @@ MHD_C_DECLRATIONS_START_HERE_ * they are parsed as decimal numbers. * Example: 0x01093001 = 1.9.30-1. */ -#define MHD_VERSION 0x01000200 +#define MHD_VERSION 0x01000201 /* If generic headers don't work on your platform, include headers which define 'va_list', 'size_t', 'ssize_t', 'intptr_t', 'off_t', @@ -2198,6 +2198,26 @@ enum MHD_OPTION * @note Available since #MHD_VERSION 0x00097709 */ MHD_OPTION_DIGEST_AUTH_DEFAULT_MAX_NC = 42 + , + /** + * Default maximum nc (nonce count) value used for Digest Auth. + * This option must be followed by an 'int' argument. + * If followed by '0' (default) then: + * + requests are rejected if request URI has binary zero (the result + * of %00 decoding) in the path part of the URI. + * If followed by '1' then: + * + binary zero is allowed in request URI path; + * + #MHD_AccessHandlerCallback called with NULL in @a url parameter when + * request URI path has binary zero, the full @a url is available only + * via #MHD_get_connection_URI_path_n() + * If followed by '2' (unsafe!) then: + * + binary zero is allowed in request URI path; + * + #MHD_AccessHandlerCallback called with truncated (unsafe!) @a url + * parameter when request URI path has binary zero. + * @see #MHD_get_connection_URI_path_n() + * @note Available since #MHD_VERSION 0x01000201 + */ + MHD_OPTION_ALLOW_BIN_ZERO_IN_URI_PATH = 43 } _MHD_FIXED_ENUM; @@ -2689,7 +2709,9 @@ typedef enum MHD_Result * @param cls argument given together with the function * pointer when the handler was registered with MHD * @param connection the connection handle - * @param url the requested url + * @param url the requested url, can be truncated or can be NULL, depending + * on #MHD_OPTION_ALLOW_BIN_ZERO_IN_URI_PATH option, + * see #MHD_get_connection_URI_path_n() * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, * #MHD_HTTP_METHOD_PUT, etc.) * @param version the HTTP version string (i.e. @@ -2718,7 +2740,7 @@ typedef enum MHD_Result * #MHD_NO if the socket must be closed due to a serious * error while handling the request * - * @sa #MHD_queue_response() + * @sa #MHD_queue_response(), #MHD_get_connection_URI_path_n() */ typedef enum MHD_Result (*MHD_AccessHandlerCallback)(void *cls, @@ -3518,6 +3540,39 @@ MHD_run_from_select2 (struct MHD_Daemon *daemon, /* **************** Connection handling functions ***************** */ + +/** + * Get request URI path (the request target without query part). + * + * The value obtained by this function is the same value as @a url provided + * for #MHD_AccessHandlerCallback callback, but this function also provides + * the size of the string. + * + * This function is critically important when binary zero is allowed by daemon + * option #MHD_OPTION_ALLOW_BIN_ZERO_IN_URI_PATH as this is the only way to + * get the non-truncated request URI. + * + * Returned @a uri pointer is valid until response is started or connection + * is terminated. + * + * @param connection the connection to URI from + * @param[out] uri set to the request URI without query part, may contain + * binary zeros (NUL) characters, never set to NULL on + * success; can be NULL + * @param[out] uri_size set to the size of the @a uri in bytes, not including + * final zero-termination; can be NULL + * @return #MHD_NO if failed (request is not yet processed or response has + * been queued already); + * #MHD_YES on success (*uri set to valid pointer) + * @note Available since #MHD_VERSION 0x01000201 + * @sa #MHD_OPTION_ALLOW_BIN_ZERO_IN_URI_PATH, #MHD_AccessHandlerCallback + * @ingroup request + */ +_MHD_EXTERN enum MHD_Result +MHD_get_connection_URI_path_n (struct MHD_Connection *connection, + const char **uri, + size_t *uri_size); + /** * Get all of the headers from the request. * diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c @@ -513,6 +513,13 @@ #endif /** + * Response text used when the request target path has %00 sequence. + */ +#define REQUEST_HAS_NUL_CHAR_IN_PATH \ + "<html><head><title>Bad Request Path</title></head>" \ + "<body>The request path contains invalid characters.</body></html>" + +/** * Response text used when the request HTTP chunked encoding is * malformed. */ @@ -792,6 +799,32 @@ recv_param_adapter (struct MHD_Connection *connection, } +_MHD_EXTERN enum MHD_Result +MHD_get_connection_URI_path_n (struct MHD_Connection *connection, + const char **uri, + size_t *uri_size) +{ + if (NULL != uri) + *uri = NULL; + if (NULL != uri_size) + *uri_size = 0u; + + if (connection->state < MHD_CONNECTION_REQ_LINE_RECEIVED) + return MHD_NO; + if (connection->state >= MHD_CONNECTION_START_REPLY) + return MHD_NO; + if (NULL == connection->rq.url) + return MHD_NO; + + if (NULL != uri) + *uri = connection->rq.url; + if (NULL != uri_size) + *uri_size = connection->rq.url_len; + + return MHD_YES; +} + + /** * Get all of the headers from the request. * @@ -2917,6 +2950,7 @@ transmit_error_response_len (struct MHD_Connection *connection, connection->rq.method = NULL; connection->rq.url = NULL; connection->rq.url_len = 0; + connection->rq.url_for_callback = NULL; connection->rq.headers_received = NULL; connection->rq.headers_received_tail = NULL; connection->write_buffer = NULL; @@ -4402,7 +4436,7 @@ call_connection_handler (struct MHD_Connection *connection) if (MHD_NO == daemon->default_handler (daemon->default_handler_cls, connection, - connection->rq.url, + connection->rq.url_for_callback, connection->rq.method, connection->rq.version, NULL, @@ -4435,11 +4469,9 @@ process_request_body (struct MHD_Connection *connection) bool instant_retry; char *buffer_head; const int discp_lvl = daemon->client_discipline; - /* Treat bare LF as the end of the line. - RFC 9112, section 2.2-3 - Note: MHD never replaces bare LF with space (RFC 9110, section 5.5-5). - Bare LF is processed as end of the line or rejected as broken request. */ - const bool bare_lf_as_crlf = MHD_ALLOW_BARE_LF_AS_CRLF_ (discp_lvl); + /* RFC does not allow LF as the line termination in chunk headers. + See RFC 9112, section 7.1 and section 2.2-3 */ + const bool bare_lf_as_crlf = (-2 > discp_lvl); /* Allow "Bad WhiteSpace" in chunk extension. RFC 9112, Section 7.1.1, Paragraph 2 */ const bool allow_bws = (2 > discp_lvl); @@ -4462,6 +4494,7 @@ process_request_body (struct MHD_Connection *connection) connection->rq.current_chunk_size) && (0 != connection->rq.current_chunk_size) ) { + /* Skip CRLF chunk termination */ size_t i; mhd_assert (0 != available); /* skip new line at the *end* of a chunk */ @@ -4491,12 +4524,10 @@ process_request_body (struct MHD_Connection *connection) } if (0 != connection->rq.current_chunk_size) { + /* Process chunk "content" */ uint64_t cur_chunk_left; mhd_assert (connection->rq.current_chunk_offset < \ connection->rq.current_chunk_size); - /* we are in the middle of a chunk, give - as much as possible to the client (without - crossing chunk boundaries) */ cur_chunk_left = connection->rq.current_chunk_size - connection->rq.current_chunk_offset; @@ -4532,10 +4563,12 @@ process_request_body (struct MHD_Connection *connection) broken = (0 == num_dig); if (broken) + { /* Check whether result is invalid due to uint64_t overflow */ overflow = ((('0' <= buffer_head[0]) && ('9' >= buffer_head[0])) || (('A' <= buffer_head[0]) && ('F' >= buffer_head[0])) || (('a' <= buffer_head[0]) && ('f' >= buffer_head[0]))); + } else { /** @@ -4646,7 +4679,7 @@ process_request_body (struct MHD_Connection *connection) if (MHD_NO == daemon->default_handler (daemon->default_handler_cls, connection, - connection->rq.url, + connection->rq.url_for_callback, connection->rq.method, connection->rq.version, buffer_head, @@ -5589,6 +5622,7 @@ process_request_target (struct MHD_Connection *c) mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); mhd_assert (NULL == c->rq.url); mhd_assert (0 == c->rq.url_len); + mhd_assert (NULL == c->rq.url_for_callback); mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt); mhd_assert ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) || \ (c->rq.hdrs.rq_line.rq_tgt <= c->rq.hdrs.rq_line.rq_tgt_qmark)); @@ -5630,6 +5664,7 @@ process_request_target (struct MHD_Connection *c) params_len = 0; #endif /* _DEBUG */ + mhd_assert (NULL == c->rq.url_for_callback); mhd_assert (strlen (c->rq.hdrs.rq_line.rq_tgt) == \ c->rq.req_target_len - params_len); @@ -5640,6 +5675,18 @@ process_request_target (struct MHD_Connection *c) c->rq.hdrs.rq_line.rq_tgt); c->rq.url = c->rq.hdrs.rq_line.rq_tgt; + if (2 == c->daemon->allow_bzero_in_url) + c->rq.url_for_callback = c->rq.url; + else if (strlen (c->rq.url) == c->rq.url_len) + c->rq.url_for_callback = c->rq.url; + else if (0 == c->daemon->allow_bzero_in_url) + { + transmit_error_response_static (c, + MHD_HTTP_BAD_REQUEST, + REQUEST_HAS_NUL_CHAR_IN_PATH); + return false; + } + return true; } @@ -5684,6 +5731,7 @@ get_request_line (struct MHD_Connection *c) mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); mhd_assert (NULL == c->rq.url); mhd_assert (0 == c->rq.url_len); + mhd_assert (NULL == c->rq.url_for_callback); mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt); if (0 != c->rq.hdrs.rq_line.num_ws_in_uri) { diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c @@ -7125,6 +7125,12 @@ parse_options_va (struct MHD_Daemon *daemon, } #endif /* HAVE_MESSAGES */ break; + case MHD_OPTION_ALLOW_BIN_ZERO_IN_URI_PATH: + daemon->allow_bzero_in_url = va_arg (ap, int); + if ((0 > daemon->allow_bzero_in_url) || + (2 < daemon->allow_bzero_in_url)) + daemon->allow_bzero_in_url = 1; + break; case MHD_OPTION_ARRAY: params->num_opts--; /* Do not count MHD_OPTION_ARRAY */ oa = va_arg (ap, struct MHD_OptionItem *); @@ -7189,6 +7195,7 @@ parse_options_va (struct MHD_Daemon *daemon, case MHD_OPTION_SIGPIPE_HANDLED_BY_APP: case MHD_OPTION_TLS_NO_ALPN: case MHD_OPTION_APP_FD_SETSIZE: + case MHD_OPTION_ALLOW_BIN_ZERO_IN_URI_PATH: if (MHD_NO == parse_options (daemon, params, opt, diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h @@ -1106,6 +1106,12 @@ struct MHD_Request size_t req_target_len; /** + * Requested URL (everything after "GET" only). + * Depending on daemon setting either the same as @a URL or NULL. + */ + const char *url_for_callback; + + /** * Linked list of parsed headers. */ struct MHD_HTTP_Req_Header *headers_received; @@ -2264,6 +2270,13 @@ struct MHD_Daemon */ int client_discipline; + /** + * Allow binary zero in the URI (excluding query part). + * When set to '1' then @a url parameter must be NULL, when binary zero + * is used in URI. + */ + int allow_bzero_in_url; + #ifdef HAS_FD_SETSIZE_OVERRIDABLE /** * The value of FD_SETSIZE used by the daemon.