haproxy/src/http_fetch.c
Christopher Faulet eca8854555 MINOR: http_fetch/htx: Use stream flags instead of px mode in smp_prefetch_htx
In the function smp_prefetch_htx(), we must know if data in the channel's buffer
are structured or not. Before the proxy mode was tested. Now we test if the
stream is an HTX stream or not. If yes, we know the HTX is used to structure
data in the channel's buffer.
2019-04-12 22:06:53 +02:00

2931 lines
83 KiB
C

/*
* 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/h1.h>
#include <common/http.h>
#include <common/htx.h>
#include <common/initcall.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/hdr_idx.h>
#include <proto/http_fetch.h>
#include <proto/http_htx.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;
static THREAD_LOCAL struct http_hdr_ctx static_http_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 sample *smp)
{
struct stream *s = smp->strm;
struct http_txn *txn = s->txn;
struct buffer auth_method;
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;
if (IS_HTX_STRM(s) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = htxbuf(&s->req.buf);
struct http_hdr_ctx ctx = { .blk = NULL };
struct ist hdr;
if (txn->flags & TX_USE_PX_CONN)
hdr = ist("Proxy-Authorization");
else
hdr = ist("Authorization");
ctx.blk = NULL;
if (!http_find_header(htx, hdr, &ctx, 0))
return 0;
p = memchr(ctx.value.ptr, ' ', ctx.value.len);
len = p - ctx.value.ptr;
if (!p || len <= 0)
return 0;
if (chunk_initlen(&auth_method, ctx.value.ptr, 0, len) != 1)
return 0;
chunk_initlen(&txn->auth.method_data, p + 1, 0, ctx.value.len - len - 1);
}
else {
/* LEGACY version */
struct hdr_ctx 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.
*
* The function returns :
* NULL with SMP_F_MAY_CHANGE in the sample flags if some data is missing to
* decide whether or not an HTTP message is present ;
* NULL if the requested data cannot be fetched or if it is certain that
* we'll never have any HTTP message there ;
* The HTX message if ready
*/
struct htx *smp_prefetch_htx(struct sample *smp, const struct arg *args)
{
struct stream *s = smp->strm;
unsigned int opt = smp->opt;
struct http_txn *txn = NULL;
struct htx *htx = NULL;
struct htx_sl *sl;
/* 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 NULL;
if (!s->txn) {
if (unlikely(!http_alloc_txn(s)))
return NULL; /* not enough memory */
http_init_txn(s);
txn = s->txn;
}
if (IS_HTX_STRM(s)) {
if ((opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) {
htx = htxbuf(&s->req.buf);
if (htx_is_empty(htx) || htx_get_tail_type(htx) < HTX_BLK_EOH) {
/* Parsing is done by the mux, just wait */
smp->flags |= SMP_F_MAY_CHANGE;
return NULL;
}
/* 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 (txn) {
sl = http_find_stline(htx);
txn->meth = sl->info.req.meth;
if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)
s->flags |= SF_REDIRECTABLE;
}
/* otherwise everything's ready for the request */
}
else {
htx = htxbuf(&s->res.buf);
if (htx_is_empty(htx) || htx_get_tail_type(htx) < HTX_BLK_EOH) {
/* Parsing is done by the mux, just wait */
smp->flags |= SMP_F_MAY_CHANGE;
return NULL;
}
}
}
else { /* RAW mode */
if ((opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) {
struct buffer *buf;
struct h1m h1m;
struct http_hdr hdrs[MAX_HTTP_HDR];
union h1_sl h1sl;
unsigned int flags = HTX_FL_NONE;
int ret;
buf = &s->req.buf;
if (b_head(buf) + b_data(buf) > b_wrap(buf))
b_slow_realign(buf, trash.area, 0);
h1m_init_req(&h1m);
ret = h1_headers_to_hdr_list(b_head(buf), b_stop(buf),
hdrs, sizeof(hdrs)/sizeof(hdrs[0]), &h1m, &h1sl);
if (ret <= 0) {
/* Invalid or too big*/
if (ret < 0 || channel_full(&s->req, global.tune.maxrewrite))
return NULL;
/* wait for a full request */
smp->flags |= SMP_F_MAY_CHANGE;
return NULL;
}
/* OK we just got a valid HTTP request. We have to
* convert it into an HTX message.
*/
if (unlikely(h1sl.rq.v.len == 0)) {
/* try to convert HTTP/0.9 requests to HTTP/1.0 */
if (h1sl.rq.meth != HTTP_METH_GET || !h1sl.rq.u.len)
return NULL;
h1sl.rq.v = ist("HTTP/1.0");
}
else if ((h1sl.rq.v.len == 8) &&
((*(h1sl.rq.v.ptr + 5) > '1') ||
((*(h1sl.rq.v.ptr + 5) == '1') && (*(h1sl.rq.v.ptr + 7) >= '1'))))
h1m.flags |= H1_MF_VER_11;
/* Set HTX start-line flags */
if (h1m.flags & H1_MF_VER_11)
flags |= HTX_SL_F_VER_11;
if (h1m.flags & H1_MF_XFER_ENC)
flags |= HTX_SL_F_XFER_ENC;
if (h1m.flags & H1_MF_XFER_LEN) {
flags |= HTX_SL_F_XFER_LEN;
if (h1m.flags & H1_MF_CHNK)
flags |= HTX_SL_F_CHNK;
else if (h1m.flags & H1_MF_CLEN)
flags |= HTX_SL_F_CLEN;
}
htx = htx_from_buf(get_trash_chunk());
sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, h1sl.rq.m, h1sl.rq.u, h1sl.rq.v);
if (!sl || !htx_add_all_headers(htx, hdrs))
return NULL;
sl->info.req.meth = h1sl.rq.meth;
if (txn) {
txn->meth = h1sl.rq.meth;
if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)
s->flags |= SF_REDIRECTABLE;
}
/* Ok, now everything's ready for the request */
}
else {
/* Impossible, no HTTP fetch on tcp-response */
return NULL;
}
}
/* everything's OK */
smp->data.u.sint = 1;
return htx;
}
/* 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;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
if (!htx)
return 0;
txn = smp->strm->txn;
meth = txn->meth;
smp->data.type = SMP_T_METH;
smp->data.u.meth.meth = meth;
if (meth == HTTP_METH_OTHER) {
struct htx_sl *sl;
if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE)
/* ensure the indexes are not affected */
return 0;
sl = http_find_stline(htx);
smp->flags |= SMP_F_CONST;
smp->data.u.meth.str.area = HTX_SL_REQ_MPTR(sl);
smp->data.u.meth.str.data = HTX_SL_REQ_MLEN(sl);
}
smp->flags |= SMP_F_VOL_1ST;
}
else {
/* LEGACY version */
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;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct htx_sl *sl;
if (!htx)
return 0;
sl = http_find_stline(htx);
len = HTX_SL_REQ_VLEN(sl);
ptr = HTX_SL_REQ_VPTR(sl);
}
else {
/* LEGACY version */
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;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct htx_sl *sl;
if (!htx)
return 0;
sl = http_find_stline(htx);
len = HTX_SL_RES_VLEN(sl);
ptr = HTX_SL_RES_VPTR(sl);
}
else {
/* LEGACY version */
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;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct htx_sl *sl;
if (!htx)
return 0;
sl = http_find_stline(htx);
len = HTX_SL_RES_CLEN(sl);
ptr = HTX_SL_RES_CPTR(sl);
}
else {
/* LEGACY version */
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 which separes headers from the body. This is useful
* for some headers analysis.
*/
static int smp_fetch_hdrs(const struct arg *args, struct sample *smp, const char *kw, void *private)
{
struct http_txn *txn;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct buffer *temp;
int32_t pos;
if (!htx)
return 0;
temp = get_trash_chunk();
for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
struct htx_blk *blk = htx_get_blk(htx, pos);
enum htx_blk_type type = htx_get_blk_type(blk);
if (type == HTX_BLK_HDR) {
struct ist n = htx_get_blk_name(htx, blk);
struct ist v = htx_get_blk_value(htx, blk);
if (!htx_hdr_to_h1(n, v, temp))
return 0;
}
else if (type == HTX_BLK_EOH) {
if (!chunk_memcat(temp, "\r\n", 2))
return 0;
break;
}
}
smp->data.type = SMP_T_STR;
smp->data.u.str = *temp;
}
else {
/* LEGACY version */
struct http_msg *msg;
struct hdr_idx *idx;
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_txn *txn;
struct buffer *temp;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct buffer *temp;
char *p, *end;
int32_t pos;
int ret;
if (!htx)
return 0;
temp = get_trash_chunk();
p = temp->area;
end = temp->area + temp->size;
for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
struct htx_blk *blk = htx_get_blk(htx, pos);
enum htx_blk_type type = htx_get_blk_type(blk);
struct ist n, v;
if (type == HTX_BLK_HDR) {
n = htx_get_blk_name(htx,blk);
v = htx_get_blk_value(htx, blk);
/* encode the header name. */
ret = encode_varint(n.len, &p, end);
if (ret == -1)
return 0;
if (p + n.len > end)
return 0;
memcpy(p, n.ptr, n.len);
p += n.len;
/* encode the header value. */
ret = encode_varint(v.len, &p, end);
if (ret == -1)
return 0;
if (p + v.len > end)
return 0;
memcpy(p, v.ptr, v.len);
p += v.len;
}
else if (type == HTX_BLK_EOH) {
/* encode the end of the header list with empty
* header name and header value.
*/
ret = encode_varint(0, &p, end);
if (ret == -1)
return 0;
ret = encode_varint(0, &p, end);
if (ret == -1)
return 0;
break;
}
}
/* 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 = p - temp->area;
smp->data.u.str.size = temp->size;
}
else {
/* LEGACY version */
struct http_msg *msg;
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;
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 buffer *temp;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
int32_t pos;
if (!htx)
return 0;
temp = get_trash_chunk();
for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
struct htx_blk *blk = htx_get_blk(htx, pos);
enum htx_blk_type type = htx_get_blk_type(blk);
if (type == HTX_BLK_EOM || type == HTX_BLK_EOD)
break;
if (type == HTX_BLK_DATA) {
if (!htx_data_to_h1(htx_get_blk_value(htx, blk), temp, 0))
return 0;
}
}
smp->data.type = SMP_T_BIN;
smp->data.u.str = *temp;
smp->flags = SMP_F_VOL_TEST;
}
else {
/* LEGACY version */
struct http_msg *msg;
unsigned long len;
unsigned long block1;
char *body;
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)
{
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
int32_t pos;
unsigned long long len = 0;
if (!htx)
return 0;
for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
struct htx_blk *blk = htx_get_blk(htx, pos);
enum htx_blk_type type = htx_get_blk_type(blk);
if (type == HTX_BLK_EOM || type == HTX_BLK_EOD)
break;
if (type == HTX_BLK_DATA)
len += htx_get_blksz(blk);
}
smp->data.type = SMP_T_SINT;
smp->data.u.sint = len;
smp->flags = SMP_F_VOL_TEST;
}
else {
/* LEGACY version */
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)
{
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
int32_t pos;
unsigned long long len = 0;
if (!htx)
return 0;
for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
struct htx_blk *blk = htx_get_blk(htx, pos);
enum htx_blk_type type = htx_get_blk_type(blk);
if (type == HTX_BLK_EOM || type == HTX_BLK_EOD)
break;
if (type == HTX_BLK_DATA)
len += htx_get_blksz(blk);
}
if (htx->extra != ULLONG_MAX)
len += htx->extra;
smp->data.type = SMP_T_SINT;
smp->data.u.sint = len;
smp->flags = SMP_F_VOL_TEST;
}
else {
/* LEGACY version */
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;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct htx_sl *sl;
if (!htx)
return 0;
sl = http_find_stline(htx);
smp->data.type = SMP_T_STR;
smp->data.u.str.area = HTX_SL_REQ_UPTR(sl);
smp->data.u.str.data = HTX_SL_REQ_ULEN(sl);
smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
}
else {
/* LEGACY version */
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;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct htx_sl *sl;
if (!htx)
return 0;
sl = http_find_stline(htx);
url2sa(HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl), &addr, NULL);
}
else {
/* LEGACY version */
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;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct htx_sl *sl;
if (!htx)
return 0;
sl = http_find_stline(htx);
url2sa(HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl), &addr, NULL);
}
else {
/* LEGACY version */
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)
{
int occ = 0;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct http_hdr_ctx *ctx = smp->ctx.a[0];
struct ist name;
if (!ctx) {
/* first call */
ctx = &static_http_hdr_ctx;
ctx->blk = NULL;
smp->ctx.a[0] = ctx;
}
if (args) {
if (args[0].type != ARGT_STR)
return 0;
name.ptr = args[0].data.str.area;
name.len = args[0].data.str.data;
if (args[1].type == ARGT_SINT)
occ = args[1].data.sint;
}
if (!htx)
return 0;
if (ctx && !(smp->flags & SMP_F_NOT_LAST))
/* search for header from the beginning */
ctx->blk = NULL;
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_htx_fhdr(htx, name, occ, ctx, &smp->data.u.str.area, &smp->data.u.str.data))
return 1;
}
else {
/* LEGACY version */
struct hdr_idx *idx;
struct hdr_ctx *ctx = smp->ctx.a[0];
const struct http_msg *msg;
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)
{
int cnt;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct http_hdr_ctx ctx;
struct ist name;
if (!htx)
return 0;
if (args && args->type == ARGT_STR) {
name.ptr = args->data.str.area;
name.len = args->data.str.data;
} else {
name.ptr = NULL;
name.len = 0;
}
ctx.blk = NULL;
cnt = 0;
while (http_find_header(htx, name, &ctx, 1))
cnt++;
}
else {
/* LEGACY version */
struct hdr_idx *idx;
struct hdr_ctx ctx;
const struct http_msg *msg;
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 buffer *temp;
char del = ',';
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
int32_t pos;
if (!htx)
return 0;
if (args && args->type == ARGT_STR)
del = *args[0].data.str.area;
temp = get_trash_chunk();
for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
struct htx_blk *blk = htx_get_blk(htx, pos);
enum htx_blk_type type = htx_get_blk_type(blk);
struct ist n;
if (type == HTX_BLK_EOH)
break;
if (type != HTX_BLK_HDR)
continue;
n = htx_get_blk_name(htx, blk);
if (temp->data)
temp->area[temp->data++] = del;
chunk_memcat(temp, n.ptr, n.len);
}
}
else {
/* LEGACY version */
struct hdr_idx *idx;
struct hdr_ctx ctx;
const struct http_msg *msg;
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 = *temp;
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)
{
int occ = 0;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct http_hdr_ctx *ctx = smp->ctx.a[0];
struct ist name;
if (!ctx) {
/* first call */
ctx = &static_http_hdr_ctx;
ctx->blk = NULL;
smp->ctx.a[0] = ctx;
}
if (args) {
if (args[0].type != ARGT_STR)
return 0;
name.ptr = args[0].data.str.area;
name.len = args[0].data.str.data;
if (args[1].type == ARGT_SINT)
occ = args[1].data.sint;
}
if (!htx)
return 0;
if (ctx && !(smp->flags & SMP_F_NOT_LAST))
/* search for header from the beginning */
ctx->blk = NULL;
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_htx_hdr(htx, name, occ, ctx, &smp->data.u.str.area, &smp->data.u.str.data))
return 1;
}
else {
/* LEGACY version */
struct hdr_idx *idx;
struct hdr_ctx *ctx = smp->ctx.a[0];
const struct http_msg *msg;
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)
{
int cnt;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct http_hdr_ctx ctx;
struct ist name;
if (!htx)
return 0;
if (args && args->type == ARGT_STR) {
name.ptr = args->data.str.area;
name.len = args->data.str.data;
} else {
name.ptr = NULL;
name.len = 0;
}
ctx.blk = NULL;
cnt = 0;
while (http_find_header(htx, name, &ctx, 0))
cnt++;
}
else {
/* LEGACY version */
struct hdr_idx *idx;
struct hdr_ctx ctx;
const struct http_msg *msg;
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)
{
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct htx_sl *sl;
struct ist path;
size_t len;
if (!htx)
return 0;
sl = http_find_stline(htx);
path = http_get_path(htx_sl_req_uri(sl));
if (!path.ptr)
return 0;
for (len = 0; len < path.len && *(path.ptr + len) != '?'; len++)
;
/* OK, we got the '/' ! */
smp->data.type = SMP_T_STR;
smp->data.u.str.area = path.ptr;
smp->data.u.str.data = len;
smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
}
else {
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 buffer *temp;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct htx_sl *sl;
struct http_hdr_ctx ctx;
struct ist path;
if (!htx)
return 0;
ctx.blk = NULL;
if (!http_find_header(htx, ist("Host"), &ctx, 0) || !ctx.value.len)
return smp_fetch_path(args, smp, kw, private);
/* OK we have the header value in ctx.value */
temp = get_trash_chunk();
chunk_memcat(temp, ctx.value.ptr, ctx.value.len);
/* now retrieve the path */
sl = http_find_stline(htx);
path = http_get_path(htx_sl_req_uri(sl));
if (path.ptr) {
size_t len;
for (len = 0; len < path.len && *(path.ptr + len) != '?'; len++)
;
if (len && *(path.ptr) == '/')
chunk_memcat(temp, path.ptr, len);
}
smp->data.type = SMP_T_STR;
smp->data.u.str = *temp;
}
else {
/* LEGACY version */
struct http_txn *txn;
char *ptr, *end, *beg;
struct hdr_ctx ctx;
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)
{
unsigned int hash = 0;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct htx_sl *sl;
struct http_hdr_ctx ctx;
struct ist path;
if (!htx)
return 0;
ctx.blk = NULL;
if (http_find_header(htx, ist("Host"), &ctx, 0)) {
/* OK we have the header value in ctx.value */
while (ctx.value.len--)
hash = *(ctx.value.ptr++) + (hash << 6) + (hash << 16) - hash;
}
/* now retrieve the path */
sl = http_find_stline(htx);
path = http_get_path(htx_sl_req_uri(sl));
if (path.ptr) {
size_t len;
for (len = 0; len < path.len && *(path.ptr + len) != '?'; len++)
;
if (len && *(path.ptr) == '/') {
while (len--)
hash = *(path.ptr++) + (hash << 6) + (hash << 16) - hash;
}
}
}
else {
/* LEGACY version */
struct http_txn *txn;
struct hdr_ctx ctx;
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)
{
char *ptr, *end;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct htx_sl *sl;
if (!htx)
return 0;
sl = http_find_stline(htx);
ptr = HTX_SL_REQ_UPTR(sl);
end = HTX_SL_REQ_UPTR(sl) + HTX_SL_REQ_ULEN(sl);
}
else {
/* LEGACY version */
struct http_txn *txn;
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)
{
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
if (!htx)
return 0;
}
else {
/* LEGACY version */
/* 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;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
if (!htx)
return 0;
}
else {
/* LEGACY version */
CHECK_HTTP_MESSAGE_FIRST();
}
if (!get_http_auth(smp))
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;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
if (!htx)
return 0;
}
else {
/* LEGACY version */
CHECK_HTTP_MESSAGE_FIRST();
}
if (!get_http_auth(smp))
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;
/* skip the first space and find space after URI */
path = ist2(++ptr, 0);
while (*ptr != ' ' && *ptr != '\0')
ptr++;
path.len = ptr - path.ptr;
path = http_get_path(path);
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)
{
int occ = 0;
int found = 0;
if (!args || args->type != ARGT_STR)
return 0;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct http_hdr_ctx *ctx = smp->ctx.a[2];
struct ist hdr;
if (!ctx) {
/* first call */
ctx = &static_http_hdr_ctx;
ctx->blk = NULL;
smp->ctx.a[2] = ctx;
}
if (!htx)
return 0;
hdr = (((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ)
? ist("Cookie")
: ist("Set-Cookie"));
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.
*/
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->blk = NULL;
}
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_header(htx, hdr, ctx, 0))
goto out;
if (ctx->value.len < args->data.str.data + 1)
continue;
smp->ctx.a[0] = ctx->value.ptr;
smp->ctx.a[1] = smp->ctx.a[0] + ctx->value.len;
}
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 */
}
}
else {
/* LEGACY version */
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;
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)
{
char *val_beg, *val_end;
int cnt;
if (!args || args->type != ARGT_STR)
return 0;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct http_hdr_ctx ctx;
struct ist hdr;
if (!htx)
return 0;
hdr = (((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ)
? ist("Cookie")
: ist("Set-Cookie"));
val_end = val_beg = NULL;
ctx.blk = NULL;
cnt = 0;
while (1) {
/* Note: val_beg == NULL every time we need to fetch a new header */
if (!val_beg) {
if (!http_find_header(htx, hdr, &ctx, 0))
break;
if (ctx.value.len < args->data.str.data + 1)
continue;
val_beg = ctx.value.ptr;
val_end = val_beg + ctx.value.len;
}
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++;
}
}
}
else {
/* LEGACY version */
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;
char *sol;
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)
{
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
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct htx_sl *sl;
if (!htx)
return 0;
sl = http_find_stline(htx);
smp->ctx.a[0] = http_find_param_list(HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl), delim);
if (!smp->ctx.a[0])
return 0;
smp->ctx.a[1] = HTX_SL_REQ_UPTR(sl) + HTX_SL_REQ_ULEN(sl);
}
else {
/* LEGACY version */
struct http_msg *msg;
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)
{
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
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct buffer *temp;
int32_t pos;
if (!htx)
return 0;
temp = get_trash_chunk();
for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
struct htx_blk *blk = htx_get_blk(htx, pos);
enum htx_blk_type type = htx_get_blk_type(blk);
if (type == HTX_BLK_EOM || type == HTX_BLK_EOD)
break;
if (type == HTX_BLK_DATA) {
if (!htx_data_to_h1(htx_get_blk_value(htx, blk), temp, 0))
return 0;
}
}
smp->ctx.a[0] = temp->area;
smp->ctx.a[1] = temp->area + temp->data;
/* Assume that the context is filled with NULL pointer
* before the first call.
* smp->ctx.a[2] = NULL;
* smp->ctx.a[3] = NULL;
*/
}
else {
/* LEGACY version */
struct http_msg *msg;
unsigned long len;
unsigned long block1;
char *body;
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)
{
unsigned int hash = 0;
if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) {
/* HTX version */
struct htx *htx = smp_prefetch_htx(smp, args);
struct http_hdr_ctx ctx;
struct htx_sl *sl;
struct ist path;
if (!htx)
return 0;
ctx.blk = NULL;
if (http_find_header(htx, ist("Host"), &ctx, 1)) {
/* OK we have the header value in ctx.value */
while (ctx.value.len--)
hash = *(ctx.value.ptr++) + (hash << 6) + (hash << 16) - hash;
}
/* now retrieve the path */
sl = http_find_stline(htx);
path = http_get_path(htx_sl_req_uri(sl));
while (path.len > 0 && *(path.ptr) != '?') {
path.ptr++;
path.len--;
}
if (path.len && *(path.ptr) == '/') {
while (path.len--)
hash = *(path.ptr++) + (hash << 6) + (hash << 16) - hash;
}
}
else {
/* LEGACY version */
struct http_txn *txn;
struct hdr_ctx ctx;
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 */ },
}};
INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/