]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MEDIUM: mux-h1: don't send chunked responses to HTTP/1.0 clients
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 7 Oct 2026 14:05:00 +0000 (16:05 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 16 Oct 2026 17:10:27 +0000 (19:10 +0200)
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.

reg-tests/http-messaging/h1_chunked_resp_to_http10.vtc [new file with mode: 0644]
src/mux_h1.c

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 (file)
index 0000000..7f80dda
--- /dev/null
@@ -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 == "<undef>"
+       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 == "<undef>"
+} -run
index 18c844856b57814d8a790849fc4c91c79de01c79..0a6d29e801cc4bb46b41f4c6f072993f641ef43b 100644 (file)
@@ -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);