From b2ba3c666202706fd2602f1bda4e4067034d04cc Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Wed, 25 Feb 2026 16:20:56 +0100 Subject: [PATCH] MEDIUM: http-fetch: Rework how HTTP message version is retrieved Thanks to previous patches, we can now rely on the version stored in the http_msg structure to get the request or the response version. "req.ver" and "res.ver" sample fetch functions returns the string representation of the version, without the prefix, so ".", but only if the version is valid. For the response, "res.ver" may be added from a health-check context, in that case, the HTX message is used. "capture.req.ver" and "capture.res.ver" does the same but the "HTTP/" prefix is added to the result. And "capture.res.ver" cannot be called from a health-check. To ease the version formatting and avoid code duplication, an helper function was added. So these samples are now relying on "get_msg_version()". --- doc/configuration.txt | 31 +++++----- src/http_fetch.c | 140 ++++++++++++++++++++++-------------------- 2 files changed, 90 insertions(+), 81 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 5245fd084..e34546088 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -26907,9 +26907,10 @@ capture.req.uri : string allocated. capture.req.ver : string - This extracts the request's HTTP version and returns either "HTTP/1.0" or - "HTTP/1.1". Unlike "req.ver", it can be used in both request, response, and - logs because it relies on a persistent flag. + This extracts the request's HTTP version and returns it with the format + "HTTP/.". It can be used in both request, response, and logs + because it relies on a persistent information. If the request version is not + valid, this sample fetch fails. capture.res.hdr() : string This extracts the content of the header captured by the "capture response @@ -26918,9 +26919,10 @@ capture.res.hdr() : string See also: "capture response header" capture.res.ver : string - This extracts the response's HTTP version and returns either "HTTP/1.0" or - "HTTP/1.1". Unlike "res.ver", it can be used in logs because it relies on a - persistent flag. + This extracts the response's HTTP version and returns it with the format + "HTTP/.". It can be used in logs because it relies on a + persistent information. If the response version is not valid, this sample + fetch fails. cookie([]) : string (deprecated) This extracts the last occurrence of the cookie name on a "Cookie" @@ -27271,16 +27273,14 @@ req.timer.tq : integer req.ver : string req_ver : string (deprecated) - Returns the version string from the HTTP request, for example "1.1". This can - be useful for ACL. For logs use the "%HV" logformat alias. Some predefined - ACL already check for versions 1.0 and 1.1. + Returns the version string from the HTTP request, with the format + ".". This can be useful for ACL. Some predefined ACL already + check for common versions. It can be used in both request, response, and + logs because it relies on a persistent information. If the request version is + not valid, this sample fetch fails. Common values are "1.0", "1.1", "2.0" or "3.0". - In the case of http/2 and http/3, the value is not extracted from the HTTP - version in the request line but is determined by the negotiated protocol - version. - ACL derivatives : req.ver : exact string match @@ -27484,8 +27484,9 @@ res.timer.hdr : integer res.ver : string resp_ver : string (deprecated) - Returns the version string from the HTTP response, for example "1.1". This - can be useful for logs, but is mostly there for ACL. + Returns the version string from the HTTP response, with the format + ".". This can be useful for logs, but is mostly there for + ACL. If the response version is not valid, this sample fetch fails. It may be used in tcp-check based expect rules. diff --git a/src/http_fetch.c b/src/http_fetch.c index eae140b3b..8ce9a38e5 100644 --- a/src/http_fetch.c +++ b/src/http_fetch.c @@ -311,19 +311,33 @@ struct htx *smp_prefetch_htx(struct sample *smp, struct channel *chn, struct che * that further checks can rely on HTTP tests. */ if (sl && msg->msg_state < HTTP_MSG_BODY) { + struct ist vsn; + if (!(chn->flags & CF_ISRESP)) { + vsn = htx_sl_req_vsn(sl); txn->meth = sl->info.req.meth; if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD) s->flags |= SF_REDIRECTABLE; } else { + vsn = htx_sl_res_vsn(sl); if (txn->status == -1) txn->status = sl->info.res.status; if (txn->server_status == -1) txn->server_status = sl->info.res.status; } - if (sl->flags & HTX_SL_F_VER_11) - msg->flags |= HTTP_MSGF_VER_11; + + if ((sl->flags & HTX_SL_F_NOT_HTTP) || istlen(vsn) != 8) { + /* Not an HTTP message */ + msg->vsn = 0; + } + else { + char *ptr = istptr(vsn); + + msg->vsn = ((ptr[5] - '0') << 4) + (ptr[7] - '0'); + if (sl->flags & HTX_SL_F_VER_11) + msg->flags |= HTTP_MSGF_VER_11; + } } /* everything's OK */ @@ -331,6 +345,37 @@ struct htx *smp_prefetch_htx(struct sample *smp, struct channel *chn, struct che return htx; } +/* Get the HTTP version from or and append it into the chunk + * with the format ".". + * It returns 0 if and are both NULL or if the version + * is not a valid HTTP version. Otherwise, it returns 1 (success). + * + * The version is retrieved from , if not NULL. Otherwise, it is retrieved + * from . + */ +static int get_msg_version(const struct http_msg *msg, const struct htx *htx, struct buffer *chk) +{ + if (msg) { + if (msg->vsn) { + chunk_appendf(chk, "%d.%d", (msg->vsn & 0xf0) >> 4, msg->vsn & 0xf); + return 1; + } + } + else if (htx) { + struct htx_sl *sl = http_get_stline(htx); + struct ist vsn = htx_sl_vsn(sl); + + if (!(sl->flags & HTX_SL_F_NOT_HTTP) && istlen(vsn) == 8) { + chunk_appendf(chk, "%d.%d", istptr(vsn)[5] - '0', istptr(vsn)[7] - '0'); + return 1; + } + } + + /* and are both NULL or not a valid HTTP version */ + return 0; +} + + /* This function fetches the method of current HTTP request and stores * it in the global pattern struct as a chunk. There are two possibilities : * - if the method is known (not HTTP_METH_OTHER), its identifier is stored @@ -374,56 +419,34 @@ static int smp_fetch_meth(const struct arg *args, struct sample *smp, const char static int smp_fetch_rqver(const struct arg *args, struct sample *smp, const char *kw, void *private) { + struct stream *s = smp->strm; struct channel *chn = SMP_REQ_CHN(smp); struct htx *htx = smp_prefetch_htx(smp, chn, NULL, 1); - struct htx_sl *sl; - char *ptr; - int len; + struct buffer *vsn = get_trash_chunk(); - if (!htx) - return 0; - - sl = http_get_stline(htx); - len = HTX_SL_REQ_VLEN(sl); - ptr = HTX_SL_REQ_VPTR(sl); - - while ((len-- > 0) && (*ptr++ != '/')); - if (len <= 0) + if (!get_msg_version((s && s->txn) ? &s->txn->req : NULL, htx, vsn)) return 0; smp->data.type = SMP_T_STR; - smp->data.u.str.area = ptr; - smp->data.u.str.data = len; - - smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; + smp->data.u.str = *vsn; return 1; } static int smp_fetch_stver(const struct arg *args, struct sample *smp, const char *kw, void *private) { + struct stream *s = smp->strm; struct channel *chn = SMP_RES_CHN(smp); struct check *check = objt_check(smp->sess->origin); struct htx *htx = smp_prefetch_htx(smp, chn, check, 1); - struct htx_sl *sl; - char *ptr; - int len; + struct buffer *vsn = get_trash_chunk(); - if (!htx) - return 0; - - sl = http_get_stline(htx); - len = HTX_SL_RES_VLEN(sl); - ptr = HTX_SL_RES_VPTR(sl); - - while ((len-- > 0) && (*ptr++ != '/')); - if (len <= 0) + if (!get_msg_version((s && s->txn) ? &s->txn->rsp : NULL, htx, vsn)) return 0; smp->data.type = SMP_T_STR; - smp->data.u.str.area = ptr; - smp->data.u.str.data = len; + smp->data.u.str = *vsn; + return 1; - smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; return 1; } @@ -1638,25 +1661,17 @@ static int smp_fetch_capture_req_uri(const struct arg *args, struct sample *smp, */ static int smp_fetch_capture_req_ver(const struct arg *args, struct sample *smp, const char *kw, void *private) { - struct http_txn *txn; + struct stream *s = smp->strm; + struct buffer *vsn; - if (!smp->strm) + vsn = get_trash_chunk(); + chunk_memcat(vsn, "HTTP/", 5); + if (!get_msg_version((s && s->txn) ? &s->txn->req : NULL, NULL, vsn)) return 0; - txn = smp->strm->txn; - if (!txn || txn->req.msg_state < HTTP_MSG_BODY) - return 0; - - if (txn->req.flags & HTTP_MSGF_VER_11) - smp->data.u.str.area = "HTTP/1.1"; - else - smp->data.u.str.area = "HTTP/1.0"; - - smp->data.u.str.data = 8; - smp->data.type = SMP_T_STR; - smp->flags = SMP_F_CONST; + smp->data.type = SMP_T_STR; + smp->data.u.str = *vsn; return 1; - } /* Retrieves the HTTP version from the response (either 1.0 or 1.1) and emits it @@ -1664,23 +1679,16 @@ static int smp_fetch_capture_req_ver(const struct arg *args, struct sample *smp, */ static int smp_fetch_capture_res_ver(const struct arg *args, struct sample *smp, const char *kw, void *private) { - struct http_txn *txn; + struct stream *s = smp->strm; + struct buffer *vsn; - if (!smp->strm) + vsn = get_trash_chunk(); + chunk_memcat(vsn, "HTTP/", 5); + if (!get_msg_version((s && s->txn) ? &s->txn->rsp : NULL, NULL, vsn)) return 0; - txn = smp->strm->txn; - if (!txn || txn->rsp.msg_state < HTTP_MSG_BODY) - return 0; - - if (txn->rsp.flags & HTTP_MSGF_VER_11) - smp->data.u.str.area = "HTTP/1.1"; - else - smp->data.u.str.area = "HTTP/1.0"; - - smp->data.u.str.data = 8; - smp->data.type = SMP_T_STR; - smp->flags = SMP_F_CONST; + smp->data.type = SMP_T_STR; + smp->data.u.str = *vsn; return 1; } @@ -2333,8 +2341,8 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { { "req_proto_http", smp_fetch_proto_http, 0, NULL, SMP_T_BOOL, SMP_USE_HRQHP }, /* HTTP version on the request path */ - { "req.ver", smp_fetch_rqver, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, - { "req_ver", smp_fetch_rqver, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, + { "req.ver", smp_fetch_rqver, 0, NULL, SMP_T_STR, SMP_USE_HRQHP }, + { "req_ver", smp_fetch_rqver, 0, NULL, SMP_T_STR, SMP_USE_HRQHP }, { "req.body", smp_fetch_body, 0, NULL, SMP_T_BIN, SMP_USE_HRQHV }, { "req.body_len", smp_fetch_body_len, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV }, @@ -2345,8 +2353,8 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { { "req.hdrs_bin", smp_fetch_hdrs_bin, 0, NULL, SMP_T_BIN, SMP_USE_HRQHV }, /* HTTP version on the response path */ - { "res.ver", smp_fetch_stver, 0, NULL, SMP_T_STR, SMP_USE_HRSHV }, - { "resp_ver", smp_fetch_stver, 0, NULL, SMP_T_STR, SMP_USE_HRSHV }, + { "res.ver", smp_fetch_stver, 0, NULL, SMP_T_STR, SMP_USE_HRSHP }, + { "resp_ver", smp_fetch_stver, 0, NULL, SMP_T_STR, SMP_USE_HRSHP }, { "res.body", smp_fetch_body, 0, NULL, SMP_T_BIN, SMP_USE_HRSHV }, { "res.body_len", smp_fetch_body_len, 0, NULL, SMP_T_SINT, SMP_USE_HRSHV },