]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: proto_htx: Add functions htx_req_get_intercept_rule and htx_res_get_intercept_rule
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 24 Oct 2018 09:39:23 +0000 (11:39 +0200)
committerWilly Tarreau <w@1wt.eu>
Sun, 18 Nov 2018 21:08:57 +0000 (22:08 +0100)
It is more or less the same than legacy versions but adapted to be called from
HTX analyzers.

src/proto_htx.c

index 83a2c33a4c3ceb42493952e7614603fba6d6cd4c..2b2fcf6d053045a8ea96b5df8ef24f8ff246a88e 100644 (file)
@@ -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 size_t htx_fmt_res_line(const union h1_sl sl, char *str, size_t len);
 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 @@ void htx_res_set_status(unsigned int status, const char *reason, struct stream *
                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.
  */