[MEDIUM] acl: support '-i' to ignore case when matching

Implemented the "-i" option on ACLs to state that the matching
will have to be performed for all patterns ignoring case. The
usage is :

   acl <aclname> <aclsubject> -i pattern1 ...

If a pattern must begin with "-", either it must not be the first one,
or the "--" option should be specified first.
diff --git a/include/types/acl.h b/include/types/acl.h
index db76a27..ef5be58 100644
--- a/include/types/acl.h
+++ b/include/types/acl.h
@@ -67,6 +67,12 @@
 	ACL_DIR_RTR,            /* ACL evaluated on response */
 };
 
+/* possible flags for expressions or patterns */
+enum {
+	ACL_PAT_F_IGNORE_CASE = 1 << 0,       /* ignore case */
+	ACL_PAT_F_FROM_FILE   = 1 << 1,       /* pattern comes from a file */
+};
+
 /* How to store a time range and the valid days in 29 bits */
 struct acl_time {
 	int dow:7;              /* 1 bit per day of week: 0-6 */
@@ -96,6 +102,7 @@
 		regex_t *reg;           /* a compiled regex */
 	} ptr;                          /* indirect values, allocated */
 	int len;                        /* data length when required  */
+	int flags;                      /* expr or pattern flags. */
 };
 
 /* The structure exchanged between an acl_fetch_* function responsible for
diff --git a/src/acl.c b/src/acl.c
index c4860ab..c266c4a 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -41,9 +41,14 @@
 /* NB: For two strings to be identical, it is required that their lengths match */
 int acl_match_str(struct acl_test *test, struct acl_pattern *pattern)
 {
+	int icase;
+
 	if (pattern->len != test->len)
 		return 0;
-	if (strncmp(pattern->ptr.str, test->ptr, test->len) == 0)
+
+	icase = pattern->flags & ACL_PAT_F_IGNORE_CASE;
+	if ((icase && strncasecmp(pattern->ptr.str, test->ptr, test->len) == 0) ||
+	    (!icase && strncmp(pattern->ptr.str, test->ptr, test->len) == 0))
 		return 1;
 	return 0;
 }
@@ -89,9 +94,14 @@
 /* Checks that the pattern matches the beginning of the tested string. */
 int acl_match_beg(struct acl_test *test, struct acl_pattern *pattern)
 {
+	int icase;
+
 	if (pattern->len > test->len)
 		return 0;
-	if (strncmp(pattern->ptr.str, test->ptr, pattern->len) != 0)
+
+	icase = pattern->flags & ACL_PAT_F_IGNORE_CASE;
+	if ((icase && strncasecmp(pattern->ptr.str, test->ptr, pattern->len) != 0) ||
+	    (!icase && strncmp(pattern->ptr.str, test->ptr, pattern->len) != 0))
 		return 0;
 	return 1;
 }
@@ -99,9 +109,13 @@
 /* Checks that the pattern matches the end of the tested string. */
 int acl_match_end(struct acl_test *test, struct acl_pattern *pattern)
 {
+	int icase;
+
 	if (pattern->len > test->len)
 		return 0;
-	if (strncmp(pattern->ptr.str, test->ptr + test->len - pattern->len, pattern->len) != 0)
+	icase = pattern->flags & ACL_PAT_F_IGNORE_CASE;
+	if ((icase && strncasecmp(pattern->ptr.str, test->ptr + test->len - pattern->len, pattern->len) != 0) ||
+	    (!icase && strncmp(pattern->ptr.str, test->ptr + test->len - pattern->len, pattern->len) != 0))
 		return 0;
 	return 1;
 }
@@ -111,6 +125,7 @@
  */
 int acl_match_sub(struct acl_test *test, struct acl_pattern *pattern)
 {
+	int icase;
 	char *end;
 	char *c;
 
@@ -118,11 +133,21 @@
 		return 0;
 
 	end = test->ptr + test->len - pattern->len;
-	for (c = test->ptr; c <= end; c++) {
-		if (*c != *pattern->ptr.str)
-			continue;
-		if (strncmp(pattern->ptr.str, c, pattern->len) == 0)
-			return 1;
+	icase = pattern->flags & ACL_PAT_F_IGNORE_CASE;
+	if (icase) {
+		for (c = test->ptr; c <= end; c++) {
+			if (tolower(*c) != tolower(*pattern->ptr.str))
+				continue;
+			if (strncasecmp(pattern->ptr.str, c, pattern->len) == 0)
+				return 1;
+		}
+	} else {
+		for (c = test->ptr; c <= end; c++) {
+			if (*c != *pattern->ptr.str)
+				continue;
+			if (strncmp(pattern->ptr.str, c, pattern->len) == 0)
+				return 1;
+		}
 	}
 	return 0;
 }
@@ -130,41 +155,52 @@
 /* This one is used by other real functions. It checks that the pattern is
  * included inside the tested string, but enclosed between the specified
  * delimitor, or a '/' or a '?' or at the beginning or end of the string.
- * The delimitor is stripped at the beginning or end of the pattern are
- * ignored.
+ * The delimitor is stripped at the beginning or end of the pattern.
  */
 static int match_word(struct acl_test *test, struct acl_pattern *pattern, char delim)
 {
-	int may_match;
+	int may_match, icase;
 	char *c, *end;
 	char *ps;
 	int pl;
 
 	pl = pattern->len;
 	ps = pattern->ptr.str;
-	while (pl > 0 && *ps == delim) {
+	while (pl > 0 && (*ps == delim || *ps == '/' || *ps == '?')) {
 		pl--;
 		ps++;
 	}
 
-	while (pl > 0 && *(ps + pl - 1) == delim)
+	while (pl > 0 &&
+	       (ps[pl - 1] == delim || ps[pl - 1] == '/' || ps[pl - 1] == '?'))
 		pl--;
 
 	if (pl > test->len)
 		return 0;
 
 	may_match = 1;
+	icase = pattern->flags & ACL_PAT_F_IGNORE_CASE;
 	end = test->ptr + test->len - pl;
 	for (c = test->ptr; c <= end; c++) {
 		if (*c == '/' || *c == delim || *c == '?') {
 			may_match = 1;
 			continue;
 		}
-		if (may_match && (*c == *ps) &&
-		    (strncmp(ps, c, pl) == 0) &&
-		    (c == end || c[pl] == '/' || c[pl] == delim || c[pl] == '?'))
-				return 1;
 
+		if (!may_match)
+			continue;
+
+		if (icase) {
+			if ((tolower(*c) == tolower(*ps)) &&
+			    (strncasecmp(ps, c, pl) == 0) &&
+			    (c == end || c[pl] == '/' || c[pl] == delim || c[pl] == '?'))
+				return 1;
+		} else {
+			if ((*c == *ps) &&
+			    (strncmp(ps, c, pl) == 0) &&
+			    (c == end || c[pl] == '/' || c[pl] == delim || c[pl] == '?'))
+				return 1;
+		}
 		may_match = 0;
 	}
 	return 0;
@@ -227,13 +263,15 @@
 int acl_parse_reg(const char **text, struct acl_pattern *pattern, int *opaque)
 {
 	regex_t *preg;
+	int icase;
 
 	preg = calloc(1, sizeof(regex_t));
 
 	if (!preg)
 		return 0;
 
-	if (regcomp(preg, *text, REG_EXTENDED | REG_NOSUB) != 0) {
+	icase = (pattern->flags & ACL_PAT_F_IGNORE_CASE) ? REG_ICASE : 0;
+	if (regcomp(preg, *text, REG_EXTENDED | REG_NOSUB | icase) != 0) {
 		free(preg);
 		return 0;
 	}
@@ -420,7 +458,7 @@
 	struct acl_expr *expr;
 	struct acl_keyword *aclkw;
 	struct acl_pattern *pattern;
-	int opaque;
+	int opaque, patflags;
 	const char *arg;
 
 	aclkw = find_acl_kw(args[0]);
@@ -454,14 +492,37 @@
 		expr->arg.str = arg2;
 	}
 
-	/* now parse all patterns */
 	args++;
+
+	/* check for options before patterns. Supported options are :
+	 *   -i : ignore case for all patterns by default
+	 *   -f : read patterns from those files
+	 *   -- : everything after this is not an option
+	 */
+	patflags = 0;
+	while (**args == '-') {
+		if ((*args)[1] == 'i')
+			patflags |= ACL_PAT_F_IGNORE_CASE;
+		else if ((*args)[1] == 'f')
+			patflags |= ACL_PAT_F_FROM_FILE;
+		else if ((*args)[1] == '-') {
+			args++;
+			break;
+		}
+		else
+			break;
+		args++;
+	}
+
+	/* now parse all patterns */
 	opaque = 0;
 	while (**args) {
 		int ret;
 		pattern = (struct acl_pattern *)calloc(1, sizeof(*pattern));
 		if (!pattern)
 			goto out_free_expr;
+		pattern->flags = patflags;
+
 		ret = aclkw->parse(args, pattern, &opaque);
 		if (!ret)
 			goto out_free_pattern;
diff --git a/src/proto_http.c b/src/proto_http.c
index 8a13a71..f92d604 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -5283,6 +5283,8 @@
 
 static int acl_match_meth(struct acl_test *test, struct acl_pattern *pattern)
 {
+	int icase;
+
 	if (test->i != pattern->val.i)
 		return 0;
 
@@ -5292,7 +5294,10 @@
 	/* Other method, we must compare the strings */
 	if (pattern->len != test->len)
 		return 0;
-	if (strncmp(pattern->ptr.str, test->ptr, test->len) != 0)
+
+	icase = pattern->flags & ACL_PAT_F_IGNORE_CASE;
+	if ((icase && strncasecmp(pattern->ptr.str, test->ptr, test->len) != 0) ||
+	    (!icase && strncmp(pattern->ptr.str, test->ptr, test->len) != 0))
 		return 0;
 	return 1;
 }