From: Christopher Faulet Date: Wed, 7 Oct 2026 14:05:00 +0000 (+0200) Subject: BUG/MEDIUM: mux-h1: don't send chunked responses to HTTP/1.0 clients X-Git-Tag: v3.5-dev10~137 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=bdc4b82d16a87c1b730c2565bf5c3fdd4a46b976;p=haproxy.git BUG/MEDIUM: mux-h1: don't send chunked responses to HTTP/1.0 clients When a response without content-length is sent to an H1 client, the chunked transfer-coding is added if the response is in HTTP/1.1, and a chunked response is forwarded as-is. But the version which matters here is the request's one: the chunked transfer-coding must not be sent in a response to an HTTP/1.0 request (RFC9112#6.1), since such a client doesn't know about it. Now, on the frontend side, when the request was not HTTP/1.1 and the response has a body without content-length, it is switched to an unknown transfer length: no transfer-encoding header is emitted, the payload is sent close-delimited (trailers are dropped) and the close mode is forced. For bodyless responses (HEAD, 204, 304), only the transfer-encoding header is removed. A reg-test was added for this. This patch must be backported to all supported versions. --- diff --git a/reg-tests/http-messaging/h1_chunked_resp_to_http10.vtc b/reg-tests/http-messaging/h1_chunked_resp_to_http10.vtc new file mode 100644 index 000000000..7f80dda15 --- /dev/null +++ b/reg-tests/http-messaging/h1_chunked_resp_to_http10.vtc @@ -0,0 +1,76 @@ +varnishtest "No chunked responses to HTTP/1.0 clients" + +# The chunked transfer-coding must not be used in responses to HTTP/1.0 +# requests. Responses without content-length are sent close-delimited +# instead, while HTTP/1.1 clients still get chunked responses. + +feature ignore_unknown_macro + +server s1 { + rxreq + expect req.proto == "HTTP/1.0" + txresp -nolen -hdr "Transfer-Encoding: chunked" + chunkedlen 5 + chunkedlen 7 + chunkedlen 0 +} -start + +server s3 { + rxreq + expect req.method == "HEAD" + txresp -nolen -hdr "Transfer-Encoding: chunked" +} -start + +server s2 { + rxreq + expect req.proto == "HTTP/1.1" + txresp -nolen -hdr "Transfer-Encoding: chunked" + chunkedlen 5 + chunkedlen 7 + chunkedlen 0 +} -start + +haproxy h1 -conf { + defaults + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + listen li1 + bind "fd@${li1}" + server s1 ${s1_addr}:${s1_port} + + listen li3 + bind "fd@${li3}" + server s3 ${s3_addr}:${s3_port} + + listen li2 + bind "fd@${li2}" + server s2 ${s2_addr}:${s2_port} +} -start + +client c1 -connect ${h1_li1_sock} { + txreq -req GET -url / -proto HTTP/1.0 -hdr "Connection: keep-alive" + rxresp + expect resp.status == 200 + expect resp.http.transfer-encoding == "" + expect resp.http.connection == "close" + expect resp.bodylen == 12 + expect_close +} -run + +client c2 -connect ${h1_li2_sock} { + txreq -req GET -url / + rxresp + expect resp.status == 200 + expect resp.http.transfer-encoding == "chunked" + expect resp.bodylen == 12 +} -run + +client c3 -connect ${h1_li3_sock} { + txreq -req HEAD -url / -proto HTTP/1.0 -hdr "Connection: keep-alive" + rxresp -no_obj + expect resp.status == 200 + expect resp.http.transfer-encoding == "" +} -run diff --git a/src/mux_h1.c b/src/mux_h1.c index 18c844856..0a6d29e80 100644 --- a/src/mux_h1.c +++ b/src/mux_h1.c @@ -2630,6 +2630,16 @@ static size_t h1_make_stline(struct h1s *h1s, struct h1m *h1m, struct htx *htx, else if ((sl->flags & HTX_SL_F_BODYLESS_RESP) || h1s->status == 204 || h1s->status == 304) h1s->flags |= H1S_F_BODYLESS_RESP; + /* The chunked transfer-coding must not be used in a response to an + * HTTP/1.0 request (RFC9112#6.1). + */ + if (!(h1s->req.flags & H1_MF_VER_11) && h1s->status >= 200 && !(h1m->flags & H1_MF_CLEN)) { + if (h1s->flags & H1S_F_BODYLESS_RESP) + h1m->flags &= ~(H1_MF_XFER_ENC|H1_MF_CHNK); + else + h1m->flags &= ~(H1_MF_XFER_LEN|H1_MF_XFER_ENC|H1_MF_CHNK); + } + h1m->state = H1_MSG_HDR_NAME; ret += sz; htx_remove_blk(htx, blk);