From: Olivier Houchard Date: Wed, 7 Oct 2026 09:20:11 +0000 (+0200) Subject: BUG/MEDIUM: h3: split absolute URIs like the HTTP analysers do on output X-Git-Tag: v3.5-dev10~121 X-Git-Url: https://git.kaiwu.me/$%7BISSUES%7D/static/gitweb.js?a=commitdiff_plain;h=4f0076be836f2a80d5aad09a4fe17f745d7c50b8;p=haproxy.git BUG/MEDIUM: h3: split absolute URIs like the HTTP analysers do on output h3_req_headers_send() derived :authority and :path from absolute-form URIs with the same flaw as the H2 mux, fixed by the previous commit: the authority ended at the first '/' instead of the first '/', '?' or '#' (RFC3986#3.2), the userinfo was not stripped, and an empty path was sent as an empty :path. A request evaluated by haproxy for one host/path could be delivered to an H3 server with another one, bypassing host and path based rules. Now the authority is extracted like http_parse_authority() does, the userinfo is stripped, and :path always starts with a '/' (or is '*' for OPTIONS). As H3 backends are still experimental, the impact is limited. This was introduced by commit 235e818fa ("MINOR: h3: complete HTTP/3 request scheme encoding") in 3.3. Must be backported as far as 3.3. --- diff --git a/src/h3.c b/src/h3.c index 78655e314..a5d53f40c 100644 --- a/src/h3.c +++ b/src/h3.c @@ -2425,7 +2425,7 @@ static int h3_req_headers_send(struct qcs *qcs, struct htx *htx, char *fin) enum htx_blk_type type; struct htx_blk *blk; struct htx_sl *sl; - struct ist meth, uri, scheme = IST_NULL, auth = IST_NULL; + struct ist meth, uri, scheme = IST_NULL, auth = IST_NULL, at; int frame_length_size; /* size in bytes of frame length varint field */ int smallbuf = 1; int ret = 0; @@ -2511,15 +2511,35 @@ static int h3_req_headers_send(struct qcs *qcs, struct htx *htx, char *fin) scheme = ist2(uri.ptr, len); uri = istadv(uri, len + 3); - /* find the auth part of the URI */ + /* find the auth part of the URI. As per RFC3986#3.2, + * it ends at the first '/', '?' or '#'. + */ auth = ist2(uri.ptr, 0); - while (auth.len < uri.len && auth.ptr[auth.len] != '/') + while (auth.len < uri.len && auth.ptr[auth.len] != '/' && + auth.ptr[auth.len] != '?' && auth.ptr[auth.len] != '#') auth.len++; uri = istadv(uri, auth.len); + + for (at = ist2(istend(auth), 0); at.ptr > auth.ptr; at.ptr--) { + if (at.ptr[-1] == '@') { + auth = ist2(at.ptr, istend(auth) - at.ptr); + break; + } + } } } + if (!uri.len) + uri = ((sl->info.req.meth == HTTP_METH_OPTIONS) ? ist("*") : ist("/")); + else if (*uri.ptr != '/' && *uri.ptr != '*') { + struct buffer *path = get_trash_chunk(); + + if (!chunk_memcat(path, "/", 1) || !chunk_memcat(path, uri.ptr, uri.len)) + goto err; + uri = ist2(b_orig(path), b_data(path)); + } + if (!istlen(scheme)) { if ((sl->flags & (HTX_SL_F_HAS_SCHM|HTX_SL_F_SCHM_HTTP)) == (HTX_SL_F_HAS_SCHM|HTX_SL_F_SCHM_HTTP)) scheme = ist("http");