MEDIUM: cli: rework the CLI proxy parser

Rework the CLI proxy parser to look more like the CLI parser, corner
case and escaping are handled the same way.

The parser now splits the commands in words instead of just handling
the prefixes.

It's easier to compare words and arguments of a command this way and to
parse internal command that will be consumed directly by the CLI proxy.
diff --git a/src/cli.c b/src/cli.c
index 0fbb6f3..e7682f5 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -1679,6 +1679,10 @@
 {
 	struct mworker_proc *child;
 
+	/* return the CLI applet of the master */
+	if (proc_pid == 0)
+		return &cli_applet.obj_type;
+
 	list_for_each_entry(child, &proc_list, list) {
 		if (child->pid == proc_pid){
 			return &child->srv->obj_type;
@@ -1752,123 +1756,154 @@
 	return -1;
 }
 
-/* Parse the CLI request:
- *
- *  - it can rewrite the buffer by trimming the prefix
- *  - fill dst with the destination server if there is one
+/* Return::
+ *  >= 0 : number of words to escape
+ *  = -1 : error
+ */
+
+int pcli_find_and_exec_kw(struct stream *s, char **args, int argl, char **errmsg, int *next_pid)
+{
+	if (argl < 1)
+		return 0;
+
+	/* there is a prefix */
+	if (args[0][0] == '@') {
+		int target_pid = pcli_prefix_to_pid(args[0]);
+
+		if (target_pid == -1) {
+			memprintf(errmsg, "Can't find the target PID matching the prefix '%s'\n", args[0]);
+			return -1;
+		}
+
+		/* if the prefix is alone, define a default target */
+		if (argl == 1)
+			s->pcli_next_pid = target_pid;
+		else
+			*next_pid = target_pid;
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Parse the CLI request:
+ *  - It does basically the same as the cli_io_handler, but as a proxy
+ *  - It can exec a command and strip non forwardable commands
  *
  *  Return:
- *  - the amount of data to forward or
- *  - -1 if there is no end to the command or
- *  - 0 everything has been trimmed (only a prefix)
+ *  - the number of characters to forward or
+ *  - 1 if there is an error or not enough data
  */
-#define PCLI_REQ_INIT      0
-#define PCLI_REQ_PFX       1
-#define PCLI_REQ_TRIM      2
-#define PCLI_REQ_CMD       3
-
-int pcli_parse_request(struct channel *req, int *target_pid)
+int pcli_parse_request(struct stream *s, struct channel *req, char **errmsg, int *next_pid)
 {
-	char *input = (char *)ci_head(req);
-	const char *end;
-	char *ptr, *trim = NULL, *pfx_b = NULL, *cmd_b = NULL;
-	struct buffer *buf = &req->buf;
-	int ret = 0;
-	int state = PCLI_REQ_INIT;
+	char *str = (char *)ci_head(req);
+	char *end = (char *)ci_stop(req);
+	char *args[MAX_STATS_ARGS + 1]; /* +1 for storing a NULL */
+	int argl; /* number of args */
+	char *p;
+	char *trim = NULL;
+	int wtrim = 0; /* number of words to trim */
+	int reql = 0;
+	int i = 0;
+
+	p = str;
 
-	ptr = input;
-	end = b_stop(buf);
+	/* Looks for the end of one command */
+	while (p+reql < end) {
+		/* handle escaping */
+		if (str[reql] == '\\') {
+			reql++;
+			continue;
+		}
+		if (str[reql] == ';' || str[reql] == '\n') {
+			/* found the end of the command */
+			str[reql] = '\n';
+			reql++;
+			break;
+		}
+		reql++;
+	}
 
-	 /* The while loop condition is checking the end of the command.
-	    It is needed to iterate for each ptr++ done in the parser */
-	while (ptr < end && *ptr != '\n' && *ptr != '\r' && *ptr != ';') {
-		switch (state) {
-			/* The init state only trims the useless chars */
-			case PCLI_REQ_INIT:
+	/* set end to first byte after the end of the command */
+	end = p + reql;
 
-				/* skip every  spaces at the start of the command */
-				if (*ptr == ' ') {
-					ptr++;
-					continue;
-				}
-				pfx_b = ptr; /* this is the start of the command or of the @ prefix */
-				state = PCLI_REQ_PFX;
+	/* there is no end to this command, need more to parse ! */
+	if (*(end-1) != '\n') {
+		return -1;
+	}
 
-			/* the atprefix state looks for a @ prefix. If it finds
-			   it, it will check to which server send the request.
-			   It also ajust the trim pointer */
-			case PCLI_REQ_PFX:
+	*(end-1) = '\0';
 
-				if (*pfx_b != '@') {
-					/* there is no prefix */
-					pfx_b = NULL;
-					cmd_b = input; /* if no prefix we don't trim anything */
-					state = PCLI_REQ_CMD;
-					continue;
-				}
+	/* splits the command in words */
+	while (i < MAX_STATS_ARGS && p < end) {
+		int j, k;
 
-				if (*ptr != ' ') {
-					ptr++;
-					continue;
-				}
-				*ptr = '\0';  /* this the end of the prefix */
-				ptr++;
-				trim = ptr;
-				state = PCLI_REQ_TRIM;
+		/* skip leading spaces/tabs */
+		p += strspn(p, " \t");
+		if (!*p)
 			break;
 
-			/* we really need to trim there because that's the only
-			   way to know if we are going to send a command or if
-			   there is only a prefix */
-			case PCLI_REQ_TRIM:
-				if (*ptr == ' ') {
-					ptr++;
-					continue;
-				}
-				cmd_b = trim = ptr;
-				state = PCLI_REQ_CMD;
+		args[i] = p;
+		p += strcspn(p, " \t");
+		*p++ = 0;
 
-			/* just look for the end of the command */
-			case PCLI_REQ_CMD:
-				ptr++;
-				continue;
+		/* unescape backslashes (\) */
+		for (j = 0, k = 0; args[i][k]; k++) {
+			if (args[i][k] == '\\') {
+				if (args[i][k + 1] == '\\')
+					k++;
+				else
+					continue;
+			}
+			args[i][j] = args[i][k];
+			j++;
 		}
+		args[i][j] = 0;
+		i++;
 	}
 
-	/* we didn't find a command separator, not enough data */
-	if (ptr >= end)
-		return -1;
+	argl = i;
 
-	if (!pfx_b && !cmd_b) {
-		/* probably just a \n or a ; */
-		return 1;
-	} else if (pfx_b && !cmd_b) {
-		/* it's only a prefix, we don't want to forward it */
-		*ptr = '\0';
-		trim = ptr + 1; /* we want to trim the whole command */
-		ret = 0;
-	} else if (cmd_b) {
-		/* command without a prefix */
-		*ptr = '\n';
-		ret = ptr - cmd_b + 1;
+	for (; i < MAX_STATS_ARGS + 1; i++)
+		args[i] = NULL;
+
+	wtrim = pcli_find_and_exec_kw(s, args, argl, errmsg, next_pid);
+
+	/* End of words are ending by \0, we need to replace the \0s by spaces
+1	   before forwarding them */
+	p = str;
+	while (p < end) {
+		if (*p == '\0')
+			*p = ' ';
+		p++;
 	}
 
-	if (pfx_b)
-		*target_pid = pcli_prefix_to_pid(pfx_b);
+	*(end-1) = '\n';
 
-	/* trim the useless chars */
-	if (trim)
-		b_del(&req->buf, trim - input);
+	if (wtrim > 0) {
+		trim = &args[wtrim][0];
+		if (trim == NULL) /* if this was the last word in the table */
+			trim = end;
 
-	return ret;
+		b_del(&req->buf, trim - str);
+
+		return end - trim;
+	} else if (wtrim < 0) {
+		/* parsing error */
+		return -1;
+	}
+
+	/* foward the whole comand */
+	return end - str;
+
 }
 
 int pcli_wait_for_request(struct stream *s, struct channel *req, int an_bit)
 {
-	int target_pid;
+	int next_pid = -1;
 	int to_forward;
-
-	target_pid = s->pcli_next_pid;
+	char *errmsg = NULL;
 
 read_again:
 	/* if the channel is closed for read, we won't receive any more data
@@ -1898,16 +1933,11 @@
 	if (c_data(req) && s->logs.t_idle == -1)
 		s->logs.t_idle = tv_ms_elapsed(&s->logs.tv_accept, &now) - s->logs.t_handshake;
 
-	to_forward = pcli_parse_request(req, &target_pid);
+	to_forward = pcli_parse_request(s, req, &errmsg, &next_pid);
 	if (to_forward > 0) {
+		int target_pid;
 		/* enough data */
 
-		/* we didn't find the process, send an error and close */
-		if (target_pid < 0) {
-			pcli_reply_and_close(s, "Can't find the target CLI!\n");
-			return 0;
-		}
-
 		/* forward only 1 command */
 		channel_forward(req, to_forward);
 		/* we send only 1 command per request, and we write close after it */
@@ -1921,29 +1951,24 @@
 		req->analysers |= AN_REQ_FLT_END|CF_FLT_ANALYZE;
 		s->res.analysers |= AN_RES_WAIT_CLI;
 
+		if (next_pid > -1)
+			target_pid = next_pid;
+		else
+			target_pid = s->pcli_next_pid;
 		/* we can connect now */
 		s->target = pcli_pid_to_server(target_pid);
-		if (!s->target) {
-			s->target = &cli_applet.obj_type;
-		}
 
 		s->flags |= (SF_DIRECT | SF_ASSIGNED);
 		channel_auto_connect(req);
 
 	} else if (to_forward == 0) {
-		/* we only received a prefix without command, which
-		   mean that we want to store it for every other
-		   command for this session */
-		if (target_pid > -1) {
-			s->pcli_next_pid = target_pid;
-			pcli_write_prompt(s);
-		} else {
-			s->pcli_next_pid = 0;
-			pcli_reply_and_close(s, "Can't find the target CLI!\n");
-		}
-
 		/* we trimmed things but we might have other commands to consume */
+		pcli_write_prompt(s);
 		goto read_again;
+	} else if (to_forward == -1 && errmsg) {
+		/* there was an error during the parsing */
+			pcli_reply_and_close(s, errmsg);
+			return 0;
 	} else if (to_forward == -1 && channel_full(req, global.tune.maxrewrite)) {
 		/* buffer is full and we didn't catch the end of a command */
 		goto send_help;