MEDIUM: sample: Enhances converter "bytes" to take variable names as arguments
Prior to this commit, converter "bytes" takes only integer values as arguments. After this commit, it can take variable names as inputs. This allows us to dynamically determine the offset/length and capture them in variables. These variables can then be used with the converter. Example use case: parsing a token present in a request header.
This commit is contained in:
parent
6601317b3b
commit
915e48675a
@ -18059,7 +18059,21 @@ bool
|
||||
bytes(<offset>[,<length>])
|
||||
Extracts some bytes from an input binary sample. The result is a binary
|
||||
sample starting at an offset (in bytes) of the original sample and
|
||||
optionally truncated at the given length.
|
||||
optionally truncated at the given length. <offset> and <length> can be numeric
|
||||
values or variable names. The converter returns an empty sample if either
|
||||
<offset> or <length> is invalid. Invalid <offset> means a negative value or a
|
||||
value >= length of the input sample. Invalid <length> means a negative value
|
||||
or, in some cases, a value bigger than the length of the input sample.
|
||||
|
||||
Example:
|
||||
http-request set-var(txn.input) req.hdr(input) # let's say input is "012345"
|
||||
|
||||
http-response set-header bytes_0 "%[var(txn.input),bytes(0)]" # outputs "012345"
|
||||
http-response set-header bytes_1_3 "%[var(txn.input),bytes(1,3)]" # outputs "123"
|
||||
|
||||
http-response set-var(txn.var_start) int(1)
|
||||
http-response set-var(txn.var_length) int(3)
|
||||
http-response set-header bytes_var1_var3 "%[var(txn.input),bytes(txn.var_start,txn.var_length)]" # outputs "123"
|
||||
|
||||
concat([<start>],[<var>],[<end>])
|
||||
Concatenates up to 3 fields after the current sample which is then turned to
|
||||
|
156
reg-tests/converter/bytes.vtc
Normal file
156
reg-tests/converter/bytes.vtc
Normal file
@ -0,0 +1,156 @@
|
||||
varnishtest "bytes converter Test"
|
||||
|
||||
feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(2.9-dev4)'"
|
||||
|
||||
feature ignore_unknown_macro
|
||||
|
||||
# TEST - 1
|
||||
server s1 {
|
||||
rxreq
|
||||
txresp -hdr "Connection: close"
|
||||
} -repeat 1 -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-response set-header bytes_0 "%[var(txn.input),bytes(0)]"
|
||||
http-response set-header bytes_1 "%[var(txn.input),bytes(1)]"
|
||||
http-response set-header bytes_0_3 "%[var(txn.input),bytes(0,3)]"
|
||||
http-response set-header bytes_1_3 "%[var(txn.input),bytes(1,3)]"
|
||||
http-response set-header bytes_99 "%[var(txn.input),bytes(99)]"
|
||||
http-response set-header bytes_5 "%[var(txn.input),bytes(5)]"
|
||||
http-response set-header bytes_6 "%[var(txn.input),bytes(6)]"
|
||||
http-response set-header bytes_0_6 "%[var(txn.input),bytes(0,6)]"
|
||||
http-response set-header bytes_0_7 "%[var(txn.input),bytes(0,7)]"
|
||||
|
||||
http-response set-var(txn.var_start) int(0)
|
||||
http-response set-header bytes_var0 "%[var(txn.input),bytes(txn.var_start)]"
|
||||
|
||||
http-response set-var(txn.var_start) int(1)
|
||||
http-response set-var(txn.var_length) int(3)
|
||||
http-response set-header bytes_var1_var3 "%[var(txn.input),bytes(txn.var_start,txn.var_length)]"
|
||||
|
||||
http-response set-var(txn.var_start) int(99)
|
||||
http-response set-header bytes_var99 "%[var(txn.input),bytes(txn.var_start)]"
|
||||
|
||||
http-response set-var(txn.var_start) int(0)
|
||||
http-response set-var(txn.var_length) int(7)
|
||||
http-response set-header bytes_var0_var7 "%[var(txn.input),bytes(txn.var_start,txn.var_length)]"
|
||||
|
||||
http-response set-var(txn.var_start) int(1)
|
||||
http-response set-var(txn.var_length) int(3)
|
||||
http-response set-header bytes_var1_3 "%[var(txn.input),bytes(txn.var_start,3)]"
|
||||
http-response set-header bytes_1_var3 "%[var(txn.input),bytes(1,txn.var_length)]"
|
||||
|
||||
http-response set-var(txn.var_start) int(-1)
|
||||
http-response set-var(txn.var_length) int(-1)
|
||||
http-response set-header bytes_varminus1 "%[var(txn.input),bytes(txn.var_start)]"
|
||||
http-response set-header bytes_0_varminus1 "%[var(txn.input),bytes(0,txn.var_length)]"
|
||||
|
||||
http-response set-header bytes_varNA "%[var(txn.input),bytes(txn.NA)]"
|
||||
http-response set-header bytes_1_varNA "%[var(txn.input),bytes(1,txn.NA)]"
|
||||
|
||||
default_backend be
|
||||
|
||||
backend be
|
||||
server s1 ${s1_addr}:${s1_port}
|
||||
} -start
|
||||
|
||||
client c1 -connect ${h1_fe_sock} {
|
||||
txreq -url "/" \
|
||||
-hdr "input: 012345"
|
||||
rxresp
|
||||
expect resp.status == 200
|
||||
expect resp.http.bytes_0 == "012345"
|
||||
expect resp.http.bytes_1 == "12345"
|
||||
expect resp.http.bytes_0_3 == "012"
|
||||
expect resp.http.bytes_1_3 == "123"
|
||||
expect resp.http.bytes_99 == ""
|
||||
expect resp.http.bytes_5 == "5"
|
||||
expect resp.http.bytes_6 == ""
|
||||
expect resp.http.bytes_0_6 == "012345"
|
||||
|
||||
# since specified length is > input length, response contains the input till the end
|
||||
expect resp.http.bytes_0_7 == "012345"
|
||||
|
||||
expect resp.http.bytes_var0 == "012345"
|
||||
expect resp.http.bytes_var1_var3 == "123"
|
||||
expect resp.http.bytes_var99 == ""
|
||||
expect resp.http.bytes_var0_var7 == "012345"
|
||||
expect resp.http.bytes_var1_3 == "123"
|
||||
expect resp.http.bytes_1_var3 == "123"
|
||||
expect resp.http.bytes_varminus1 == ""
|
||||
expect resp.http.bytes_0_varminus1 == ""
|
||||
expect resp.http.bytes_varNA == ""
|
||||
expect resp.http.bytes_1_varNA == ""
|
||||
} -run
|
||||
|
||||
# TEST - 2
|
||||
# negative starting index causes startup failure
|
||||
haproxy h2 -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}"
|
||||
|
||||
http-response set-header bytes_output "%[var(txn.input),bytes(-1)]"
|
||||
|
||||
default_backend be
|
||||
|
||||
backend be
|
||||
server s1 ${s1_addr}:${s1_port}
|
||||
} -start -expectexit 1
|
||||
|
||||
# TEST - 3
|
||||
# negative length causes startup failure
|
||||
haproxy h3 -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}"
|
||||
|
||||
http-response set-header bytes_output "%[var(txn.input),bytes(0,-1)]"
|
||||
|
||||
default_backend be
|
||||
|
||||
backend be
|
||||
server s1 ${s1_addr}:${s1_port}
|
||||
} -start -expectexit 1
|
||||
|
||||
# TEST - 4
|
||||
# 0 length causes startup failure
|
||||
haproxy h4 -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}"
|
||||
|
||||
http-response set-header bytes_output "%[var(txn.input),bytes(0,0)]"
|
||||
|
||||
default_backend be
|
||||
|
||||
backend be
|
||||
server s1 ${s1_addr}:${s1_port}
|
||||
} -start -expectexit 1
|
84
src/sample.c
84
src/sample.c
@ -2705,18 +2705,54 @@ static int sample_conv_json(const struct arg *arg_p, struct sample *smp, void *p
|
||||
* Optional second arg is the length to truncate */
|
||||
static int sample_conv_bytes(const struct arg *arg_p, struct sample *smp, void *private)
|
||||
{
|
||||
if (smp->data.u.str.data <= arg_p[0].data.sint) {
|
||||
struct sample smp_arg0, smp_arg1;
|
||||
long long start_idx, length;
|
||||
|
||||
// determine the start_idx and length of the output
|
||||
smp_set_owner(&smp_arg0, smp->px, smp->sess, smp->strm, smp->opt);
|
||||
if (!sample_conv_var2smp_sint(&arg_p[0], &smp_arg0)) {
|
||||
smp->data.u.str.data = 0;
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
if (smp_arg0.data.u.sint < 0 || (smp_arg0.data.u.sint >= smp->data.u.str.data)) {
|
||||
// empty output if the arg0 value is negative or >= the input length
|
||||
smp->data.u.str.data = 0;
|
||||
return 0;
|
||||
}
|
||||
start_idx = smp_arg0.data.u.sint;
|
||||
|
||||
// length comes from arg1 if present, otherwise it's the remaining length
|
||||
if (arg_p[1].type != ARGT_STOP) {
|
||||
smp_set_owner(&smp_arg1, smp->px, smp->sess, smp->strm, smp->opt);
|
||||
if (!sample_conv_var2smp_sint(&arg_p[1], &smp_arg1)) {
|
||||
smp->data.u.str.data = 0;
|
||||
return 0;
|
||||
}
|
||||
if (smp_arg1.data.u.sint < 0) {
|
||||
// empty output if the arg1 value is negative
|
||||
smp->data.u.str.data = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (smp_arg1.data.u.sint > (smp->data.u.str.data - start_idx)) {
|
||||
// arg1 value is greater than the remaining length
|
||||
if (smp->opt & SMP_OPT_FINAL) {
|
||||
// truncate to remaining length
|
||||
length = smp->data.u.str.data - start_idx;
|
||||
} else {
|
||||
smp->data.u.str.data = 0;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
length = smp_arg1.data.u.sint;
|
||||
}
|
||||
} else {
|
||||
length = smp->data.u.str.data - start_idx;
|
||||
}
|
||||
|
||||
if (smp->data.u.str.size)
|
||||
smp->data.u.str.size -= arg_p[0].data.sint;
|
||||
smp->data.u.str.data -= arg_p[0].data.sint;
|
||||
smp->data.u.str.area += arg_p[0].data.sint;
|
||||
|
||||
if ((arg_p[1].type == ARGT_SINT) && (arg_p[1].data.sint < smp->data.u.str.data))
|
||||
smp->data.u.str.data = arg_p[1].data.sint;
|
||||
// update the output using the start_idx and length
|
||||
smp->data.u.str.area += start_idx;
|
||||
smp->data.u.str.data = length;
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -4929,6 +4965,34 @@ static int smp_fetch_bytes(const struct arg *args, struct sample *smp, const cha
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int sample_conv_bytes_check(struct arg *args, struct sample_conv *conv,
|
||||
const char *file, int line, char **err)
|
||||
{
|
||||
// arg0 is not optional, must be >= 0
|
||||
if (!check_operator(&args[0], conv, file, line, err)) {
|
||||
return 0;
|
||||
}
|
||||
if (args[0].type != ARGT_VAR) {
|
||||
if (args[0].type != ARGT_SINT || args[0].data.sint < 0) {
|
||||
memprintf(err, "expects a non-negative integer");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// arg1 is optional, must be > 0
|
||||
if (args[1].type != ARGT_STOP) {
|
||||
if (!check_operator(&args[1], conv, file, line, err)) {
|
||||
return 0;
|
||||
}
|
||||
if (args[1].type != ARGT_VAR) {
|
||||
if (args[1].type != ARGT_SINT || args[1].data.sint <= 0) {
|
||||
memprintf(err, "expects a positive integer");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct sample_fetch_kw_list smp_logs_kws = {ILH, {
|
||||
{ "bytes_in", smp_fetch_bytes, 0, NULL, SMP_T_SINT, SMP_USE_INTRN },
|
||||
@ -5025,7 +5089,7 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
|
||||
{ "xxh32", sample_conv_xxh32, ARG1(0,SINT), NULL, SMP_T_BIN, SMP_T_SINT },
|
||||
{ "xxh64", sample_conv_xxh64, ARG1(0,SINT), NULL, SMP_T_BIN, SMP_T_SINT },
|
||||
{ "json", sample_conv_json, ARG1(1,STR), sample_conv_json_check, SMP_T_STR, SMP_T_STR },
|
||||
{ "bytes", sample_conv_bytes, ARG2(1,SINT,SINT), NULL, SMP_T_BIN, SMP_T_BIN },
|
||||
{ "bytes", sample_conv_bytes, ARG2(1,STR,STR), sample_conv_bytes_check, 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 },
|
||||
|
Loading…
Reference in New Issue
Block a user