From: Christopher Faulet Date: Fri, 24 Jul 2026 09:08:15 +0000 (+0200) Subject: BUG/MEDIUM: mux-h1: Always adjust case for all outgoing headers as expected X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=f66cb3e5bff9518319bfd99faf78ad44f4308b7b;p=thirdparty%2Fhaproxy.git BUG/MEDIUM: mux-h1: Always adjust case for all outgoing headers as expected 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. --- diff --git a/include/haproxy/h1_htx.h b/include/haproxy/h1_htx.h index 74e6fcb1b..07672e65f 100644 --- a/include/haproxy/h1_htx.h +++ b/include/haproxy/h1_htx.h @@ -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 */ diff --git a/src/h1_htx.c b/src/h1_htx.c index c226c0007..5b112d2f8 100644 --- a/src/h1_htx.c +++ b/src/h1_htx.c @@ -1110,14 +1110,26 @@ int h1_format_htx_stline(const struct htx_sl *sl, struct buffer *chk) * returns 0. * 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 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) diff --git a/src/http_fetch.c b/src/http_fetch.c index 01222c294..de0688278 100644 --- a/src/http_fetch.c +++ b/src/http_fetch.c @@ -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) { diff --git a/src/httpclient_cli.c b/src/httpclient_cli.c index aa94136fe..c7b3166f5 100644 --- a/src/httpclient_cli.c +++ b/src/httpclient_cli.c @@ -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)) diff --git a/src/mux_h1.c b/src/mux_h1.c index c42ce6d3e..2d9b13372 100644 --- a/src/mux_h1.c +++ b/src/mux_h1.c @@ -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 - * . +/* 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 into . @@ -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)) {