]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: http_act: Add -m flag for del-header name matching method
authorMaciej Zdeb <maciej@zdeb.pl>
Fri, 20 Nov 2020 13:58:48 +0000 (13:58 +0000)
committerWilly Tarreau <w@1wt.eu>
Sat, 21 Nov 2020 14:54:30 +0000 (15:54 +0100)
This patch adds -m flag which allows to specify header name
matching method when deleting headers from http request/response.
Currently beg, end, sub, str and reg are supported.

This is related to GitHub issue #909

doc/configuration.txt
include/haproxy/action-t.h
reg-tests/http-rules/del_header.vtc [new file with mode: 0644]
src/http_act.c
src/http_ana.c

index 9dbe432084d89761606dd924347678b8433caf98..69da6bee24ecfbb63729054f6838e82f2e6cc7f9 100644 (file)
@@ -4687,9 +4687,13 @@ http-after-response allow [ { if | unless } <condition> ]
   This stops the evaluation of the rules and lets the response pass the check.
   No further "http-after-response" rules are evaluated.
 
-http-after-response del-header <name> [ { if | unless } <condition> ]
+http-after-response del-header <name> [ -m <meth> ] [ { if | unless } <condition> ]
 
-  This removes all HTTP header fields whose name is specified in <name>.
+  This removes all HTTP header fields whose name is specified in <name>. <meth>
+  is the matching method, applied on the header name. Supported matching methods
+  are "str" (exact match), "beg" (prefix match), "end" (suffix match), "sub"
+  (substring match) and "reg" (regex match). If not specified, exact matching
+  method is used.
 
 http-after-response replace-header <name> <regex-match> <replace-fmt>
                                    [ { if | unless } <condition> ]
@@ -5461,9 +5465,13 @@ http-request del-acl(<file-name>) <key fmt> [ { if | unless } <condition> ]
   It is the equivalent of the "del acl" command from the stats socket, but can
   be triggered by an HTTP request.
 
-http-request del-header <name> [ { if | unless } <condition> ]
+http-request del-header <name> [ -m <meth> ] [ { if | unless } <condition> ]
 
-  This removes all HTTP header fields whose name is specified in <name>.
+  This removes all HTTP header fields whose name is specified in <name>. <meth>
+  is the matching method, applied on the header name. Supported matching methods
+  are "str" (exact match), "beg" (prefix match), "end" (suffix match), "sub"
+  (substring match) and "reg" (regex match). If not specified, exact matching
+  method is used.
 
 http-request del-map(<file-name>) <key fmt> [ { if | unless } <condition> ]
 
@@ -6274,9 +6282,13 @@ http-response del-acl(<file-name>) <key fmt> [ { if | unless } <condition> ]
   It is the equivalent of the "del acl" command from the stats socket, but can
   be triggered by an HTTP response.
 
-http-response del-header <name> [ { if | unless } <condition> ]
+http-response del-header <name> [ -m <meth> ] [ { if | unless } <condition> ]
 
-  This removes all HTTP header fields whose name is specified in <name>.
+  This removes all HTTP header fields whose name is specified in <name>. <meth>
+  is the matching method, applied on the header name. Supported matching methods
+  are "str" (exact match), "beg" (prefix match), "end" (suffix match), "sub"
+  (substring match) and "reg" (regex match). If not specified, exact matching
+  method is used.
 
 http-response del-map(<file-name>) <key fmt> [ { if | unless } <condition> ]
 
index a002153d708d3f5ace7e45dbeab21e14f81147b5..36aa5bbdcecc394783aea91a45aebded34bf0db3 100644 (file)
@@ -78,7 +78,6 @@ enum act_name {
        ACT_ACTION_DENY,
 
        /* common http actions .*/
-       ACT_HTTP_DEL_HDR,
        ACT_HTTP_REDIR,
        ACT_HTTP_SET_NICE,
        ACT_HTTP_SET_LOGL,
diff --git a/reg-tests/http-rules/del_header.vtc b/reg-tests/http-rules/del_header.vtc
new file mode 100644 (file)
index 0000000..32a7a70
--- /dev/null
@@ -0,0 +1,93 @@
+varnishtest "del-header tests"
+
+# This config tests various http-request/response del-header operations
+# with or without specified header name matching method.
+
+feature ignore_unknown_macro
+
+server s1 {
+    rxreq
+    expect req.url           == /
+    expect req.http.x-always == always
+    expect req.http.x-str1   == <undef>
+    expect req.http.x-str2   == <undef>
+    expect req.http.x-beg1   == <undef>
+    expect req.http.x-beg2   == <undef>
+    expect req.http.x-end1   == <undef>
+    expect req.http.x-end2   == end2
+    expect req.http.x-sub1   == <undef>
+    expect req.http.x-sub2   == <undef>
+    expect req.http.x-reg1   == <undef>
+    expect req.http.x-reg2   == <undef>
+    txresp -hdr "x-always: always" \
+        -hdr "x-str1: str1" \
+        -hdr "x-str2: str2" \
+        -hdr "x-beg1: beg1" \
+        -hdr "x-beg2: beg2" \
+        -hdr "x-end1: end1" \
+        -hdr "x-end2: end2" \
+        -hdr "x-sub1: sub1" \
+        -hdr "x-sub2: sub2" \
+        -hdr "x-reg1: reg1" \
+        -hdr "x-reg2: reg2"
+
+} -start
+
+haproxy h1 -conf {
+    defaults
+        mode http
+        timeout connect 1s
+        timeout client  1s
+        timeout server  1s
+
+    frontend fe
+        bind "fd@${fe}"
+
+        http-request del-header x-str1
+        http-request del-header x-str2 -m str
+        http-request del-header x-beg -m beg
+        http-request del-header end1 -m end
+        http-request del-header sub -m sub
+        http-request del-header ^x.reg.$ -m reg
+
+        http-response del-header x-str1
+        http-response del-header x-str2 -m str
+        http-response del-header x-beg -m beg
+        http-response del-header end1 -m end
+        http-response del-header sub -m sub
+        http-response del-header ^x.reg.$ -m reg
+
+        default_backend be
+
+    backend be
+        server s1 ${s1_addr}:${s1_port}
+
+} -start
+
+client c1 -connect ${h1_fe_sock} {
+    txreq -req GET -url / \
+        -hdr "x-always: always" \
+        -hdr "x-str1: str1" \
+        -hdr "x-str2: str2" \
+        -hdr "x-beg1: beg1" \
+        -hdr "x-beg2: beg2" \
+        -hdr "x-end1: end1" \
+        -hdr "x-end2: end2" \
+        -hdr "x-sub1: sub1" \
+        -hdr "x-sub2: sub2" \
+        -hdr "x-reg1: reg1" \
+        -hdr "x-reg2: reg2"
+    rxresp
+    expect resp.status        == 200
+    expect resp.http.x-always == always
+    expect resp.http.x-str1   == <undef>
+    expect resp.http.x-str2   == <undef>
+    expect resp.http.x-beg1   == <undef>
+    expect resp.http.x-beg2   == <undef>
+    expect resp.http.x-end1   == <undef>
+    expect resp.http.x-end2   == end2
+    expect resp.http.x-sub1   == <undef>
+    expect resp.http.x-sub2   == <undef>
+    expect resp.http.x-reg1   == <undef>
+    expect resp.http.x-reg2   == <undef>
+} -run
index 27db478335550f183daf0de83f433a7229f6705d..13a5c370da861c78f21037186808ee16325ab45a 100644 (file)
@@ -1438,20 +1438,67 @@ static enum act_parse_ret parse_http_replace_header(const char **args, int *orig
        return ACT_RET_PRS_OK;
 }
 
-/* Parse a "del-header" action. It takes an header name as argument. It returns
- * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
+/* This function executes a del-header action with selected matching mode for
+ * header name. It finds the matching method to be performed in <.action>, previously
+ * filled by function parse_http_del_header(). On success, it returns ACT_RET_CONT.
+ * Otherwise ACT_RET_ERR is returned.
+ */
+static enum act_return http_action_del_header(struct act_rule *rule, struct proxy *px,
+                                                 struct session *sess, struct stream *s, int flags)
+{
+       struct http_hdr_ctx ctx;
+       struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
+       struct htx *htx = htxbuf(&msg->chn->buf);
+       enum act_return ret = ACT_RET_CONT;
+
+       /* remove all occurrences of the header */
+       ctx.blk = NULL;
+       switch (rule->action) {
+       case PAT_MATCH_STR:
+               while (http_find_header(htx, rule->arg.http.str, &ctx, 1))
+                       http_remove_header(htx, &ctx);
+               break;
+       case PAT_MATCH_BEG:
+               while (http_find_pfx_header(htx, rule->arg.http.str, &ctx, 1))
+                       http_remove_header(htx, &ctx);
+               break;
+       case PAT_MATCH_END:
+               while (http_find_sfx_header(htx, rule->arg.http.str, &ctx, 1))
+                       http_remove_header(htx, &ctx);
+               break;
+       case PAT_MATCH_SUB:
+               while (http_find_sub_header(htx, rule->arg.http.str, &ctx, 1))
+                       http_remove_header(htx, &ctx);
+               break;
+       case PAT_MATCH_REG:
+               while (http_match_header(htx, rule->arg.http.re, &ctx, 1))
+                       http_remove_header(htx, &ctx);
+               break;
+       default:
+               return ACT_RET_ERR;
+       }
+       return ret;
+}
+
+/* Parse a "del-header" action. It takes string as a required argument,
+ * optional flag (currently only -m) and optional matching method of input string
+ * with header name to be deleted. Default matching method is exact match (-m str).
+ * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
  */
 static enum act_parse_ret parse_http_del_header(const char **args, int *orig_arg, struct proxy *px,
                                                struct act_rule *rule, char **err)
 {
        int cur_arg;
+       int pat_idx;
 
-       rule->action = ACT_HTTP_DEL_HDR;
+       /* set exact matching (-m str) as default */
+       rule->action = PAT_MATCH_STR;
+       rule->action_ptr = http_action_del_header;
        rule->release_ptr = release_http_action;
 
        cur_arg = *orig_arg;
        if (!*args[cur_arg]) {
-               memprintf(err, "expects exactly 1 arguments");
+               memprintf(err, "expects at least 1 argument");
                return ACT_RET_PRS_ERR;
        }
 
@@ -1459,7 +1506,32 @@ static enum act_parse_ret parse_http_del_header(const char **args, int *orig_arg
        rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
        px->conf.args.ctx = (rule->from == ACT_F_HTTP_REQ ? ARGC_HRQ : ARGC_HRS);
 
-       LIST_INIT(&rule->arg.http.fmt);
+       if (strcmp(args[cur_arg+1], "-m") == 0) {
+               cur_arg++;
+               if (!*args[cur_arg+1]) {
+                       memprintf(err, "-m flag expects exactly 1 argument");
+                       return ACT_RET_PRS_ERR;
+               }
+
+               cur_arg++;
+               pat_idx = pat_find_match_name(args[cur_arg]);
+               switch (pat_idx) {
+               case PAT_MATCH_REG:
+                       if (!(rule->arg.http.re = regex_comp(rule->arg.http.str.ptr, 1, 1, err)))
+                               return ACT_RET_PRS_ERR;
+                       /* fall through */
+               case PAT_MATCH_STR:
+               case PAT_MATCH_BEG:
+               case PAT_MATCH_END:
+               case PAT_MATCH_SUB:
+                       rule->action = pat_idx;
+                       break;
+               default:
+                       memprintf(err, "-m with unsupported matching method '%s'", args[cur_arg]);
+                       return ACT_RET_PRS_ERR;
+               }
+       }
+
        *orig_arg = cur_arg + 1;
        return ACT_RET_PRS_OK;
 }
index 7a9cd0bbcc7c38a1bcb36943824b023b933aea8c..d89b7d5d7ca83f445b3b85a44bb281b89c103cb4 100644 (file)
@@ -2838,14 +2838,10 @@ static enum rule_result http_req_get_intercept_rule(struct proxy *px, struct lis
 {
        struct session *sess = strm_sess(s);
        struct http_txn *txn = s->txn;
-       struct htx *htx;
        struct act_rule *rule;
-       struct http_hdr_ctx ctx;
        enum rule_result rule_ret = HTTP_RULE_RES_CONT;
        int act_opts = 0;
 
-       htx = htxbuf(&s->req.buf);
-
        /* If "the current_rule_list" match the executed rule list, we are in
         * resume condition. If a resume is needed it is always in the action
         * and never in the ACL or converters. In this case, we initialise the
@@ -2958,13 +2954,6 @@ static enum rule_result http_req_get_intercept_rule(struct proxy *px, struct lis
                                s->logs.level = rule->arg.http.i;
                                break;
 
-                       case ACT_HTTP_DEL_HDR:
-                               /* remove all occurrences of the header */
-                               ctx.blk = NULL;
-                               while (http_find_header(htx, rule->arg.http.str, &ctx, 1))
-                                       http_remove_header(htx, &ctx);
-                               break;
-
                        /* other flags exists, but normally, they never be matched. */
                        default:
                                break;
@@ -2994,14 +2983,10 @@ static enum rule_result http_res_get_intercept_rule(struct proxy *px, struct lis
 {
        struct session *sess = strm_sess(s);
        struct http_txn *txn = s->txn;
-       struct htx *htx;
        struct act_rule *rule;
-       struct http_hdr_ctx ctx;
        enum rule_result rule_ret = HTTP_RULE_RES_CONT;
        int act_opts = 0;
 
-       htx = htxbuf(&s->res.buf);
-
        /* If "the current_rule_list" match the executed rule list, we are in
         * resume condition. If a resume is needed it is always in the action
         * and never in the ACL or converters. In this case, we initialise the
@@ -3102,13 +3087,6 @@ resume_execution:
                                s->logs.level = rule->arg.http.i;
                                break;
 
-                       case ACT_HTTP_DEL_HDR:
-                               /* remove all occurrences of the header */
-                               ctx.blk = NULL;
-                               while (http_find_header(htx, rule->arg.http.str, &ctx, 1))
-                                       http_remove_header(htx, &ctx);
-                               break;
-
                        case ACT_HTTP_REDIR:
                                rule_ret = HTTP_RULE_RES_ABRT;
                                if (!http_apply_redirect_rule(rule->arg.redir, s, txn))