]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] acl: implement matching on header values
authorWilly Tarreau <w@1wt.eu>
Sun, 10 Jun 2007 17:45:56 +0000 (19:45 +0200)
committerWilly Tarreau <w@1wt.eu>
Sun, 10 Jun 2007 17:45:56 +0000 (19:45 +0200)
hdr(x), hdr_reg(x), hdr_beg(x), hdr_end(x), hdr_sub(x), hdr_dir(x),
hdr_dom(x), hdr_cnt(x) and hdr_val(x) have been implemented. They
apply to any of the possibly multiple values of header <x>.

Right now, hdr_val() is limited to integer matching, but it should
reasonably be upgraded to match long long ints.

include/types/acl.h
include/types/proto_http.h
src/proto_http.c

index fe0ce5f64b8cf5304ddf1a178709a2126d1fba06..db76a272419a31077030532cc1c96ee23f2bca3a 100644 (file)
@@ -102,13 +102,16 @@ struct acl_pattern {
  * retrieving a value, and an acl_match_* function responsible for testing it.
  */
 struct acl_test {
-       int i;          /* integer value */
-       char *ptr;      /* pointer to beginning of value */
-       int len;        /* length of value at ptr, otherwise ignored */
-       int flags;      /* ACL_TEST_F_* set to 0 on first call */
-       union {         /* fetch_* functions context for any purpose */
-               void *p;
-               int i;
+       int i;                  /* integer value */
+       char *ptr;              /* pointer to beginning of value */
+       int len;                /* length of value at ptr, otherwise ignored */
+       int flags;              /* ACL_TEST_F_* set to 0 on first call */
+       union {                 /* fetch_* functions context for any purpose */
+               void *p;        /* any pointer */
+               int i;          /* any integer */
+               long long ll;   /* any long long or smaller */
+               double d;       /* any float or double */
+               void *a[8];     /* any array of up to 8 pointers */
        } ctx;
 };
 
index 6e6836275ecf50359b9a0a78ab7903b613cd2dd0..c5e051e2285883661c412514217eb24db46b06f4 100644 (file)
@@ -243,6 +243,15 @@ struct http_txn {
        unsigned int flags;             /* transaction flags */
 };
 
+/* This structure is used by http_find_header() to return values of headers.
+ * The header starts at <line>, the value at <line>+<val> for <vlen> bytes.
+ */
+struct hdr_ctx {
+       const char *line;
+       int  idx;
+       int  val;  /* relative to line */
+       int  vlen; /* relative to line+val */
+};
 
 #endif /* _TYPES_PROTO_HTTP_H */
 
index ea199d9b9a47d4ae5d08ba24e6f4c41df15a9f71..13ee74655be9e40d17340867d48f7474f43eb378 100644 (file)
@@ -433,6 +433,98 @@ int http_header_match2(const char *hdr, const char *end,
        return val - hdr;
 }
 
+/* Find the end of the header value contained between <s> and <e>.
+ * See RFC2616, par 2.2 for more information. Note that it requires
+ * a valid header to return a valid result.
+ */
+const char *find_hdr_value_end(const char *s, const char *e)
+{
+       int quoted, qdpair;
+
+       quoted = qdpair = 0;
+       for (; s < e; s++) {
+               if (qdpair)                    qdpair = 0;
+               else if (quoted && *s == '\\') qdpair = 1;
+               else if (quoted && *s == '"')  quoted = 0;
+               else if (*s == '"')            quoted = 1;
+               else if (*s == ',')            return s;
+       }
+       return s;
+}
+
+/* Find the first or next occurrence of header <name> in message buffer <sol>
+ * using headers index <idx>, and return it in the <ctx> structure. This
+ * structure holds everything necessary to use the header and find next
+ * occurrence. If its <idx> member is 0, the header is searched from the
+ * beginning. Otherwise, the next occurrence is returned. The function returns
+ * 1 when it finds a value, and 0 when there is no more.
+ */
+int http_find_header2(const char *name, int len,
+                     const char *sol, struct hdr_idx *idx,
+                     struct hdr_ctx *ctx)
+{
+       __label__ return_hdr, next_hdr;
+       const char *eol, *sov;
+       int cur_idx;
+
+       if (ctx->idx) {
+               /* We have previously returned a value, let's search
+                * another one on the same line.
+                */
+               cur_idx = ctx->idx;
+               sol = ctx->line;
+               sov = sol + ctx->val + ctx->vlen;
+               eol = sol + idx->v[cur_idx].len;
+
+               if (sov >= eol)
+                       /* no more values in this header */
+                       goto next_hdr;
+
+               /* values remaining for this header, skip the comma */
+               sov++;
+               while (sov < eol && http_is_lws[(unsigned char)*sov])
+                       sov++;
+
+               goto return_hdr;
+       }
+
+       /* first request for this header */
+       sol += hdr_idx_first_pos(idx);
+       cur_idx = hdr_idx_first_idx(idx);
+
+       while (cur_idx) {
+               eol = sol + idx->v[cur_idx].len;
+
+               if ((len < eol - sol) &&
+                   (sol[len] == ':') &&
+                   (strncasecmp(sol, name, len) == 0)) {
+
+                       sov = sol + len + 1;
+                       while (sov < eol && http_is_lws[(unsigned char)*sov])
+                               sov++;
+               return_hdr:
+                       ctx->line = sol;
+                       ctx->idx  = cur_idx;
+                       ctx->val  = sov - sol;
+
+                       eol = find_hdr_value_end(sov, eol);
+                       ctx->vlen = eol - sov;
+                       return 1;
+               }
+       next_hdr:
+               sol = eol + idx->v[cur_idx].cr + 1;
+               cur_idx = idx->v[cur_idx].next;
+       }
+       return 0;
+}
+
+int http_find_header(const char *name,
+                    const char *sol, struct hdr_idx *idx,
+                    struct hdr_ctx *ctx)
+{
+       return http_find_header2(name, strlen(name), sol, idx, ctx);
+}
+
 /*
  * returns a message to the client ; the connection is shut down for read,
  * and the request is cleared so that no server connection can be initiated.
@@ -5285,6 +5377,86 @@ acl_fetch_url(struct proxy *px, struct session *l4, void *l7, int dir,
        return 1;
 }
 
+/* 5. Check on HTTP header. A pointer to the beginning of the value is returned. */
+static int
+acl_fetch_hdr(struct proxy *px, struct session *l4, void *l7, int dir,
+              struct acl_expr *expr, struct acl_test *test)
+{
+       struct http_txn *txn = l7;
+       struct hdr_idx *idx = &txn->hdr_idx;
+       struct hdr_ctx *ctx = (struct hdr_ctx *)test->ctx.a;
+       char *sol;
+
+       if (!(test->flags & ACL_TEST_F_FETCH_MORE))
+               /* search for header from the beginning */
+               ctx->idx = 0;
+
+       sol = (dir == ACL_DIR_REQ) ? txn->req.sol : txn->rsp.sol;
+       if (http_find_header2(expr->arg.str, expr->arg_len, sol, idx, ctx)) {
+               test->flags |= ACL_TEST_F_FETCH_MORE;
+               test->flags |= ACL_TEST_F_VOL_HDR;
+               test->len = ctx->vlen;
+               test->ptr = (char *)ctx->line + ctx->val;
+               return 1;
+       }
+
+       test->flags &= ~ACL_TEST_F_FETCH_MORE;
+       test->flags |= ACL_TEST_F_VOL_HDR;
+       return 0;
+}
+
+/* 6. Check on HTTP header count. The number of occurrences is returned. */
+static int
+acl_fetch_hdr_cnt(struct proxy *px, struct session *l4, void *l7, int dir,
+                  struct acl_expr *expr, struct acl_test *test)
+{
+       struct http_txn *txn = l7;
+       struct hdr_idx *idx = &txn->hdr_idx;
+       struct hdr_ctx ctx;
+       char *sol;
+       int cnt;
+
+       sol = (dir == ACL_DIR_REQ) ? txn->req.sol : txn->rsp.sol;
+
+       ctx.idx = 0;
+       cnt = 0;
+       while (http_find_header2(expr->arg.str, expr->arg_len, sol, idx, &ctx))
+               cnt++;
+
+       test->i = cnt;
+       test->flags = ACL_TEST_F_VOL_HDR;
+       return 1;
+}
+
+/* 7. Check on HTTP header's integer value. The integer value is returned.
+ * FIXME: the type is 'int', it may not be appropriate for everything.
+ */
+static int
+acl_fetch_hdr_val(struct proxy *px, struct session *l4, void *l7, int dir,
+                  struct acl_expr *expr, struct acl_test *test)
+{
+       struct http_txn *txn = l7;
+       struct hdr_idx *idx = &txn->hdr_idx;
+       struct hdr_ctx *ctx = (struct hdr_ctx *)test->ctx.a;
+       char *sol;
+
+       if (!(test->flags & ACL_TEST_F_FETCH_MORE))
+               /* search for header from the beginning */
+               ctx->idx = 0;
+
+       sol = (dir == ACL_DIR_REQ) ? txn->req.sol : txn->rsp.sol;
+       if (http_find_header2(expr->arg.str, expr->arg_len, sol, idx, ctx)) {
+               test->flags |= ACL_TEST_F_FETCH_MORE;
+               test->flags |= ACL_TEST_F_VOL_HDR;
+               test->i = strl2ic((char *)ctx->line + ctx->val, ctx->vlen);
+               return 1;
+       }
+
+       test->flags &= ~ACL_TEST_F_FETCH_MORE;
+       test->flags |= ACL_TEST_F_VOL_HDR;
+       return 0;
+}
+
 
 
 /************************************************************************/
@@ -5306,6 +5478,15 @@ static struct acl_kw_list acl_kws = {{ },{
        { "url_dom",    acl_parse_str,   acl_fetch_url,    acl_match_dom  },
        { "url_reg",    acl_parse_reg,   acl_fetch_url,    acl_match_reg  },
 
+       { "hdr",        acl_parse_str,   acl_fetch_hdr,     acl_match_str },
+       { "hdr_reg",    acl_parse_reg,   acl_fetch_hdr,     acl_match_reg },
+       { "hdr_beg",    acl_parse_str,   acl_fetch_hdr,     acl_match_beg },
+       { "hdr_end",    acl_parse_str,   acl_fetch_hdr,     acl_match_end },
+       { "hdr_sub",    acl_parse_str,   acl_fetch_hdr,     acl_match_sub },
+       { "hdr_dir",    acl_parse_str,   acl_fetch_hdr,     acl_match_dir },
+       { "hdr_dom",    acl_parse_str,   acl_fetch_hdr,     acl_match_dom },
+       { "hdr_cnt",    acl_parse_int,   acl_fetch_hdr_cnt, acl_match_int },
+       { "hdr_val",    acl_parse_int,   acl_fetch_hdr_val, acl_match_int },
        { NULL, NULL, NULL, NULL },
 
 #if 0
@@ -5325,15 +5506,6 @@ static struct acl_kw_list acl_kws = {{ },{
        { "path_dir",   acl_parse_str,   acl_fetch_path,   acl_match_dir   },
        { "path_dom",   acl_parse_str,   acl_fetch_path,   acl_match_dom   },
 
-       { "hdr",        acl_parse_str,   acl_fetch_hdr,    acl_match_str   },
-       { "hdr_reg",    acl_parse_reg,   acl_fetch_hdr,    acl_match_reg   },
-       { "hdr_beg",    acl_parse_str,   acl_fetch_hdr,    acl_match_beg   },
-       { "hdr_end",    acl_parse_str,   acl_fetch_hdr,    acl_match_end   },
-       { "hdr_sub",    acl_parse_str,   acl_fetch_hdr,    acl_match_sub   },
-       { "hdr_dir",    acl_parse_str,   acl_fetch_hdr,    acl_match_dir   },
-       { "hdr_dom",    acl_parse_str,   acl_fetch_hdr,    acl_match_dom   },
-       { "hdr_pst",    acl_parse_none,  acl_fetch_hdr,    acl_match_pst   },
-
        { "cook",       acl_parse_str,   acl_fetch_cook,   acl_match_str   },
        { "cook_reg",   acl_parse_reg,   acl_fetch_cook,   acl_match_reg   },
        { "cook_beg",   acl_parse_str,   acl_fetch_cook,   acl_match_beg   },