]> git.kaiwu.me - haproxy.git/commitdiff
REGTESTS: http-messaging: add tests for the H1 trailers
authorWilly Tarreau <w@1wt.eu>
Wed, 9 Sep 2026 07:11:01 +0000 (09:11 +0200)
committerWilly Tarreau <w@1wt.eu>
Fri, 11 Sep 2026 08:08:12 +0000 (10:08 +0200)
The Transfer-Encoding test is extended to cover the HTTP/1 trailers,
both on the request and on the response path.

Valid trailers are checked to be forwarded in both directions. vtest is
not able to parse HTTP/1 trailers (it stops on the CRLF following the
last chunk), so they are read using "recv" and only the number of bytes
emitted by haproxy is verified. This is sufficient to prove that the
spaces surrounding the values were trimmed, since it changes the length,
and a second trailer-less message is exchanged over the same connection
to make sure nothing was emitted after the trailers.

The trailers rejected by the H1 parser are checked to result in a 400 on
the request path and in a 502 on the response path: invalid token in the
field name, missing colon, pseudo-header, empty field name and a CR not
followed by an LF in the value. Note that each message must be emitted
at once, otherwise haproxy has already forwarded the beginning of the
message when the invalid trailers are parsed and can only abort the
stream instead of responding with an error.

reg-tests/http-messaging/http_transfer_encoding.vtc

index a04a5f76519e7d1ff3465668f2a2833b81607a3c..65088037fbb9d379df21038df6001e9ebce0b29c 100644 (file)
@@ -76,6 +76,54 @@ server s2 {
         -hdr "Transfer-Encoding: gzip"
 } -start
 
+# Valid trailers must be forwarded in both directions. vtest is not able to
+# parse HTTP/1 trailers, so they are read using "recv" and only the number of
+# bytes emitted by haproxy is verified. This is sufficient to prove that the
+# the spaces surrounding the values were trimmed, since it changes the length.
+# A second, trailer-less message is then exchanged over the same connection to
+# make sure nothing was emitted after the trailers.
+server s3 {
+    rxreqhdrs
+    expect req.url == "/tlr-1"
+    expect req.http.transfer-encoding == "chunked"
+    # "5\r\nhello\r\n0\r\nx-tlr1: value1\r\nx-tlr2: value2\r\n\r\n"
+    recv 47
+    send "HTTP/1.1 200 OK\r\n"
+    send "Transfer-Encoding: chunked\r\n\r\n"
+    send "5\r\nworld\r\n"
+    send "0\r\nx-tlr1: value1\r\nX-Tlr2: \tvalue2\t \r\n\r\n"
+
+    rxreq
+    expect req.url == "/tlr-2"
+    expect req.bodylen == 0
+    txresp
+} -start
+
+# Invalid response trailers must be rejected as a server error. Each response
+# is emitted at once so that haproxy sees the invalid trailers before it has
+# forwarded the response headers to the client, otherwise it can only abort
+# the stream.
+server s4 {
+    rxreq
+    send "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nx tlr: value\r\n\r\n"
+
+    accept
+    rxreq
+    send "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nx-tlr value\r\n\r\n"
+
+    accept
+    rxreq
+    send "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n:status: 200\r\n\r\n"
+
+    accept
+    rxreq
+    send "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n: value\r\n\r\n"
+
+    accept
+    rxreq
+    send "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nx-tlr: val\rue\r\n\r\n"
+} -start
+
 haproxy h1 -conf {
     global
     .if feature(THREAD)
@@ -95,6 +143,23 @@ haproxy h1 -conf {
     listen fe2
         bind "fd@${fe2}"
         server s2 ${s2_addr}:${s2_port}
+
+    listen fe3
+        bind "fd@${fe3}"
+        server s3 ${s3_addr}:${s3_port}
+
+    listen fe4
+        bind "fd@${fe4}"
+        server s4 ${s4_addr}:${s4_port}
+
+    # The request body is waited for so that the invalid trailers below are
+    # rejected before the request is forwarded anywhere. Note that each
+    # request must be emitted at once, otherwise haproxy can only abort the
+    # stream instead of responding with a 400.
+    listen fe5
+        bind "fd@${fe5}"
+        option http-buffer-request
+        http-request return status 200
 } -start
 
 client c1 -connect ${h1_fe1_sock} {
@@ -205,3 +270,72 @@ client c11 -connect ${h1_fe2_sock} {
     rxresp
     expect resp.status == 502
 } -run -repeat 4
+
+# valid trailers, forwarded in both directions
+client c12 -connect ${h1_fe3_sock} {
+    txreq -method POST -url "/tlr-1" -nolen \
+        -hdr "Transfer-Encoding: chunked" \
+        -body "5\r\nhello\r\n0\r\nx-tlr1: value1\r\nX-Tlr2: \tvalue2\t \r\n\r\n"
+    rxresphdrs
+    expect resp.status == 200
+    expect resp.http.transfer-encoding == "chunked"
+    # "5\r\nworld\r\n0\r\nx-tlr1: value1\r\nx-tlr2: value2\r\n\r\n"
+    recv 47
+
+    txreq -url "/tlr-2"
+    rxresp
+    expect resp.status == 200
+} -run
+
+# invalid response trailers : invalid token in the name, missing colon,
+# pseudo-header, empty name and bare CR in the value
+client c13 -connect ${h1_fe4_sock} {
+    txreq
+    rxresp
+    expect resp.status == 502
+} -run -repeat 5
+
+# a trailer name must be a valid token
+client c14 -connect ${h1_fe5_sock} {
+    txreq -method POST -nolen \
+        -hdr "Transfer-Encoding: chunked" \
+        -body "5\r\nhello\r\n0\r\nx tlr: value\r\n\r\n"
+    rxresp
+    expect resp.status == 400
+} -run
+
+# a trailer must have a colon
+client c15 -connect ${h1_fe5_sock} {
+    txreq -method POST -nolen \
+        -hdr "Transfer-Encoding: chunked" \
+        -body "5\r\nhello\r\n0\r\nx-tlr value\r\n\r\n"
+    rxresp
+    expect resp.status == 400
+} -run
+
+# pseudo-headers are not valid tokens either
+client c16 -connect ${h1_fe5_sock} {
+    txreq -method POST -nolen \
+        -hdr "Transfer-Encoding: chunked" \
+        -body "5\r\nhello\r\n0\r\n:method: GET\r\n\r\n"
+    rxresp
+    expect resp.status == 400
+} -run
+
+# a trailer name cannot be empty
+client c17 -connect ${h1_fe5_sock} {
+    txreq -method POST -nolen \
+        -hdr "Transfer-Encoding: chunked" \
+        -body "5\r\nhello\r\n0\r\n: value\r\n\r\n"
+    rxresp
+    expect resp.status == 400
+} -run
+
+# a CR not followed by an LF is forbidden in a trailer value
+client c18 -connect ${h1_fe5_sock} {
+    txreq -method POST -nolen \
+        -hdr "Transfer-Encoding: chunked" \
+        -body "5\r\nhello\r\n0\r\nx-tlr: val\rue\r\n\r\n"
+    rxresp
+    expect resp.status == 400
+} -run