]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MEDIUM: mux-h1: don't drop pending output on a short content-length
authorOlivier Houchard <ohouchard@haproxy.com>
Fri, 30 Oct 2026 15:04:33 +0000 (16:04 +0100)
committerOlivier Houchard <cognet@ci0.org>
Tue, 3 Nov 2026 13:58:48 +0000 (14:58 +0100)
When a fixed-length message is truncated and output data is still pending
in the H1 output buffer, h1s_destroy() flags the connection in error
because we got less data than expected, and so h1_send() will refuse to
send the remaining data, and not everything will be transmitted.

Now, when a truncated message is detected while output data is still
pending, H1C_F_ERROR is not set immediately, we just avoid adding it to
the idle pool, and the connection will be closed later when everything
has been sent.

To release the connection once the output buffer is drained, the
condition in h1_process() that only matched delayed silent shutdowns is
generalized to any connection in CLOSING state with an empty output
buffer. This covers both silent shutdowns and the short content-length
case. The connection is still not released while a stream remains
attached, since h1_close() keeps it in CLOSING until the stream is
detached.

This should be backported where 75dd2c18dda29f9893574ef982ddf66de16f0408
has been backported, up to 2.8.

src/mux_h1.c

index d9c68514ba8ec3761bd4ed038c7fa9a42bc4e7f3..63e16e355526a455cc8d6d1de9bca1ff61dd0e3f 100644 (file)
@@ -1091,6 +1091,7 @@ static void h1s_destroy(struct h1s *h1s)
                 * belong to us anymore and must not be passed to traces.
                 */
                struct connection *conn = (h1c->conn && h1c->conn->ctx == h1c) ? h1c->conn : NULL;
+               int short_clen = 0;
 
                TRACE_POINT(H1_EV_H1S_END, conn, h1s);
                h1c->h1s = NULL;
@@ -1107,14 +1108,21 @@ static void h1s_destroy(struct h1s *h1s)
 
                /*
                 * If we reached the end of the stream and sent less bits than
-                * announced in Content-Length, we have little choice but to
-                * close the connection, as the peer will expect more.
+                * announced in Content-Length, we can't add the connection
+                * to the idle pool, as the peer will expect more. Just flag
+                * the connection in error if there is nothing left to send,
+                * otherwise the connection will be closed once everything
+                * has been sent.
                 */
                if ((h1s->req.curr_len && !(h1s->flags & H1S_F_BODYLESS_REQ)) ||
-                   (h1s->res.curr_len && !(h1s->flags & H1S_F_BODYLESS_RESP)))
-                       h1c->flags |= H1C_F_ERROR;
+                   (h1s->res.curr_len && !(h1s->flags & H1S_F_BODYLESS_RESP))) {
+                       short_clen = 1;
+                       if (!b_data(&h1c->obuf))
+                               h1c->flags |= H1C_F_ERROR;
+               }
 
                if (!(h1c->flags & (H1C_F_EOS|H1C_F_ERR_PENDING|H1C_F_ERROR|H1C_F_ABRT_PENDING|H1C_F_ABRTED)) &&  /* No error/read0/abort */
+                   !short_clen &&                                                           /* no truncated message */
                    h1_is_alive(h1c) &&                                                      /* still alive */
                    (h1s->flags & H1S_F_WANT_KAL) &&                                         /* K/A possible */
                    h1s->req.state == H1_MSG_DONE && h1s->res.state == H1_MSG_DONE) {        /* req/res in DONE state */
@@ -4378,10 +4386,10 @@ static int h1_process(struct h1c * h1c)
        /* H1 connection must be released ASAP if:
         *  - an error occurred on the H1C or
         *  - a read0 was received or
-        *  - a silent shutdown was emitted and all outgoing data sent
+        *  - the connection is closing and all outgoing data sent
         */
        if ((h1c->flags & (H1C_F_EOS|H1C_F_ERROR|H1C_F_ABRT_PENDING|H1C_F_ABRTED)) ||
-           (h1c->state >= H1_CS_CLOSING && (h1c->flags & H1C_F_SILENT_SHUT) && !b_data(&h1c->obuf))) {
+           (h1c->state >= H1_CS_CLOSING && !b_data(&h1c->obuf))) {
                if (h1c->state != H1_CS_RUNNING) {
                        /* No stream connector or upgrading */
                        if (h1c->state == H1_CS_IDLE)