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.
*/