]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MAJOR: mux-h2: split absolute URIs like the HTTP analysers do on output
authorOlivier Houchard <ohouchard@haproxy.com>
Wed, 7 Oct 2026 09:18:12 +0000 (11:18 +0200)
committerOlivier Houchard <cognet@ci0.org>
Mon, 19 Oct 2026 18:56:23 +0000 (20:56 +0200)
When deriving :authority and :path from an absolute-form URI, the
authority ended at the first '/' only instead of the first '/', '?' or
'#' (RFC3986#3.2), and the userinfo was stripped up to the first '@'
instead of the last one. The analysers and sample fetches rely on
http_parse_authority() which does it correctly, so a request could be
evaluated for one host/path and delivered to the H2 server with
another one, bypassing host and path based rules.

Now the authority is extracted like http_parse_authority() does, and a
'/' is prepended to :path when needed.

This exists since commit b8ce8905c ("MEDIUM: mux-h2: do not map Host to
:authority on output") in 2.1, the userinfo part since 29fa2667a
("BUG/MINOR: mux-h2: strip the userinfo when deriving :authority for a
server") in 3.5-dev5. Must be backported to all stable versions (the
userinfo part only where 29fa2667a was backported).

src/mux_h2.c

index 27fe9894815b7291c55db39f6f91c6d4f8cb7bfb..ab13520b6ba731107a9f9410e98af7ccaf9aefb1 100644 (file)
@@ -7203,17 +7203,30 @@ static size_t h2s_snd_bhdrs(struct h2s *h2s, struct htx *htx)
                                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);
 
-                               /* RFC9113#8.3.1: :authority must not carry the deprecated userinfo */
-                               at = istfind(auth, '@');
-                               if (istlen(at))
-                                       auth = istadv(at, 1);
+                               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.ptr != '/') {
+                                       struct buffer *path = get_trash_chunk();
+
+                                       if (!chunk_memcat(path, "/", 1) || !chunk_memcat(path, uri.ptr, uri.len))
+                                               goto fail;
+                                       uri = ist2(b_orig(path), b_data(path));
+                               }
                        }
                }