MEDIUM: cfgparse: introduce weak and strong quoting
This patch introduces quoting which allows to write configuration string
including spaces without escaping them.
Strong (with single quotes) and weak (with double quotes) quoting are
supported. Weak quoting supports escaping and special characters when
strong quoting does not interpret anything.
This patch could break configuration files where ' and " where used.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 85d94d9..9a6202d 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -39,8 +39,9 @@
2. Configuring HAProxy
2.1. Configuration file format
-2.2. Time format
-2.3. Examples
+2.2. Quoting and escaping
+2.3. Time format
+2.4. Examples
3. Global parameters
3.1. Process management and security
@@ -366,12 +367,61 @@
The configuration file syntax consists in lines beginning with a keyword
referenced in this manual, optionally followed by one or several parameters
-delimited by spaces. If spaces have to be entered in strings, then they must be
-preceded by a backslash ('\') to be escaped. Backslashes also have to be
-escaped by doubling them.
+delimited by spaces.
-2.2. Time format
+2.2. Quoting and escaping
+-------------------------
+
+HAProxy's configuration introduces a quoting and escaping system similar to
+many programming languages. The configuration file supports 3 types: escaping
+with a backslash, weak quoting with double quotes, and strong quoting with
+single quotes.
+
+If spaces have to be entered in strings, then they must be escaped by preceding
+them by a backslash ('\') or by quoting them. Backslashes also have to be
+escaped by doubling or strong quoting them.
+
+Escaping is achieved by preceding a special character by a backslash ('\'):
+
+ \ to mark a space and differentiate it from a delimiter
+ \# to mark a hash and differentiate it from a comment
+ \\ to use a backslash
+ \' to use a single quote and differentiate it from strong quoting
+ \" to use a double quote and differentiate it from weak quoting
+
+Weak quoting is achieved by using double quotes (""). Weak quoting prevents
+the interpretation of:
+
+ space as a parameter separator
+ ' single quote as a strong quoting delimiter
+ # hash as a comment start
+
+But interpretation of escaping and special characters are not prevented by weak
+quoting.
+
+Strong quoting is achieved by using single quotes (''). Inside single quotes,
+nothing is interpreted, it's the efficient way to quote regexes.
+
+Quoted and escaped strings are replaced in memory by their interpreted
+equivalent, it allows you to perform concatenation.
+
+ Example:
+ # those are equivalents:
+ log-format %{+Q}o\ %t\ %s\ %{-Q}r
+ log-format "%{+Q}o %t %s %{-Q}r"
+ log-format '%{+Q}o %t %s %{-Q}r'
+ log-format "%{+Q}o %t"' %s %{-Q}r'
+ log-format "%{+Q}o %t"' %s'\ %{-Q}r
+
+ # those are equivalents:
+ reqrep "^([^\ :]*)\ /static/(.*)" \1\ /\2
+ reqrep "^([^ :]*)\ /static/(.*)" '\1 /\2'
+ reqrep "^([^ :]*)\ /static/(.*)" "\1 /\2"
+ reqrep "^([^ :]*)\ /static/(.*)" "\1\ /\2"
+
+
+2.3. Time format
----------------
Some parameters involve values representing time, such as timeouts. These
@@ -388,7 +438,7 @@
- d : days. 1d = 24h = 1440m = 86400s = 86400000ms
-2.3. Examples
+2.4. Examples
-------------
# Simple configuration for an HTTP proxy listening on port 80 on all
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 4d0a91a..96ee798 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -6207,6 +6207,8 @@
char *end;
char *args[MAX_LINE_ARGS + 1];
char *line = thisline;
+ int dquote = 0; /* double quote */
+ int squote = 0; /* simple quote */
linenum++;
@@ -6224,15 +6226,31 @@
/* skip leading spaces */
while (isspace((unsigned char)*line))
line++;
-
+
arg = 0;
args[arg] = line;
while (*line && arg < MAX_LINE_ARGS) {
+ if (*line == '"' && !squote) { /* double quote outside single quotes */
+ if (dquote)
+ dquote = 0;
+ else
+ dquote = 1;
+ memmove(line, line + 1, end - (line + 1));
+ end--;
+ }
+ else if (*line == '\'' && !dquote) { /* single quote outside double quotes */
+ if (squote)
+ squote = 0;
+ else
+ squote = 1;
+ memmove(line, line + 1, end - (line + 1));
+ end--;
+ }
+ else if (*line == '\\' && !squote) {
/* first, we'll replace \\, \<space>, \#, \r, \n, \t, \xXX with their
* C equivalent value. Other combinations left unchanged (eg: \1).
*/
- if (*line == '\\') {
int skip = 0;
if (line[1] == ' ' || line[1] == '\\' || line[1] == '#') {
*line = line[1];
@@ -6241,7 +6259,7 @@
else if (line[1] == 'r') {
*line = '\r';
skip = 1;
- }
+ }
else if (line[1] == 'n') {
*line = '\n';
skip = 1;
@@ -6264,6 +6282,12 @@
Alert("parsing [%s:%d] : invalid or incomplete '\\x' sequence in '%s'.\n", file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
}
+ } else if (line[1] == '"') {
+ *line = '"';
+ skip = 1;
+ } else if (line[1] == '\'') {
+ *line = '\'';
+ skip = 1;
}
if (skip) {
memmove(line + 1, line + 1 + skip, end - (line + skip));
@@ -6271,12 +6295,12 @@
}
line++;
}
- else if (*line == '#' || *line == '\n' || *line == '\r') {
+ else if ((!squote && !dquote && *line == '#') || *line == '\n' || *line == '\r') {
/* end of string, end of loop */
*line = 0;
break;
}
- else if (isspace((unsigned char)*line)) {
+ else if (!squote && !dquote && isspace((unsigned char)*line)) {
/* a non-escaped space is an argument separator */
*line++ = '\0';
while (isspace((unsigned char)*line))
@@ -6287,6 +6311,15 @@
line++;
}
}
+ if (dquote) {
+ Alert("parsing [%s:%d] : Mismatched double quotes.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ }
+
+ if (squote) {
+ Alert("parsing [%s:%d] : Mismatched simple quotes.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ }
/* empty line */
if (!**args)