MINOR: tools: support for word expansion of environment in parse_line

Allow the syntax "${...[*]}" to expand an environment variable
containing several values separated by spaces as individual arguments. A
new flag PARSE_OPT_WORD_EXPAND has been added to toggle this feature on
parse_line invocation. In case of an invalid syntax, a new error
PARSE_ERR_WRONG_EXPAND will be triggered.

This feature has been asked on the github issue #165.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 87f35e9..db4a4a7 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -475,7 +475,10 @@
 configuration parsing. Variable names must be preceded by a dollar ("$") and
 optionally enclosed with braces ("{}") similarly to what is done in Bourne
 shell. Variable names can contain alphanumerical characters or the character
-underscore ("_") but should not start with a digit.
+underscore ("_") but should not start with a digit. If the variable contains a
+list of several values separated by spaces, it can be expanded as individual
+arguments by enclosing the variable with braces and appending the suffix '[*]'
+before the closing brace.
 
   Example:
 
diff --git a/include/haproxy/tools-t.h b/include/haproxy/tools-t.h
index b1e7249..34f79be 100644
--- a/include/haproxy/tools-t.h
+++ b/include/haproxy/tools-t.h
@@ -57,6 +57,7 @@
 #define PARSE_OPT_DQUOTE        0x00000008      // '"' encloses a string
 #define PARSE_OPT_ENV           0x00000010      // '$' is followed by environment variables
 #define PARSE_OPT_INPLACE       0x00000020      // parse and tokenize in-place (src == dst)
+#define PARSE_OPT_WORD_EXPAND   0x00000040      // '[*]' suffix to expand an environment variable as several individual arguments
 
 /* return error flags from parse_line() */
 #define PARSE_ERR_TOOLARGE      0x00000001      // result is too large for initial outlen
@@ -66,6 +67,7 @@
 #define PARSE_ERR_HEX           0x00000010      // unparsable hex sequence (at errptr)
 #define PARSE_ERR_VARNAME       0x00000020      // invalid variable name (at errptr)
 #define PARSE_ERR_OVERLAP       0x00000040      // output overlaps with input, need to allocate
+#define PARSE_ERR_WRONG_EXPAND  0x00000080      // unparsable word expansion sequence
 
 /* special return values for the time parser (parse_time_err()) */
 #define PARSE_TIME_UNDER ((char *)1)
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 768fea3..cb48fef 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1908,7 +1908,8 @@
 			outlen = outlinesize;
 			err = parse_line(line, outline, &outlen, args, &arg,
 					 PARSE_OPT_ENV | PARSE_OPT_DQUOTE | PARSE_OPT_SQUOTE |
-					 PARSE_OPT_BKSLASH | PARSE_OPT_SHARP, &errptr);
+					 PARSE_OPT_BKSLASH | PARSE_OPT_SHARP | PARSE_OPT_WORD_EXPAND,
+					 &errptr);
 
 			if (err & PARSE_ERR_QUOTE) {
 				size_t newpos = sanitize_for_printing(line, errptr - line, 80);
@@ -1950,6 +1951,16 @@
 				goto next_line;
 			}
 
+			if (err & PARSE_ERR_WRONG_EXPAND) {
+				size_t newpos = sanitize_for_printing(line, errptr - line, 80);
+
+				ha_alert("parsing [%s:%d]: truncated or invalid word expansion sequence at position %d:\n"
+					 "  %s\n  %*s\n", file, linenum, (int)(errptr-thisline+1), line, (int)(newpos+1), "^");
+				err_code |= ERR_ALERT | ERR_FATAL;
+				fatal++;
+				goto next_line;
+			}
+
 			if (err & (PARSE_ERR_TOOLARGE|PARSE_ERR_OVERLAP)) {
 				outlinesize = (outlen + 1023) & -1024;
 				outline = realloc(outline, outlinesize);
diff --git a/src/tools.c b/src/tools.c
index 53d2486..c2a9664 100644
--- a/src/tools.c
+++ b/src/tools.c
@@ -4911,6 +4911,7 @@
 {
 	char *quote = NULL;
 	char *brace = NULL;
+	char *word_expand = NULL;
 	unsigned char hex1, hex2;
 	size_t outmax = *outlen;
 	int argsmax = *nbargs - 1;
@@ -5089,6 +5090,19 @@
 			value = getenv(var_name);
 			*in = save_char;
 
+			/* support for '[*]' sequence to force word expansion,
+			 * only available inside braces */
+			if (*in == '[' && brace && (opts & PARSE_OPT_WORD_EXPAND)) {
+				word_expand = in++;
+
+				if (*in++ != '*' || *in++ != ']') {
+					err |= PARSE_ERR_WRONG_EXPAND;
+					if (errptr)
+						*errptr = word_expand;
+					goto leave;
+				}
+			}
+
 			if (brace) {
 				if (*in != '}') {
 					/* unmatched brace */
@@ -5102,9 +5116,25 @@
 			}
 
 			if (value) {
-				while (*value)
-					EMIT_CHAR(*value++);
+				while (*value) {
+					/* expand as individual parameters on a space character */
+					if (word_expand && isspace(*value)) {
+						EMIT_CHAR(0);
+						++arg;
+						if (arg < argsmax)
+							args[arg] = out + outpos;
+						else
+							err |= PARSE_ERR_TOOMANY;
+
+						/* skip consecutive spaces */
+						while (isspace(*++value))
+							;
+					} else {
+						EMIT_CHAR(*value++);
+					}
+				}
 			}
+			word_expand = NULL;
 		}
 		else {
 			/* any other regular char */