MEDIUM: http: implement http-request set-{method,path,query,uri}

This commit implements the following new actions :

- "set-method" rewrites the request method with the result of the
  evaluation of format string <fmt>. There should be very few valid reasons
  for having to do so as this is more likely to break something than to fix
  it.

- "set-path" rewrites the request path with the result of the evaluation of
  format string <fmt>. The query string, if any, is left intact. If a
  scheme and authority is found before the path, they are left intact as
  well. If the request doesn't have a path ("*"), this one is replaced with
  the format. This can be used to prepend a directory component in front of
  a path for example. See also "set-query" and "set-uri".

  Example :
      # prepend the host name before the path
      http-request set-path /%[hdr(host)]%[path]

- "set-query" rewrites the request's query string which appears after the
  first question mark ("?") with the result of the evaluation of format
  string <fmt>. The part prior to the question mark is left intact. If the
  request doesn't contain a question mark and the new value is not empty,
  then one is added at the end of the URI, followed by the new value. If
  a question mark was present, it will never be removed even if the value
  is empty. This can be used to add or remove parameters from the query
  string. See also "set-query" and "set-uri".

  Example :
      # replace "%3D" with "=" in the query string
      http-request set-query %[query,regsub(%3D,=,g)]

- "set-uri" rewrites the request URI with the result of the evaluation of
  format string <fmt>. The scheme, authority, path and query string are all
  replaced at once. This can be used to rewrite hosts in front of proxies,
  or to perform complex modifications to the URI such as moving parts
  between the path and the query string. See also "set-path" and
  "set-query".

All of them are handled by the same parser and the same exec function,
which is why they're merged all together. For once, instead of adding
even more entries to the huge switch/case, we used the new facility to
register action keywords. A number of the existing ones should probably
move there as well.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 380fb83..60d1df2 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2971,7 +2971,8 @@
               del-header <name> | set-nice <nice> | set-log-level <level> |
               replace-header <name> <match-regex> <replace-fmt> |
               replace-value <name> <match-regex> <replace-fmt> |
-              set-tos <tos> | set-mark <mark> |
+              set-method <fmt> | set-path <fmt> | set-query <fmt> |
+              set-uri <fmt> | set-tos <tos> | set-mark <mark> |
               add-acl(<file name>) <key fmt> |
               del-acl(<file name>) <key fmt> |
               del-map(<file name>) <key fmt> |
@@ -3081,6 +3082,42 @@
 
         X-Forwarded-For: 172.16.10.1, 172.16.13.24, 10.0.0.37
 
+    - "set-method" rewrites the request method with the result of the
+      evaluation of format string <fmt>. There should be very few valid reasons
+      for having to do so as this is more likely to break something than to fix
+      it.
+
+    - "set-path" rewrites the request path with the result of the evaluation of
+      format string <fmt>. The query string, if any, is left intact. If a
+      scheme and authority is found before the path, they are left intact as
+      well. If the request doesn't have a path ("*"), this one is replaced with
+      the format. This can be used to prepend a directory component in front of
+      a path for example. See also "set-query" and "set-uri".
+
+      Example :
+          # prepend the host name before the path
+          http-request set-path /%[hdr(host)]%[path]
+
+    - "set-query" rewrites the request's query string which appears after the
+      first question mark ("?") with the result of the evaluation of format
+      string <fmt>. The part prior to the question mark is left intact. If the
+      request doesn't contain a question mark and the new value is not empty,
+      then one is added at the end of the URI, followed by the new value. If
+      a question mark was present, it will never be removed even if the value
+      is empty. This can be used to add or remove parameters from the query
+      string. See also "set-query" and "set-uri".
+
+      Example :
+          # replace "%3D" with "=" in the query string
+          http-request set-query %[query,regsub(%3D,=,g)]
+
+    - "set-uri" rewrites the request URI with the result of the evaluation of
+      format string <fmt>. The scheme, authority, path and query string are all
+      replaced at once. This can be used to rewrite hosts in front of proxies,
+      or to perform complex modifications to the URI such as moving parts
+      between the path and the query string. See also "set-path" and
+      "set-query".
+
     - "set-nice" sets the "nice" factor of the current request being processed.
       It only has effect against the other requests being processed at the same
       time. The default value is 0, unless altered by the "nice" setting on the
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index 2654b78..d9cda71 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -438,6 +438,9 @@
 			struct list key;       /* pattern to retrieve MAP or ACL key */
 			struct list value;     /* pattern to retrieve MAP value */
 		} map;
+		struct {
+			void *p[4];
+		} act;                         /* generic pointers to be used by custom actions */
 	} arg;                                 /* arguments used by some actions */
 
 	union {
diff --git a/src/proto_http.c b/src/proto_http.c
index ac1c60fb..3c87478 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -11476,6 +11476,149 @@
 	return smp->data.str.len != 0;
 }
 
+/* This function executes one of the set-{method,path,query,uri} actions. It
+ * builds a string in the trash from the specified format string, then modifies
+ * the relevant part of the request line accordingly. Then it updates various
+ * pointers to the next elements which were moved, and the total buffer length.
+ * It finds the action to be performed in p[2], previously filled by function
+ * parse_set_req_line(). It returns 0 in case of success, -1 in case of internal
+ * error, though this can be revisited when this code is finally exploited.
+ */
+int http_action_set_req_line(struct http_req_rule *rule, struct proxy *px, struct session *s, struct http_txn *txn)
+{
+	char *cur_ptr, *cur_end;
+	int offset;
+	int delta;
+
+	chunk_reset(&trash);
+
+	/* prepare a '?' just in case we have to create a query string */
+	trash.str[trash.len++] = '?';
+	offset = 1;
+	trash.len += build_logline(s, trash.str + trash.len, trash.size - trash.len, (struct list *)&rule->arg.act.p[0]);
+
+	switch (*(int *)&rule->arg.act.p[2]) {
+	case 0: // method
+		cur_ptr = s->req->buf->p;
+		cur_end = cur_ptr + txn->req.sl.rq.m_l;
+
+		/* adjust req line offsets and lengths */
+		delta = trash.len - offset - (cur_end - cur_ptr);
+		txn->req.sl.rq.m_l += delta;
+		txn->req.sl.rq.u   += delta;
+		txn->req.sl.rq.v   += delta;
+		break;
+
+	case 1: // path
+		cur_ptr = http_get_path(txn);
+		if (!cur_ptr)
+			cur_ptr = s->req->buf->p + txn->req.sl.rq.u;
+
+		cur_end = cur_ptr;
+		while (cur_end < s->req->buf->p + txn->req.sl.rq.u + txn->req.sl.rq.u_l && *cur_end != '?')
+			cur_end++;
+
+		/* adjust req line offsets and lengths */
+		delta = trash.len - offset - (cur_end - cur_ptr);
+		txn->req.sl.rq.u_l += delta;
+		txn->req.sl.rq.v   += delta;
+		break;
+
+	case 2: // query
+		cur_ptr = s->req->buf->p + txn->req.sl.rq.u;
+		cur_end = cur_ptr + txn->req.sl.rq.u_l;
+		while (cur_ptr < cur_end && *cur_ptr != '?')
+			cur_ptr++;
+
+		/* skip the question mark or indicate that we must insert it
+		 * (but only if the format string is not empty then).
+		 */
+		if (cur_ptr < cur_end)
+			cur_ptr++;
+		else if (trash.len > 1)
+			offset = 0;
+
+		/* adjust req line offsets and lengths */
+		delta = trash.len - offset - (cur_end - cur_ptr);
+		txn->req.sl.rq.u_l += delta;
+		txn->req.sl.rq.v   += delta;
+		break;
+
+	case 3: // uri
+		cur_ptr = s->req->buf->p + txn->req.sl.rq.u;
+		cur_end = cur_ptr + txn->req.sl.rq.u_l;
+
+		/* adjust req line offsets and lengths */
+		delta = trash.len - offset - (cur_end - cur_ptr);
+		txn->req.sl.rq.u_l += delta;
+		txn->req.sl.rq.v   += delta;
+		break;
+
+	default:
+		return -1;
+	}
+
+	/* commit changes and adjust end of message */
+	delta = buffer_replace2(s->req->buf, cur_ptr, cur_end, trash.str + offset, trash.len - offset);
+	http_msg_move_end(&txn->req, delta);
+	return 0;
+}
+
+/* parse an http-request action among :
+ *   set-method
+ *   set-path
+ *   set-query
+ *   set-uri
+ *
+ * All of them accept a single argument of type string representing a log-format.
+ * The resulting rule makes use of arg->act.p[0..1] to store the log-format list
+ * head, and p[2] to store the action as an int (0=method, 1=path, 2=query, 3=uri).
+ * It returns 0 on success, < 0 on error.
+ */
+int parse_set_req_line(const char **args, int *orig_arg, struct proxy *px, struct http_req_rule *rule, char **err)
+{
+	int cur_arg = *orig_arg;
+
+	rule->action = HTTP_REQ_ACT_CUSTOM_CONT;
+
+	switch (args[0][4]) {
+	case 'm' :
+		*(int *)&rule->arg.act.p[2] = 0;
+		rule->action_ptr = http_action_set_req_line;
+		break;
+	case 'p' :
+		*(int *)&rule->arg.act.p[2] = 1;
+		rule->action_ptr = http_action_set_req_line;
+		break;
+	case 'q' :
+		*(int *)&rule->arg.act.p[2] = 2;
+		rule->action_ptr = http_action_set_req_line;
+		break;
+	case 'u' :
+		*(int *)&rule->arg.act.p[2] = 3;
+		rule->action_ptr = http_action_set_req_line;
+		break;
+	default:
+		memprintf(err, "internal error: unhandled action '%s'", args[0]);
+		return -1;
+	}
+
+	if (!*args[cur_arg] ||
+	    (*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) {
+		memprintf(err, "expects exactly 1 argument <format>");
+		return -1;
+	}
+
+	LIST_INIT((struct list *)&rule->arg.act.p[0]);
+	proxy->conf.args.ctx = ARGC_HRQ;
+	parse_logformat_string(args[cur_arg], proxy, (struct list *)&rule->arg.act.p[0], LOG_OPT_HTTP,
+			       (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR,
+			       proxy->conf.args.file, proxy->conf.args.line);
+
+	(*orig_arg)++;
+	return 0;
+}
+
 /*
  * Return the struct http_req_action_kw associated to a keyword.
  */
@@ -11715,6 +11858,9 @@
 }};
 
 
+/************************************************************************/
+/*        All supported converter keywords must be declared here.       */
+/************************************************************************/
 /* Note: must not be declared <const> as its list will be overwritten */
 static struct sample_conv_kw_list sample_conv_kws = {ILH, {
 	{ "http_date", sample_conv_http_date,  ARG1(0,SINT),     NULL, SMP_T_UINT, SMP_T_STR},
@@ -11722,12 +11868,26 @@
 	{ NULL, NULL, 0, 0, 0 },
 }};
 
+/************************************************************************/
+/*   All supported http-request action keywords must be declared here.  */
+/************************************************************************/
+struct http_req_action_kw_list http_req_actions = {
+	.scope = "http",
+	.kw = {
+		{ "set-method", parse_set_req_line },
+		{ "set-path",   parse_set_req_line },
+		{ "set-query",  parse_set_req_line },
+		{ "set-uri",    parse_set_req_line },
+	}
+};
+
 __attribute__((constructor))
 static void __http_protocol_init(void)
 {
 	acl_register_keywords(&acl_kws);
 	sample_register_fetches(&sample_fetch_keywords);
 	sample_register_convs(&sample_conv_kws);
+	http_req_keywords_register(&http_req_actions);
 }