]> git.kaiwu.me - haproxy.git/commitdiff
REGTESTS: converter: add a test for the string trimming and parsing converters
authorWilly Tarreau <w@1wt.eu>
Mon, 21 Sep 2026 20:04:11 +0000 (20:04 +0000)
committerWilly Tarreau <w@1wt.eu>
Wed, 30 Sep 2026 14:26:01 +0000 (16:26 +0200)
The ltrim, rtrim, cut_crlf, base2, port_only and language converters had
no coverage at all, and host_only was only used incidentally by other
tests. They are all checked here, including the corner cases of a string
entirely made of trimmed characters, of a host value without a port, of
an IPv6 host, and of an accept-language header matching nothing with and
without a default value. The "q=" test is also improved to test "q=0".

The input of cut_crlf comes from an URL-encoded query string parameter
since a header field may carry neither a CR nor a LF.

reg-tests/converter/strings.vtc [new file with mode: 0644]

diff --git a/reg-tests/converter/strings.vtc b/reg-tests/converter/strings.vtc
new file mode 100644 (file)
index 0000000..dbfd9dd
--- /dev/null
@@ -0,0 +1,116 @@
+varnishtest "Test the string trimming, splitting and formatting converters"
+
+# This reg-test covers a few string converters which had no coverage so
+# far: ltrim, rtrim, cut_crlf, base2, port_only and language, plus
+# host_only which is only indirectly used elsewhere. The input of
+# cut_crlf is built from an URL-encoded query string parameter since a
+# header field may not carry a CR nor a LF.
+
+feature ignore_unknown_macro
+
+haproxy h1 -conf {
+    global
+    .if feature(THREAD)
+        thread-groups 1
+    .endif
+        chroot /
+
+    defaults
+       mode http
+       timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+       timeout client  "${HAPROXY_TEST_TIMEOUT-5s}"
+       timeout server  "${HAPROXY_TEST_TIMEOUT-5s}"
+
+    frontend fe
+       bind "fd@${fe}"
+
+       http-request set-var(txn.raw) url_param(s),url_dec
+       # request headers are not reachable from http-after-response rules
+       http-request set-var(txn.lang) req.fhdr(accept-language),language(es;fr;en)
+       http-request set-var(txn.lang_def) req.fhdr(accept-language),language(de;it,none)
+       http-request return status 200
+
+       http-after-response set-header ltrim     "%[str(xyhelloxy),ltrim(xy)]"
+       http-after-response set-header ltrim-non "%[str(hello),ltrim(xy)]"
+       http-after-response set-header ltrim-all "%[str(xyxy),ltrim(xy)]"
+       http-after-response set-header rtrim     "%[str(xyhelloxy),rtrim(xy)]"
+       http-after-response set-header rtrim-non "%[str(hello),rtrim(xy)]"
+       http-after-response set-header trim      "%[str(--hello--),ltrim(-),rtrim(-)]"
+       http-after-response set-header cut       "%[var(txn.raw),cut_crlf]"
+       http-after-response set-header cut-len   "%[var(txn.raw),cut_crlf,length]"
+       http-after-response set-header base2     "%[str(A),base2]"
+       http-after-response set-header base2-2   "%[str(Az),base2]"
+       http-after-response set-header host      "%[str(Example.COM:8080),host_only]"
+       http-after-response set-header host-v6   "%[str([::1]:80),host_only]"
+       http-after-response set-header port      "%[str(example.com:8080),port_only]"
+       http-after-response set-header port-none "%[str(example.com),port_only]"
+       http-after-response set-header port-v6   "%[str([::1]:80),port_only]"
+       http-after-response set-header lang      "%[var(txn.lang)]"
+       http-after-response set-header lang-def  "%[var(txn.lang_def)]"
+} -start
+
+client c1 -connect ${h1_fe_sock} {
+       # "hello%0Aworld" once decoded holds an LF in the middle
+       txreq -url "/?s=hello%0Aworld"
+       rxresp
+       expect resp.status == 200
+       expect resp.http.ltrim     == "helloxy"
+       expect resp.http.ltrim-non == "hello"
+       expect resp.http.ltrim-all == ""
+       expect resp.http.rtrim     == "xyhello"
+       expect resp.http.rtrim-non == "hello"
+       expect resp.http.trim      == "hello"
+       expect resp.http.cut       == "hello"
+       expect resp.http.cut-len   == "5"
+       expect resp.http.base2     == "01000001"
+       expect resp.http.base2-2   == "0100000101111010"
+       expect resp.http.host      == "example.com"
+       expect resp.http.host-v6   == "[::1]"
+       expect resp.http.port      == "8080"
+       expect resp.http.port-none == "0"
+       expect resp.http.port-v6   == "80"
+       # no accept-language header at all: the fetch fails before language()
+       expect resp.http.lang      == ""
+       expect resp.http.lang-def  == ""
+
+       # a CR is cut as well as a LF
+       txreq -url "/?s=first%0Dsecond"
+       rxresp
+       expect resp.status == 200
+       expect resp.http.cut       == "first"
+
+       # nothing to cut, the string is left unmodified
+       txreq -url "/?s=intact"
+       rxresp
+       expect resp.status == 200
+       expect resp.http.cut       == "intact"
+} -run
+
+client c2 -connect ${h1_fe_sock} {
+       # the highest q-factor among the values listed in the argument wins
+       txreq -url "/?s=x" -hdr "accept-language: fr;q=0.2, en;q=0.8, de"
+       rxresp
+       expect resp.status == 200
+       expect resp.http.lang     == "en"
+       expect resp.http.lang-def == "de"
+
+       # a variant is matched on its base language when not listed
+       txreq -url "/?s=x" -hdr "accept-language: es-AR"
+       rxresp
+       expect resp.status == 200
+       expect resp.http.lang == "es"
+
+       # a null q-factor drops the value, including when it is spelled with
+       # no decimal part at all
+       txreq -url "/?s=x" -hdr "accept-language: fr;q=0, en;q=0.1"
+       rxresp
+       expect resp.status == 200
+       expect resp.http.lang == "en"
+
+       # nothing in the list matches, only the form with a default emits something
+       txreq -url "/?s=x" -hdr "accept-language: ja"
+       rxresp
+       expect resp.status == 200
+       expect resp.http.lang     == ""
+       expect resp.http.lang-def == "none"
+} -run