MINOR: sample: add param converter

Add a converter that extracts a parameter from string of delimited
key/value pairs.

Fixes: #1697
This commit is contained in:
Thayne McCombs 2022-12-14 00:19:59 -07:00 committed by Willy Tarreau
parent 6b6f082969
commit 02cf4ecb5a
3 changed files with 170 additions and 0 deletions

View File

@ -17702,6 +17702,32 @@ or(<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 '_'.
param(<name>,[<delim>])
This extracts the first occurrence of the parameter <name> in the input string
where parameters are delimited by <delim>, which defaults to "&", and the name
and value of the parameter are separated by a "=". If there is no "=" and
value before the end of the parameter segment, it is treated as equivalent to
a value of an empty string.
This can be useful for extracting parameters from a query string, or possibly
a x-www-form-urlencoded body. In particular, `query,param(<name>)` can be used
as an alternative to `urlp(<name>)` which only uses "&" as a delimiter,
whereas "urlp" also uses "?" and ";".
Note that this converter doesn't do anything special with url encoded
characters. If you want to decode the value, you can use the url_dec converter
on the output. If the name of the parameter in the input might contain encoded
characters, you'll probably want do normalize the input before calling
"param". This can be done using "http-request normalize-uri", in particular
the percent-decode-unreserved and percent-to-uppercase options.
Example :
str(a=b&c=d&a=r),param(a) # b
str(a&b=c),param(a) # ""
str(a=&b&c=a),param(b) # ""
str(a=1;b=2;c=4),param(b,;) # 2
query,param(redirect_uri),urldec()
port_only
Converts a string which contains a Host header value into an integer by
returning its port.

View File

@ -0,0 +1,80 @@
varnishtest "param converter Test"
feature ignore_unknown_macro
server s1 {
rxreq
txresp -hdr "Connection: close"
} -repeat 10 -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.query) query
http-response set-header Found %[var(txn.query),param(test)] if { var(txn.query),param(test) -m found }
default_backend be
backend be
server s1 ${s1_addr}:${s1_port}
} -start
client c1 -connect ${h1_fe_sock} {
txreq -url "/foo/?test=1&b=4&d"
rxresp
expect resp.status == 200
expect resp.http.found == "1"
txreq -url "/?a=1&b=4&test=34"
rxresp
expect resp.status == 200
expect resp.http.found == "34"
txreq -url "/?test=bar"
rxresp
expect resp.status == 200
expect resp.http.found == "bar"
txreq -url "/?a=b&c=d"
rxresp
expect resp.status == 200
expect resp.http.found == "<undef>"
txreq -url "/?a=b&test=t&c=d"
rxresp
expect resp.status == 200
expect resp.http.found == "t"
txreq -url "/?a=b&test&c=d"
rxresp
expect resp.status == 200
expect resp.http.found == ""
txreq -url "/?test="
rxresp
expect resp.status == 200
expect resp.http.found == ""
txreq -url "/?a=b&test"
rxresp
expect resp.status == 200
expect resp.http.found == ""
txreq -url "/?testing=123"
rxresp
expect resp.status == 200
expect resp.http.found == "<undef>"
txreq -url "/?testing=123&test=4"
rxresp
expect resp.status == 200
expect resp.http.found == "4"
} -run

View File

@ -2607,6 +2607,69 @@ found:
return 1;
}
static int sample_conv_param_check(struct arg *arg, struct sample_conv *conv,
const char *file, int line, char **err)
{
if (arg[1].type == ARGT_STR && arg[1].data.str.data != 1) {
memprintf(err, "Delimiter must be exactly 1 character.");
return 0;
}
return 1;
}
static int sample_conv_param(const struct arg *arg_p, struct sample *smp, void *private)
{
char *pos, *end, *pend, *equal;
char delim = '&';
const char *name = arg_p[0].data.str.area;
size_t name_l = arg_p[0].data.str.data;
if (arg_p[1].type == ARGT_STR)
delim = *arg_p[1].data.str.area;
pos = smp->data.u.str.area;
end = pos + smp->data.u.str.data;
while (pos < end) {
equal = pos + name_l;
/* Parameter not found */
if (equal > end)
break;
if (equal == end || *equal == delim) {
if (memcmp(pos, name, name_l) == 0) {
/* input contains parameter, but no value is supplied */
smp->data.u.str.data = 0;
return 1;
}
pos = equal + 1;
continue;
}
if (*equal == '=' && memcmp(pos, name, name_l) == 0) {
pos = equal + 1;
pend = memchr(pos, delim, end - pos);
if (pend == NULL)
pend = end;
if (smp->data.u.str.size)
smp->data.u.str.size -= pos - smp->data.u.str.area;
smp->data.u.str.area = pos;
smp->data.u.str.data = pend - pos;
return 1;
}
/* find the next delimiter and set position to character after that */
pos = memchr(pos, delim, end - pos);
if (pos == NULL)
pos = end;
else
pos++;
}
/* Parameter not found */
smp->data.u.str.data = 0;
return 0;
}
static int sample_conv_regsub_check(struct arg *args, struct sample_conv *conv,
const char *file, int line, char **err)
{
@ -4399,6 +4462,7 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
{ "bytes", sample_conv_bytes, ARG2(1,SINT,SINT), NULL, SMP_T_BIN, SMP_T_BIN },
{ "field", sample_conv_field, ARG3(2,SINT,STR,SINT), sample_conv_field_check, SMP_T_STR, SMP_T_STR },
{ "word", sample_conv_word, ARG3(2,SINT,STR,SINT), sample_conv_field_check, SMP_T_STR, SMP_T_STR },
{ "param", sample_conv_param, ARG2(1,STR,STR), sample_conv_param_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 },
{ "strcmp", sample_conv_strcmp, ARG1(1,STR), smp_check_strcmp, SMP_T_STR, SMP_T_SINT },