MEDIUM: http-rules: Support an optional error message in http deny rules

It is now possible to set the error message to use when a deny rule is
executed. It may be a specific error file, adding "errorfile <file>" :

  http-request deny deny_status 400 errorfile /etc/haproxy/errorfiles/400badreq.http

It may also be an error file from an http-errors section, adding "errorfiles
<name>" :

  http-request deny errorfiles my-errors  # use 403 error from "my-errors" section

When defined, this error message is set in the HTTP transaction. The tarpit rule
is also concerned by this change.
diff --git a/src/http_act.c b/src/http_act.c
index 218b1c4..774df14 100644
--- a/src/http_act.c
+++ b/src/http_act.c
@@ -785,52 +785,118 @@
 	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 2 optional arguments to define the status code. It
- * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
+ * 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.
  */
 static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
 					  struct act_rule *rule, char **err)
 {
-	int code, hc, cur_arg;
+	int default_status, status, hc, cur_arg;
+
 
 	cur_arg = *orig_arg;
 	if (rule->from == ACT_F_HTTP_REQ) {
 		if (!strcmp(args[cur_arg-1], "tarpit")) {
 			rule->action = ACT_HTTP_REQ_TARPIT;
-			rule->arg.http.i = HTTP_ERR_500;
+			default_status = status = 500;
 		}
 		else {
 			rule->action = ACT_ACTION_DENY;
-			rule->arg.http.i = HTTP_ERR_403;
+			default_status = status = 403;
 		}
 	}
 	else {
-		rule->action = ACT_ACTION_DENY;;
-		rule->arg.http.i = HTTP_ERR_502;
+		rule->action = ACT_ACTION_DENY;
+		default_status = status = 502;
 	}
 	rule->flags |= ACT_FLAG_FINAL;
 
 	if (strcmp(args[cur_arg], "deny_status") == 0) {
 		cur_arg++;
 		if (!*args[cur_arg]) {
-			memprintf(err, "missing status code.\n");
+			memprintf(err, "'%s' expects <status_code> as argument", args[cur_arg-1]);
 			return ACT_RET_PRS_ERR;
 		}
 
-		code = atol(args[cur_arg]);
+		status = atol(args[cur_arg]);
 		cur_arg++;
 		for (hc = 0; hc < HTTP_ERR_SIZE; hc++) {
-			if (http_err_codes[hc] == code) {
-				rule->arg.http.i = hc;
+			if (http_err_codes[hc] == status)
 				break;
-			}
 		}
-		if (hc >= HTTP_ERR_SIZE)
-			memprintf(err, "status code %d not handled, using default code %d",
-				  code, http_err_codes[rule->arg.http.i]);
+		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 (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;
+		}
+
+		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++;
+	}
+	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;
+		}
+		/* 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;
+	}
+
+	rule->arg.http_deny.status = status;
+
+  out:
 	*orig_arg = cur_arg;
 	return ACT_RET_PRS_OK;
 }
diff --git a/src/http_ana.c b/src/http_ana.c
index 10ee241..82a9e14 100644
--- a/src/http_ana.c
+++ b/src/http_ana.c
@@ -2896,13 +2896,17 @@
 
 			case ACT_ACTION_DENY:
 				txn->flags |= TX_CLDENY;
-				txn->status = http_err_codes[rule->arg.http.i];
+				txn->status = rule->arg.http_deny.status;
+				if (rule->arg.http_deny.errmsg)
+					txn->errmsg = rule->arg.http_deny.errmsg;
 				rule_ret = HTTP_RULE_RES_DENY;
 				goto end;
 
 			case ACT_HTTP_REQ_TARPIT:
 				txn->flags |= TX_CLTARPIT;
-				txn->status = http_err_codes[rule->arg.http.i];
+				txn->status = rule->arg.http_deny.status;
+				if (rule->arg.http_deny.errmsg)
+					txn->errmsg = rule->arg.http_deny.errmsg;
 				rule_ret = HTTP_RULE_RES_DENY;
 				goto end;
 
@@ -3073,7 +3077,9 @@
 
 			case ACT_ACTION_DENY:
 				txn->flags |= TX_CLDENY;
-				txn->status = http_err_codes[rule->arg.http.i];
+				txn->status = rule->arg.http_deny.status;
+				if (rule->arg.http_deny.errmsg)
+					txn->errmsg = rule->arg.http_deny.errmsg;
 				rule_ret = HTTP_RULE_RES_DENY;
 				goto end;