diff --git a/Makefile b/Makefile index 074e016981..3140a4ac33 100644 --- a/Makefile +++ b/Makefile @@ -894,6 +894,7 @@ OBJS = src/proto_http.o src/cfgparse.o src/server.o src/stream.o \ src/time.o src/proto_udp.o src/arg.o src/signal.o \ src/protocol.o src/lru.o src/hdr_idx.o src/hpack-huff.o \ src/mailers.o src/h2.o src/base64.o src/hash.o src/http.o \ + src/http_acl.o src/http_fetch.o src/http_conv.o src/http_act.o \ src/proto_sockpair.o EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o $(EBTREE_DIR)/eb32sctree.o \ diff --git a/include/common/http.h b/include/common/http.h index 3377db475b..9da2af8577 100644 --- a/include/common/http.h +++ b/include/common/http.h @@ -161,6 +161,32 @@ static inline int http_is_param_delimiter(char c, char delim) return c == '&' || c == ';' || c == delim; } +/* Match language range with language tag. RFC2616 14.4: + * + * A language-range matches a language-tag if it exactly equals + * the tag, or if it exactly equals a prefix of the tag such + * that the first tag character following the prefix is "-". + * + * Return 1 if the strings match, else return 0. + */ +static inline int http_language_range_match(const char *range, int range_len, + const char *tag, int tag_len) +{ + const char *end = range + range_len; + const char *tend = tag + tag_len; + + while (range < end) { + if (*range == '-' && tag == tend) + return 1; + if (*range != *tag || tag == tend) + return 0; + range++; + tag++; + } + /* Return true only if the last char of the tag is matched. */ + return tag == tend; +} + #endif /* _COMMON_HTTP_H */ diff --git a/include/proto/http_fetch.h b/include/proto/http_fetch.h new file mode 100644 index 0000000000..efd188a404 --- /dev/null +++ b/include/proto/http_fetch.h @@ -0,0 +1,52 @@ +/* + * include/proto/http_fetch.h + * This file contains the minimally required http sample fetch declarations. + * + * Copyright (C) 2000-2018 Willy Tarreau - w@1wt.eu + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PROTO_HTTP_FETCH_H +#define _PROTO_HTTP_FETCH_H + +#include +#include +#include +#include + +/* Note: these functions *do* modify the sample. Even in case of success, at + * least the type and uint value are modified. + */ +#define CHECK_HTTP_MESSAGE_FIRST() \ + do { int r = smp_prefetch_http(smp->px, smp->strm, smp->opt, args, smp, 1); if (r <= 0) return r; } while (0) + +#define CHECK_HTTP_MESSAGE_FIRST_PERM() \ + do { int r = smp_prefetch_http(smp->px, smp->strm, smp->opt, args, smp, 0); if (r <= 0) return r; } while (0) + +int smp_prefetch_http(struct proxy *px, struct stream *s, unsigned int opt, + const struct arg *args, struct sample *smp, int req_vol); + +int val_hdr(struct arg *arg, char **err_msg); + + +#endif /* _PROTO_HTTP_FETCH_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index 1b8da06618..da6d0c74a5 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -44,6 +44,8 @@ * ver_token = 'H', 'P', 'T', '/', '.', and digits. */ +extern struct pool_head *pool_head_uniqueid; + int process_cli(struct stream *s); int process_srv_data(struct stream *s); int process_srv_conn(struct stream *s); @@ -57,6 +59,7 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit); int http_process_res_common(struct stream *s, struct channel *rep, int an_bit, struct proxy *px); int http_request_forward_body(struct stream *s, struct channel *req, int an_bit); int http_response_forward_body(struct stream *s, struct channel *res, int an_bit); +int http_upgrade_v09_to_v10(struct http_txn *txn); void http_msg_analyzer(struct http_msg *msg, struct hdr_idx *idx); void http_txn_reset_req(struct http_txn *txn); void http_txn_reset_res(struct http_txn *txn); @@ -100,6 +103,9 @@ void http_capture_bad_message(struct proxy *proxy, struct stream *s, unsigned int http_get_hdr(const struct http_msg *msg, const char *hname, int hlen, struct hdr_idx *idx, int occ, struct hdr_ctx *ctx, char **vptr, size_t *vlen); +unsigned int http_get_fhdr(const struct http_msg *msg, const char *hname, int hlen, + struct hdr_idx *idx, int occ, + struct hdr_ctx *ctx, char **vptr, size_t *vlen); char *http_txn_get_path(const struct http_txn *txn); struct http_txn *http_alloc_txn(struct stream *s); @@ -117,31 +123,9 @@ void http_reply_and_close(struct stream *s, short status, struct buffer *msg); struct buffer *http_error_message(struct stream *s); struct redirect_rule *http_parse_redirect_rule(const char *file, int linenum, struct proxy *curproxy, const char **args, char **errmsg, int use_fmt, int dir); -int smp_fetch_cookie(const struct arg *args, struct sample *smp, const char *kw, void *private); -int smp_fetch_base32(const struct arg *args, struct sample *smp, const char *kw, void *private); struct action_kw *action_http_req_custom(const char *kw); struct action_kw *action_http_res_custom(const char *kw); -int val_hdr(struct arg *arg, char **err_msg); - -int smp_prefetch_http(struct proxy *px, struct stream *s, unsigned int opt, - const struct arg *args, struct sample *smp, int req_vol); - -enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px, - struct session *sess, struct stream *s, int flags); -enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px, - struct session *sess, struct stream *s, int flags); - -int parse_qvalue(const char *qvalue, const char **end); - -/* Note: these functions *do* modify the sample. Even in case of success, at - * least the type and uint value are modified. - */ -#define CHECK_HTTP_MESSAGE_FIRST() \ - do { int r = smp_prefetch_http(smp->px, smp->strm, smp->opt, args, smp, 1); if (r <= 0) return r; } while (0) - -#define CHECK_HTTP_MESSAGE_FIRST_PERM() \ - do { int r = smp_prefetch_http(smp->px, smp->strm, smp->opt, args, smp, 0); if (r <= 0) return r; } while (0) static inline void http_req_keywords_register(struct action_kw_list *kw_list) { diff --git a/src/51d.c b/src/51d.c index 687528e683..e092c5ba83 100644 --- a/src/51d.c +++ b/src/51d.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include diff --git a/src/da.c b/src/da.c index 23da04aed6..c653848120 100644 --- a/src/da.c +++ b/src/da.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/src/hlua.c b/src/hlua.c index 759e7b21e3..c2be5f9f80 100644 --- a/src/hlua.c +++ b/src/hlua.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include diff --git a/src/http_acl.c b/src/http_acl.c new file mode 100644 index 0000000000..7864df89fe --- /dev/null +++ b/src/http_acl.c @@ -0,0 +1,194 @@ +/* + * HTTP ACLs declaration + * + * Copyright 2000-2018 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + + +/* We use the pre-parsed method if it is known, and store its number as an + * integer. If it is unknown, we use the pointer and the length. + */ +static int pat_parse_meth(const char *text, struct pattern *pattern, int mflags, char **err) +{ + int len, meth; + + len = strlen(text); + meth = find_http_meth(text, len); + + pattern->val.i = meth; + if (meth == HTTP_METH_OTHER) { + pattern->ptr.str = (char *)text; + pattern->len = len; + } + else { + pattern->ptr.str = NULL; + pattern->len = 0; + } + return 1; +} + +/* See above how the method is stored in the global pattern */ +static struct pattern *pat_match_meth(struct sample *smp, struct pattern_expr *expr, int fill) +{ + int icase; + struct pattern_list *lst; + struct pattern *pattern; + + list_for_each_entry(lst, &expr->patterns, list) { + pattern = &lst->pat; + + /* well-known method */ + if (pattern->val.i != HTTP_METH_OTHER) { + if (smp->data.u.meth.meth == pattern->val.i) + return pattern; + else + continue; + } + + /* Other method, we must compare the strings */ + if (pattern->len != smp->data.u.meth.str.data) + continue; + + icase = expr->mflags & PAT_MF_IGNORE_CASE; + if ((icase && strncasecmp(pattern->ptr.str, smp->data.u.meth.str.area, smp->data.u.meth.str.data) == 0) || + (!icase && strncmp(pattern->ptr.str, smp->data.u.meth.str.area, smp->data.u.meth.str.data) == 0)) + return pattern; + } + return NULL; +} + +/************************************************************************/ +/* All supported ACL keywords must be declared here. */ +/************************************************************************/ + +/* Note: must not be declared as its list will be overwritten. + * Please take care of keeping this list alphabetically sorted. + */ +static struct acl_kw_list acl_kws = {ILH, { + { "base", "base", PAT_MATCH_STR }, + { "base_beg", "base", PAT_MATCH_BEG }, + { "base_dir", "base", PAT_MATCH_DIR }, + { "base_dom", "base", PAT_MATCH_DOM }, + { "base_end", "base", PAT_MATCH_END }, + { "base_len", "base", PAT_MATCH_LEN }, + { "base_reg", "base", PAT_MATCH_REG }, + { "base_sub", "base", PAT_MATCH_SUB }, + + { "cook", "req.cook", PAT_MATCH_STR }, + { "cook_beg", "req.cook", PAT_MATCH_BEG }, + { "cook_dir", "req.cook", PAT_MATCH_DIR }, + { "cook_dom", "req.cook", PAT_MATCH_DOM }, + { "cook_end", "req.cook", PAT_MATCH_END }, + { "cook_len", "req.cook", PAT_MATCH_LEN }, + { "cook_reg", "req.cook", PAT_MATCH_REG }, + { "cook_sub", "req.cook", PAT_MATCH_SUB }, + + { "hdr", "req.hdr", PAT_MATCH_STR }, + { "hdr_beg", "req.hdr", PAT_MATCH_BEG }, + { "hdr_dir", "req.hdr", PAT_MATCH_DIR }, + { "hdr_dom", "req.hdr", PAT_MATCH_DOM }, + { "hdr_end", "req.hdr", PAT_MATCH_END }, + { "hdr_len", "req.hdr", PAT_MATCH_LEN }, + { "hdr_reg", "req.hdr", PAT_MATCH_REG }, + { "hdr_sub", "req.hdr", PAT_MATCH_SUB }, + + /* these two declarations uses strings with list storage (in place + * of tree storage). The basic match is PAT_MATCH_STR, but the indexation + * and delete functions are relative to the list management. The parse + * and match method are related to the corresponding fetch methods. This + * is very particular ACL declaration mode. + */ + { "http_auth_group", NULL, PAT_MATCH_STR, NULL, pat_idx_list_str, pat_del_list_ptr, NULL, pat_match_auth }, + { "method", NULL, PAT_MATCH_STR, pat_parse_meth, pat_idx_list_str, pat_del_list_ptr, NULL, pat_match_meth }, + + { "path", "path", PAT_MATCH_STR }, + { "path_beg", "path", PAT_MATCH_BEG }, + { "path_dir", "path", PAT_MATCH_DIR }, + { "path_dom", "path", PAT_MATCH_DOM }, + { "path_end", "path", PAT_MATCH_END }, + { "path_len", "path", PAT_MATCH_LEN }, + { "path_reg", "path", PAT_MATCH_REG }, + { "path_sub", "path", PAT_MATCH_SUB }, + + { "req_ver", "req.ver", PAT_MATCH_STR }, + { "resp_ver", "res.ver", PAT_MATCH_STR }, + + { "scook", "res.cook", PAT_MATCH_STR }, + { "scook_beg", "res.cook", PAT_MATCH_BEG }, + { "scook_dir", "res.cook", PAT_MATCH_DIR }, + { "scook_dom", "res.cook", PAT_MATCH_DOM }, + { "scook_end", "res.cook", PAT_MATCH_END }, + { "scook_len", "res.cook", PAT_MATCH_LEN }, + { "scook_reg", "res.cook", PAT_MATCH_REG }, + { "scook_sub", "res.cook", PAT_MATCH_SUB }, + + { "shdr", "res.hdr", PAT_MATCH_STR }, + { "shdr_beg", "res.hdr", PAT_MATCH_BEG }, + { "shdr_dir", "res.hdr", PAT_MATCH_DIR }, + { "shdr_dom", "res.hdr", PAT_MATCH_DOM }, + { "shdr_end", "res.hdr", PAT_MATCH_END }, + { "shdr_len", "res.hdr", PAT_MATCH_LEN }, + { "shdr_reg", "res.hdr", PAT_MATCH_REG }, + { "shdr_sub", "res.hdr", PAT_MATCH_SUB }, + + { "url", "url", PAT_MATCH_STR }, + { "url_beg", "url", PAT_MATCH_BEG }, + { "url_dir", "url", PAT_MATCH_DIR }, + { "url_dom", "url", PAT_MATCH_DOM }, + { "url_end", "url", PAT_MATCH_END }, + { "url_len", "url", PAT_MATCH_LEN }, + { "url_reg", "url", PAT_MATCH_REG }, + { "url_sub", "url", PAT_MATCH_SUB }, + + { "urlp", "urlp", PAT_MATCH_STR }, + { "urlp_beg", "urlp", PAT_MATCH_BEG }, + { "urlp_dir", "urlp", PAT_MATCH_DIR }, + { "urlp_dom", "urlp", PAT_MATCH_DOM }, + { "urlp_end", "urlp", PAT_MATCH_END }, + { "urlp_len", "urlp", PAT_MATCH_LEN }, + { "urlp_reg", "urlp", PAT_MATCH_REG }, + { "urlp_sub", "urlp", PAT_MATCH_SUB }, + + { /* END */ }, +}}; + +__attribute__((constructor)) +static void __http_acl_init(void) +{ + acl_register_keywords(&acl_kws); +} + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/http_act.c b/src/http_act.c new file mode 100644 index 0000000000..6ed4852f18 --- /dev/null +++ b/src/http_act.c @@ -0,0 +1,608 @@ +/* + * HTTP actions + * + * Copyright 2000-2018 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + + +/* This function executes one of the set-{method,path,query,uri} actions. It + * builds a string in the trash from the specified format string. It finds + * the action to be performed in , previously filled by function + * parse_set_req_line(). The replacement action is excuted by the function + * http_action_set_req_line(). It always returns ACT_RET_CONT. If an error + * occurs the action is canceled, but the rule processing continue. + */ +static enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + struct buffer *replace; + enum act_return ret = ACT_RET_ERR; + + replace = alloc_trash_chunk(); + if (!replace) + goto leave; + + /* If we have to create a query string, prepare a '?'. */ + if (rule->arg.http.action == 2) + replace->area[replace->data++] = '?'; + replace->data += build_logline(s, replace->area + replace->data, + replace->size - replace->data, + &rule->arg.http.logfmt); + + http_replace_req_line(rule->arg.http.action, replace->area, + replace->data, px, s); + + ret = ACT_RET_CONT; + +leave: + free_trash_chunk(replace); + return ret; +} + +/* parse an http-request action among : + * set-method + * set-path + * set-query + * set-uri + * + * All of them accept a single argument of type string representing a log-format. + * The resulting rule makes use of arg->act.p[0..1] to store the log-format list + * head, and p[2] to store the action as an int (0=method, 1=path, 2=query, 3=uri). + * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error. + */ +static enum act_parse_ret parse_set_req_line(const char **args, int *orig_arg, struct proxy *px, + struct act_rule *rule, char **err) +{ + int cur_arg = *orig_arg; + + rule->action = ACT_CUSTOM; + + switch (args[0][4]) { + case 'm' : + rule->arg.http.action = 0; + rule->action_ptr = http_action_set_req_line; + break; + case 'p' : + rule->arg.http.action = 1; + rule->action_ptr = http_action_set_req_line; + break; + case 'q' : + rule->arg.http.action = 2; + rule->action_ptr = http_action_set_req_line; + break; + case 'u' : + rule->arg.http.action = 3; + rule->action_ptr = http_action_set_req_line; + break; + default: + memprintf(err, "internal error: unhandled action '%s'", args[0]); + return ACT_RET_PRS_ERR; + } + + if (!*args[cur_arg] || + (*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) { + memprintf(err, "expects exactly 1 argument "); + return ACT_RET_PRS_ERR; + } + + LIST_INIT(&rule->arg.http.logfmt); + px->conf.args.ctx = ARGC_HRQ; + if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.logfmt, LOG_OPT_HTTP, + (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) { + return ACT_RET_PRS_ERR; + } + + (*orig_arg)++; + return ACT_RET_PRS_OK; +} + +/* This function is just a compliant action wrapper for "set-status". */ +static enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + http_set_status(rule->arg.status.code, rule->arg.status.reason, s); + return ACT_RET_CONT; +} + +/* parse set-status action: + * This action accepts a single argument of type int representing + * an http status code. It returns ACT_RET_PRS_OK on success, + * ACT_RET_PRS_ERR on error. + */ +static enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struct proxy *px, + struct act_rule *rule, char **err) +{ + char *error; + + rule->action = ACT_CUSTOM; + rule->action_ptr = action_http_set_status; + + /* Check if an argument is available */ + if (!*args[*orig_arg]) { + memprintf(err, "expects 1 argument: ; or 3 arguments: reason "); + return ACT_RET_PRS_ERR; + } + + /* convert status code as integer */ + rule->arg.status.code = strtol(args[*orig_arg], &error, 10); + if (*error != '\0' || rule->arg.status.code < 100 || rule->arg.status.code > 999) { + memprintf(err, "expects an integer status code between 100 and 999"); + return ACT_RET_PRS_ERR; + } + + (*orig_arg)++; + + /* set custom reason string */ + rule->arg.status.reason = NULL; // If null, we use the default reason for the status code. + if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 && + (*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) { + (*orig_arg)++; + rule->arg.status.reason = strdup(args[*orig_arg]); + (*orig_arg)++; + } + + return ACT_RET_PRS_OK; +} + +/* This function executes the "reject" HTTP action. It clears the request and + * response buffer without sending any response. It can be useful as an HTTP + * alternative to the silent-drop action to defend against DoS attacks, and may + * also be used with HTTP/2 to close a connection instead of just a stream. + * The txn status is unchanged, indicating no response was sent. The termination + * flags will indicate "PR". It always returns ACT_RET_STOP. + */ +static enum act_return http_action_reject(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + channel_abort(&s->req); + channel_abort(&s->res); + s->req.analysers = 0; + s->res.analysers = 0; + + HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1); + HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1); + if (sess->listener && sess->listener->counters) + HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1); + + if (!(s->flags & SF_ERR_MASK)) + s->flags |= SF_ERR_PRXCOND; + if (!(s->flags & SF_FINST_MASK)) + s->flags |= SF_FINST_R; + + return ACT_RET_CONT; +} + +/* parse the "reject" action: + * This action takes no argument and returns ACT_RET_PRS_OK on success, + * ACT_RET_PRS_ERR on error. + */ +static enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px, + struct act_rule *rule, char **err) +{ + rule->action = ACT_CUSTOM; + rule->action_ptr = http_action_reject; + return ACT_RET_PRS_OK; +} + +/* This function executes the "capture" action. It executes a fetch expression, + * turns the result into a string and puts it in a capture slot. It always + * returns 1. If an error occurs the action is cancelled, but the rule + * processing continues. + */ +static enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + struct sample *key; + struct cap_hdr *h = rule->arg.cap.hdr; + char **cap = s->req_cap; + int len; + + key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR); + if (!key) + return ACT_RET_CONT; + + if (cap[h->index] == NULL) + cap[h->index] = pool_alloc(h->pool); + + if (cap[h->index] == NULL) /* no more capture memory */ + return ACT_RET_CONT; + + len = key->data.u.str.data; + if (len > h->len) + len = h->len; + + memcpy(cap[h->index], key->data.u.str.area, len); + cap[h->index][len] = 0; + return ACT_RET_CONT; +} + +/* This function executes the "capture" action and store the result in a + * capture slot if exists. It executes a fetch expression, turns the result + * into a string and puts it in a capture slot. It always returns 1. If an + * error occurs the action is cancelled, but the rule processing continues. + */ +static enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + struct sample *key; + struct cap_hdr *h; + char **cap = s->req_cap; + struct proxy *fe = strm_fe(s); + int len; + int i; + + /* Look for the original configuration. */ + for (h = fe->req_cap, i = fe->nb_req_cap - 1; + h != NULL && i != rule->arg.capid.idx ; + i--, h = h->next); + if (!h) + return ACT_RET_CONT; + + key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR); + if (!key) + return ACT_RET_CONT; + + if (cap[h->index] == NULL) + cap[h->index] = pool_alloc(h->pool); + + if (cap[h->index] == NULL) /* no more capture memory */ + return ACT_RET_CONT; + + len = key->data.u.str.data; + if (len > h->len) + len = h->len; + + memcpy(cap[h->index], key->data.u.str.area, len); + cap[h->index][len] = 0; + return ACT_RET_CONT; +} + +/* Check an "http-request capture" action. + * + * The function returns 1 in success case, otherwise, it returns 0 and err is + * filled. + */ +static int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err) +{ + if (rule->action_ptr != http_action_req_capture_by_id) + return 1; + + if (rule->arg.capid.idx >= px->nb_req_cap) { + memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule", + rule->arg.capid.idx); + return 0; + } + + return 1; +} + +/* parse an "http-request capture" action. It takes a single argument which is + * a sample fetch expression. It stores the expression into arg->act.p[0] and + * the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1]. + * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error. + */ +static enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px, + struct act_rule *rule, char **err) +{ + struct sample_expr *expr; + struct cap_hdr *hdr; + int cur_arg; + int len = 0; + + for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++) + if (strcmp(args[cur_arg], "if") == 0 || + strcmp(args[cur_arg], "unless") == 0) + break; + + if (cur_arg < *orig_arg + 3) { + memprintf(err, "expects [ 'len' | id ]"); + return ACT_RET_PRS_ERR; + } + + cur_arg = *orig_arg; + expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args); + if (!expr) + return ACT_RET_PRS_ERR; + + if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) { + memprintf(err, + "fetch method '%s' extracts information from '%s', none of which is available here", + args[cur_arg-1], sample_src_names(expr->fetch->use)); + free(expr); + return ACT_RET_PRS_ERR; + } + + if (!args[cur_arg] || !*args[cur_arg]) { + memprintf(err, "expects 'len or 'id'"); + free(expr); + return ACT_RET_PRS_ERR; + } + + if (strcmp(args[cur_arg], "len") == 0) { + cur_arg++; + + if (!(px->cap & PR_CAP_FE)) { + memprintf(err, "proxy '%s' has no frontend capability", px->id); + return ACT_RET_PRS_ERR; + } + + px->conf.args.ctx = ARGC_CAP; + + if (!args[cur_arg]) { + memprintf(err, "missing length value"); + free(expr); + return ACT_RET_PRS_ERR; + } + /* we copy the table name for now, it will be resolved later */ + len = atoi(args[cur_arg]); + if (len <= 0) { + memprintf(err, "length must be > 0"); + free(expr); + return ACT_RET_PRS_ERR; + } + cur_arg++; + + if (!len) { + memprintf(err, "a positive 'len' argument is mandatory"); + free(expr); + return ACT_RET_PRS_ERR; + } + + hdr = calloc(1, sizeof(*hdr)); + hdr->next = px->req_cap; + hdr->name = NULL; /* not a header capture */ + hdr->namelen = 0; + hdr->len = len; + hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED); + hdr->index = px->nb_req_cap++; + + px->req_cap = hdr; + px->to_log |= LW_REQHDR; + + rule->action = ACT_CUSTOM; + rule->action_ptr = http_action_req_capture; + rule->arg.cap.expr = expr; + rule->arg.cap.hdr = hdr; + } + + else if (strcmp(args[cur_arg], "id") == 0) { + int id; + char *error; + + cur_arg++; + + if (!args[cur_arg]) { + memprintf(err, "missing id value"); + free(expr); + return ACT_RET_PRS_ERR; + } + + id = strtol(args[cur_arg], &error, 10); + if (*error != '\0') { + memprintf(err, "cannot parse id '%s'", args[cur_arg]); + free(expr); + return ACT_RET_PRS_ERR; + } + cur_arg++; + + px->conf.args.ctx = ARGC_CAP; + + rule->action = ACT_CUSTOM; + rule->action_ptr = http_action_req_capture_by_id; + rule->check_ptr = check_http_req_capture; + rule->arg.capid.expr = expr; + rule->arg.capid.idx = id; + } + + else { + memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]); + free(expr); + return ACT_RET_PRS_ERR; + } + + *orig_arg = cur_arg; + return ACT_RET_PRS_OK; +} + +/* This function executes the "capture" action and store the result in a + * capture slot if exists. It executes a fetch expression, turns the result + * into a string and puts it in a capture slot. It always returns 1. If an + * error occurs the action is cancelled, but the rule processing continues. + */ +static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + struct sample *key; + struct cap_hdr *h; + char **cap = s->res_cap; + struct proxy *fe = strm_fe(s); + int len; + int i; + + /* Look for the original configuration. */ + for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1; + h != NULL && i != rule->arg.capid.idx ; + i--, h = h->next); + if (!h) + return ACT_RET_CONT; + + key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR); + if (!key) + return ACT_RET_CONT; + + if (cap[h->index] == NULL) + cap[h->index] = pool_alloc(h->pool); + + if (cap[h->index] == NULL) /* no more capture memory */ + return ACT_RET_CONT; + + len = key->data.u.str.data; + if (len > h->len) + len = h->len; + + memcpy(cap[h->index], key->data.u.str.area, len); + cap[h->index][len] = 0; + return ACT_RET_CONT; +} + +/* Check an "http-response capture" action. + * + * The function returns 1 in success case, otherwise, it returns 0 and err is + * filled. + */ +static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err) +{ + if (rule->action_ptr != http_action_res_capture_by_id) + return 1; + + if (rule->arg.capid.idx >= px->nb_rsp_cap) { + memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule", + rule->arg.capid.idx); + return 0; + } + + return 1; +} + +/* parse an "http-response capture" action. It takes a single argument which is + * a sample fetch expression. It stores the expression into arg->act.p[0] and + * the allocated hdr_cap struct od the preallocated id into arg->act.p[1]. + * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error. + */ +static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px, + struct act_rule *rule, char **err) +{ + struct sample_expr *expr; + int cur_arg; + int id; + char *error; + + for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++) + if (strcmp(args[cur_arg], "if") == 0 || + strcmp(args[cur_arg], "unless") == 0) + break; + + if (cur_arg < *orig_arg + 3) { + memprintf(err, "expects id "); + return ACT_RET_PRS_ERR; + } + + cur_arg = *orig_arg; + expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args); + if (!expr) + return ACT_RET_PRS_ERR; + + if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) { + memprintf(err, + "fetch method '%s' extracts information from '%s', none of which is available here", + args[cur_arg-1], sample_src_names(expr->fetch->use)); + free(expr); + return ACT_RET_PRS_ERR; + } + + if (!args[cur_arg] || !*args[cur_arg]) { + memprintf(err, "expects 'id'"); + free(expr); + return ACT_RET_PRS_ERR; + } + + if (strcmp(args[cur_arg], "id") != 0) { + memprintf(err, "expects 'id', found '%s'", args[cur_arg]); + free(expr); + return ACT_RET_PRS_ERR; + } + + cur_arg++; + + if (!args[cur_arg]) { + memprintf(err, "missing id value"); + free(expr); + return ACT_RET_PRS_ERR; + } + + id = strtol(args[cur_arg], &error, 10); + if (*error != '\0') { + memprintf(err, "cannot parse id '%s'", args[cur_arg]); + free(expr); + return ACT_RET_PRS_ERR; + } + cur_arg++; + + px->conf.args.ctx = ARGC_CAP; + + rule->action = ACT_CUSTOM; + rule->action_ptr = http_action_res_capture_by_id; + rule->check_ptr = check_http_res_capture; + rule->arg.capid.expr = expr; + rule->arg.capid.idx = id; + + *orig_arg = cur_arg; + return ACT_RET_PRS_OK; +} + +/************************************************************************/ +/* All supported http-request action keywords must be declared here. */ +/************************************************************************/ + +static struct action_kw_list http_req_actions = { + .kw = { + { "capture", parse_http_req_capture }, + { "reject", parse_http_action_reject }, + { "set-method", parse_set_req_line }, + { "set-path", parse_set_req_line }, + { "set-query", parse_set_req_line }, + { "set-uri", parse_set_req_line }, + { NULL, NULL } + } +}; + +static struct action_kw_list http_res_actions = { + .kw = { + { "capture", parse_http_res_capture }, + { "set-status", parse_http_set_status }, + { NULL, NULL } + } +}; + +__attribute__((constructor)) +static void __http_act_init(void) +{ + http_req_keywords_register(&http_req_actions); + http_res_keywords_register(&http_res_actions); +} + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/http_conv.c b/src/http_conv.c new file mode 100644 index 0000000000..42a7616b93 --- /dev/null +++ b/src/http_conv.c @@ -0,0 +1,349 @@ +/* + * HTTP sample conversion + * + * Copyright 2000-2018 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +/* takes an UINT value on input supposed to represent the time since EPOCH, + * adds an optional offset found in args[0] and emits a string representing + * the date in RFC-1123/5322 format. + */ +static int sample_conv_http_date(const struct arg *args, struct sample *smp, void *private) +{ + const char day[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + const char mon[12][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + struct buffer *temp; + struct tm *tm; + /* With high numbers, the date returned can be negative, the 55 bits mask prevent this. */ + time_t curr_date = smp->data.u.sint & 0x007fffffffffffffLL; + + /* add offset */ + if (args && (args[0].type == ARGT_SINT)) + curr_date += args[0].data.sint; + + tm = gmtime(&curr_date); + if (!tm) + return 0; + + temp = get_trash_chunk(); + temp->data = snprintf(temp->area, temp->size - temp->data, + "%s, %02d %s %04d %02d:%02d:%02d GMT", + day[tm->tm_wday], tm->tm_mday, mon[tm->tm_mon], + 1900+tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec); + + smp->data.u.str = *temp; + smp->data.type = SMP_T_STR; + return 1; +} + +/* Arguments: The list of expected value, the number of parts returned and the separator */ +static int sample_conv_q_preferred(const struct arg *args, struct sample *smp, void *private) +{ + const char *al = smp->data.u.str.area; + const char *end = al + smp->data.u.str.data; + const char *token; + int toklen; + int qvalue; + const char *str; + const char *w; + int best_q = 0; + + /* Set the constant to the sample, because the output of the + * function will be peek in the constant configuration string. + */ + smp->flags |= SMP_F_CONST; + smp->data.u.str.size = 0; + smp->data.u.str.area = ""; + smp->data.u.str.data = 0; + + /* Parse the accept language */ + while (1) { + + /* Jump spaces, quit if the end is detected. */ + while (al < end && isspace((unsigned char)*al)) + al++; + if (al >= end) + break; + + /* Start of the fisrt word. */ + token = al; + + /* Look for separator: isspace(), ',' or ';'. Next value if 0 length word. */ + while (al < end && *al != ';' && *al != ',' && !isspace((unsigned char)*al)) + al++; + if (al == token) + goto expect_comma; + + /* Length of the token. */ + toklen = al - token; + qvalue = 1000; + + /* Check if the token exists in the list. If the token not exists, + * jump to the next token. + */ + str = args[0].data.str.area; + w = str; + while (1) { + if (*str == ';' || *str == '\0') { + if (http_language_range_match(token, toklen, w, str - w)) + goto look_for_q; + if (*str == '\0') + goto expect_comma; + w = str + 1; + } + str++; + } + goto expect_comma; + +look_for_q: + + /* Jump spaces, quit if the end is detected. */ + while (al < end && isspace((unsigned char)*al)) + al++; + if (al >= end) + goto process_value; + + /* If ',' is found, process the result */ + if (*al == ',') + goto process_value; + + /* If the character is different from ';', look + * for the end of the header part in best effort. + */ + if (*al != ';') + goto expect_comma; + + /* Assumes that the char is ';', now expect "q=". */ + al++; + + /* Jump spaces, process value if the end is detected. */ + while (al < end && isspace((unsigned char)*al)) + al++; + if (al >= end) + goto process_value; + + /* Expect 'q'. If no 'q', continue in best effort */ + if (*al != 'q') + goto process_value; + al++; + + /* Jump spaces, process value if the end is detected. */ + while (al < end && isspace((unsigned char)*al)) + al++; + if (al >= end) + goto process_value; + + /* Expect '='. If no '=', continue in best effort */ + if (*al != '=') + goto process_value; + al++; + + /* Jump spaces, process value if the end is detected. */ + while (al < end && isspace((unsigned char)*al)) + al++; + if (al >= end) + goto process_value; + + /* Parse the q value. */ + qvalue = http_parse_qvalue(al, &al); + +process_value: + + /* If the new q value is the best q value, then store the associated + * language in the response. If qvalue is the biggest value (1000), + * break the process. + */ + if (qvalue > best_q) { + smp->data.u.str.area = (char *)w; + smp->data.u.str.data = str - w; + if (qvalue >= 1000) + break; + best_q = qvalue; + } + +expect_comma: + + /* Expect comma or end. If the end is detected, quit the loop. */ + while (al < end && *al != ',') + al++; + if (al >= end) + break; + + /* Comma is found, jump it and restart the analyzer. */ + al++; + } + + /* Set default value if required. */ + if (smp->data.u.str.data == 0 && args[1].type == ARGT_STR) { + smp->data.u.str.area = args[1].data.str.area; + smp->data.u.str.data = args[1].data.str.data; + } + + /* Return true only if a matching language was found. */ + return smp->data.u.str.data != 0; +} + +/* This fetch url-decode any input string. */ +static int sample_conv_url_dec(const struct arg *args, struct sample *smp, void *private) +{ + int len; + + /* If the constant flag is set or if not size is avalaible at + * the end of the buffer, copy the string in other buffer + * before decoding. + */ + if (smp->flags & SMP_F_CONST || smp->data.u.str.size <= smp->data.u.str.data) { + struct buffer *str = get_trash_chunk(); + memcpy(str->area, smp->data.u.str.area, smp->data.u.str.data); + smp->data.u.str.area = str->area; + smp->data.u.str.size = str->size; + smp->flags &= ~SMP_F_CONST; + } + + /* Add final \0 required by url_decode(), and convert the input string. */ + smp->data.u.str.area[smp->data.u.str.data] = '\0'; + len = url_decode(smp->data.u.str.area); + if (len < 0) + return 0; + smp->data.u.str.data = len; + return 1; +} + +static int smp_conv_req_capture(const struct arg *args, struct sample *smp, void *private) +{ + struct proxy *fe = strm_fe(smp->strm); + int idx, i; + struct cap_hdr *hdr; + int len; + + if (!args || args->type != ARGT_SINT) + return 0; + + idx = args->data.sint; + + /* Check the availibity of the capture id. */ + if (idx > fe->nb_req_cap - 1) + return 0; + + /* Look for the original configuration. */ + for (hdr = fe->req_cap, i = fe->nb_req_cap - 1; + hdr != NULL && i != idx ; + i--, hdr = hdr->next); + if (!hdr) + return 0; + + /* check for the memory allocation */ + if (smp->strm->req_cap[hdr->index] == NULL) + smp->strm->req_cap[hdr->index] = pool_alloc(hdr->pool); + if (smp->strm->req_cap[hdr->index] == NULL) + return 0; + + /* Check length. */ + len = smp->data.u.str.data; + if (len > hdr->len) + len = hdr->len; + + /* Capture input data. */ + memcpy(smp->strm->req_cap[idx], smp->data.u.str.area, len); + smp->strm->req_cap[idx][len] = '\0'; + + return 1; +} + +static int smp_conv_res_capture(const struct arg *args, struct sample *smp, void *private) +{ + struct proxy *fe = strm_fe(smp->strm); + int idx, i; + struct cap_hdr *hdr; + int len; + + if (!args || args->type != ARGT_SINT) + return 0; + + idx = args->data.sint; + + /* Check the availibity of the capture id. */ + if (idx > fe->nb_rsp_cap - 1) + return 0; + + /* Look for the original configuration. */ + for (hdr = fe->rsp_cap, i = fe->nb_rsp_cap - 1; + hdr != NULL && i != idx ; + i--, hdr = hdr->next); + if (!hdr) + return 0; + + /* check for the memory allocation */ + if (smp->strm->res_cap[hdr->index] == NULL) + smp->strm->res_cap[hdr->index] = pool_alloc(hdr->pool); + if (smp->strm->res_cap[hdr->index] == NULL) + return 0; + + /* Check length. */ + len = smp->data.u.str.data; + if (len > hdr->len) + len = hdr->len; + + /* Capture input data. */ + memcpy(smp->strm->res_cap[idx], smp->data.u.str.area, len); + smp->strm->res_cap[idx][len] = '\0'; + + return 1; +} + +/************************************************************************/ +/* All supported converter keywords must be declared here. */ +/************************************************************************/ + +/* Note: must not be declared as its list will be overwritten */ +static struct sample_conv_kw_list sample_conv_kws = {ILH, { + { "http_date", sample_conv_http_date, ARG1(0,SINT), NULL, SMP_T_SINT, SMP_T_STR}, + { "language", sample_conv_q_preferred, ARG2(1,STR,STR), NULL, SMP_T_STR, SMP_T_STR}, + { "capture-req", smp_conv_req_capture, ARG1(1,SINT), NULL, SMP_T_STR, SMP_T_STR}, + { "capture-res", smp_conv_res_capture, ARG1(1,SINT), NULL, SMP_T_STR, SMP_T_STR}, + { "url_dec", sample_conv_url_dec, 0, NULL, SMP_T_STR, SMP_T_STR}, + { NULL, NULL, 0, 0, 0 }, +}}; + +__attribute__((constructor)) +static void __http_conv_init(void) +{ + sample_register_convs(&sample_conv_kws); +} + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/http_fetch.c b/src/http_fetch.c new file mode 100644 index 0000000000..40381ccbc1 --- /dev/null +++ b/src/http_fetch.c @@ -0,0 +1,1930 @@ +/* + * HTTP samples fetching + * + * Copyright 2000-2018 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +/* this struct is used between calls to smp_fetch_hdr() or smp_fetch_cookie() */ +static THREAD_LOCAL struct hdr_ctx static_hdr_ctx; + +/* + * Returns the data from Authorization header. Function may be called more + * than once so data is stored in txn->auth_data. When no header is found + * or auth method is unknown auth_method is set to HTTP_AUTH_WRONG to avoid + * searching again for something we are unable to find anyway. However, if + * the result if valid, the cache is not reused because we would risk to + * have the credentials overwritten by another stream in parallel. + */ + +static int get_http_auth(struct stream *s) +{ + + struct http_txn *txn = s->txn; + struct buffer auth_method; + struct hdr_ctx ctx; + char *h, *p; + int len; + +#ifdef DEBUG_AUTH + printf("Auth for stream %p: %d\n", s, txn->auth.method); +#endif + + if (txn->auth.method == HTTP_AUTH_WRONG) + return 0; + + txn->auth.method = HTTP_AUTH_WRONG; + + ctx.idx = 0; + + if (txn->flags & TX_USE_PX_CONN) { + h = "Proxy-Authorization"; + len = strlen(h); + } else { + h = "Authorization"; + len = strlen(h); + } + + if (!http_find_header2(h, len, ci_head(&s->req), &txn->hdr_idx, &ctx)) + return 0; + + h = ctx.line + ctx.val; + + p = memchr(h, ' ', ctx.vlen); + len = p - h; + if (!p || len <= 0) + return 0; + + if (chunk_initlen(&auth_method, h, 0, len) != 1) + return 0; + + chunk_initlen(&txn->auth.method_data, p + 1, 0, ctx.vlen - len - 1); + + if (!strncasecmp("Basic", auth_method.area, auth_method.data)) { + struct buffer *http_auth = get_trash_chunk(); + + len = base64dec(txn->auth.method_data.area, + txn->auth.method_data.data, + http_auth->area, global.tune.bufsize - 1); + + if (len < 0) + return 0; + + + http_auth->area[len] = '\0'; + + p = strchr(http_auth->area, ':'); + + if (!p) + return 0; + + txn->auth.user = http_auth->area; + *p = '\0'; + txn->auth.pass = p+1; + + txn->auth.method = HTTP_AUTH_BASIC; + return 1; + } + + return 0; +} + +/* This function ensures that the prerequisites for an L7 fetch are ready, + * which means that a request or response is ready. If some data is missing, + * a parsing attempt is made. This is useful in TCP-based ACLs which are able + * to extract data from L7. If is non-null during a request prefetch, + * another test is made to ensure the required information is not gone. + * + * The function returns : + * 0 with SMP_F_MAY_CHANGE in the sample flags if some data is missing to + * decide whether or not an HTTP message is present ; + * 0 if the requested data cannot be fetched or if it is certain that + * we'll never have any HTTP message there ; + * 1 if an HTTP message is ready + */ +int smp_prefetch_http(struct proxy *px, struct stream *s, unsigned int opt, + const struct arg *args, struct sample *smp, int req_vol) +{ + struct http_txn *txn; + struct http_msg *msg; + + /* Note: it is possible that is NULL when called before stream + * initialization (eg: tcp-request connection), so this function is the + * one responsible for guarding against this case for all HTTP users. + */ + if (!s) + return 0; + + if (!s->txn) { + if (unlikely(!http_alloc_txn(s))) + return 0; /* not enough memory */ + http_init_txn(s); + } + txn = s->txn; + msg = &txn->req; + + /* Check for a dependency on a request */ + smp->data.type = SMP_T_BOOL; + + if ((opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) { + /* If the buffer does not leave enough free space at the end, + * we must first realign it. + */ + if (ci_head(&s->req) > b_orig(&s->req.buf) && + ci_head(&s->req) + ci_data(&s->req) > b_wrap(&s->req.buf) - global.tune.maxrewrite) + channel_slow_realign(&s->req, trash.area); + + if (unlikely(txn->req.msg_state < HTTP_MSG_BODY)) { + if (msg->msg_state == HTTP_MSG_ERROR) + return 0; + + /* Try to decode HTTP request */ + if (likely(msg->next < ci_data(&s->req))) + http_msg_analyzer(msg, &txn->hdr_idx); + + /* Still no valid request ? */ + if (unlikely(msg->msg_state < HTTP_MSG_BODY)) { + if ((msg->msg_state == HTTP_MSG_ERROR) || + channel_full(&s->req, global.tune.maxrewrite)) { + return 0; + } + /* wait for final state */ + smp->flags |= SMP_F_MAY_CHANGE; + return 0; + } + + /* OK we just got a valid HTTP request. We have some minor + * preparation to perform so that further checks can rely + * on HTTP tests. + */ + + /* If the request was parsed but was too large, we must absolutely + * return an error so that it is not processed. At the moment this + * cannot happen, but if the parsers are to change in the future, + * we want this check to be maintained. + */ + if (unlikely(ci_head(&s->req) + ci_data(&s->req) > + b_wrap(&s->req.buf) - global.tune.maxrewrite)) { + msg->err_state = msg->msg_state; + msg->msg_state = HTTP_MSG_ERROR; + smp->data.u.sint = 1; + return 1; + } + + txn->meth = find_http_meth(ci_head(msg->chn), msg->sl.rq.m_l); + if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD) + s->flags |= SF_REDIRECTABLE; + + if (unlikely(msg->sl.rq.v_l == 0) && !http_upgrade_v09_to_v10(txn)) + return 0; + } + + if (req_vol && txn->rsp.msg_state != HTTP_MSG_RPBEFORE) { + return 0; /* data might have moved and indexes changed */ + } + + /* otherwise everything's ready for the request */ + } + else { + /* Check for a dependency on a response */ + if (txn->rsp.msg_state < HTTP_MSG_BODY) { + smp->flags |= SMP_F_MAY_CHANGE; + return 0; + } + } + + /* everything's OK */ + smp->data.u.sint = 1; + return 1; +} + +/* This function fetches the method of current HTTP request and stores + * it in the global pattern struct as a chunk. There are two possibilities : + * - if the method is known (not HTTP_METH_OTHER), its identifier is stored + * in and is NULL ; + * - if the method is unknown (HTTP_METH_OTHER), points to the text and + * to its length. + * This is intended to be used with pat_match_meth() only. + */ +static int smp_fetch_meth(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + int meth; + struct http_txn *txn; + + CHECK_HTTP_MESSAGE_FIRST_PERM(); + + txn = smp->strm->txn; + meth = txn->meth; + smp->data.type = SMP_T_METH; + smp->data.u.meth.meth = meth; + if (meth == HTTP_METH_OTHER) { + if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE) + /* ensure the indexes are not affected */ + return 0; + smp->flags |= SMP_F_CONST; + smp->data.u.meth.str.data = txn->req.sl.rq.m_l; + smp->data.u.meth.str.area = ci_head(txn->req.chn); + } + smp->flags |= SMP_F_VOL_1ST; + return 1; +} + +static int smp_fetch_rqver(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + char *ptr; + int len; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + len = txn->req.sl.rq.v_l; + ptr = ci_head(txn->req.chn) + txn->req.sl.rq.v; + + while ((len-- > 0) && (*ptr++ != '/')); + if (len <= 0) + return 0; + + smp->data.type = SMP_T_STR; + smp->data.u.str.area = ptr; + smp->data.u.str.data = len; + + smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; + return 1; +} + +static int smp_fetch_stver(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + char *ptr; + int len; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + if (txn->rsp.msg_state < HTTP_MSG_BODY) + return 0; + + len = txn->rsp.sl.st.v_l; + ptr = ci_head(txn->rsp.chn); + + while ((len-- > 0) && (*ptr++ != '/')); + if (len <= 0) + return 0; + + smp->data.type = SMP_T_STR; + smp->data.u.str.area = ptr; + smp->data.u.str.data = len; + + smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; + return 1; +} + +/* 3. Check on Status Code. We manipulate integers here. */ +static int smp_fetch_stcode(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + char *ptr; + int len; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + if (txn->rsp.msg_state < HTTP_MSG_BODY) + return 0; + + len = txn->rsp.sl.st.c_l; + ptr = ci_head(txn->rsp.chn) + txn->rsp.sl.st.c; + + smp->data.type = SMP_T_SINT; + smp->data.u.sint = __strl2ui(ptr, len); + smp->flags = SMP_F_VOL_1ST; + return 1; +} + +static int smp_fetch_uniqueid(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + if (LIST_ISEMPTY(&smp->sess->fe->format_unique_id)) + return 0; + + if (!smp->strm->unique_id) { + if ((smp->strm->unique_id = pool_alloc(pool_head_uniqueid)) == NULL) + return 0; + smp->strm->unique_id[0] = '\0'; + } + smp->data.u.str.data = build_logline(smp->strm, smp->strm->unique_id, + UNIQUEID_LEN, &smp->sess->fe->format_unique_id); + + smp->data.type = SMP_T_STR; + smp->data.u.str.area = smp->strm->unique_id; + smp->flags = SMP_F_CONST; + return 1; +} + +/* Returns a string block containing all headers including the + * empty line wich separes headers from the body. This is useful + * form some headers analysis. + */ +static int smp_fetch_hdrs(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_msg *msg; + struct hdr_idx *idx; + struct http_txn *txn; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + idx = &txn->hdr_idx; + msg = &txn->req; + + smp->data.type = SMP_T_STR; + smp->data.u.str.area = ci_head(msg->chn) + hdr_idx_first_pos(idx); + smp->data.u.str.data = msg->eoh - hdr_idx_first_pos(idx) + 1 + + (ci_head(msg->chn)[msg->eoh] == '\r'); + + return 1; +} + +/* Returns the header request in a length/value encoded format. + * This is useful for exchanges with the SPOE. + * + * A "length value" is a multibyte code encoding numbers. It uses the + * SPOE format. The encoding is the following: + * + * Each couple "header name" / "header value" is composed + * like this: + * "length value" "header name bytes" + * "length value" "header value bytes" + * When the last header is reached, the header name and the header + * value are empty. Their length are 0 + */ +static int smp_fetch_hdrs_bin(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_msg *msg; + struct buffer *temp; + struct hdr_idx *idx; + const char *cur_ptr, *cur_next, *p; + int old_idx, cur_idx; + struct hdr_idx_elem *cur_hdr; + const char *hn, *hv; + int hnl, hvl; + int ret; + struct http_txn *txn; + char *buf; + char *end; + + CHECK_HTTP_MESSAGE_FIRST(); + + temp = get_trash_chunk(); + buf = temp->area; + end = temp->area + temp->size; + + txn = smp->strm->txn; + idx = &txn->hdr_idx; + msg = &txn->req; + + /* Build array of headers. */ + old_idx = 0; + cur_next = ci_head(msg->chn) + hdr_idx_first_pos(idx); + while (1) { + cur_idx = idx->v[old_idx].next; + if (!cur_idx) + break; + old_idx = cur_idx; + + cur_hdr = &idx->v[cur_idx]; + cur_ptr = cur_next; + cur_next = cur_ptr + cur_hdr->len + cur_hdr->cr + 1; + + /* Now we have one full header at cur_ptr of len cur_hdr->len, + * and the next header starts at cur_next. We'll check + * this header in the list as well as against the default + * rule. + */ + + /* look for ': *'. */ + hn = cur_ptr; + for (p = cur_ptr; p < cur_ptr + cur_hdr->len && *p != ':'; p++); + if (p >= cur_ptr+cur_hdr->len) + continue; + hnl = p - hn; + p++; + while (p < cur_ptr + cur_hdr->len && (*p == ' ' || *p == '\t')) + p++; + if (p >= cur_ptr + cur_hdr->len) + continue; + hv = p; + hvl = cur_ptr + cur_hdr->len-p; + + /* encode the header name. */ + ret = encode_varint(hnl, &buf, end); + if (ret == -1) + return 0; + if (buf + hnl > end) + return 0; + memcpy(buf, hn, hnl); + buf += hnl; + + /* encode and copy the value. */ + ret = encode_varint(hvl, &buf, end); + if (ret == -1) + return 0; + if (buf + hvl > end) + return 0; + memcpy(buf, hv, hvl); + buf += hvl; + } + + /* encode the end of the header list with empty + * header name and header value. + */ + ret = encode_varint(0, &buf, end); + if (ret == -1) + return 0; + ret = encode_varint(0, &buf, end); + if (ret == -1) + return 0; + + /* Initialise sample data which will be filled. */ + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = temp->area; + smp->data.u.str.data = buf - temp->area; + smp->data.u.str.size = temp->size; + + return 1; +} + +/* returns the longest available part of the body. This requires that the body + * has been waited for using http-buffer-request. + */ +static int smp_fetch_body(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_msg *msg; + unsigned long len; + unsigned long block1; + char *body; + struct buffer *temp; + + CHECK_HTTP_MESSAGE_FIRST(); + + if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) + msg = &smp->strm->txn->req; + else + msg = &smp->strm->txn->rsp; + + len = http_body_bytes(msg); + body = c_ptr(msg->chn, -http_data_rewind(msg)); + + block1 = len; + if (block1 > b_wrap(&msg->chn->buf) - body) + block1 = b_wrap(&msg->chn->buf) - body; + + if (block1 == len) { + /* buffer is not wrapped (or empty) */ + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = body; + smp->data.u.str.data = len; + smp->flags = SMP_F_VOL_TEST | SMP_F_CONST; + } + else { + /* buffer is wrapped, we need to defragment it */ + temp = get_trash_chunk(); + memcpy(temp->area, body, block1); + memcpy(temp->area + block1, b_orig(&msg->chn->buf), + len - block1); + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = temp->area; + smp->data.u.str.data = len; + smp->flags = SMP_F_VOL_TEST; + } + return 1; +} + + +/* returns the available length of the body. This requires that the body + * has been waited for using http-buffer-request. + */ +static int smp_fetch_body_len(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_msg *msg; + + CHECK_HTTP_MESSAGE_FIRST(); + + if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) + msg = &smp->strm->txn->req; + else + msg = &smp->strm->txn->rsp; + + smp->data.type = SMP_T_SINT; + smp->data.u.sint = http_body_bytes(msg); + + smp->flags = SMP_F_VOL_TEST; + return 1; +} + + +/* returns the advertised length of the body, or the advertised size of the + * chunks available in the buffer. This requires that the body has been waited + * for using http-buffer-request. + */ +static int smp_fetch_body_size(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_msg *msg; + + CHECK_HTTP_MESSAGE_FIRST(); + + if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) + msg = &smp->strm->txn->req; + else + msg = &smp->strm->txn->rsp; + + smp->data.type = SMP_T_SINT; + smp->data.u.sint = msg->body_len; + + smp->flags = SMP_F_VOL_TEST; + return 1; +} + + +/* 4. Check on URL/URI. A pointer to the URI is stored. */ +static int smp_fetch_url(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + smp->data.type = SMP_T_STR; + smp->data.u.str.data = txn->req.sl.rq.u_l; + smp->data.u.str.area = ci_head(txn->req.chn) + txn->req.sl.rq.u; + smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; + return 1; +} + +static int smp_fetch_url_ip(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + struct sockaddr_storage addr; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + url2sa(ci_head(txn->req.chn) + txn->req.sl.rq.u, txn->req.sl.rq.u_l, &addr, NULL); + if (((struct sockaddr_in *)&addr)->sin_family != AF_INET) + return 0; + + smp->data.type = SMP_T_IPV4; + smp->data.u.ipv4 = ((struct sockaddr_in *)&addr)->sin_addr; + smp->flags = 0; + return 1; +} + +static int smp_fetch_url_port(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + struct sockaddr_storage addr; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + url2sa(ci_head(txn->req.chn) + txn->req.sl.rq.u, txn->req.sl.rq.u_l, &addr, NULL); + if (((struct sockaddr_in *)&addr)->sin_family != AF_INET) + return 0; + + smp->data.type = SMP_T_SINT; + smp->data.u.sint = ntohs(((struct sockaddr_in *)&addr)->sin_port); + smp->flags = 0; + return 1; +} + +/* Fetch an HTTP header. A pointer to the beginning of the value is returned. + * Accepts an optional argument of type string containing the header field name, + * and an optional argument of type signed or unsigned integer to request an + * explicit occurrence of the header. Note that in the event of a missing name, + * headers are considered from the first one. It does not stop on commas and + * returns full lines instead (useful for User-Agent or Date for example). + */ +static int smp_fetch_fhdr(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct hdr_idx *idx; + struct hdr_ctx *ctx = smp->ctx.a[0]; + const struct http_msg *msg; + int occ = 0; + const char *name_str = NULL; + int name_len = 0; + + if (!ctx) { + /* first call */ + ctx = &static_hdr_ctx; + ctx->idx = 0; + smp->ctx.a[0] = ctx; + } + + if (args) { + if (args[0].type != ARGT_STR) + return 0; + name_str = args[0].data.str.area; + name_len = args[0].data.str.data; + + if (args[1].type == ARGT_SINT) + occ = args[1].data.sint; + } + + CHECK_HTTP_MESSAGE_FIRST(); + + idx = &smp->strm->txn->hdr_idx; + msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp; + + if (ctx && !(smp->flags & SMP_F_NOT_LAST)) + /* search for header from the beginning */ + ctx->idx = 0; + + if (!occ && !(smp->opt & SMP_OPT_ITERATE)) + /* no explicit occurrence and single fetch => last header by default */ + occ = -1; + + if (!occ) + /* prepare to report multiple occurrences for ACL fetches */ + smp->flags |= SMP_F_NOT_LAST; + + smp->data.type = SMP_T_STR; + smp->flags |= SMP_F_VOL_HDR | SMP_F_CONST; + if (http_get_fhdr(msg, name_str, name_len, idx, occ, ctx, &smp->data.u.str.area, &smp->data.u.str.data)) + return 1; + + smp->flags &= ~SMP_F_NOT_LAST; + return 0; +} + +/* 6. Check on HTTP header count. The number of occurrences is returned. + * Accepts exactly 1 argument of type string. It does not stop on commas and + * returns full lines instead (useful for User-Agent or Date for example). + */ +static int smp_fetch_fhdr_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct hdr_idx *idx; + struct hdr_ctx ctx; + const struct http_msg *msg; + int cnt; + const char *name = NULL; + int len = 0; + + if (args && args->type == ARGT_STR) { + name = args->data.str.area; + len = args->data.str.data; + } + + CHECK_HTTP_MESSAGE_FIRST(); + + idx = &smp->strm->txn->hdr_idx; + msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp; + + ctx.idx = 0; + cnt = 0; + while (http_find_full_header2(name, len, ci_head(msg->chn), idx, &ctx)) + cnt++; + + smp->data.type = SMP_T_SINT; + smp->data.u.sint = cnt; + smp->flags = SMP_F_VOL_HDR; + return 1; +} + +static int smp_fetch_hdr_names(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct hdr_idx *idx; + struct hdr_ctx ctx; + const struct http_msg *msg; + struct buffer *temp; + char del = ','; + + if (args && args->type == ARGT_STR) + del = *args[0].data.str.area; + + CHECK_HTTP_MESSAGE_FIRST(); + + idx = &smp->strm->txn->hdr_idx; + msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp; + + temp = get_trash_chunk(); + + ctx.idx = 0; + while (http_find_next_header(ci_head(msg->chn), idx, &ctx)) { + if (temp->data) + temp->area[temp->data++] = del; + memcpy(temp->area + temp->data, ctx.line, ctx.del); + temp->data += ctx.del; + } + + smp->data.type = SMP_T_STR; + smp->data.u.str.area = temp->area; + smp->data.u.str.data = temp->data; + smp->flags = SMP_F_VOL_HDR; + return 1; +} + +/* Fetch an HTTP header. A pointer to the beginning of the value is returned. + * Accepts an optional argument of type string containing the header field name, + * and an optional argument of type signed or unsigned integer to request an + * explicit occurrence of the header. Note that in the event of a missing name, + * headers are considered from the first one. + */ +static int smp_fetch_hdr(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct hdr_idx *idx; + struct hdr_ctx *ctx = smp->ctx.a[0]; + const struct http_msg *msg; + int occ = 0; + const char *name_str = NULL; + int name_len = 0; + + if (!ctx) { + /* first call */ + ctx = &static_hdr_ctx; + ctx->idx = 0; + smp->ctx.a[0] = ctx; + } + + if (args) { + if (args[0].type != ARGT_STR) + return 0; + name_str = args[0].data.str.area; + name_len = args[0].data.str.data; + + if (args[1].type == ARGT_SINT) + occ = args[1].data.sint; + } + + CHECK_HTTP_MESSAGE_FIRST(); + + idx = &smp->strm->txn->hdr_idx; + msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp; + + if (ctx && !(smp->flags & SMP_F_NOT_LAST)) + /* search for header from the beginning */ + ctx->idx = 0; + + if (!occ && !(smp->opt & SMP_OPT_ITERATE)) + /* no explicit occurrence and single fetch => last header by default */ + occ = -1; + + if (!occ) + /* prepare to report multiple occurrences for ACL fetches */ + smp->flags |= SMP_F_NOT_LAST; + + smp->data.type = SMP_T_STR; + smp->flags |= SMP_F_VOL_HDR | SMP_F_CONST; + if (http_get_hdr(msg, name_str, name_len, idx, occ, ctx, &smp->data.u.str.area, &smp->data.u.str.data)) + return 1; + + smp->flags &= ~SMP_F_NOT_LAST; + return 0; +} + +/* 6. Check on HTTP header count. The number of occurrences is returned. + * Accepts exactly 1 argument of type string. + */ +static int smp_fetch_hdr_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct hdr_idx *idx; + struct hdr_ctx ctx; + const struct http_msg *msg; + int cnt; + const char *name = NULL; + int len = 0; + + if (args && args->type == ARGT_STR) { + name = args->data.str.area; + len = args->data.str.data; + } + + CHECK_HTTP_MESSAGE_FIRST(); + + idx = &smp->strm->txn->hdr_idx; + msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp; + + ctx.idx = 0; + cnt = 0; + while (http_find_header2(name, len, ci_head(msg->chn), idx, &ctx)) + cnt++; + + smp->data.type = SMP_T_SINT; + smp->data.u.sint = cnt; + smp->flags = SMP_F_VOL_HDR; + return 1; +} + +/* Fetch an HTTP header's integer value. The integer value is returned. It + * takes a mandatory argument of type string and an optional one of type int + * to designate a specific occurrence. It returns an unsigned integer, which + * may or may not be appropriate for everything. + */ +static int smp_fetch_hdr_val(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + int ret = smp_fetch_hdr(args, smp, kw, private); + + if (ret > 0) { + smp->data.type = SMP_T_SINT; + smp->data.u.sint = strl2ic(smp->data.u.str.area, + smp->data.u.str.data); + } + + return ret; +} + +/* Fetch an HTTP header's IP value. takes a mandatory argument of type string + * and an optional one of type int to designate a specific occurrence. + * It returns an IPv4 or IPv6 address. + */ +static int smp_fetch_hdr_ip(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + int ret; + + while ((ret = smp_fetch_hdr(args, smp, kw, private)) > 0) { + if (url2ipv4((char *) smp->data.u.str.area, &smp->data.u.ipv4)) { + smp->data.type = SMP_T_IPV4; + break; + } else { + struct buffer *temp = get_trash_chunk(); + if (smp->data.u.str.data < temp->size - 1) { + memcpy(temp->area, smp->data.u.str.area, + smp->data.u.str.data); + temp->area[smp->data.u.str.data] = '\0'; + if (inet_pton(AF_INET6, temp->area, &smp->data.u.ipv6)) { + smp->data.type = SMP_T_IPV6; + break; + } + } + } + + /* if the header doesn't match an IP address, fetch next one */ + if (!(smp->flags & SMP_F_NOT_LAST)) + return 0; + } + return ret; +} + +/* 8. Check on URI PATH. A pointer to the PATH is stored. The path starts at + * the first '/' after the possible hostname, and ends before the possible '?'. + */ +static int smp_fetch_path(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + char *ptr, *end; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l; + ptr = http_txn_get_path(txn); + if (!ptr) + return 0; + + /* OK, we got the '/' ! */ + smp->data.type = SMP_T_STR; + smp->data.u.str.area = ptr; + + while (ptr < end && *ptr != '?') + ptr++; + + smp->data.u.str.data = ptr - smp->data.u.str.area; + smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; + return 1; +} + +/* This produces a concatenation of the first occurrence of the Host header + * followed by the path component if it begins with a slash ('/'). This means + * that '*' will not be added, resulting in exactly the first Host entry. + * If no Host header is found, then the path is returned as-is. The returned + * value is stored in the trash so it does not need to be marked constant. + * The returned sample is of type string. + */ +static int smp_fetch_base(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + char *ptr, *end, *beg; + struct hdr_ctx ctx; + struct buffer *temp; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + ctx.idx = 0; + if (!http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx) || !ctx.vlen) + return smp_fetch_path(args, smp, kw, private); + + /* OK we have the header value in ctx.line+ctx.val for ctx.vlen bytes */ + temp = get_trash_chunk(); + memcpy(temp->area, ctx.line + ctx.val, ctx.vlen); + smp->data.type = SMP_T_STR; + smp->data.u.str.area = temp->area; + smp->data.u.str.data = ctx.vlen; + + /* now retrieve the path */ + end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l; + beg = http_txn_get_path(txn); + if (!beg) + beg = end; + + for (ptr = beg; ptr < end && *ptr != '?'; ptr++); + + if (beg < ptr && *beg == '/') { + memcpy(smp->data.u.str.area + smp->data.u.str.data, beg, + ptr - beg); + smp->data.u.str.data += ptr - beg; + } + + smp->flags = SMP_F_VOL_1ST; + return 1; +} + +/* This produces a 32-bit hash of the concatenation of the first occurrence of + * the Host header followed by the path component if it begins with a slash ('/'). + * This means that '*' will not be added, resulting in exactly the first Host + * entry. If no Host header is found, then the path is used. The resulting value + * is hashed using the path hash followed by a full avalanche hash and provides a + * 32-bit integer value. This fetch is useful for tracking per-path activity on + * high-traffic sites without having to store whole paths. + */ +static int smp_fetch_base32(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + struct hdr_ctx ctx; + unsigned int hash = 0; + char *ptr, *beg, *end; + int len; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + ctx.idx = 0; + if (http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx)) { + /* OK we have the header value in ctx.line+ctx.val for ctx.vlen bytes */ + ptr = ctx.line + ctx.val; + len = ctx.vlen; + while (len--) + hash = *(ptr++) + (hash << 6) + (hash << 16) - hash; + } + + /* now retrieve the path */ + end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l; + beg = http_txn_get_path(txn); + if (!beg) + beg = end; + + for (ptr = beg; ptr < end && *ptr != '?'; ptr++); + + if (beg < ptr && *beg == '/') { + while (beg < ptr) + hash = *(beg++) + (hash << 6) + (hash << 16) - hash; + } + hash = full_hash(hash); + + smp->data.type = SMP_T_SINT; + smp->data.u.sint = hash; + smp->flags = SMP_F_VOL_1ST; + return 1; +} + +/* This concatenates the source address with the 32-bit hash of the Host and + * path as returned by smp_fetch_base32(). The idea is to have per-source and + * per-path counters. The result is a binary block from 8 to 20 bytes depending + * on the source address length. The path hash is stored before the address so + * that in environments where IPv6 is insignificant, truncating the output to + * 8 bytes would still work. + */ +static int smp_fetch_base32_src(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct buffer *temp; + struct connection *cli_conn = objt_conn(smp->sess->origin); + + if (!cli_conn) + return 0; + + if (!smp_fetch_base32(args, smp, kw, private)) + return 0; + + temp = get_trash_chunk(); + *(unsigned int *) temp->area = htonl(smp->data.u.sint); + temp->data += sizeof(unsigned int); + + switch (cli_conn->addr.from.ss_family) { + case AF_INET: + memcpy(temp->area + temp->data, + &((struct sockaddr_in *)&cli_conn->addr.from)->sin_addr, + 4); + temp->data += 4; + break; + case AF_INET6: + memcpy(temp->area + temp->data, + &((struct sockaddr_in6 *)&cli_conn->addr.from)->sin6_addr, + 16); + temp->data += 16; + break; + default: + return 0; + } + + smp->data.u.str = *temp; + smp->data.type = SMP_T_BIN; + return 1; +} + +/* Extracts the query string, which comes after the question mark '?'. If no + * question mark is found, nothing is returned. Otherwise it returns a sample + * of type string carrying the whole query string. + */ +static int smp_fetch_query(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + char *ptr, *end; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + ptr = ci_head(txn->req.chn) + txn->req.sl.rq.u; + end = ptr + txn->req.sl.rq.u_l; + + /* look up the '?' */ + do { + if (ptr == end) + return 0; + } while (*ptr++ != '?'); + + smp->data.type = SMP_T_STR; + smp->data.u.str.area = ptr; + smp->data.u.str.data = end - ptr; + smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; + return 1; +} + +static int smp_fetch_proto_http(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + /* Note: hdr_idx.v cannot be NULL in this ACL because the ACL is tagged + * as a layer7 ACL, which involves automatic allocation of hdr_idx. + */ + + CHECK_HTTP_MESSAGE_FIRST_PERM(); + + smp->data.type = SMP_T_BOOL; + smp->data.u.sint = 1; + return 1; +} + +/* return a valid test if the current request is the first one on the connection */ +static int smp_fetch_http_first_req(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + smp->data.type = SMP_T_BOOL; + smp->data.u.sint = !(smp->strm->txn->flags & TX_NOT_FIRST); + return 1; +} + +/* Accepts exactly 1 argument of type userlist */ +static int smp_fetch_http_auth(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + + if (!args || args->type != ARGT_USR) + return 0; + + CHECK_HTTP_MESSAGE_FIRST(); + + if (!get_http_auth(smp->strm)) + return 0; + + smp->data.type = SMP_T_BOOL; + smp->data.u.sint = check_user(args->data.usr, smp->strm->txn->auth.user, + smp->strm->txn->auth.pass); + return 1; +} + +/* Accepts exactly 1 argument of type userlist */ +static int smp_fetch_http_auth_grp(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + if (!args || args->type != ARGT_USR) + return 0; + + CHECK_HTTP_MESSAGE_FIRST(); + + if (!get_http_auth(smp->strm)) + return 0; + + /* if the user does not belong to the userlist or has a wrong password, + * report that it unconditionally does not match. Otherwise we return + * a string containing the username. + */ + if (!check_user(args->data.usr, smp->strm->txn->auth.user, + smp->strm->txn->auth.pass)) + return 0; + + /* pat_match_auth() will need the user list */ + smp->ctx.a[0] = args->data.usr; + + smp->data.type = SMP_T_STR; + smp->flags = SMP_F_CONST; + smp->data.u.str.area = smp->strm->txn->auth.user; + smp->data.u.str.data = strlen(smp->strm->txn->auth.user); + + return 1; +} + +/* Fetch a captured HTTP request header. The index is the position of + * the "capture" option in the configuration file + */ +static int smp_fetch_capture_req_hdr(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct proxy *fe = strm_fe(smp->strm); + int idx; + + if (!args || args->type != ARGT_SINT) + return 0; + + idx = args->data.sint; + + if (idx > (fe->nb_req_cap - 1) || smp->strm->req_cap == NULL || smp->strm->req_cap[idx] == NULL) + return 0; + + smp->data.type = SMP_T_STR; + smp->flags |= SMP_F_CONST; + smp->data.u.str.area = smp->strm->req_cap[idx]; + smp->data.u.str.data = strlen(smp->strm->req_cap[idx]); + + return 1; +} + +/* Fetch a captured HTTP response header. The index is the position of + * the "capture" option in the configuration file + */ +static int smp_fetch_capture_res_hdr(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct proxy *fe = strm_fe(smp->strm); + int idx; + + if (!args || args->type != ARGT_SINT) + return 0; + + idx = args->data.sint; + + if (idx > (fe->nb_rsp_cap - 1) || smp->strm->res_cap == NULL || smp->strm->res_cap[idx] == NULL) + return 0; + + smp->data.type = SMP_T_STR; + smp->flags |= SMP_F_CONST; + smp->data.u.str.area = smp->strm->res_cap[idx]; + smp->data.u.str.data = strlen(smp->strm->res_cap[idx]); + + return 1; +} + +/* Extracts the METHOD in the HTTP request, the txn->uri should be filled before the call */ +static int smp_fetch_capture_req_method(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct buffer *temp; + struct http_txn *txn = smp->strm->txn; + char *ptr; + + if (!txn || !txn->uri) + return 0; + + ptr = txn->uri; + + while (*ptr != ' ' && *ptr != '\0') /* find first space */ + ptr++; + + temp = get_trash_chunk(); + temp->area = txn->uri; + temp->data = ptr - txn->uri; + smp->data.u.str = *temp; + smp->data.type = SMP_T_STR; + smp->flags = SMP_F_CONST; + + return 1; + +} + +/* Extracts the path in the HTTP request, the txn->uri should be filled before the call */ +static int smp_fetch_capture_req_uri(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn = smp->strm->txn; + struct ist path; + const char *ptr; + + if (!txn || !txn->uri) + return 0; + + ptr = txn->uri; + + while (*ptr != ' ' && *ptr != '\0') /* find first space */ + ptr++; + + if (!*ptr) + return 0; + + ptr++; /* skip the space */ + + path = http_get_path(ist(ptr)); + if (!path.ptr) + return 0; + + smp->data.u.str.area = path.ptr; + smp->data.u.str.data = path.len; + smp->data.type = SMP_T_STR; + smp->flags = SMP_F_CONST; + + return 1; +} + +/* Retrieves the HTTP version from the request (either 1.0 or 1.1) and emits it + * as a string (either "HTTP/1.0" or "HTTP/1.1"). + */ +static int smp_fetch_capture_req_ver(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn = smp->strm->txn; + + if (!txn || txn->req.msg_state < HTTP_MSG_HDR_FIRST) + return 0; + + if (txn->req.flags & HTTP_MSGF_VER_11) + smp->data.u.str.area = "HTTP/1.1"; + else + smp->data.u.str.area = "HTTP/1.0"; + + smp->data.u.str.data = 8; + smp->data.type = SMP_T_STR; + smp->flags = SMP_F_CONST; + return 1; + +} + +/* Retrieves the HTTP version from the response (either 1.0 or 1.1) and emits it + * as a string (either "HTTP/1.0" or "HTTP/1.1"). + */ +static int smp_fetch_capture_res_ver(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn = smp->strm->txn; + + if (!txn || txn->rsp.msg_state < HTTP_MSG_HDR_FIRST) + return 0; + + if (txn->rsp.flags & HTTP_MSGF_VER_11) + smp->data.u.str.area = "HTTP/1.1"; + else + smp->data.u.str.area = "HTTP/1.0"; + + smp->data.u.str.data = 8; + smp->data.type = SMP_T_STR; + smp->flags = SMP_F_CONST; + return 1; + +} + +/* Iterate over all cookies present in a message. The context is stored in + * smp->ctx.a[0] for the in-header position, smp->ctx.a[1] for the + * end-of-header-value, and smp->ctx.a[2] for the hdr_ctx. Depending on + * the direction, multiple cookies may be parsed on the same line or not. + * The cookie name is in args and the name length in args->data.str.len. + * Accepts exactly 1 argument of type string. If the input options indicate + * that no iterating is desired, then only last value is fetched if any. + * The returned sample is of type CSTR. Can be used to parse cookies in other + * files. + */ +static int smp_fetch_cookie(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + struct hdr_idx *idx; + struct hdr_ctx *ctx = smp->ctx.a[2]; + const struct http_msg *msg; + const char *hdr_name; + int hdr_name_len; + char *sol; + int occ = 0; + int found = 0; + + if (!args || args->type != ARGT_STR) + return 0; + + if (!ctx) { + /* first call */ + ctx = &static_hdr_ctx; + ctx->idx = 0; + smp->ctx.a[2] = ctx; + } + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + idx = &smp->strm->txn->hdr_idx; + + if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) { + msg = &txn->req; + hdr_name = "Cookie"; + hdr_name_len = 6; + } else { + msg = &txn->rsp; + hdr_name = "Set-Cookie"; + hdr_name_len = 10; + } + + if (!occ && !(smp->opt & SMP_OPT_ITERATE)) + /* no explicit occurrence and single fetch => last cookie by default */ + occ = -1; + + /* OK so basically here, either we want only one value and it's the + * last one, or we want to iterate over all of them and we fetch the + * next one. + */ + + sol = ci_head(msg->chn); + if (!(smp->flags & SMP_F_NOT_LAST)) { + /* search for the header from the beginning, we must first initialize + * the search parameters. + */ + smp->ctx.a[0] = NULL; + ctx->idx = 0; + } + + smp->flags |= SMP_F_VOL_HDR; + + while (1) { + /* Note: smp->ctx.a[0] == NULL every time we need to fetch a new header */ + if (!smp->ctx.a[0]) { + if (!http_find_header2(hdr_name, hdr_name_len, sol, idx, ctx)) + goto out; + + if (ctx->vlen < args->data.str.data + 1) + continue; + + smp->ctx.a[0] = ctx->line + ctx->val; + smp->ctx.a[1] = smp->ctx.a[0] + ctx->vlen; + } + + smp->data.type = SMP_T_STR; + smp->flags |= SMP_F_CONST; + smp->ctx.a[0] = http_extract_cookie_value(smp->ctx.a[0], smp->ctx.a[1], + args->data.str.area, args->data.str.data, + (smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ, + &smp->data.u.str.area, &smp->data.u.str.data); + if (smp->ctx.a[0]) { + found = 1; + if (occ >= 0) { + /* one value was returned into smp->data.u.str.{str,len} */ + smp->flags |= SMP_F_NOT_LAST; + return 1; + } + } + /* if we're looking for last occurrence, let's loop */ + } + /* all cookie headers and values were scanned. If we're looking for the + * last occurrence, we may return it now. + */ + out: + smp->flags &= ~SMP_F_NOT_LAST; + return found; +} + +/* Iterate over all cookies present in a request to count how many occurrences + * match the name in args and args->data.str.len. If is non-null, then + * multiple cookies may be parsed on the same line. The returned sample is of + * type UINT. Accepts exactly 1 argument of type string. + */ +static int smp_fetch_cookie_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + struct hdr_idx *idx; + struct hdr_ctx ctx; + const struct http_msg *msg; + const char *hdr_name; + int hdr_name_len; + int cnt; + char *val_beg, *val_end; + char *sol; + + if (!args || args->type != ARGT_STR) + return 0; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + idx = &smp->strm->txn->hdr_idx; + + if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) { + msg = &txn->req; + hdr_name = "Cookie"; + hdr_name_len = 6; + } else { + msg = &txn->rsp; + hdr_name = "Set-Cookie"; + hdr_name_len = 10; + } + + sol = ci_head(msg->chn); + val_end = val_beg = NULL; + ctx.idx = 0; + cnt = 0; + + while (1) { + /* Note: val_beg == NULL every time we need to fetch a new header */ + if (!val_beg) { + if (!http_find_header2(hdr_name, hdr_name_len, sol, idx, &ctx)) + break; + + if (ctx.vlen < args->data.str.data + 1) + continue; + + val_beg = ctx.line + ctx.val; + val_end = val_beg + ctx.vlen; + } + + smp->data.type = SMP_T_STR; + smp->flags |= SMP_F_CONST; + while ((val_beg = http_extract_cookie_value(val_beg, val_end, + args->data.str.area, args->data.str.data, + (smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ, + &smp->data.u.str.area, &smp->data.u.str.data))) { + cnt++; + } + } + + smp->data.type = SMP_T_SINT; + smp->data.u.sint = cnt; + smp->flags |= SMP_F_VOL_HDR; + return 1; +} + +/* Fetch an cookie's integer value. The integer value is returned. It + * takes a mandatory argument of type string. It relies on smp_fetch_cookie(). + */ +static int smp_fetch_cookie_val(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + int ret = smp_fetch_cookie(args, smp, kw, private); + + if (ret > 0) { + smp->data.type = SMP_T_SINT; + smp->data.u.sint = strl2ic(smp->data.u.str.area, + smp->data.u.str.data); + } + + return ret; +} + +/************************************************************************/ +/* The code below is dedicated to sample fetches */ +/************************************************************************/ + +/* This scans a URL-encoded query string. It takes an optionally wrapping + * string whose first contigous chunk has its beginning in ctx->a[0] and end + * in ctx->a[1], and the optional second part in (ctx->a[2]..ctx->a[3]). The + * pointers are updated for next iteration before leaving. + */ +static int smp_fetch_param(char delim, const char *name, int name_len, const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + const char *vstart, *vend; + struct buffer *temp; + const char **chunks = (const char **)smp->ctx.a; + + if (!http_find_next_url_param(chunks, name, name_len, + &vstart, &vend, delim)) + return 0; + + /* Create sample. If the value is contiguous, return the pointer as CONST, + * if the value is wrapped, copy-it in a buffer. + */ + smp->data.type = SMP_T_STR; + if (chunks[2] && + vstart >= chunks[0] && vstart <= chunks[1] && + vend >= chunks[2] && vend <= chunks[3]) { + /* Wrapped case. */ + temp = get_trash_chunk(); + memcpy(temp->area, vstart, chunks[1] - vstart); + memcpy(temp->area + ( chunks[1] - vstart ), chunks[2], + vend - chunks[2]); + smp->data.u.str.area = temp->area; + smp->data.u.str.data = ( chunks[1] - vstart ) + ( vend - chunks[2] ); + } else { + /* Contiguous case. */ + smp->data.u.str.area = (char *)vstart; + smp->data.u.str.data = vend - vstart; + smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; + } + + /* Update context, check wrapping. */ + chunks[0] = vend; + if (chunks[2] && vend >= chunks[2] && vend <= chunks[3]) { + chunks[1] = chunks[3]; + chunks[2] = NULL; + } + + if (chunks[0] < chunks[1]) + smp->flags |= SMP_F_NOT_LAST; + + return 1; +} + +/* This function iterates over each parameter of the query string. It uses + * ctx->a[0] and ctx->a[1] to store the beginning and end of the current + * parameter. Since it uses smp_fetch_param(), ctx->a[2..3] are both NULL. + * An optional parameter name is passed in args[0], otherwise any parameter is + * considered. It supports an optional delimiter argument for the beginning of + * the string in args[1], which defaults to "?". + */ +static int smp_fetch_url_param(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_msg *msg; + char delim = '?'; + const char *name; + int name_len; + + if (!args || + (args[0].type && args[0].type != ARGT_STR) || + (args[1].type && args[1].type != ARGT_STR)) + return 0; + + name = ""; + name_len = 0; + if (args->type == ARGT_STR) { + name = args->data.str.area; + name_len = args->data.str.data; + } + + if (args[1].type) + delim = *args[1].data.str.area; + + if (!smp->ctx.a[0]) { // first call, find the query string + CHECK_HTTP_MESSAGE_FIRST(); + + msg = &smp->strm->txn->req; + + smp->ctx.a[0] = http_find_param_list(ci_head(msg->chn) + msg->sl.rq.u, + msg->sl.rq.u_l, delim); + if (!smp->ctx.a[0]) + return 0; + + smp->ctx.a[1] = ci_head(msg->chn) + msg->sl.rq.u + msg->sl.rq.u_l; + + /* Assume that the context is filled with NULL pointer + * before the first call. + * smp->ctx.a[2] = NULL; + * smp->ctx.a[3] = NULL; + */ + } + + return smp_fetch_param(delim, name, name_len, args, smp, kw, private); +} + +/* This function iterates over each parameter of the body. This requires + * that the body has been waited for using http-buffer-request. It uses + * ctx->a[0] and ctx->a[1] to store the beginning and end of the first + * contigous part of the body, and optionally ctx->a[2..3] to reference the + * optional second part if the body wraps at the end of the buffer. An optional + * parameter name is passed in args[0], otherwise any parameter is considered. + */ +static int smp_fetch_body_param(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_msg *msg; + unsigned long len; + unsigned long block1; + char *body; + const char *name; + int name_len; + + if (!args || (args[0].type && args[0].type != ARGT_STR)) + return 0; + + name = ""; + name_len = 0; + if (args[0].type == ARGT_STR) { + name = args[0].data.str.area; + name_len = args[0].data.str.data; + } + + if (!smp->ctx.a[0]) { // first call, find the query string + CHECK_HTTP_MESSAGE_FIRST(); + + if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) + msg = &smp->strm->txn->req; + else + msg = &smp->strm->txn->rsp; + + len = http_body_bytes(msg); + body = c_ptr(msg->chn, -http_data_rewind(msg)); + + block1 = len; + if (block1 > b_wrap(&msg->chn->buf) - body) + block1 = b_wrap(&msg->chn->buf) - body; + + if (block1 == len) { + /* buffer is not wrapped (or empty) */ + smp->ctx.a[0] = body; + smp->ctx.a[1] = body + len; + + /* Assume that the context is filled with NULL pointer + * before the first call. + * smp->ctx.a[2] = NULL; + * smp->ctx.a[3] = NULL; + */ + } + else { + /* buffer is wrapped, we need to defragment it */ + smp->ctx.a[0] = body; + smp->ctx.a[1] = body + block1; + smp->ctx.a[2] = b_orig(&msg->chn->buf); + smp->ctx.a[3] = b_orig(&msg->chn->buf) + ( len - block1 ); + } + } + return smp_fetch_param('&', name, name_len, args, smp, kw, private); +} + +/* Return the signed integer value for the specified url parameter (see url_param + * above). + */ +static int smp_fetch_url_param_val(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + int ret = smp_fetch_url_param(args, smp, kw, private); + + if (ret > 0) { + smp->data.type = SMP_T_SINT; + smp->data.u.sint = strl2ic(smp->data.u.str.area, + smp->data.u.str.data); + } + + return ret; +} + +/* This produces a 32-bit hash of the concatenation of the first occurrence of + * the Host header followed by the path component if it begins with a slash ('/'). + * This means that '*' will not be added, resulting in exactly the first Host + * entry. If no Host header is found, then the path is used. The resulting value + * is hashed using the url hash followed by a full avalanche hash and provides a + * 32-bit integer value. This fetch is useful for tracking per-URL activity on + * high-traffic sites without having to store whole paths. + * this differs from the base32 functions in that it includes the url parameters + * as well as the path + */ +static int smp_fetch_url32(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct http_txn *txn; + struct hdr_ctx ctx; + unsigned int hash = 0; + char *ptr, *beg, *end; + int len; + + CHECK_HTTP_MESSAGE_FIRST(); + + txn = smp->strm->txn; + ctx.idx = 0; + if (http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx)) { + /* OK we have the header value in ctx.line+ctx.val for ctx.vlen bytes */ + ptr = ctx.line + ctx.val; + len = ctx.vlen; + while (len--) + hash = *(ptr++) + (hash << 6) + (hash << 16) - hash; + } + + /* now retrieve the path */ + end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l; + beg = http_txn_get_path(txn); + if (!beg) + beg = end; + + for (ptr = beg; ptr < end ; ptr++); + + if (beg < ptr && *beg == '/') { + while (beg < ptr) + hash = *(beg++) + (hash << 6) + (hash << 16) - hash; + } + hash = full_hash(hash); + + smp->data.type = SMP_T_SINT; + smp->data.u.sint = hash; + smp->flags = SMP_F_VOL_1ST; + return 1; +} + +/* This concatenates the source address with the 32-bit hash of the Host and + * URL as returned by smp_fetch_base32(). The idea is to have per-source and + * per-url counters. The result is a binary block from 8 to 20 bytes depending + * on the source address length. The URL hash is stored before the address so + * that in environments where IPv6 is insignificant, truncating the output to + * 8 bytes would still work. + */ +static int smp_fetch_url32_src(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct buffer *temp; + struct connection *cli_conn = objt_conn(smp->sess->origin); + + if (!cli_conn) + return 0; + + if (!smp_fetch_url32(args, smp, kw, private)) + return 0; + + temp = get_trash_chunk(); + *(unsigned int *) temp->area = htonl(smp->data.u.sint); + temp->data += sizeof(unsigned int); + + switch (cli_conn->addr.from.ss_family) { + case AF_INET: + memcpy(temp->area + temp->data, + &((struct sockaddr_in *)&cli_conn->addr.from)->sin_addr, + 4); + temp->data += 4; + break; + case AF_INET6: + memcpy(temp->area + temp->data, + &((struct sockaddr_in6 *)&cli_conn->addr.from)->sin6_addr, + 16); + temp->data += 16; + break; + default: + return 0; + } + + smp->data.u.str = *temp; + smp->data.type = SMP_T_BIN; + return 1; +} + +/************************************************************************/ +/* Other utility functions */ +/************************************************************************/ + +/* This function is used to validate the arguments passed to any "hdr" fetch + * keyword. These keywords support an optional positive or negative occurrence + * number. We must ensure that the number is greater than -MAX_HDR_HISTORY. It + * is assumed that the types are already the correct ones. Returns 0 on error, + * non-zero if OK. If is not NULL, it will be filled with a pointer to an + * error message in case of error, that the caller is responsible for freeing. + * The initial location must either be freeable or NULL. + * Note: this function's pointer is checked from Lua. + */ +int val_hdr(struct arg *arg, char **err_msg) +{ + if (arg && arg[1].type == ARGT_SINT && arg[1].data.sint < -MAX_HDR_HISTORY) { + memprintf(err_msg, "header occurrence must be >= %d", -MAX_HDR_HISTORY); + return 0; + } + return 1; +} + +/************************************************************************/ +/* All supported sample fetch keywords must be declared here. */ +/************************************************************************/ + +/* Note: must not be declared as its list will be overwritten */ +static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { + { "base", smp_fetch_base, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, + { "base32", smp_fetch_base32, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV }, + { "base32+src", smp_fetch_base32_src, 0, NULL, SMP_T_BIN, SMP_USE_HRQHV }, + + /* capture are allocated and are permanent in the stream */ + { "capture.req.hdr", smp_fetch_capture_req_hdr, ARG1(1,SINT), NULL, SMP_T_STR, SMP_USE_HRQHP }, + + /* retrieve these captures from the HTTP logs */ + { "capture.req.method", smp_fetch_capture_req_method, 0, NULL, SMP_T_STR, SMP_USE_HRQHP }, + { "capture.req.uri", smp_fetch_capture_req_uri, 0, NULL, SMP_T_STR, SMP_USE_HRQHP }, + { "capture.req.ver", smp_fetch_capture_req_ver, 0, NULL, SMP_T_STR, SMP_USE_HRQHP }, + + { "capture.res.hdr", smp_fetch_capture_res_hdr, ARG1(1,SINT), NULL, SMP_T_STR, SMP_USE_HRSHP }, + { "capture.res.ver", smp_fetch_capture_res_ver, 0, NULL, SMP_T_STR, SMP_USE_HRQHP }, + + /* cookie is valid in both directions (eg: for "stick ...") but cook* + * are only here to match the ACL's name, are request-only and are used + * for ACL compatibility only. + */ + { "cook", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRQHV }, + { "cookie", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRQHV|SMP_USE_HRSHV }, + { "cook_cnt", smp_fetch_cookie_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, + { "cook_val", smp_fetch_cookie_val, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, + + /* hdr is valid in both directions (eg: for "stick ...") but hdr_* are + * only here to match the ACL's name, are request-only and are used for + * ACL compatibility only. + */ + { "hdr", smp_fetch_hdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRQHV|SMP_USE_HRSHV }, + { "hdr_cnt", smp_fetch_hdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, + { "hdr_ip", smp_fetch_hdr_ip, ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRQHV }, + { "hdr_val", smp_fetch_hdr_val, ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRQHV }, + + { "http_auth", smp_fetch_http_auth, ARG1(1,USR), NULL, SMP_T_BOOL, SMP_USE_HRQHV }, + { "http_auth_group", smp_fetch_http_auth_grp, ARG1(1,USR), NULL, SMP_T_STR, SMP_USE_HRQHV }, + { "http_first_req", smp_fetch_http_first_req, 0, NULL, SMP_T_BOOL, SMP_USE_HRQHP }, + { "method", smp_fetch_meth, 0, NULL, SMP_T_METH, SMP_USE_HRQHP }, + { "path", smp_fetch_path, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, + { "query", smp_fetch_query, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, + + /* HTTP protocol on the request path */ + { "req.proto_http", smp_fetch_proto_http, 0, NULL, SMP_T_BOOL, SMP_USE_HRQHP }, + { "req_proto_http", smp_fetch_proto_http, 0, NULL, SMP_T_BOOL, SMP_USE_HRQHP }, + + /* HTTP version on the request path */ + { "req.ver", smp_fetch_rqver, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, + { "req_ver", smp_fetch_rqver, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, + + { "req.body", smp_fetch_body, 0, NULL, SMP_T_BIN, SMP_USE_HRQHV }, + { "req.body_len", smp_fetch_body_len, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV }, + { "req.body_size", smp_fetch_body_size, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV }, + { "req.body_param", smp_fetch_body_param, ARG1(0,STR), NULL, SMP_T_BIN, SMP_USE_HRQHV }, + + { "req.hdrs", smp_fetch_hdrs, 0, NULL, SMP_T_BIN, SMP_USE_HRQHV }, + { "req.hdrs_bin", smp_fetch_hdrs_bin, 0, NULL, SMP_T_BIN, SMP_USE_HRQHV }, + + /* HTTP version on the response path */ + { "res.ver", smp_fetch_stver, 0, NULL, SMP_T_STR, SMP_USE_HRSHV }, + { "resp_ver", smp_fetch_stver, 0, NULL, SMP_T_STR, SMP_USE_HRSHV }, + + /* explicit req.{cook,hdr} are used to force the fetch direction to be request-only */ + { "req.cook", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRQHV }, + { "req.cook_cnt", smp_fetch_cookie_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, + { "req.cook_val", smp_fetch_cookie_val, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, + + { "req.fhdr", smp_fetch_fhdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRQHV }, + { "req.fhdr_cnt", smp_fetch_fhdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, + { "req.hdr", smp_fetch_hdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRQHV }, + { "req.hdr_cnt", smp_fetch_hdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, + { "req.hdr_ip", smp_fetch_hdr_ip, ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRQHV }, + { "req.hdr_names", smp_fetch_hdr_names, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRQHV }, + { "req.hdr_val", smp_fetch_hdr_val, ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRQHV }, + + /* explicit req.{cook,hdr} are used to force the fetch direction to be response-only */ + { "res.cook", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRSHV }, + { "res.cook_cnt", smp_fetch_cookie_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, + { "res.cook_val", smp_fetch_cookie_val, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, + + { "res.fhdr", smp_fetch_fhdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRSHV }, + { "res.fhdr_cnt", smp_fetch_fhdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, + { "res.hdr", smp_fetch_hdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRSHV }, + { "res.hdr_cnt", smp_fetch_hdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, + { "res.hdr_ip", smp_fetch_hdr_ip, ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRSHV }, + { "res.hdr_names", smp_fetch_hdr_names, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRSHV }, + { "res.hdr_val", smp_fetch_hdr_val, ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRSHV }, + + /* scook is valid only on the response and is used for ACL compatibility */ + { "scook", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRSHV }, + { "scook_cnt", smp_fetch_cookie_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, + { "scook_val", smp_fetch_cookie_val, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, + { "set-cookie", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRSHV }, /* deprecated */ + + /* shdr is valid only on the response and is used for ACL compatibility */ + { "shdr", smp_fetch_hdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRSHV }, + { "shdr_cnt", smp_fetch_hdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, + { "shdr_ip", smp_fetch_hdr_ip, ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRSHV }, + { "shdr_val", smp_fetch_hdr_val, ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRSHV }, + + { "status", smp_fetch_stcode, 0, NULL, SMP_T_SINT, SMP_USE_HRSHP }, + { "unique-id", smp_fetch_uniqueid, 0, NULL, SMP_T_STR, SMP_SRC_L4SRV }, + { "url", smp_fetch_url, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, + { "url32", smp_fetch_url32, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV }, + { "url32+src", smp_fetch_url32_src, 0, NULL, SMP_T_BIN, SMP_USE_HRQHV }, + { "url_ip", smp_fetch_url_ip, 0, NULL, SMP_T_IPV4, SMP_USE_HRQHV }, + { "url_port", smp_fetch_url_port, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV }, + { "url_param", smp_fetch_url_param, ARG2(0,STR,STR), NULL, SMP_T_STR, SMP_USE_HRQHV }, + { "urlp" , smp_fetch_url_param, ARG2(0,STR,STR), NULL, SMP_T_STR, SMP_USE_HRQHV }, + { "urlp_val", smp_fetch_url_param_val, ARG2(0,STR,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, + { /* END */ }, +}}; + + +__attribute__((constructor)) +static void __http_fetch_init(void) +{ + sample_register_fetches(&sample_fetch_keywords); +} + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/proto_http.c b/src/proto_http.c index 9f2ccc01de..94e672d11d 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -98,9 +98,6 @@ struct action_kw_list http_res_keywords = { .list = LIST_HEAD_INIT(http_res_keywords.list) }; -/* this struct is used between calls to smp_fetch_hdr() or smp_fetch_cookie() */ -static THREAD_LOCAL struct hdr_ctx static_hdr_ctx; - static int http_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struct http_txn *txn); static inline int http_msg_forward_body(struct stream *s, struct http_msg *msg); @@ -666,94 +663,11 @@ void capture_headers(char *som, struct hdr_idx *idx, } } -/* - * Returns the data from Authorization header. Function may be called more - * than once so data is stored in txn->auth_data. When no header is found - * or auth method is unknown auth_method is set to HTTP_AUTH_WRONG to avoid - * searching again for something we are unable to find anyway. However, if - * the result if valid, the cache is not reused because we would risk to - * have the credentials overwritten by another stream in parallel. - */ - -int -get_http_auth(struct stream *s) -{ - - struct http_txn *txn = s->txn; - struct buffer auth_method; - struct hdr_ctx ctx; - char *h, *p; - int len; - -#ifdef DEBUG_AUTH - printf("Auth for stream %p: %d\n", s, txn->auth.method); -#endif - - if (txn->auth.method == HTTP_AUTH_WRONG) - return 0; - - txn->auth.method = HTTP_AUTH_WRONG; - - ctx.idx = 0; - - if (txn->flags & TX_USE_PX_CONN) { - h = "Proxy-Authorization"; - len = strlen(h); - } else { - h = "Authorization"; - len = strlen(h); - } - - if (!http_find_header2(h, len, ci_head(&s->req), &txn->hdr_idx, &ctx)) - return 0; - - h = ctx.line + ctx.val; - - p = memchr(h, ' ', ctx.vlen); - len = p - h; - if (!p || len <= 0) - return 0; - - if (chunk_initlen(&auth_method, h, 0, len) != 1) - return 0; - - chunk_initlen(&txn->auth.method_data, p + 1, 0, ctx.vlen - len - 1); - - if (!strncasecmp("Basic", auth_method.area, auth_method.data)) { - struct buffer *http_auth = get_trash_chunk(); - - len = base64dec(txn->auth.method_data.area, - txn->auth.method_data.data, - http_auth->area, global.tune.bufsize - 1); - - if (len < 0) - return 0; - - - http_auth->area[len] = '\0'; - - p = strchr(http_auth->area, ':'); - - if (!p) - return 0; - - txn->auth.user = http_auth->area; - *p = '\0'; - txn->auth.pass = p+1; - - txn->auth.method = HTTP_AUTH_BASIC; - return 1; - } - - return 0; -} - - /* convert an HTTP/0.9 request into an HTTP/1.0 request. Returns 1 if the * conversion succeeded, 0 in case of error. If the request was already 1.X, * nothing is done and 1 is returned. */ -static int http_upgrade_v09_to_v10(struct http_txn *txn) +int http_upgrade_v09_to_v10(struct http_txn *txn) { int delta; char *cur_end; @@ -8888,2092 +8802,6 @@ struct redirect_rule *http_parse_redirect_rule(const char *file, int linenum, st return NULL; } -/************************************************************************/ -/* The code below is dedicated to ACL parsing and matching */ -/************************************************************************/ - - -/* This function ensures that the prerequisites for an L7 fetch are ready, - * which means that a request or response is ready. If some data is missing, - * a parsing attempt is made. This is useful in TCP-based ACLs which are able - * to extract data from L7. If is non-null during a request prefetch, - * another test is made to ensure the required information is not gone. - * - * The function returns : - * 0 with SMP_F_MAY_CHANGE in the sample flags if some data is missing to - * decide whether or not an HTTP message is present ; - * 0 if the requested data cannot be fetched or if it is certain that - * we'll never have any HTTP message there ; - * 1 if an HTTP message is ready - */ -int smp_prefetch_http(struct proxy *px, struct stream *s, unsigned int opt, - const struct arg *args, struct sample *smp, int req_vol) -{ - struct http_txn *txn; - struct http_msg *msg; - - /* Note: it is possible that is NULL when called before stream - * initialization (eg: tcp-request connection), so this function is the - * one responsible for guarding against this case for all HTTP users. - */ - if (!s) - return 0; - - if (!s->txn) { - if (unlikely(!http_alloc_txn(s))) - return 0; /* not enough memory */ - http_init_txn(s); - } - txn = s->txn; - msg = &txn->req; - - /* Check for a dependency on a request */ - smp->data.type = SMP_T_BOOL; - - if ((opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) { - /* If the buffer does not leave enough free space at the end, - * we must first realign it. - */ - if (ci_head(&s->req) > b_orig(&s->req.buf) && - ci_head(&s->req) + ci_data(&s->req) > b_wrap(&s->req.buf) - global.tune.maxrewrite) - channel_slow_realign(&s->req, trash.area); - - if (unlikely(txn->req.msg_state < HTTP_MSG_BODY)) { - if (msg->msg_state == HTTP_MSG_ERROR) - return 0; - - /* Try to decode HTTP request */ - if (likely(msg->next < ci_data(&s->req))) - http_msg_analyzer(msg, &txn->hdr_idx); - - /* Still no valid request ? */ - if (unlikely(msg->msg_state < HTTP_MSG_BODY)) { - if ((msg->msg_state == HTTP_MSG_ERROR) || - channel_full(&s->req, global.tune.maxrewrite)) { - return 0; - } - /* wait for final state */ - smp->flags |= SMP_F_MAY_CHANGE; - return 0; - } - - /* OK we just got a valid HTTP request. We have some minor - * preparation to perform so that further checks can rely - * on HTTP tests. - */ - - /* If the request was parsed but was too large, we must absolutely - * return an error so that it is not processed. At the moment this - * cannot happen, but if the parsers are to change in the future, - * we want this check to be maintained. - */ - if (unlikely(ci_head(&s->req) + ci_data(&s->req) > - b_wrap(&s->req.buf) - global.tune.maxrewrite)) { - msg->err_state = msg->msg_state; - msg->msg_state = HTTP_MSG_ERROR; - smp->data.u.sint = 1; - return 1; - } - - txn->meth = find_http_meth(ci_head(msg->chn), msg->sl.rq.m_l); - if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD) - s->flags |= SF_REDIRECTABLE; - - if (unlikely(msg->sl.rq.v_l == 0) && !http_upgrade_v09_to_v10(txn)) - return 0; - } - - if (req_vol && txn->rsp.msg_state != HTTP_MSG_RPBEFORE) { - return 0; /* data might have moved and indexes changed */ - } - - /* otherwise everything's ready for the request */ - } - else { - /* Check for a dependency on a response */ - if (txn->rsp.msg_state < HTTP_MSG_BODY) { - smp->flags |= SMP_F_MAY_CHANGE; - return 0; - } - } - - /* everything's OK */ - smp->data.u.sint = 1; - return 1; -} - -/* 1. Check on METHOD - * We use the pre-parsed method if it is known, and store its number as an - * integer. If it is unknown, we use the pointer and the length. - */ -static int pat_parse_meth(const char *text, struct pattern *pattern, int mflags, char **err) -{ - int len, meth; - - len = strlen(text); - meth = find_http_meth(text, len); - - pattern->val.i = meth; - if (meth == HTTP_METH_OTHER) { - pattern->ptr.str = (char *)text; - pattern->len = len; - } - else { - pattern->ptr.str = NULL; - pattern->len = 0; - } - return 1; -} - -/* This function fetches the method of current HTTP request and stores - * it in the global pattern struct as a chunk. There are two possibilities : - * - if the method is known (not HTTP_METH_OTHER), its identifier is stored - * in and is NULL ; - * - if the method is unknown (HTTP_METH_OTHER), points to the text and - * to its length. - * This is intended to be used with pat_match_meth() only. - */ -static int -smp_fetch_meth(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - int meth; - struct http_txn *txn; - - CHECK_HTTP_MESSAGE_FIRST_PERM(); - - txn = smp->strm->txn; - meth = txn->meth; - smp->data.type = SMP_T_METH; - smp->data.u.meth.meth = meth; - if (meth == HTTP_METH_OTHER) { - if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE) - /* ensure the indexes are not affected */ - return 0; - smp->flags |= SMP_F_CONST; - smp->data.u.meth.str.data = txn->req.sl.rq.m_l; - smp->data.u.meth.str.area = ci_head(txn->req.chn); - } - smp->flags |= SMP_F_VOL_1ST; - return 1; -} - -/* See above how the method is stored in the global pattern */ -static struct pattern *pat_match_meth(struct sample *smp, struct pattern_expr *expr, int fill) -{ - int icase; - struct pattern_list *lst; - struct pattern *pattern; - - list_for_each_entry(lst, &expr->patterns, list) { - pattern = &lst->pat; - - /* well-known method */ - if (pattern->val.i != HTTP_METH_OTHER) { - if (smp->data.u.meth.meth == pattern->val.i) - return pattern; - else - continue; - } - - /* Other method, we must compare the strings */ - if (pattern->len != smp->data.u.meth.str.data) - continue; - - icase = expr->mflags & PAT_MF_IGNORE_CASE; - if ((icase && strncasecmp(pattern->ptr.str, smp->data.u.meth.str.area, smp->data.u.meth.str.data) == 0) || - (!icase && strncmp(pattern->ptr.str, smp->data.u.meth.str.area, smp->data.u.meth.str.data) == 0)) - return pattern; - } - return NULL; -} - -static int -smp_fetch_rqver(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - char *ptr; - int len; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - len = txn->req.sl.rq.v_l; - ptr = ci_head(txn->req.chn) + txn->req.sl.rq.v; - - while ((len-- > 0) && (*ptr++ != '/')); - if (len <= 0) - return 0; - - smp->data.type = SMP_T_STR; - smp->data.u.str.area = ptr; - smp->data.u.str.data = len; - - smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; - return 1; -} - -static int -smp_fetch_stver(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - char *ptr; - int len; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - if (txn->rsp.msg_state < HTTP_MSG_BODY) - return 0; - - len = txn->rsp.sl.st.v_l; - ptr = ci_head(txn->rsp.chn); - - while ((len-- > 0) && (*ptr++ != '/')); - if (len <= 0) - return 0; - - smp->data.type = SMP_T_STR; - smp->data.u.str.area = ptr; - smp->data.u.str.data = len; - - smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; - return 1; -} - -/* 3. Check on Status Code. We manipulate integers here. */ -static int -smp_fetch_stcode(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - char *ptr; - int len; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - if (txn->rsp.msg_state < HTTP_MSG_BODY) - return 0; - - len = txn->rsp.sl.st.c_l; - ptr = ci_head(txn->rsp.chn) + txn->rsp.sl.st.c; - - smp->data.type = SMP_T_SINT; - smp->data.u.sint = __strl2ui(ptr, len); - smp->flags = SMP_F_VOL_1ST; - return 1; -} - -static int -smp_fetch_uniqueid(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - if (LIST_ISEMPTY(&smp->sess->fe->format_unique_id)) - return 0; - - if (!smp->strm->unique_id) { - if ((smp->strm->unique_id = pool_alloc(pool_head_uniqueid)) == NULL) - return 0; - smp->strm->unique_id[0] = '\0'; - } - smp->data.u.str.data = build_logline(smp->strm, smp->strm->unique_id, - UNIQUEID_LEN, &smp->sess->fe->format_unique_id); - - smp->data.type = SMP_T_STR; - smp->data.u.str.area = smp->strm->unique_id; - smp->flags = SMP_F_CONST; - return 1; -} - -/* Returns a string block containing all headers including the - * empty line wich separes headers from the body. This is useful - * form some headers analysis. - */ -static int -smp_fetch_hdrs(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_msg *msg; - struct hdr_idx *idx; - struct http_txn *txn; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - idx = &txn->hdr_idx; - msg = &txn->req; - - smp->data.type = SMP_T_STR; - smp->data.u.str.area = ci_head(msg->chn) + hdr_idx_first_pos(idx); - smp->data.u.str.data = msg->eoh - hdr_idx_first_pos(idx) + 1 + - (ci_head(msg->chn)[msg->eoh] == '\r'); - - return 1; -} - -/* Returns the header request in a length/value encoded format. - * This is useful for exchanges with the SPOE. - * - * A "length value" is a multibyte code encoding numbers. It uses the - * SPOE format. The encoding is the following: - * - * Each couple "header name" / "header value" is composed - * like this: - * "length value" "header name bytes" - * "length value" "header value bytes" - * When the last header is reached, the header name and the header - * value are empty. Their length are 0 - */ -static int -smp_fetch_hdrs_bin(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_msg *msg; - struct buffer *temp; - struct hdr_idx *idx; - const char *cur_ptr, *cur_next, *p; - int old_idx, cur_idx; - struct hdr_idx_elem *cur_hdr; - const char *hn, *hv; - int hnl, hvl; - int ret; - struct http_txn *txn; - char *buf; - char *end; - - CHECK_HTTP_MESSAGE_FIRST(); - - temp = get_trash_chunk(); - buf = temp->area; - end = temp->area + temp->size; - - txn = smp->strm->txn; - idx = &txn->hdr_idx; - msg = &txn->req; - - /* Build array of headers. */ - old_idx = 0; - cur_next = ci_head(msg->chn) + hdr_idx_first_pos(idx); - while (1) { - cur_idx = idx->v[old_idx].next; - if (!cur_idx) - break; - old_idx = cur_idx; - - cur_hdr = &idx->v[cur_idx]; - cur_ptr = cur_next; - cur_next = cur_ptr + cur_hdr->len + cur_hdr->cr + 1; - - /* Now we have one full header at cur_ptr of len cur_hdr->len, - * and the next header starts at cur_next. We'll check - * this header in the list as well as against the default - * rule. - */ - - /* look for ': *'. */ - hn = cur_ptr; - for (p = cur_ptr; p < cur_ptr + cur_hdr->len && *p != ':'; p++); - if (p >= cur_ptr+cur_hdr->len) - continue; - hnl = p - hn; - p++; - while (p < cur_ptr + cur_hdr->len && (*p == ' ' || *p == '\t')) - p++; - if (p >= cur_ptr + cur_hdr->len) - continue; - hv = p; - hvl = cur_ptr + cur_hdr->len-p; - - /* encode the header name. */ - ret = encode_varint(hnl, &buf, end); - if (ret == -1) - return 0; - if (buf + hnl > end) - return 0; - memcpy(buf, hn, hnl); - buf += hnl; - - /* encode and copy the value. */ - ret = encode_varint(hvl, &buf, end); - if (ret == -1) - return 0; - if (buf + hvl > end) - return 0; - memcpy(buf, hv, hvl); - buf += hvl; - } - - /* encode the end of the header list with empty - * header name and header value. - */ - ret = encode_varint(0, &buf, end); - if (ret == -1) - return 0; - ret = encode_varint(0, &buf, end); - if (ret == -1) - return 0; - - /* Initialise sample data which will be filled. */ - smp->data.type = SMP_T_BIN; - smp->data.u.str.area = temp->area; - smp->data.u.str.data = buf - temp->area; - smp->data.u.str.size = temp->size; - - return 1; -} - -/* returns the longest available part of the body. This requires that the body - * has been waited for using http-buffer-request. - */ -static int -smp_fetch_body(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_msg *msg; - unsigned long len; - unsigned long block1; - char *body; - struct buffer *temp; - - CHECK_HTTP_MESSAGE_FIRST(); - - if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) - msg = &smp->strm->txn->req; - else - msg = &smp->strm->txn->rsp; - - len = http_body_bytes(msg); - body = c_ptr(msg->chn, -http_data_rewind(msg)); - - block1 = len; - if (block1 > b_wrap(&msg->chn->buf) - body) - block1 = b_wrap(&msg->chn->buf) - body; - - if (block1 == len) { - /* buffer is not wrapped (or empty) */ - smp->data.type = SMP_T_BIN; - smp->data.u.str.area = body; - smp->data.u.str.data = len; - smp->flags = SMP_F_VOL_TEST | SMP_F_CONST; - } - else { - /* buffer is wrapped, we need to defragment it */ - temp = get_trash_chunk(); - memcpy(temp->area, body, block1); - memcpy(temp->area + block1, b_orig(&msg->chn->buf), - len - block1); - smp->data.type = SMP_T_BIN; - smp->data.u.str.area = temp->area; - smp->data.u.str.data = len; - smp->flags = SMP_F_VOL_TEST; - } - return 1; -} - - -/* returns the available length of the body. This requires that the body - * has been waited for using http-buffer-request. - */ -static int -smp_fetch_body_len(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_msg *msg; - - CHECK_HTTP_MESSAGE_FIRST(); - - if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) - msg = &smp->strm->txn->req; - else - msg = &smp->strm->txn->rsp; - - smp->data.type = SMP_T_SINT; - smp->data.u.sint = http_body_bytes(msg); - - smp->flags = SMP_F_VOL_TEST; - return 1; -} - - -/* returns the advertised length of the body, or the advertised size of the - * chunks available in the buffer. This requires that the body has been waited - * for using http-buffer-request. - */ -static int -smp_fetch_body_size(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_msg *msg; - - CHECK_HTTP_MESSAGE_FIRST(); - - if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) - msg = &smp->strm->txn->req; - else - msg = &smp->strm->txn->rsp; - - smp->data.type = SMP_T_SINT; - smp->data.u.sint = msg->body_len; - - smp->flags = SMP_F_VOL_TEST; - return 1; -} - - -/* 4. Check on URL/URI. A pointer to the URI is stored. */ -static int -smp_fetch_url(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - smp->data.type = SMP_T_STR; - smp->data.u.str.data = txn->req.sl.rq.u_l; - smp->data.u.str.area = ci_head(txn->req.chn) + txn->req.sl.rq.u; - smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; - return 1; -} - -static int -smp_fetch_url_ip(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - struct sockaddr_storage addr; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - url2sa(ci_head(txn->req.chn) + txn->req.sl.rq.u, txn->req.sl.rq.u_l, &addr, NULL); - if (((struct sockaddr_in *)&addr)->sin_family != AF_INET) - return 0; - - smp->data.type = SMP_T_IPV4; - smp->data.u.ipv4 = ((struct sockaddr_in *)&addr)->sin_addr; - smp->flags = 0; - return 1; -} - -static int -smp_fetch_url_port(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - struct sockaddr_storage addr; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - url2sa(ci_head(txn->req.chn) + txn->req.sl.rq.u, txn->req.sl.rq.u_l, &addr, NULL); - if (((struct sockaddr_in *)&addr)->sin_family != AF_INET) - return 0; - - smp->data.type = SMP_T_SINT; - smp->data.u.sint = ntohs(((struct sockaddr_in *)&addr)->sin_port); - smp->flags = 0; - return 1; -} - -/* Fetch an HTTP header. A pointer to the beginning of the value is returned. - * Accepts an optional argument of type string containing the header field name, - * and an optional argument of type signed or unsigned integer to request an - * explicit occurrence of the header. Note that in the event of a missing name, - * headers are considered from the first one. It does not stop on commas and - * returns full lines instead (useful for User-Agent or Date for example). - */ -static int -smp_fetch_fhdr(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct hdr_idx *idx; - struct hdr_ctx *ctx = smp->ctx.a[0]; - const struct http_msg *msg; - int occ = 0; - const char *name_str = NULL; - int name_len = 0; - - if (!ctx) { - /* first call */ - ctx = &static_hdr_ctx; - ctx->idx = 0; - smp->ctx.a[0] = ctx; - } - - if (args) { - if (args[0].type != ARGT_STR) - return 0; - name_str = args[0].data.str.area; - name_len = args[0].data.str.data; - - if (args[1].type == ARGT_SINT) - occ = args[1].data.sint; - } - - CHECK_HTTP_MESSAGE_FIRST(); - - idx = &smp->strm->txn->hdr_idx; - msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp; - - if (ctx && !(smp->flags & SMP_F_NOT_LAST)) - /* search for header from the beginning */ - ctx->idx = 0; - - if (!occ && !(smp->opt & SMP_OPT_ITERATE)) - /* no explicit occurrence and single fetch => last header by default */ - occ = -1; - - if (!occ) - /* prepare to report multiple occurrences for ACL fetches */ - smp->flags |= SMP_F_NOT_LAST; - - smp->data.type = SMP_T_STR; - smp->flags |= SMP_F_VOL_HDR | SMP_F_CONST; - if (http_get_fhdr(msg, name_str, name_len, idx, occ, ctx, &smp->data.u.str.area, &smp->data.u.str.data)) - return 1; - - smp->flags &= ~SMP_F_NOT_LAST; - return 0; -} - -/* 6. Check on HTTP header count. The number of occurrences is returned. - * Accepts exactly 1 argument of type string. It does not stop on commas and - * returns full lines instead (useful for User-Agent or Date for example). - */ -static int -smp_fetch_fhdr_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct hdr_idx *idx; - struct hdr_ctx ctx; - const struct http_msg *msg; - int cnt; - const char *name = NULL; - int len = 0; - - if (args && args->type == ARGT_STR) { - name = args->data.str.area; - len = args->data.str.data; - } - - CHECK_HTTP_MESSAGE_FIRST(); - - idx = &smp->strm->txn->hdr_idx; - msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp; - - ctx.idx = 0; - cnt = 0; - while (http_find_full_header2(name, len, ci_head(msg->chn), idx, &ctx)) - cnt++; - - smp->data.type = SMP_T_SINT; - smp->data.u.sint = cnt; - smp->flags = SMP_F_VOL_HDR; - return 1; -} - -static int -smp_fetch_hdr_names(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct hdr_idx *idx; - struct hdr_ctx ctx; - const struct http_msg *msg; - struct buffer *temp; - char del = ','; - - if (args && args->type == ARGT_STR) - del = *args[0].data.str.area; - - CHECK_HTTP_MESSAGE_FIRST(); - - idx = &smp->strm->txn->hdr_idx; - msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp; - - temp = get_trash_chunk(); - - ctx.idx = 0; - while (http_find_next_header(ci_head(msg->chn), idx, &ctx)) { - if (temp->data) - temp->area[temp->data++] = del; - memcpy(temp->area + temp->data, ctx.line, ctx.del); - temp->data += ctx.del; - } - - smp->data.type = SMP_T_STR; - smp->data.u.str.area = temp->area; - smp->data.u.str.data = temp->data; - smp->flags = SMP_F_VOL_HDR; - return 1; -} - -/* Fetch an HTTP header. A pointer to the beginning of the value is returned. - * Accepts an optional argument of type string containing the header field name, - * and an optional argument of type signed or unsigned integer to request an - * explicit occurrence of the header. Note that in the event of a missing name, - * headers are considered from the first one. - */ -static int -smp_fetch_hdr(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct hdr_idx *idx; - struct hdr_ctx *ctx = smp->ctx.a[0]; - const struct http_msg *msg; - int occ = 0; - const char *name_str = NULL; - int name_len = 0; - - if (!ctx) { - /* first call */ - ctx = &static_hdr_ctx; - ctx->idx = 0; - smp->ctx.a[0] = ctx; - } - - if (args) { - if (args[0].type != ARGT_STR) - return 0; - name_str = args[0].data.str.area; - name_len = args[0].data.str.data; - - if (args[1].type == ARGT_SINT) - occ = args[1].data.sint; - } - - CHECK_HTTP_MESSAGE_FIRST(); - - idx = &smp->strm->txn->hdr_idx; - msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp; - - if (ctx && !(smp->flags & SMP_F_NOT_LAST)) - /* search for header from the beginning */ - ctx->idx = 0; - - if (!occ && !(smp->opt & SMP_OPT_ITERATE)) - /* no explicit occurrence and single fetch => last header by default */ - occ = -1; - - if (!occ) - /* prepare to report multiple occurrences for ACL fetches */ - smp->flags |= SMP_F_NOT_LAST; - - smp->data.type = SMP_T_STR; - smp->flags |= SMP_F_VOL_HDR | SMP_F_CONST; - if (http_get_hdr(msg, name_str, name_len, idx, occ, ctx, &smp->data.u.str.area, &smp->data.u.str.data)) - return 1; - - smp->flags &= ~SMP_F_NOT_LAST; - return 0; -} - -/* 6. Check on HTTP header count. The number of occurrences is returned. - * Accepts exactly 1 argument of type string. - */ -static int -smp_fetch_hdr_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct hdr_idx *idx; - struct hdr_ctx ctx; - const struct http_msg *msg; - int cnt; - const char *name = NULL; - int len = 0; - - if (args && args->type == ARGT_STR) { - name = args->data.str.area; - len = args->data.str.data; - } - - CHECK_HTTP_MESSAGE_FIRST(); - - idx = &smp->strm->txn->hdr_idx; - msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp; - - ctx.idx = 0; - cnt = 0; - while (http_find_header2(name, len, ci_head(msg->chn), idx, &ctx)) - cnt++; - - smp->data.type = SMP_T_SINT; - smp->data.u.sint = cnt; - smp->flags = SMP_F_VOL_HDR; - return 1; -} - -/* Fetch an HTTP header's integer value. The integer value is returned. It - * takes a mandatory argument of type string and an optional one of type int - * to designate a specific occurrence. It returns an unsigned integer, which - * may or may not be appropriate for everything. - */ -static int -smp_fetch_hdr_val(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - int ret = smp_fetch_hdr(args, smp, kw, private); - - if (ret > 0) { - smp->data.type = SMP_T_SINT; - smp->data.u.sint = strl2ic(smp->data.u.str.area, - smp->data.u.str.data); - } - - return ret; -} - -/* Fetch an HTTP header's IP value. takes a mandatory argument of type string - * and an optional one of type int to designate a specific occurrence. - * It returns an IPv4 or IPv6 address. - */ -static int -smp_fetch_hdr_ip(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - int ret; - - while ((ret = smp_fetch_hdr(args, smp, kw, private)) > 0) { - if (url2ipv4((char *) smp->data.u.str.area, &smp->data.u.ipv4)) { - smp->data.type = SMP_T_IPV4; - break; - } else { - struct buffer *temp = get_trash_chunk(); - if (smp->data.u.str.data < temp->size - 1) { - memcpy(temp->area, smp->data.u.str.area, - smp->data.u.str.data); - temp->area[smp->data.u.str.data] = '\0'; - if (inet_pton(AF_INET6, temp->area, &smp->data.u.ipv6)) { - smp->data.type = SMP_T_IPV6; - break; - } - } - } - - /* if the header doesn't match an IP address, fetch next one */ - if (!(smp->flags & SMP_F_NOT_LAST)) - return 0; - } - return ret; -} - -/* 8. Check on URI PATH. A pointer to the PATH is stored. The path starts at - * the first '/' after the possible hostname, and ends before the possible '?'. - */ -static int -smp_fetch_path(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - char *ptr, *end; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l; - ptr = http_txn_get_path(txn); - if (!ptr) - return 0; - - /* OK, we got the '/' ! */ - smp->data.type = SMP_T_STR; - smp->data.u.str.area = ptr; - - while (ptr < end && *ptr != '?') - ptr++; - - smp->data.u.str.data = ptr - smp->data.u.str.area; - smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; - return 1; -} - -/* This produces a concatenation of the first occurrence of the Host header - * followed by the path component if it begins with a slash ('/'). This means - * that '*' will not be added, resulting in exactly the first Host entry. - * If no Host header is found, then the path is returned as-is. The returned - * value is stored in the trash so it does not need to be marked constant. - * The returned sample is of type string. - */ -static int -smp_fetch_base(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - char *ptr, *end, *beg; - struct hdr_ctx ctx; - struct buffer *temp; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - ctx.idx = 0; - if (!http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx) || !ctx.vlen) - return smp_fetch_path(args, smp, kw, private); - - /* OK we have the header value in ctx.line+ctx.val for ctx.vlen bytes */ - temp = get_trash_chunk(); - memcpy(temp->area, ctx.line + ctx.val, ctx.vlen); - smp->data.type = SMP_T_STR; - smp->data.u.str.area = temp->area; - smp->data.u.str.data = ctx.vlen; - - /* now retrieve the path */ - end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l; - beg = http_txn_get_path(txn); - if (!beg) - beg = end; - - for (ptr = beg; ptr < end && *ptr != '?'; ptr++); - - if (beg < ptr && *beg == '/') { - memcpy(smp->data.u.str.area + smp->data.u.str.data, beg, - ptr - beg); - smp->data.u.str.data += ptr - beg; - } - - smp->flags = SMP_F_VOL_1ST; - return 1; -} - -/* This produces a 32-bit hash of the concatenation of the first occurrence of - * the Host header followed by the path component if it begins with a slash ('/'). - * This means that '*' will not be added, resulting in exactly the first Host - * entry. If no Host header is found, then the path is used. The resulting value - * is hashed using the path hash followed by a full avalanche hash and provides a - * 32-bit integer value. This fetch is useful for tracking per-path activity on - * high-traffic sites without having to store whole paths. - */ -int -smp_fetch_base32(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - struct hdr_ctx ctx; - unsigned int hash = 0; - char *ptr, *beg, *end; - int len; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - ctx.idx = 0; - if (http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx)) { - /* OK we have the header value in ctx.line+ctx.val for ctx.vlen bytes */ - ptr = ctx.line + ctx.val; - len = ctx.vlen; - while (len--) - hash = *(ptr++) + (hash << 6) + (hash << 16) - hash; - } - - /* now retrieve the path */ - end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l; - beg = http_txn_get_path(txn); - if (!beg) - beg = end; - - for (ptr = beg; ptr < end && *ptr != '?'; ptr++); - - if (beg < ptr && *beg == '/') { - while (beg < ptr) - hash = *(beg++) + (hash << 6) + (hash << 16) - hash; - } - hash = full_hash(hash); - - smp->data.type = SMP_T_SINT; - smp->data.u.sint = hash; - smp->flags = SMP_F_VOL_1ST; - return 1; -} - -/* This concatenates the source address with the 32-bit hash of the Host and - * path as returned by smp_fetch_base32(). The idea is to have per-source and - * per-path counters. The result is a binary block from 8 to 20 bytes depending - * on the source address length. The path hash is stored before the address so - * that in environments where IPv6 is insignificant, truncating the output to - * 8 bytes would still work. - */ -static int -smp_fetch_base32_src(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct buffer *temp; - struct connection *cli_conn = objt_conn(smp->sess->origin); - - if (!cli_conn) - return 0; - - if (!smp_fetch_base32(args, smp, kw, private)) - return 0; - - temp = get_trash_chunk(); - *(unsigned int *) temp->area = htonl(smp->data.u.sint); - temp->data += sizeof(unsigned int); - - switch (cli_conn->addr.from.ss_family) { - case AF_INET: - memcpy(temp->area + temp->data, - &((struct sockaddr_in *)&cli_conn->addr.from)->sin_addr, - 4); - temp->data += 4; - break; - case AF_INET6: - memcpy(temp->area + temp->data, - &((struct sockaddr_in6 *)&cli_conn->addr.from)->sin6_addr, - 16); - temp->data += 16; - break; - default: - return 0; - } - - smp->data.u.str = *temp; - smp->data.type = SMP_T_BIN; - return 1; -} - -/* Extracts the query string, which comes after the question mark '?'. If no - * question mark is found, nothing is returned. Otherwise it returns a sample - * of type string carrying the whole query string. - */ -static int -smp_fetch_query(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - char *ptr, *end; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - ptr = ci_head(txn->req.chn) + txn->req.sl.rq.u; - end = ptr + txn->req.sl.rq.u_l; - - /* look up the '?' */ - do { - if (ptr == end) - return 0; - } while (*ptr++ != '?'); - - smp->data.type = SMP_T_STR; - smp->data.u.str.area = ptr; - smp->data.u.str.data = end - ptr; - smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; - return 1; -} - -static int -smp_fetch_proto_http(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - /* Note: hdr_idx.v cannot be NULL in this ACL because the ACL is tagged - * as a layer7 ACL, which involves automatic allocation of hdr_idx. - */ - - CHECK_HTTP_MESSAGE_FIRST_PERM(); - - smp->data.type = SMP_T_BOOL; - smp->data.u.sint = 1; - return 1; -} - -/* return a valid test if the current request is the first one on the connection */ -static int -smp_fetch_http_first_req(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - smp->data.type = SMP_T_BOOL; - smp->data.u.sint = !(smp->strm->txn->flags & TX_NOT_FIRST); - return 1; -} - -/* Accepts exactly 1 argument of type userlist */ -static int -smp_fetch_http_auth(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - - if (!args || args->type != ARGT_USR) - return 0; - - CHECK_HTTP_MESSAGE_FIRST(); - - if (!get_http_auth(smp->strm)) - return 0; - - smp->data.type = SMP_T_BOOL; - smp->data.u.sint = check_user(args->data.usr, smp->strm->txn->auth.user, - smp->strm->txn->auth.pass); - return 1; -} - -/* Accepts exactly 1 argument of type userlist */ -static int -smp_fetch_http_auth_grp(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - if (!args || args->type != ARGT_USR) - return 0; - - CHECK_HTTP_MESSAGE_FIRST(); - - if (!get_http_auth(smp->strm)) - return 0; - - /* if the user does not belong to the userlist or has a wrong password, - * report that it unconditionally does not match. Otherwise we return - * a string containing the username. - */ - if (!check_user(args->data.usr, smp->strm->txn->auth.user, - smp->strm->txn->auth.pass)) - return 0; - - /* pat_match_auth() will need the user list */ - smp->ctx.a[0] = args->data.usr; - - smp->data.type = SMP_T_STR; - smp->flags = SMP_F_CONST; - smp->data.u.str.area = smp->strm->txn->auth.user; - smp->data.u.str.data = strlen(smp->strm->txn->auth.user); - - return 1; -} - -/* Fetch a captured HTTP request header. The index is the position of - * the "capture" option in the configuration file - */ -static int -smp_fetch_capture_header_req(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct proxy *fe = strm_fe(smp->strm); - int idx; - - if (!args || args->type != ARGT_SINT) - return 0; - - idx = args->data.sint; - - if (idx > (fe->nb_req_cap - 1) || smp->strm->req_cap == NULL || smp->strm->req_cap[idx] == NULL) - return 0; - - smp->data.type = SMP_T_STR; - smp->flags |= SMP_F_CONST; - smp->data.u.str.area = smp->strm->req_cap[idx]; - smp->data.u.str.data = strlen(smp->strm->req_cap[idx]); - - return 1; -} - -/* Fetch a captured HTTP response header. The index is the position of - * the "capture" option in the configuration file - */ -static int -smp_fetch_capture_header_res(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct proxy *fe = strm_fe(smp->strm); - int idx; - - if (!args || args->type != ARGT_SINT) - return 0; - - idx = args->data.sint; - - if (idx > (fe->nb_rsp_cap - 1) || smp->strm->res_cap == NULL || smp->strm->res_cap[idx] == NULL) - return 0; - - smp->data.type = SMP_T_STR; - smp->flags |= SMP_F_CONST; - smp->data.u.str.area = smp->strm->res_cap[idx]; - smp->data.u.str.data = strlen(smp->strm->res_cap[idx]); - - return 1; -} - -/* Extracts the METHOD in the HTTP request, the txn->uri should be filled before the call */ -static int -smp_fetch_capture_req_method(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct buffer *temp; - struct http_txn *txn = smp->strm->txn; - char *ptr; - - if (!txn || !txn->uri) - return 0; - - ptr = txn->uri; - - while (*ptr != ' ' && *ptr != '\0') /* find first space */ - ptr++; - - temp = get_trash_chunk(); - temp->area = txn->uri; - temp->data = ptr - txn->uri; - smp->data.u.str = *temp; - smp->data.type = SMP_T_STR; - smp->flags = SMP_F_CONST; - - return 1; - -} - -/* Extracts the path in the HTTP request, the txn->uri should be filled before the call */ -static int -smp_fetch_capture_req_uri(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn = smp->strm->txn; - struct ist path; - const char *ptr; - - if (!txn || !txn->uri) - return 0; - - ptr = txn->uri; - - while (*ptr != ' ' && *ptr != '\0') /* find first space */ - ptr++; - - if (!*ptr) - return 0; - - ptr++; /* skip the space */ - - path = http_get_path(ist(ptr)); - if (!path.ptr) - return 0; - - smp->data.u.str.area = path.ptr; - smp->data.u.str.data = path.len; - smp->data.type = SMP_T_STR; - smp->flags = SMP_F_CONST; - - return 1; -} - -/* Retrieves the HTTP version from the request (either 1.0 or 1.1) and emits it - * as a string (either "HTTP/1.0" or "HTTP/1.1"). - */ -static int -smp_fetch_capture_req_ver(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn = smp->strm->txn; - - if (!txn || txn->req.msg_state < HTTP_MSG_HDR_FIRST) - return 0; - - if (txn->req.flags & HTTP_MSGF_VER_11) - smp->data.u.str.area = "HTTP/1.1"; - else - smp->data.u.str.area = "HTTP/1.0"; - - smp->data.u.str.data = 8; - smp->data.type = SMP_T_STR; - smp->flags = SMP_F_CONST; - return 1; - -} - -/* Retrieves the HTTP version from the response (either 1.0 or 1.1) and emits it - * as a string (either "HTTP/1.0" or "HTTP/1.1"). - */ -static int -smp_fetch_capture_res_ver(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn = smp->strm->txn; - - if (!txn || txn->rsp.msg_state < HTTP_MSG_HDR_FIRST) - return 0; - - if (txn->rsp.flags & HTTP_MSGF_VER_11) - smp->data.u.str.area = "HTTP/1.1"; - else - smp->data.u.str.area = "HTTP/1.0"; - - smp->data.u.str.data = 8; - smp->data.type = SMP_T_STR; - smp->flags = SMP_F_CONST; - return 1; - -} - - -/* Iterate over all cookies present in a message. The context is stored in - * smp->ctx.a[0] for the in-header position, smp->ctx.a[1] for the - * end-of-header-value, and smp->ctx.a[2] for the hdr_ctx. Depending on - * the direction, multiple cookies may be parsed on the same line or not. - * The cookie name is in args and the name length in args->data.str.len. - * Accepts exactly 1 argument of type string. If the input options indicate - * that no iterating is desired, then only last value is fetched if any. - * The returned sample is of type CSTR. Can be used to parse cookies in other - * files. - */ -int smp_fetch_cookie(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - struct hdr_idx *idx; - struct hdr_ctx *ctx = smp->ctx.a[2]; - const struct http_msg *msg; - const char *hdr_name; - int hdr_name_len; - char *sol; - int occ = 0; - int found = 0; - - if (!args || args->type != ARGT_STR) - return 0; - - if (!ctx) { - /* first call */ - ctx = &static_hdr_ctx; - ctx->idx = 0; - smp->ctx.a[2] = ctx; - } - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - idx = &smp->strm->txn->hdr_idx; - - if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) { - msg = &txn->req; - hdr_name = "Cookie"; - hdr_name_len = 6; - } else { - msg = &txn->rsp; - hdr_name = "Set-Cookie"; - hdr_name_len = 10; - } - - if (!occ && !(smp->opt & SMP_OPT_ITERATE)) - /* no explicit occurrence and single fetch => last cookie by default */ - occ = -1; - - /* OK so basically here, either we want only one value and it's the - * last one, or we want to iterate over all of them and we fetch the - * next one. - */ - - sol = ci_head(msg->chn); - if (!(smp->flags & SMP_F_NOT_LAST)) { - /* search for the header from the beginning, we must first initialize - * the search parameters. - */ - smp->ctx.a[0] = NULL; - ctx->idx = 0; - } - - smp->flags |= SMP_F_VOL_HDR; - - while (1) { - /* Note: smp->ctx.a[0] == NULL every time we need to fetch a new header */ - if (!smp->ctx.a[0]) { - if (!http_find_header2(hdr_name, hdr_name_len, sol, idx, ctx)) - goto out; - - if (ctx->vlen < args->data.str.data + 1) - continue; - - smp->ctx.a[0] = ctx->line + ctx->val; - smp->ctx.a[1] = smp->ctx.a[0] + ctx->vlen; - } - - smp->data.type = SMP_T_STR; - smp->flags |= SMP_F_CONST; - smp->ctx.a[0] = http_extract_cookie_value(smp->ctx.a[0], smp->ctx.a[1], - args->data.str.area, args->data.str.data, - (smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ, - &smp->data.u.str.area, &smp->data.u.str.data); - if (smp->ctx.a[0]) { - found = 1; - if (occ >= 0) { - /* one value was returned into smp->data.u.str.{str,len} */ - smp->flags |= SMP_F_NOT_LAST; - return 1; - } - } - /* if we're looking for last occurrence, let's loop */ - } - /* all cookie headers and values were scanned. If we're looking for the - * last occurrence, we may return it now. - */ - out: - smp->flags &= ~SMP_F_NOT_LAST; - return found; -} - -/* Iterate over all cookies present in a request to count how many occurrences - * match the name in args and args->data.str.len. If is non-null, then - * multiple cookies may be parsed on the same line. The returned sample is of - * type UINT. Accepts exactly 1 argument of type string. - */ -static int -smp_fetch_cookie_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - struct hdr_idx *idx; - struct hdr_ctx ctx; - const struct http_msg *msg; - const char *hdr_name; - int hdr_name_len; - int cnt; - char *val_beg, *val_end; - char *sol; - - if (!args || args->type != ARGT_STR) - return 0; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - idx = &smp->strm->txn->hdr_idx; - - if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) { - msg = &txn->req; - hdr_name = "Cookie"; - hdr_name_len = 6; - } else { - msg = &txn->rsp; - hdr_name = "Set-Cookie"; - hdr_name_len = 10; - } - - sol = ci_head(msg->chn); - val_end = val_beg = NULL; - ctx.idx = 0; - cnt = 0; - - while (1) { - /* Note: val_beg == NULL every time we need to fetch a new header */ - if (!val_beg) { - if (!http_find_header2(hdr_name, hdr_name_len, sol, idx, &ctx)) - break; - - if (ctx.vlen < args->data.str.data + 1) - continue; - - val_beg = ctx.line + ctx.val; - val_end = val_beg + ctx.vlen; - } - - smp->data.type = SMP_T_STR; - smp->flags |= SMP_F_CONST; - while ((val_beg = http_extract_cookie_value(val_beg, val_end, - args->data.str.area, args->data.str.data, - (smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ, - &smp->data.u.str.area, &smp->data.u.str.data))) { - cnt++; - } - } - - smp->data.type = SMP_T_SINT; - smp->data.u.sint = cnt; - smp->flags |= SMP_F_VOL_HDR; - return 1; -} - -/* Fetch an cookie's integer value. The integer value is returned. It - * takes a mandatory argument of type string. It relies on smp_fetch_cookie(). - */ -static int -smp_fetch_cookie_val(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - int ret = smp_fetch_cookie(args, smp, kw, private); - - if (ret > 0) { - smp->data.type = SMP_T_SINT; - smp->data.u.sint = strl2ic(smp->data.u.str.area, - smp->data.u.str.data); - } - - return ret; -} - -/************************************************************************/ -/* The code below is dedicated to sample fetches */ -/************************************************************************/ - -/* This scans a URL-encoded query string. It takes an optionally wrapping - * string whose first contigous chunk has its beginning in ctx->a[0] and end - * in ctx->a[1], and the optional second part in (ctx->a[2]..ctx->a[3]). The - * pointers are updated for next iteration before leaving. - */ -static int -smp_fetch_param(char delim, const char *name, int name_len, const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - const char *vstart, *vend; - struct buffer *temp; - const char **chunks = (const char **)smp->ctx.a; - - if (!http_find_next_url_param(chunks, name, name_len, - &vstart, &vend, delim)) - return 0; - - /* Create sample. If the value is contiguous, return the pointer as CONST, - * if the value is wrapped, copy-it in a buffer. - */ - smp->data.type = SMP_T_STR; - if (chunks[2] && - vstart >= chunks[0] && vstart <= chunks[1] && - vend >= chunks[2] && vend <= chunks[3]) { - /* Wrapped case. */ - temp = get_trash_chunk(); - memcpy(temp->area, vstart, chunks[1] - vstart); - memcpy(temp->area + ( chunks[1] - vstart ), chunks[2], - vend - chunks[2]); - smp->data.u.str.area = temp->area; - smp->data.u.str.data = ( chunks[1] - vstart ) + ( vend - chunks[2] ); - } else { - /* Contiguous case. */ - smp->data.u.str.area = (char *)vstart; - smp->data.u.str.data = vend - vstart; - smp->flags = SMP_F_VOL_1ST | SMP_F_CONST; - } - - /* Update context, check wrapping. */ - chunks[0] = vend; - if (chunks[2] && vend >= chunks[2] && vend <= chunks[3]) { - chunks[1] = chunks[3]; - chunks[2] = NULL; - } - - if (chunks[0] < chunks[1]) - smp->flags |= SMP_F_NOT_LAST; - - return 1; -} - -/* This function iterates over each parameter of the query string. It uses - * ctx->a[0] and ctx->a[1] to store the beginning and end of the current - * parameter. Since it uses smp_fetch_param(), ctx->a[2..3] are both NULL. - * An optional parameter name is passed in args[0], otherwise any parameter is - * considered. It supports an optional delimiter argument for the beginning of - * the string in args[1], which defaults to "?". - */ -static int -smp_fetch_url_param(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_msg *msg; - char delim = '?'; - const char *name; - int name_len; - - if (!args || - (args[0].type && args[0].type != ARGT_STR) || - (args[1].type && args[1].type != ARGT_STR)) - return 0; - - name = ""; - name_len = 0; - if (args->type == ARGT_STR) { - name = args->data.str.area; - name_len = args->data.str.data; - } - - if (args[1].type) - delim = *args[1].data.str.area; - - if (!smp->ctx.a[0]) { // first call, find the query string - CHECK_HTTP_MESSAGE_FIRST(); - - msg = &smp->strm->txn->req; - - smp->ctx.a[0] = http_find_param_list(ci_head(msg->chn) + msg->sl.rq.u, - msg->sl.rq.u_l, delim); - if (!smp->ctx.a[0]) - return 0; - - smp->ctx.a[1] = ci_head(msg->chn) + msg->sl.rq.u + msg->sl.rq.u_l; - - /* Assume that the context is filled with NULL pointer - * before the first call. - * smp->ctx.a[2] = NULL; - * smp->ctx.a[3] = NULL; - */ - } - - return smp_fetch_param(delim, name, name_len, args, smp, kw, private); -} - -/* This function iterates over each parameter of the body. This requires - * that the body has been waited for using http-buffer-request. It uses - * ctx->a[0] and ctx->a[1] to store the beginning and end of the first - * contigous part of the body, and optionally ctx->a[2..3] to reference the - * optional second part if the body wraps at the end of the buffer. An optional - * parameter name is passed in args[0], otherwise any parameter is considered. - */ -static int -smp_fetch_body_param(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_msg *msg; - unsigned long len; - unsigned long block1; - char *body; - const char *name; - int name_len; - - if (!args || (args[0].type && args[0].type != ARGT_STR)) - return 0; - - name = ""; - name_len = 0; - if (args[0].type == ARGT_STR) { - name = args[0].data.str.area; - name_len = args[0].data.str.data; - } - - if (!smp->ctx.a[0]) { // first call, find the query string - CHECK_HTTP_MESSAGE_FIRST(); - - if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) - msg = &smp->strm->txn->req; - else - msg = &smp->strm->txn->rsp; - - len = http_body_bytes(msg); - body = c_ptr(msg->chn, -http_data_rewind(msg)); - - block1 = len; - if (block1 > b_wrap(&msg->chn->buf) - body) - block1 = b_wrap(&msg->chn->buf) - body; - - if (block1 == len) { - /* buffer is not wrapped (or empty) */ - smp->ctx.a[0] = body; - smp->ctx.a[1] = body + len; - - /* Assume that the context is filled with NULL pointer - * before the first call. - * smp->ctx.a[2] = NULL; - * smp->ctx.a[3] = NULL; - */ - } - else { - /* buffer is wrapped, we need to defragment it */ - smp->ctx.a[0] = body; - smp->ctx.a[1] = body + block1; - smp->ctx.a[2] = b_orig(&msg->chn->buf); - smp->ctx.a[3] = b_orig(&msg->chn->buf) + ( len - block1 ); - } - } - return smp_fetch_param('&', name, name_len, args, smp, kw, private); -} - -/* Return the signed integer value for the specified url parameter (see url_param - * above). - */ -static int -smp_fetch_url_param_val(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - int ret = smp_fetch_url_param(args, smp, kw, private); - - if (ret > 0) { - smp->data.type = SMP_T_SINT; - smp->data.u.sint = strl2ic(smp->data.u.str.area, - smp->data.u.str.data); - } - - return ret; -} - -/* This produces a 32-bit hash of the concatenation of the first occurrence of - * the Host header followed by the path component if it begins with a slash ('/'). - * This means that '*' will not be added, resulting in exactly the first Host - * entry. If no Host header is found, then the path is used. The resulting value - * is hashed using the url hash followed by a full avalanche hash and provides a - * 32-bit integer value. This fetch is useful for tracking per-URL activity on - * high-traffic sites without having to store whole paths. - * this differs from the base32 functions in that it includes the url parameters - * as well as the path - */ -static int -smp_fetch_url32(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct http_txn *txn; - struct hdr_ctx ctx; - unsigned int hash = 0; - char *ptr, *beg, *end; - int len; - - CHECK_HTTP_MESSAGE_FIRST(); - - txn = smp->strm->txn; - ctx.idx = 0; - if (http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx)) { - /* OK we have the header value in ctx.line+ctx.val for ctx.vlen bytes */ - ptr = ctx.line + ctx.val; - len = ctx.vlen; - while (len--) - hash = *(ptr++) + (hash << 6) + (hash << 16) - hash; - } - - /* now retrieve the path */ - end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l; - beg = http_txn_get_path(txn); - if (!beg) - beg = end; - - for (ptr = beg; ptr < end ; ptr++); - - if (beg < ptr && *beg == '/') { - while (beg < ptr) - hash = *(beg++) + (hash << 6) + (hash << 16) - hash; - } - hash = full_hash(hash); - - smp->data.type = SMP_T_SINT; - smp->data.u.sint = hash; - smp->flags = SMP_F_VOL_1ST; - return 1; -} - -/* This concatenates the source address with the 32-bit hash of the Host and - * URL as returned by smp_fetch_base32(). The idea is to have per-source and - * per-url counters. The result is a binary block from 8 to 20 bytes depending - * on the source address length. The URL hash is stored before the address so - * that in environments where IPv6 is insignificant, truncating the output to - * 8 bytes would still work. - */ -static int -smp_fetch_url32_src(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct buffer *temp; - struct connection *cli_conn = objt_conn(smp->sess->origin); - - if (!cli_conn) - return 0; - - if (!smp_fetch_url32(args, smp, kw, private)) - return 0; - - temp = get_trash_chunk(); - *(unsigned int *) temp->area = htonl(smp->data.u.sint); - temp->data += sizeof(unsigned int); - - switch (cli_conn->addr.from.ss_family) { - case AF_INET: - memcpy(temp->area + temp->data, - &((struct sockaddr_in *)&cli_conn->addr.from)->sin_addr, - 4); - temp->data += 4; - break; - case AF_INET6: - memcpy(temp->area + temp->data, - &((struct sockaddr_in6 *)&cli_conn->addr.from)->sin6_addr, - 16); - temp->data += 16; - break; - default: - return 0; - } - - smp->data.u.str = *temp; - smp->data.type = SMP_T_BIN; - return 1; -} - -/* This function is used to validate the arguments passed to any "hdr" fetch - * keyword. These keywords support an optional positive or negative occurrence - * number. We must ensure that the number is greater than -MAX_HDR_HISTORY. It - * is assumed that the types are already the correct ones. Returns 0 on error, - * non-zero if OK. If is not NULL, it will be filled with a pointer to an - * error message in case of error, that the caller is responsible for freeing. - * The initial location must either be freeable or NULL. - */ -int val_hdr(struct arg *arg, char **err_msg) -{ - if (arg && arg[1].type == ARGT_SINT && arg[1].data.sint < -MAX_HDR_HISTORY) { - memprintf(err_msg, "header occurrence must be >= %d", -MAX_HDR_HISTORY); - return 0; - } - return 1; -} - -/* takes an UINT value on input supposed to represent the time since EPOCH, - * adds an optional offset found in args[0] and emits a string representing - * the date in RFC-1123/5322 format. - */ -static int sample_conv_http_date(const struct arg *args, struct sample *smp, void *private) -{ - const char day[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; - const char mon[12][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - struct buffer *temp; - struct tm *tm; - /* With high numbers, the date returned can be negative, the 55 bits mask prevent this. */ - time_t curr_date = smp->data.u.sint & 0x007fffffffffffffLL; - - /* add offset */ - if (args && (args[0].type == ARGT_SINT)) - curr_date += args[0].data.sint; - - tm = gmtime(&curr_date); - if (!tm) - return 0; - - temp = get_trash_chunk(); - temp->data = snprintf(temp->area, temp->size - temp->data, - "%s, %02d %s %04d %02d:%02d:%02d GMT", - day[tm->tm_wday], tm->tm_mday, mon[tm->tm_mon], - 1900+tm->tm_year, - tm->tm_hour, tm->tm_min, tm->tm_sec); - - smp->data.u.str = *temp; - smp->data.type = SMP_T_STR; - return 1; -} - -/* Match language range with language tag. RFC2616 14.4: - * - * A language-range matches a language-tag if it exactly equals - * the tag, or if it exactly equals a prefix of the tag such - * that the first tag character following the prefix is "-". - * - * Return 1 if the strings match, else return 0. - */ -static inline int language_range_match(const char *range, int range_len, - const char *tag, int tag_len) -{ - const char *end = range + range_len; - const char *tend = tag + tag_len; - while (range < end) { - if (*range == '-' && tag == tend) - return 1; - if (*range != *tag || tag == tend) - return 0; - range++; - tag++; - } - /* Return true only if the last char of the tag is matched. */ - return tag == tend; -} - -/* Arguments: The list of expected value, the number of parts returned and the separator */ -static int sample_conv_q_prefered(const struct arg *args, struct sample *smp, void *private) -{ - const char *al = smp->data.u.str.area; - const char *end = al + smp->data.u.str.data; - const char *token; - int toklen; - int qvalue; - const char *str; - const char *w; - int best_q = 0; - - /* Set the constant to the sample, because the output of the - * function will be peek in the constant configuration string. - */ - smp->flags |= SMP_F_CONST; - smp->data.u.str.size = 0; - smp->data.u.str.area = ""; - smp->data.u.str.data = 0; - - /* Parse the accept language */ - while (1) { - - /* Jump spaces, quit if the end is detected. */ - while (al < end && isspace((unsigned char)*al)) - al++; - if (al >= end) - break; - - /* Start of the fisrt word. */ - token = al; - - /* Look for separator: isspace(), ',' or ';'. Next value if 0 length word. */ - while (al < end && *al != ';' && *al != ',' && !isspace((unsigned char)*al)) - al++; - if (al == token) - goto expect_comma; - - /* Length of the token. */ - toklen = al - token; - qvalue = 1000; - - /* Check if the token exists in the list. If the token not exists, - * jump to the next token. - */ - str = args[0].data.str.area; - w = str; - while (1) { - if (*str == ';' || *str == '\0') { - if (language_range_match(token, toklen, w, str-w)) - goto look_for_q; - if (*str == '\0') - goto expect_comma; - w = str + 1; - } - str++; - } - goto expect_comma; - -look_for_q: - - /* Jump spaces, quit if the end is detected. */ - while (al < end && isspace((unsigned char)*al)) - al++; - if (al >= end) - goto process_value; - - /* If ',' is found, process the result */ - if (*al == ',') - goto process_value; - - /* If the character is different from ';', look - * for the end of the header part in best effort. - */ - if (*al != ';') - goto expect_comma; - - /* Assumes that the char is ';', now expect "q=". */ - al++; - - /* Jump spaces, process value if the end is detected. */ - while (al < end && isspace((unsigned char)*al)) - al++; - if (al >= end) - goto process_value; - - /* Expect 'q'. If no 'q', continue in best effort */ - if (*al != 'q') - goto process_value; - al++; - - /* Jump spaces, process value if the end is detected. */ - while (al < end && isspace((unsigned char)*al)) - al++; - if (al >= end) - goto process_value; - - /* Expect '='. If no '=', continue in best effort */ - if (*al != '=') - goto process_value; - al++; - - /* Jump spaces, process value if the end is detected. */ - while (al < end && isspace((unsigned char)*al)) - al++; - if (al >= end) - goto process_value; - - /* Parse the q value. */ - qvalue = http_parse_qvalue(al, &al); - -process_value: - - /* If the new q value is the best q value, then store the associated - * language in the response. If qvalue is the biggest value (1000), - * break the process. - */ - if (qvalue > best_q) { - smp->data.u.str.area = (char *)w; - smp->data.u.str.data = str - w; - if (qvalue >= 1000) - break; - best_q = qvalue; - } - -expect_comma: - - /* Expect comma or end. If the end is detected, quit the loop. */ - while (al < end && *al != ',') - al++; - if (al >= end) - break; - - /* Comma is found, jump it and restart the analyzer. */ - al++; - } - - /* Set default value if required. */ - if (smp->data.u.str.data == 0 && args[1].type == ARGT_STR) { - smp->data.u.str.area = args[1].data.str.area; - smp->data.u.str.data = args[1].data.str.data; - } - - /* Return true only if a matching language was found. */ - return smp->data.u.str.data != 0; -} - -/* This fetch url-decode any input string. */ -static int sample_conv_url_dec(const struct arg *args, struct sample *smp, void *private) -{ - int len; - - /* If the constant flag is set or if not size is avalaible at - * the end of the buffer, copy the string in other buffer - * before decoding. - */ - if (smp->flags & SMP_F_CONST || smp->data.u.str.size <= smp->data.u.str.data) { - struct buffer *str = get_trash_chunk(); - memcpy(str->area, smp->data.u.str.area, smp->data.u.str.data); - smp->data.u.str.area = str->area; - smp->data.u.str.size = str->size; - smp->flags &= ~SMP_F_CONST; - } - - /* Add final \0 required by url_decode(), and convert the input string. */ - smp->data.u.str.area[smp->data.u.str.data] = '\0'; - len = url_decode(smp->data.u.str.area); - if (len < 0) - return 0; - smp->data.u.str.data = len; - return 1; -} - -static int smp_conv_req_capture(const struct arg *args, struct sample *smp, void *private) -{ - struct proxy *fe = strm_fe(smp->strm); - int idx, i; - struct cap_hdr *hdr; - int len; - - if (!args || args->type != ARGT_SINT) - return 0; - - idx = args->data.sint; - - /* Check the availibity of the capture id. */ - if (idx > fe->nb_req_cap - 1) - return 0; - - /* Look for the original configuration. */ - for (hdr = fe->req_cap, i = fe->nb_req_cap - 1; - hdr != NULL && i != idx ; - i--, hdr = hdr->next); - if (!hdr) - return 0; - - /* check for the memory allocation */ - if (smp->strm->req_cap[hdr->index] == NULL) - smp->strm->req_cap[hdr->index] = pool_alloc(hdr->pool); - if (smp->strm->req_cap[hdr->index] == NULL) - return 0; - - /* Check length. */ - len = smp->data.u.str.data; - if (len > hdr->len) - len = hdr->len; - - /* Capture input data. */ - memcpy(smp->strm->req_cap[idx], smp->data.u.str.area, len); - smp->strm->req_cap[idx][len] = '\0'; - - return 1; -} - -static int smp_conv_res_capture(const struct arg *args, struct sample *smp, void *private) -{ - struct proxy *fe = strm_fe(smp->strm); - int idx, i; - struct cap_hdr *hdr; - int len; - - if (!args || args->type != ARGT_SINT) - return 0; - - idx = args->data.sint; - - /* Check the availibity of the capture id. */ - if (idx > fe->nb_rsp_cap - 1) - return 0; - - /* Look for the original configuration. */ - for (hdr = fe->rsp_cap, i = fe->nb_rsp_cap - 1; - hdr != NULL && i != idx ; - i--, hdr = hdr->next); - if (!hdr) - return 0; - - /* check for the memory allocation */ - if (smp->strm->res_cap[hdr->index] == NULL) - smp->strm->res_cap[hdr->index] = pool_alloc(hdr->pool); - if (smp->strm->res_cap[hdr->index] == NULL) - return 0; - - /* Check length. */ - len = smp->data.u.str.data; - if (len > hdr->len) - len = hdr->len; - - /* Capture input data. */ - memcpy(smp->strm->res_cap[idx], smp->data.u.str.area, len); - smp->strm->res_cap[idx][len] = '\0'; - - return 1; -} - /* This function executes one of the set-{method,path,query,uri} actions. It * takes the string from the variable 'replace' with length 'len', then modifies * the relevant part of the request line accordingly. Then it updates various @@ -11112,541 +8940,6 @@ void http_set_status(unsigned int status, const char *reason, struct stream *s) http_msg_move_end(&txn->rsp, delta); } -/* This function executes one of the set-{method,path,query,uri} actions. It - * builds a string in the trash from the specified format string. It finds - * the action to be performed in , previously filled by function - * parse_set_req_line(). The replacement action is excuted by the function - * http_action_set_req_line(). It always returns ACT_RET_CONT. If an error - * occurs the action is canceled, but the rule processing continue. - */ -enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px, - struct session *sess, struct stream *s, int flags) -{ - struct buffer *replace; - enum act_return ret = ACT_RET_ERR; - - replace = alloc_trash_chunk(); - if (!replace) - goto leave; - - /* If we have to create a query string, prepare a '?'. */ - if (rule->arg.http.action == 2) - replace->area[replace->data++] = '?'; - replace->data += build_logline(s, replace->area + replace->data, - replace->size - replace->data, - &rule->arg.http.logfmt); - - http_replace_req_line(rule->arg.http.action, replace->area, - replace->data, px, s); - - ret = ACT_RET_CONT; - -leave: - free_trash_chunk(replace); - return ret; -} - -/* This function is just a compliant action wrapper for "set-status". */ -enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px, - struct session *sess, struct stream *s, int flags) -{ - http_set_status(rule->arg.status.code, rule->arg.status.reason, s); - return ACT_RET_CONT; -} - -/* parse an http-request action among : - * set-method - * set-path - * set-query - * set-uri - * - * All of them accept a single argument of type string representing a log-format. - * The resulting rule makes use of arg->act.p[0..1] to store the log-format list - * head, and p[2] to store the action as an int (0=method, 1=path, 2=query, 3=uri). - * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error. - */ -enum act_parse_ret parse_set_req_line(const char **args, int *orig_arg, struct proxy *px, - struct act_rule *rule, char **err) -{ - int cur_arg = *orig_arg; - - rule->action = ACT_CUSTOM; - - switch (args[0][4]) { - case 'm' : - rule->arg.http.action = 0; - rule->action_ptr = http_action_set_req_line; - break; - case 'p' : - rule->arg.http.action = 1; - rule->action_ptr = http_action_set_req_line; - break; - case 'q' : - rule->arg.http.action = 2; - rule->action_ptr = http_action_set_req_line; - break; - case 'u' : - rule->arg.http.action = 3; - rule->action_ptr = http_action_set_req_line; - break; - default: - memprintf(err, "internal error: unhandled action '%s'", args[0]); - return ACT_RET_PRS_ERR; - } - - if (!*args[cur_arg] || - (*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) { - memprintf(err, "expects exactly 1 argument "); - return ACT_RET_PRS_ERR; - } - - LIST_INIT(&rule->arg.http.logfmt); - px->conf.args.ctx = ARGC_HRQ; - if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.logfmt, LOG_OPT_HTTP, - (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) { - return ACT_RET_PRS_ERR; - } - - (*orig_arg)++; - return ACT_RET_PRS_OK; -} - -/* parse set-status action: - * This action accepts a single argument of type int representing - * an http status code. It returns ACT_RET_PRS_OK on success, - * ACT_RET_PRS_ERR on error. - */ -enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struct proxy *px, - struct act_rule *rule, char **err) -{ - char *error; - - rule->action = ACT_CUSTOM; - rule->action_ptr = action_http_set_status; - - /* Check if an argument is available */ - if (!*args[*orig_arg]) { - memprintf(err, "expects 1 argument: ; or 3 arguments: reason "); - return ACT_RET_PRS_ERR; - } - - /* convert status code as integer */ - rule->arg.status.code = strtol(args[*orig_arg], &error, 10); - if (*error != '\0' || rule->arg.status.code < 100 || rule->arg.status.code > 999) { - memprintf(err, "expects an integer status code between 100 and 999"); - return ACT_RET_PRS_ERR; - } - - (*orig_arg)++; - - /* set custom reason string */ - rule->arg.status.reason = NULL; // If null, we use the default reason for the status code. - if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 && - (*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) { - (*orig_arg)++; - rule->arg.status.reason = strdup(args[*orig_arg]); - (*orig_arg)++; - } - - return ACT_RET_PRS_OK; -} - -/* This function executes the "reject" HTTP action. It clears the request and - * response buffer without sending any response. It can be useful as an HTTP - * alternative to the silent-drop action to defend against DoS attacks, and may - * also be used with HTTP/2 to close a connection instead of just a stream. - * The txn status is unchanged, indicating no response was sent. The termination - * flags will indicate "PR". It always returns ACT_RET_STOP. - */ -enum act_return http_action_reject(struct act_rule *rule, struct proxy *px, - struct session *sess, struct stream *s, int flags) -{ - channel_abort(&s->req); - channel_abort(&s->res); - s->req.analysers = 0; - s->res.analysers = 0; - - HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1); - HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1); - if (sess->listener && sess->listener->counters) - HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1); - - if (!(s->flags & SF_ERR_MASK)) - s->flags |= SF_ERR_PRXCOND; - if (!(s->flags & SF_FINST_MASK)) - s->flags |= SF_FINST_R; - - return ACT_RET_CONT; -} - -/* parse the "reject" action: - * This action takes no argument and returns ACT_RET_PRS_OK on success, - * ACT_RET_PRS_ERR on error. - */ -enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px, - struct act_rule *rule, char **err) -{ - rule->action = ACT_CUSTOM; - rule->action_ptr = http_action_reject; - return ACT_RET_PRS_OK; -} - -/* This function executes the "capture" action. It executes a fetch expression, - * turns the result into a string and puts it in a capture slot. It always - * returns 1. If an error occurs the action is cancelled, but the rule - * processing continues. - */ -enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px, - struct session *sess, struct stream *s, int flags) -{ - struct sample *key; - struct cap_hdr *h = rule->arg.cap.hdr; - char **cap = s->req_cap; - int len; - - key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR); - if (!key) - return ACT_RET_CONT; - - if (cap[h->index] == NULL) - cap[h->index] = pool_alloc(h->pool); - - if (cap[h->index] == NULL) /* no more capture memory */ - return ACT_RET_CONT; - - len = key->data.u.str.data; - if (len > h->len) - len = h->len; - - memcpy(cap[h->index], key->data.u.str.area, len); - cap[h->index][len] = 0; - return ACT_RET_CONT; -} - -/* This function executes the "capture" action and store the result in a - * capture slot if exists. It executes a fetch expression, turns the result - * into a string and puts it in a capture slot. It always returns 1. If an - * error occurs the action is cancelled, but the rule processing continues. - */ -enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px, - struct session *sess, struct stream *s, int flags) -{ - struct sample *key; - struct cap_hdr *h; - char **cap = s->req_cap; - struct proxy *fe = strm_fe(s); - int len; - int i; - - /* Look for the original configuration. */ - for (h = fe->req_cap, i = fe->nb_req_cap - 1; - h != NULL && i != rule->arg.capid.idx ; - i--, h = h->next); - if (!h) - return ACT_RET_CONT; - - key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR); - if (!key) - return ACT_RET_CONT; - - if (cap[h->index] == NULL) - cap[h->index] = pool_alloc(h->pool); - - if (cap[h->index] == NULL) /* no more capture memory */ - return ACT_RET_CONT; - - len = key->data.u.str.data; - if (len > h->len) - len = h->len; - - memcpy(cap[h->index], key->data.u.str.area, len); - cap[h->index][len] = 0; - return ACT_RET_CONT; -} - -/* Check an "http-request capture" action. - * - * The function returns 1 in success case, otherwise, it returns 0 and err is - * filled. - */ -int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err) -{ - if (rule->action_ptr != http_action_req_capture_by_id) - return 1; - - if (rule->arg.capid.idx >= px->nb_req_cap) { - memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule", - rule->arg.capid.idx); - return 0; - } - - return 1; -} - -/* parse an "http-request capture" action. It takes a single argument which is - * a sample fetch expression. It stores the expression into arg->act.p[0] and - * the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1]. - * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error. - */ -enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px, - struct act_rule *rule, char **err) -{ - struct sample_expr *expr; - struct cap_hdr *hdr; - int cur_arg; - int len = 0; - - for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++) - if (strcmp(args[cur_arg], "if") == 0 || - strcmp(args[cur_arg], "unless") == 0) - break; - - if (cur_arg < *orig_arg + 3) { - memprintf(err, "expects [ 'len' | id ]"); - return ACT_RET_PRS_ERR; - } - - cur_arg = *orig_arg; - expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args); - if (!expr) - return ACT_RET_PRS_ERR; - - if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) { - memprintf(err, - "fetch method '%s' extracts information from '%s', none of which is available here", - args[cur_arg-1], sample_src_names(expr->fetch->use)); - free(expr); - return ACT_RET_PRS_ERR; - } - - if (!args[cur_arg] || !*args[cur_arg]) { - memprintf(err, "expects 'len or 'id'"); - free(expr); - return ACT_RET_PRS_ERR; - } - - if (strcmp(args[cur_arg], "len") == 0) { - cur_arg++; - - if (!(px->cap & PR_CAP_FE)) { - memprintf(err, "proxy '%s' has no frontend capability", px->id); - return ACT_RET_PRS_ERR; - } - - px->conf.args.ctx = ARGC_CAP; - - if (!args[cur_arg]) { - memprintf(err, "missing length value"); - free(expr); - return ACT_RET_PRS_ERR; - } - /* we copy the table name for now, it will be resolved later */ - len = atoi(args[cur_arg]); - if (len <= 0) { - memprintf(err, "length must be > 0"); - free(expr); - return ACT_RET_PRS_ERR; - } - cur_arg++; - - if (!len) { - memprintf(err, "a positive 'len' argument is mandatory"); - free(expr); - return ACT_RET_PRS_ERR; - } - - hdr = calloc(1, sizeof(*hdr)); - hdr->next = px->req_cap; - hdr->name = NULL; /* not a header capture */ - hdr->namelen = 0; - hdr->len = len; - hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED); - hdr->index = px->nb_req_cap++; - - px->req_cap = hdr; - px->to_log |= LW_REQHDR; - - rule->action = ACT_CUSTOM; - rule->action_ptr = http_action_req_capture; - rule->arg.cap.expr = expr; - rule->arg.cap.hdr = hdr; - } - - else if (strcmp(args[cur_arg], "id") == 0) { - int id; - char *error; - - cur_arg++; - - if (!args[cur_arg]) { - memprintf(err, "missing id value"); - free(expr); - return ACT_RET_PRS_ERR; - } - - id = strtol(args[cur_arg], &error, 10); - if (*error != '\0') { - memprintf(err, "cannot parse id '%s'", args[cur_arg]); - free(expr); - return ACT_RET_PRS_ERR; - } - cur_arg++; - - px->conf.args.ctx = ARGC_CAP; - - rule->action = ACT_CUSTOM; - rule->action_ptr = http_action_req_capture_by_id; - rule->check_ptr = check_http_req_capture; - rule->arg.capid.expr = expr; - rule->arg.capid.idx = id; - } - - else { - memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]); - free(expr); - return ACT_RET_PRS_ERR; - } - - *orig_arg = cur_arg; - return ACT_RET_PRS_OK; -} - -/* This function executes the "capture" action and store the result in a - * capture slot if exists. It executes a fetch expression, turns the result - * into a string and puts it in a capture slot. It always returns 1. If an - * error occurs the action is cancelled, but the rule processing continues. - */ -enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px, - struct session *sess, struct stream *s, int flags) -{ - struct sample *key; - struct cap_hdr *h; - char **cap = s->res_cap; - struct proxy *fe = strm_fe(s); - int len; - int i; - - /* Look for the original configuration. */ - for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1; - h != NULL && i != rule->arg.capid.idx ; - i--, h = h->next); - if (!h) - return ACT_RET_CONT; - - key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR); - if (!key) - return ACT_RET_CONT; - - if (cap[h->index] == NULL) - cap[h->index] = pool_alloc(h->pool); - - if (cap[h->index] == NULL) /* no more capture memory */ - return ACT_RET_CONT; - - len = key->data.u.str.data; - if (len > h->len) - len = h->len; - - memcpy(cap[h->index], key->data.u.str.area, len); - cap[h->index][len] = 0; - return ACT_RET_CONT; -} - -/* Check an "http-response capture" action. - * - * The function returns 1 in success case, otherwise, it returns 0 and err is - * filled. - */ -int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err) -{ - if (rule->action_ptr != http_action_res_capture_by_id) - return 1; - - if (rule->arg.capid.idx >= px->nb_rsp_cap) { - memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule", - rule->arg.capid.idx); - return 0; - } - - return 1; -} - -/* parse an "http-response capture" action. It takes a single argument which is - * a sample fetch expression. It stores the expression into arg->act.p[0] and - * the allocated hdr_cap struct od the preallocated id into arg->act.p[1]. - * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error. - */ -enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px, - struct act_rule *rule, char **err) -{ - struct sample_expr *expr; - int cur_arg; - int id; - char *error; - - for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++) - if (strcmp(args[cur_arg], "if") == 0 || - strcmp(args[cur_arg], "unless") == 0) - break; - - if (cur_arg < *orig_arg + 3) { - memprintf(err, "expects id "); - return ACT_RET_PRS_ERR; - } - - cur_arg = *orig_arg; - expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args); - if (!expr) - return ACT_RET_PRS_ERR; - - if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) { - memprintf(err, - "fetch method '%s' extracts information from '%s', none of which is available here", - args[cur_arg-1], sample_src_names(expr->fetch->use)); - free(expr); - return ACT_RET_PRS_ERR; - } - - if (!args[cur_arg] || !*args[cur_arg]) { - memprintf(err, "expects 'id'"); - free(expr); - return ACT_RET_PRS_ERR; - } - - if (strcmp(args[cur_arg], "id") != 0) { - memprintf(err, "expects 'id', found '%s'", args[cur_arg]); - free(expr); - return ACT_RET_PRS_ERR; - } - - cur_arg++; - - if (!args[cur_arg]) { - memprintf(err, "missing id value"); - free(expr); - return ACT_RET_PRS_ERR; - } - - id = strtol(args[cur_arg], &error, 10); - if (*error != '\0') { - memprintf(err, "cannot parse id '%s'", args[cur_arg]); - free(expr); - return ACT_RET_PRS_ERR; - } - cur_arg++; - - px->conf.args.ctx = ARGC_CAP; - - rule->action = ACT_CUSTOM; - rule->action_ptr = http_action_res_capture_by_id; - rule->check_ptr = check_http_res_capture; - rule->arg.capid.expr = expr; - rule->arg.capid.idx = id; - - *orig_arg = cur_arg; - return ACT_RET_PRS_OK; -} - /* * Return the struct http_req_action_kw associated to a keyword. */ @@ -11664,263 +8957,9 @@ struct action_kw *action_http_res_custom(const char *kw) } -/************************************************************************/ -/* All supported ACL keywords must be declared here. */ -/************************************************************************/ - -/* Note: must not be declared as its list will be overwritten. - * Please take care of keeping this list alphabetically sorted. - */ -static struct acl_kw_list acl_kws = {ILH, { - { "base", "base", PAT_MATCH_STR }, - { "base_beg", "base", PAT_MATCH_BEG }, - { "base_dir", "base", PAT_MATCH_DIR }, - { "base_dom", "base", PAT_MATCH_DOM }, - { "base_end", "base", PAT_MATCH_END }, - { "base_len", "base", PAT_MATCH_LEN }, - { "base_reg", "base", PAT_MATCH_REG }, - { "base_sub", "base", PAT_MATCH_SUB }, - - { "cook", "req.cook", PAT_MATCH_STR }, - { "cook_beg", "req.cook", PAT_MATCH_BEG }, - { "cook_dir", "req.cook", PAT_MATCH_DIR }, - { "cook_dom", "req.cook", PAT_MATCH_DOM }, - { "cook_end", "req.cook", PAT_MATCH_END }, - { "cook_len", "req.cook", PAT_MATCH_LEN }, - { "cook_reg", "req.cook", PAT_MATCH_REG }, - { "cook_sub", "req.cook", PAT_MATCH_SUB }, - - { "hdr", "req.hdr", PAT_MATCH_STR }, - { "hdr_beg", "req.hdr", PAT_MATCH_BEG }, - { "hdr_dir", "req.hdr", PAT_MATCH_DIR }, - { "hdr_dom", "req.hdr", PAT_MATCH_DOM }, - { "hdr_end", "req.hdr", PAT_MATCH_END }, - { "hdr_len", "req.hdr", PAT_MATCH_LEN }, - { "hdr_reg", "req.hdr", PAT_MATCH_REG }, - { "hdr_sub", "req.hdr", PAT_MATCH_SUB }, - - /* these two declarations uses strings with list storage (in place - * of tree storage). The basic match is PAT_MATCH_STR, but the indexation - * and delete functions are relative to the list management. The parse - * and match method are related to the corresponding fetch methods. This - * is very particular ACL declaration mode. - */ - { "http_auth_group", NULL, PAT_MATCH_STR, NULL, pat_idx_list_str, pat_del_list_ptr, NULL, pat_match_auth }, - { "method", NULL, PAT_MATCH_STR, pat_parse_meth, pat_idx_list_str, pat_del_list_ptr, NULL, pat_match_meth }, - - { "path", "path", PAT_MATCH_STR }, - { "path_beg", "path", PAT_MATCH_BEG }, - { "path_dir", "path", PAT_MATCH_DIR }, - { "path_dom", "path", PAT_MATCH_DOM }, - { "path_end", "path", PAT_MATCH_END }, - { "path_len", "path", PAT_MATCH_LEN }, - { "path_reg", "path", PAT_MATCH_REG }, - { "path_sub", "path", PAT_MATCH_SUB }, - - { "req_ver", "req.ver", PAT_MATCH_STR }, - { "resp_ver", "res.ver", PAT_MATCH_STR }, - - { "scook", "res.cook", PAT_MATCH_STR }, - { "scook_beg", "res.cook", PAT_MATCH_BEG }, - { "scook_dir", "res.cook", PAT_MATCH_DIR }, - { "scook_dom", "res.cook", PAT_MATCH_DOM }, - { "scook_end", "res.cook", PAT_MATCH_END }, - { "scook_len", "res.cook", PAT_MATCH_LEN }, - { "scook_reg", "res.cook", PAT_MATCH_REG }, - { "scook_sub", "res.cook", PAT_MATCH_SUB }, - - { "shdr", "res.hdr", PAT_MATCH_STR }, - { "shdr_beg", "res.hdr", PAT_MATCH_BEG }, - { "shdr_dir", "res.hdr", PAT_MATCH_DIR }, - { "shdr_dom", "res.hdr", PAT_MATCH_DOM }, - { "shdr_end", "res.hdr", PAT_MATCH_END }, - { "shdr_len", "res.hdr", PAT_MATCH_LEN }, - { "shdr_reg", "res.hdr", PAT_MATCH_REG }, - { "shdr_sub", "res.hdr", PAT_MATCH_SUB }, - - { "url", "url", PAT_MATCH_STR }, - { "url_beg", "url", PAT_MATCH_BEG }, - { "url_dir", "url", PAT_MATCH_DIR }, - { "url_dom", "url", PAT_MATCH_DOM }, - { "url_end", "url", PAT_MATCH_END }, - { "url_len", "url", PAT_MATCH_LEN }, - { "url_reg", "url", PAT_MATCH_REG }, - { "url_sub", "url", PAT_MATCH_SUB }, - - { "urlp", "urlp", PAT_MATCH_STR }, - { "urlp_beg", "urlp", PAT_MATCH_BEG }, - { "urlp_dir", "urlp", PAT_MATCH_DIR }, - { "urlp_dom", "urlp", PAT_MATCH_DOM }, - { "urlp_end", "urlp", PAT_MATCH_END }, - { "urlp_len", "urlp", PAT_MATCH_LEN }, - { "urlp_reg", "urlp", PAT_MATCH_REG }, - { "urlp_sub", "urlp", PAT_MATCH_SUB }, - - { /* END */ }, -}}; - -/************************************************************************/ -/* All supported pattern keywords must be declared here. */ -/************************************************************************/ -/* Note: must not be declared as its list will be overwritten */ -static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { - { "base", smp_fetch_base, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, - { "base32", smp_fetch_base32, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV }, - { "base32+src", smp_fetch_base32_src, 0, NULL, SMP_T_BIN, SMP_USE_HRQHV }, - - /* capture are allocated and are permanent in the stream */ - { "capture.req.hdr", smp_fetch_capture_header_req, ARG1(1,SINT), NULL, SMP_T_STR, SMP_USE_HRQHP }, - - /* retrieve these captures from the HTTP logs */ - { "capture.req.method", smp_fetch_capture_req_method, 0, NULL, SMP_T_STR, SMP_USE_HRQHP }, - { "capture.req.uri", smp_fetch_capture_req_uri, 0, NULL, SMP_T_STR, SMP_USE_HRQHP }, - { "capture.req.ver", smp_fetch_capture_req_ver, 0, NULL, SMP_T_STR, SMP_USE_HRQHP }, - - { "capture.res.hdr", smp_fetch_capture_header_res, ARG1(1,SINT), NULL, SMP_T_STR, SMP_USE_HRSHP }, - { "capture.res.ver", smp_fetch_capture_res_ver, 0, NULL, SMP_T_STR, SMP_USE_HRQHP }, - - /* cookie is valid in both directions (eg: for "stick ...") but cook* - * are only here to match the ACL's name, are request-only and are used - * for ACL compatibility only. - */ - { "cook", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRQHV }, - { "cookie", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRQHV|SMP_USE_HRSHV }, - { "cook_cnt", smp_fetch_cookie_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, - { "cook_val", smp_fetch_cookie_val, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, - - /* hdr is valid in both directions (eg: for "stick ...") but hdr_* are - * only here to match the ACL's name, are request-only and are used for - * ACL compatibility only. - */ - { "hdr", smp_fetch_hdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRQHV|SMP_USE_HRSHV }, - { "hdr_cnt", smp_fetch_hdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, - { "hdr_ip", smp_fetch_hdr_ip, ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRQHV }, - { "hdr_val", smp_fetch_hdr_val, ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRQHV }, - - { "http_auth", smp_fetch_http_auth, ARG1(1,USR), NULL, SMP_T_BOOL, SMP_USE_HRQHV }, - { "http_auth_group", smp_fetch_http_auth_grp, ARG1(1,USR), NULL, SMP_T_STR, SMP_USE_HRQHV }, - { "http_first_req", smp_fetch_http_first_req, 0, NULL, SMP_T_BOOL, SMP_USE_HRQHP }, - { "method", smp_fetch_meth, 0, NULL, SMP_T_METH, SMP_USE_HRQHP }, - { "path", smp_fetch_path, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, - { "query", smp_fetch_query, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, - - /* HTTP protocol on the request path */ - { "req.proto_http", smp_fetch_proto_http, 0, NULL, SMP_T_BOOL, SMP_USE_HRQHP }, - { "req_proto_http", smp_fetch_proto_http, 0, NULL, SMP_T_BOOL, SMP_USE_HRQHP }, - - /* HTTP version on the request path */ - { "req.ver", smp_fetch_rqver, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, - { "req_ver", smp_fetch_rqver, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, - - { "req.body", smp_fetch_body, 0, NULL, SMP_T_BIN, SMP_USE_HRQHV }, - { "req.body_len", smp_fetch_body_len, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV }, - { "req.body_size", smp_fetch_body_size, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV }, - { "req.body_param", smp_fetch_body_param, ARG1(0,STR), NULL, SMP_T_BIN, SMP_USE_HRQHV }, - - { "req.hdrs", smp_fetch_hdrs, 0, NULL, SMP_T_BIN, SMP_USE_HRQHV }, - { "req.hdrs_bin", smp_fetch_hdrs_bin, 0, NULL, SMP_T_BIN, SMP_USE_HRQHV }, - - /* HTTP version on the response path */ - { "res.ver", smp_fetch_stver, 0, NULL, SMP_T_STR, SMP_USE_HRSHV }, - { "resp_ver", smp_fetch_stver, 0, NULL, SMP_T_STR, SMP_USE_HRSHV }, - - /* explicit req.{cook,hdr} are used to force the fetch direction to be request-only */ - { "req.cook", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRQHV }, - { "req.cook_cnt", smp_fetch_cookie_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, - { "req.cook_val", smp_fetch_cookie_val, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, - - { "req.fhdr", smp_fetch_fhdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRQHV }, - { "req.fhdr_cnt", smp_fetch_fhdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, - { "req.hdr", smp_fetch_hdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRQHV }, - { "req.hdr_cnt", smp_fetch_hdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, - { "req.hdr_ip", smp_fetch_hdr_ip, ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRQHV }, - { "req.hdr_names", smp_fetch_hdr_names, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRQHV }, - { "req.hdr_val", smp_fetch_hdr_val, ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRQHV }, - - /* explicit req.{cook,hdr} are used to force the fetch direction to be response-only */ - { "res.cook", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRSHV }, - { "res.cook_cnt", smp_fetch_cookie_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, - { "res.cook_val", smp_fetch_cookie_val, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, - - { "res.fhdr", smp_fetch_fhdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRSHV }, - { "res.fhdr_cnt", smp_fetch_fhdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, - { "res.hdr", smp_fetch_hdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRSHV }, - { "res.hdr_cnt", smp_fetch_hdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, - { "res.hdr_ip", smp_fetch_hdr_ip, ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRSHV }, - { "res.hdr_names", smp_fetch_hdr_names, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRSHV }, - { "res.hdr_val", smp_fetch_hdr_val, ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRSHV }, - - /* scook is valid only on the response and is used for ACL compatibility */ - { "scook", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRSHV }, - { "scook_cnt", smp_fetch_cookie_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, - { "scook_val", smp_fetch_cookie_val, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, - { "set-cookie", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRSHV }, /* deprecated */ - - /* shdr is valid only on the response and is used for ACL compatibility */ - { "shdr", smp_fetch_hdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRSHV }, - { "shdr_cnt", smp_fetch_hdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV }, - { "shdr_ip", smp_fetch_hdr_ip, ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRSHV }, - { "shdr_val", smp_fetch_hdr_val, ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRSHV }, - - { "status", smp_fetch_stcode, 0, NULL, SMP_T_SINT, SMP_USE_HRSHP }, - { "unique-id", smp_fetch_uniqueid, 0, NULL, SMP_T_STR, SMP_SRC_L4SRV }, - { "url", smp_fetch_url, 0, NULL, SMP_T_STR, SMP_USE_HRQHV }, - { "url32", smp_fetch_url32, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV }, - { "url32+src", smp_fetch_url32_src, 0, NULL, SMP_T_BIN, SMP_USE_HRQHV }, - { "url_ip", smp_fetch_url_ip, 0, NULL, SMP_T_IPV4, SMP_USE_HRQHV }, - { "url_port", smp_fetch_url_port, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV }, - { "url_param", smp_fetch_url_param, ARG2(0,STR,STR), NULL, SMP_T_STR, SMP_USE_HRQHV }, - { "urlp" , smp_fetch_url_param, ARG2(0,STR,STR), NULL, SMP_T_STR, SMP_USE_HRQHV }, - { "urlp_val", smp_fetch_url_param_val, ARG2(0,STR,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV }, - { /* END */ }, -}}; - - -/************************************************************************/ -/* All supported converter keywords must be declared here. */ -/************************************************************************/ -/* Note: must not be declared as its list will be overwritten */ -static struct sample_conv_kw_list sample_conv_kws = {ILH, { - { "http_date", sample_conv_http_date, ARG1(0,SINT), NULL, SMP_T_SINT, SMP_T_STR}, - { "language", sample_conv_q_prefered, ARG2(1,STR,STR), NULL, SMP_T_STR, SMP_T_STR}, - { "capture-req", smp_conv_req_capture, ARG1(1,SINT), NULL, SMP_T_STR, SMP_T_STR}, - { "capture-res", smp_conv_res_capture, ARG1(1,SINT), NULL, SMP_T_STR, SMP_T_STR}, - { "url_dec", sample_conv_url_dec, 0, NULL, SMP_T_STR, SMP_T_STR}, - { NULL, NULL, 0, 0, 0 }, -}}; - - -/************************************************************************/ -/* All supported http-request action keywords must be declared here. */ -/************************************************************************/ -struct action_kw_list http_req_actions = { - .kw = { - { "capture", parse_http_req_capture }, - { "reject", parse_http_action_reject }, - { "set-method", parse_set_req_line }, - { "set-path", parse_set_req_line }, - { "set-query", parse_set_req_line }, - { "set-uri", parse_set_req_line }, - { NULL, NULL } - } -}; - -struct action_kw_list http_res_actions = { - .kw = { - { "capture", parse_http_res_capture }, - { "set-status", parse_http_set_status }, - { NULL, NULL } - } -}; - __attribute__((constructor)) static void __http_protocol_init(void) { - acl_register_keywords(&acl_kws); - sample_register_fetches(&sample_fetch_keywords); - sample_register_convs(&sample_conv_kws); - http_req_keywords_register(&http_req_actions); - http_res_keywords_register(&http_res_actions); }