MINOR: proto_htx: Add functions htx_req_get_intercept_rule and htx_res_get_intercept_rule

It is more or less the same than legacy versions but adapted to be called from
HTX analyzers.
diff --git a/src/proto_htx.c b/src/proto_htx.c
index 83a2c33..2b2fcf6 100644
--- a/src/proto_htx.c
+++ b/src/proto_htx.c
@@ -19,6 +19,7 @@
 #include <types/capture.h>
 
 #include <proto/acl.h>
+#include <proto/action.h>
 #include <proto/channel.h>
 #include <proto/checks.h>
 #include <proto/connection.h>
@@ -27,6 +28,7 @@
 #include <proto/http_htx.h>
 #include <proto/htx.h>
 #include <proto/log.h>
+#include <proto/pattern.h>
 #include <proto/proto_http.h>
 #include <proto/proxy.h>
 #include <proto/stream.h>
@@ -44,6 +46,9 @@
 static void htx_debug_stline(const char *dir, struct stream *s, const union h1_sl sl);
 static void htx_debug_hdr(const char *dir, struct stream *s, const struct ist n, const struct ist v);
 
+static enum rule_result htx_req_get_intercept_rule(struct proxy *px, struct list *rules, struct stream *s, int *deny_status);
+static enum rule_result htx_res_get_intercept_rule(struct proxy *px, struct list *rules, struct stream *s);
+
 /* This stream analyser waits for a complete HTTP request. It returns 1 if the
  * processing can continue on next analysers, or zero if it either needs more
  * data or wants to immediately abort the request (eg: timeout, error, ...). It
@@ -2708,6 +2713,747 @@
 		http_replace_res_reason(htx, ist2(reason, strlen(reason)));
 }
 
+/* Executes the http-request rules <rules> for stream <s>, proxy <px> and
+ * transaction <txn>. Returns the verdict of the first rule that prevents
+ * further processing of the request (auth, deny, ...), and defaults to
+ * HTTP_RULE_RES_STOP if it executed all rules or stopped on an allow, or
+ * HTTP_RULE_RES_CONT if the last rule was reached. It may set the TX_CLTARPIT
+ * on txn->flags if it encounters a tarpit rule. If <deny_status> is not NULL
+ * and a deny/tarpit rule is matched, it will be filled with this rule's deny
+ * status.
+ */
+static enum rule_result htx_req_get_intercept_rule(struct proxy *px, struct list *rules,
+						   struct stream *s, int *deny_status)
+{
+	struct session *sess = strm_sess(s);
+	struct http_txn *txn = s->txn;
+	struct htx *htx;
+	struct connection *cli_conn;
+	struct act_rule *rule;
+	struct http_hdr_ctx ctx;
+	const char *auth_realm;
+	struct buffer *early_hints = NULL;
+	enum rule_result rule_ret = HTTP_RULE_RES_CONT;
+	int act_flags = 0;
+
+	htx = htx_from_buf(&s->req.buf);
+
+	/* If "the current_rule_list" match the executed rule list, we are in
+	 * resume condition. If a resume is needed it is always in the action
+	 * and never in the ACL or converters. In this case, we initialise the
+	 * current rule, and go to the action execution point.
+	 */
+	if (s->current_rule) {
+		rule = s->current_rule;
+		s->current_rule = NULL;
+		if (s->current_rule_list == rules)
+			goto resume_execution;
+	}
+	s->current_rule_list = rules;
+
+	list_for_each_entry(rule, rules, list) {
+		/* check optional condition */
+		if (rule->cond) {
+			int ret;
+
+			ret = acl_exec_cond(rule->cond, px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
+			ret = acl_pass(ret);
+
+			if (rule->cond->pol == ACL_COND_UNLESS)
+				ret = !ret;
+
+			if (!ret) /* condition not matched */
+				continue;
+		}
+
+		act_flags |= ACT_FLAG_FIRST;
+  resume_execution:
+		switch (rule->action) {
+			case ACT_ACTION_ALLOW:
+				rule_ret = HTTP_RULE_RES_STOP;
+				goto end;
+
+			case ACT_ACTION_DENY:
+				if (deny_status)
+					*deny_status = rule->deny_status;
+				rule_ret = HTTP_RULE_RES_DENY;
+				goto end;
+
+			case ACT_HTTP_REQ_TARPIT:
+				txn->flags |= TX_CLTARPIT;
+				if (deny_status)
+					*deny_status = rule->deny_status;
+				rule_ret = HTTP_RULE_RES_DENY;
+				goto end;
+
+			case ACT_HTTP_REQ_AUTH:
+				/* Be sure to sned any pending HTTP 103 response first */
+				if (early_hints) {
+					htx_send_early_hints(s, early_hints);
+					free_trash_chunk(early_hints);
+					early_hints = NULL;
+				}
+				/* Auth might be performed on regular http-req rules as well as on stats */
+				auth_realm = rule->arg.auth.realm;
+				if (!auth_realm) {
+					if (px->uri_auth && rules == &px->uri_auth->http_req_rules)
+						auth_realm = STATS_DEFAULT_REALM;
+					else
+						auth_realm = px->id;
+				}
+				/* send 401/407 depending on whether we use a proxy or not. We still
+				 * count one error, because normal browsing won't significantly
+				 * increase the counter but brute force attempts will.
+				 */
+				chunk_printf(&trash, (txn->flags & TX_USE_PX_CONN) ? HTTP_407_fmt : HTTP_401_fmt, auth_realm);
+				txn->status = (txn->flags & TX_USE_PX_CONN) ? 407 : 401;
+				htx_reply_and_close(s, txn->status, &trash);
+				stream_inc_http_err_ctr(s);
+				rule_ret = HTTP_RULE_RES_ABRT;
+				goto end;
+
+			case ACT_HTTP_REDIR:
+				/* Be sure to sned any pending HTTP 103 response first */
+				if (early_hints) {
+					htx_send_early_hints(s, early_hints);
+					free_trash_chunk(early_hints);
+					early_hints = NULL;
+				}
+				rule_ret = HTTP_RULE_RES_DONE;
+				if (!htx_apply_redirect_rule(rule->arg.redir, s, txn))
+					rule_ret = HTTP_RULE_RES_BADREQ;
+				goto end;
+
+			case ACT_HTTP_SET_NICE:
+				s->task->nice = rule->arg.nice;
+				break;
+
+			case ACT_HTTP_SET_TOS:
+				if ((cli_conn = objt_conn(sess->origin)) && conn_ctrl_ready(cli_conn))
+					inet_set_tos(cli_conn->handle.fd, &cli_conn->addr.from, rule->arg.tos);
+				break;
+
+			case ACT_HTTP_SET_MARK:
+#ifdef SO_MARK
+				if ((cli_conn = objt_conn(sess->origin)) && conn_ctrl_ready(cli_conn))
+					setsockopt(cli_conn->handle.fd, SOL_SOCKET, SO_MARK, &rule->arg.mark, sizeof(rule->arg.mark));
+#endif
+				break;
+
+			case ACT_HTTP_SET_LOGL:
+				s->logs.level = rule->arg.loglevel;
+				break;
+
+			case ACT_HTTP_REPLACE_HDR:
+			case ACT_HTTP_REPLACE_VAL:
+				if (htx_transform_header(s, &s->req, htx,
+							 ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len),
+							 &rule->arg.hdr_add.fmt,
+							 &rule->arg.hdr_add.re, rule->action)) {
+					rule_ret = HTTP_RULE_RES_BADREQ;
+					goto end;
+				}
+				break;
+
+			case ACT_HTTP_DEL_HDR:
+				/* remove all occurrences of the header */
+				ctx.blk = NULL;
+				while (http_find_header(htx, ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len), &ctx, 1))
+					http_remove_header(htx, &ctx);
+				break;
+
+			case ACT_HTTP_SET_HDR:
+			case ACT_HTTP_ADD_HDR: {
+				/* The scope of the trash buffer must be limited to this function. The
+				 * build_logline() function can execute a lot of other function which
+				 * can use the trash buffer. So for limiting the scope of this global
+				 * buffer, we build first the header value using build_logline, and
+				 * after we store the header name.
+				 */
+				struct buffer *replace;
+				struct ist n, v;
+
+				replace = alloc_trash_chunk();
+				if (!replace) {
+					rule_ret = HTTP_RULE_RES_BADREQ;
+					goto end;
+				}
+
+				replace->data = build_logline(s, replace->area, replace->size, &rule->arg.hdr_add.fmt);
+				n = ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len);
+				v = ist2(replace->area, replace->data);
+
+				if (rule->action == ACT_HTTP_SET_HDR) {
+					/* remove all occurrences of the header */
+					ctx.blk = NULL;
+					while (http_find_header(htx, ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len), &ctx, 1))
+						http_remove_header(htx, &ctx);
+				}
+
+				if (!http_add_header(htx, n, v)) {
+					static unsigned char rate_limit = 0;
+
+					if ((rate_limit++ & 255) == 0) {
+						send_log(px, LOG_WARNING, "Proxy %s failed to add or set the request header '%.*s' for request #%u. You might need to increase tune.maxrewrite.", px->id, (int)n.len, n.ptr, s->uniq_id);
+					}
+
+					HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
+					if (sess->fe != s->be)
+						HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
+					if (sess->listener->counters)
+						HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
+				}
+				free_trash_chunk(replace);
+				break;
+			}
+
+			case ACT_HTTP_DEL_ACL:
+			case ACT_HTTP_DEL_MAP: {
+				struct pat_ref *ref;
+				struct buffer *key;
+
+				/* collect reference */
+				ref = pat_ref_lookup(rule->arg.map.ref);
+				if (!ref)
+					continue;
+
+				/* allocate key */
+				key = alloc_trash_chunk();
+				if (!key) {
+					rule_ret = HTTP_RULE_RES_BADREQ;
+					goto end;
+				}
+
+				/* collect key */
+				key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
+				key->area[key->data] = '\0';
+
+				/* perform update */
+				/* returned code: 1=ok, 0=ko */
+				HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
+				pat_ref_delete(ref, key->area);
+				HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
+
+				free_trash_chunk(key);
+				break;
+			}
+
+			case ACT_HTTP_ADD_ACL: {
+				struct pat_ref *ref;
+				struct buffer *key;
+
+				/* collect reference */
+				ref = pat_ref_lookup(rule->arg.map.ref);
+				if (!ref)
+					continue;
+
+				/* allocate key */
+				key = alloc_trash_chunk();
+				if (!key) {
+					rule_ret = HTTP_RULE_RES_BADREQ;
+					goto end;
+				}
+
+				/* collect key */
+				key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
+				key->area[key->data] = '\0';
+
+				/* perform update */
+				/* add entry only if it does not already exist */
+				HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
+				if (pat_ref_find_elt(ref, key->area) == NULL)
+					pat_ref_add(ref, key->area, NULL, NULL);
+				HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
+
+				free_trash_chunk(key);
+				break;
+			}
+
+			case ACT_HTTP_SET_MAP: {
+				struct pat_ref *ref;
+				struct buffer *key, *value;
+
+				/* collect reference */
+				ref = pat_ref_lookup(rule->arg.map.ref);
+				if (!ref)
+					continue;
+
+				/* allocate key */
+				key = alloc_trash_chunk();
+				if (!key) {
+					rule_ret = HTTP_RULE_RES_BADREQ;
+					goto end;
+				}
+
+				/* allocate value */
+				value = alloc_trash_chunk();
+				if (!value) {
+					free_trash_chunk(key);
+					rule_ret = HTTP_RULE_RES_BADREQ;
+					goto end;
+				}
+
+				/* collect key */
+				key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
+				key->area[key->data] = '\0';
+
+				/* collect value */
+				value->data = build_logline(s, value->area, value->size, &rule->arg.map.value);
+				value->area[value->data] = '\0';
+
+				/* perform update */
+				if (pat_ref_find_elt(ref, key->area) != NULL)
+					/* update entry if it exists */
+					pat_ref_set(ref, key->area, value->area, NULL);
+				else
+					/* insert a new entry */
+					pat_ref_add(ref, key->area, value->area, NULL);
+
+				free_trash_chunk(key);
+				free_trash_chunk(value);
+				break;
+			}
+
+			case ACT_HTTP_EARLY_HINT:
+				if (!(txn->req.flags & HTTP_MSGF_VER_11))
+					break;
+				early_hints = htx_apply_early_hint_rule(s, early_hints,
+									rule->arg.early_hint.name,
+									rule->arg.early_hint.name_len,
+									&rule->arg.early_hint.fmt);
+				if (!early_hints) {
+					rule_ret = HTTP_RULE_RES_DONE;
+					goto end;
+				}
+				break;
+
+			case ACT_CUSTOM:
+				if ((s->req.flags & CF_READ_ERROR) ||
+				    ((s->req.flags & (CF_SHUTR|CF_READ_NULL)) &&
+				     !(s->si[0].flags & SI_FL_CLEAN_ABRT) &&
+				     (px->options & PR_O_ABRT_CLOSE)))
+					act_flags |= ACT_FLAG_FINAL;
+
+				switch (rule->action_ptr(rule, px, s->sess, s, act_flags)) {
+					case ACT_RET_ERR:
+					case ACT_RET_CONT:
+						break;
+					case ACT_RET_STOP:
+						rule_ret = HTTP_RULE_RES_DONE;
+						goto end;
+					case ACT_RET_YIELD:
+						s->current_rule = rule;
+						rule_ret = HTTP_RULE_RES_YIELD;
+						goto end;
+				}
+				break;
+
+			case ACT_ACTION_TRK_SC0 ... ACT_ACTION_TRK_SCMAX:
+				/* Note: only the first valid tracking parameter of each
+				 * applies.
+				 */
+
+				if (stkctr_entry(&s->stkctr[trk_idx(rule->action)]) == NULL) {
+					struct stktable *t;
+					struct stksess *ts;
+					struct stktable_key *key;
+					void *ptr1, *ptr2;
+
+					t = rule->arg.trk_ctr.table.t;
+					key = stktable_fetch_key(t, s->be, sess, s, SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
+								 rule->arg.trk_ctr.expr, NULL);
+
+					if (key && (ts = stktable_get_entry(t, key))) {
+						stream_track_stkctr(&s->stkctr[trk_idx(rule->action)], t, ts);
+
+						/* let's count a new HTTP request as it's the first time we do it */
+						ptr1 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
+						ptr2 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
+						if (ptr1 || ptr2) {
+							HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
+
+							if (ptr1)
+								stktable_data_cast(ptr1, http_req_cnt)++;
+
+							if (ptr2)
+								update_freq_ctr_period(&stktable_data_cast(ptr2, http_req_rate),
+										       t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
+
+							HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
+
+							/* If data was modified, we need to touch to re-schedule sync */
+							stktable_touch_local(t, ts, 0);
+						}
+
+						stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_CONTENT);
+						if (sess->fe != s->be)
+							stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_BACKEND);
+					}
+				}
+				break;
+
+				/* other flags exists, but normaly, they never be matched. */
+			default:
+				break;
+		}
+	}
+
+  end:
+	if (early_hints) {
+		htx_send_early_hints(s, early_hints);
+		free_trash_chunk(early_hints);
+	}
+
+	/* we reached the end of the rules, nothing to report */
+	return rule_ret;
+}
+
+/* Executes the http-response rules <rules> for stream <s> and proxy <px>. It
+ * returns one of 5 possible statuses: HTTP_RULE_RES_CONT, HTTP_RULE_RES_STOP,
+ * HTTP_RULE_RES_DONE, HTTP_RULE_RES_YIELD, or HTTP_RULE_RES_BADREQ. If *CONT
+ * is returned, the process can continue the evaluation of next rule list. If
+ * *STOP or *DONE is returned, the process must stop the evaluation. If *BADREQ
+ * is returned, it means the operation could not be processed and a server error
+ * must be returned. It may set the TX_SVDENY on txn->flags if it encounters a
+ * deny rule. If *YIELD is returned, the caller must call again the function
+ * with the same context.
+ */
+static enum rule_result htx_res_get_intercept_rule(struct proxy *px, struct list *rules,
+						   struct stream *s)
+{
+	struct session *sess = strm_sess(s);
+	struct http_txn *txn = s->txn;
+	struct htx *htx;
+	struct connection *cli_conn;
+	struct act_rule *rule;
+	struct http_hdr_ctx ctx;
+	enum rule_result rule_ret = HTTP_RULE_RES_CONT;
+	int act_flags = 0;
+
+	htx = htx_from_buf(&s->res.buf);
+
+	/* If "the current_rule_list" match the executed rule list, we are in
+	 * resume condition. If a resume is needed it is always in the action
+	 * and never in the ACL or converters. In this case, we initialise the
+	 * current rule, and go to the action execution point.
+	 */
+	if (s->current_rule) {
+		rule = s->current_rule;
+		s->current_rule = NULL;
+		if (s->current_rule_list == rules)
+			goto resume_execution;
+	}
+	s->current_rule_list = rules;
+
+	list_for_each_entry(rule, rules, list) {
+		/* check optional condition */
+		if (rule->cond) {
+			int ret;
+
+			ret = acl_exec_cond(rule->cond, px, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL);
+			ret = acl_pass(ret);
+
+			if (rule->cond->pol == ACL_COND_UNLESS)
+				ret = !ret;
+
+			if (!ret) /* condition not matched */
+				continue;
+		}
+
+		act_flags |= ACT_FLAG_FIRST;
+resume_execution:
+		switch (rule->action) {
+			case ACT_ACTION_ALLOW:
+				rule_ret = HTTP_RULE_RES_STOP; /* "allow" rules are OK */
+				goto end;
+
+			case ACT_ACTION_DENY:
+				txn->flags |= TX_SVDENY;
+				rule_ret = HTTP_RULE_RES_STOP;
+				goto end;
+
+			case ACT_HTTP_SET_NICE:
+				s->task->nice = rule->arg.nice;
+				break;
+
+			case ACT_HTTP_SET_TOS:
+				if ((cli_conn = objt_conn(sess->origin)) && conn_ctrl_ready(cli_conn))
+					inet_set_tos(cli_conn->handle.fd, &cli_conn->addr.from, rule->arg.tos);
+				break;
+
+			case ACT_HTTP_SET_MARK:
+#ifdef SO_MARK
+				if ((cli_conn = objt_conn(sess->origin)) && conn_ctrl_ready(cli_conn))
+					setsockopt(cli_conn->handle.fd, SOL_SOCKET, SO_MARK, &rule->arg.mark, sizeof(rule->arg.mark));
+#endif
+				break;
+
+			case ACT_HTTP_SET_LOGL:
+				s->logs.level = rule->arg.loglevel;
+				break;
+
+			case ACT_HTTP_REPLACE_HDR:
+			case ACT_HTTP_REPLACE_VAL:
+				if (htx_transform_header(s, &s->res, htx,
+							 ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len),
+							 &rule->arg.hdr_add.fmt,
+							 &rule->arg.hdr_add.re, rule->action)) {
+					rule_ret = HTTP_RULE_RES_BADREQ;
+					goto end;
+				}
+				break;
+
+			case ACT_HTTP_DEL_HDR:
+				/* remove all occurrences of the header */
+				ctx.blk = NULL;
+				while (http_find_header(htx, ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len), &ctx, 1))
+					http_remove_header(htx, &ctx);
+				break;
+
+			case ACT_HTTP_SET_HDR:
+			case ACT_HTTP_ADD_HDR: {
+				struct buffer *replace;
+				struct ist n, v;
+
+				replace = alloc_trash_chunk();
+				if (!replace) {
+					rule_ret = HTTP_RULE_RES_BADREQ;
+					goto end;
+				}
+
+				replace->data = build_logline(s, replace->area, replace->size, &rule->arg.hdr_add.fmt);
+				n = ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len);
+				v = ist2(replace->area, replace->data);
+
+				if (rule->action == ACT_HTTP_SET_HDR) {
+					/* remove all occurrences of the header */
+					ctx.blk = NULL;
+					while (http_find_header(htx, ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len), &ctx, 1))
+						http_remove_header(htx, &ctx);
+				}
+
+				if (!http_add_header(htx, n, v)) {
+					static unsigned char rate_limit = 0;
+
+					if ((rate_limit++ & 255) == 0) {
+						send_log(px, LOG_WARNING, "Proxy %s failed to add or set the response header '%.*s' for request #%u. You might need to increase tune.maxrewrite.", px->id, (int)n.len, n.ptr, s->uniq_id);
+					}
+
+					HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
+					if (sess->fe != s->be)
+						HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
+					if (sess->listener->counters)
+						HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
+					if (objt_server(s->target))
+						HA_ATOMIC_ADD(&objt_server(s->target)->counters.failed_rewrites, 1);
+				}
+				free_trash_chunk(replace);
+				break;
+			}
+
+			case ACT_HTTP_DEL_ACL:
+			case ACT_HTTP_DEL_MAP: {
+				struct pat_ref *ref;
+				struct buffer *key;
+
+				/* collect reference */
+				ref = pat_ref_lookup(rule->arg.map.ref);
+				if (!ref)
+					continue;
+
+			/* allocate key */
+				key = alloc_trash_chunk();
+				if (!key) {
+					rule_ret = HTTP_RULE_RES_BADREQ;
+					goto end;
+				}
+
+				/* collect key */
+				key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
+				key->area[key->data] = '\0';
+
+				/* perform update */
+				/* returned code: 1=ok, 0=ko */
+				HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
+				pat_ref_delete(ref, key->area);
+				HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
+
+				free_trash_chunk(key);
+				break;
+			}
+
+			case ACT_HTTP_ADD_ACL: {
+				struct pat_ref *ref;
+				struct buffer *key;
+
+				/* collect reference */
+				ref = pat_ref_lookup(rule->arg.map.ref);
+				if (!ref)
+					continue;
+
+				/* allocate key */
+				key = alloc_trash_chunk();
+				if (!key) {
+					rule_ret = HTTP_RULE_RES_BADREQ;
+					goto end;
+				}
+
+				/* collect key */
+				key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
+				key->area[key->data] = '\0';
+
+				/* perform update */
+				/* check if the entry already exists */
+				if (pat_ref_find_elt(ref, key->area) == NULL)
+					pat_ref_add(ref, key->area, NULL, NULL);
+
+				free_trash_chunk(key);
+				break;
+			}
+
+			case ACT_HTTP_SET_MAP: {
+				struct pat_ref *ref;
+				struct buffer *key, *value;
+
+				/* collect reference */
+				ref = pat_ref_lookup(rule->arg.map.ref);
+				if (!ref)
+					continue;
+
+				/* allocate key */
+				key = alloc_trash_chunk();
+				if (!key) {
+					rule_ret = HTTP_RULE_RES_BADREQ;
+					goto end;
+				}
+
+				/* allocate value */
+				value = alloc_trash_chunk();
+				if (!value) {
+					free_trash_chunk(key);
+					rule_ret = HTTP_RULE_RES_BADREQ;
+					goto end;
+				}
+
+				/* collect key */
+				key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
+				key->area[key->data] = '\0';
+
+				/* collect value */
+				value->data = build_logline(s, value->area, value->size, &rule->arg.map.value);
+				value->area[value->data] = '\0';
+
+				/* perform update */
+				HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
+				if (pat_ref_find_elt(ref, key->area) != NULL)
+					/* update entry if it exists */
+					pat_ref_set(ref, key->area, value->area, NULL);
+				else
+					/* insert a new entry */
+					pat_ref_add(ref, key->area, value->area, NULL);
+				HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
+				free_trash_chunk(key);
+				free_trash_chunk(value);
+				break;
+			}
+
+			case ACT_HTTP_REDIR:
+				rule_ret = HTTP_RULE_RES_DONE;
+				if (!http_apply_redirect_rule(rule->arg.redir, s, txn))
+					rule_ret = HTTP_RULE_RES_BADREQ;
+				goto end;
+
+			case ACT_ACTION_TRK_SC0 ... ACT_ACTION_TRK_SCMAX:
+				/* Note: only the first valid tracking parameter of each
+				 * applies.
+				 */
+				if (stkctr_entry(&s->stkctr[trk_idx(rule->action)]) == NULL) {
+					struct stktable *t;
+					struct stksess *ts;
+					struct stktable_key *key;
+					void *ptr;
+
+					t = rule->arg.trk_ctr.table.t;
+					key = stktable_fetch_key(t, s->be, sess, s, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
+								 rule->arg.trk_ctr.expr, NULL);
+
+					if (key && (ts = stktable_get_entry(t, key))) {
+						stream_track_stkctr(&s->stkctr[trk_idx(rule->action)], t, ts);
+
+						HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
+
+						/* let's count a new HTTP request as it's the first time we do it */
+						ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
+						if (ptr)
+							stktable_data_cast(ptr, http_req_cnt)++;
+
+						ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
+						if (ptr)
+							update_freq_ctr_period(&stktable_data_cast(ptr, http_req_rate),
+									       t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
+
+						/* When the client triggers a 4xx from the server, it's most often due
+						 * to a missing object or permission. These events should be tracked
+						 * because if they happen often, it may indicate a brute force or a
+						 * vulnerability scan. Normally this is done when receiving the response
+						 * but here we're tracking after this ought to have been done so we have
+						 * to do it on purpose.
+						 */
+						if ((unsigned)(txn->status - 400) < 100) {
+							ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_CNT);
+							if (ptr)
+								stktable_data_cast(ptr, http_err_cnt)++;
+
+							ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_RATE);
+							if (ptr)
+								update_freq_ctr_period(&stktable_data_cast(ptr, http_err_rate),
+										       t->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1);
+						}
+
+						HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
+
+						/* If data was modified, we need to touch to re-schedule sync */
+						stktable_touch_local(t, ts, 0);
+
+						stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_CONTENT);
+						if (sess->fe != s->be)
+							stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_BACKEND);
+					}
+				}
+				break;
+
+			case ACT_CUSTOM:
+				if ((s->req.flags & CF_READ_ERROR) ||
+				    ((s->req.flags & (CF_SHUTR|CF_READ_NULL)) &&
+				     !(s->si[0].flags & SI_FL_CLEAN_ABRT) &&
+				     (px->options & PR_O_ABRT_CLOSE)))
+					act_flags |= ACT_FLAG_FINAL;
+
+				switch (rule->action_ptr(rule, px, s->sess, s, act_flags)) {
+					case ACT_RET_ERR:
+					case ACT_RET_CONT:
+						break;
+					case ACT_RET_STOP:
+						rule_ret = HTTP_RULE_RES_STOP;
+						goto end;
+					case ACT_RET_YIELD:
+						s->current_rule = rule;
+						rule_ret = HTTP_RULE_RES_YIELD;
+						goto end;
+				}
+				break;
+
+				/* other flags exists, but normaly, they never be matched. */
+			default:
+				break;
+		}
+	}
+
+  end:
+	/* we reached the end of the rules, nothing to report */
+	return rule_ret;
+}
+
 /* This function terminates the request because it was completly analyzed or
  * because an error was triggered during the body forwarding.
  */