MEDIUM: http_act: define set-timeout server/tunnel action

Add a new http-request action 'set-timeout [server/tunnel]'. This action
can be used to update the server or tunnel timeout of a stream. It takes
two parameters, the timeout name to update and the new timeout value.
This rule is only valid for a proxy with backend capabilities. The
timeout value cannot be null. A sample expression can also be used
instead of a plain value.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 81a90da..d4bfd1c 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -6265,6 +6265,24 @@
   the address family supports a port, otherwise it forces the source address to
   IPv4 "0.0.0.0" before rewriting the port.
 
+http-request set-timeout server|tunnel { <timeout> | <expr> }
+                                       [ { if | unless } <condition> ]
+
+  This action overrides the specified "server" or "tunnel" timeout for the
+  current stream only. The timeout can be specified in millisecond or with any
+  other unit if the number is suffixed by the unit as explained at the top of
+  this document. It is also possible to write an expression which must returns
+  a number interpreted as a timeout in millisecond.
+
+  Note that the server/tunnel timeouts are only relevant on the backend side
+  and thus this rule is only available for the proxies with backend
+  capabilities. Also the timeout value must be non-null to obtain the expected
+  results.
+
+  Example:
+    http-request set-timeout server 5s
+    http-request set-timeout hdr(host),map_int(host.lst)
+
 http-request set-tos <tos> [ { if | unless } <condition> ]
 
   This is used to set the TOS or DSCP field value of packets sent to the client
diff --git a/include/haproxy/action-t.h b/include/haproxy/action-t.h
index 73a846f..2ea524d 100644
--- a/include/haproxy/action-t.h
+++ b/include/haproxy/action-t.h
@@ -143,6 +143,11 @@
 			struct sample_expr *expr;
 			int idx;
 		} capid;
+		struct {
+			int value;                  /* plain timeout value in ms if no expr is used */
+			enum act_timeout_name type; /* timeout type */
+			struct sample_expr *expr;   /* timeout value as an expression */
+		} timeout;
 		struct hlua_rule *hlua_rule;
 		struct {
 			struct sample_expr *expr;
diff --git a/include/haproxy/action.h b/include/haproxy/action.h
index c7ce92d..c5c0a6c 100644
--- a/include/haproxy/action.h
+++ b/include/haproxy/action.h
@@ -25,6 +25,7 @@
 #include <stdio.h>
 #include <haproxy/action-t.h>
 #include <haproxy/list.h>
+#include <haproxy/sample.h>
 
 int act_resolution_cb(struct dns_requester *requester, struct dns_nameserver *nameserver);
 int act_resolution_error_cb(struct dns_requester *requester, int error_code);
@@ -90,4 +91,14 @@
  */
 int check_capture(struct act_rule *rule, struct proxy *px, char **err);
 
+int cfg_parse_rule_set_timeout(const char **args, int idx, int *out_timeout,
+                               enum act_timeout_name *name,
+                               struct sample_expr **expr, char **err,
+                               const char *file, int line, struct arg_list *al);
+
+static inline void release_timeout_action(struct act_rule *rule)
+{
+	release_sample_expr(rule->arg.timeout.expr);
+}
+
 #endif /* _HAPROXY_ACTION_H */
diff --git a/src/action.c b/src/action.c
index 870a839..c4c263f 100644
--- a/src/action.c
+++ b/src/action.c
@@ -149,3 +149,60 @@
 	return 0;
 }
 
+/* Parse a set-timeout rule statement. It first checks if the timeout name is
+ * valid and returns it in <name>. Then the timeout is parsed as a plain value
+ * and * returned in <out_timeout>. If there is a parsing error, the value is
+ * reparsed as an expression and returned in <expr>.
+ *
+ * Returns -1 if the name is invalid or neither a time or an expression can be
+ * parsed, or if the timeout value is 0.
+ */
+int cfg_parse_rule_set_timeout(const char **args, int idx, int *out_timeout,
+                               enum act_timeout_name *name,
+                               struct sample_expr **expr, char **err,
+                               const char *file, int line, struct arg_list *al)
+{
+	const char *res;
+	const char *timeout_name = args[idx++];
+
+	if (!strcmp(timeout_name, "server")) {
+		*name = ACT_TIMEOUT_SERVER;
+	}
+	else if (!strcmp(timeout_name, "tunnel")) {
+		*name = ACT_TIMEOUT_TUNNEL;
+	}
+	else {
+		memprintf(err,
+		          "'set-timeout' rule supports 'server'/'tunnel' (got '%s')",
+		          timeout_name);
+		return -1;
+	}
+
+	res = parse_time_err(args[idx], (unsigned int *)out_timeout, TIME_UNIT_MS);
+	if (res == PARSE_TIME_OVER) {
+		memprintf(err, "timer overflow in argument '%s' to rule 'set-timeout %s' (maximum value is 2147483647 ms or ~24.8 days)",
+			  args[idx], timeout_name);
+		return -1;
+	}
+	else if (res == PARSE_TIME_UNDER) {
+		memprintf(err, "timer underflow in argument '%s' to rule 'set-timeout %s' (minimum value is 1 ms)",
+			  args[idx], timeout_name);
+		return -1;
+	}
+	/* res not NULL, parsing error */
+	else if (res) {
+		*expr = sample_parse_expr((char **)args, &idx, file, line, err, al, NULL);
+		if (!*expr) {
+			memprintf(err, "unexpected character '%c' in rule 'set-timeout %s'", *res, timeout_name);
+			return -1;
+		}
+	}
+	/* res NULL, parsing ok but value is 0 */
+	else if (!(*out_timeout)) {
+		memprintf(err, "null value is not valid for a 'set-timeout %s' rule",
+			  timeout_name);
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/src/http_act.c b/src/http_act.c
index 85a534e..140cdf1 100644
--- a/src/http_act.c
+++ b/src/http_act.c
@@ -1901,6 +1901,67 @@
 	return ACT_RET_PRS_OK;
 }
 
+static enum act_return action_timeout_set_stream_timeout(struct act_rule *rule,
+                                                         struct proxy *px,
+                                                         struct session *sess,
+                                                         struct stream *s,
+                                                         int flags)
+{
+	struct sample *key;
+
+	if (rule->arg.timeout.expr) {
+		key = sample_fetch_as_type(px, sess, s, SMP_OPT_FINAL, rule->arg.timeout.expr, SMP_T_SINT);
+		if (!key)
+			return ACT_RET_CONT;
+
+		stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(key->data.u.sint));
+	}
+	else {
+		stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(rule->arg.timeout.value));
+	}
+
+	return ACT_RET_CONT;
+}
+
+/* Parse a "set-timeout" action. Returns ACT_RET_PRS_ERR if parsing error.
+ */
+static enum act_parse_ret parse_http_set_timeout(const char **args,
+                                                 int *orig_arg,
+                                                 struct proxy *px,
+                                                 struct act_rule *rule, char **err)
+{
+	int cur_arg;
+
+	rule->action = ACT_CUSTOM;
+	rule->action_ptr = action_timeout_set_stream_timeout;
+	rule->release_ptr = release_timeout_action;
+
+	cur_arg = *orig_arg;
+	if (!*args[cur_arg] || !*args[cur_arg + 1]) {
+		memprintf(err, "expects exactly 2 arguments");
+		return ACT_RET_PRS_ERR;
+	}
+
+	if (!(px->cap & PR_CAP_BE)) {
+		memprintf(err, "proxy '%s' has no backend capability", px->id);
+		return ACT_RET_PRS_ERR;
+	}
+
+	if (cfg_parse_rule_set_timeout(args, cur_arg,
+	                               &rule->arg.timeout.value,
+	                               &rule->arg.timeout.type,
+	                               &rule->arg.timeout.expr,
+	                               err,
+	                               px->conf.args.file,
+	                               px->conf.args.line, &px->conf.args) == -1) {
+		return ACT_RET_PRS_ERR;
+	}
+
+	*orig_arg = cur_arg + 2;
+
+	return ACT_RET_PRS_OK;
+}
+
 /* This function executes a strict-mode actions. On success, it always returns
  * ACT_RET_CONT
  */
@@ -2034,6 +2095,7 @@
 		{ "strict-mode",      parse_http_strict_mode,          0 },
 		{ "tarpit",           parse_http_deny,                 0 },
 		{ "track-sc",         parse_http_track_sc,             1 },
+		{ "set-timeout",      parse_http_set_timeout,          0 },
 		{ NULL, NULL }
 	}
 };