MINOR: sample: converter: Add add_item convertor

This new converter is similar to the concat converter and can be used to
build new variables made of a succession of other variables but the main
difference is that it does the checks if adding a delimiter makes sense as
wouldn't be the case if e.g the current input sample is empty. That
situation would require 2 separate rules using concat converter where the
first rule would have to check if the current sample string is empty before
adding a delimiter. This resolves GitHub Issue #1621.
This commit is contained in:
Nikola Sale 2022-04-03 18:11:53 +02:00 committed by Willy Tarreau
parent 34107800dd
commit 0dbf03871f
3 changed files with 178 additions and 1 deletions

View File

@ -16308,6 +16308,35 @@ add(<value>)
This prefix is followed by a name. The separator is a '.'. The name may only
contain characters 'a-z', 'A-Z', '0-9', '.' and '_'.
add_item(<delim>,[<var>][,<suff>]])
Concatenates a minimum of 2 and up to 3 fields after the current sample which
is then turned into a string. The first one, <delim>, is a constant string,
that will be appended immediately after the existing sample if an existing
sample is not empty and either the <var> or the <suff> is not empty. The
second one, <var>, is a variable name. The variable will be looked up, its
contents converted to a string, and it will be appended immediately after
the <delim> part. If the variable is not found, nothing is appended. It is
optional and may optionally be followed by a constant string <suff>, however
if <var> is omitted, then <suff> is mandatory. This converter is similar to
the concat converter and can be used to build new variables made of a
succession of other variables but the main difference is that it does the
checks if adding a delimiter makes sense as wouldn't be the case if e.g. the
current sample is empty. That situation would require 2 separate rules using
concat converter where the first rule would have to check if the current
sample string is empty before adding a delimiter. If commas or closing
parenthesis are needed as delimiters, they must be protected by quotes or
backslashes, themselves protected so that they are not stripped by the first
level parser. See examples below.
Example:
http-request set-var(req.tagged) 'var(req.tagged),add_item(",",req.score1,"(site1)") if src,in_table(site1)'
http-request set-var(req.tagged) 'var(req.tagged),add_item(",",req.score2,"(site2)") if src,in_table(site2)'
http-request set-var(req.tagged) 'var(req.tagged),add_item(",",req.score3,"(site3)") if src,in_table(site3)'
http-request set-header x-tagged %[var(req.tagged)]
http-request set-var(req.tagged) 'var(req.tagged),add_item(",",req.score1),add_item(",",req.score2)'
http-request set-var(req.tagged) 'var(req.tagged),add_item(",",,(site1))' if src,in_table(site1)
aes_gcm_dec(<bits>,<nonce>,<key>,<aead_tag>)
Decrypts the raw byte input using the AES128-GCM, AES192-GCM or
AES256-GCM algorithm, depending on the <bits> parameter. All other parameters

View File

@ -0,0 +1,50 @@
varnishtest "be2dec converter Test"
feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(2.6-dev0)'"
feature ignore_unknown_macro
server s1 {
rxreq
txresp
} -repeat 3 -start
haproxy h1 -conf {
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}"
#### requests
http-request set-var(txn.input) req.hdr(input)
http-request set-var(txn.var) str("var_content")
http-response set-header add_item-1 "%[var(txn.input),add_item(',',txn.var,_suff_)]"
http-response set-header add_item-2 "%[var(txn.input),add_item(',',txn.var)]"
http-response set-header add_item-3 "%[var(txn.input),add_item(',',,_suff_)]"
default_backend be
backend be
server s1 ${s1_addr}:${s1_port}
} -start
client c1 -connect ${h1_fe_sock} {
txreq -url "/" \
-hdr "input:"
rxresp
expect resp.status == 200
expect resp.http.add_item-1 == "var_content_suff_"
expect resp.http.add_item-2 == "var_content"
expect resp.http.add_item-3 == "_suff_"
txreq -url "/" \
-hdr "input: input_string"
rxresp
expect resp.status == 200
expect resp.http.add_item-1 == "input_string,var_content_suff_"
expect resp.http.add_item-2 == "input_string,var_content"
expect resp.http.add_item-3 == "input_string,_suff_"
} -run

View File

@ -3122,6 +3122,103 @@ static int smp_check_concat(struct arg *args, struct sample_conv *conv,
return 1;
}
/* Append delimiter (only to a non empty input) followed by the optional
* variable contents concatenated with the optional sufix.
*/
static int sample_conv_add_item(const struct arg *arg_p, struct sample *smp, void *private)
{
struct buffer *tmpbuf;
struct sample tmp;
size_t max;
int var_available;
tmpbuf = alloc_trash_chunk();
if (!tmpbuf)
return 0;
tmpbuf->data = smp->data.u.str.data;
if (tmpbuf->data > tmpbuf->size - 1)
tmpbuf->data = tmpbuf->size - 1;
memcpy(tmpbuf->area, smp->data.u.str.area, tmpbuf->data);
tmpbuf->area[tmpbuf->data] = 0;
/* Check if variable is found and we can turn into a string. */
var_available = 0;
smp_set_owner(&tmp, smp->px, smp->sess, smp->strm, smp->opt);
if (arg_p[1].type == ARGT_VAR && vars_get_by_desc(&arg_p[1].data.var, &tmp, NULL) &&
(sample_casts[tmp.data.type][SMP_T_STR] == c_none ||
sample_casts[tmp.data.type][SMP_T_STR](&tmp)))
var_available = 1;
/* Append delimiter only if input is not empty and either
* the variable or the suffix are not empty
*/
if (smp->data.u.str.data && ((var_available && tmp.data.u.str.data) ||
arg_p[2].data.str.data)) {
max = arg_p[0].data.str.data;
if (max > tmpbuf->size - 1 - tmpbuf->data)
max = tmpbuf->size - 1 - tmpbuf->data;
if (max) {
memcpy(tmpbuf->area + tmpbuf->data, arg_p[0].data.str.area, max);
tmpbuf->data += max;
tmpbuf->area[tmpbuf->data] = 0;
}
}
/* Append variable contents if variable is found and turned into string. */
if (var_available) {
max = tmp.data.u.str.data;
if (max > tmpbuf->size - 1 - tmpbuf->data)
max = tmpbuf->size - 1 - tmpbuf->data;
if (max) {
memcpy(tmpbuf->area + tmpbuf->data, tmp.data.u.str.area, max);
tmpbuf->data += max;
tmpbuf->area[tmpbuf->data] = 0;
}
}
/* Append optional suffix. */
max = arg_p[2].data.str.data;
if (max > tmpbuf->size - 1 - tmpbuf->data)
max = tmpbuf->size - 1 - tmpbuf->data;
if (max) {
memcpy(tmpbuf->area + tmpbuf->data, arg_p[2].data.str.area, max);
tmpbuf->data += max;
tmpbuf->area[tmpbuf->data] = 0;
}
smp->data.u.str = *tmpbuf;
smp->data.type = SMP_T_STR;
smp_dup(smp);
free_trash_chunk(tmpbuf);
return 1;
}
/* Check the "add_item" converter's arguments and extracts the
* variable name and its scope.
*/
static int smp_check_add_item(struct arg *args, struct sample_conv *conv,
const char *file, int line, char **err)
{
/* Try to decode a variable. */
if (args[1].data.str.data > 0 && !vars_check_arg(&args[1], NULL)) {
memprintf(err, "failed to register variable name '%s'",
args[1].data.str.area);
return 0;
}
if (args[1].data.str.data == 0 && args[2].data.str.data == 0) {
memprintf(err, "one of the optional arguments has to be nonempty");
return 0;
}
return 1;
}
/* Compares string with a variable containing a string. Return value
* is compatible with strcmp(3)'s return value.
*/
@ -4206,9 +4303,11 @@ INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
/* Note: must not be declared <const> as its list will be overwritten */
static struct sample_conv_kw_list sample_conv_kws = {ILH, {
{ "add_item",sample_conv_add_item, ARG3(2,STR,STR,STR), smp_check_add_item, SMP_T_STR, SMP_T_STR },
{ "debug", sample_conv_debug, ARG2(0,STR,STR), smp_check_debug, SMP_T_ANY, SMP_T_ANY },
{ "b64dec", sample_conv_base642bin, 0, NULL, SMP_T_STR, SMP_T_BIN },
{ "base64", sample_conv_bin2base64, 0, NULL, SMP_T_BIN, SMP_T_STR },
{ "concat", sample_conv_concat, ARG3(1,STR,STR,STR), smp_check_concat, SMP_T_STR, SMP_T_STR },
{ "ub64enc", sample_conv_bin2base64url,0, NULL, SMP_T_BIN, SMP_T_STR },
{ "ub64dec", sample_conv_base64url2bin,0, NULL, SMP_T_STR, SMP_T_BIN },
{ "upper", sample_conv_str2upper, 0, NULL, SMP_T_STR, SMP_T_STR },
@ -4235,7 +4334,6 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
{ "word", sample_conv_word, ARG3(2,SINT,STR,SINT), sample_conv_field_check, SMP_T_STR, SMP_T_STR },
{ "regsub", sample_conv_regsub, ARG3(2,REG,STR,STR), sample_conv_regsub_check, SMP_T_STR, SMP_T_STR },
{ "sha1", sample_conv_sha1, 0, NULL, SMP_T_BIN, SMP_T_BIN },
{ "concat", sample_conv_concat, ARG3(1,STR,STR,STR), smp_check_concat, SMP_T_STR, SMP_T_STR },
{ "strcmp", sample_conv_strcmp, ARG1(1,STR), smp_check_strcmp, SMP_T_STR, SMP_T_SINT },
/* gRPC converters. */