MINOR: http: add a new "http-request replace-uri" action

This action is particularly convenient to replace some deprecated usees
of "reqrep". It takes a match and a format string including back-
references. The reqrep warning was updated to suggest it as well.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index fa12a16..bf1e4cb 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4447,6 +4447,33 @@
 
       # assuming the backend IP is 192.168.1.20
 
+http-request replace-uri <match-regex> <replace-fmt>
+                           [ { if | unless } <condition> ]
+
+    This matches the regular expression in the URI part of the request
+    according to <match-regex>, and replaces it with the <replace-fmt>
+    argument. Standard back-references using the backslash ('\') followed by a
+    number are supported. The <fmt> field is interpreted as a log-format string
+    so it may contain special expressions just like the <fmt> argument passed
+    to "http-request set-uri". The match is exclusively case-sensitive. Any
+    optional scheme, authority or query string are considered in the matching
+    part of the URI. It is worth noting that regular expressions may be more
+    expensive to evaluate than certain ACLs, so rare replacements may benefit
+    from a condition to avoid performing the evaluation at all if it does not
+    match.
+
+    Example:
+        # prefix /foo : turn /bar?q=1 into /foo/bar?q=1 :
+        http-request replace-uri (.*) /foo\1
+
+        # suffix /foo : turn /bar?q=1 into /bar/foo?q=1 :
+        http-request replace-uri ([^?]*)(\?(.*))? \1/foo\2
+
+        # strip /foo : turn /foo/bar?q=1 into /bar?q=1
+        http-request replace-uri /foo/(.*) /\1
+        # or more efficient if only some requests match :
+        http-request replace-uri /foo/(.*) /\1 if { url_beg /foo/ }
+
 http-request replace-value <name> <match-regex> <replace-fmt>
                            [ { if | unless } <condition> ]
 
diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c
index d659779..6c18bfc 100644
--- a/src/cfgparse-listen.c
+++ b/src/cfgparse-listen.c
@@ -3866,7 +3866,7 @@
 	}
 	else if (!strcmp(args[0], "cliexp") || !strcmp(args[0], "reqrep")) {  /* replace request header from a regex */
 		if (!already_warned(WARN_REQREP_DEPRECATED))
-			ha_warning("parsing [%s:%d] : The '%s' directive is deprecated in favor of 'http-request replace-header' and will be removed in next version.\n", file, linenum, args[0]);
+			ha_warning("parsing [%s:%d] : The '%s' directive is deprecated in favor of 'http-request replace-uri' and 'http-request replace-header' and will be removed in next version.\n", file, linenum, args[0]);
 
 		if (*(args[2]) == 0) {
 			ha_alert("parsing [%s:%d] : '%s' expects <search> and <replace> as arguments.\n",
diff --git a/src/http_act.c b/src/http_act.c
index daa789a..65d9595 100644
--- a/src/http_act.c
+++ b/src/http_act.c
@@ -32,6 +32,7 @@
 #include <proto/acl.h>
 #include <proto/arg.h>
 #include <proto/http_rules.h>
+#include <proto/http_htx.h>
 #include <proto/log.h>
 #include <proto/proto_http.h>
 #include <proto/stream_interface.h>
@@ -128,6 +129,93 @@
 	return ACT_RET_PRS_OK;
 }
 
+/* This function executes a replace-uri action. It finds its arguments in
+ * <rule>.arg.act.p[]. It builds a string in the trash from the format string
+ * previously filled by function parse_replace_uri() and will execute the regex
+ * in p[1] to replace the URI. It uses the format string present in act.p[2..3].
+ * It always returns ACT_RET_CONT. If an error occurs, the action is canceled,
+ * but the rule processing continues.
+ */
+static enum act_return http_action_replace_uri(struct act_rule *rule, struct proxy *px,
+                                               struct session *sess, struct stream *s, int flags)
+{
+	enum act_return ret = ACT_RET_ERR;
+	struct buffer *replace, *output;
+	struct ist uri;
+	int len;
+
+	replace = alloc_trash_chunk();
+	output  = alloc_trash_chunk();
+	if (!replace || !output)
+		goto leave;
+
+	if (IS_HTX_STRM(s))
+		uri = htx_sl_req_uri(http_get_stline(htxbuf(&s->req.buf)));
+	else
+		uri = ist2(ci_head(&s->req) + s->txn->req.sl.rq.u, s->txn->req.sl.rq.u_l);
+
+	if (!regex_exec_match2(rule->arg.act.p[1], uri.ptr, uri.len, MAX_MATCH, pmatch, 0))
+		goto leave;
+
+	replace->data = build_logline(s, replace->area, replace->size, (struct list *)&rule->arg.act.p[2]);
+
+	/* note: uri.ptr doesn't need to be zero-terminated because it will
+	 * only be used to pick pmatch references.
+	 */
+	len = exp_replace(output->area, output->size, uri.ptr, replace->area, pmatch);
+	if (len == -1)
+		goto leave;
+
+	/* 3 is the set-uri action */
+	http_replace_req_line(3, output->area, len, px, s);
+
+	ret = ACT_RET_CONT;
+
+leave:
+	free_trash_chunk(output);
+	free_trash_chunk(replace);
+	return ret;
+}
+
+/* parse a "replace-uri" http-request action.
+ * This action takes 2 arguments (a regex and a replacement format string).
+ * The resulting rule makes use of arg->act.p[0] to store the action (0 for now),
+ * p[1] to store the compiled regex, and arg->act.p[2..3] to store the log-format
+ * list head. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
+ */
+static enum act_parse_ret parse_replace_uri(const char **args, int *orig_arg, struct proxy *px,
+                                            struct act_rule *rule, char **err)
+{
+	int cur_arg = *orig_arg;
+	char *error = NULL;
+
+	rule->action = ACT_CUSTOM;
+	rule->arg.act.p[0] = (void *)0; // replace-uri
+	rule->action_ptr = http_action_replace_uri;
+
+	if (!*args[cur_arg] || !*args[cur_arg+1] ||
+	    (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
+		memprintf(err, "expects exactly 2 arguments <match-regex> and <replace-format>");
+		return ACT_RET_PRS_ERR;
+	}
+
+	if (!(rule->arg.act.p[1] = regex_comp(args[cur_arg], 1, 1, &error))) {
+		memprintf(err, "failed to parse the regex : %s", error);
+		free(error);
+		return ACT_RET_PRS_ERR;
+	}
+
+	LIST_INIT((struct list *)&rule->arg.act.p[2]);
+	px->conf.args.ctx = ARGC_HRQ;
+	if (!parse_logformat_string(args[cur_arg + 1], px, (struct list *)&rule->arg.act.p[2], LOG_OPT_HTTP,
+	                            (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) {
+		return ACT_RET_PRS_ERR;
+	}
+
+	(*orig_arg) += 2;
+	return ACT_RET_PRS_OK;
+}
+
 /* This function is just a compliant action wrapper for "set-status". */
 static enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px,
                                               struct session *sess, struct stream *s, int flags)
@@ -608,6 +696,7 @@
 		{ "capture",    parse_http_req_capture },
 		{ "reject",     parse_http_action_reject },
 		{ "disable-l7-retry", parse_http_req_disable_l7_retry },
+		{ "replace-uri", parse_replace_uri },
 		{ "set-method", parse_set_req_line },
 		{ "set-path",   parse_set_req_line },
 		{ "set-query",  parse_set_req_line },