MEDIUM: http: ACL and MAP updates through http-(request|response) rules

This patch allows manipulation of ACL and MAP content thanks to any
information available in a session: source IP address, HTTP request or
response header, etc...

It's an update "on the fly" of the content  of the map/acls. This means
it does not resist to reload or restart of HAProxy.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 6bb5ffd..84c4fb5 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2848,7 +2848,12 @@
 http-request { allow | deny | tarpit | auth [realm <realm>] | redirect <rule> |
               add-header <name> <fmt> | set-header <name> <fmt> |
               del-header <name> | set-nice <nice> | set-log-level <level> |
-              set-tos <tos> | set-mark <mark> }
+              set-tos <tos> | set-mark <mark> |
+              add-acl(<file name>) <key fmt> |
+              del-acl(<file name>) <key fmt> |
+              del-map(<file name>) <key fmt> |
+              set-map(<file name>) <key fmt> <value fmt>
+             }
              [ { if | unless } <condition> ]
   Access control for Layer 7 requests
 
@@ -2944,6 +2949,39 @@
       downloads). This works on Linux kernels 2.6.32 and above and requires
       admin privileges.
 
+    - "add-acl" is used to add a new entry into an ACL. The ACL must be loaded
+      from a file (even a dummy empty file). The file name of the ACL to be
+      updated is passed between parentheses. It takes one argument: <key fmt>,
+      which follows log-format rules, to collect content of the new entry. It
+      performs a lookup in the ACL before insertion, to avoid duplicated (or
+      more) values. This lookup is done by a linear search and can be expensive
+      with large lists! It is the equivalent of the "add acl" command from the
+      stats socket, but can be triggered by an HTTP request.
+
+    - "del-acl" is used to delete an entry from an ACL. The ACL must be loaded
+      from a file (even a dummy empty file). The file name of the ACL to be
+      updated is passed between parentheses. It takes one argument: <key fmt>,
+      which follows log-format rules, to collect content of the entry to delete.
+      It is the equivalent of the "del acl" command from the stats socket, but
+      can be triggered by an HTTP request.
+
+    - "del-map" is used to delete an entry from a MAP. The MAP must be loaded
+      from a file (even a dummy empty file). The file name of the MAP to be
+      updated is passed between parentheses. It takes one argument: <key fmt>,
+      which follows log-format rules, to collect content of the entry to delete.
+      It takes one argument: "file name" It is the equivalent of the "del map"
+      command from the stats socket, but can be triggered by an HTTP request.
+
+    - "set-map" is used to add a new entry into a MAP. The MAP must be loaded
+      from a file (even a dummy empty file). The file name of the MAP to be
+      updated is passed between parentheses. It takes 2 arguments: <key fmt>,
+      which follows log-format rules, used to collect MAP key, and <value fmt>,
+      which follows log-format rules, used to collect content for the new entry.
+      It performs a lookup in the MAP before insertion, to avoid duplicated (or
+      more) values. This lookup is done by a linear search and can be expensive
+      with large lists! It is the equivalent of the "set map" command from the
+      stats socket, but can be triggered by an HTTP request.
+
   There is no limit to the number of http-request statements per instance.
 
   It is important to know that http-request rules are processed very early in
@@ -2976,12 +3014,37 @@
         http-request set-header X-SSL-Client-NotBefore %{+Q}[ssl_c_notbefore]
         http-request set-header X-SSL-Client-NotAfter  %{+Q}[ssl_c_notafter]
 
+  Example:
+        acl key req.hdr(X-Add-Acl-Key) -m found
+        acl add path /addacl
+        acl del path /delacl
+
+        acl myhost hdr(Host) -f myhost.lst
+
+        http-request add-acl(myhost.lst) %[req.hdr(X-Add-Acl-Key)] if key add
+        http-request del-acl(myhost.lst) %[req.hdr(X-Add-Acl-Key)] if key del
+
+  Example:
+        acl value  req.hdr(X-Value) -m found
+        acl setmap path /setmap
+        acl delmap path /delmap
+
+        use_backend bk_appli if { hdr(Host),map_str(map.lst) -m found }
+
+        http-request set-map(map.lst) %[src] %[req.hdr(X-Value)] if setmap value
+        http-request del-map(map.lst) %[src]                     if delmap
+
   See also : "stats http-request", section 3.4 about userlists and section 7
              about ACL usage.
 
 http-response { allow | deny | add-header <name> <fmt> | set-nice <nice> |
                 set-header <name> <fmt> | del-header <name> |
-                set-log-level <level> | set-mark <mark> | set-tos <tos> }
+                set-log-level <level> | set-mark <mark> | set-tos <tos> |
+                add-acl(<file name>) <key fmt> |
+                del-acl(<file name>) <key fmt> |
+                del-map(<file name>) <key fmt> |
+                set-map(<file name>) <key fmt> <value fmt>
+              }
               [ { if | unless } <condition> ]
   Access control for Layer 7 responses
 
@@ -3054,6 +3117,39 @@
       downloads). This works on Linux kernels 2.6.32 and above and requires
       admin privileges.
 
+    - "add-acl" is used to add a new entry into an ACL. The ACL must be loaded
+      from a file (even a dummy empty file). The file name of the ACL to be
+      updated is passed between parentheses. It takes one argument: <key fmt>,
+      which follows log-format rules, to collect content of the new entry. It
+      performs a lookup in the ACL before insertion, to avoid duplicated (or
+      more) values. This lookup is done by a linear search and can be expensive
+      with large lists! It is the equivalent of the "add acl" command from the
+      stats socket, but can be triggered by an HTTP response.
+
+    - "del-acl" is used to delete an entry from an ACL. The ACL must be loaded
+      from a file (even a dummy empty file). The file name of the ACL to be
+      updated is passed between parentheses. It takes one argument: <key fmt>,
+      which follows log-format rules, to collect content of the entry to delete.
+      It is the equivalent of the "del acl" command from the stats socket, but
+      can be triggered by an HTTP response.
+
+    - "del-map" is used to delete an entry from a MAP. The MAP must be loaded
+      from a file (even a dummy empty file). The file name of the MAP to be
+      updated is passed between parentheses. It takes one argument: <key fmt>,
+      which follows log-format rules, to collect content of the entry to delete.
+      It takes one argument: "file name" It is the equivalent of the "del map"
+      command from the stats socket, but can be triggered by an HTTP response.
+
+    - "set-map" is used to add a new entry into a MAP. The MAP must be loaded
+      from a file (even a dummy empty file). The file name of the MAP to be
+      updated is passed between parentheses. It takes 2 arguments: <key fmt>,
+      which follows log-format rules, used to collect MAP key, and <value fmt>,
+      which follows log-format rules, used to collect content for the new entry.
+      It performs a lookup in the MAP before insertion, to avoid duplicated (or
+      more) values. This lookup is done by a linear search and can be expensive
+      with large lists! It is the equivalent of the "set map" command from the
+      stats socket, but can be triggered by an HTTP response.
+
   There is no limit to the number of http-response statements per instance.
 
   It is important to know that http-response rules are processed very early in
@@ -3061,6 +3157,22 @@
   added by "add-header"/"set-header" are visible by almost all further ACL
   rules.
 
+  Example:
+         acl key_acl res.hdr(X-Acl-Key) -m found
+
+         acl myhost hdr(Host) -f myhost.lst
+
+         http-response add-acl(myhost.lst) %[res.hdr(X-Acl-Key)] if key_acl
+         http-response del-acl(myhost.lst) %[res.hdr(X-Acl-Key)] if key_acl
+
+  Example:
+         acl value  res.hdr(X-Value) -m found
+
+         use_backend bk_appli if { hdr(Host),map_str(map.lst) -m found }
+
+         http-response set-map(map.lst) %[src] %[res.hdr(X-Value)] if value
+         http-response del-map(map.lst) %[src]                     if ! value
+
   See also : "http-request", section 3.4 about userlists and section 7 about
              ACL usage.
 
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index d937ce1..f084ecd 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -252,6 +252,10 @@
 	HTTP_REQ_ACT_SET_LOGL,
 	HTTP_REQ_ACT_SET_TOS,
 	HTTP_REQ_ACT_SET_MARK,
+	HTTP_REQ_ACT_ADD_ACL,
+	HTTP_REQ_ACT_DEL_ACL,
+	HTTP_REQ_ACT_DEL_MAP,
+	HTTP_REQ_ACT_SET_MAP,
 	HTTP_REQ_ACT_MAX /* must always be last */
 };
 
@@ -267,6 +271,10 @@
 	HTTP_RES_ACT_SET_LOGL,
 	HTTP_RES_ACT_SET_TOS,
 	HTTP_RES_ACT_SET_MARK,
+	HTTP_RES_ACT_ADD_ACL,
+	HTTP_RES_ACT_DEL_ACL,
+	HTTP_RES_ACT_DEL_MAP,
+	HTTP_RES_ACT_SET_MAP,
 	HTTP_RES_ACT_MAX /* must always be last */
 };
 
@@ -404,6 +412,11 @@
 		int loglevel;                  /* log-level value for HTTP_REQ_ACT_SET_LOGL */
 		int tos;                       /* tos value for HTTP_REQ_ACT_SET_TOS */
 		int mark;                      /* nfmark value for HTTP_REQ_ACT_SET_MARK */
+		struct {
+			char *ref;             /* MAP or ACL file name to update */
+			struct list key;       /* pattern to retrieve MAP or ACL key */
+			struct list value;     /* pattern to retrieve MAP value */
+		} map;
 	} arg;                                 /* arguments used by some actions */
 };
 
@@ -421,6 +434,11 @@
 		int loglevel;                  /* log-level value for HTTP_RES_ACT_SET_LOGL */
 		int tos;                       /* tos value for HTTP_RES_ACT_SET_TOS */
 		int mark;                      /* nfmark value for HTTP_RES_ACT_SET_MARK */
+		struct {
+			char *ref;             /* MAP or ACL file name to update */
+			struct list key;       /* pattern to retrieve MAP or ACL key */
+			struct list value;     /* pattern to retrieve MAP value */
+		} map;
 	} arg;                                 /* arguments used by some actions */
 };
 
diff --git a/src/proto_http.c b/src/proto_http.c
index 3675b85..e9004f8 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -64,6 +64,7 @@
 #include <proto/session.h>
 #include <proto/stream_interface.h>
 #include <proto/task.h>
+#include <proto/pattern.h>
 
 const char HTTP_100[] =
 	"HTTP/1.1 100 Continue\r\n\r\n";
@@ -3204,6 +3205,90 @@
 			trash.len += build_logline(s, trash.str + trash.len, trash.size - trash.len, &rule->arg.hdr_add.fmt);
 			http_header_add_tail2(&txn->req, &txn->hdr_idx, trash.str, trash.len);
 			break;
+
+		case HTTP_REQ_ACT_DEL_ACL:
+		case HTTP_REQ_ACT_DEL_MAP: {
+			struct pat_ref *ref;
+			char *key;
+			int len;
+
+			/* collect reference */
+			ref = pat_ref_lookup(rule->arg.map.ref);
+			if (!ref)
+				continue;
+
+			/* collect key */
+			len = build_logline(s, trash.str, trash.size, &rule->arg.map.key);
+			key = trash.str;
+			key[len] = '\0';
+
+			/* perform update */
+			/* returned code: 1=ok, 0=ko */
+			pat_ref_delete(ref, key);
+
+			break;
+			}
+
+		case HTTP_REQ_ACT_ADD_ACL: {
+			struct pat_ref *ref;
+			char *key;
+			struct chunk *trash_key;
+			int len;
+
+			trash_key = get_trash_chunk();
+
+			/* collect reference */
+			ref = pat_ref_lookup(rule->arg.map.ref);
+			if (!ref)
+				continue;
+
+			/* collect key */
+			len = build_logline(s, trash_key->str, trash_key->size, &rule->arg.map.key);
+			key = trash_key->str;
+			key[len] = '\0';
+
+			/* perform update */
+			/* add entry only if it does not already exist */
+			if (pat_ref_find_elt(ref, key) == NULL)
+				pat_ref_add(ref, key, NULL, NULL);
+
+			break;
+			}
+
+		case HTTP_REQ_ACT_SET_MAP: {
+			struct pat_ref *ref;
+			char *key, *value;
+			struct chunk *trash_key, *trash_value;
+			int len;
+
+			trash_key = get_trash_chunk();
+			trash_value = get_trash_chunk();
+
+			/* collect reference */
+			ref = pat_ref_lookup(rule->arg.map.ref);
+			if (!ref)
+				continue;
+
+			/* collect key */
+			len = build_logline(s, trash_key->str, trash_key->size, &rule->arg.map.key);
+			key = trash_key->str;
+			key[len] = '\0';
+
+			/* collect value */
+			len = build_logline(s, trash_value->str, trash_value->size, &rule->arg.map.value);
+			value = trash_value->str;
+			value[len] = '\0';
+
+			/* perform update */
+			if (pat_ref_find_elt(ref, key) != NULL)
+				/* update entry if it exists */
+				pat_ref_set(ref, key, value, NULL);
+			else
+				/* insert a new entry */
+				pat_ref_add(ref, key, value, NULL);
+
+			break;
+			}
 		}
 	}
 
@@ -3293,6 +3378,90 @@
 			trash.len += build_logline(s, trash.str + trash.len, trash.size - trash.len, &rule->arg.hdr_add.fmt);
 			http_header_add_tail2(&txn->rsp, &txn->hdr_idx, trash.str, trash.len);
 			break;
+
+		case HTTP_RES_ACT_DEL_ACL:
+		case HTTP_RES_ACT_DEL_MAP: {
+			struct pat_ref *ref;
+			char *key;
+			int len;
+
+			/* collect reference */
+			ref = pat_ref_lookup(rule->arg.map.ref);
+			if (!ref)
+				continue;
+
+			/* collect key */
+			len = build_logline(s, trash.str, trash.size, &rule->arg.map.key);
+			key = trash.str;
+			key[len] = '\0';
+
+			/* perform update */
+			/* returned code: 1=ok, 0=ko */
+			pat_ref_delete(ref, key);
+
+			break;
+			}
+
+		case HTTP_RES_ACT_ADD_ACL: {
+			struct pat_ref *ref;
+			char *key;
+			struct chunk *trash_key;
+			int len;
+
+			trash_key = get_trash_chunk();
+
+			/* collect reference */
+			ref = pat_ref_lookup(rule->arg.map.ref);
+			if (!ref)
+				continue;
+
+			/* collect key */
+			len = build_logline(s, trash_key->str, trash_key->size, &rule->arg.map.key);
+			key = trash_key->str;
+			key[len] = '\0';
+
+			/* perform update */
+			/* check if the entry already exists */
+			if (pat_ref_find_elt(ref, key) == NULL)
+				pat_ref_add(ref, key, NULL, NULL);
+
+			break;
+			}
+
+		case HTTP_RES_ACT_SET_MAP: {
+			struct pat_ref *ref;
+			char *key, *value;
+			struct chunk *trash_key, *trash_value;
+			int len;
+
+			trash_key = get_trash_chunk();
+			trash_value = get_trash_chunk();
+
+			/* collect reference */
+			ref = pat_ref_lookup(rule->arg.map.ref);
+			if (!ref)
+				continue;
+
+			/* collect key */
+			len = build_logline(s, trash_key->str, trash_key->size, &rule->arg.map.key);
+			key = trash_key->str;
+			key[len] = '\0';
+
+			/* collect value */
+			len = build_logline(s, trash_value->str, trash_value->size, &rule->arg.map.value);
+			value = trash_value->str;
+			value[len] = '\0';
+
+			/* perform update */
+			if (pat_ref_find_elt(ref, key) != NULL)
+				/* update entry if it exists */
+				pat_ref_set(ref, key, value, NULL);
+			else
+				/* insert a new entry */
+				pat_ref_add(ref, key, value, NULL);
+
+			break;
+			}
 		}
 	}
 
@@ -8652,8 +8821,125 @@
 		redir->cond = NULL;
 		cur_arg = 2;
 		return rule;
+	} else if (strncmp(args[0], "add-acl", 7) == 0) {
+		/* http-request add-acl(<reference (acl name)>) <key pattern> */
+		rule->action = HTTP_REQ_ACT_ADD_ACL;
+		/*
+		 * '+ 8' for 'add-acl('
+		 * '- 9' for 'add-acl(' + trailing ')'
+		 */
+		rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9);
+
+		cur_arg = 1;
+
+		if (!*args[cur_arg] ||
+		    (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) {
+			Alert("parsing [%s:%d]: 'http-request %s' expects exactly 1 argument.\n",
+			      file, linenum, args[0]);
+			goto out_err;
+		}
+
+		LIST_INIT(&rule->arg.map.key);
+		proxy->conf.args.ctx = ARGC_HRQ;
+		parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP,
+			(proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR,
+			file, linenum);
+		free(proxy->conf.lfs_file);
+		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
+		proxy->conf.lfs_line = proxy->conf.args.line;
+		cur_arg += 1;
+	} else if (strncmp(args[0], "del-acl", 7) == 0) {
+		/* http-request del-acl(<reference (acl name)>) <key pattern> */
+		rule->action = HTTP_REQ_ACT_DEL_ACL;
+		/*
+		 * '+ 8' for 'del-acl('
+		 * '- 9' for 'del-acl(' + trailing ')'
+		 */
+		rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9);
+
+		cur_arg = 1;
+
+		if (!*args[cur_arg] ||
+		    (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) {
+			Alert("parsing [%s:%d]: 'http-request %s' expects exactly 1 argument.\n",
+			      file, linenum, args[0]);
+			goto out_err;
+		}
+
+		LIST_INIT(&rule->arg.map.key);
+		proxy->conf.args.ctx = ARGC_HRQ;
+		parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP,
+			(proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR,
+			file, linenum);
+		free(proxy->conf.lfs_file);
+		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
+		proxy->conf.lfs_line = proxy->conf.args.line;
+		cur_arg += 1;
+	} else if (strncmp(args[0], "del-map", 7) == 0) {
+		/* http-request del-map(<reference (map name)>) <key pattern> */
+		rule->action = HTTP_REQ_ACT_DEL_MAP;
+		/*
+		 * '+ 8' for 'del-map('
+		 * '- 9' for 'del-map(' + trailing ')'
+		 */
+		rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9);
+
+		cur_arg = 1;
+
+		if (!*args[cur_arg] ||
+		    (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) {
+			Alert("parsing [%s:%d]: 'http-request %s' expects exactly 1 argument.\n",
+			      file, linenum, args[0]);
+			goto out_err;
+		}
+
+		LIST_INIT(&rule->arg.map.key);
+		proxy->conf.args.ctx = ARGC_HRQ;
+		parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP,
+			(proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR,
+			file, linenum);
+		free(proxy->conf.lfs_file);
+		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
+		proxy->conf.lfs_line = proxy->conf.args.line;
+		cur_arg += 1;
+	} else if (strncmp(args[0], "set-map", 7) == 0) {
+		/* http-request set-map(<reference (map name)>) <key pattern> <value pattern> */
+		rule->action = HTTP_REQ_ACT_SET_MAP;
+		/*
+		 * '+ 8' for 'set-map('
+		 * '- 9' for 'set-map(' + trailing ')'
+		 */
+		rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9);
+
+		cur_arg = 1;
+
+		if (!*args[cur_arg] || !*args[cur_arg+1] ||
+		    (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
+			Alert("parsing [%s:%d]: 'http-request %s' expects exactly 2 arguments.\n",
+			      file, linenum, args[0]);
+			goto out_err;
+		}
+
+		LIST_INIT(&rule->arg.map.key);
+		LIST_INIT(&rule->arg.map.value);
+		proxy->conf.args.ctx = ARGC_HRQ;
+
+		/* key pattern */
+		parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP,
+			(proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR,
+			file, linenum);
+
+		/* value pattern */
+		parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.map.value, LOG_OPT_HTTP,
+			(proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR,
+			file, linenum);
+		free(proxy->conf.lfs_file);
+		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
+		proxy->conf.lfs_line = proxy->conf.args.line;
+
+		cur_arg += 2;
 	} else {
-		Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', 'redirect', 'tarpit', 'add-header', 'set-header', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', but got '%s'%s.\n",
+		Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', 'redirect', 'tarpit', 'add-header', 'set-header', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map', but got '%s'%s.\n",
 		      file, linenum, args[0], *args[0] ? "" : " (missing argument)");
 		goto out_err;
 	}
@@ -8819,13 +9105,133 @@
 		rule->arg.hdr_add.name = strdup(args[cur_arg]);
 		rule->arg.hdr_add.name_len = strlen(rule->arg.hdr_add.name);
 
+		proxy->conf.args.ctx = ARGC_HRS;
+		free(proxy->conf.lfs_file);
+		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
+		proxy->conf.lfs_line = proxy->conf.args.line;
+		cur_arg += 1;
+	} else if (strncmp(args[0], "add-acl", 7) == 0) {
+		/* http-request add-acl(<reference (acl name)>) <key pattern> */
+		rule->action = HTTP_RES_ACT_ADD_ACL;
+		/*
+		 * '+ 8' for 'add-acl('
+		 * '- 9' for 'add-acl(' + trailing ')'
+		 */
+		rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9);
+
+		cur_arg = 1;
+
+		if (!*args[cur_arg] ||
+		    (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) {
+			Alert("parsing [%s:%d]: 'http-response %s' expects exactly 1 argument.\n",
+			      file, linenum, args[0]);
+			goto out_err;
+		}
+
+		LIST_INIT(&rule->arg.map.key);
 		proxy->conf.args.ctx = ARGC_HRS;
+		parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP,
+			(proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR,
+			file, linenum);
 		free(proxy->conf.lfs_file);
 		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
 		proxy->conf.lfs_line = proxy->conf.args.line;
+
 		cur_arg += 1;
+	} else if (strncmp(args[0], "del-acl", 7) == 0) {
+		/* http-response del-acl(<reference (acl name)>) <key pattern> */
+		rule->action = HTTP_RES_ACT_DEL_ACL;
+		/*
+		 * '+ 8' for 'del-acl('
+		 * '- 9' for 'del-acl(' + trailing ')'
+		 */
+		rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9);
+
+		cur_arg = 1;
+
+		if (!*args[cur_arg] ||
+		    (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) {
+			Alert("parsing [%s:%d]: 'http-response %s' expects exactly 1 argument.\n",
+			      file, linenum, args[0]);
+			goto out_err;
+		}
+
+		LIST_INIT(&rule->arg.map.key);
+		proxy->conf.args.ctx = ARGC_HRS;
+		parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP,
+			(proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR,
+			file, linenum);
+		free(proxy->conf.lfs_file);
+		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
+		proxy->conf.lfs_line = proxy->conf.args.line;
+		cur_arg += 1;
+	} else if (strncmp(args[0], "del-map", 7) == 0) {
+		/* http-response del-map(<reference (map name)>) <key pattern> */
+		rule->action = HTTP_RES_ACT_DEL_MAP;
+		/*
+		 * '+ 8' for 'del-map('
+		 * '- 9' for 'del-map(' + trailing ')'
+		 */
+		rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9);
+
+		cur_arg = 1;
+
+		if (!*args[cur_arg] ||
+		    (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) {
+			Alert("parsing [%s:%d]: 'http-response %s' expects exactly 1 argument.\n",
+			      file, linenum, args[0]);
+			goto out_err;
+		}
+
+		LIST_INIT(&rule->arg.map.key);
+		proxy->conf.args.ctx = ARGC_HRS;
+		parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP,
+			(proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR,
+			file, linenum);
+		free(proxy->conf.lfs_file);
+		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
+		proxy->conf.lfs_line = proxy->conf.args.line;
+		cur_arg += 1;
+	} else if (strncmp(args[0], "set-map", 7) == 0) {
+		/* http-response set-map(<reference (map name)>) <key pattern> <value pattern> */
+		rule->action = HTTP_RES_ACT_SET_MAP;
+		/*
+		 * '+ 8' for 'set-map('
+		 * '- 9' for 'set-map(' + trailing ')'
+		 */
+		rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9);
+
+		cur_arg = 1;
+
+		if (!*args[cur_arg] || !*args[cur_arg+1] ||
+		    (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
+			Alert("parsing [%s:%d]: 'http-response %s' expects exactly 2 arguments.\n",
+			      file, linenum, args[0]);
+			goto out_err;
+		}
+
+		LIST_INIT(&rule->arg.map.key);
+		LIST_INIT(&rule->arg.map.value);
+
+		proxy->conf.args.ctx = ARGC_HRS;
+
+		/* key pattern */
+		parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP,
+			(proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR,
+			file, linenum);
+
+		/* value pattern */
+		parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.map.value, LOG_OPT_HTTP,
+			(proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR,
+			file, linenum);
+
+		free(proxy->conf.lfs_file);
+		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
+		proxy->conf.lfs_line = proxy->conf.args.line;
+
+		cur_arg += 2;
 	} else {
-		Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', 'add-header', 'set-header', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', but got '%s'%s.\n",
+		Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', 'add-header', 'del-header', 'set-header', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', 'del-acl', 'add-acl', 'del-map', 'set-map', but got '%s'%s.\n",
 		      file, linenum, args[0], *args[0] ? "" : " (missing argument)");
 		goto out_err;
 	}