MEDIUM: http: register http-request and http-response keywords

The http_(res|req)_keywords_register() functions allow to register
new keywords.

You need to declare a keyword list:

struct http_req_action_kw_list test_kws = {
	.scope = "testscope",
	.kw = {
		{ "test", parse_test },
		{ NULL, NULL },
	}
};

and a parsing function:

int parse_test(const char **args, int *cur_arg, struct proxy *px, struct http_req_rule *rule, char **err)
{
	rule->action = HTTP_REQ_ACT_CUSTOM_STOP;
	rule->action_ptr = action_function;

	return 0;
}

http_req_keywords_register(&test_kws);

The HTTP_REQ_ACT_CUSTOM_STOP action stops evaluation of rules after
your rule, HTTP_REQ_ACT_CUSTOM_CONT permits the evaluation of rules
after your rule.
diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h
index 9653d8b..065a0ea 100644
--- a/include/proto/proto_http.h
+++ b/include/proto/proto_http.h
@@ -122,6 +122,20 @@
 
 enum http_meth_t find_http_meth(const char *str, const int len);
 
+struct http_req_action_kw *action_http_req_custom(const char *kw);
+struct http_res_action_kw *action_http_res_custom(const char *kw);
+
+static inline void http_req_keywords_register(struct http_req_action_kw_list *kw_list)
+{
+	LIST_ADDQ(&http_req_keywords.list, &kw_list->list);
+}
+
+static inline void http_res_keywords_register(struct http_res_action_kw_list *kw_list)
+{
+	LIST_ADDQ(&http_res_keywords.list, &kw_list->list);
+}
+
+
 /* to be used when contents change in an HTTP message */
 #define http_msg_move_end(msg, bytes) do { \
 		unsigned int _bytes = (bytes);	\
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index f084ecd..a030fee 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -256,6 +256,8 @@
 	HTTP_REQ_ACT_DEL_ACL,
 	HTTP_REQ_ACT_DEL_MAP,
 	HTTP_REQ_ACT_SET_MAP,
+	HTTP_REQ_ACT_CUSTOM_STOP,
+	HTTP_REQ_ACT_CUSTOM_CONT,
 	HTTP_REQ_ACT_MAX /* must always be last */
 };
 
@@ -275,6 +277,8 @@
 	HTTP_RES_ACT_DEL_ACL,
 	HTTP_RES_ACT_DEL_MAP,
 	HTTP_RES_ACT_SET_MAP,
+	HTTP_RES_ACT_CUSTOM_STOP,  /* used for module keywords */
+	HTTP_RES_ACT_CUSTOM_CONT,  /* used for module keywords */
 	HTTP_RES_ACT_MAX /* must always be last */
 };
 
@@ -394,10 +398,15 @@
 	char *user, *pass;                    /* extracted username & password */
 };
 
+struct proxy;
+struct http_txn;
+struct session;
+
 struct http_req_rule {
 	struct list list;
 	struct acl_cond *cond;                 /* acl condition to meet */
 	unsigned int action;                   /* HTTP_REQ_* */
+	int (*action_ptr)(struct http_req_rule *rule, struct proxy *px, struct session *s, struct http_txn *http_txn);  /* ptr to custom action */
 	union {
 		struct {
 			char *realm;
@@ -424,6 +433,7 @@
 	struct list list;
 	struct acl_cond *cond;                 /* acl condition to meet */
 	unsigned int action;                   /* HTTP_RES_* */
+	int (*action_ptr)(struct http_res_rule *rule, struct proxy *px, struct session *s, struct http_txn *http_txn);  /* ptr to custom action */
 	union {
 		struct {
 			char *name;            /* header name */
@@ -464,6 +474,7 @@
 	struct http_auth_data auth;	/* HTTP auth data */
 };
 
+
 /* This structure is used by http_find_header() to return values of headers.
  * The header starts at <line>, the value (excluding leading and trailing white
  * spaces) at <line>+<val> for <vlen> bytes, followed by optional <tws> trailing
@@ -486,6 +497,31 @@
 	int len;
 };
 
+struct http_req_action_kw {
+       const char *kw;
+       int (*parse)(const char **args, int *cur_arg, struct proxy *px, struct http_req_rule *rule, char **err);
+};
+
+struct http_res_action_kw {
+       const char *kw;
+       int (*parse)(const char **args, int *cur_arg, struct proxy *px, struct http_res_rule *rule, char **err);
+};
+
+struct http_req_action_kw_list {
+       const char *scope;
+       struct list list;
+       struct http_req_action_kw kw[VAR_ARRAY];
+};
+
+struct http_res_action_kw_list {
+       const char *scope;
+       struct list list;
+       struct http_res_action_kw kw[VAR_ARRAY];
+};
+
+extern struct http_req_action_kw_list http_req_keywords;
+extern struct http_res_action_kw_list http_res_keywords;
+
 extern const struct http_method_name http_known_methods[HTTP_METH_OTHER];
 
 #endif /* _TYPES_PROTO_HTTP_H */
diff --git a/src/proto_http.c b/src/proto_http.c
index e9004f8..8467dbc 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -217,6 +217,16 @@
 };
 
 
+/* List head of all known action keywords for "http-request" */
+struct http_req_action_kw_list http_req_keywords = {
+       .list = LIST_HEAD_INIT(http_req_keywords.list)
+};
+
+/* List head of all known action keywords for "http-response" */
+struct http_res_action_kw_list http_res_keywords = {
+       .list = LIST_HEAD_INIT(http_res_keywords.list)
+};
+
 /* We must put the messages here since GCC cannot initialize consts depending
  * on strlen().
  */
@@ -3289,6 +3299,14 @@
 
 			break;
 			}
+
+		case HTTP_REQ_ACT_CUSTOM_CONT:
+			rule->action_ptr(rule, px, s, txn);
+			break;
+
+		case HTTP_REQ_ACT_CUSTOM_STOP:
+			rule->action_ptr(rule, px, s, txn);
+			return rule;
 		}
 	}
 
@@ -3462,6 +3480,14 @@
 
 			break;
 			}
+
+		case HTTP_RES_ACT_CUSTOM_CONT:
+			rule->action_ptr(rule, px, s, txn);
+			break;
+
+		case HTTP_RES_ACT_CUSTOM_STOP:
+			rule->action_ptr(rule, px, s, txn);
+			return rule;
 		}
 	}
 
@@ -3959,6 +3985,11 @@
 		return 1;
 	}
 
+	if (http_req_last_rule && http_req_last_rule->action == HTTP_REQ_ACT_CUSTOM_STOP) {
+		req->analyse_exp = TICK_ETERNITY;
+		return 1;
+	}
+
 	if (unlikely(objt_applet(s->target) == &http_stats_applet)) {
 		/* process the stats request now */
 		if (s->fe == s->be) /* report it if the request was intercepted by the frontend */
@@ -8650,6 +8681,7 @@
 struct http_req_rule *parse_http_req_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
 {
 	struct http_req_rule *rule;
+	struct http_req_action_kw *custom = NULL;
 	int cur_arg;
 
 	rule = (struct http_req_rule*)calloc(1, sizeof(struct http_req_rule));
@@ -8938,6 +8970,16 @@
 		proxy->conf.lfs_line = proxy->conf.args.line;
 
 		cur_arg += 2;
+	} else if (((custom = action_http_req_custom(args[0])) != NULL)) {
+		char *errmsg = NULL;
+		cur_arg = 1;
+		/* try in the module list */
+		if (custom->parse(args, &cur_arg, proxy, rule, &errmsg) < 0) {
+			Alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-request %s' rule : %s.\n",
+			      file, linenum, proxy_type_str(proxy), proxy->id, args[0], errmsg);
+			free(errmsg);
+			goto out_err;
+		}
 	} else {
 		Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', 'redirect', 'tarpit', 'add-header', 'set-header', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map', but got '%s'%s.\n",
 		      file, linenum, args[0], *args[0] ? "" : " (missing argument)");
@@ -8973,6 +9015,7 @@
 struct http_res_rule *parse_http_res_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
 {
 	struct http_res_rule *rule;
+	struct http_res_action_kw *custom = NULL;
 	int cur_arg;
 
 	rule = calloc(1, sizeof(*rule));
@@ -9230,6 +9273,16 @@
 		proxy->conf.lfs_line = proxy->conf.args.line;
 
 		cur_arg += 2;
+	} else if (((custom = action_http_res_custom(args[0])) != NULL)) {
+		char *errmsg = NULL;
+		cur_arg = 1;
+		/* try in the module list */
+		if (custom->parse(args, &cur_arg, proxy, rule, &errmsg) < 0) {
+			Alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-response %s' rule : %s.\n",
+			      file, linenum, proxy_type_str(proxy), proxy->id, args[0], errmsg);
+			free(errmsg);
+			goto out_err;
+		}
 	} else {
 		Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', 'add-header', 'del-header', 'set-header', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', 'del-acl', 'add-acl', 'del-map', 'set-map', but got '%s'%s.\n",
 		      file, linenum, args[0], *args[0] ? "" : " (missing argument)");
@@ -11122,6 +11175,44 @@
 	return smp->data.str.len != 0;
 }
 
+/*
+ * Return the struct http_req_action_kw associated to a keyword.
+ */
+struct http_req_action_kw *action_http_req_custom(const char *kw)
+{
+	if (!LIST_ISEMPTY(&http_req_keywords.list)) {
+		struct http_req_action_kw_list *kw_list;
+		int i;
+
+		list_for_each_entry(kw_list, &http_req_keywords.list, list) {
+			for (i = 0; kw_list->kw[i].kw != NULL; i++) {
+				if (!strcmp(kw, kw_list->kw[i].kw))
+					return &kw_list->kw[i];
+			}
+		}
+	}
+	return NULL;
+}
+
+/*
+ * Return the struct http_res_action_kw associated to a keyword.
+ */
+struct http_res_action_kw *action_http_res_custom(const char *kw)
+{
+	if (!LIST_ISEMPTY(&http_res_keywords.list)) {
+		struct http_res_action_kw_list *kw_list;
+		int i;
+
+		list_for_each_entry(kw_list, &http_res_keywords.list, list) {
+			for (i = 0; kw_list->kw[i].kw != NULL; i++) {
+				if (!strcmp(kw, kw_list->kw[i].kw))
+					return &kw_list->kw[i];
+			}
+		}
+	}
+	return NULL;
+}
+
 /************************************************************************/
 /*          All supported ACL keywords must be declared here.           */
 /************************************************************************/