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 \
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 */
--- /dev/null
+/*
+ * 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 <common/config.h>
+#include <common/mini-clist.h>
+#include <types/action.h>
+#include <types/proxy.h>
+
+/* 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:
+ */
* 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);
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);
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);
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)
{
#include <common/buffer.h>
#include <common/errors.h>
#include <proto/arg.h>
+#include <proto/http_fetch.h>
#include <proto/log.h>
#include <proto/proto_http.h>
#include <proto/sample.h>
#include <common/cfgparse.h>
#include <common/errors.h>
#include <proto/arg.h>
+#include <proto/http_fetch.h>
#include <proto/log.h>
#include <proto/proto_http.h>
#include <proto/sample.h>
#include <proto/hdr_idx.h>
#include <proto/hlua.h>
#include <proto/hlua_fcn.h>
+#include <proto/http_fetch.h>
#include <proto/map.h>
#include <proto/obj_type.h>
#include <proto/queue.h>
--- /dev/null
+/*
+ * HTTP ACLs declaration
+ *
+ * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
+ *
+ * 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 <sys/types.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+#include <common/chunk.h>
+#include <common/compat.h>
+#include <common/config.h>
+#include <common/debug.h>
+#include <common/http.h>
+#include <common/memory.h>
+#include <common/standard.h>
+#include <common/version.h>
+
+#include <types/global.h>
+
+#include <proto/acl.h>
+#include <proto/arg.h>
+#include <proto/auth.h>
+#include <proto/pattern.h>
+
+
+/* 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 <const> 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:
+ */
--- /dev/null
+/*
+ * HTTP actions
+ *
+ * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
+ *
+ * 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 <sys/types.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+#include <common/chunk.h>
+#include <common/compat.h>
+#include <common/config.h>
+#include <common/debug.h>
+#include <common/http.h>
+#include <common/memory.h>
+#include <common/standard.h>
+#include <common/version.h>
+
+#include <types/capture.h>
+#include <types/global.h>
+
+#include <proto/acl.h>
+#include <proto/arg.h>
+#include <proto/log.h>
+#include <proto/proto_http.h>
+
+
+/* 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 <http.action>, 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 <format>");
+ 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: <status>; or 3 arguments: <status> reason <fmt>");
+ 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 <expression> [ 'len' <length> | id <idx> ]");
+ 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 <expression> id <idx>");
+ 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:
+ */
--- /dev/null
+/*
+ * HTTP sample conversion
+ *
+ * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
+ *
+ * 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 <sys/types.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+#include <common/chunk.h>
+#include <common/compat.h>
+#include <common/config.h>
+#include <common/debug.h>
+#include <common/http.h>
+#include <common/memory.h>
+#include <common/standard.h>
+#include <common/version.h>
+
+#include <types/capture.h>
+#include <types/global.h>
+
+#include <proto/arg.h>
+#include <proto/sample.h>
+#include <proto/stream.h>
+
+
+/* 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 <const> 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:
+ */
--- /dev/null
+/*
+ * HTTP samples fetching
+ *
+ * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
+ *
+ * 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 <sys/types.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+#include <common/base64.h>
+#include <common/chunk.h>
+#include <common/compat.h>
+#include <common/config.h>
+#include <common/debug.h>
+#include <common/http.h>
+#include <common/memory.h>
+#include <common/standard.h>
+#include <common/version.h>
+
+#include <types/global.h>
+
+#include <proto/arg.h>
+#include <proto/auth.h>
+#include <proto/http_fetch.h>
+#include <proto/log.h>
+#include <proto/obj_type.h>
+#include <proto/proto_http.h>
+#include <proto/sample.h>
+#include <proto/stream.h>
+
+
+/* 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 <req_vol> 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 <s> 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 <len> and <ptr> is NULL ;
+ * - if the method is unknown (HTTP_METH_OTHER), <ptr> points to the text and
+ * <len> 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 <multi> 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 <err> 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 <const> 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:
+ */
.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);
}
}
-/*
- * 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;
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 <req_vol> is non-null during a request prefetch,
- * another test is made to ensure the required information is not gone.
+/* 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
+ * pointers to the next elements which were moved, and the total buffer length.
+ * It finds the action to be performed in p[2], previously filled by function
+ * parse_set_req_line(). It returns 0 in case of success, -1 in case of internal
+ * error, though this can be revisited when this code is finally exploited.
+ *
+ * 'action' can be '0' to replace method, '1' to replace path, '2' to replace
+ * query string and 3 to replace uri.
*
- * 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
+ * In query string case, the mark question '?' must be set at the start of the
+ * string by the caller, event if the replacement query string is empty.
*/
-int smp_prefetch_http(struct proxy *px, struct stream *s, unsigned int opt,
- const struct arg *args, struct sample *smp, int req_vol)
+int http_replace_req_line(int action, const char *replace, int len,
+ struct proxy *px, struct stream *s)
{
- struct http_txn *txn;
- struct http_msg *msg;
-
- /* Note: it is possible that <s> 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;
+ struct http_txn *txn = s->txn;
+ char *cur_ptr, *cur_end;
+ int offset = 0;
+ int delta;
- /* Check for a dependency on a request */
- smp->data.type = SMP_T_BOOL;
+ switch (action) {
+ case 0: // method
+ cur_ptr = ci_head(&s->req);
+ cur_end = cur_ptr + txn->req.sl.rq.m_l;
- 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);
+ /* adjust req line offsets and lengths */
+ delta = len - offset - (cur_end - cur_ptr);
+ txn->req.sl.rq.m_l += delta;
+ txn->req.sl.rq.u += delta;
+ txn->req.sl.rq.v += delta;
+ break;
- if (unlikely(txn->req.msg_state < HTTP_MSG_BODY)) {
- if (msg->msg_state == HTTP_MSG_ERROR)
- return 0;
+ case 1: // path
+ cur_ptr = http_txn_get_path(txn);
+ if (!cur_ptr)
+ cur_ptr = ci_head(&s->req) + txn->req.sl.rq.u;
- /* Try to decode HTTP request */
- if (likely(msg->next < ci_data(&s->req)))
- http_msg_analyzer(msg, &txn->hdr_idx);
+ cur_end = cur_ptr;
+ while (cur_end < ci_head(&s->req) + txn->req.sl.rq.u + txn->req.sl.rq.u_l && *cur_end != '?')
+ cur_end++;
- /* 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;
- }
+ /* adjust req line offsets and lengths */
+ delta = len - offset - (cur_end - cur_ptr);
+ txn->req.sl.rq.u_l += delta;
+ txn->req.sl.rq.v += delta;
+ break;
- /* OK we just got a valid HTTP request. We have some minor
- * preparation to perform so that further checks can rely
- * on HTTP tests.
- */
+ case 2: // query
+ offset = 1;
+ cur_ptr = ci_head(&s->req) + txn->req.sl.rq.u;
+ cur_end = cur_ptr + txn->req.sl.rq.u_l;
+ while (cur_ptr < cur_end && *cur_ptr != '?')
+ cur_ptr++;
- /* 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;
- }
+ /* skip the question mark or indicate that we must insert it
+ * (but only if the format string is not empty then).
+ */
+ if (cur_ptr < cur_end)
+ cur_ptr++;
+ else if (len > 1)
+ offset = 0;
- 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;
+ /* adjust req line offsets and lengths */
+ delta = len - offset - (cur_end - cur_ptr);
+ txn->req.sl.rq.u_l += delta;
+ txn->req.sl.rq.v += delta;
+ break;
- if (unlikely(msg->sl.rq.v_l == 0) && !http_upgrade_v09_to_v10(txn))
- return 0;
- }
+ case 3: // uri
+ cur_ptr = ci_head(&s->req) + txn->req.sl.rq.u;
+ cur_end = cur_ptr + txn->req.sl.rq.u_l;
- if (req_vol && txn->rsp.msg_state != HTTP_MSG_RPBEFORE) {
- return 0; /* data might have moved and indexes changed */
- }
+ /* adjust req line offsets and lengths */
+ delta = len - offset - (cur_end - cur_ptr);
+ txn->req.sl.rq.u_l += delta;
+ txn->req.sl.rq.v += delta;
+ break;
- /* 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;
- }
+ default:
+ return -1;
}
- /* 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;
+ /* commit changes and adjust end of message */
+ delta = b_rep_blk(&s->req.buf, cur_ptr, cur_end, replace + offset, len - offset);
+ txn->req.sl.rq.l += delta;
+ txn->hdr_idx.v[0].len += delta;
+ http_msg_move_end(&txn->req, delta);
+ return 0;
}
-/* 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 <len> and <ptr> is NULL ;
- * - if the method is unknown (HTTP_METH_OTHER), <ptr> points to the text and
- * <len> to its length.
- * This is intended to be used with pat_match_meth() only.
+/* This function replace the HTTP status code and the associated message. The
+ * variable <status> contains the new status code. This function never fails.
*/
-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)
+void http_set_status(unsigned int status, const char *reason, struct stream *s)
{
- 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);
+ struct http_txn *txn = s->txn;
+ char *cur_ptr, *cur_end;
+ int delta;
+ char *res;
+ int c_l;
+ const char *msg = reason;
+ int msg_len;
- while ((len-- > 0) && (*ptr++ != '/'));
- if (len <= 0)
- return 0;
+ chunk_reset(&trash);
- smp->data.type = SMP_T_STR;
- smp->data.u.str.area = ptr;
- smp->data.u.str.data = len;
+ res = ultoa_o(status, trash.area, trash.size);
+ c_l = res - trash.area;
- smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
- return 1;
-}
+ trash.area[c_l] = ' ';
+ trash.data = c_l + 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;
+ /* Do we have a custom reason format string? */
+ if (msg == NULL)
+ msg = http_get_reason(status);
+ msg_len = strlen(msg);
+ strncpy(&trash.area[trash.data], msg, trash.size - trash.data);
+ trash.data += msg_len;
- CHECK_HTTP_MESSAGE_FIRST();
+ cur_ptr = ci_head(&s->res) + txn->rsp.sl.st.c;
+ cur_end = ci_head(&s->res) + txn->rsp.sl.st.r + txn->rsp.sl.st.r_l;
- txn = smp->strm->txn;
- if (txn->rsp.msg_state < HTTP_MSG_BODY)
- return 0;
+ /* commit changes and adjust message */
+ delta = b_rep_blk(&s->res.buf, cur_ptr, cur_end, trash.area,
+ trash.data);
- len = txn->rsp.sl.st.c_l;
- ptr = ci_head(txn->rsp.chn) + txn->rsp.sl.st.c;
+ /* adjust res line offsets and lengths */
+ txn->rsp.sl.st.r += c_l - txn->rsp.sl.st.c_l;
+ txn->rsp.sl.st.c_l = c_l;
+ txn->rsp.sl.st.r_l = msg_len;
- smp->data.type = SMP_T_SINT;
- smp->data.u.sint = __strl2ui(ptr, len);
- smp->flags = SMP_F_VOL_1ST;
- return 1;
+ delta = trash.data - (cur_end - cur_ptr);
+ txn->rsp.sl.st.l += delta;
+ txn->hdr_idx.v[0].len += delta;
+ http_msg_move_end(&txn->rsp, delta);
}
-static int
-smp_fetch_uniqueid(const struct arg *args, struct sample *smp, const char *kw, void *private)
+/*
+ * Return the struct http_req_action_kw associated to a keyword.
+ */
+struct action_kw *action_http_req_custom(const char *kw)
{
- 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;
+ return action_lookup(&http_req_keywords.list, kw);
}
-/* Returns a string block containing all headers including the
- * empty line wich separes headers from the body. This is useful
- * form some headers analysis.
+/*
+ * Return the struct http_res_action_kw associated to a keyword.
*/
-static int
-smp_fetch_hdrs(const struct arg *args, struct sample *smp, const char *kw, void *private)
+struct action_kw *action_http_res_custom(const char *kw)
{
- 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;
+ return action_lookup(&http_res_keywords.list, kw);
+}
- 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 <multi> 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 <err> 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
- * pointers to the next elements which were moved, and the total buffer length.
- * It finds the action to be performed in p[2], previously filled by function
- * parse_set_req_line(). It returns 0 in case of success, -1 in case of internal
- * error, though this can be revisited when this code is finally exploited.
- *
- * 'action' can be '0' to replace method, '1' to replace path, '2' to replace
- * query string and 3 to replace uri.
- *
- * In query string case, the mark question '?' must be set at the start of the
- * string by the caller, event if the replacement query string is empty.
- */
-int http_replace_req_line(int action, const char *replace, int len,
- struct proxy *px, struct stream *s)
-{
- struct http_txn *txn = s->txn;
- char *cur_ptr, *cur_end;
- int offset = 0;
- int delta;
-
- switch (action) {
- case 0: // method
- cur_ptr = ci_head(&s->req);
- cur_end = cur_ptr + txn->req.sl.rq.m_l;
-
- /* adjust req line offsets and lengths */
- delta = len - offset - (cur_end - cur_ptr);
- txn->req.sl.rq.m_l += delta;
- txn->req.sl.rq.u += delta;
- txn->req.sl.rq.v += delta;
- break;
-
- case 1: // path
- cur_ptr = http_txn_get_path(txn);
- if (!cur_ptr)
- cur_ptr = ci_head(&s->req) + txn->req.sl.rq.u;
-
- cur_end = cur_ptr;
- while (cur_end < ci_head(&s->req) + txn->req.sl.rq.u + txn->req.sl.rq.u_l && *cur_end != '?')
- cur_end++;
-
- /* adjust req line offsets and lengths */
- delta = len - offset - (cur_end - cur_ptr);
- txn->req.sl.rq.u_l += delta;
- txn->req.sl.rq.v += delta;
- break;
-
- case 2: // query
- offset = 1;
- cur_ptr = ci_head(&s->req) + txn->req.sl.rq.u;
- cur_end = cur_ptr + txn->req.sl.rq.u_l;
- while (cur_ptr < cur_end && *cur_ptr != '?')
- cur_ptr++;
-
- /* skip the question mark or indicate that we must insert it
- * (but only if the format string is not empty then).
- */
- if (cur_ptr < cur_end)
- cur_ptr++;
- else if (len > 1)
- offset = 0;
-
- /* adjust req line offsets and lengths */
- delta = len - offset - (cur_end - cur_ptr);
- txn->req.sl.rq.u_l += delta;
- txn->req.sl.rq.v += delta;
- break;
-
- case 3: // uri
- cur_ptr = ci_head(&s->req) + txn->req.sl.rq.u;
- cur_end = cur_ptr + txn->req.sl.rq.u_l;
-
- /* adjust req line offsets and lengths */
- delta = len - offset - (cur_end - cur_ptr);
- txn->req.sl.rq.u_l += delta;
- txn->req.sl.rq.v += delta;
- break;
-
- default:
- return -1;
- }
-
- /* commit changes and adjust end of message */
- delta = b_rep_blk(&s->req.buf, cur_ptr, cur_end, replace + offset, len - offset);
- txn->req.sl.rq.l += delta;
- txn->hdr_idx.v[0].len += delta;
- http_msg_move_end(&txn->req, delta);
- return 0;
-}
-
-/* This function replace the HTTP status code and the associated message. The
- * variable <status> contains the new status code. This function never fails.
- */
-void http_set_status(unsigned int status, const char *reason, struct stream *s)
-{
- struct http_txn *txn = s->txn;
- char *cur_ptr, *cur_end;
- int delta;
- char *res;
- int c_l;
- const char *msg = reason;
- int msg_len;
-
- chunk_reset(&trash);
-
- res = ultoa_o(status, trash.area, trash.size);
- c_l = res - trash.area;
-
- trash.area[c_l] = ' ';
- trash.data = c_l + 1;
-
- /* Do we have a custom reason format string? */
- if (msg == NULL)
- msg = http_get_reason(status);
- msg_len = strlen(msg);
- strncpy(&trash.area[trash.data], msg, trash.size - trash.data);
- trash.data += msg_len;
-
- cur_ptr = ci_head(&s->res) + txn->rsp.sl.st.c;
- cur_end = ci_head(&s->res) + txn->rsp.sl.st.r + txn->rsp.sl.st.r_l;
-
- /* commit changes and adjust message */
- delta = b_rep_blk(&s->res.buf, cur_ptr, cur_end, trash.area,
- trash.data);
-
- /* adjust res line offsets and lengths */
- txn->rsp.sl.st.r += c_l - txn->rsp.sl.st.c_l;
- txn->rsp.sl.st.c_l = c_l;
- txn->rsp.sl.st.r_l = msg_len;
-
- delta = trash.data - (cur_end - cur_ptr);
- txn->rsp.sl.st.l += delta;
- txn->hdr_idx.v[0].len += delta;
- 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 <http.action>, 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 <format>");
- 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: <status>; or 3 arguments: <status> reason <fmt>");
- 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 <expression> [ 'len' <length> | id <idx> ]");
- 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 <expression> id <idx>");
- 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.
- */
-struct action_kw *action_http_req_custom(const char *kw)
-{
- return action_lookup(&http_req_keywords.list, kw);
-}
-
-/*
- * Return the struct http_res_action_kw associated to a keyword.
- */
-struct action_kw *action_http_res_custom(const char *kw)
-{
- return action_lookup(&http_res_keywords.list, kw);
-}
-
-
-/************************************************************************/
-/* All supported ACL keywords must be declared here. */
-/************************************************************************/
-
-/* Note: must not be declared <const> 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 <const> 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 <const> 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);
}