MEDIUM: http-rules: Add the return action to HTTP rules

Thanks to this new action, it is now possible to return any responses from
HAProxy, with any status code, based on an errorfile, a file or a string. Unlike
the other internal messages generated by HAProxy, these ones are not interpreted
as errors. And it is not necessary to use a file containing a full HTTP
response, although it is still possible. In addition, using a log-format string
or a log-format file, it is possible to have responses with a dynamic
content. This action can be used on the request path or the response path. The
only constraint is to have a responses smaller than a buffer. And to avoid any
warning the buffer space reserved to the headers rewritting should also be free.

When a response is returned with a file or a string as payload, it only contains
the content-length header and the content-type header, if applicable. Here are
examples:

  http-request return content-type image/x-icon file /var/www/favicon.ico  \
      if { path /favicon.ico }

  http-request return status 403 content-type text/plain    \
      lf-string "Access denied. IP %[src] is blacklisted."  \
      if { src -f /etc/haproxy/blacklist.lst }
diff --git a/doc/configuration.txt b/doc/configuration.txt
index cfbcc5c..f4a4354 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4840,6 +4840,73 @@
     # outputs:
     X-Forwarded-For: 172.16.10.1, 172.16.13.24, 10.0.0.37
 
+http-request return [status <code>] [content-type <type>]
+          [ { default-errorfiles | errorfile <file> | errorfiles <name> |
+              file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
+          [ { if | unless } <condition> ]
+
+  This stops the evaluation of the rules and immediatly returns a response. The
+  default status code used for the response is 200. It can be optionally
+  specified as an arguments to "status". The response content-type may also be
+  specified as an argument to "content-type". Finally the response itselft may
+  be defined. If can be a full HTTP response specifying the errorfile to use,
+  or the response payload specifing the file or the string to use. These rules
+  are followed to create the response :
+
+  * If neither the errorfile nor the payload to use is defined, a dummy
+    response is returned. Only the "status" argument is considered. It can be
+    any code in the range [200, 599]. The "content-type" argument, if any, is
+    ignored.
+
+  * If "default-errorfiles" argument is set, the proxy's errorfiles are
+    considered.  If the "status" argument is defined, it must be one of the
+    status code handled by hparoxy (200, 400, 403, 404, 405, 408, 410, 425,
+    429, 500, 502, 503, and 504). The "content-type" argument, if any, is
+    ignored.
+
+  * If a specific errorfile is defined, with an "errorfile" argument, the
+    corresponding file, containing a full HTTP response, is returned. Only the
+    "status" argument is considered. It must be one of the status code handled
+    by hparoxy (200, 400, 403, 404, 405, 408, 410, 425, 429, 500, 502, 503, and
+    504). The "content-type" argument, if any, is ignored.
+
+  * If an http-errors section is defined, with an "errorfiles" argument, the
+    corresponding file in the specified http-errors section, containing a full
+    HTTP response, is returned. Only the "status" argument is considered. It
+    must be one of the status code handled by hparoxy (200, 400, 403, 404, 405,
+    408, 410, 425, 429, 500, 502, 503, and 504). The "content-type" argument,
+    if any, is ignored.
+
+  * If a "file" or a "lf-file" argument is specified, the file's content is
+    used as the response payload. If the file is not empty, its content-type
+    must be set as argument to "content-type". Otherwise, any "content-type"
+    argument is ignored. With a "lf-file" argument, the file's content is
+    evaluated as a log-format string. With a "file" argument, it is considered
+    as a raw content.
+
+  * If a "string" or "lf-string" argument is specified, the defined string is
+    used as the response payload. The content-type must always be set as
+    argument to "content-type". With a "lf-string" argument, the string is
+    evaluated as a log-format string. With a "string" argument, it is
+    considered as a raw string.
+
+  Note that the generated response must be smaller than a buffer. And to avoid
+  any warning, when an errorfile or a raw file is loaded, the buffer space
+  reserved to the headers rewritting should also be free.
+
+  No further "http-request" rules are evaluated.
+
+  Example:
+    http-request return errorfile /etc/haproy/errorfiles/200.http \
+        if { path /ping }
+
+    http-request return content-type image/x-icon file /var/www/favicon.ico  \
+        if { path /favicon.ico }
+
+    http-request return status 403 content-type text/plain    \
+        lf-string "Access denied. IP %[src] is blacklisted."  \
+        if { src -f /etc/haproxy/blacklist.lst }
+
 http-request sc-inc-gpc0(<sc-id>) [ { if | unless } <condition> ]
 http-request sc-inc-gpc1(<sc-id>) [ { if | unless } <condition> ]
 
@@ -5397,6 +5464,70 @@
     # outputs:
     Cache-Control: max-age=3600, private
 
+http-response return [status <code>] [content-type <type>]
+          [ { default-errorfiles | errorfile <file> | errorfiles <name> |
+              file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
+          [ { if | unless } <condition> ]
+
+  This stops the evaluation of the rules and immediatly returns a response. The
+  default status code used for the response is 200. It can be optionally
+  specified as an arguments to "status". The response content-type may also be
+  specified as an argument to "content-type". Finally the response itselft may
+  be defined. If can be a full HTTP response specifying the errorfile to use,
+  or the response payload specifing the file or the string to use. These rules
+  are followed to create the response :
+
+  * If neither the errorfile nor the payload to use is defined, a dummy
+    response is returned. Only the "status" argument is considered. It can be
+    any code in the range [200, 599]. The "content-type" argument, if any, is
+    ignored.
+
+  * If "default-errorfiles" argument is set, the proxy's errorfiles are
+    considered.  If the "status" argument is defined, it must be one of the
+    status code handled by hparoxy (200, 400, 403, 404, 405, 408, 410, 425,
+    429, 500, 502, 503, and 504). The "content-type" argument, if any, is
+    ignored.
+
+  * If a specific errorfile is defined, with an "errorfile" argument, the
+    corresponding file, containing a full HTTP response, is returned. Only the
+    "status" argument is considered. It must be one of the status code handled
+    by hparoxy (200, 400, 403, 404, 405, 408, 410, 425, 429, 500, 502, 503, and
+    504). The "content-type" argument, if any, is ignored.
+
+  * If an http-errors section is defined, with an "errorfiles" argument, the
+    corresponding file in the specified http-errors section, containing a full
+    HTTP response, is returned. Only the "status" argument is considered. It
+    must be one of the status code handled by hparoxy (200, 400, 403, 404, 405,
+    408, 410, 425, 429, 500, 502, 503, and 504). The "content-type" argument,
+    if any, is ignored.
+
+  * If a "file" or a "lf-file" argument is specified, the file's content is
+    used as the response payload. If the file is not empty, its content-type
+    must be set as argument to "content-type". Otherwise, any "content-type"
+    argument is ignored. With a "lf-file" argument, the file's content is
+    evaluated as a log-format string. With a "file" argument, it is considered
+    as a raw content.
+
+  * If a "string" or "lf-string" argument is specified, the defined string is
+    used as the response payload. The content-type must always be set as
+    argument to "content-type". With a "lf-string" argument, the string is
+    evaluated as a log-format string. With a "string" argument, it is
+    considered as a raw string.
+
+  Note that the generated response must be smaller than a buffer. And to avoid
+  any warning, when an errorfile or a raw file is loaded, the buffer space
+  reserved to the headers rewritting should also be free.
+
+  No further "http-response" rules are evaluated.
+
+  Example:
+    http-response return errorfile /etc/haproy/errorfiles/200.http \
+        if { status eq 404 }
+
+    http-response return content-type text/plain  \
+        string "This is the end !"                \
+        if { status eq 500 }
+
 http-response sc-inc-gpc0(<sc-id>) [ { if | unless } <condition> ]
 http-response sc-inc-gpc1(<sc-id>) [ { if | unless } <condition> ]
 
diff --git a/include/types/action.h b/include/types/action.h
index 293e1d8..e65beaf 100644
--- a/include/types/action.h
+++ b/include/types/action.h
@@ -127,6 +127,15 @@
 			int status;            /* status code */
 			struct buffer *errmsg; /* HTTP error message, may be NULL */
 		} http_deny;                   /* args used by HTTP deny rules */
+		struct {
+			int status;
+			char *ctype;
+			union {
+				struct list   fmt;
+				struct buffer obj;
+				struct buffer *errmsg;
+			} body;
+		} http_return;
 		struct redirect_rule *redir;   /* redirect rule or "http-request redirect" */
 		struct {
 			char *ref;             /* MAP or ACL file name to update */
diff --git a/src/http_act.c b/src/http_act.c
index a74aeea..7c33e6c 100644
--- a/src/http_act.c
+++ b/src/http_act.c
@@ -11,6 +11,8 @@
  */
 
 #include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 
 #include <ctype.h>
 #include <string.h>
@@ -1784,7 +1786,6 @@
 {
 	int cur_arg;
 
-
 	cur_arg = *orig_arg;
 	if (!*args[cur_arg]) {
 		memprintf(err, "expects exactly 1 arguments");
@@ -1805,6 +1806,517 @@
 	return ACT_RET_PRS_OK;
 }
 
+/* Release <.arg.http_return> */
+static void release_http_return(struct act_rule *rule)
+{
+	struct logformat_node *lf, *lfb;
+
+	free(rule->arg.http_return.ctype);
+	if (rule->action == 2) {
+		chunk_destroy(&rule->arg.http_return.body.obj);
+	}
+	else if (rule->action == 3) {
+		list_for_each_entry_safe(lf, lfb, &rule->arg.http_return.body.fmt, list) {
+			LIST_DEL(&lf->list);
+			release_sample_expr(lf->expr);
+			free(lf->arg);
+			free(lf);
+		}
+	}
+}
+
+/* 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
+ * returned.
+ */
+static enum act_return http_action_return(struct act_rule *rule, struct proxy *px,
+					  struct session *sess, struct stream *s, int flags)
+{
+	struct channel *req = &s->req;
+	struct channel *res = &s->res;
+	struct buffer *errmsg;
+	struct htx *htx = htx_from_buf(&res->buf);
+	struct htx_sl *sl;
+	struct buffer *body = NULL;
+	const char *status, *reason, *clen, *ctype;
+	unsigned int slflags;
+	enum act_return ret = ACT_RET_DONE;
+
+	s->txn->status = rule->arg.http_return.status;
+	channel_htx_truncate(res, htx);
+
+	if (rule->action == 1) {
+		/* implicit or explicit error message*/
+		errmsg = rule->arg.http_return.body.errmsg;
+		if (!errmsg) {
+			/* get default error message */
+			errmsg = http_error_message(s);
+		}
+		if (b_is_null(errmsg))
+			goto end;
+		if (!channel_htx_copy_msg(res, htx, errmsg))
+			goto fail;
+	}
+	else {
+		/* no payload, file or log-format string */
+		if (rule->action == 2) {
+			/* file */
+			body = &rule->arg.http_return.body.obj;
+		}
+		else if (rule->action == 3) {
+			/* log-format string */
+			body = alloc_trash_chunk();
+			if (!body)
+				goto fail_alloc;
+			body->data = build_logline(s, body->area, body->size, &rule->arg.http_return.body.fmt);
+		}
+		/* else no payload */
+
+		status = ultoa(rule->arg.http_return.status);
+		reason = http_get_reason(rule->arg.http_return.status);
+		slflags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
+		if (!body || !b_data(body))
+			slflags |= HTX_SL_F_BODYLESS;
+		sl = htx_add_stline(htx, HTX_BLK_RES_SL, slflags, ist("HTTP/1.1"), ist(status), ist(reason));
+		if (!sl)
+			goto fail;
+		sl->info.res.status = rule->arg.http_return.status;
+
+		clen = (body ? ultoa(b_data(body)) : "0");
+		ctype = rule->arg.http_return.ctype;
+
+		if (!htx_add_header(htx, ist("content-length"), ist(clen)) ||
+		    (body && b_data(body) && ctype && !htx_add_header(htx, ist("content-type"), ist(ctype))) ||
+		    !htx_add_endof(htx, HTX_BLK_EOH) ||
+		    (body && b_data(body) && !htx_add_data_atonce(htx, ist2(b_head(body), b_data(body)))) ||
+		    !htx_add_endof(htx, HTX_BLK_EOM))
+			goto fail;
+	}
+
+	htx_to_buf(htx, &s->res.buf);
+	if (!http_forward_proxy_resp(s, 1))
+		goto fail;
+
+  end:
+	if (rule->from == ACT_F_HTTP_REQ) {
+		/* let's log the request time */
+		s->logs.tv_request = now;
+		req->analysers &= AN_REQ_FLT_END;
+
+		if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
+			_HA_ATOMIC_ADD(&s->sess->fe->fe_counters.intercepted_req, 1);
+	}
+
+	if (!(s->flags & SF_ERR_MASK))
+		s->flags |= SF_ERR_LOCAL;
+	if (!(s->flags & SF_FINST_MASK))
+		s->flags |= ((rule->from == ACT_F_HTTP_REQ) ? SF_FINST_R : SF_FINST_H);
+
+  leave:
+	if (rule->action == 3)
+		free_trash_chunk(body);
+	return ret;
+
+  fail_alloc:
+	if (!(s->flags & SF_ERR_MASK))
+		s->flags |= SF_ERR_RESOURCE;
+	ret = ACT_RET_ERR;
+	goto leave;
+
+  fail:
+	/* If an error occurred, remove the incomplete HTTP response from the
+	 * buffer */
+	channel_htx_truncate(res, htx);
+	ret = ACT_RET_ERR;
+	if (!(s->flags & SF_ERR_MASK))
+		s->flags |= SF_ERR_PRXCOND;
+	goto leave;
+}
+
+/* Check an "http-request return" 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_return_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_return.status = status;
+			rule->arg.http_return.ctype = NULL;
+			rule->action = 1;
+			rule->arg.http_return.body.errmsg = http_errs->errmsg[http_get_status_idx(status)];
+			rule->action_ptr = http_action_return;
+			rule->release_ptr = release_http_return;
+
+			if (!rule->arg.http_return.body.errmsg)
+				ha_warning("Proxy '%s': status '%d' referenced by http return 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 return rule",
+			  (char *)rule->arg.act.p[1]);
+		free(rule->arg.act.p[1]);
+		ret = 0;
+	}
+
+	return ret;
+}
+
+/* Parse a "return" action. It returns ACT_RET_PRS_OK on success,
+ * ACT_RET_PRS_ERR on error. This function creates 4 action types:
+ *
+ *   - action 0  : dummy response, no payload
+ *   - action 1  : implicit error message depending on the status code or explicit one
+ *   - action 2  : explicit file oject ('file' argument)
+ *   - action 3  : explicit log-format string ('content' argument)
+ *
+ * The content-type must be defined for non-empty payload. It is ignored for
+ * error messages (implicit or explicit). When an http-errors section is
+ * referenced, action is set to -1 and the real action is resolved during the
+ * configuration validity check.
+ */
+static enum act_parse_ret parse_http_return(const char **args, int *orig_arg, struct proxy *px,
+					    struct act_rule *rule, char **err)
+{
+	struct logformat_node *lf, *lfb;
+	struct stat stat;
+	const char *file = NULL, *act_arg = NULL;
+	char *obj = NULL, *ctype = NULL, *name = NULL;
+	int cur_arg, cap, objlen = 0, action = 0, status = 200, fd = -1;
+
+	cur_arg = *orig_arg;
+
+	while (*args[cur_arg]) {
+		if (strcmp(args[cur_arg], "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]);
+			if (status < 200 || status > 599) {
+				memprintf(err, "Unexpected status code '%d'", status);
+				goto error;
+			}
+			cur_arg++;
+		}
+		else if (strcmp(args[cur_arg], "content-type") == 0) {
+			cur_arg++;
+			if (!*args[cur_arg]) {
+				memprintf(err, "'%s' expects <ctype> as argument", args[cur_arg-1]);
+				goto error;
+			}
+			free(ctype);
+			ctype = strdup(args[cur_arg]);
+			cur_arg++;
+		}
+		else if (strcmp(args[cur_arg], "errorfiles") == 0) {
+			if (action != 0) {
+				memprintf(err, "unexpected '%s' argument, '%s' already defined", args[cur_arg], act_arg);
+				goto error;
+			}
+			act_arg = args[cur_arg];
+			cur_arg++;
+			if (!*args[cur_arg]) {
+				memprintf(err, "'%s' expects <name> as argument", args[cur_arg-1]);
+				goto error;
+			}
+			/* Must be resolved during the config validity check */
+			name = strdup(args[cur_arg]);
+			action = -1;
+			cur_arg++;
+		}
+		else if (strcmp(args[cur_arg], "default-errorfiles") == 0) {
+			if (action != 0) {
+				memprintf(err, "unexpected '%s' argument, '%s' already defined", args[cur_arg], act_arg);
+				goto error;
+			}
+			act_arg = args[cur_arg];
+			action = 1;
+			cur_arg++;
+		}
+		else if (strcmp(args[cur_arg], "errorfile") == 0) {
+			if (action != 0) {
+				memprintf(err, "unexpected '%s' argument, '%s' already defined", args[cur_arg], act_arg);
+				goto error;
+			}
+			act_arg = args[cur_arg];
+			cur_arg++;
+			if (!*args[cur_arg]) {
+				memprintf(err, "'%s' expects <fmt> as argument", args[cur_arg-1]);
+				goto error;
+			}
+			file = args[cur_arg];
+			rule->arg.http_return.body.errmsg = http_load_errorfile(args[cur_arg], err);
+			if (!rule->arg.http_return.body.errmsg) {
+				goto error;
+			}
+			action = 1;
+			cur_arg++;
+		}
+		else if (strcmp(args[cur_arg], "file") == 0) {
+			if (action != 0) {
+				memprintf(err, "unexpected '%s' argument, '%s' already defined", args[cur_arg], act_arg);
+				goto error;
+			}
+			act_arg = args[cur_arg];
+			cur_arg++;
+			if (!*args[cur_arg]) {
+				memprintf(err, "'%s' expects <file> as argument", args[cur_arg-1]);
+				goto error;
+			}
+			file = args[cur_arg];
+			fd = open(args[cur_arg], O_RDONLY);
+			if ((fd < 0) || (fstat(fd, &stat) < 0)) {
+				memprintf(err, "error opening file '%s'", args[cur_arg]);
+				goto error;
+			}
+			if (stat.st_size > global.tune.bufsize) {
+				memprintf(err, "file '%s' exceeds the buffer size (%ld > %d)",
+					  args[cur_arg], stat.st_size, global.tune.bufsize);
+				goto error;
+			}
+			objlen = stat.st_size;
+			obj = malloc(objlen);
+			if (!obj || read(fd, obj, objlen) != objlen) {
+				memprintf(err, "error reading file '%s'", args[cur_arg]);
+				goto error;
+			}
+			close(fd);
+			fd = -1;
+			action = 2;
+			cur_arg++;
+		}
+		else if (strcmp(args[cur_arg], "string") == 0) {
+			if (action != 0) {
+				memprintf(err, "unexpected '%s' argument, '%s' already defined", args[cur_arg], act_arg);
+				goto error;
+			}
+			act_arg = args[cur_arg];
+			cur_arg++;
+			if (!*args[cur_arg]) {
+				memprintf(err, "'%s' expects <str> as argument", args[cur_arg-1]);
+				goto error;
+			}
+			obj = strdup(args[cur_arg]);
+			objlen = strlen(args[cur_arg]);
+			action = 2;
+			cur_arg++;
+		}
+		else if (strcmp(args[cur_arg], "lf-file") == 0) {
+			if (action != 0) {
+				memprintf(err, "unexpected '%s' argument, '%s' already defined", args[cur_arg], act_arg);
+				goto error;
+			}
+			act_arg = args[cur_arg];
+			cur_arg++;
+			if (!*args[cur_arg]) {
+				memprintf(err, "'%s' expects <file> as argument", args[cur_arg-1]);
+				goto error;
+			}
+			file = args[cur_arg];
+			fd = open(args[cur_arg], O_RDONLY);
+			if ((fd < 0) || (fstat(fd, &stat) < 0)) {
+				memprintf(err, "error opening file '%s'", args[cur_arg]);
+				goto error;
+			}
+			if (stat.st_size > global.tune.bufsize) {
+				memprintf(err, "file '%s' exceeds the buffer size (%ld > %d)",
+					  args[cur_arg], stat.st_size, global.tune.bufsize);
+				goto error;
+			}
+			objlen = stat.st_size;
+			obj = malloc(objlen + 1);
+			if (!obj || read(fd, obj, objlen) != objlen) {
+				memprintf(err, "error reading file '%s'", args[cur_arg]);
+				goto error;
+			}
+			close(fd);
+			fd = -1;
+			obj[objlen] = '\0';
+			action = 3;
+			cur_arg++;
+		}
+		else if (strcmp(args[cur_arg], "lf-string") == 0) {
+			if (action != 0) {
+				memprintf(err, "unexpected '%s' argument, '%s' already defined", args[cur_arg], act_arg);
+				goto error;
+			}
+			act_arg = args[cur_arg];
+			cur_arg++;
+			if (!*args[cur_arg]) {
+				memprintf(err, "'%s' expects <fmt> as argument", args[cur_arg-1]);
+				goto error;
+			}
+			obj = strdup(args[cur_arg]);
+			objlen = strlen(args[cur_arg]);
+			action = 3;
+			cur_arg++;
+		}
+		else
+			break;
+	}
+
+
+	if (action == -1) { /* errorfiles */
+		int rc;
+
+		for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
+			if (http_err_codes[rc] == status)
+				break;
+		}
+
+		if (rc >= HTTP_ERR_SIZE) {
+			memprintf(err, "status code '%d' not handled by default with '%s' argument.",
+				  status, act_arg);
+			goto error;
+		}
+		if (ctype) {
+			ha_warning("parsing [%s:%d] : 'http-%s return' : content-type '%s' ignored when the "
+				   "returned response is an erorrfile.\n",
+				   px->conf.args.file, px->conf.args.line,
+				   (rule->from == ACT_F_HTTP_REQ ? "request" : "response"),
+				   ctype);
+			free(ctype);
+			ctype = NULL;
+		}
+
+		rule->arg.act.p[0] = (void *)((intptr_t)status);
+		rule->arg.act.p[1] = name;
+		rule->check_ptr    = check_http_return_action;
+		goto out;
+	}
+	else if (action == 0) { /* no payload */
+		if (ctype) {
+			ha_warning("parsing [%s:%d] : 'http-%s return' : content-type '%s' ignored because"
+				   " neither errorfile nor payload defined.\n",
+				   px->conf.args.file, px->conf.args.line,
+				   (rule->from == ACT_F_HTTP_REQ ? "request" : "response"),
+				   ctype);
+			free(ctype);
+			ctype = NULL;
+		}
+	}
+	else if (action == 1) { /* errorfile */
+		if (!rule->arg.http_return.body.errmsg) { /* default errorfile */
+			int rc;
+
+			for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
+				if (http_err_codes[rc] == status)
+					break;
+			}
+
+			if (rc >= HTTP_ERR_SIZE) {
+				memprintf(err, "status code '%d' not handled by default with '%s' argument",
+					  status, act_arg);
+				goto error;
+			}
+			if (ctype) {
+				ha_warning("parsing [%s:%d] : 'http-%s return' : content-type '%s' ignored when the "
+					   "returned response is an erorrfile.\n",
+					   px->conf.args.file, px->conf.args.line,
+					   (rule->from == ACT_F_HTTP_REQ ? "request" : "response"),
+					   ctype);
+				free(ctype);
+				ctype = NULL;
+			}
+		}
+		else { /* explicit payload using 'errorfile' parameter */
+			if (ctype) {
+				ha_warning("parsing [%s:%d] : 'http-%s return' : content-type '%s' ignored because"
+					   " the errorfile '%s' is used.\n",
+					   px->conf.args.file, px->conf.args.line,
+					   (rule->from == ACT_F_HTTP_REQ ? "request" : "response"),
+					   ctype, file);
+				free(ctype);
+				ctype = NULL;
+			}
+		}
+	}
+	else if (action == 2) { /* explicit parameter using 'file' parameter*/
+		if (!ctype && objlen) {
+			memprintf(err, "a content type must be defined when non-empty payload is configured");
+			goto error;
+		}
+		if (ctype && !objlen) {
+				ha_warning("parsing [%s:%d] : 'http-%s return' : content-type '%s' ignored when the "
+					   "configured payload is empty.\n",
+					   px->conf.args.file, px->conf.args.line,
+					   (rule->from == ACT_F_HTTP_REQ ? "request" : "response"),
+					   ctype);
+			free(ctype);
+			ctype = NULL;
+		}
+		if (global.tune.bufsize - objlen < global.tune.maxrewrite) {
+			ha_warning("parsing [%s:%d] : 'http-%s return' : the payload runs ober the buffer space reserved to headers rewritting."
+				   " It may lead to internal errors if strict rewritting mode is enabled.\n",
+				   px->conf.args.file, px->conf.args.line,
+				   (rule->from == ACT_F_HTTP_REQ ? "request" : "response"));
+		}
+		chunk_initlen(&rule->arg.http_return.body.obj, obj, global.tune.bufsize, objlen);
+	}
+	else if (action == 3) { /* log-format payload using 'lf-file' of 'lf-string' parameter */
+		LIST_INIT(&rule->arg.http_return.body.fmt);
+		if (!ctype) {
+			memprintf(err, "a content type must be defined with a log-format payload");
+			goto error;
+		}
+		if (rule->from == ACT_F_HTTP_REQ) {
+			px->conf.args.ctx = ARGC_HRQ;
+			cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
+		}
+		else {
+			px->conf.args.ctx =  ARGC_HRS;
+			cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
+		}
+		if (!parse_logformat_string(obj, px, &rule->arg.http_return.body.fmt, LOG_OPT_HTTP, cap, err))
+			goto error;
+
+		free(px->conf.lfs_file);
+		px->conf.lfs_file = strdup(px->conf.args.file);
+		px->conf.lfs_line = px->conf.args.line;
+		free(obj);
+	}
+
+	rule->arg.http_return.status = status;
+	rule->arg.http_return.ctype = ctype;
+	rule->action = action;
+	rule->action_ptr = http_action_return;
+	rule->release_ptr = release_http_return;
+
+  out:
+	*orig_arg = cur_arg;
+	return ACT_RET_PRS_OK;
+
+  error:
+	free(obj);
+	free(ctype);
+	free(name);
+	if (fd >= 0)
+		close(fd);
+	if (action == 3) {
+		list_for_each_entry_safe(lf, lfb, &rule->arg.http_return.body.fmt, list) {
+			LIST_DEL(&lf->list);
+			release_sample_expr(lf->expr);
+			free(lf->arg);
+			free(lf);
+		}
+	}
+	free(rule->arg.http_return.ctype);
+	return ACT_RET_PRS_ERR;
+}
+
 /************************************************************************/
 /*   All supported http-request action keywords must be declared here.  */
 /************************************************************************/
@@ -1828,6 +2340,7 @@
 		{ "replace-path",     parse_replace_uri,               0 },
 		{ "replace-uri",      parse_replace_uri,               0 },
 		{ "replace-value",    parse_http_replace_header,       0 },
+		{ "return",           parse_http_return,               0 },
 		{ "set-header",       parse_http_set_header,           0 },
 		{ "set-log-level",    parse_http_set_log_level,        0 },
 		{ "set-map",          parse_http_set_map,              1 },
@@ -1860,6 +2373,7 @@
 		{ "redirect",        parse_http_redirect,       0 },
 		{ "replace-header",  parse_http_replace_header, 0 },
 		{ "replace-value",   parse_http_replace_header, 0 },
+		{ "return",          parse_http_return,         0 },
 		{ "set-header",      parse_http_set_header,     0 },
 		{ "set-log-level",   parse_http_set_log_level,  0 },
 		{ "set-map",         parse_http_set_map,        1 },