MEDIUM: checks: Support matching on headers for http-check expect rules

It is now possible to add http-check expect rules matching HTTP header names and
values. Here is the format of these rules:

  http-check expect header name [ -m <meth> ] <name> [log-format] \
                           [ value [ -m <meth> ] <value> [log-format] [full] ]

the name pattern (name ...) is mandatory but the value pattern (value ...) is
optionnal. If not specified, only the header presence is verified. <meth> is the
matching method, applied on the header name or the header value. Supported
matching methods are:

  * "str" (exact match)
  * "beg" (prefix match)
  * "end" (suffix match)
  * "sub" (substring match)
  * "reg" (regex match)

If not specified, exact matching method is used. If the "log-format" option is
used, the pattern (<name> or <value>) is evaluated as a log-format string. This
option cannot be used with the regex matching method. Finally, by default, the
header value is considered as comma-separated list. Each part may be tested. The
"full" option may be used to test the full header line. Note that matchings are
case insensitive on the header names.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 87cc691..1092fd5 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4565,9 +4565,10 @@
               between the exclamation mark and the keyword. See below for more
               details on the supported keywords.
 
-    <pattern> is the pattern to look for. It may be a string or a regular
-              expression. If the pattern contains spaces, they must be escaped
-              with the usual backslash ('\').
+    <pattern> is the pattern to look for. It may be a string, a regular
+              expression or a more complex pattern with several arguments. If
+              the string pattern contains spaces, they must be escaped with the
+              usual backslash ('\').
 
   By default, "option httpchk" considers that response statuses 2xx and 3xx
   are valid, and that others are invalid. When "http-check expect" is used,
@@ -4590,6 +4591,25 @@
                       will be considered invalid if the status code matches.
                       This is mostly used to check for multiple codes.
 
+    header name [ -m <meth> ] <name> [log-format]
+           [ value [ -m <meth> ] <value> [log-format] [full] ] :
+                      test the specified header pattern on the HTTP response
+                      headers. The name pattern is mandatory but the value
+                      pattern is optional. If not specified, only the header
+                      presence is verified. <meth> is the matching method,
+                      applied on the header name or the header value. Supported
+                      matching methods are "str" (exact match), "beg" (prefix
+                      match), "end" (suffix match), "sub" (substring match) or
+                      "reg" (regex match). If not specified, exact matching
+                      method is used. If the "log-format" option is used, the
+                      pattern (<name> or <value>) is evaluated as a log-format
+                      string. This option cannot be used with the regex
+                      matching method. Finally, by default, the header value is
+                      considered as comma-separated list. Each part may be
+                      tested. The "full" option may be used to test the full
+                      header line. Note that matchings are case insensitive on
+                      the header names.
+
     string <string> : test the exact string match in the HTTP response body.
                       A health check response will be considered valid if the
                       response's body contains this exact string. If the
@@ -4631,6 +4651,9 @@
          # only accept status 200 as valid
          http-check expect status 200,201,300-310
 
+         # be sure a sessid coookie is set
+         http-check expect header name "set-cookie" value -m beg "sessid="
+
          # consider SQL errors as errors
          http-check expect ! string SQL\ Error
 
diff --git a/include/types/checks.h b/include/types/checks.h
index 4809285..927dada 100644
--- a/include/types/checks.h
+++ b/include/types/checks.h
@@ -248,17 +248,34 @@
 	TCPCHK_EXPECT_STRING,            /* Matches a string. */
 	TCPCHK_EXPECT_REGEX,             /* Matches a regular pattern. */
 	TCPCHK_EXPECT_REGEX_BINARY,      /* Matches a regular pattern on a hex-encoded text. */
-	TCPCHK_EXPECT_BINARY,            /* Matches a binary sequence. */
+	TCPCHK_EXPECT_BINARY,            /* Matches a binary sequence on a hex-encoded text. */
 	TCPCHK_EXPECT_CUSTOM,            /* Execute a custom function. */
-	TCPCHK_EXPECT_HTTP_STATUS,       /* Matches a string */
-	TCPCHK_EXPECT_HTTP_REGEX_STATUS, /* Matches a regular pattern */
-	TCPCHK_EXPECT_HTTP_BODY,         /* Matches a string */
-	TCPCHK_EXPECT_HTTP_REGEX_BODY,   /* Matches a regular pattern */
+	TCPCHK_EXPECT_HTTP_STATUS,       /* Matches a list of codes on the HTTP status */
+	TCPCHK_EXPECT_HTTP_REGEX_STATUS, /* Matches a regular pattern on the HTTP status */
+	TCPCHK_EXPECT_HTTP_HEADER,       /* Matches on HTTP headers */
+	TCPCHK_EXPECT_HTTP_BODY,         /* Matches a string oa the HTTP payload */
+	TCPCHK_EXPECT_HTTP_REGEX_BODY,   /* Matches a regular pattern on a HTTP payload */
 };
 
 /* tcp-check expect flags */
-#define TCPCHK_EXPT_FL_INV    0x0001 /* Matching is inversed */
+#define TCPCHK_EXPT_FL_INV             0x0001 /* Matching is inversed */
+#define TCPCHK_EXPT_FL_HTTP_HNAME_STR  0x0002 /* Exact match on the HTTP header name */
+#define TCPCHK_EXPT_FL_HTTP_HNAME_BEG  0x0004 /* Prefix match on the HTTP header name */
+#define TCPCHK_EXPT_FL_HTTP_HNAME_END  0x0008 /* Suffix match on the HTTP header name */
+#define TCPCHK_EXPT_FL_HTTP_HNAME_SUB  0x0010 /* Substring match on the HTTP header name */
+#define TCPCHK_EXPT_FL_HTTP_HNAME_REG  0x0020 /* Regex match on the HTTP header name */
+#define TCPCHK_EXPT_FL_HTTP_HNAME_FMT  0x0040 /* The HTTP header name is a log-format string */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_NONE  0x0080 /* No match on the HTTP header value */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_STR   0x0100 /* Exact match on the HTTP header value */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_BEG   0x0200 /* Prefix match on the HTTP header value */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_END   0x0400 /* Suffix match on the HTTP header value */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_SUB   0x0800 /* Substring match on the HTTP header value */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_REG   0x1000 /* Regex match on the HTTP header value*/
+#define TCPCHK_EXPT_FL_HTTP_HVAL_FMT   0x2000 /* The HTTP header value is a log-format string */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_FULL  0x4000 /* Match the full header value ( no stop on commas ) */
 
+#define TCPCHK_EXPT_FL_HTTP_HNAME_TYPE 0x003E /* Mask to get matching method on header name */
+#define TCPCHK_EXPT_FL_HTTP_HVAL_TYPE  0x1F00 /* Mask to get matching method on header value */
 struct tcpcheck_expect {
 	enum tcpcheck_expect_type type;   /* Type of pattern used for matching. */
 	unsigned int flags;               /* TCPCHK_EXPT_FL_* */
@@ -266,6 +283,19 @@
 		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 */
+		struct {
+			union {
+				struct ist name;
+				struct list name_fmt;
+				struct my_regex *name_re;
+			};
+			union {
+				struct ist value;
+				struct list value_fmt;
+				struct my_regex *value_re;
+			};
+		} hdr;                       /* Matching a header pattern */
+
 
 		/* custom function to eval epxect rule */
 		enum tcpcheck_eval_ret (*custom)(struct check *, struct tcpcheck_rule *, int);
diff --git a/reg-tests/checks/http-check-expect.vtc b/reg-tests/checks/http-check-expect.vtc
new file mode 100644
index 0000000..83e4330
--- /dev/null
+++ b/reg-tests/checks/http-check-expect.vtc
@@ -0,0 +1,66 @@
+varnishtest "Health-checks: some http-check expect tests"
+feature ignore_unknown_macro
+#REQUIRE_VERSION=2.2
+#REGTEST_TYPE=slow
+# This script tests http-check expect rules.
+
+server s1 {
+    rxreq
+    expect req.method == OPTIONS
+    expect req.url == /
+    expect req.proto == HTTP/1.0
+    txresp -status 202 \
+      -hdr "x-test1: true, next value" \
+      -hdr "x-test2: true, begin-value, value-end, value-sub-string, value-reg-123ABC" \
+      -hdr "x-begin-test: 1" \
+      -hdr "x-test-end: 1" \
+      -hdr "x-sub-test: 1" \
+      -hdr "x-reg-test1: 1" \
+      -hdr "x-hdr-name: x-test1"
+} -start
+
+syslog S1 -level notice {
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Proxy be1 started."
+    recv
+    expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be1/srv succeeded.*code: 202"
+} -start
+
+haproxy h1 -conf {
+    defaults
+        mode http
+        timeout client 1s
+        timeout server 1s
+        timeout connect 100ms
+        option log-health-checks
+
+    backend be1
+        log ${S1_addr}:${S1_port} len 2048 local0
+        option httpchk
+	http-check expect status 200-399
+
+	http-check expect header name "x-test1"
+	http-check expect header name -m str "X-Test2"
+	http-check expect header name -m beg "X-Begin-"
+	http-check expect header name -m end "-End"
+	http-check expect header name -m sub "-Sub-"
+	http-check expect header name -m reg "^[a-z]+-Reg-[a-z]+[0-9]\$"
+	http-check set-var(check.hdr_name) check.fhdr(x-hdr-name)
+	http-check expect header name -m str "%[var(check.hdr_name)]" log-format
+	http-check expect header name -m str "%[check.fhdr(x-hdr-name)]" log-format
+
+	http-check expect header name "x-test1" value "true, next value" full
+	http-check expect header name "x-test2" value -m str "true"
+	http-check expect header name -m beg "x-test" value -m beg "begin-"
+	http-check expect header name -m beg "x-test" value -m end "-end"
+	http-check expect header name -m beg "x-test" value -m sub "-sub-"
+	http-check expect header name -m beg "x-test" value -m reg "^value-reg-[A-Z0-9]+\$"
+	http-check expect header name -m beg "x-test" value -m reg "value-reg-[A-Z0-9]+" full
+	http-check set-var(check.hdr_value) str(x-test1)
+	http-check expect header name -m beg "x-" value -m str "%[var(check.hdr_value)]" log-format
+	http-check expect header name -m beg "x-" value -m str "%[check.fhdr(x-hdr-name)]" log-format full
+
+        server srv ${s1_addr}:${s1_port} check inter 100ms rise 1 fall 1
+} -start
+
+syslog S1 -wait
diff --git a/src/checks.c b/src/checks.c
index 8d371e6..a5ddc13 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -606,6 +606,9 @@
 				case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
 					chunk_appendf(chk, " (expect HTTP status regex)");
 					break;
+				case TCPCHK_EXPECT_HTTP_HEADER:
+					chunk_appendf(chk, " (expect HTTP header pattern)");
+					break;
 				case TCPCHK_EXPECT_HTTP_BODY:
 					chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
 					break;
@@ -796,6 +799,21 @@
 		case TCPCHK_EXPECT_HTTP_REGEX_BODY:
 			regex_free(rule->expect.regex);
 			break;
+		case TCPCHK_EXPECT_HTTP_HEADER:
+			if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
+				regex_free(rule->expect.hdr.name_re);
+			else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
+				free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
+			else
+				istfree(&rule->expect.hdr.name);
+
+			if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
+				regex_free(rule->expect.hdr.value_re);
+			else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
+				free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
+			else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
+				istfree(&rule->expect.hdr.value);
+			break;
 		case TCPCHK_EXPECT_CUSTOM:
 		case TCPCHK_EXPECT_UNDEF:
 			break;
@@ -1069,6 +1087,8 @@
 	case TCPCHK_EXPECT_CUSTOM:
 		chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
 		break;
+	case TCPCHK_EXPECT_HTTP_HEADER:
+		chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
 	case TCPCHK_EXPECT_UNDEF:
 		/* Should never happen. */
 		return;
@@ -2116,8 +2136,8 @@
 	struct htx_blk *blk;
 	enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
 	struct tcpcheck_expect *expect = &rule->expect;
-	struct buffer *msg = NULL;
-	enum healthcheck_status status;
+	struct buffer *msg = NULL, *nbuf = NULL, *vbuf = NULL;
+	enum healthcheck_status status = HCHK_STATUS_L7RSP;
 	struct ist desc = IST_NULL;
 	int i, match, inverse;
 
@@ -2176,6 +2196,110 @@
 			desc = htx_sl_res_reason(sl);
 		break;
 
+	case TCPCHK_EXPECT_HTTP_HEADER: {
+		struct http_hdr_ctx ctx;
+		struct ist npat, vpat, value;
+		int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
+
+		if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
+			nbuf = alloc_trash_chunk();
+			if (!nbuf)
+				goto error;
+			nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
+			npat = ist2(b_orig(nbuf), b_data(nbuf));
+		}
+		else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
+			npat = expect->hdr.name;
+
+		if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
+			vbuf = alloc_trash_chunk();
+			if (!vbuf)
+				goto error;
+			vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
+			vpat = ist2(b_orig(vbuf), b_data(vbuf));
+		}
+		else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
+			vpat = expect->hdr.value;
+
+		match = 0;
+		ctx.blk = NULL;
+		while (1) {
+			switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
+			case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
+				if (!http_find_str_header(htx, npat, &ctx, full))
+					goto end_of_match;
+				break;
+			case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
+				if (!http_find_pfx_header(htx, npat, &ctx, full))
+					goto end_of_match;
+				break;
+			case TCPCHK_EXPT_FL_HTTP_HNAME_END:
+				if (!http_find_sfx_header(htx, npat, &ctx, full))
+					goto end_of_match;
+				break;
+			case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
+				if (!http_find_sub_header(htx, npat, &ctx, full))
+					goto end_of_match;
+				break;
+			case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
+				if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
+					goto end_of_match;
+				break;
+			}
+
+			if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
+				match = 1;
+				goto end_of_match;
+			}
+
+			value = ctx.value;
+			switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
+			case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
+				if (isteq(value, vpat)) {
+					match = 1;
+					goto end_of_match;
+				}
+				break;
+			case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
+				if (istlen(value) < istlen(vpat))
+					break;
+				value = ist2(istptr(value), istlen(vpat));
+				if (isteq(value, vpat)) {
+					match = 1;
+					goto end_of_match;
+				}
+				break;
+			case TCPCHK_EXPT_FL_HTTP_HVAL_END:
+				if (istlen(value) < istlen(vpat))
+					break;
+				value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
+				if (isteq(value, vpat)) {
+					match = 1;
+					goto end_of_match;
+				}
+				break;
+			case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
+				if (isttest(istist(value, vpat))) {
+					match = 1;
+					goto end_of_match;
+				}
+				break;
+			case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
+				if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
+					match = 1;
+					goto end_of_match;
+				}
+				break;
+			}
+		}
+
+	  end_of_match:
+		status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7STS);
+		if (LIST_ISEMPTY(&expect->onerror_fmt))
+			desc = htx_sl_res_reason(sl);
+		break;
+	}
+
 	case TCPCHK_EXPECT_HTTP_BODY:
 	case TCPCHK_EXPECT_HTTP_REGEX_BODY:
 		chunk_reset(&trash);
@@ -2237,6 +2361,8 @@
 		goto error;
 
   out:
+	free_trash_chunk(nbuf);
+	free_trash_chunk(vbuf);
 	free_trash_chunk(msg);
 	return ret;
 
@@ -3969,15 +4095,16 @@
 {
 	struct tcpcheck_rule *prev_check, *chk = NULL;
 	struct sample_expr *status_expr = NULL;
-	char *on_success_msg, *on_error_msg, *comment, *pattern;
+	char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
 	enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
 	enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
 	enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
 	enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
+	unsigned int flags = 0;
 	long min_recv = -1;
 	int inverse = 0;
 
-	on_success_msg = on_error_msg = comment = pattern = NULL;
+	on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
 	if (!*(args[cur_arg+1])) {
 		memprintf(errmsg, "expects at least a matching pattern as arguments");
 		goto error;
@@ -4075,6 +4202,116 @@
 			}
 			type = TCPCHK_EXPECT_CUSTOM;
 		}
+		else if (strcmp(args[cur_arg], "header") == 0) {
+			int orig_arg = cur_arg;
+
+			if (proto != TCPCHK_RULES_HTTP_CHK)
+				goto bad_tcp_kw;
+			if (type != TCPCHK_EXPECT_UNDEF) {
+				memprintf(errmsg, "only on pattern expected");
+				goto error;
+			}
+			type = TCPCHK_EXPECT_HTTP_HEADER;
+
+			/* Parse the name pattern, mandatory */
+			if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) || strcmp(args[cur_arg+1], "name") != 0) {
+				memprintf(errmsg, "'%s' expects at the keyword name as first argument followed by a pattern",
+					  args[orig_arg]);
+				goto error;
+			}
+			cur_arg += 2;
+			if (strcmp(args[cur_arg], "-m") == 0) {
+				if  (!*(args[cur_arg+1])) {
+					memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
+						  args[orig_arg], args[cur_arg]);
+					goto error;
+				}
+				if (strcmp(args[cur_arg+1], "str") == 0)
+					flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
+				else if (strcmp(args[cur_arg+1], "beg") == 0)
+					flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
+				else if (strcmp(args[cur_arg+1], "end") == 0)
+					flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
+				else if (strcmp(args[cur_arg+1], "sub") == 0)
+					flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
+				else if (strcmp(args[cur_arg+1], "reg") == 0)
+					flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
+				else {
+					memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
+						  args[orig_arg], args[cur_arg], args[cur_arg+1]);
+					goto error;
+				}
+				cur_arg += 2;
+			}
+			else
+				flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
+			npat = args[cur_arg];
+
+			if (!(*args[cur_arg+1])) {
+				flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
+				goto next;
+			}
+
+			if (strcmp(args[cur_arg+1], "log-format") == 0) {
+				if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
+					memprintf(errmsg, "'%s': '%s' cannot be used with a regex matching pattern",
+						  args[orig_arg], args[cur_arg+1]);
+					goto error;
+				}
+				flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
+				cur_arg++;
+			}
+
+			if (!(*args[cur_arg+1]) || strcmp(args[cur_arg+1], "value") != 0) {
+				flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
+				goto next;
+			}
+
+			/* Parse the value pattern, optionnal */
+			cur_arg += 2;
+			if (strcmp(args[cur_arg], "-m") == 0) {
+				if  (!*(args[cur_arg+1])) {
+					memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
+						  args[orig_arg], args[cur_arg]);
+					goto error;
+				}
+				if (strcmp(args[cur_arg+1], "str") == 0)
+					flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
+				else if (strcmp(args[cur_arg+1], "beg") == 0)
+					flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
+				else if (strcmp(args[cur_arg+1], "end") == 0)
+					flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
+				else if (strcmp(args[cur_arg+1], "sub") == 0)
+					flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
+				else if (strcmp(args[cur_arg+1], "reg") == 0)
+					flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
+				else {
+					memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
+						  args[orig_arg], args[cur_arg], args[cur_arg+1]);
+					goto error;
+				}
+				cur_arg += 2;
+			}
+			else
+				flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
+			vpat = args[cur_arg];
+
+			while (*args[cur_arg+1]) {
+				if (strcmp(args[cur_arg+1], "log-format") == 0) {
+					if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
+						memprintf(errmsg, "'%s': '%s' cannot be used with a regex matching pattern",
+							  args[orig_arg], args[cur_arg+1]);
+						goto error;
+					}
+					flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
+				}
+				else if (strcmp(args[cur_arg+1], "full") == 0)
+					flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
+				else
+					break;
+				cur_arg++;
+			}
+		}
 		else if (strcmp(args[cur_arg], "comment") == 0) {
 			if (in_pattern) {
 				memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
@@ -4220,7 +4457,7 @@
 			if (proto == TCPCHK_RULES_HTTP_CHK) {
 			  bad_http_kw:
 				memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
-					  " or comment but got '%s' as argument.", args[cur_arg]);
+					  "[!]header or comment but got '%s' as argument.", args[cur_arg]);
 			}
 			else {
 			  bad_tcp_kw:
@@ -4229,7 +4466,7 @@
 			}
 			goto error;
 		}
-
+	  next:
 		cur_arg++;
 	}
 
@@ -4244,7 +4481,7 @@
 	chk->comment = comment; comment = NULL;
 	chk->expect.type = type;
 	chk->expect.min_recv = min_recv;
-	chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
+	chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
 	chk->expect.ok_status = ok_st;
 	chk->expect.err_status = err_st;
 	chk->expect.tout_status = tout_st;
@@ -4328,6 +4565,63 @@
 		chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
 		if (!chk->expect.regex)
 			goto error;
+		break;
+	case TCPCHK_EXPECT_HTTP_HEADER:
+		if (!npat) {
+			memprintf(errmsg, "unexpected error, undefined header name pattern");
+			goto error;
+		}
+		if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
+			chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
+			if (!chk->expect.hdr.name_re)
+				goto error;
+		}
+		else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
+			px->conf.args.ctx = ARGC_SRV;
+			LIST_INIT(&chk->expect.hdr.name_fmt);
+			if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
+				memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
+				goto error;
+			}
+		}
+		else {
+			chk->expect.hdr.name = ist2(strdup(npat), strlen(npat));
+			if (!isttest(chk->expect.hdr.name)) {
+				memprintf(errmsg, "out of memory");
+				goto error;
+			}
+		}
+
+		if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
+			chk->expect.hdr.value = IST_NULL;
+			break;
+		}
+
+		if (!vpat) {
+			memprintf(errmsg, "unexpected error, undefined header value pattern");
+			goto error;
+		}
+		else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
+			chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
+			if (!chk->expect.hdr.value_re)
+				goto error;
+		}
+		else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
+			px->conf.args.ctx = ARGC_SRV;
+			LIST_INIT(&chk->expect.hdr.value_fmt);
+			if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
+				memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
+				goto error;
+			}
+		}
+		else {
+			chk->expect.hdr.value = ist2(strdup(vpat), strlen(vpat));
+			if (!isttest(chk->expect.hdr.value)) {
+				memprintf(errmsg, "out of memory");
+				goto error;
+			}
+		}
+
 		break;
 	case TCPCHK_EXPECT_CUSTOM:
 		chk->expect.custom = NULL; /* Must be defined by the caller ! */