MEDIUM: http-rules: Rely on http reply for http deny/tarpit rules

"http-request deny", "http-request tarpit" and "http-response deny" rules now
use the same syntax than http return rules and internally rely on the http
replies. The behaviour is not the same when no argument is specified (or only
the status code). For http replies, a dummy response is produced, with no
payload. For old deny/tarpit rules, the proxy's error messages are used. Thus,
to be compatible with existing configuration, the "default-errorfiles" parameter
is implied. For instance :

  http-request deny deny_status 404

is now an alias of

  http-request deny status 404 default-errorfiles
diff --git a/doc/configuration.txt b/doc/configuration.txt
index e434a47..cd75de5 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4991,18 +4991,22 @@
   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.
 
-http-request deny [deny_status <status>] [ { errorfile | errorfiles } <err> ]
-                           [ { if | unless } <condition> ]
+http-request deny [deny_status <status>] [ { if | unless } <condition> ]
+http-request deny [ { status | deny_status } <code>] [content-type <type>]
+          [ { default-errorfiles | errorfile <file> | errorfiles <name> |
+              file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
+          [ hdr <name> <fmt> ]*
+          [ { if | unless } <condition> ]
 
-  This stops the evaluation of the rules and immediately rejects the request
-  and emits an HTTP 403 error, or optionally the status code specified as an
-  argument to "deny_status". The list of permitted status codes is limited to
-  those that can be overridden by the "errorfile" directive. A specific error
-  message may be specified. It may be an error file, using the "errorfile"
-  keyword followed by the file containing the full HTTP response. It may also
-  be an error from an http-errors section, using the "errorfiles" keyword
-  followed by the section name.
+  This stops the evaluation of the rules and immediately rejects the request.
+  By default an HTTP 403 error is returned. But the response may be customized
+  using same syntax than "http-request return" rules. Thus, see "http-request
+  return" for details. For compatiblity purpose, when no argument is defined,
+  or only "deny_status", the argument "default-errorfiles" is implied. It means
+  "http-request deny [deny_status <status>]" is an alias of
+  "http-request deny [status <status>] default-errorfiles".
   No further "http-request" rules are evaluated.
+  See also "http-request return".
 
 http-request disable-l7-retry [ { if | unless } <condition> ]
   This disables any attempt to retry the request if it fails for any other
@@ -5576,26 +5580,32 @@
   the frontend, the default mode is restored when HAProxy starts the backend
   rules evaluation.
 
-http-request tarpit [deny_status <status>] [ { errorfile | errorfiles } <err> ]
-                           [ { if | unless } <condition> ]
+http-request tarpit [deny_status <status>] [ { if | unless } <condition> ]
+http-request tarpit [ { status | deny_status } <code>] [content-type <type>]
+          [ { default-errorfiles | errorfile <file> | errorfiles <name> |
+              file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
+          [ hdr <name> <fmt> ]*
+          [ { if | unless } <condition> ]
 
   This stops the evaluation of the rules and immediately blocks the request
   without responding for a delay specified by "timeout tarpit" or
   "timeout connect" if the former is not set. After that delay, if the client
-  is still connected, an HTTP error 500 (or optionally the status code
-  specified as an argument to "deny_status") is returned so that the client
-  does not suspect it has been tarpitted. Logs will report the flags "PT".
-  The goal of the tarpit rule is to slow down robots during an attack when
-  they're limited on the number of concurrent requests. It can be very
-  efficient against very dumb robots, and will significantly reduce the load
-  on firewalls compared to a "deny" rule. But when facing "correctly"
-  developed robots, it can make things worse by forcing haproxy and the front
-  firewall to support insane number of concurrent connections. A specific error
-  message may be specified. It may be an error file, using the "errorfile"
-  keyword followed by the file containing the full HTTP response. It may also
-  be an error from an http-errors section, using the "errorfiles" keyword
-  followed by the section name.
-  See also the "silent-drop" action.
+  is still connected, a response is returned so that the client does not
+  suspect it has been tarpitted. Logs will report the flags "PT". The goal of
+  the tarpit rule is to slow down robots during an attack when they're limited
+  on the number of concurrent requests. It can be very efficient against very
+  dumb robots, and will significantly reduce the load on firewalls compared to
+  a "deny" rule. But when facing "correctly" developed robots, it can make
+  things worse by forcing haproxy and the front firewall to support insane
+  number of concurrent connections. By default an HTTP error 500 is returned.
+  But the response may be customized using same syntax than
+  "http-request return" rules. Thus, see "http-request return" for details.
+  For compatiblity purpose, when no argument is defined, or only "deny_status",
+  the argument "default-errorfiles" is implied. It means
+  "http-request tarpit [deny_status <status>]" is an alias of
+  "http-request tarpit [status <status>] default-errorfiles".
+  No further "http-request" rules are evaluated.
+  See also "http-request return" and "http-request silent-drop".
 
 http-request track-sc0 <key> [table <table>] [ { if | unless } <condition> ]
 http-request track-sc1 <key> [table <table>] [ { if | unless } <condition> ]
@@ -5779,18 +5789,22 @@
   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.
 
-http-response deny [deny_status <status>] [ { errorfile | errorfiles } <err> ]
-                           [ { if | unless } <condition> ]
+http-response deny [deny_status <status>] [ { if | unless } <condition> ]
+http-response deny [ { status | deny_status } <code>] [content-type <type>]
+          [ { default-errorfiles | errorfile <file> | errorfiles <name> |
+              file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
+          [ hdr <name> <fmt> ]*
+          [ { if | unless } <condition> ]
 
-  This stops the evaluation of the rules and immediately rejects the response
-  and emits an HTTP 502 error, or optionally the status code specified as an
-  argument to "deny_status". The list of permitted status codes is limited to
-  those that can be overridden by the "errorfile" directive. A specific error
-  message may be specified. It may be an error file, using the "errorfile"
-  keyword followed by the file containing the full HTTP response. It may also
-  be an error from an http-errors section, using the "errorfiles" keyword
-  followed by the section name.
+  This stops the evaluation of the rules and immediately rejects the response.
+  By default an HTTP 502 error is returned. But the response may be customized
+  using same syntax than "http-response return" rules. Thus, see
+  "http-response return" for details. For compatiblity purpose, when no
+  argument is defined, or only "deny_status", the argument "default-errorfiles"
+  is implied. It means "http-response deny [deny_status <status>]" is an alias
+  of "http-response deny [status <status>] default-errorfiles".
   No further "http-response" rules are evaluated.
+  See also "http-response return".
 
 http-response redirect <rule> [ { if | unless } <condition> ]
 
diff --git a/include/types/action.h b/include/types/action.h
index 8d68f6d..b01380c 100644
--- a/include/types/action.h
+++ b/include/types/action.h
@@ -125,11 +125,7 @@
 			struct list fmt;       /* log-format compatible expression */
 			struct my_regex *re;   /* used by replace-header/value/uri/path */
 		} http;                        /* args used by some HTTP rules */
-		struct {
-			int status;            /* status code */
-			struct buffer *errmsg; /* HTTP error message, may be NULL */
-		} http_deny;                   /* args used by HTTP deny rules */
-		struct http_reply *http_reply; /* HTTP response to be used by return rules */
+		struct http_reply *http_reply; /* HTTP response to be used by return/deny/tarpit rules */
 		struct redirect_rule *redir;   /* redirect rule or "http-request redirect" */
 		struct {
 			char *ref;             /* MAP or ACL file name to update */
diff --git a/include/types/http_ana.h b/include/types/http_ana.h
index 764ebc2..316a271 100644
--- a/include/types/http_ana.h
+++ b/include/types/http_ana.h
@@ -26,6 +26,7 @@
 #include <common/http.h>
 
 #include <types/channel.h>
+#include <types/http_htx.h>
 
 /* These are the flags that are found in txn->flags */
 
@@ -174,6 +175,7 @@
 	/* 1 unused byte here */
 	short status;                   /* HTTP status from the server, negative if from proxy */
 	struct buffer *errmsg;          /* custom HTTP error message to use as reply */
+	struct http_reply *http_reply;  /* The HTTP reply to use as reply */
 
 	char cache_hash[20];               /* Store the cache hash  */
 	char *uri;                      /* first line if log needed, NULL otherwise */
diff --git a/reg-tests/http-errorfiles/errors/lf-403.txt b/reg-tests/http-errorfiles/errors/lf-403.txt
new file mode 100644
index 0000000..3a3c3aa
--- /dev/null
+++ b/reg-tests/http-errorfiles/errors/lf-403.txt
@@ -0,0 +1 @@
+The path "%[path]" is forbidden
diff --git a/reg-tests/http-errorfiles/http_deny_errors.vtc b/reg-tests/http-errorfiles/http_deny_errors.vtc
index de456d7..3a02af0 100644
--- a/reg-tests/http-errorfiles/http_deny_errors.vtc
+++ b/reg-tests/http-errorfiles/http_deny_errors.vtc
@@ -27,6 +27,9 @@
         http-request deny deny_status 500 errorfile  /dev/null if { path /500-1 }
         http-request deny deny_status 500 errorfiles errors-1 if { path /500-2 }
 
+        http-request deny status 500 hdr x-err-info "path=%[path]" content-type "text/plain" string "Internal Error" if { path /int-err }
+        http-request deny status 403 hdr x-err-info "path=%[path]" content-type "text/plain" lf-file ${testdir}/errors/lf-403.txt if { path /forbidden }
+
 } -start
 
 client c1r1  -connect ${h1_fe1_sock} {
@@ -55,3 +58,20 @@
         txreq -req GET -url /500-2
         expect_close
 } -run
+client c1r6  -connect ${h1_fe1_sock} {
+        txreq -req GET -url /int-err
+	rxresp
+	expect resp.status == 500
+	expect resp.http.x-err-info == "path=/int-err"
+	expect resp.http.content-type == "text/plain"
+        expect resp.http.content-length == 14
+	expect resp.body == "Internal Error"
+} -run
+client c1r7  -connect ${h1_fe1_sock} {
+        txreq -req GET -url /forbidden
+	rxresp
+	expect resp.status == 403
+	expect resp.http.x-err-info == "path=/forbidden"
+	expect resp.http.content-type == "text/plain"
+	expect resp.body == "The path \"/forbidden\" is forbidden\n"
+} -run
diff --git a/src/http_act.c b/src/http_act.c
index 61ffd52..ab4a8bb 100644
--- a/src/http_act.c
+++ b/src/http_act.c
@@ -58,6 +58,30 @@
 	}
 }
 
+/* Release memory allocated by HTTP actions relying on an http reply. Concretly,
+ * it releases <.arg.http_reply>
+ */
+static void release_act_http_reply(struct act_rule *rule)
+{
+	release_http_reply(rule->arg.http_reply);
+	rule->arg.http_reply = NULL;
+}
+
+
+/* Check function for HTTP actions relying on an http reply. The function
+ * returns 1 in success case, otherwise, it returns 0 and err is filled.
+ */
+static int check_act_http_reply(struct act_rule *rule, struct proxy *px, char **err)
+{
+	struct http_reply *reply = rule->arg.http_reply;
+
+	if (!http_check_http_reply(reply, px, err)) {
+		release_act_http_reply(rule);
+		return 0;
+	}
+	return 1;
+}
+
 
 /* This function executes one of the set-{method,path,query,uri} actions. It
  * builds a string in the trash from the specified format string. It finds
@@ -797,119 +821,66 @@
 	return ACT_RET_PRS_OK;
 }
 
-/* Check an "http-request deny" action when an http-errors section is referenced.
- *
- * The function returns 1 in success case, otherwise, it returns 0 and err is
- * filled.
- */
-static int check_http_deny_action(struct act_rule *rule, struct proxy *px, char **err)
-{
-	struct http_errors *http_errs;
-	int status = (intptr_t)(rule->arg.act.p[0]);
-	int ret = 1;
-
-	list_for_each_entry(http_errs, &http_errors_list, list) {
-		if (strcmp(http_errs->id, (char *)rule->arg.act.p[1]) == 0) {
-			free(rule->arg.act.p[1]);
-			rule->arg.http_deny.status = status;
-			rule->arg.http_deny.errmsg = http_errs->errmsg[http_get_status_idx(status)];
-			if (!rule->arg.http_deny.errmsg)
-				ha_warning("Proxy '%s': status '%d' referenced by http deny rule "
-					   "not declared in http-errors section '%s'.\n",
-					   px->id, status, http_errs->id);
-			break;
-		}
-	}
-
-	if (&http_errs->list == &http_errors_list) {
-		memprintf(err, "unknown http-errors section '%s' referenced by http deny rule",
-			  (char *)rule->arg.act.p[1]);
-		free(rule->arg.act.p[1]);
-		ret = 0;
-	}
-
-	return ret;
-}
-
 /* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a
- * response rule. It may take optional arguments to define the status code, the
- * error file or the http-errors section to use. It returns ACT_RET_PRS_OK on
- * success, ACT_RET_PRS_ERR on error.
+ * response rule. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on
+ * error. It relies on http_parse_http_reply() to set
+ * <.arg.http_reply>.
  */
 static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
 					  struct act_rule *rule, char **err)
 {
-	int default_status, status, hc, cur_arg;
-
+	int default_status;
+	int cur_arg, arg = 0;
 
 	cur_arg = *orig_arg;
 	if (rule->from == ACT_F_HTTP_REQ) {
 		if (!strcmp(args[cur_arg-1], "tarpit")) {
 			rule->action = ACT_HTTP_REQ_TARPIT;
-			default_status = status = 500;
+			default_status = 500;
 		}
 		else {
 			rule->action = ACT_ACTION_DENY;
-			default_status = status = 403;
+			default_status = 403;
 		}
 	}
 	else {
 		rule->action = ACT_ACTION_DENY;
-		default_status = status = 502;
+		default_status = 502;
 	}
-	rule->flags |= ACT_FLAG_FINAL;
-
-	if (strcmp(args[cur_arg], "deny_status") == 0) {
-		cur_arg++;
-		if (!*args[cur_arg]) {
-			memprintf(err, "'%s' expects <status_code> as argument", args[cur_arg-1]);
-			return ACT_RET_PRS_ERR;
-		}
 
-		status = atol(args[cur_arg]);
-		cur_arg++;
-		for (hc = 0; hc < HTTP_ERR_SIZE; hc++) {
-			if (http_err_codes[hc] == status)
-				break;
-		}
-		if (hc >= HTTP_ERR_SIZE) {
-			memprintf(err, "status code '%d' not handled, using default code '%d'",
-				  status, default_status);
-			status = default_status;
-			hc = http_get_status_idx(status);
-		}
-	}
+	/* If no args or only a deny_status specified, fallback on the legacy
+	 * mode and use default error files despite the fact that
+	 * default-errorfiles is not used. Otherwise, parse an http reply.
+	 */
 
-	if (strcmp(args[cur_arg], "errorfile") == 0) {
-		cur_arg++;
-		if (!*args[cur_arg]) {
-			memprintf(err, "'%s' expects <file> as argument", args[cur_arg-1]);
-			return ACT_RET_PRS_ERR;
-		}
+	/* Prepare parsing of log-format strings */
+	px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
 
-		rule->arg.http_deny.errmsg = http_load_errorfile(args[cur_arg], err);
-		if (!rule->arg.http_deny.errmsg)
-			return ACT_RET_PRS_ERR;
-		cur_arg++;
+	if (!*(args[cur_arg])) {
+		rule->arg.http_reply = http_parse_http_reply((const char *[]){"default-errorfiles", ""}, &arg, px, default_status, err);
+		goto end;
 	}
-	else if (strcmp(args[cur_arg], "errorfiles") == 0) {
-		cur_arg++;
-		if (!*args[cur_arg]) {
-			memprintf(err, "'%s' expects <http_errors_name> as argument", args[cur_arg-1]);
-			return ACT_RET_PRS_ERR;
+
+	if (strcmp(args[cur_arg], "deny_status") == 0) {
+		if (!*(args[cur_arg+2]) ||
+		    (strcmp(args[cur_arg+2], "errorfile") != 0 && strcmp(args[cur_arg+2], "errorfiles") != 0)) {
+			rule->arg.http_reply = http_parse_http_reply((const char *[]){"status", args[cur_arg+1], "default-errorfiles", ""},
+								     &arg, px, default_status, err);
+			*orig_arg += 2;
+			goto end;
 		}
-		/* Must be resolved during the config validity check */
-		rule->arg.act.p[0] = (void *)((intptr_t)status);
-		rule->arg.act.p[1] = strdup(args[cur_arg]);
-		rule->check_ptr = check_http_deny_action;
-		cur_arg++;
-		goto out;
+		args[cur_arg] += 5; /* skip "deny_" for the parsing */
 	}
 
-	rule->arg.http_deny.status = status;
+	rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, default_status, err);
 
-  out:
-	*orig_arg = cur_arg;
+  end:
+	if (!rule->arg.http_reply)
+		return ACT_RET_PRS_ERR;
+
+	rule->flags |= ACT_FLAG_FINAL;
+	rule->check_ptr = check_act_http_reply;
+	rule->release_ptr = release_act_http_reply;
 	return ACT_RET_PRS_OK;
 }
 
@@ -1802,13 +1773,6 @@
 	return ACT_RET_PRS_OK;
 }
 
-/* Release <.arg.http_reply> */
-static void release_http_return(struct act_rule *rule)
-{
-	release_http_reply(rule->arg.http_reply);
-	rule->arg.http_reply = NULL;
-}
-
 /* This function executes a return action. It builds an HTX message from an
  * errorfile, an raw file or a log-format string, depending on <.action>
  * value. On success, it returns ACT_RET_ABRT. If an error occurs ACT_RET_ERR is
@@ -1839,20 +1803,6 @@
 	return ACT_RET_ABRT;
 }
 
-/* Check an "http-request return" action. The function returns 1 in success
- * case, otherwise, it returns 0 and err is filled.
- */
-static int check_http_return_action(struct act_rule *rule, struct proxy *px, char **err)
-{
-	struct http_reply *reply = rule->arg.http_reply;
-
-	if (!http_check_http_reply(reply, px, err)) {
-		release_http_return(rule);
-		return 0;
-	}
-	return 1;
-}
-
 /* Parse a "return" action. It returns ACT_RET_PRS_OK on success,
  * ACT_RET_PRS_ERR on error. It relies on http_parse_http_reply() to set
  * <.arg.http_reply>.
@@ -1868,9 +1818,9 @@
 
 	rule->flags |= ACT_FLAG_FINAL;
 	rule->action = ACT_CUSTOM;
-	rule->check_ptr = check_http_return_action;
+	rule->check_ptr = check_act_http_reply;
 	rule->action_ptr = http_action_return;
-	rule->release_ptr = release_http_return;
+	rule->release_ptr = release_act_http_reply;
 	return ACT_RET_PRS_OK;
 }
 
diff --git a/src/http_ana.c b/src/http_ana.c
index 0439377..adadc18 100644
--- a/src/http_ana.c
+++ b/src/http_ana.c
@@ -672,6 +672,12 @@
 		_HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
 	if (sess->listener->counters)
 		_HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
+
+	if (txn->http_reply) {
+		if (http_reply_message(s, txn->http_reply) == -1)
+			goto return_int_err;
+		goto return_prx_cond;
+	}
 	goto return_prx_err;
 
  return_int_err:
@@ -1002,8 +1008,28 @@
 	 */
 	s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now);
 
+	if (req->flags & CF_READ_ERROR) {
+		http_reply_and_close(s, txn->status, NULL);
+		goto end;
+	}
+
-	http_reply_and_close(s, txn->status, (!(req->flags & CF_READ_ERROR) ? http_error_message(s) : NULL));
+	if (txn->http_reply) {
+		if (!http_reply_message(s, txn->http_reply))
+			goto end;
 
+		txn->status = 500;
+		if (!(s->flags & SF_ERR_MASK))
+			s->flags |= SF_ERR_INTERNAL;
+		_HA_ATOMIC_ADD(&s->sess->fe->fe_counters.internal_errors, 1);
+		if (s->flags & SF_BE_ASSIGNED)
+			_HA_ATOMIC_ADD(&s->be->be_counters.internal_errors, 1);
+		if (s->sess->listener->counters)
+			_HA_ATOMIC_ADD(&s->sess->listener->counters->internal_errors, 1);
+	}
+
+	http_reply_and_close(s, txn->status, http_error_message(s));
+
+  end:
 	req->analysers &= AN_REQ_FLT_END;
 	req->analyse_exp = TICK_ETERNITY;
 
@@ -2159,6 +2185,12 @@
 		_HA_ATOMIC_ADD(&sess->listener->counters->denied_resp, 1);
 	if (objt_server(s->target))
 		_HA_ATOMIC_ADD(&__objt_server(s->target)->counters.denied_resp, 1);
+
+	if (txn->http_reply) {
+		if (http_reply_message(s, txn->http_reply) == -1)
+			goto return_int_err;
+		goto return_prx_cond;
+	}
 	goto return_prx_err;
 
  return_int_err:
@@ -2907,17 +2939,15 @@
 				goto end;
 
 			case ACT_ACTION_DENY:
-				txn->status = rule->arg.http_deny.status;
-				if (rule->arg.http_deny.errmsg)
-					txn->errmsg = rule->arg.http_deny.errmsg;
+				txn->status = rule->arg.http_reply->status;
+				txn->http_reply = rule->arg.http_reply;
 				rule_ret = HTTP_RULE_RES_DENY;
 				goto end;
 
 			case ACT_HTTP_REQ_TARPIT:
 				txn->flags |= TX_CLTARPIT;
-				txn->status = rule->arg.http_deny.status;
-				if (rule->arg.http_deny.errmsg)
-					txn->errmsg = rule->arg.http_deny.errmsg;
+				txn->status = rule->arg.http_reply->status;
+				txn->http_reply = rule->arg.http_reply;
 				rule_ret = HTTP_RULE_RES_DENY;
 				goto end;
 
@@ -3085,9 +3115,8 @@
 				goto end;
 
 			case ACT_ACTION_DENY:
-				txn->status = rule->arg.http_deny.status;
-				if (rule->arg.http_deny.errmsg)
-					txn->errmsg = rule->arg.http_deny.errmsg;
+				txn->status = rule->arg.http_reply->status;
+				txn->http_reply = rule->arg.http_reply;
 				rule_ret = HTTP_RULE_RES_DENY;
 				goto end;
 
@@ -4688,9 +4717,7 @@
 			/* get default error message */
 			errmsg = http_error_message(s);
 		}
-		if (b_is_null(errmsg))
-			goto leave;
-		if (!channel_htx_copy_msg(res, htx, errmsg))
+		if (!b_is_null(errmsg) && !channel_htx_copy_msg(res, htx, errmsg))
 			goto fail;
 	}
 	else {
@@ -5178,6 +5205,7 @@
 		      : 0);
 	txn->status = -1;
 	txn->errmsg = NULL;
+	txn->http_reply = NULL;
 	write_u32(txn->cache_hash, 0);
 
 	txn->cookie_first_date = 0;