]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: mux-h1: Always adjust case for all outgoing headers as expected
authorChristopher Faulet <cfaulet@haproxy.com>
Fri, 24 Jul 2026 09:08:15 +0000 (11:08 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 24 Jul 2026 09:56:44 +0000 (11:56 +0200)
In HTX, all header names are converted to lowercase. However, some legacy H1
applications are still sensitive to the header names case.  For this
purpose, it is possible to provide a map to automatically adjust the case of
header names. While it is performed for most responses, it is not true for
the low-level errors triggered during requests parsing. It the same ways,
the case of "Sec-Websocket-Key" a "Sec-Websocket-Accept" headers were
adjusted as expected.

To fix this issue the h1-htx API was slightly changed. Now the map used to
adjust the case of header names, if any, is passed to the function
responsible to format the headers. In the H1 multiplexer, the map is first
retrieved then passed as argument to h1_format_htx_hdr() and
h1_format_htx_msg() functions. A NULL pointer is passed if no rewrite must
be performed.

In the H1 multiplexer, h1_adjust_case_outgoing_hdr() was replaced by
h1_get_hdrs_map().

All other calls to h1_format_htx_hdr() were adapted to use a NULL pointer
(httpclient, http-fetch).

This patch relies on "REORG: h1-htx: Move h1 headers map in h1-htx". Both
commits should be backported to all supported versions.

This should fix the issue #3448.

include/haproxy/h1_htx.h
src/h1_htx.c
src/http_fetch.c
src/httpclient_cli.c
src/mux_h1.c

index 74e6fcb1b1b2407f849f2c1e8f9295c67b582185..07672e65f531ba15623fafed871b1110af94ef5c 100644 (file)
@@ -64,9 +64,9 @@ static inline struct ist h1_get_uri(const struct htx_sl *sl)
 
 int h1_format_htx_reqline(const struct htx_sl *sl, struct buffer *chk);
 int h1_format_htx_stline(const struct htx_sl *sl, struct buffer *chk);
-int h1_format_htx_hdr(const struct ist n, const struct ist v, struct buffer *chk);
+int h1_format_htx_hdr(const struct ist n, const struct ist v, struct buffer *chk, struct h1_hdrs_map *hdrs_map);
 int h1_format_htx_data(const struct ist data, struct buffer *chk, int chunked);
-int h1_format_htx_msg(const struct htx *htx, struct buffer *outbuf);
+int h1_format_htx_msg(const struct htx *htx, struct buffer *outbuf, struct h1_hdrs_map *hdrs_map);
 
 #endif /* _HAPROXY_H1_HTX_H */
 
index c226c0007ed0e9bd15abe2545d95dc5e65e9fdf4..5b112d2f88a38987d7d1ff8e40b56032abe774dd 100644 (file)
@@ -1110,14 +1110,26 @@ int h1_format_htx_stline(const struct htx_sl *sl, struct buffer *chk)
  * returns 0.
  * <chk> buffer must not wrap.
  */
-int h1_format_htx_hdr(const struct ist n, const struct ist v, struct buffer *chk)
+int h1_format_htx_hdr(const struct ist n, const struct ist v, struct buffer *chk,
+                     struct h1_hdrs_map *hdrs_map)
 {
+       struct ebpt_node *node;
+       struct h1_hdr_entry *entry;
+       struct ist name = n;
        size_t sz = chk->data;
 
        if (n.len + v.len + 4 > b_room(chk))
                return 0;
 
-       if (!chunk_memcat(chk, n.ptr, n.len) ||
+       if (hdrs_map && !eb_is_empty(&hdrs_map->map)) {
+               node = ebis_lookup_len(&hdrs_map->map, istptr(n), istlen(n));
+               if (node) {
+                       entry = container_of(node, struct h1_hdr_entry, node);
+                       name = entry->name;
+               }
+       }
+
+       if (!chunk_memcat(chk, name.ptr, name.len) ||
            !chunk_memcat(chk, ": ", 2) ||
            !chunk_memcat(chk, v.ptr, v.len) ||
            !chunk_memcat(chk, "\r\n", 2))
@@ -1174,7 +1186,7 @@ int h1_format_htx_data(const struct ist data, struct buffer *chk, int chunked)
  * 0 if <outbuf> is full or not empty. No check is performed on the message, it must be
  * valid. Trailers are silently ignored if the message is not chunked.
  */
-int h1_format_htx_msg(const struct htx *htx, struct buffer *outbuf)
+int h1_format_htx_msg(const struct htx *htx, struct buffer *outbuf, struct h1_hdrs_map *hdrs_map)
 {
        const struct htx_sl *sl;
         const struct htx_blk *blk;
@@ -1204,7 +1216,7 @@ int h1_format_htx_msg(const struct htx *htx, struct buffer *outbuf)
                        n = htx_get_blk_name(htx, blk);
                        v = htx_get_blk_value(htx, blk);
 
-                       if (!h1_format_htx_hdr(n, v, outbuf))
+                       if (!h1_format_htx_hdr(n, v, outbuf, hdrs_map))
                                goto full;
                }
                else if (type == HTX_BLK_EOH) {
@@ -1223,7 +1235,7 @@ int h1_format_htx_msg(const struct htx *htx, struct buffer *outbuf)
                        n = htx_get_blk_name(htx, blk);
                        v = htx_get_blk_value(htx, blk);
 
-                       if (!h1_format_htx_hdr(n, v, outbuf))
+                       if (!h1_format_htx_hdr(n, v, outbuf, hdrs_map))
                                goto full;
                }
                else if (type == HTX_BLK_EOT)
index 01222c294e5e6930387ca8b95e97f0755841987b..de068827829684d1e06f0aed5b46ae58d10e07ad 100644 (file)
@@ -553,7 +553,7 @@ static int smp_fetch_hdrs(const struct arg *args, struct sample *smp, const char
                        struct ist n = htx_get_blk_name(htx, blk);
                        struct ist v = htx_get_blk_value(htx, blk);
 
-                       if (!h1_format_htx_hdr(n, v, temp))
+                       if (!h1_format_htx_hdr(n, v, temp, NULL))
                                return 0;
                }
                else if (type == HTX_BLK_EOH) {
index aa94136fe26bd8210483e2d6b4f296ad188c8f3f..c7b3166f5d82173b1f23aef92517eefe17cc28b0 100644 (file)
@@ -202,7 +202,7 @@ static int hc_cli_io_handler(struct appctx *appctx)
                        chunk_reset(&trash);
                        hdrs = hc->res.hdrs;
                        for (hdr = hdrs; isttest(hdr->v); hdr++) {
-                               if (!h1_format_htx_hdr(hdr->n, hdr->v, &trash))
+                               if (!h1_format_htx_hdr(hdr->n, hdr->v, &trash, NULL))
                                        goto too_many_hdrs;
                        }
                        if (!chunk_memcat(&trash, "\r\n", 2))
index c42ce6d3e6d0657125acbaf87d6e615a2a6939a6..2d9b13372e55132d036ea53cc7b91d607c10d0b5 100644 (file)
@@ -1730,32 +1730,19 @@ static void h1_process_output_conn_mode(struct h1s *h1s, struct h1m *h1m, struct
                h1_update_res_conn_value(h1s, h1m, conn_val);
 }
 
-/* Try to adjust the case of the message header name using the global map
- * <hdrs_map>.
+/* Depending on the proxy options, return the global map to use to adjust the
+ * case of the message headers if conversion must be performed, or NULL.
  */
-static void h1_adjust_case_outgoing_hdr(struct h1s *h1s, struct h1m *h1m, struct ist *name)
+static struct h1_hdrs_map *h1_get_hdrs_map(struct h1c *h1c, int isresp)
 {
-       struct ebpt_node *node;
-       struct h1_hdr_entry *entry;
-
-       /* No entry in the map, do nothing */
-       if (eb_is_empty(&hdrs_map.map))
-               return;
-
        /* No conversion for the request headers */
-       if (!(h1m->flags & H1_MF_RESP) && !(h1s->h1c->px->options2 & PR_O2_H1_ADJ_BUGSRV))
-               return;
-
+       if (!isresp && !(h1c->px->options2 & PR_O2_H1_ADJ_BUGSRV))
+               return NULL;
        /* No conversion for the response headers */
-       if ((h1m->flags & H1_MF_RESP) && !(h1s->h1c->px->options2 & PR_O2_H1_ADJ_BUGCLI))
-               return;
+       if (isresp && !(h1c->px->options2 & PR_O2_H1_ADJ_BUGCLI))
+               return NULL;
 
-       node = ebis_lookup_len(&hdrs_map.map, name->ptr, name->len);
-       if (!node)
-               return;
-       entry = container_of(node, struct h1_hdr_entry, node);
-       name->ptr = entry->name.ptr;
-       name->len = entry->name.len;
+       return &hdrs_map;
 }
 
 /* Append the description of what is present in error snapshot <es> into <out>.
@@ -2645,6 +2632,7 @@ static size_t h1_make_stline(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
 static size_t h1_make_headers(struct h1s *h1s, struct h1m *h1m, struct htx *htx, size_t count)
 {
        struct h1c *h1c = h1s->h1c;
+       struct h1_hdrs_map *hdrs_map = h1_get_hdrs_map(h1c, (h1m->flags & H1_MF_RESP));
        struct htx_blk *blk;
        struct buffer outbuf;
        enum htx_blk_type type;
@@ -2737,10 +2725,7 @@ static size_t h1_make_headers(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
                            isteqi(n, h1c->px->server_id_hdr_name))
                                goto nextblk;
 
-                       /* Try to adjust the case of the header name */
-                       if (h1c->px->options2 & (PR_O2_H1_ADJ_BUGCLI|PR_O2_H1_ADJ_BUGSRV))
-                               h1_adjust_case_outgoing_hdr(h1s, h1m, &n);
-                       if (!h1_format_htx_hdr(n, v, &outbuf))
+                       if (!h1_format_htx_hdr(n, v, &outbuf, hdrs_map))
                                goto full;
                }
                else if (type == HTX_BLK_EOH) {
@@ -2787,6 +2772,7 @@ static size_t h1_make_headers(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
 static size_t h1_make_eoh(struct h1s *h1s, struct h1m *h1m, struct htx *htx, size_t count)
 {
        struct h1c *h1c = h1s->h1c;
+       struct h1_hdrs_map *hdrs_map = h1_get_hdrs_map(h1c, (h1m->flags & H1_MF_RESP));
        struct htx_blk *blk;
        struct buffer outbuf;
        enum htx_blk_type type;
@@ -2840,9 +2826,7 @@ static size_t h1_make_eoh(struct h1s *h1s, struct h1m *h1m, struct htx *htx, siz
                        else {
                                /* It is the request: Add "Content-Length: 0" header and skip payload */
                                struct ist n = ist("content-length");
-                               if (h1c->px->options2 & (PR_O2_H1_ADJ_BUGCLI|PR_O2_H1_ADJ_BUGSRV))
-                                       h1_adjust_case_outgoing_hdr(h1s, h1m, &n);
-                               if (!h1_format_htx_hdr(n, ist("0"), &outbuf))
+                               if (!h1_format_htx_hdr(n, ist("0"), &outbuf, hdrs_map))
                                        goto full;
 
                                h1m->flags = (h1m->flags & ~(H1_MF_XFER_ENC|H1_MF_CHNK)) | H1_MF_CLEN;
@@ -2881,10 +2865,7 @@ static size_t h1_make_eoh(struct h1s *h1s, struct h1m *h1m, struct htx *htx, siz
                v = ist("");
                h1_process_output_conn_mode(h1s, h1m, &v);
                if (v.len) {
-                       /* Try to adjust the case of the header name */
-                       if (h1c->px->options2 & (PR_O2_H1_ADJ_BUGCLI|PR_O2_H1_ADJ_BUGSRV))
-                               h1_adjust_case_outgoing_hdr(h1s, h1m, &n);
-                       if (!h1_format_htx_hdr(n, v, &outbuf))
+                       if (!h1_format_htx_hdr(n, v, &outbuf, hdrs_map))
                                goto full;
                }
                h1s->flags |= H1S_F_HAVE_O_CONN;
@@ -2904,9 +2885,7 @@ static size_t h1_make_eoh(struct h1s *h1s, struct h1m *h1m, struct htx *htx, siz
                /* chunking needed but header not seen */
                n = ist("transfer-encoding");
                v = ist("chunked");
-               if (h1c->px->options2 & (PR_O2_H1_ADJ_BUGCLI|PR_O2_H1_ADJ_BUGSRV))
-                       h1_adjust_case_outgoing_hdr(h1s, h1m, &n);
-               if (!h1_format_htx_hdr(n, v, &outbuf))
+               if (!h1_format_htx_hdr(n, v, &outbuf, hdrs_map))
                        goto full;
                TRACE_STATE("add \"Transfer-Encoding: chunked\"", H1_EV_TX_DATA|H1_EV_TX_HDRS, h1c->conn, h1s);
                h1s->flags |= H1S_F_HAVE_CHNK;
@@ -2920,11 +2899,7 @@ static size_t h1_make_eoh(struct h1s *h1s, struct h1m *h1m, struct htx *htx, siz
                if (srv) {
                        n = h1c->px->server_id_hdr_name;
                        v = ist(srv->id);
-
-                       /* Try to adjust the case of the header name */
-                       if (h1c->px->options2 & (PR_O2_H1_ADJ_BUGCLI|PR_O2_H1_ADJ_BUGSRV))
-                               h1_adjust_case_outgoing_hdr(h1s, h1m, &n);
-                       if (!h1_format_htx_hdr(n, v, &outbuf))
+                       if (!h1_format_htx_hdr(n, v, &outbuf, hdrs_map))
                                goto full;
                }
                TRACE_STATE("add server name header", H1_EV_TX_DATA|H1_EV_TX_HDRS, h1c->conn, h1s);
@@ -2943,7 +2918,7 @@ static size_t h1_make_eoh(struct h1s *h1s, struct h1m *h1m, struct htx *htx, siz
 
                        if (!h1_format_htx_hdr(ist("Sec-Websocket-Key"),
                                               ist(h1s->ws_key),
-                                              &outbuf)) {
+                                              &outbuf, hdrs_map)) {
                                goto full;
                        }
                }
@@ -2954,7 +2929,7 @@ static size_t h1_make_eoh(struct h1s *h1s, struct h1m *h1m, struct htx *htx, siz
                        h1_calculate_ws_output_key(h1s->ws_key, key);
                        if (!h1_format_htx_hdr(ist("Sec-Websocket-Accept"),
                                               ist(key),
-                                              &outbuf)) {
+                                              &outbuf, hdrs_map)) {
                                goto full;
                        }
                }
@@ -3453,6 +3428,7 @@ static size_t h1_make_tunnel(struct h1s *h1s, struct h1m *h1m, struct buffer *bu
 static size_t h1_make_trailers(struct h1s *h1s, struct h1m *h1m, struct htx *htx, size_t count)
 {
        struct h1c *h1c = h1s->h1c;
+       struct h1_hdrs_map *hdrs_map = h1_get_hdrs_map(h1c, (h1m->flags & H1_MF_RESP));
        struct htx_blk *blk;
        struct buffer outbuf;
        enum htx_blk_type type;
@@ -3489,10 +3465,7 @@ static size_t h1_make_trailers(struct h1s *h1s, struct h1m *h1m, struct htx *htx
                        n = htx_get_blk_name(htx, blk);
                        v = htx_get_blk_value(htx, blk);
 
-                       /* Try to adjust the case of the header name */
-                       if (h1c->px->options2 & (PR_O2_H1_ADJ_BUGCLI|PR_O2_H1_ADJ_BUGSRV))
-                               h1_adjust_case_outgoing_hdr(h1s, h1m, &n);
-                       if (!h1_format_htx_hdr(n, v, &outbuf))
+                       if (!h1_format_htx_hdr(n, v, &outbuf, hdrs_map))
                                goto full;
                }
                else if (type == HTX_BLK_EOT) {
@@ -3799,8 +3772,11 @@ static int h1_send_error(struct h1c *h1c)
                TRACE_STATE("waiting for h1c obuf allocation", H1_EV_H1C_ERR|H1_EV_H1C_BLK, h1c->conn);
                goto out;
        }
-       if (errmsg)
-               ret = h1_format_htx_msg(htxbuf(errmsg), &h1c->obuf);
+       if (errmsg) {
+               struct h1_hdrs_map *hdrs_map = h1_get_hdrs_map(h1c, 1);
+
+               ret = h1_format_htx_msg(htxbuf(errmsg), &h1c->obuf, hdrs_map);
+       }
        else
                ret = b_istput(&h1c->obuf, ist(http_err_msgs[rc]));
        if (unlikely(ret <= 0)) {