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:
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,