MEDIUM: pattern: add an argument validation callback to pattern descriptors

This is used to validate that arguments are coherent. For instance,
payload_lv expects that the last arg (if any) is not more negative
than the sum of the first two. The error is reported if any.
diff --git a/include/types/pattern.h b/include/types/pattern.h
index c371886..42dac41 100644
--- a/include/types/pattern.h
+++ b/include/types/pattern.h
@@ -65,6 +65,8 @@
 	int (*process)(const struct arg *arg_p,
 		       union pattern_data *data); /* process function */
 	unsigned int arg_mask;                    /* arguments (ARG*()) */
+	int (*val_args)(struct arg *arg_p,
+			char **err_msg);          /* argument validation function */
 	unsigned int in_type;                     /* input needed pattern type */
 	unsigned int out_type;                    /* output pattern type */
 };
@@ -85,6 +87,8 @@
 	               int dir, const struct arg *arg_p,
 	               union pattern_data *data); /* fetch processing function */
 	unsigned int arg_mask;                    /* arguments (ARG*()) */
+	int (*val_args)(struct arg *arg_p,
+			char **err_msg);          /* argument validation function */
 	unsigned long out_type;                   /* output pattern type */
 	int dir;                                  /* usable directions */
 };
diff --git a/src/pattern.c b/src/pattern.c
index 670eff7..c2de92d 100644
--- a/src/pattern.c
+++ b/src/pattern.c
@@ -316,6 +316,8 @@
 	expr->fetch = fetch;
 
 	if (end != endw) {
+		char *err_msg;
+
 		if (!fetch->arg_mask) {
 			p = my_strndup(str[*idx], endw - str[*idx]);
 			if (p) {
@@ -331,6 +333,16 @@
 				snprintf(err, err_size, "invalid args in fetch method '%s'.", p);
 				free(p);
 			}
+			goto out_error;
+		}
+
+		if (fetch->val_args && !fetch->val_args(expr->arg_p, &err_msg)) {
+			p = my_strndup(str[*idx], endw - str[*idx]);
+			if (p) {
+				snprintf(err, err_size, "invalid args in fetch method '%s' : %s.", p, err_msg);
+				free(p);
+			}
+			free(err_msg);
 			goto out_error;
 		}
 	}
@@ -393,6 +405,8 @@
 		conv_expr->conv = conv;
 
 		if (end != endw) {
+			char *err_msg;
+
 			if (!conv->arg_mask) {
 				p = my_strndup(str[*idx], endw - str[*idx]);
 
@@ -409,6 +423,16 @@
 					snprintf(err, err_size, "invalid args in conv method '%s'.", p);
 					free(p);
 				}
+				goto out_error;
+			}
+
+			if (conv->val_args && !conv->val_args(conv_expr->arg_p, &err_msg)) {
+				p = my_strndup(str[*idx], endw - str[*idx]);
+				if (p) {
+					snprintf(err, err_size, "invalid args in conv method '%s' : %s.", p, err_msg);
+					free(p);
+				}
+				free(err_msg);
 				goto out_error;
 			}
 		}
@@ -505,9 +529,9 @@
 
 /* Note: must not be declared <const> as its list will be overwritten */
 static struct pattern_conv_kw_list pattern_conv_kws = {{ },{
-	{ "upper",  pattern_conv_str2upper, 0,            PATTERN_TYPE_STRING, PATTERN_TYPE_STRING },
-	{ "lower",  pattern_conv_str2lower, 0,            PATTERN_TYPE_STRING, PATTERN_TYPE_STRING },
-	{ "ipmask", pattern_conv_ipmask,    ARG1(1,MSK4), PATTERN_TYPE_IP,     PATTERN_TYPE_IP },
+	{ "upper",  pattern_conv_str2upper, 0,            NULL, PATTERN_TYPE_STRING, PATTERN_TYPE_STRING },
+	{ "lower",  pattern_conv_str2lower, 0,            NULL, PATTERN_TYPE_STRING, PATTERN_TYPE_STRING },
+	{ "ipmask", pattern_conv_ipmask,    ARG1(1,MSK4), NULL, PATTERN_TYPE_IP,     PATTERN_TYPE_IP },
 	{ NULL, NULL, 0, 0, 0 },
 }};
 
diff --git a/src/proto_http.c b/src/proto_http.c
index 7809b7a..f099913 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -8533,16 +8533,15 @@
 	return found;
 }
 
-
 /************************************************************************/
 /*             All supported keywords must be declared here.            */
 /************************************************************************/
 /* Note: must not be declared <const> as its list will be overwritten */
 static struct pattern_fetch_kw_list pattern_fetch_keywords = {{ },{
-	{ "hdr",        pattern_fetch_hdr,        ARG1(1,STR), PATTERN_TYPE_STRING, PATTERN_FETCH_REQ },
-	{ "url_param",  pattern_fetch_url_param,  ARG1(1,STR), PATTERN_TYPE_STRING, PATTERN_FETCH_REQ },
-	{ "cookie",     pattern_fetch_cookie,     ARG1(1,STR), PATTERN_TYPE_STRING, PATTERN_FETCH_REQ },
-	{ "set-cookie", pattern_fetch_set_cookie, ARG1(1,STR), PATTERN_TYPE_STRING, PATTERN_FETCH_RTR },
+	{ "hdr",        pattern_fetch_hdr,        ARG1(1,STR), NULL, PATTERN_TYPE_STRING, PATTERN_FETCH_REQ },
+	{ "url_param",  pattern_fetch_url_param,  ARG1(1,STR), NULL, PATTERN_TYPE_STRING, PATTERN_FETCH_REQ },
+	{ "cookie",     pattern_fetch_cookie,     ARG1(1,STR), NULL, PATTERN_TYPE_STRING, PATTERN_FETCH_REQ },
+	{ "set-cookie", pattern_fetch_set_cookie, ARG1(1,STR), NULL, PATTERN_TYPE_STRING, PATTERN_FETCH_RTR },
 	{ NULL, NULL, 0, 0, 0 },
 }};
 
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 52d5caf..715f1e9 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -1493,6 +1493,50 @@
 	return 1;
 }
 
+/* This function is used to validate the arguments passed to a "payload" fetch
+ * keyword. This keyword expects two positive integers, with the second one
+ * being strictly positive. It is assumed that the types are already the correct
+ * ones. Returns 0 on error, non-zero if OK. If <err_msg> is not NULL, it will be
+ * filled with a pointer to an error message in case of error, that the caller
+ * is responsible for freeing. The initial location must either be freeable or
+ * NULL.
+ */
+static int val_payload(struct arg *arg, char **err_msg)
+{
+	if (!arg[1].data.uint) {
+		if (err_msg)
+			memprintf(err_msg, "payload length must be > 0");
+		return 0;
+	}
+	return 1;
+}
+
+/* This function is used to validate the arguments passed to a "payload_lv" fetch
+ * keyword. This keyword allows two positive integers and an optional signed one,
+ * with the second one being strictly positive and the third one being greater than
+ * the opposite of the two others if negative. It is assumed that the types are
+ * already the correct ones. Returns 0 on error, non-zero if OK. If <err_msg> is
+ * not NULL, it will be filled with a pointer to an error message in case of
+ * error, that the caller is responsible for freeing. The initial location must
+ * either be freeable or NULL.
+ */
+static int val_payload_lv(struct arg *arg, char **err_msg)
+{
+	if (!arg[1].data.uint) {
+		if (err_msg)
+			memprintf(err_msg, "payload length must be > 0");
+		return 0;
+	}
+
+	if (arg[2].type == ARGT_SINT &&
+	    (int)(arg[0].data.uint + arg[1].data.uint + arg[2].data.sint) < 0) {
+		if (err_msg)
+			memprintf(err_msg, "payload offset too negative");
+		return 0;
+	}
+	return 1;
+}
+
 static struct cfg_kw_list cfg_kws = {{ },{
 	{ CFG_LISTEN, "tcp-request", tcp_parse_tcp_req },
 	{ CFG_LISTEN, "tcp-response", tcp_parse_tcp_rep },
@@ -1512,14 +1556,14 @@
 
 /* Note: must not be declared <const> as its list will be overwritten */
 static struct pattern_fetch_kw_list pattern_fetch_keywords = {{ },{
-	{ "src",         pattern_fetch_src,       0,                      PATTERN_TYPE_IP,        PATTERN_FETCH_REQ },
-	{ "src6",        pattern_fetch_src6,      0,                      PATTERN_TYPE_IPV6,      PATTERN_FETCH_REQ },
-	{ "dst",         pattern_fetch_dst,       0,                      PATTERN_TYPE_IP,        PATTERN_FETCH_REQ },
-	{ "dst6",        pattern_fetch_dst6,      0,                      PATTERN_TYPE_IPV6,      PATTERN_FETCH_REQ },
-	{ "dst_port",    pattern_fetch_dport,     0,                      PATTERN_TYPE_INTEGER,   PATTERN_FETCH_REQ },
-	{ "payload",     pattern_fetch_payload,   ARG2(2,UINT,UINT),      PATTERN_TYPE_CONSTDATA, PATTERN_FETCH_REQ|PATTERN_FETCH_RTR },
-	{ "payload_lv",  pattern_fetch_payloadlv, ARG3(2,UINT,UINT,SINT), PATTERN_TYPE_CONSTDATA, PATTERN_FETCH_REQ|PATTERN_FETCH_RTR },
-	{ "rdp_cookie",  pattern_fetch_rdp_cookie, ARG1(1,STR),           PATTERN_TYPE_CONSTSTRING, PATTERN_FETCH_REQ },
+	{ "src",         pattern_fetch_src,       0,                      NULL,           PATTERN_TYPE_IP,        PATTERN_FETCH_REQ },
+	{ "src6",        pattern_fetch_src6,      0,                      NULL,           PATTERN_TYPE_IPV6,      PATTERN_FETCH_REQ },
+	{ "dst",         pattern_fetch_dst,       0,                      NULL,           PATTERN_TYPE_IP,        PATTERN_FETCH_REQ },
+	{ "dst6",        pattern_fetch_dst6,      0,                      NULL,           PATTERN_TYPE_IPV6,      PATTERN_FETCH_REQ },
+	{ "dst_port",    pattern_fetch_dport,     0,                      NULL,           PATTERN_TYPE_INTEGER,   PATTERN_FETCH_REQ },
+	{ "payload",     pattern_fetch_payload,   ARG2(2,UINT,UINT),      val_payload,    PATTERN_TYPE_CONSTDATA, PATTERN_FETCH_REQ|PATTERN_FETCH_RTR },
+	{ "payload_lv",  pattern_fetch_payloadlv, ARG3(2,UINT,UINT,SINT), val_payload_lv, PATTERN_TYPE_CONSTDATA, PATTERN_FETCH_REQ|PATTERN_FETCH_RTR },
+	{ "rdp_cookie",  pattern_fetch_rdp_cookie, ARG1(1,STR),           NULL,           PATTERN_TYPE_CONSTSTRING, PATTERN_FETCH_REQ },
 	{ NULL, NULL, 0, 0, 0 },
 }};