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 | 21 +++++++++++++++++
reg-tests/converter/reverse.vtc | 41 +++++++++++++++++++++++++++++++++
src/sample.c | 24 +++++++++++++++++++
3 files changed, 86 insertions(+)
create mode 100644 reg-tests/converter/reverse.vtc
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 9601e4564..70a10f561 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -21075,6 +21075,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
@@ -22558,6 +22559,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
index 000000000..7b4d4ef63
--- /dev/null
+++ b/reg-tests/converter/reverse.vtc
@@ -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
diff --git a/src/sample.c b/src/sample.c
index 24891d457..19ca8caa6 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -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 },
--
2.54.0