mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2026-02-07 02:11:34 +01:00
MINOR: proto_htx: Add functions to manage cookies on HTX messages
It is more or less the same than legacy versions but adapted to be called from HTX analyzers.
This commit is contained in:
parent
336400861e
commit
fcda7c6850
685
src/proto_htx.c
685
src/proto_htx.c
@ -52,6 +52,9 @@ static enum rule_result htx_res_get_intercept_rule(struct proxy *px, struct list
|
||||
static int htx_apply_filters_to_request(struct stream *s, struct channel *req, struct proxy *px);
|
||||
static int htx_apply_filters_to_response(struct stream *s, struct channel *res, struct proxy *px);
|
||||
|
||||
static void htx_manage_client_side_cookies(struct stream *s, struct channel *req);
|
||||
static void htx_manage_server_side_cookies(struct stream *s, struct channel *res);
|
||||
|
||||
/* This stream analyser waits for a complete HTTP request. It returns 1 if the
|
||||
* processing can continue on next analysers, or zero if it either needs more
|
||||
* data or wants to immediately abort the request (eg: timeout, error, ...). It
|
||||
@ -3882,6 +3885,688 @@ static int htx_apply_filters_to_response(struct stream *s, struct channel *res,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Manage client-side cookie. It can impact performance by about 2% so it is
|
||||
* desirable to call it only when needed. This code is quite complex because
|
||||
* of the multiple very crappy and ambiguous syntaxes we have to support. it
|
||||
* highly recommended not to touch this part without a good reason !
|
||||
*/
|
||||
static void htx_manage_client_side_cookies(struct stream *s, struct channel *req)
|
||||
{
|
||||
struct session *sess = s->sess;
|
||||
struct http_txn *txn = s->txn;
|
||||
struct htx *htx;
|
||||
struct http_hdr_ctx ctx;
|
||||
char *hdr_beg, *hdr_end, *del_from;
|
||||
char *prev, *att_beg, *att_end, *equal, *val_beg, *val_end, *next;
|
||||
int preserve_hdr;
|
||||
|
||||
htx = htx_from_buf(&req->buf);
|
||||
ctx.blk = NULL;
|
||||
while (http_find_header(htx, ist("Cookie"), &ctx, 1)) {
|
||||
del_from = NULL; /* nothing to be deleted */
|
||||
preserve_hdr = 0; /* assume we may kill the whole header */
|
||||
|
||||
/* Now look for cookies. Conforming to RFC2109, we have to support
|
||||
* attributes whose name begin with a '$', and associate them with
|
||||
* the right cookie, if we want to delete this cookie.
|
||||
* So there are 3 cases for each cookie read :
|
||||
* 1) it's a special attribute, beginning with a '$' : ignore it.
|
||||
* 2) it's a server id cookie that we *MAY* want to delete : save
|
||||
* some pointers on it (last semi-colon, beginning of cookie...)
|
||||
* 3) it's an application cookie : we *MAY* have to delete a previous
|
||||
* "special" cookie.
|
||||
* At the end of loop, if a "special" cookie remains, we may have to
|
||||
* remove it. If no application cookie persists in the header, we
|
||||
* *MUST* delete it.
|
||||
*
|
||||
* Note: RFC2965 is unclear about the processing of spaces around
|
||||
* the equal sign in the ATTR=VALUE form. A careful inspection of
|
||||
* the RFC explicitly allows spaces before it, and not within the
|
||||
* tokens (attrs or values). An inspection of RFC2109 allows that
|
||||
* too but section 10.1.3 lets one think that spaces may be allowed
|
||||
* after the equal sign too, resulting in some (rare) buggy
|
||||
* implementations trying to do that. So let's do what servers do.
|
||||
* Latest ietf draft forbids spaces all around. Also, earlier RFCs
|
||||
* allowed quoted strings in values, with any possible character
|
||||
* after a backslash, including control chars and delimitors, which
|
||||
* causes parsing to become ambiguous. Browsers also allow spaces
|
||||
* within values even without quotes.
|
||||
*
|
||||
* We have to keep multiple pointers in order to support cookie
|
||||
* removal at the beginning, middle or end of header without
|
||||
* corrupting the header. All of these headers are valid :
|
||||
*
|
||||
* hdr_beg hdr_end
|
||||
* | |
|
||||
* v |
|
||||
* NAME1=VALUE1;NAME2=VALUE2;NAME3=VALUE3 |
|
||||
* NAME1=VALUE1;NAME2_ONLY ;NAME3=VALUE3 v
|
||||
* NAME1 = VALUE 1 ; NAME2 = VALUE2 ; NAME3 = VALUE3
|
||||
* | | | | | | |
|
||||
* | | | | | | |
|
||||
* | | | | | | +--> next
|
||||
* | | | | | +----> val_end
|
||||
* | | | | +-----------> val_beg
|
||||
* | | | +--------------> equal
|
||||
* | | +----------------> att_end
|
||||
* | +---------------------> att_beg
|
||||
* +--------------------------> prev
|
||||
*
|
||||
*/
|
||||
hdr_beg = ctx.value.ptr;
|
||||
hdr_end = hdr_beg + ctx.value.len;
|
||||
for (prev = hdr_beg; prev < hdr_end; prev = next) {
|
||||
/* Iterate through all cookies on this line */
|
||||
|
||||
/* find att_beg */
|
||||
att_beg = prev;
|
||||
if (prev > hdr_beg)
|
||||
att_beg++;
|
||||
|
||||
while (att_beg < hdr_end && HTTP_IS_SPHT(*att_beg))
|
||||
att_beg++;
|
||||
|
||||
/* find att_end : this is the first character after the last non
|
||||
* space before the equal. It may be equal to hdr_end.
|
||||
*/
|
||||
equal = att_end = att_beg;
|
||||
while (equal < hdr_end) {
|
||||
if (*equal == '=' || *equal == ',' || *equal == ';')
|
||||
break;
|
||||
if (HTTP_IS_SPHT(*equal++))
|
||||
continue;
|
||||
att_end = equal;
|
||||
}
|
||||
|
||||
/* here, <equal> points to '=', a delimitor or the end. <att_end>
|
||||
* is between <att_beg> and <equal>, both may be identical.
|
||||
*/
|
||||
/* look for end of cookie if there is an equal sign */
|
||||
if (equal < hdr_end && *equal == '=') {
|
||||
/* look for the beginning of the value */
|
||||
val_beg = equal + 1;
|
||||
while (val_beg < hdr_end && HTTP_IS_SPHT(*val_beg))
|
||||
val_beg++;
|
||||
|
||||
/* find the end of the value, respecting quotes */
|
||||
next = http_find_cookie_value_end(val_beg, hdr_end);
|
||||
|
||||
/* make val_end point to the first white space or delimitor after the value */
|
||||
val_end = next;
|
||||
while (val_end > val_beg && HTTP_IS_SPHT(*(val_end - 1)))
|
||||
val_end--;
|
||||
}
|
||||
else
|
||||
val_beg = val_end = next = equal;
|
||||
|
||||
/* We have nothing to do with attributes beginning with
|
||||
* '$'. However, they will automatically be removed if a
|
||||
* header before them is removed, since they're supposed
|
||||
* to be linked together.
|
||||
*/
|
||||
if (*att_beg == '$')
|
||||
continue;
|
||||
|
||||
/* Ignore cookies with no equal sign */
|
||||
if (equal == next) {
|
||||
/* This is not our cookie, so we must preserve it. But if we already
|
||||
* scheduled another cookie for removal, we cannot remove the
|
||||
* complete header, but we can remove the previous block itself.
|
||||
*/
|
||||
preserve_hdr = 1;
|
||||
if (del_from != NULL) {
|
||||
int delta = htx_del_hdr_value(hdr_beg, hdr_end, &del_from, prev);
|
||||
val_end += delta;
|
||||
next += delta;
|
||||
hdr_end += delta;
|
||||
prev = del_from;
|
||||
del_from = NULL;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* if there are spaces around the equal sign, we need to
|
||||
* strip them otherwise we'll get trouble for cookie captures,
|
||||
* or even for rewrites. Since this happens extremely rarely,
|
||||
* it does not hurt performance.
|
||||
*/
|
||||
if (unlikely(att_end != equal || val_beg > equal + 1)) {
|
||||
int stripped_before = 0;
|
||||
int stripped_after = 0;
|
||||
|
||||
if (att_end != equal) {
|
||||
memmove(att_end, equal, hdr_end - equal);
|
||||
stripped_before = (att_end - equal);
|
||||
equal += stripped_before;
|
||||
val_beg += stripped_before;
|
||||
}
|
||||
|
||||
if (val_beg > equal + 1) {
|
||||
memmove(equal + 1, val_beg, hdr_end + stripped_before - val_beg);
|
||||
stripped_after = (equal + 1) - val_beg;
|
||||
val_beg += stripped_after;
|
||||
stripped_before += stripped_after;
|
||||
}
|
||||
|
||||
val_end += stripped_before;
|
||||
next += stripped_before;
|
||||
hdr_end += stripped_before;
|
||||
}
|
||||
/* now everything is as on the diagram above */
|
||||
|
||||
/* First, let's see if we want to capture this cookie. We check
|
||||
* that we don't already have a client side cookie, because we
|
||||
* can only capture one. Also as an optimisation, we ignore
|
||||
* cookies shorter than the declared name.
|
||||
*/
|
||||
if (sess->fe->capture_name != NULL && txn->cli_cookie == NULL &&
|
||||
(val_end - att_beg >= sess->fe->capture_namelen) &&
|
||||
memcmp(att_beg, sess->fe->capture_name, sess->fe->capture_namelen) == 0) {
|
||||
int log_len = val_end - att_beg;
|
||||
|
||||
if ((txn->cli_cookie = pool_alloc(pool_head_capture)) == NULL) {
|
||||
ha_alert("HTTP logging : out of memory.\n");
|
||||
} else {
|
||||
if (log_len > sess->fe->capture_len)
|
||||
log_len = sess->fe->capture_len;
|
||||
memcpy(txn->cli_cookie, att_beg, log_len);
|
||||
txn->cli_cookie[log_len] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Persistence cookies in passive, rewrite or insert mode have the
|
||||
* following form :
|
||||
*
|
||||
* Cookie: NAME=SRV[|<lastseen>[|<firstseen>]]
|
||||
*
|
||||
* For cookies in prefix mode, the form is :
|
||||
*
|
||||
* Cookie: NAME=SRV~VALUE
|
||||
*/
|
||||
if ((att_end - att_beg == s->be->cookie_len) && (s->be->cookie_name != NULL) &&
|
||||
(memcmp(att_beg, s->be->cookie_name, att_end - att_beg) == 0)) {
|
||||
struct server *srv = s->be->srv;
|
||||
char *delim;
|
||||
|
||||
/* if we're in cookie prefix mode, we'll search the delimitor so that we
|
||||
* have the server ID between val_beg and delim, and the original cookie between
|
||||
* delim+1 and val_end. Otherwise, delim==val_end :
|
||||
*
|
||||
* hdr_beg
|
||||
* |
|
||||
* v
|
||||
* NAME=SRV; # in all but prefix modes
|
||||
* NAME=SRV~OPAQUE ; # in prefix mode
|
||||
* || || | |+-> next
|
||||
* || || | +--> val_end
|
||||
* || || +---------> delim
|
||||
* || |+------------> val_beg
|
||||
* || +-------------> att_end = equal
|
||||
* |+-----------------> att_beg
|
||||
* +------------------> prev
|
||||
*
|
||||
*/
|
||||
if (s->be->ck_opts & PR_CK_PFX) {
|
||||
for (delim = val_beg; delim < val_end; delim++)
|
||||
if (*delim == COOKIE_DELIM)
|
||||
break;
|
||||
}
|
||||
else {
|
||||
char *vbar1;
|
||||
delim = val_end;
|
||||
/* Now check if the cookie contains a date field, which would
|
||||
* appear after a vertical bar ('|') just after the server name
|
||||
* and before the delimiter.
|
||||
*/
|
||||
vbar1 = memchr(val_beg, COOKIE_DELIM_DATE, val_end - val_beg);
|
||||
if (vbar1) {
|
||||
/* OK, so left of the bar is the server's cookie and
|
||||
* right is the last seen date. It is a base64 encoded
|
||||
* 30-bit value representing the UNIX date since the
|
||||
* epoch in 4-second quantities.
|
||||
*/
|
||||
int val;
|
||||
delim = vbar1++;
|
||||
if (val_end - vbar1 >= 5) {
|
||||
val = b64tos30(vbar1);
|
||||
if (val > 0)
|
||||
txn->cookie_last_date = val << 2;
|
||||
}
|
||||
/* look for a second vertical bar */
|
||||
vbar1 = memchr(vbar1, COOKIE_DELIM_DATE, val_end - vbar1);
|
||||
if (vbar1 && (val_end - vbar1 > 5)) {
|
||||
val = b64tos30(vbar1 + 1);
|
||||
if (val > 0)
|
||||
txn->cookie_first_date = val << 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* if the cookie has an expiration date and the proxy wants to check
|
||||
* it, then we do that now. We first check if the cookie is too old,
|
||||
* then only if it has expired. We detect strict overflow because the
|
||||
* time resolution here is not great (4 seconds). Cookies with dates
|
||||
* in the future are ignored if their offset is beyond one day. This
|
||||
* allows an admin to fix timezone issues without expiring everyone
|
||||
* and at the same time avoids keeping unwanted side effects for too
|
||||
* long.
|
||||
*/
|
||||
if (txn->cookie_first_date && s->be->cookie_maxlife &&
|
||||
(((signed)(date.tv_sec - txn->cookie_first_date) > (signed)s->be->cookie_maxlife) ||
|
||||
((signed)(txn->cookie_first_date - date.tv_sec) > 86400))) {
|
||||
txn->flags &= ~TX_CK_MASK;
|
||||
txn->flags |= TX_CK_OLD;
|
||||
delim = val_beg; // let's pretend we have not found the cookie
|
||||
txn->cookie_first_date = 0;
|
||||
txn->cookie_last_date = 0;
|
||||
}
|
||||
else if (txn->cookie_last_date && s->be->cookie_maxidle &&
|
||||
(((signed)(date.tv_sec - txn->cookie_last_date) > (signed)s->be->cookie_maxidle) ||
|
||||
((signed)(txn->cookie_last_date - date.tv_sec) > 86400))) {
|
||||
txn->flags &= ~TX_CK_MASK;
|
||||
txn->flags |= TX_CK_EXPIRED;
|
||||
delim = val_beg; // let's pretend we have not found the cookie
|
||||
txn->cookie_first_date = 0;
|
||||
txn->cookie_last_date = 0;
|
||||
}
|
||||
|
||||
/* Here, we'll look for the first running server which supports the cookie.
|
||||
* This allows to share a same cookie between several servers, for example
|
||||
* to dedicate backup servers to specific servers only.
|
||||
* However, to prevent clients from sticking to cookie-less backup server
|
||||
* when they have incidentely learned an empty cookie, we simply ignore
|
||||
* empty cookies and mark them as invalid.
|
||||
* The same behaviour is applied when persistence must be ignored.
|
||||
*/
|
||||
if ((delim == val_beg) || (s->flags & (SF_IGNORE_PRST | SF_ASSIGNED)))
|
||||
srv = NULL;
|
||||
|
||||
while (srv) {
|
||||
if (srv->cookie && (srv->cklen == delim - val_beg) &&
|
||||
!memcmp(val_beg, srv->cookie, delim - val_beg)) {
|
||||
if ((srv->cur_state != SRV_ST_STOPPED) ||
|
||||
(s->be->options & PR_O_PERSIST) ||
|
||||
(s->flags & SF_FORCE_PRST)) {
|
||||
/* we found the server and we can use it */
|
||||
txn->flags &= ~TX_CK_MASK;
|
||||
txn->flags |= (srv->cur_state != SRV_ST_STOPPED) ? TX_CK_VALID : TX_CK_DOWN;
|
||||
s->flags |= SF_DIRECT | SF_ASSIGNED;
|
||||
s->target = &srv->obj_type;
|
||||
break;
|
||||
} else {
|
||||
/* we found a server, but it's down,
|
||||
* mark it as such and go on in case
|
||||
* another one is available.
|
||||
*/
|
||||
txn->flags &= ~TX_CK_MASK;
|
||||
txn->flags |= TX_CK_DOWN;
|
||||
}
|
||||
}
|
||||
srv = srv->next;
|
||||
}
|
||||
|
||||
if (!srv && !(txn->flags & (TX_CK_DOWN|TX_CK_EXPIRED|TX_CK_OLD))) {
|
||||
/* no server matched this cookie or we deliberately skipped it */
|
||||
txn->flags &= ~TX_CK_MASK;
|
||||
if ((s->flags & (SF_IGNORE_PRST | SF_ASSIGNED)))
|
||||
txn->flags |= TX_CK_UNUSED;
|
||||
else
|
||||
txn->flags |= TX_CK_INVALID;
|
||||
}
|
||||
|
||||
/* depending on the cookie mode, we may have to either :
|
||||
* - delete the complete cookie if we're in insert+indirect mode, so that
|
||||
* the server never sees it ;
|
||||
* - remove the server id from the cookie value, and tag the cookie as an
|
||||
* application cookie so that it does not get accidentely removed later,
|
||||
* if we're in cookie prefix mode
|
||||
*/
|
||||
if ((s->be->ck_opts & PR_CK_PFX) && (delim != val_end)) {
|
||||
int delta; /* negative */
|
||||
|
||||
memmove(val_beg, delim + 1, hdr_end - (delim + 1));
|
||||
delta = val_beg - (delim + 1);
|
||||
val_end += delta;
|
||||
next += delta;
|
||||
hdr_end += delta;
|
||||
del_from = NULL;
|
||||
preserve_hdr = 1; /* we want to keep this cookie */
|
||||
}
|
||||
else if (del_from == NULL &&
|
||||
(s->be->ck_opts & (PR_CK_INS | PR_CK_IND)) == (PR_CK_INS | PR_CK_IND)) {
|
||||
del_from = prev;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* This is not our cookie, so we must preserve it. But if we already
|
||||
* scheduled another cookie for removal, we cannot remove the
|
||||
* complete header, but we can remove the previous block itself.
|
||||
*/
|
||||
preserve_hdr = 1;
|
||||
|
||||
if (del_from != NULL) {
|
||||
int delta = htx_del_hdr_value(hdr_beg, hdr_end, &del_from, prev);
|
||||
if (att_beg >= del_from)
|
||||
att_beg += delta;
|
||||
if (att_end >= del_from)
|
||||
att_end += delta;
|
||||
val_beg += delta;
|
||||
val_end += delta;
|
||||
next += delta;
|
||||
hdr_end += delta;
|
||||
prev = del_from;
|
||||
del_from = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* continue with next cookie on this header line */
|
||||
att_beg = next;
|
||||
} /* for each cookie */
|
||||
|
||||
|
||||
/* There are no more cookies on this line.
|
||||
* We may still have one (or several) marked for deletion at the
|
||||
* end of the line. We must do this now in two ways :
|
||||
* - if some cookies must be preserved, we only delete from the
|
||||
* mark to the end of line ;
|
||||
* - if nothing needs to be preserved, simply delete the whole header
|
||||
*/
|
||||
if (del_from) {
|
||||
hdr_end = (preserve_hdr ? del_from : hdr_beg);
|
||||
}
|
||||
if ((hdr_end - hdr_beg) != ctx.value.len) {
|
||||
if (hdr_beg != hdr_end) {
|
||||
htx_set_blk_value_len(ctx.blk, hdr_end - hdr_beg);
|
||||
htx->data -= (hdr_end - ctx.value.ptr);
|
||||
}
|
||||
else
|
||||
http_remove_header(htx, &ctx);
|
||||
}
|
||||
} /* for each "Cookie header */
|
||||
}
|
||||
|
||||
/*
|
||||
* Manage server-side cookies. It can impact performance by about 2% so it is
|
||||
* desirable to call it only when needed. This function is also used when we
|
||||
* just need to know if there is a cookie (eg: for check-cache).
|
||||
*/
|
||||
static void htx_manage_server_side_cookies(struct stream *s, struct channel *res)
|
||||
{
|
||||
struct session *sess = s->sess;
|
||||
struct http_txn *txn = s->txn;
|
||||
struct htx *htx;
|
||||
struct http_hdr_ctx ctx;
|
||||
struct server *srv;
|
||||
char *hdr_beg, *hdr_end;
|
||||
char *prev, *att_beg, *att_end, *equal, *val_beg, *val_end, *next;
|
||||
int is_cookie2;
|
||||
|
||||
htx = htx_from_buf(&res->buf);
|
||||
|
||||
ctx.blk = NULL;
|
||||
while (1) {
|
||||
if (!http_find_header(htx, ist("Set-Cookie"), &ctx, 1)) {
|
||||
if (!http_find_header(htx, ist("Set-Cookie2"), &ctx, 1))
|
||||
break;
|
||||
is_cookie2 = 1;
|
||||
}
|
||||
|
||||
/* OK, right now we know we have a Set-Cookie* at hdr_beg, and
|
||||
* <prev> points to the colon.
|
||||
*/
|
||||
txn->flags |= TX_SCK_PRESENT;
|
||||
|
||||
/* Maybe we only wanted to see if there was a Set-Cookie (eg:
|
||||
* check-cache is enabled) and we are not interested in checking
|
||||
* them. Warning, the cookie capture is declared in the frontend.
|
||||
*/
|
||||
if (s->be->cookie_name == NULL && sess->fe->capture_name == NULL)
|
||||
break;
|
||||
|
||||
/* OK so now we know we have to process this response cookie.
|
||||
* The format of the Set-Cookie header is slightly different
|
||||
* from the format of the Cookie header in that it does not
|
||||
* support the comma as a cookie delimiter (thus the header
|
||||
* cannot be folded) because the Expires attribute described in
|
||||
* the original Netscape's spec may contain an unquoted date
|
||||
* with a comma inside. We have to live with this because
|
||||
* many browsers don't support Max-Age and some browsers don't
|
||||
* support quoted strings. However the Set-Cookie2 header is
|
||||
* clean.
|
||||
*
|
||||
* We have to keep multiple pointers in order to support cookie
|
||||
* removal at the beginning, middle or end of header without
|
||||
* corrupting the header (in case of set-cookie2). A special
|
||||
* pointer, <scav> points to the beginning of the set-cookie-av
|
||||
* fields after the first semi-colon. The <next> pointer points
|
||||
* either to the end of line (set-cookie) or next unquoted comma
|
||||
* (set-cookie2). All of these headers are valid :
|
||||
*
|
||||
* hdr_beg hdr_end
|
||||
* | |
|
||||
* v |
|
||||
* NAME1 = VALUE 1 ; Secure; Path="/" |
|
||||
* NAME=VALUE; Secure; Expires=Thu, 01-Jan-1970 00:00:01 GMT v
|
||||
* NAME = VALUE ; Secure; Expires=Thu, 01-Jan-1970 00:00:01 GMT
|
||||
* NAME1 = VALUE 1 ; Max-Age=0, NAME2=VALUE2; Discard
|
||||
* | | | | | | | |
|
||||
* | | | | | | | +-> next
|
||||
* | | | | | | +------------> scav
|
||||
* | | | | | +--------------> val_end
|
||||
* | | | | +--------------------> val_beg
|
||||
* | | | +----------------------> equal
|
||||
* | | +------------------------> att_end
|
||||
* | +----------------------------> att_beg
|
||||
* +------------------------------> prev
|
||||
* -------------------------------> hdr_beg
|
||||
*/
|
||||
hdr_beg = ctx.value.ptr;
|
||||
hdr_end = hdr_beg + ctx.value.len;
|
||||
for (prev = hdr_beg; prev < hdr_end; prev = next) {
|
||||
|
||||
/* Iterate through all cookies on this line */
|
||||
|
||||
/* find att_beg */
|
||||
att_beg = prev;
|
||||
if (prev > hdr_beg)
|
||||
att_beg++;
|
||||
|
||||
while (att_beg < hdr_end && HTTP_IS_SPHT(*att_beg))
|
||||
att_beg++;
|
||||
|
||||
/* find att_end : this is the first character after the last non
|
||||
* space before the equal. It may be equal to hdr_end.
|
||||
*/
|
||||
equal = att_end = att_beg;
|
||||
|
||||
while (equal < hdr_end) {
|
||||
if (*equal == '=' || *equal == ';' || (is_cookie2 && *equal == ','))
|
||||
break;
|
||||
if (HTTP_IS_SPHT(*equal++))
|
||||
continue;
|
||||
att_end = equal;
|
||||
}
|
||||
|
||||
/* here, <equal> points to '=', a delimitor or the end. <att_end>
|
||||
* is between <att_beg> and <equal>, both may be identical.
|
||||
*/
|
||||
|
||||
/* look for end of cookie if there is an equal sign */
|
||||
if (equal < hdr_end && *equal == '=') {
|
||||
/* look for the beginning of the value */
|
||||
val_beg = equal + 1;
|
||||
while (val_beg < hdr_end && HTTP_IS_SPHT(*val_beg))
|
||||
val_beg++;
|
||||
|
||||
/* find the end of the value, respecting quotes */
|
||||
next = http_find_cookie_value_end(val_beg, hdr_end);
|
||||
|
||||
/* make val_end point to the first white space or delimitor after the value */
|
||||
val_end = next;
|
||||
while (val_end > val_beg && HTTP_IS_SPHT(*(val_end - 1)))
|
||||
val_end--;
|
||||
}
|
||||
else {
|
||||
/* <equal> points to next comma, semi-colon or EOL */
|
||||
val_beg = val_end = next = equal;
|
||||
}
|
||||
|
||||
if (next < hdr_end) {
|
||||
/* Set-Cookie2 supports multiple cookies, and <next> points to
|
||||
* a colon or semi-colon before the end. So skip all attr-value
|
||||
* pairs and look for the next comma. For Set-Cookie, since
|
||||
* commas are permitted in values, skip to the end.
|
||||
*/
|
||||
if (is_cookie2)
|
||||
next = http_find_hdr_value_end(next, hdr_end);
|
||||
else
|
||||
next = hdr_end;
|
||||
}
|
||||
|
||||
/* Now everything is as on the diagram above */
|
||||
|
||||
/* Ignore cookies with no equal sign */
|
||||
if (equal == val_end)
|
||||
continue;
|
||||
|
||||
/* If there are spaces around the equal sign, we need to
|
||||
* strip them otherwise we'll get trouble for cookie captures,
|
||||
* or even for rewrites. Since this happens extremely rarely,
|
||||
* it does not hurt performance.
|
||||
*/
|
||||
if (unlikely(att_end != equal || val_beg > equal + 1)) {
|
||||
int stripped_before = 0;
|
||||
int stripped_after = 0;
|
||||
|
||||
if (att_end != equal) {
|
||||
memmove(att_end, equal, hdr_end - equal);
|
||||
stripped_before = (att_end - equal);
|
||||
equal += stripped_before;
|
||||
val_beg += stripped_before;
|
||||
}
|
||||
|
||||
if (val_beg > equal + 1) {
|
||||
memmove(equal + 1, val_beg, hdr_end + stripped_before - val_beg);
|
||||
stripped_after = (equal + 1) - val_beg;
|
||||
val_beg += stripped_after;
|
||||
stripped_before += stripped_after;
|
||||
}
|
||||
|
||||
val_end += stripped_before;
|
||||
next += stripped_before;
|
||||
hdr_end += stripped_before;
|
||||
|
||||
ctx.value.len = hdr_end - hdr_beg;
|
||||
htx_set_blk_value_len(ctx.blk, ctx.value.len);
|
||||
htx->data -= (hdr_end - ctx.value.ptr);
|
||||
}
|
||||
|
||||
/* First, let's see if we want to capture this cookie. We check
|
||||
* that we don't already have a server side cookie, because we
|
||||
* can only capture one. Also as an optimisation, we ignore
|
||||
* cookies shorter than the declared name.
|
||||
*/
|
||||
if (sess->fe->capture_name != NULL &&
|
||||
txn->srv_cookie == NULL &&
|
||||
(val_end - att_beg >= sess->fe->capture_namelen) &&
|
||||
memcmp(att_beg, sess->fe->capture_name, sess->fe->capture_namelen) == 0) {
|
||||
int log_len = val_end - att_beg;
|
||||
if ((txn->srv_cookie = pool_alloc(pool_head_capture)) == NULL) {
|
||||
ha_alert("HTTP logging : out of memory.\n");
|
||||
}
|
||||
else {
|
||||
if (log_len > sess->fe->capture_len)
|
||||
log_len = sess->fe->capture_len;
|
||||
memcpy(txn->srv_cookie, att_beg, log_len);
|
||||
txn->srv_cookie[log_len] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
srv = objt_server(s->target);
|
||||
/* now check if we need to process it for persistence */
|
||||
if (!(s->flags & SF_IGNORE_PRST) &&
|
||||
(att_end - att_beg == s->be->cookie_len) && (s->be->cookie_name != NULL) &&
|
||||
(memcmp(att_beg, s->be->cookie_name, att_end - att_beg) == 0)) {
|
||||
/* assume passive cookie by default */
|
||||
txn->flags &= ~TX_SCK_MASK;
|
||||
txn->flags |= TX_SCK_FOUND;
|
||||
|
||||
/* If the cookie is in insert mode on a known server, we'll delete
|
||||
* this occurrence because we'll insert another one later.
|
||||
* We'll delete it too if the "indirect" option is set and we're in
|
||||
* a direct access.
|
||||
*/
|
||||
if (s->be->ck_opts & PR_CK_PSV) {
|
||||
/* The "preserve" flag was set, we don't want to touch the
|
||||
* server's cookie.
|
||||
*/
|
||||
}
|
||||
else if ((srv && (s->be->ck_opts & PR_CK_INS)) ||
|
||||
((s->flags & SF_DIRECT) && (s->be->ck_opts & PR_CK_IND))) {
|
||||
/* this cookie must be deleted */
|
||||
if (prev == hdr_beg && next == hdr_end) {
|
||||
/* whole header */
|
||||
http_remove_header(htx, &ctx);
|
||||
/* note: while both invalid now, <next> and <hdr_end>
|
||||
* are still equal, so the for() will stop as expected.
|
||||
*/
|
||||
} else {
|
||||
/* just remove the value */
|
||||
int delta = htx_del_hdr_value(hdr_beg, hdr_end, &prev, next);
|
||||
next = prev;
|
||||
hdr_end += delta;
|
||||
}
|
||||
txn->flags &= ~TX_SCK_MASK;
|
||||
txn->flags |= TX_SCK_DELETED;
|
||||
/* and go on with next cookie */
|
||||
}
|
||||
else if (srv && srv->cookie && (s->be->ck_opts & PR_CK_RW)) {
|
||||
/* replace bytes val_beg->val_end with the cookie name associated
|
||||
* with this server since we know it.
|
||||
*/
|
||||
int sliding, delta;
|
||||
|
||||
ctx.value = ist2(val_beg, val_end - val_beg);
|
||||
ctx.lws_before = ctx.lws_after = 0;
|
||||
http_replace_header_value(htx, &ctx, ist2(srv->cookie, srv->cklen));
|
||||
delta = srv->cklen - (val_end - val_beg);
|
||||
sliding = (ctx.value.ptr - val_beg);
|
||||
hdr_beg += sliding;
|
||||
val_beg += sliding;
|
||||
next += sliding + delta;
|
||||
hdr_end += sliding + delta;
|
||||
|
||||
txn->flags &= ~TX_SCK_MASK;
|
||||
txn->flags |= TX_SCK_REPLACED;
|
||||
}
|
||||
else if (srv && srv->cookie && (s->be->ck_opts & PR_CK_PFX)) {
|
||||
/* insert the cookie name associated with this server
|
||||
* before existing cookie, and insert a delimiter between them..
|
||||
*/
|
||||
int sliding, delta;
|
||||
ctx.value = ist2(val_beg, 0);
|
||||
ctx.lws_before = ctx.lws_after = 0;
|
||||
http_replace_header_value(htx, &ctx, ist2(srv->cookie, srv->cklen + 1));
|
||||
delta = srv->cklen + 1;
|
||||
sliding = (ctx.value.ptr - val_beg);
|
||||
hdr_beg += sliding;
|
||||
val_beg += sliding;
|
||||
next += sliding + delta;
|
||||
hdr_end += sliding + delta;
|
||||
|
||||
val_beg[srv->cklen] = COOKIE_DELIM;
|
||||
txn->flags &= ~TX_SCK_MASK;
|
||||
txn->flags |= TX_SCK_REPLACED;
|
||||
}
|
||||
}
|
||||
/* that's done for this cookie, check the next one on the same
|
||||
* line when next != hdr_end (only if is_cookie2).
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This function terminates the request because it was completly analyzed or
|
||||
* because an error was triggered during the body forwarding.
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user