]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: sample: add a generic reverse converter
authorManu Nicolas <e.nicolas@criteo.com>
Mon, 27 Apr 2026 14:57:58 +0000 (14:57 +0000)
committerWilly Tarreau <w@1wt.eu>
Wed, 13 May 2026 14:45:25 +0000 (16:45 +0200)
Some use cases benefit from reversing a string before passing it to other
converters or lookups. While reverse_dom addresses domain-specific label
reversal, a generic byte-wise string reversal remains useful on its own and can
also be combined with other converters such as concat().

A common lookup use case is turning a suffix match on the original string into
a prefix match on the reversed string. Prefix string matches use the
prefix-tree index (PAT_MATCH_BEG with pat_idx_tree_pfx), while end matches use
the string-list index (PAT_MATCH_END with pat_idx_list_str), so reversing
before map_beg can avoid linear suffix scans for large maps.

This patch adds a new string converter named "reverse". It reverses the input
string byte by byte and returns the resulting string unchanged otherwise. It
does not apply any domain-specific semantics or character-encoding semantics.

The documentation is updated and a reg-test is added to cover the basic
conversion as well as a simple composition with concat(.).

doc/configuration.txt
reg-tests/converter/reverse.vtc [new file with mode: 0644]
src/sample.c

index 828e2cdad206eb9615d3cdc27f2fce2b4064919d..32748c976812e63d9b0b84428c7f906049888cbf 100644 (file)
@@ -21145,6 +21145,7 @@ param(name[,delim])                                string       string
 port_only                                          string       integer
 protobuf(field_number[,field_type])                binary       binary
 regsub(regex,subst[,flags])                        string       string
+reverse                                            string       string
 rfc7239_field(field)                               string       string
 rfc7239_is_valid                                   string       boolean
 rfc7239_n2nn                                       string       address / str
@@ -22642,6 +22643,26 @@ regsub(<regex>,<subst>[,<flags>])
      http-request redirect location %[url,'regsub("(foo|bar)([0-9]+)?","\2\1",i)']
      http-request redirect location %[url,regsub(\"(foo|bar)([0-9]+)?\",\"\2\1\",i)]
 
+reverse
+  Reverses the input string byte by byte.
+
+  This converter is encoding-agnostic and reverses bytes, not characters; it is
+  not suitable for reversing human text encoded as UTF-8.
+
+  This can turn suffix lookups on the original string into prefix lookups on
+  the reversed string, allowing the use of indexed prefix matchers such as
+  "map_beg" on large maps.
+
+  Examples:
+    "example.com" -> "moc.elpmaxe"
+    "ab cd" -> "dc ba"
+
+    # Given a map file where each key contains a reversed hostname:
+    #   moc.elpmaxe.ppa app1
+    #   moc.elpmaxe.bd  dbcluster
+    # Pick a backend based on the domain suffix of the Host header:
+    use_backend %[req.hdr(host),lower,reverse,map_beg(/etc/haproxy/hosts.map,default)]
+
 rfc7239_field(<field>)
   Extracts a single field/parameter from RFC 7239 compliant header value input.
 
diff --git a/reg-tests/converter/reverse.vtc b/reg-tests/converter/reverse.vtc
new file mode 100644 (file)
index 0000000..7b4d4ef
--- /dev/null
@@ -0,0 +1,41 @@
+varnishtest "reverse converter test"
+
+feature ignore_unknown_macro
+
+server s1 {
+       rxreq
+       txresp -hdr "Connection: close"
+} -repeat 4 -start
+
+haproxy h1 -conf {
+    global
+    .if feature(THREAD)
+        thread-groups 1
+    .endif
+
+    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 return status 200 hdr X-Reverse "%[str(example.com),reverse]" hdr X-Reverse2 "%[str(ab cd),reverse]" hdr X-Reverse3 "%[str(example.com),reverse,concat(.)]" hdr X-Reverse4 "%[str(),reverse]"
+
+       default_backend be
+
+    backend be
+       server s1 ${s1_addr}:${s1_port}
+} -start
+
+client c1 -connect ${h1_fe_sock} {
+       txreq -url "/"
+       rxresp
+       expect resp.status == 200
+       expect resp.http.x-reverse == "moc.elpmaxe"
+       expect resp.http.x-reverse2 == "dc ba"
+       expect resp.http.x-reverse3 == "moc.elpmaxe."
+       expect resp.http.x-reverse4 == "<undef>"
+} -run
index 24891d457ded23b5562f476e3df73554a021a5df..19ca8caa61eb8a1d62290bfabd470c175d3f3d15 100644 (file)
@@ -2311,6 +2311,29 @@ static int sample_conv_str2upper(const struct arg *arg_p, struct sample *smp, vo
        return 1;
 }
 
+/* Reverses the input string byte by byte. */
+static int sample_conv_reverse(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       const char *input = smp->data.u.str.area;
+       struct buffer *trash;
+       int input_len = smp->data.u.str.data;
+       int i;
+
+       trash = get_trash_chunk_sz(input_len + 1);
+       if (!trash)
+               return 0;
+
+       for (i = 0; i < input_len; i++)
+               trash->area[i] = input[input_len - 1 - i];
+
+       trash->area[input_len] = 0;
+       trash->data = input_len;
+       smp->data.u.str = *trash;
+       smp->data.type = SMP_T_STR;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
 /* takes the IPv4 mask in args[0] and an optional IPv6 mask in args[1] */
 static int sample_conv_ipmask(const struct arg *args, struct sample *smp, void *private)
 {
@@ -5777,6 +5800,7 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
        { "strcmp",  sample_conv_strcmp,       ARG1(1,STR),           smp_check_strcmp,         SMP_T_STR,  SMP_T_SINT },
        { "host_only", sample_conv_host_only,  0,                     NULL,                     SMP_T_STR,  SMP_T_STR  },
        { "port_only", sample_conv_port_only,  0,                     NULL,                     SMP_T_STR,  SMP_T_SINT },
+       { "reverse",     sample_conv_reverse,     0,                  NULL,                     SMP_T_STR,  SMP_T_STR  },
 
        /* gRPC converters. */
        { "ungrpc", sample_conv_ungrpc,    ARG2(1,PBUF_FNUM,STR), sample_conv_protobuf_check, SMP_T_BIN, SMP_T_BIN  },