add base support for url encode following RFC3986, supporting `query`
type only.

- add test checking url_enc/url_dec/url_enc
- update documentation
- leave the door open for future changes

this should resolve github issue #941

Signed-off-by: William Dauchy <[email protected]>
---
 doc/configuration.txt           |  6 +++++
 reg-tests/converter/url_enc.vtc | 43 +++++++++++++++++++++++++++++++++
 src/http_conv.c                 | 31 ++++++++++++++++++++++++
 3 files changed, 80 insertions(+)
 create mode 100644 reg-tests/converter/url_enc.vtc

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 95b020c03..fd8c709fa 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -16230,6 +16230,12 @@ url_dec([<in_form>])
   space (' '). Otherwise this will only happen after a question mark indicating
   a query string ('?').
 
+url_enc([<enc_type>])
+  Takes a string provided as input and returns the encoded version as output.
+  The input and the output are of type string. By default the type of encoding
+  is meant for `query` type. There is no other type supported for now but the
+  optional argument is here for future changes.
+
 ungrpc(<field_number>,[<field_type>])
   This extracts the protocol buffers message field in raw mode of an input 
binary
   sample representation of a gRPC message with <field_number> as field number
diff --git a/reg-tests/converter/url_enc.vtc b/reg-tests/converter/url_enc.vtc
new file mode 100644
index 000000000..360f144c9
--- /dev/null
+++ b/reg-tests/converter/url_enc.vtc
@@ -0,0 +1,43 @@
+varnishtest "url_enc converter Test"
+
+#REQUIRE_VERSION=2.4
+
+feature ignore_unknown_macro
+
+server s1 {
+       rxreq
+       txresp
+} -repeat 2 -start
+
+haproxy h1 -conf {
+    defaults
+       mode http
+       timeout connect 1s
+       timeout client  1s
+       timeout server  1s
+
+    frontend fe
+       bind "fd@${fe}"
+
+       http-request set-var(txn.url0) "str(foo=bar+42 42 )"
+       http-request set-var(txn.url1) "var(txn.url0),url_enc"
+       http-request set-var(txn.url2) "var(txn.url1),url_dec"
+       http-request set-var(txn.url3) "var(txn.url2),url_enc"
+       http-response set-header url_enc0 "%[var(txn.url1)]"
+       http-response set-header url_dec "%[var(txn.url2)]"
+       http-response set-header url_enc1 "%[var(txn.url3)]"
+
+       default_backend be
+
+    backend be
+       server s1 ${s1_addr}:${s1_port}
+} -start
+
+client c1 -connect ${h1_fe_sock} {
+       txreq -url "/"
+       rxresp
+       expect resp.http.url_enc0 == "foo%3Dbar%2B42%2042%20"
+       expect resp.http.url_dec == "foo=bar+42 42 "
+       expect resp.http.url_enc1 == "foo%3Dbar%2B42%2042%20"
+       expect resp.status == 200
+} -run
diff --git a/src/http_conv.c b/src/http_conv.c
index 4afa6a2fd..b68de7aa4 100644
--- a/src/http_conv.c
+++ b/src/http_conv.c
@@ -268,6 +268,36 @@ static int sample_conv_url_dec(const struct arg *args, 
struct sample *smp, void
        return 1;
 }
 
+/* This fetch url-encode any input string. Only support query string for now */
+static int sample_conv_url_enc(const struct arg *args, struct sample *smp, void
+               *private)
+{
+       struct buffer *trash = get_trash_chunk();
+       long url_encode_map[(256 / 8) / sizeof(long)];
+       char *ret;
+       int i;
+
+       /* Add final \0 required by encode_string() */
+       smp->data.u.str.area[smp->data.u.str.data] = '\0';
+
+       /* use rfc3986 to determine list of characters to keep unchanged for
+        * query string */
+       memset(url_encode_map, 0, sizeof(url_encode_map));
+       for (i = 0; i < 256; i++) {
+               if (!((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z')
+                   || (i >= '0' && i <= '9') ||
+                   i == '-' || i == '.' || i == '_' || i == '~'))
+                       ha_bit_set(i, url_encode_map);
+       }
+       ret = encode_string(trash->area, trash->area + trash->size, '%',
+                           url_encode_map, smp->data.u.str.area);
+       if (ret == NULL || *ret != '\0')
+               return 0;
+       trash->data = strlen(trash->area);
+       smp->data.u.str = *trash;
+       return 1;
+}
+
 static int smp_conv_req_capture(const struct arg *args, struct sample *smp, 
void *private)
 {
        struct proxy *fe;
@@ -369,6 +399,7 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
        { "capture-req",    smp_conv_req_capture,     ARG1(1,SINT),     NULL,   
SMP_T_STR,  SMP_T_STR},
        { "capture-res",    smp_conv_res_capture,     ARG1(1,SINT),     NULL,   
SMP_T_STR,  SMP_T_STR},
        { "url_dec",        sample_conv_url_dec,      ARG1(0,SINT),     NULL,   
SMP_T_STR,  SMP_T_STR},
+       { "url_enc",        sample_conv_url_enc,      ARG1(0,SINT),     NULL,   
SMP_T_STR,  SMP_T_STR},
        { NULL, NULL, 0, 0, 0 },
 }};
 
-- 
2.29.2


Reply via email to