MINOR: checks: Support list of status codes on http-check expect rules

It is now possible to match on a comma-separated list of status codes or range
of codes. In addtion, instead of a string comparison to match the response's
status code, a integer comparison is performed. Here is an example:

  http-check expect status 200,201,300-310
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 3e4be61..6e39c89 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4473,10 +4473,10 @@
 
          http-check connect
          http-check send GET / HTTP/1.1 hdr host haproxy.1wt.eu
-         http-check expect rstatus "^[23][0-9]{2}"
+         http-check expect status 200-399
          http-check connect port 443 ssl sni haproxy.1wt.eu
          http-check send GET / HTTP/1.1 hdr host haproxy.1wt.eu
-         http-check expect rstatus "^[23][0-9]{2}"
+         http-check expect status 200-399
 
          server www 10.0.0.1 check port 80
 
@@ -4575,11 +4575,13 @@
   statement is supported in a backend. If a server fails to respond or times
   out, the check obviously fails. The available matches are :
 
-    status <string> : test the exact string match for the HTTP status code.
-                      A health check response will be considered valid if the
-                      response's status code is exactly this string. If the
-                      "status" keyword is prefixed with "!", then the response
-                      will be considered invalid if the status code matches.
+    status <codes> :  test the status codes found parsing <codes> string. it
+                      must be a comma-separated list of status codes or range
+                      codes. A health check response will be considered as
+                      valid if the response's status code matches any status
+                      code or is inside any range of the list. If the "status"
+                      keyword is prefixed with "!", then the response will be
+                      considered invalid if the status code matches.
 
     rstatus <regex> : test a regular expression for the HTTP status code.
                       A health check response will be considered valid if the
@@ -4627,7 +4629,7 @@
 
   Examples :
          # only accept status 200 as valid
-         http-check expect status 200
+         http-check expect status 200,201,300-310
 
          # consider SQL errors as errors
          http-check expect ! string SQL\ Error
diff --git a/include/types/checks.h b/include/types/checks.h
index 8d17d51..867d2f7 100644
--- a/include/types/checks.h
+++ b/include/types/checks.h
@@ -200,6 +200,11 @@
 	struct list list;  /* header chained list */
 };
 
+struct tcpcheck_codes {
+	unsigned int (*codes)[2]; /* an array of roange of codes: [0]=min [1]=max */
+	size_t num;               /* number of entry in the array */
+};
+
 #define TCPCHK_SND_HTTP_FL_URI_FMT    0x0001 /* Use a log-format string for the uri */
 #define TCPCHK_SND_HTTP_FL_BODY_FMT   0x0002 /* Use a log-format string for the body */
 #define TCPCHK_SND_HTTP_FROM_OPT      0x0004 /* Send rule coming from "option httpck" directive */
@@ -252,8 +257,9 @@
 	enum tcpcheck_expect_type type;   /* Type of pattern used for matching. */
 	unsigned int flags;               /* TCPCHK_EXPT_FL_* */
 	union {
-		struct ist data;          /* Matching a literal string / binary anywhere in the response. */
-		struct my_regex *regex;   /* Matching a regex pattern. */
+		struct ist data;             /* Matching a literal string / binary anywhere in the response. */
+		struct my_regex *regex;      /* Matching a regex pattern. */
+		struct tcpcheck_codes codes; /* Matching a list of codes */
 
 		/* custom function to eval epxect rule */
 		enum tcpcheck_eval_ret (*custom)(struct check *, struct tcpcheck_rule *, int);
diff --git a/src/checks.c b/src/checks.c
index d2765fe..47468af 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -601,7 +601,7 @@
 					chunk_appendf(chk, " (expect binary regex)");
 					break;
 				case TCPCHK_EXPECT_HTTP_STATUS:
-					chunk_appendf(chk, " (expect HTTP status '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
+					chunk_appendf(chk, " (expect HTTP status codes)");
 					break;
 				case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
 					chunk_appendf(chk, " (expect HTTP status regex)");
@@ -781,9 +781,11 @@
 		free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
 		release_sample_expr(rule->expect.status_expr);
 		switch (rule->expect.type) {
+		case TCPCHK_EXPECT_HTTP_STATUS:
+			free(rule->expect.codes.codes);
+			break;
 		case TCPCHK_EXPECT_STRING:
 		case TCPCHK_EXPECT_BINARY:
-		case TCPCHK_EXPECT_HTTP_STATUS:
 		case TCPCHK_EXPECT_HTTP_BODY:
 			istfree(&rule->expect.data);
 			break;
@@ -1044,8 +1046,10 @@
 
 	chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
 	switch (rule->expect.type) {
-	case TCPCHK_EXPECT_STRING:
 	case TCPCHK_EXPECT_HTTP_STATUS:
+		chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
+		break;
+	case TCPCHK_EXPECT_STRING:
 	case TCPCHK_EXPECT_HTTP_BODY:
 		chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
 			      tcpcheck_get_step_id(check, rule));
@@ -2093,7 +2097,7 @@
 	struct buffer *msg = NULL;
 	enum healthcheck_status status;
 	struct ist desc = IST_NULL;
-	int match, inverse;
+	int i, match, inverse;
 
 	last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
 
@@ -2127,7 +2131,14 @@
 
 	switch (expect->type) {
 	case TCPCHK_EXPECT_HTTP_STATUS:
-		match = isteq(htx_sl_res_code(sl), expect->data);
+		match = 0;
+		for (i = 0; i < expect->codes.num; i++) {
+			if (sl->info.res.status >= expect->codes.codes[i][0] &&
+			    sl->info.res.status <= expect->codes.codes[i][1]) {
+				match = 1;
+				break;
+			}
+		}
 
 		/* Set status and description in case of error */
 		status = HCHK_STATUS_L7STS;
@@ -3918,7 +3929,7 @@
 {
 	struct tcpcheck_rule *prev_check, *chk = NULL;
 	struct sample_expr *status_expr = NULL;
-	char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
+	char *on_success_msg, *on_error_msg, *comment, *pattern;
 	enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
 	enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
 	enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
@@ -3926,7 +3937,7 @@
 	long min_recv = -1;
 	int inverse = 0;
 
-	str = on_success_msg = on_error_msg = comment = pattern = NULL;
+	on_success_msg = on_error_msg = comment = pattern = NULL;
 	if (!*(args[cur_arg+1])) {
 		memprintf(errmsg, "expects at least a matching pattern as arguments");
 		goto error;
@@ -4229,8 +4240,44 @@
 	}
 
 	switch (chk->expect.type) {
+	case TCPCHK_EXPECT_HTTP_STATUS: {
+		const char *p = pattern;
+		unsigned int c1,c2;
+
+		chk->expect.codes.codes = NULL;
+		chk->expect.codes.num   = 0;
+		while (1) {
+			c1 = c2 = read_uint(&p, pattern + strlen(pattern));
+			if (*p == '-') {
+				p++;
+				c2 = read_uint(&p, pattern + strlen(pattern));
+			}
+			if (c1 > c2) {
+				memprintf(errmsg, "invalid range of status codes '%s'", pattern);
+				goto error;
+			}
+
+			chk->expect.codes.num++;
+			chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
+							      chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
+			if (!chk->expect.codes.codes) {
+				memprintf(errmsg, "out of memory");
+				goto error;
+			}
+			chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
+			chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
+
+			if (*p == '\0')
+				break;
+			if (*p != ',') {
+				memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
+				goto error;
+			}
+			p++;
+		}
+		break;
+	}
 	case TCPCHK_EXPECT_STRING:
-	case TCPCHK_EXPECT_HTTP_STATUS:
 	case TCPCHK_EXPECT_HTTP_BODY:
 		chk->expect.data = ist2(strdup(pattern), strlen(pattern));
 		if (!isttest(chk->expect.data)) {
@@ -4277,7 +4324,6 @@
 
   error:
 	free_tcpcheck(chk, 0);
-	free(str);
 	free(comment);
 	free(on_success_msg);
 	free(on_error_msg);
@@ -4917,7 +4963,7 @@
 		 */
 		chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
 		if (chk && chk->action == TCPCHK_ACT_SEND) {
-			next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "rstatus", "^[23]", ""},
+			next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
 						     1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
 						     px->conf.file, px->conf.line, &errmsg);
 			if (!next) {