MINOR: http_act: Add -m flag for del-header name matching method

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
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 9dbe432..69da6be 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4687,9 +4687,13 @@
   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 @@
   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 @@
   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> ]
 
diff --git a/include/haproxy/action-t.h b/include/haproxy/action-t.h
index a002153..36aa5bb 100644
--- a/include/haproxy/action-t.h
+++ b/include/haproxy/action-t.h
@@ -78,7 +78,6 @@
 	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
index 0000000..32a7a70
--- /dev/null
+++ b/reg-tests/http-rules/del_header.vtc
@@ -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
diff --git a/src/http_act.c b/src/http_act.c
index 27db478..13a5c37 100644
--- a/src/http_act.c
+++ b/src/http_act.c
@@ -1438,20 +1438,67 @@
 	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 @@
 	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);
 
+	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;
+		}
+	}
+
-	LIST_INIT(&rule->arg.http.fmt);
 	*orig_arg = cur_arg + 1;
 	return ACT_RET_PRS_OK;
 }
diff --git a/src/http_ana.c b/src/http_ana.c
index 7a9cd0b..d89b7d5 100644
--- a/src/http_ana.c
+++ b/src/http_ana.c
@@ -2838,14 +2838,10 @@
 {
 	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 @@
 				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 @@
 {
 	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 @@
 				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))