MINOR: http-htx/proxy: Add http-error directive using http return syntax

The http-error directive can now be used instead of errorfile to define an error
message in a proxy section (including default sections). This directive uses the
same syntax that http return rules. The only real difference is the limitation
on status code that may be specified. Only status codes supported by errorfile
directives are supported for this new directive. Parsing of errorfile directive
remains independent from http-error parsing. But functionally, it may be
expressed in terms of http-errors :

  errorfile <status> <file> ==> http-errror status <status> errorfile <file>
diff --git a/doc/configuration.txt b/doc/configuration.txt
index cd75de5..9eaec2a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2659,6 +2659,7 @@
 http-check send-state                     X          -         X         X
 http-check set-var                        X          -         X         X
 http-check unset-var                      X          -         X         X
+http-error                                X          X         X         X
 http-request                              -          X         X         X
 http-response                             -          X         X         X
 http-reuse                                X          -         X         X
@@ -3810,7 +3811,7 @@
   simple method for developing those files consists in associating them to the
   403 status code and interrogating a blocked URL.
 
-  See also : "errorloc", "errorloc302", "errorloc303"
+  See also : "http-error", "errorloc", "errorloc302", "errorloc303"
 
   Example :
         errorfile 400 /etc/haproxy/errorfiles/400badreq.http
@@ -3839,8 +3840,8 @@
   ones. Fonctionnly, it is exactly the same than declaring all error files by
   hand using "errorfile" directives.
 
-  See also : "errorfile", "errorloc", "errorloc302" , "errorloc303" and section
-             3.8 about http-errors.
+  See also : "http-error", "errorfile", "errorloc", "errorloc302" ,
+             "errorloc303" and section 3.8 about http-errors.
 
   Example :
         errorfiles generic
@@ -3877,7 +3878,7 @@
   status code, indicating to the client that the URL must be fetched with a GET
   request.
 
-  See also : "errorfile", "errorloc303"
+  See also : "http-error", "errorfile", "errorloc303"
 
 
 errorloc303 <code> <url>
@@ -3907,7 +3908,7 @@
   possible that some very old browsers designed before HTTP/1.1 do not support
   it, but no such problem has been reported till now.
 
-  See also : "errorfile", "errorloc", "errorloc302"
+  See also : "http-error", "errorfile", "errorloc", "errorloc302"
 
 
 email-alert from <emailaddr>
@@ -4852,6 +4853,83 @@
         http-check unset-var(check.port)
 
 
+http-error status <code> [content-type <type>]
+           [ { default-errorfiles | errorfile <file> | errorfiles <name> |
+               file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
+           [ hdr <name> <fmt> ]*
+  Defines a custom error message to use instead of errors generated by HAProxy.
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    yes   |   yes  |   yes
+  Arguments :
+    staus <code>         is the HTTP status code. It must be specified.
+                         Currently, HAProxy is capable of generating codes
+                         200, 400, 403, 404, 405, 408, 410, 425, 429, 500,
+                         502, 503, and 504.
+
+    content-type <type>  is the response content type, for instance
+                         "text/plain". This parameter is ignored and should be
+                         omitted when an errorfile is configured or when the
+                         payload is empty. Otherwise, it must be defined.
+
+    default-errorfiles   Reset the previously defined error message for current
+                         proxy for the status <code>. If used on a backend, the
+                         frontend error message is used, if defined. If used on
+                         a frontend, the default error message is used.
+
+    errorfile <file>     designates a file containing the full HTTP response.
+                         It is recommended to follow the common practice of
+                         appending ".http" to the filename so that people do
+                         not confuse the response with HTML error pages, and to
+                         use absolute paths, since files are read before any
+                         chroot is performed.
+
+    errorfiles <name>    designates the http-errors section to use to import
+                         the error message with the status code <code>. If no
+                         such message is found, the proxy's error messages are
+                         considered.
+
+    file <file>          specifies the file to use as 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. <file> is
+                         considered as a raw string.
+
+    string <str>         specifies the raw string to use as response payload.
+                         The content-type must always be set as argument to
+                         "content-type".
+
+    lf-file <file>       specifies the file to use as 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. <file> is
+                         evaluated as a log-format string.
+
+    lf-string <str>      specifies the log-format string to use as response
+                         payload. The content-type must always be set as
+                         argument to "content-type".
+
+    hdr <name> <fmt>     adds to the response the HTTP header field whose name
+                         is specified in <name> and whose value is defined by
+                         <fmt>, which follows to the log-format rules.
+                         This parameter is ignored if an errorfile is used.
+
+  This directive may be used instead of "errorfile", to define a custom error
+  message. As "errorfile" directive, it is used for errors detected and
+  returned by HAProxy. If an errorfile is defined, it is parsed when HAProxy
+  starts and must be valid according to the HTTP standards. The generated
+  response must not exceed the configured buffer size (BUFFSIZE), otherwise an
+  internal error will be returned.  Finally, if you consider to use some
+  http-after-response rules to rewrite these errors, the reserved buffer space
+  should be available (see "tune.maxrewrite").
+
+  The files are read at the same time as the configuration and kept in memory.
+  For this reason, the errors continue to be returned even when the process is
+  chrooted, and no file change is considered while the process is running.
+
+  See also : "errorfile", "errorfiles", "errorloc", "errorloc302",
+             "errorloc303" and section 3.8 about http-errors.
+
+
 http-request <action> [options...] [ { if | unless } <condition> ]
   Access control for Layer 7 requests
 
diff --git a/include/types/arg.h b/include/types/arg.h
index 80e0b0a..60fd007 100644
--- a/include/types/arg.h
+++ b/include/types/arg.h
@@ -82,6 +82,7 @@
 	ARGC_SPOE,     /* spoe message args */
 	ARGC_UBK,      /* use_backend message */
 	ARGC_USRV,     /* use-server message */
+	ARGC_HERR,     /* http-error */
 };
 
 /* flags used when compiling and executing regex */
diff --git a/reg-tests/http-errorfiles/http-error.vtc b/reg-tests/http-errorfiles/http-error.vtc
new file mode 100644
index 0000000..b03f2ac
--- /dev/null
+++ b/reg-tests/http-errorfiles/http-error.vtc
@@ -0,0 +1,75 @@
+varnishtest "Test the http-error directive"
+#REQUIRE_VERSION=2.2
+
+# This config tests the http-error directive.
+
+feature ignore_unknown_macro
+
+
+haproxy h1 -conf {
+    http-errors errors-1
+        errorfile 400  ${testdir}/errors/400-1.http
+        errorfile 403  ${testdir}/errors/403-1.http
+        errorfile 404  ${testdir}/errors/404-1.http
+        errorfile 500  ${testdir}/errors/500-1.http
+
+    defaults
+        mode http
+        timeout connect 1s
+        timeout client  1s
+        timeout server  1s
+        errorfile 400  ${testdir}/errors/400.http
+        errorfile 404  ${testdir}/errors/404.http
+
+    frontend fe1
+        bind "fd@${fe1}"
+
+        http-error status 400
+        http-error status 403 default-errorfiles
+        http-error status 404 errorfiles errors-1
+        http-error status 500 errorfile ${testdir}/errors/500.http
+        http-error status 200 content-type "text/plain" hdr x-path "path=%[path]" lf-string "The path is \"%[path]\""
+
+        http-request return status 200 default-errorfiles if { path /200 }
+        http-request deny deny_status 400 if { path /400 }
+        http-request deny deny_status 403 if { path /403 }
+        http-request deny deny_status 404 if { path /404 }
+        http-request deny deny_status 500 if { path /500 }
+
+} -start
+
+client c1r1  -connect ${h1_fe1_sock} {
+        txreq -req GET -url /200
+        rxresp
+        expect resp.status == 200
+        expect resp.http.x-path == "path=/200"
+        expect resp.http.content-type == "text/plain"
+        expect resp.body == "The path is \"/200\""
+} -run
+client c1r2  -connect ${h1_fe1_sock} {
+        txreq -req GET -url /400
+        rxresp
+        expect resp.status == 400
+        expect resp.http.x-err-type == <undef>
+        expect resp.http.content-length == 0
+} -run
+client c1r3  -connect ${h1_fe1_sock} {
+        txreq -req GET -url /403
+        rxresp
+        expect resp.status == 403
+        expect resp.http.x-err-type == <undef>
+        expect resp.http.content-length == 93
+        expect resp.http.content-type == "text/html"
+} -run
+client c1r3  -connect ${h1_fe1_sock} {
+        txreq -req GET -url /404
+        rxresp
+        expect resp.status == 404
+        expect resp.http.x-err-type == "errors-1"
+} -run
+client c1r4  -connect ${h1_fe1_sock} {
+        txreq -req GET -url /500
+        rxresp
+        expect resp.status == 500
+        expect resp.http.x-err-type == "default"
+} -run
diff --git a/src/http_htx.c b/src/http_htx.c
index 78a5553..aabf1a5 100644
--- a/src/http_htx.c
+++ b/src/http_htx.c
@@ -1335,9 +1335,12 @@
 	reply->type = HTTP_REPLY_EMPTY;
 	reply->status = default_status;
 
-	cap = ((px->conf.args.ctx == ARGC_HRQ)
-	       ? ((px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR)
-	       : ((px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR));
+	if (px->conf.args.ctx == ARGC_HERR)
+		cap = (SMP_VAL_REQUEST | SMP_VAL_RESPONSE);
+	else
+		cap = ((px->conf.args.ctx == ARGC_HRQ)
+		       ? ((px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR)
+		       : ((px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR));
 
 	cur_arg = *orig_arg;
 	while (*args[cur_arg]) {
@@ -1837,6 +1840,80 @@
 	goto out;
 }
 
+/* Parses the "http-error" proxy keyword */
+static int proxy_parse_http_error(char **args, int section, struct proxy *curpx,
+				  struct proxy *defpx, const char *file, int line,
+				  char **errmsg)
+{
+	struct conf_errors *conf_err;
+	struct http_reply *reply = NULL;
+	int rc, cur_arg, ret = 0;
+
+	if (warnifnotcap(curpx, PR_CAP_FE | PR_CAP_BE, file, line, args[0], NULL)) {
+		ret = 1;
+		goto out;
+	}
+
+	cur_arg = 1;
+	curpx->conf.args.ctx = ARGC_HERR;
+	reply = http_parse_http_reply((const char **)args, &cur_arg, curpx, 0, errmsg);
+	if (!reply) {
+		memprintf(errmsg, "%s : %s", args[0], *errmsg);
+		goto error;
+	}
+	else if (!reply->status) {
+		memprintf(errmsg, "%s : expects at least a <status> as arguments.\n", args[0]);
+		goto error;
+	}
+
+	for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
+		if (http_err_codes[rc] == reply->status)
+			break;
+	}
+
+	if (rc >= HTTP_ERR_SIZE) {
+		memprintf(errmsg, "%s: status code '%d' not handled.", args[0], reply->status);
+		goto error;
+	}
+	if (*args[cur_arg]) {
+		memprintf(errmsg, "%s : unknown keyword '%s'.", args[0], args[cur_arg]);
+		goto error;
+	}
+
+	conf_err = calloc(1, sizeof(*conf_err));
+	if (!conf_err) {
+		memprintf(errmsg, "%s : out of memory.", args[0]);
+		goto error;
+	}
+	if (reply->type == HTTP_REPLY_ERRFILES) {
+		int rc = http_get_status_idx(reply->status);
+
+		conf_err->type = 2;
+		conf_err->info.errorfiles.name = reply->body.http_errors;
+		conf_err->info.errorfiles.status[rc] = 2;
+		reply->body.http_errors = NULL;
+		release_http_reply(reply);
+	}
+	else {
+		conf_err->type = 1;
+		conf_err->info.errorfile.status = reply->status;
+		conf_err->info.errorfile.reply = reply;
+		LIST_ADDQ(&http_replies_list, &reply->list);
+	}
+	conf_err->file = strdup(file);
+	conf_err->line = line;
+	LIST_ADDQ(&curpx->conf.errors, &conf_err->list);
+
+  out:
+	return ret;
+
+  error:
+	release_http_reply(reply);
+	ret = -1;
+	goto out;
+
+}
+
 /* Check "errorfiles" proxy keyword */
 static int proxy_check_errors(struct proxy *px)
 {
@@ -1849,6 +1926,10 @@
 			/* errorfile */
 			rc = http_get_status_idx(conf_err->info.errorfile.status);
 			px->replies[rc] = conf_err->info.errorfile.reply;
+
+			/* For proxy, to rely on default replies, just don't reference a reply */
+			if (px->replies[rc]->type == HTTP_REPLY_ERRMSG && !px->replies[rc]->body.errmsg)
+				px->replies[rc] = NULL;
 		}
 		else {
 			/* errorfiles */
@@ -2069,6 +2150,7 @@
         { CFG_LISTEN, "errorloc303",  proxy_parse_errorloc },
         { CFG_LISTEN, "errorfile",    proxy_parse_errorfile },
         { CFG_LISTEN, "errorfiles",   proxy_parse_errorfiles },
+        { CFG_LISTEN, "http-error",   proxy_parse_http_error },
         { 0, NULL, NULL },
 }};
 
diff --git a/src/log.c b/src/log.c
index 0aa043f..78f1a9e 100644
--- a/src/log.c
+++ b/src/log.c
@@ -310,6 +310,8 @@
 		return "spoe-message";
 	case ARGC_UBK:
 		return "use_backend";
+	case ARGC_HERR:
+		return "http-error";
 	default:
 		return "undefined(please report this bug)"; /* must never happen */
 	}
diff --git a/src/sample.c b/src/sample.c
index 47e74b5..b223547 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -1132,6 +1132,7 @@
 		case ARGC_ACL:   ctx = "ACL keyword"; break;
 		case ARGC_SRV:   where = "in server directive in"; break;
 		case ARGC_SPOE:  where = "in spoe-message directive in"; break;
+		case ARGC_HERR:  where = "in http-error directive in"; break;
 		}
 
 		/* set a few default settings */