REORG: config: move the condition preprocessing code to its own file

The .if/.else/.endif and condition evaluation code is quite dirty and
was dumped into cfgparse.c because it was easy. But it should be tidied
quite a bit as it will need to evolve.

Let's move all that to cfgcond.{c,h}.
diff --git a/Makefile b/Makefile
index 4b06767..d598e60 100644
--- a/Makefile
+++ b/Makefile
@@ -868,7 +868,7 @@
         src/listener.o src/dns.o src/connection.o src/tcp_rules.o src/debug.o  \
         src/sink.o src/payload.o src/mux_pt.o src/filters.o src/fcgi-app.o     \
         src/server_state.o src/vars.o src/map.o src/cfgparse-global.o          \
-        src/task.o src/flt_http_comp.o src/session.o src/sock.o                \
+        src/task.o src/flt_http_comp.o src/session.o src/sock.o src/cfgcond.o  \
         src/flt_trace.o src/acl.o src/trace.o src/http_rules.o src/queue.o     \
         src/mjson.o src/h2.o src/h1.o src/mworker.o src/lb_chash.o src/ring.o  \
         src/activity.o src/tcp_sample.o src/proto_tcp.o src/htx.o src/h1_htx.o \
diff --git a/include/haproxy/cfgcond-t.h b/include/haproxy/cfgcond-t.h
new file mode 100644
index 0000000..04c8df1
--- /dev/null
+++ b/include/haproxy/cfgcond-t.h
@@ -0,0 +1,62 @@
+/*
+ * include/haproxy/cfgcond-t.h
+ * Types for the configuration condition preprocessor
+ *
+ * Copyright (C) 2000-2021 Willy Tarreau - w@1wt.eu
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _HAPROXY_CFGCOND_T_H
+#define _HAPROXY_CFGCOND_T_H
+
+#include <haproxy/api-t.h>
+
+/* nested if/elif/else/endif block states */
+enum nested_cond_state {
+	NESTED_COND_IF_TAKE,      // "if" with a true condition
+	NESTED_COND_IF_DROP,      // "if" with a false condition
+	NESTED_COND_IF_SKIP,      // "if" masked by an outer false condition
+
+	NESTED_COND_ELIF_TAKE,    // "elif" with a true condition from a false one
+	NESTED_COND_ELIF_DROP,    // "elif" with a false condition from a false one
+	NESTED_COND_ELIF_SKIP,    // "elif" masked by an outer false condition or a previously taken if
+
+	NESTED_COND_ELSE_TAKE,    // taken "else" after an if false condition
+	NESTED_COND_ELSE_DROP,    // "else" masked by outer false condition or an if true condition
+};
+
+/* 100 levels of nested conditions should already be sufficient */
+#define MAXNESTEDCONDS 100
+
+/* supported conditional predicates for .if/.elif */
+enum cond_predicate {
+	CFG_PRED_NONE,            // none
+	CFG_PRED_DEFINED,         // "defined"
+	CFG_PRED_FEATURE,         // "feature"
+	CFG_PRED_STREQ,           // "streq"
+	CFG_PRED_STRNEQ,          // "strneq"
+	CFG_PRED_VERSION_ATLEAST, // "version_atleast"
+	CFG_PRED_VERSION_BEFORE,  // "version_before"
+};
+
+/* keyword for a condition predicate */
+struct cond_pred_kw {
+	const char *word;         // NULL marks the end of the list
+	enum cond_predicate prd;  // one of the CFG_PRED_* above
+	uint64_t arg_mask;        // mask of supported arguments (strings only)
+};
+
+#endif /* _HAPROXY_CFGCOND_T_H */
diff --git a/include/haproxy/cfgcond.h b/include/haproxy/cfgcond.h
new file mode 100644
index 0000000..bf8f58d
--- /dev/null
+++ b/include/haproxy/cfgcond.h
@@ -0,0 +1,31 @@
+/*
+ * include/haproxy/cfgcond.h
+ * Configuration condition preprocessor
+ *
+ * Copyright (C) 2000-2021 Willy Tarreau - w@1wt.eu
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _HAPROXY_CFGCOND_H
+#define _HAPROXY_CFGCOND_H
+
+#include <haproxy/api.h>
+#include <haproxy/cfgcond-t.h>
+
+const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str);
+int cfg_eval_condition(char **args, char **err, const char **errptr);
+
+#endif
diff --git a/include/haproxy/cfgparse.h b/include/haproxy/cfgparse.h
index 51e5b9e..1c97a88 100644
--- a/include/haproxy/cfgparse.h
+++ b/include/haproxy/cfgparse.h
@@ -126,7 +126,6 @@
 const char *cfg_find_best_match(const char *word, const struct list *list, int section, const char **extra);
 int warnifnotcap(struct proxy *proxy, int cap, const char *file, int line, const char *arg, const char *hint);
 int failifnotcap(struct proxy *proxy, int cap, const char *file, int line, const char *arg, const char *hint);
-int cfg_eval_condition(char **args, char **err, const char **errptr);
 
 /* simplified way to define a section parser */
 #define REGISTER_CONFIG_SECTION(name, parse, post)                            \
diff --git a/src/cfgcond.c b/src/cfgcond.c
new file mode 100644
index 0000000..d3c087b
--- /dev/null
+++ b/src/cfgcond.c
@@ -0,0 +1,147 @@
+/*
+ * Configuration condition preprocessor
+ *
+ * Copyright 2000-2021 Willy Tarreau <w@1wt.eu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <haproxy/api.h>
+#include <haproxy/arg.h>
+#include <haproxy/cfgcond.h>
+#include <haproxy/global.h>
+#include <haproxy/tools.h>
+
+/* supported condition predicates */
+const struct cond_pred_kw cond_predicates[] = {
+	{ "defined",          CFG_PRED_DEFINED,         ARG1(1, STR)         },
+	{ "feature",          CFG_PRED_FEATURE,         ARG1(1, STR)         },
+	{ "streq",            CFG_PRED_STREQ,           ARG2(2, STR, STR)    },
+	{ "strneq",           CFG_PRED_STRNEQ,          ARG2(2, STR, STR)    },
+	{ "version_atleast",  CFG_PRED_VERSION_ATLEAST, ARG1(1, STR)         },
+	{ "version_before",   CFG_PRED_VERSION_BEFORE,  ARG1(1, STR)         },
+	{ NULL, CFG_PRED_NONE, 0 }
+};
+
+/* looks up a cond predicate matching the keyword in <str>, possibly followed
+ * by a parenthesis. Returns a pointer to it or NULL if not found.
+ */
+const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str)
+{
+	const struct cond_pred_kw *ret;
+	int len = strcspn(str, " (");
+
+	for (ret = &cond_predicates[0]; ret->word; ret++) {
+		if (len != strlen(ret->word))
+			continue;
+		if (strncmp(str, ret->word, len) != 0)
+			continue;
+		return ret;
+	}
+	return NULL;
+}
+
+/* evaluate a condition on a .if/.elif line. The condition is already tokenized
+ * in <err>. Returns -1 on error (in which case err is filled with a message,
+ * and only in this case), 0 if the condition is false, 1 if it's true. If
+ * <errptr> is not NULL, it's set to the first invalid character on error.
+ */
+int cfg_eval_condition(char **args, char **err, const char **errptr)
+{
+	const struct cond_pred_kw *cond_pred = NULL;
+	const char *end_ptr;
+	struct arg *argp = NULL;
+	int err_arg;
+	int nbargs;
+	int ret = -1;
+	char *end;
+	long val;
+
+	if (!*args[0]) /* note: empty = false */
+		return 0;
+
+	val = strtol(args[0], &end, 0);
+	if (end && *end == '\0')
+		return val != 0;
+
+	/* below we'll likely all make_arg_list() so we must return only via
+	 * the <done> label which frees the arg list.
+	 */
+	cond_pred = cfg_lookup_cond_pred(args[0]);
+	if (cond_pred) {
+		nbargs = make_arg_list(args[0] + strlen(cond_pred->word), -1,
+		                       cond_pred->arg_mask, &argp, err,
+		                       &end_ptr, &err_arg, NULL);
+
+		if (nbargs < 0) {
+			memprintf(err, "%s in argument %d of predicate '%s' used in conditional expression", *err, err_arg, cond_pred->word);
+			if (errptr)
+				*errptr = end_ptr;
+			goto done;
+		}
+
+		/* here we know we have a valid predicate with <nbargs> valid
+		 * arguments, placed in <argp> (which we'll need to free).
+		 */
+		switch (cond_pred->prd) {
+		case CFG_PRED_DEFINED:  // checks if arg exists as an environment variable
+			ret = getenv(argp[0].data.str.area) != NULL;
+			goto done;
+
+		case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature
+			const char *p;
+
+			for (p = build_features; (p = strstr(p, argp[0].data.str.area)); p++) {
+				if ((p[argp[0].data.str.data] == ' ' || p[argp[0].data.str.data] == 0) &&
+				    p > build_features) {
+					if (*(p-1) == '+') { // "+OPENSSL"
+						ret = 1;
+						goto done;
+					}
+					else if (*(p-1) == '-') { // "-OPENSSL"
+						ret = 0;
+						goto done;
+					}
+					/* it was a sub-word, let's restart from next place */
+				}
+			}
+			/* not found */
+			ret = 0;
+			goto done;
+		}
+		case CFG_PRED_STREQ:    // checks if the two arg are equal
+			ret = strcmp(argp[0].data.str.area, argp[1].data.str.area) == 0;
+			goto done;
+
+		case CFG_PRED_STRNEQ:   // checks if the two arg are different
+			ret = strcmp(argp[0].data.str.area, argp[1].data.str.area) != 0;
+			goto done;
+
+		case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one
+			ret = compare_current_version(argp[0].data.str.area) <= 0;
+			goto done;
+
+		case CFG_PRED_VERSION_BEFORE:  // checks if the current version is older than this one
+			ret = compare_current_version(argp[0].data.str.area) > 0;
+			goto done;
+
+		default:
+			memprintf(err, "internal error: unhandled conditional expression predicate '%s'", cond_pred->word);
+			if (errptr)
+				*errptr = args[0];
+			goto done;
+		}
+	}
+
+	memprintf(err, "unparsable conditional expression '%s'", args[0]);
+	if (errptr)
+		*errptr = args[0];
+ done:
+	free_args(argp);
+	ha_free(&argp);
+	return ret;
+}
diff --git a/src/cfgparse.c b/src/cfgparse.c
index a1b9a47..4cee8a7 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -41,6 +41,7 @@
 #include <haproxy/auth.h>
 #include <haproxy/backend.h>
 #include <haproxy/capture.h>
+#include <haproxy/cfgcond.h>
 #include <haproxy/cfgparse.h>
 #include <haproxy/channel.h>
 #include <haproxy/check.h>
@@ -116,51 +117,6 @@
 	.list = LIST_HEAD_INIT(cfg_keywords.list)
 };
 
-/* nested if/elif/else/endif block states */
-enum nested_cond_state {
-	NESTED_COND_IF_TAKE,      // "if" with a true condition
-	NESTED_COND_IF_DROP,      // "if" with a false condition
-	NESTED_COND_IF_SKIP,      // "if" masked by an outer false condition
-
-	NESTED_COND_ELIF_TAKE,    // "elif" with a true condition from a false one
-	NESTED_COND_ELIF_DROP,    // "elif" with a false condition from a false one
-	NESTED_COND_ELIF_SKIP,    // "elif" masked by an outer false condition or a previously taken if
-
-	NESTED_COND_ELSE_TAKE,    // taken "else" after an if false condition
-	NESTED_COND_ELSE_DROP,    // "else" masked by outer false condition or an if true condition
-};
-
-/* 100 levels of nested conditions should already be sufficient */
-#define MAXNESTEDCONDS 100
-
-/* supported conditional predicates for .if/.elif */
-enum cond_predicate {
-	CFG_PRED_NONE,            // none
-	CFG_PRED_DEFINED,         // "defined"
-	CFG_PRED_FEATURE,         // "feature"
-	CFG_PRED_STREQ,           // "streq"
-	CFG_PRED_STRNEQ,          // "strneq"
-	CFG_PRED_VERSION_ATLEAST, // "version_atleast"
-	CFG_PRED_VERSION_BEFORE,  // "version_before"
-};
-
-struct cond_pred_kw {
-	const char *word;         // NULL marks the end of the list
-	enum cond_predicate prd;  // one of the CFG_PRED_* above
-	uint64_t arg_mask;        // mask of supported arguments (strings only)
-};
-
-/* supported condition predicates */
-const struct cond_pred_kw cond_predicates[] = {
-	{ "defined",          CFG_PRED_DEFINED,         ARG1(1, STR)         },
-	{ "feature",          CFG_PRED_FEATURE,         ARG1(1, STR)         },
-	{ "streq",            CFG_PRED_STREQ,           ARG2(2, STR, STR)    },
-	{ "strneq",           CFG_PRED_STRNEQ,          ARG2(2, STR, STR)    },
-	{ "version_atleast",  CFG_PRED_VERSION_ATLEAST, ARG1(1, STR)         },
-	{ "version_before",   CFG_PRED_VERSION_BEFORE,  ARG1(1, STR)         },
-	{ NULL, CFG_PRED_NONE, 0 }
-};
-
 /*
  * converts <str> to a list of listeners which are dynamically allocated.
  * The format is "{addr|'*'}:port[-end][,{addr|'*'}:port[-end]]*", where :
@@ -1713,125 +1669,6 @@
 	return ret;
 }
 
-/* looks up a cond predicate matching the keyword in <str>, possibly followed
- * by a parenthesis. Returns a pointer to it or NULL if not found.
- */
-static const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str)
-{
-	const struct cond_pred_kw *ret;
-	int len = strcspn(str, " (");
-
-	for (ret = &cond_predicates[0]; ret->word; ret++) {
-		if (len != strlen(ret->word))
-			continue;
-		if (strncmp(str, ret->word, len) != 0)
-			continue;
-		return ret;
-	}
-	return NULL;
-}
-
-/* evaluate a condition on a .if/.elif line. The condition is already tokenized
- * in <err>. Returns -1 on error (in which case err is filled with a message,
- * and only in this case), 0 if the condition is false, 1 if it's true. If
- * <errptr> is not NULL, it's set to the first invalid character on error.
- */
-int cfg_eval_condition(char **args, char **err, const char **errptr)
-{
-	const struct cond_pred_kw *cond_pred = NULL;
-	const char *end_ptr;
-	struct arg *argp = NULL;
-	int err_arg;
-	int nbargs;
-	int ret = -1;
-	char *end;
-	long val;
-
-	if (!*args[0]) /* note: empty = false */
-		return 0;
-
-	val = strtol(args[0], &end, 0);
-	if (end && *end == '\0')
-		return val != 0;
-
-	/* below we'll likely all make_arg_list() so we must return only via
-	 * the <done> label which frees the arg list.
-	 */
-	cond_pred = cfg_lookup_cond_pred(args[0]);
-	if (cond_pred) {
-		nbargs = make_arg_list(args[0] + strlen(cond_pred->word), -1,
-		                       cond_pred->arg_mask, &argp, err,
-		                       &end_ptr, &err_arg, NULL);
-
-		if (nbargs < 0) {
-			memprintf(err, "%s in argument %d of predicate '%s' used in conditional expression", *err, err_arg, cond_pred->word);
-			if (errptr)
-				*errptr = end_ptr;
-			goto done;
-		}
-
-		/* here we know we have a valid predicate with <nbargs> valid
-		 * arguments, placed in <argp> (which we'll need to free).
-		 */
-		switch (cond_pred->prd) {
-		case CFG_PRED_DEFINED:  // checks if arg exists as an environment variable
-			ret = getenv(argp[0].data.str.area) != NULL;
-			goto done;
-
-		case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature
-			const char *p;
-
-			for (p = build_features; (p = strstr(p, argp[0].data.str.area)); p++) {
-				if ((p[argp[0].data.str.data] == ' ' || p[argp[0].data.str.data] == 0) &&
-				    p > build_features) {
-					if (*(p-1) == '+') { // "+OPENSSL"
-						ret = 1;
-						goto done;
-					}
-					else if (*(p-1) == '-') { // "-OPENSSL"
-						ret = 0;
-						goto done;
-					}
-					/* it was a sub-word, let's restart from next place */
-				}
-			}
-			/* not found */
-			ret = 0;
-			goto done;
-		}
-		case CFG_PRED_STREQ:    // checks if the two arg are equal
-			ret = strcmp(argp[0].data.str.area, argp[1].data.str.area) == 0;
-			goto done;
-
-		case CFG_PRED_STRNEQ:   // checks if the two arg are different
-			ret = strcmp(argp[0].data.str.area, argp[1].data.str.area) != 0;
-			goto done;
-
-		case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one
-			ret = compare_current_version(argp[0].data.str.area) <= 0;
-			goto done;
-
-		case CFG_PRED_VERSION_BEFORE:  // checks if the current version is older than this one
-			ret = compare_current_version(argp[0].data.str.area) > 0;
-			goto done;
-
-		default:
-			memprintf(err, "internal error: unhandled conditional expression predicate '%s'", cond_pred->word);
-			if (errptr)
-				*errptr = args[0];
-			goto done;
-		}
-	}
-
-	memprintf(err, "unparsable conditional expression '%s'", args[0]);
-	if (errptr)
-		*errptr = args[0];
- done:
-	free_args(argp);
-	ha_free(&argp);
-	return ret;
-}
-
 /*
  * This function reads and parses the configuration file given in the argument.
  * Returns the error code, 0 if OK, -1 if the config file couldn't be opened,
diff --git a/src/haproxy.c b/src/haproxy.c
index 2d32bb8..c863e13 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -87,6 +87,7 @@
 #include <haproxy/auth.h>
 #include <haproxy/base64.h>
 #include <haproxy/capture-t.h>
+#include <haproxy/cfgcond.h>
 #include <haproxy/cfgdiag.h>
 #include <haproxy/cfgparse.h>
 #include <haproxy/chunk.h>