mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-04-07 09:42:34 +00:00
MINOR: http: add full-length header fetch methods
The req.hdr and res.hdr fetch methods do not work well on headers which are allowed to contain commas, such as User-Agent, Date or Expires. More specifically, full-length matching is impossible if a comma is present. This patch introduces 4 new fetch functions which are designed to work with these full-length headers : - req.fhdr, req.fhdr_cnt - res.fhdr, res.fhdr_cnt These ones do not stop at commas and permit to return full-length header values.
This commit is contained in:
parent
570f221cbb
commit
04ff9f105f
@ -9915,6 +9915,23 @@ The list of currently supported pattern fetch functions is the following :
|
||||
an integer which is returned. If no name is specified, the first
|
||||
cookie value is returned.
|
||||
|
||||
req.fhdr(<name>[,<occ>])
|
||||
This extracts the last occurrence of header <name> in an HTTP
|
||||
request. Optionally, a specific occurrence might be specified as
|
||||
a position number. Positive values indicate a position from the
|
||||
first occurrence, with 1 being the first one. Negative values
|
||||
indicate positions relative to the last one, with -1 being the
|
||||
last one. It differs from req.hdr() in that any commas present
|
||||
in the value are returned and are not used as delimiters. This
|
||||
is sometimes useful with headers such as User-Agent.
|
||||
|
||||
req.fhdr_cnt([<name>])
|
||||
Returns an integer value representing the number of occurrences
|
||||
of request header field name <name>, or the total number of
|
||||
header fields if <name> is not specified. Contrary to its
|
||||
req.hdr_cnt() cousin, this function returns the number of full
|
||||
line headers and does not stop on commas.
|
||||
|
||||
req.hdr(<name>[,<occ>])
|
||||
This extracts the last occurrence of header <name> in an HTTP
|
||||
request. Optionally, a specific occurrence might be specified as
|
||||
@ -9922,12 +9939,14 @@ The list of currently supported pattern fetch functions is the following :
|
||||
first occurrence, with 1 being the first one. Negative values
|
||||
indicate positions relative to the last one, with -1 being the
|
||||
last one. A typical use is with the X-Forwarded-For header once
|
||||
converted to IP, associated with an IP stick-table.
|
||||
converted to IP, associated with an IP stick-table. The function
|
||||
considers any comma as a delimiter for distinct values.
|
||||
|
||||
req.hdr_cnt([<name>])
|
||||
Returns an integer value representing the number of occurrences
|
||||
of request header field name <name>, or the total number of
|
||||
header fields if <name> is not specified.
|
||||
header field values if <name> is not specified. The function
|
||||
considers any comma as a delimiter for distinct values.
|
||||
|
||||
req.hdr_ip([<name>[,<occ>]])
|
||||
This extracts the last occurrence of header <name> in an HTTP
|
||||
@ -10074,6 +10093,23 @@ The list of currently supported pattern fetch functions is the following :
|
||||
value to an integer which is returned. If no name is specified,
|
||||
the first cookie value is returned.
|
||||
|
||||
res.fhdr(<name>[,<occ>])
|
||||
This extracts the last occurrence of header <name> in an HTTP
|
||||
response. Optionally, a specific occurrence might be specified
|
||||
as a position number. Positive values indicate a position from
|
||||
the first occurrence, with 1 being the first one. Negative
|
||||
values indicate positions relative to the last one, with -1
|
||||
being the last one. It differs from res.hdr() in that any commas
|
||||
present in the value are returned and are not used as delimiters.
|
||||
This is sometimes useful with headers such as Date or Expires.
|
||||
|
||||
res.fhdr_cnt([<name>])
|
||||
Returns an integer value representing the number of occurrences
|
||||
of response header field name <name>, or the total number of
|
||||
header fields if <name> is not specified. Contrary to its
|
||||
res.hdr_cnt() cousin, this function returns the number of full
|
||||
line headers and does not stop on commas.
|
||||
|
||||
res.hdr(<name>[,<occ>])
|
||||
This extracts the last occurrence of header <name> in an HTTP
|
||||
response. Optionally, a specific occurrence might be specified
|
||||
@ -10081,12 +10117,14 @@ The list of currently supported pattern fetch functions is the following :
|
||||
the first occurrence, with 1 being the first one. Negative
|
||||
values indicate positions relative to the last one, with -1
|
||||
being the last one. This can be useful to learn some data into
|
||||
a stick-table.
|
||||
a stick-table. The function considers any comma as a delimiter
|
||||
for distinct values.
|
||||
|
||||
res.hdr_cnt([<name>])
|
||||
Returns an integer value representing the number of occurrences
|
||||
of response header field name <name>, or the total number of
|
||||
header fields if <name> is not specified.
|
||||
header fields if <name> is not specified. The function considers
|
||||
any comma as a delimiter for distinct values.
|
||||
|
||||
res.hdr_ip([<name>[,<occ>]])
|
||||
This extracts the last occurrence of header <name> in an HTTP
|
||||
|
@ -90,6 +90,9 @@ void manage_server_side_cookies(struct session *t, struct channel *rtr);
|
||||
void check_response_for_cacheability(struct session *t, struct channel *rtr);
|
||||
int stats_check_uri(struct stream_interface *si, struct http_txn *txn, struct proxy *backend);
|
||||
void init_proto_http();
|
||||
int http_find_full_header2(const char *name, int len,
|
||||
char *sol, struct hdr_idx *idx,
|
||||
struct hdr_ctx *ctx);
|
||||
int http_find_header2(const char *name, int len,
|
||||
char *sol, struct hdr_idx *idx,
|
||||
struct hdr_ctx *ctx);
|
||||
|
229
src/proto_http.c
229
src/proto_http.c
@ -488,6 +488,75 @@ int http_header_match2(const char *hdr, const char *end,
|
||||
return val - hdr;
|
||||
}
|
||||
|
||||
/* Find the first or next occurrence of header <name> in message buffer <sol>
|
||||
* using headers index <idx>, and return it in the <ctx> structure. This
|
||||
* structure holds everything necessary to use the header and find next
|
||||
* occurrence. If its <idx> member is 0, the header is searched from the
|
||||
* beginning. Otherwise, the next occurrence is returned. The function returns
|
||||
* 1 when it finds a value, and 0 when there is no more. It is very similar to
|
||||
* http_find_header2() except that it is designed to work with full-line headers
|
||||
* whose comma is not a delimiter but is part of the syntax. As a special case,
|
||||
* if ctx->val is NULL when searching for a new values of a header, the current
|
||||
* header is rescanned. This allows rescanning after a header deletion.
|
||||
*/
|
||||
int http_find_full_header2(const char *name, int len,
|
||||
char *sol, struct hdr_idx *idx,
|
||||
struct hdr_ctx *ctx)
|
||||
{
|
||||
char *eol, *sov;
|
||||
int cur_idx, old_idx;
|
||||
|
||||
cur_idx = ctx->idx;
|
||||
if (cur_idx) {
|
||||
/* We have previously returned a header, let's search another one */
|
||||
sol = ctx->line;
|
||||
eol = sol + idx->v[cur_idx].len;
|
||||
goto next_hdr;
|
||||
}
|
||||
|
||||
/* first request for this header */
|
||||
sol += hdr_idx_first_pos(idx);
|
||||
old_idx = 0;
|
||||
cur_idx = hdr_idx_first_idx(idx);
|
||||
while (cur_idx) {
|
||||
eol = sol + idx->v[cur_idx].len;
|
||||
|
||||
if (len == 0) {
|
||||
/* No argument was passed, we want any header.
|
||||
* To achieve this, we simply build a fake request. */
|
||||
while (sol + len < eol && sol[len] != ':')
|
||||
len++;
|
||||
name = sol;
|
||||
}
|
||||
|
||||
if ((len < eol - sol) &&
|
||||
(sol[len] == ':') &&
|
||||
(strncasecmp(sol, name, len) == 0)) {
|
||||
ctx->del = len;
|
||||
sov = sol + len + 1;
|
||||
while (sov < eol && http_is_lws[(unsigned char)*sov])
|
||||
sov++;
|
||||
|
||||
ctx->line = sol;
|
||||
ctx->prev = old_idx;
|
||||
ctx->idx = cur_idx;
|
||||
ctx->val = sov - sol;
|
||||
ctx->tws = 0;
|
||||
while (eol > sov && http_is_lws[(unsigned char)*(eol - 1)]) {
|
||||
eol--;
|
||||
ctx->tws++;
|
||||
}
|
||||
ctx->vlen = eol - sov;
|
||||
return 1;
|
||||
}
|
||||
next_hdr:
|
||||
sol = eol + idx->v[cur_idx].cr + 1;
|
||||
old_idx = cur_idx;
|
||||
cur_idx = idx->v[cur_idx].next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find the end of the header value contained between <s> and <e>. See RFC2616,
|
||||
* par 2.2 for more information. Note that it requires a valid header to return
|
||||
* a valid result. This works for headers defined as comma-separated lists.
|
||||
@ -7934,7 +8003,8 @@ void http_capture_bad_message(struct error_snapshot *es, struct session *s,
|
||||
* <occ> is positive or null, occurrence #occ from the beginning (or last ctx)
|
||||
* is returned. Occ #0 and #1 are equivalent. If <occ> is negative (and no less
|
||||
* than -MAX_HDR_HISTORY), the occurrence is counted from the last one which is
|
||||
* -1.
|
||||
* -1. The value fetch stops at commas, so this function is suited for use with
|
||||
* list headers.
|
||||
* The return value is 0 if nothing was found, or non-zero otherwise.
|
||||
*/
|
||||
unsigned int http_get_hdr(const struct http_msg *msg, const char *hname, int hlen,
|
||||
@ -7990,6 +8060,70 @@ unsigned int http_get_hdr(const struct http_msg *msg, const char *hname, int hle
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Return in <vptr> and <vlen> the pointer and length of occurrence <occ> of
|
||||
* header whose name is <hname> of length <hlen>. If <ctx> is null, lookup is
|
||||
* performed over the whole headers. Otherwise it must contain a valid header
|
||||
* context, initialised with ctx->idx=0 for the first lookup in a series. If
|
||||
* <occ> is positive or null, occurrence #occ from the beginning (or last ctx)
|
||||
* is returned. Occ #0 and #1 are equivalent. If <occ> is negative (and no less
|
||||
* than -MAX_HDR_HISTORY), the occurrence is counted from the last one which is
|
||||
* -1. This function differs from http_get_hdr() in that it only returns full
|
||||
* line header values and does not stop at commas.
|
||||
* The return value is 0 if nothing was found, or non-zero otherwise.
|
||||
*/
|
||||
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, int *vlen)
|
||||
{
|
||||
struct hdr_ctx local_ctx;
|
||||
char *ptr_hist[MAX_HDR_HISTORY];
|
||||
int len_hist[MAX_HDR_HISTORY];
|
||||
unsigned int hist_ptr;
|
||||
int found;
|
||||
|
||||
if (!ctx) {
|
||||
local_ctx.idx = 0;
|
||||
ctx = &local_ctx;
|
||||
}
|
||||
|
||||
if (occ >= 0) {
|
||||
/* search from the beginning */
|
||||
while (http_find_full_header2(hname, hlen, msg->chn->buf->p, idx, ctx)) {
|
||||
occ--;
|
||||
if (occ <= 0) {
|
||||
*vptr = ctx->line + ctx->val;
|
||||
*vlen = ctx->vlen;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* negative occurrence, we scan all the list then walk back */
|
||||
if (-occ > MAX_HDR_HISTORY)
|
||||
return 0;
|
||||
|
||||
found = hist_ptr = 0;
|
||||
while (http_find_full_header2(hname, hlen, msg->chn->buf->p, idx, ctx)) {
|
||||
ptr_hist[hist_ptr] = ctx->line + ctx->val;
|
||||
len_hist[hist_ptr] = ctx->vlen;
|
||||
if (++hist_ptr >= MAX_HDR_HISTORY)
|
||||
hist_ptr = 0;
|
||||
found++;
|
||||
}
|
||||
if (-occ > found)
|
||||
return 0;
|
||||
/* OK now we have the last occurrence in [hist_ptr-1], and we need to
|
||||
* find occurrence -occ, so we have to check [hist_ptr+occ].
|
||||
*/
|
||||
hist_ptr += occ;
|
||||
if (hist_ptr >= MAX_HDR_HISTORY)
|
||||
hist_ptr -= MAX_HDR_HISTORY;
|
||||
*vptr = ptr_hist[hist_ptr];
|
||||
*vlen = len_hist[hist_ptr];
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Print a debug line with a header. Always stop at the first CR or LF char,
|
||||
* so it is safe to pass it a full buffer if needed. If <err> is not NULL, an
|
||||
@ -8718,6 +8852,95 @@ smp_fetch_url_port(struct proxy *px, struct session *l4, void *l7, unsigned int
|
||||
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(struct proxy *px, struct session *l4, void *l7, unsigned int opt,
|
||||
const struct arg *args, struct sample *smp)
|
||||
{
|
||||
struct http_txn *txn = l7;
|
||||
struct hdr_idx *idx = &txn->hdr_idx;
|
||||
struct hdr_ctx *ctx = smp->ctx.a[0];
|
||||
const struct http_msg *msg = ((opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &txn->req : &txn->rsp;
|
||||
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.str;
|
||||
name_len = args[0].data.str.len;
|
||||
|
||||
if (args[1].type == ARGT_UINT || args[1].type == ARGT_SINT)
|
||||
occ = args[1].data.uint;
|
||||
}
|
||||
|
||||
CHECK_HTTP_MESSAGE_FIRST();
|
||||
|
||||
if (ctx && !(smp->flags & SMP_F_NOT_LAST))
|
||||
/* search for header from the beginning */
|
||||
ctx->idx = 0;
|
||||
|
||||
if (!occ && !(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->type = SMP_T_CSTR;
|
||||
smp->flags |= SMP_F_VOL_HDR;
|
||||
if (http_get_fhdr(msg, name_str, name_len, idx, occ, ctx, &smp->data.str.str, &smp->data.str.len))
|
||||
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(struct proxy *px, struct session *l4, void *l7, unsigned int opt,
|
||||
const struct arg *args, struct sample *smp)
|
||||
{
|
||||
struct http_txn *txn = l7;
|
||||
struct hdr_idx *idx = &txn->hdr_idx;
|
||||
struct hdr_ctx ctx;
|
||||
const struct http_msg *msg = ((opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &txn->req : &txn->rsp;
|
||||
int cnt;
|
||||
|
||||
if (!args || args->type != ARGT_STR)
|
||||
return 0;
|
||||
|
||||
CHECK_HTTP_MESSAGE_FIRST();
|
||||
|
||||
ctx.idx = 0;
|
||||
cnt = 0;
|
||||
while (http_find_full_header2(args->data.str.str, args->data.str.len, msg->chn->buf->p, idx, &ctx))
|
||||
cnt++;
|
||||
|
||||
smp->type = SMP_T_UINT;
|
||||
smp->data.uint = cnt;
|
||||
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
|
||||
@ -9689,6 +9912,8 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {{ },{
|
||||
{ "req.cook_cnt", smp_fetch_cookie_cnt, ARG1(0,STR), NULL, SMP_T_UINT, SMP_USE_HRQHV },
|
||||
{ "req.cook_val", smp_fetch_cookie_val, ARG1(0,STR), NULL, SMP_T_UINT, SMP_USE_HRQHV },
|
||||
|
||||
{ "req.fhdr", smp_fetch_fhdr, ARG2(0,STR,SINT), val_hdr, SMP_T_CSTR, SMP_USE_HRQHV },
|
||||
{ "req.fhdr_cnt", smp_fetch_fhdr_cnt, ARG1(0,STR), NULL, SMP_T_UINT, SMP_USE_HRQHV },
|
||||
{ "req.hdr", smp_fetch_hdr, ARG2(0,STR,SINT), val_hdr, SMP_T_CSTR, SMP_USE_HRQHV },
|
||||
{ "req.hdr_cnt", smp_fetch_hdr_cnt, ARG1(0,STR), NULL, SMP_T_UINT, SMP_USE_HRQHV },
|
||||
{ "req.hdr_ip", smp_fetch_hdr_ip, ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRQHV },
|
||||
@ -9699,6 +9924,8 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {{ },{
|
||||
{ "res.cook_cnt", smp_fetch_cookie_cnt, ARG1(0,STR), NULL, SMP_T_UINT, SMP_USE_HRSHV },
|
||||
{ "res.cook_val", smp_fetch_cookie_val, ARG1(0,STR), NULL, SMP_T_UINT, SMP_USE_HRSHV },
|
||||
|
||||
{ "res.fhdr", smp_fetch_fhdr, ARG2(0,STR,SINT), val_hdr, SMP_T_CSTR, SMP_USE_HRSHV },
|
||||
{ "res.fhdr_cnt", smp_fetch_fhdr_cnt, ARG1(0,STR), NULL, SMP_T_UINT, SMP_USE_HRSHV },
|
||||
{ "res.hdr", smp_fetch_hdr, ARG2(0,STR,SINT), val_hdr, SMP_T_CSTR, SMP_USE_HRSHV },
|
||||
{ "res.hdr_cnt", smp_fetch_hdr_cnt, ARG1(0,STR), NULL, SMP_T_UINT, SMP_USE_HRSHV },
|
||||
{ "res.hdr_ip", smp_fetch_hdr_ip, ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRSHV },
|
||||
|
Loading…
Reference in New Issue
Block a user