From: Manu Nicolas Date: Mon, 27 Apr 2026 14:57:58 +0000 (+0000) Subject: MINOR: sample: add a generic reverse converter X-Git-Tag: v3.4-dev12~6 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f3fc68e3a20507fb4ce999ff9ff44b2bcf8e7a4c;p=thirdparty%2Fhaproxy.git MINOR: sample: add a generic reverse converter 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(.). --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 828e2cdad..32748c976 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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(,[,]) 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() 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 == "" +} -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 },