libmicrohttpd

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

commit 5c3a61d68c04ebfd62695fa2a90e6212e42a1cae
parent 42be9415cbcdb23463a86fbee612b3809bf7ed0c
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
Date:   Thu, 22 Dec 2022 16:30:12 +0300

Added new daemon option MHD_OPTION_CLIENT_DISCIPLINE_LV

Reject URIs with spaces as per RFC.
Fixed check for space before colon in headers (previously it was checked
only when MHD was NOT strict).
Reject HTTP/1.1 requests without host by default (as per RFC).

Diffstat:
Msrc/examples/connection_close.c | 2+-
Msrc/examples/minimal_example.c | 1-
Msrc/examples/minimal_example_empty.c | 1-
Msrc/examples/minimal_example_empty_tls.c | 2+-
Msrc/include/microhttpd.h | 74+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/microhttpd/connection.c | 18+++++++++---------
Msrc/microhttpd/daemon.c | 32++++++++++++++++++++++++++------
Msrc/microhttpd/internal.h | 5+++--
8 files changed, 103 insertions(+), 32 deletions(-)

diff --git a/src/examples/connection_close.c b/src/examples/connection_close.c @@ -118,7 +118,7 @@ main (int argc, char *const *argv) MHD_OPTION_NOTIFY_COMPLETED, &request_completed, NULL, MHD_OPTION_NOTIFY_CONNECTION, &connection_completed, NULL, MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120, - MHD_OPTION_STRICT_FOR_CLIENT, (int) 1, + MHD_OPTION_CLIENT_DISCIPLINE_LVL, (int) 1, MHD_OPTION_END); if (d == NULL) return 1; diff --git a/src/examples/minimal_example.c b/src/examples/minimal_example.c @@ -105,7 +105,6 @@ main (int argc, (uint16_t) port, NULL, NULL, &ahc_echo, &data_for_handler, MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120, - MHD_OPTION_STRICT_FOR_CLIENT, (int) 1, MHD_OPTION_END); if (d == NULL) return 1; diff --git a/src/examples/minimal_example_empty.c b/src/examples/minimal_example_empty.c @@ -93,7 +93,6 @@ main (int argc, (uint16_t) port, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120, - MHD_OPTION_STRICT_FOR_CLIENT, (int) 1, MHD_OPTION_END); if (d == NULL) return 1; diff --git a/src/examples/minimal_example_empty_tls.c b/src/examples/minimal_example_empty_tls.c @@ -160,7 +160,7 @@ main (int argc, (uint16_t) port, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120, - MHD_OPTION_STRICT_FOR_CLIENT, (int) 1, + MHD_OPTION_CLIENT_DISCIPLINE_LVL, (int) 1, /* Optionally, the gnutls_load_file() can be used to load the key and the certificate from file. */ MHD_OPTION_HTTPS_MEM_KEY, srv_signed_key_pem, diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -96,7 +96,7 @@ extern "C" * they are parsed as decimal numbers. * Example: 0x01093001 = 1.9.30-1. */ -#define MHD_VERSION 0x00097544 +#define MHD_VERSION 0x00097545 /* If generic headers don't work on your platform, include headers which define 'va_list', 'size_t', 'ssize_t', 'intptr_t', 'off_t', @@ -1291,12 +1291,14 @@ enum MHD_FLAG * as liberal as possible in what you accept" norm. It is * recommended to turn this ON if you are testing clients against * MHD, and OFF in production. + * @sa #MHD_OPTION_CLIENT_DISCIPLINE_LVL */ MHD_USE_PEDANTIC_CHECKS = 32, #if 0 /* Will be marked for real deprecation later. */ #define MHD_USE_PEDANTIC_CHECKS \ _MHD_DEPR_IN_MACRO ( \ - "Flag MHD_USE_PEDANTIC_CHECKS is deprecated, use option MHD_OPTION_STRICT_FOR_CLIENT instead") \ + "Flag MHD_USE_PEDANTIC_CHECKS is deprecated, " \ + "use option MHD_OPTION_CLIENT_DISCIPLINE_LVL instead") \ 32 #endif /* 0 */ @@ -1939,15 +1941,18 @@ enum MHD_OPTION * If set to 1 - be strict about the protocol. Use -1 to be * as tolerant as possible. * - * Specifically, at the moment, at 1 this flag - * causes MHD to reject HTTP 1.1 connections without a "Host" header, - * and to disallow spaces in the URL or (at -1) in HTTP header key strings. + * The more flexible option #MHD_OPTION_CLIENT_DISCIPLINE_LVL is recommended + * instead of this option. * - * These are required by some versions of the standard, but of - * course in violation of the "be as liberal as possible in what you - * accept" norm. It is recommended to set this to 1 if you are - * testing clients against MHD, and 0 in production. This option - * should be followed by an `int` argument. + * The values mapping table: + * #MHD_OPTION_STRICT_FOR_CLIENT | #MHD_OPTION_CLIENT_DISCIPLINE_LVL + * -----------------------------:|:--------------------------------- + * 1 | 1 + * 0 | 0 + * -1 | -3 + * + * This option should be followed by an `int` argument. + * @sa #MHD_OPTION_CLIENT_DISCIPLINE_LVL */ MHD_OPTION_STRICT_FOR_CLIENT = 29, @@ -2037,7 +2042,54 @@ enum MHD_OPTION * default priorities. * @note Available since #MHD_VERSION 0x00097542 */ - MHD_OPTION_HTTPS_PRIORITIES_APPEND = 37 + MHD_OPTION_HTTPS_PRIORITIES_APPEND = 37, + + /** + * Sets specified client discipline level (i.e. HTTP protocol parsing + * strictness level). + * + * The following basic values are supported: + * 0 - default MHD level, a balance between extra security and broader + * compatibility, as allowed by RFCs for HTTP servers; + * 1 - more strict protocol interpretation, within the limits set by + * RFCs for HTTP servers; + * -1 - more lenient protocol interpretation, within the limits set by + * RFCs for HTTP servers. + * The following extended values could be used as well: + * 2 - stricter protocol interpretation, even stricter then allowed + * by RFCs for HTTP servers, however it should be absolutely compatible + * with clients following at least RFCs' "MUST" type of requirements + * for HTTP clients; + * 3 - strictest protocol interpretation, even stricter then allowed + * by RFCs for HTTP servers, however it should be absolutely compatible + * with clients following RFCs' "SHOULD" and "MUST" types of requirements + * for HTTP clients; + * -2 - more relaxed protocol interpretation, violating RFCs' "SHOULD" type + * of requirements for HTTP servers; + * -3 - the most flexible protocol interpretation, beyond RFCs' "MUST" type of + * requirements for HTTP server. + * Values higher than "3" or lower than "-3" are interpreted as "3" or "-3" + * respectively. + * + * Higher values are more secure, lower values are more compatible with + * various HTTP clients. + * + * The default value ("0") could be used in most cases. + * Value "1" is suitable for highly loaded public servers. + * Values "2" and "3" are generally recommended only for testing of HTTP + * clients against MHD. + * Value "2" may be used for security-centric application, however it is + * slight violation of RFCs' requirements. + * Negative values are not recommended for public servers. + * Values "-1" and "-2" could be used for servers in isolated environment. + * Value "-3" is not recommended unless it is absolutely necessary to + * communicate with some client(s) with badly broken HTTP implementation. + * + * This option should be followed by an `int` argument. + * @note Available since #MHD_VERSION 0x00097545 + */ + MHD_OPTION_CLIENT_DISCIPLINE_LVL = 38 + } _MHD_FIXED_ENUM; diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c @@ -2846,15 +2846,15 @@ parse_cookies_string (char *str, size_t i; bool non_strict; /* Skip extra whitespaces and empty cookies */ - const bool allow_wsp_empty = (0 >= connection->daemon->strict_for_client); + const bool allow_wsp_empty = (0 >= connection->daemon->client_discipline); /* Allow whitespaces around '=' character */ - const bool wsp_around_eq = (0 > connection->daemon->strict_for_client); + const bool wsp_around_eq = (-3 >= connection->daemon->client_discipline); /* Allow whitespaces in quoted cookie value */ - const bool wsp_in_quoted = (0 >= connection->daemon->strict_for_client); + const bool wsp_in_quoted = (0 >= connection->daemon->client_discipline); /* Allow tab as space after semicolon between cookies */ - const bool tab_as_sp = (0 >= connection->daemon->strict_for_client); + const bool tab_as_sp = (0 >= connection->daemon->client_discipline); /* Allow no space after semicolon between cookies */ - const bool allow_no_space = (0 >= connection->daemon->strict_for_client); + const bool allow_no_space = (0 >= connection->daemon->client_discipline); non_strict = false; i = 0; @@ -3327,7 +3327,7 @@ parse_initial_message_line (struct MHD_Connection *connection, uri_len = line_len - (size_t) (uri - line); } /* check for spaces in URI if we are "strict" */ - if ( (1 <= daemon->strict_for_client) && + if ( (-2 < daemon->client_discipline) && (NULL != memchr (uri, ' ', uri_len)) ) @@ -3752,7 +3752,7 @@ process_header_line (struct MHD_Connection *connection, /* error in header line, die hard */ return MHD_NO; } - if (-1 >= connection->daemon->strict_for_client) + if (-3 < connection->daemon->client_discipline) { /* check for whitespace before colon, which is not allowed by RFC 7230 section 3.2.4; we count space ' ' and @@ -3897,7 +3897,7 @@ parse_connection_headers (struct MHD_Connection *connection) return; } #endif /* COOKIE_SUPPORT */ - if ( (1 <= connection->daemon->strict_for_client) && + if ( (-3 < connection->daemon->client_discipline) && (MHD_IS_HTTP_VER_1_1_COMPAT (connection->rq.http_ver)) && (MHD_NO == MHD_lookup_connection_value_n (connection, @@ -3946,7 +3946,7 @@ parse_connection_headers (struct MHD_Connection *connection) NULL)) { /* TODO: add individual settings */ - if (1 <= connection->daemon->strict_for_client) + if (1 <= connection->daemon->client_discipline) { transmit_error_response_static (connection, MHD_HTTP_BAD_REQUEST, diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c @@ -5729,7 +5729,7 @@ unescape_wrapper (void *cls, (void) cls; /* Mute compiler warning. */ /* TODO: add individual parameter */ - if (1 <= connection->daemon->strict_for_client) + if (0 <= connection->daemon->client_discipline) return MHD_str_pct_decode_in_place_strict_ (val); res = MHD_str_pct_decode_in_place_lenient_ (val, &broken); @@ -6653,14 +6653,33 @@ parse_options_va (struct MHD_Daemon *daemon, unsigned int); break; case MHD_OPTION_STRICT_FOR_CLIENT: - daemon->strict_for_client = va_arg (ap, int); + daemon->client_discipline = va_arg (ap, int); /* Temporal assignment */ + /* Map to correct value */ + if (-1 >= daemon->client_discipline) + daemon->client_discipline = -3; + else if (1 <= daemon->client_discipline) + daemon->client_discipline = 1; #ifdef HAVE_MESSAGES if ( (0 != (daemon->options & MHD_USE_PEDANTIC_CHECKS)) && - (1 != daemon->strict_for_client) ) + (1 != daemon->client_discipline) ) { MHD_DLOG (daemon, _ ("Flag MHD_USE_PEDANTIC_CHECKS is ignored because " - "another behavior is specified by MHD_OPTION_STRICT_CLIENT.\n")); + "another behaviour is specified by " + "MHD_OPTION_STRICT_CLIENT.\n")); + } +#endif /* HAVE_MESSAGES */ + break; + case MHD_OPTION_CLIENT_DISCIPLINE_LVL: + daemon->client_discipline = va_arg (ap, int); +#ifdef HAVE_MESSAGES + if ( (0 != (daemon->options & MHD_USE_PEDANTIC_CHECKS)) && + (1 != daemon->client_discipline) ) + { + MHD_DLOG (daemon, + _ ("Flag MHD_USE_PEDANTIC_CHECKS is ignored because " + "another behaviour is specified by " + "MHD_OPTION_CLIENT_DISCIPLINE_LVL.\n")); } #endif /* HAVE_MESSAGES */ break; @@ -6723,6 +6742,7 @@ parse_options_va (struct MHD_Daemon *daemon, break; /* all options taking 'int' */ case MHD_OPTION_STRICT_FOR_CLIENT: + case MHD_OPTION_CLIENT_DISCIPLINE_LVL: case MHD_OPTION_SIGPIPE_HANDLED_BY_APP: case MHD_OPTION_TLS_NO_ALPN: if (MHD_NO == parse_options (daemon, @@ -7102,8 +7122,8 @@ MHD_start_daemon_va (unsigned int flags, daemon->listening_address_reuse = 0; daemon->options = *pflags; pflags = &daemon->options; - daemon->strict_for_client = (0 != (*pflags & MHD_USE_PEDANTIC_CHECKS)) ? 1 : - 0; + daemon->client_discipline = (0 != (*pflags & MHD_USE_PEDANTIC_CHECKS)) ? + 1 : 0; daemon->port = port; daemon->apc = apc; daemon->apc_cls = apc_cls; diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h @@ -2104,9 +2104,10 @@ struct MHD_Daemon unsigned int per_ip_connection_limit; /** - * Be neutral (zero), strict (1) or permissive (-1) to client. + * The strictness level for parsing of incoming data. + * @see #MHD_OPTION_CLIENT_DISCIPLINE_LVL */ - int strict_for_client; + int client_discipline; /** * True if SIGPIPE is blocked