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
--- /dev/null
+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
* 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;
- 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;
+ // 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 ((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;
}
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 },
{ "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 },