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:
Willy Tarreau 2013-06-10 18:39:42 +02:00
parent 570f221cbb
commit 04ff9f105f
3 changed files with 273 additions and 5 deletions

View File

@ -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

View File

@ -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);

View File

@ -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 },