libmicrohttpd2

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

commit 981c15d31e22bea12cc8b10e4d4364344fdbeacd
parent a049cc791afcf4ae9380a87e33572b875ce4e489
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date:   Sun,  5 Jul 2026 00:43:04 +0200

Aligned with RFC interpretation of HTTP version string

Diffstat:
Msrc/include/microhttpd2.h | 25++++++++++++++++---------
Msrc/include/microhttpd2_main.h.in | 25++++++++++++++++---------
Msrc/mhd2/http_prot_ver.h | 26+++++++++++++++++---------
Msrc/mhd2/mhd_enum_to_str.c | 20++++++++++++++++----
Msrc/mhd2/stream_process_reply.c | 4++--
Msrc/mhd2/stream_process_request.c | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/mhd2/stream_process_states.c | 2+-
Msrc/mhd2/upgrade_prep.c | 2+-
Msrc/tests/raw/test_incompatible.c | 24+++++++++++++++++++++++-
Msrc/tests/raw/test_raw.c | 20++++++++++++++++++++
10 files changed, 219 insertions(+), 82 deletions(-)

diff --git a/src/include/microhttpd2.h b/src/include/microhttpd2.h @@ -5882,17 +5882,19 @@ MHD_FN_CONST_; */ enum MHD_FIXED_ENUM_MHD_SET_ MHD_HTTP_ProtocolVersion { - MHD_HTTP_VERSION_INVALID = 0 + MHD_HTTP_VERSION_INVALID = 0 /**< Invalid/unrecognised HTTP version */ , - MHD_HTTP_VERSION_1_0 = 1 + MHD_HTTP_VERSION_1_0 = 10 /**< HTTP/1.0 */ , - MHD_HTTP_VERSION_1_1 = 2 + MHD_HTTP_VERSION_1_1 = 11 /**< HTTP/1.1 */ , - MHD_HTTP_VERSION_2 = 3 + MHD_HTTP_VERSION_1_2P = 19 /**< HTTP/1.2 - HTTP/1.9 */ , - MHD_HTTP_VERSION_3 = 4 + MHD_HTTP_VERSION_2 = 20 /**< HTTP/2 */ , - MHD_HTTP_VERSION_FUTURE = 255 + MHD_HTTP_VERSION_3 = 30 /**< HTTP/3 */ + , + MHD_HTTP_VERSION_FUTURE = 255 /**< Future HTTP version */ }; #define MHD_HTTP_PROTOCOL_VER_DEFINED 1 @@ -5900,7 +5902,7 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_HTTP_ProtocolVersion /** * Return the string representation of the requested HTTP version. - * Note: this is suitable mainly for logging and similar proposes as + * Note: this is suitable mainly for logging and similar purposes as * HTTP/2 (and later) is not used inside the HTTP protocol. * @param pv the protocol version * @return the string representation of the protocol version, @@ -5919,14 +5921,19 @@ MHD_FN_CONST_; */ #define MHD_HTTP_VERSION_1_1_STR "HTTP/1.1" /** + * Identification string for clients claiming HTTP/1.2 - HTTP/1.9 + * Not used by the HTTP protocol, useful for logs and similar purposes. + */ +#define MHD_HTTP_VERSION_1_2P_STR "HTTP/1.2+" +/** * HTTP/2 identification string. * Not used by the HTTP protocol (except non-TLS handshake), useful for logs and - * similar proposes. + * similar purposes. */ #define MHD_HTTP_VERSION_2_STR "HTTP/2" /** * HTTP/3 identification string. - * Not used by the HTTP protocol, useful for logs and similar proposes. + * Not used by the HTTP protocol, useful for logs and similar purposes. */ #define MHD_HTTP_VERSION_3_STR "HTTP/3" diff --git a/src/include/microhttpd2_main.h.in b/src/include/microhttpd2_main.h.in @@ -964,17 +964,19 @@ MHD_FN_CONST_; */ enum MHD_FIXED_ENUM_MHD_SET_ MHD_HTTP_ProtocolVersion { - MHD_HTTP_VERSION_INVALID = 0 + MHD_HTTP_VERSION_INVALID = 0 /**< Invalid/unrecognised HTTP version */ , - MHD_HTTP_VERSION_1_0 = 1 + MHD_HTTP_VERSION_1_0 = 10 /**< HTTP/1.0 */ , - MHD_HTTP_VERSION_1_1 = 2 + MHD_HTTP_VERSION_1_1 = 11 /**< HTTP/1.1 */ , - MHD_HTTP_VERSION_2 = 3 + MHD_HTTP_VERSION_1_2P = 19 /**< HTTP/1.2 - HTTP/1.9 */ , - MHD_HTTP_VERSION_3 = 4 + MHD_HTTP_VERSION_2 = 20 /**< HTTP/2 */ , - MHD_HTTP_VERSION_FUTURE = 255 + MHD_HTTP_VERSION_3 = 30 /**< HTTP/3 */ + , + MHD_HTTP_VERSION_FUTURE = 255 /**< Future HTTP version */ }; #define MHD_HTTP_PROTOCOL_VER_DEFINED 1 @@ -982,7 +984,7 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_HTTP_ProtocolVersion /** * Return the string representation of the requested HTTP version. - * Note: this is suitable mainly for logging and similar proposes as + * Note: this is suitable mainly for logging and similar purposes as * HTTP/2 (and later) is not used inside the HTTP protocol. * @param pv the protocol version * @return the string representation of the protocol version, @@ -1001,14 +1003,19 @@ MHD_FN_CONST_; */ #define MHD_HTTP_VERSION_1_1_STR "HTTP/1.1" /** + * Identification string for clients claiming HTTP/1.2 - HTTP/1.9 + * Not used by the HTTP protocol, useful for logs and similar purposes. + */ +#define MHD_HTTP_VERSION_1_2P_STR "HTTP/1.2+" +/** * HTTP/2 identification string. * Not used by the HTTP protocol (except non-TLS handshake), useful for logs and - * similar proposes. + * similar purposes. */ #define MHD_HTTP_VERSION_2_STR "HTTP/2" /** * HTTP/3 identification string. - * Not used by the HTTP protocol, useful for logs and similar proposes. + * Not used by the HTTP protocol, useful for logs and similar purposes. */ #define MHD_HTTP_VERSION_3_STR "HTTP/3" diff --git a/src/mhd2/http_prot_ver.h b/src/mhd2/http_prot_ver.h @@ -56,17 +56,19 @@ */ enum MHD_FIXED_ENUM_MHD_SET_ MHD_HTTP_ProtocolVersion { - MHD_HTTP_VERSION_INVALID = 0 + MHD_HTTP_VERSION_INVALID = 0 /**< Invalid/unrecognised HTTP version */ , - MHD_HTTP_VERSION_1_0 = 1 + MHD_HTTP_VERSION_1_0 = 10 /**< HTTP/1.0 */ , - MHD_HTTP_VERSION_1_1 = 2 + MHD_HTTP_VERSION_1_1 = 11 /**< HTTP/1.1 */ , - MHD_HTTP_VERSION_2 = 3 + MHD_HTTP_VERSION_1_2P = 19 /**< HTTP/1.2 - HTTP/1.9 */ , - MHD_HTTP_VERSION_3 = 4 + MHD_HTTP_VERSION_2 = 20 /**< HTTP/2 */ , - MHD_HTTP_VERSION_FUTURE = 255 + MHD_HTTP_VERSION_3 = 30 /**< HTTP/3 */ + , + MHD_HTTP_VERSION_FUTURE = 255 /**< Future HTTP version */ }; @@ -74,10 +76,16 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_HTTP_ProtocolVersion #endif /* ! MHD_HTTP_PROTOCOL_VER_DEFINED */ /** - * Check whether version of HTTP protocol is supported + * Check whether version of HTTP protocol is supported as HTTP/1.x + */ +#define MHD_HTTP_VERSION_IS_1X(v) \ + ((MHD_HTTP_VERSION_1_0 <= (v)) && (MHD_HTTP_VERSION_1_2P >= (v))) + +/** + * Check whether version of HTTP protocol is supported like version 1.1 */ -#define MHD_HTTP_VERSION_IS_SUPPORTED(v) \ - ((MHD_HTTP_VERSION_1_0 <= (v)) && (MHD_HTTP_VERSION_1_1 >= (v))) +#define MHD_HTTP_VERSION_IS_LIKE_11(v) \ + ((MHD_HTTP_VERSION_1_1 <= (v)) && (MHD_HTTP_VERSION_1_2P >= (v))) /** * Check whether version of HTTP protocol is valid diff --git a/src/mhd2/mhd_enum_to_str.c b/src/mhd2/mhd_enum_to_str.c @@ -65,28 +65,40 @@ MHD_protocol_version_to_string (enum MHD_HTTP_ProtocolVersion pv) case MHD_HTTP_VERSION_1_0: if (1) { - static const struct MHD_String ret = mhd_MSTR_INIT ("HTTP/1.0"); + static const struct MHD_String ret = + mhd_MSTR_INIT (MHD_HTTP_VERSION_1_0_STR); return &ret; } break; case MHD_HTTP_VERSION_1_1: if (1) { - static const struct MHD_String ret = mhd_MSTR_INIT ("HTTP/1.1"); + static const struct MHD_String ret = + mhd_MSTR_INIT (MHD_HTTP_VERSION_1_1_STR); + return &ret; + } + break; + case MHD_HTTP_VERSION_1_2P: + if (1) + { + static const struct MHD_String ret = + mhd_MSTR_INIT (MHD_HTTP_VERSION_1_2P_STR); return &ret; } break; case MHD_HTTP_VERSION_2: if (1) { - static const struct MHD_String ret = mhd_MSTR_INIT ("HTTP/2"); + static const struct MHD_String ret = + mhd_MSTR_INIT (MHD_HTTP_VERSION_2_STR); return &ret; } break; case MHD_HTTP_VERSION_3: if (1) { - static const struct MHD_String ret = mhd_MSTR_INIT ("HTTP/3"); + static const struct MHD_String ret = + mhd_MSTR_INIT (MHD_HTTP_VERSION_3_STR); return &ret; } break; diff --git a/src/mhd2/stream_process_reply.c b/src/mhd2/stream_process_reply.c @@ -172,7 +172,7 @@ get_conn_reuse (struct MHD_Connection *c) mhd_assert ((MHD_SIZE_UNKNOWN != rp->cntn_size) || \ (! rp->cfg.mode_1_0)); - if (! MHD_HTTP_VERSION_IS_SUPPORTED (c->rq.http_ver)) + if (! MHD_HTTP_VERSION_IS_1X (c->rq.http_ver)) return mhd_CONN_MUST_CLOSE; if (rp->cfg.mode_1_0 && @@ -293,7 +293,7 @@ setup_reply_properties (struct MHD_Connection *restrict c) if (r->cfg.chunked) { mhd_assert (! r->cfg.mode_1_0); - use_chunked = (MHD_HTTP_VERSION_1_1 == c->rq.http_ver); + use_chunked = MHD_HTTP_VERSION_IS_LIKE_11 (c->rq.http_ver); } if ((MHD_SIZE_UNKNOWN == r->cntn_size) && (! use_chunked) && diff --git a/src/mhd2/stream_process_request.c b/src/mhd2/stream_process_request.c @@ -577,6 +577,76 @@ parse_http_std_method (struct MHD_Connection *restrict connection) /** + * Internal version of #MHD_HTTP_ProtocolVersion extended with mhd_HTTP_VER_0X + */ +enum MHD_FIXED_ENUM_MHD_SET_ mhd_HTTP_ProtVerParse +{ + mhd_HTTP_VER_INVALID = MHD_HTTP_VERSION_INVALID + , + mhd_HTTP_VER_0X = 9 + , + mhd_HTTP_VER_1_0 = MHD_HTTP_VERSION_1_0 + , + mhd_HTTP_VER_1_1 = MHD_HTTP_VERSION_1_1 + , + mhd_HTTP_VER_1_2P = MHD_HTTP_VERSION_1_2P + , + mhd_HTTP_VER_2 = MHD_HTTP_VERSION_2 + , + mhd_HTTP_VER_3 = MHD_HTTP_VERSION_3 + , + mhd_HTTP_VER_FUTURE = MHD_HTTP_VERSION_FUTURE +}; + + +/** + * Parse HTTP version + * + * @param len the length of @a http_string in bytes + * @param http_string the pointer to HTTP version string + */ +static MHD_FN_PAR_IN_SIZE_ (2,1) MHD_FN_PAR_NONNULL_ALL_ +enum mhd_HTTP_ProtVerParse +parse_http_version (size_t len, + const char *restrict http_string) +{ + const char *const h = http_string; /**< short alias */ + mhd_assert (NULL != http_string); + + /* String must start with 'HTTP/d.d', case-sensitive match. + * See https://www.rfc-editor.org/rfc/rfc9112#name-http-version */ + if ((HTTP_VER_LEN == len) + && (0 == memcmp ("HTTP/", h, 5u)) + && ('.' == h[6])) + { + const unsigned char mj = (unsigned char) (h[5] - '0'); /**< Major number */ + const unsigned char mn = (unsigned char) (h[7] - '0'); /**< Minor number */ + + if (1u == mj) + { + /* HTTP/1.x */ + if (1u == mn) + return mhd_HTTP_VER_1_1; + if (0u == mn) + return mhd_HTTP_VER_1_0; + if (9u >= mn) + return mhd_HTTP_VER_1_2P; + + return mhd_HTTP_VER_INVALID; + } + + if (0u == mj) + return mhd_HTTP_VER_0X; /* Too old major version */ + + if ((2u == mj) && (0u == mn)) + return mhd_HTTP_VER_2; + } + + return mhd_HTTP_VER_INVALID; +} + + +/** * Detect HTTP version, send error response if version is not supported * * @param connection the connection @@ -585,61 +655,52 @@ parse_http_std_method (struct MHD_Connection *restrict connection) * @return true if HTTP version is correct and supported, * false if HTTP version is not correct or unsupported. */ -static bool -parse_http_version (struct MHD_Connection *restrict connection, - const char *restrict http_string, - size_t len) +static MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_NONNULL_ALL_ bool +process_http_version (struct MHD_Connection *restrict connection, + size_t len, + const char *restrict http_string) { - const char *const h = http_string; /**< short alias */ - mhd_assert (NULL != http_string); + enum mhd_HTTP_ProtVerParse h_ver; - /* String must start with 'HTTP/d.d', case-sensetive match. - * See https://www.rfc-editor.org/rfc/rfc9112#name-http-version */ - if ((HTTP_VER_LEN != len) || - ('H' != h[0]) || ('T' != h[1]) || ('T' != h[2]) || ('P' != h[3]) || - ('/' != h[4]) - || ('.' != h[6])) + h_ver = parse_http_version (len, + http_string); + if (mhd_HTTP_VER_0X == h_ver) { connection->rq.http_ver = MHD_HTTP_VERSION_INVALID; mhd_RESPOND_WITH_ERROR_STATIC (connection, - MHD_HTTP_STATUS_BAD_REQUEST, - ERR_RSP_REQUEST_MALFORMED); + MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED, + ERR_RSP_REQ_HTTP_VER_IS_TOO_OLD); return false; } - if (1 == h[5] - '0') - { - /* HTTP/1.x */ - if (1 == h[7] - '0') - { - connection->rq.http_ver = MHD_HTTP_VERSION_1_1; - return true; - } - else if (0 == h[7] - '0') - { - connection->rq.http_ver = MHD_HTTP_VERSION_1_0; - return true; - } - else - connection->rq.http_ver = MHD_HTTP_VERSION_INVALID; - } - else if (0 == h[5] - '0') + connection->rq.http_ver = (enum MHD_HTTP_ProtocolVersion) h_ver; + + switch (connection->rq.http_ver) { - /* Too old major version */ - connection->rq.http_ver = MHD_HTTP_VERSION_INVALID; + case MHD_HTTP_VERSION_INVALID: + break; + case MHD_HTTP_VERSION_1_1: + case MHD_HTTP_VERSION_1_0: + return true; + case MHD_HTTP_VERSION_1_2P: + if (MHD_PSL_VERY_STRICT > connection->daemon->req_cfg.strictness) + return true; + break; + case MHD_HTTP_VERSION_2: mhd_RESPOND_WITH_ERROR_STATIC (connection, MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED, - ERR_RSP_REQ_HTTP_VER_IS_TOO_OLD); + ERR_RSP_REQ_HTTP_VER_IS_NOT_SUPPORTED); return false; + case MHD_HTTP_VERSION_3: + case MHD_HTTP_VERSION_FUTURE: + default: + mhd_UNREACHABLE (); + break; } - else if ((2 == h[5] - '0') && (0 == h[7] - '0')) - connection->rq.http_ver = MHD_HTTP_VERSION_2; - else - connection->rq.http_ver = MHD_HTTP_VERSION_INVALID; mhd_RESPOND_WITH_ERROR_STATIC (connection, - MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED, - ERR_RSP_REQ_HTTP_VER_IS_NOT_SUPPORTED); + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_REQUEST_MALFORMED); return false; } @@ -952,10 +1013,10 @@ get_request_line_inner (struct MHD_Connection *restrict c) if (NULL != c->rq.version) { mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt); - if (! parse_http_version (c, c->rq.version, - p - - (size_t) (c->rq.version - - read_buffer))) + if (! process_http_version (c, + p - (size_t) (c->rq.version + - read_buffer), + c->rq.version)) { mhd_assert (mhd_HTTP_STAGE_REQ_LINE_RECEIVING < c->stage); return true; /* Unsupported / broken HTTP version */ @@ -2910,7 +2971,7 @@ mhd_stream_parse_request_headers (struct MHD_Connection *restrict c) "for this request."); } - if (MHD_HTTP_VERSION_1_1 <= c->rq.http_ver) + if (MHD_HTTP_VERSION_IS_LIKE_11 (c->rq.http_ver)) { if ((! has_host) && (-3 < c->daemon->req_cfg.strictness)) @@ -2946,7 +3007,7 @@ mhd_stream_parse_request_headers (struct MHD_Connection *restrict c) static MHD_FN_PAR_NONNULL_ALL_ bool need_100_continue (struct MHD_Connection *restrict c) { - mhd_assert (MHD_HTTP_VERSION_IS_SUPPORTED (c->rq.http_ver)); + mhd_assert (MHD_HTTP_VERSION_IS_1X (c->rq.http_ver)); mhd_assert (mhd_HTTP_STAGE_HEADERS_PROCESSED <= c->stage); mhd_assert (mhd_HTTP_STAGE_BODY_RECEIVING > c->stage); diff --git a/src/mhd2/stream_process_states.c b/src/mhd2/stream_process_states.c @@ -374,7 +374,7 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) if (mhd_stream_get_request_line (c)) { mhd_assert (mhd_HTTP_STAGE_REQ_LINE_RECEIVING < c->stage); - mhd_assert ((MHD_HTTP_VERSION_IS_SUPPORTED (c->rq.http_ver)) \ + mhd_assert ((MHD_HTTP_VERSION_IS_1X (c->rq.http_ver)) \ || (c->discard_request) || (mhd_HTTP_STAGE_PRE_CLOSING <= c->stage)); continue; diff --git a/src/mhd2/upgrade_prep.c b/src/mhd2/upgrade_prep.c @@ -168,7 +168,7 @@ build_reply_header (struct MHD_Connection *restrict c, bool has_conn_hdr; bool hdr_name_invalid; - mhd_assert (MHD_HTTP_VERSION_1_1 == c->rq.http_ver); + mhd_assert (MHD_HTTP_VERSION_IS_LIKE_11 (c->rq.http_ver)); mhd_assert ((0 == c->rq.cntn.cntn_size) || \ (mhd_HTTP_STAGE_FULL_REQ_RECEIVED == c->stage)); diff --git a/src/tests/raw/test_incompatible.c b/src/tests/raw/test_incompatible.c @@ -387,10 +387,32 @@ static struct Test tests_must[] = { // RFC 9110 Section 5.1: Field names must be tokens, not quoted strings }, #endif +#if 0 /* NOT A BUG */ { .name = "HTTP/1.1 request with HTTP/1.2 version", .upload = "GET / HTTP/1.2\r\nHost: example.com\r\n\r\n", - // RFC 9112 Section 2.3: HTTP/1.2 is not defined + /* HTTP/1.2 SHOULD be processed as 1.1 if server supports 1.1. + RFC 9110, Section 6.2 + See https://www.rfc-editor.org/rfc/rfc9110.html#name-control-data */ + }, +#endif + { + .name = "HTTP/1.1 request with HTTP/1.10 version", + .upload = "GET / HTTP/1.10\r\nHost: example.com\r\n\r\n", + /* Version must be "digit dot digit" + * RFC 9112, Section 2.3 */ + }, + { + .name = "HTTP/1.1 request with HTTP/1.I version", + .upload = "GET / HTTP/1.I\r\nHost: example.com\r\n\r\n", + /* Version must be "digit dot digit" + * RFC 9112, Section 2.3 */ + }, + { + .name = "HTTP/1.1 request with HTTP/1,1 version", + .upload = "GET / HTTP/1,1\r\nHost: example.com\r\n\r\n", + /* Version must be "digit dot digit" + * RFC 9112, Section 2.3 */ }, { .name = "HTTP version HTTP/2.0 on HTTP/1.1 connection", diff --git a/src/tests/raw/test_raw.c b/src/tests/raw/test_raw.c @@ -107,6 +107,26 @@ static struct Test tests[] = { .expect_parser = "H-Host:example.com", }, { + /* HTTP/1.2 SHOULD be processed as 1.1 if server supports 1.1. + RFC 9110, Section 6.2 + See https://www.rfc-editor.org/rfc/rfc9110.html#name-control-data */ + .name = "Basic HTTP/1.2 GET", + .upload = "GET / HTTP/1.2\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com", + }, + { + /* HTTP/1.9 SHOULD be processed as 1.1 if server supports 1.1. + RFC 9110, Section 6.2 + See https://www.rfc-editor.org/rfc/rfc9110.html#name-control-data */ + .name = "Basic HTTP/1.9 GET", + .upload = "GET / HTTP/1.9\r\nHost: example.com\r\n\r\n", + .expect_method = MHD_HTTP_METHOD_GET, + .expect_path = "/", + .expect_parser = "H-Host:example.com", + }, + { .name = "Multi-header HTTP/1.1 GET with query parameters", .upload = "GET /?k=v&a HTTP/1.1\r\nHost: example.com\r\nKey: value\r\n\r\n", .expect_method = MHD_HTTP_METHOD_GET,