From: Christopher Faulet Date: Wed, 24 Oct 2018 09:56:22 +0000 (+0200) Subject: MINOR: proto_htx: Add functions to manage cookies on HTX messages X-Git-Tag: v1.9-dev7~55 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=fcda7c6850299a58e346118e17ca5895c4a2dde2;p=thirdparty%2Fhaproxy.git 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. --- diff --git a/src/proto_htx.c b/src/proto_htx.c index c0f9d07247..af4862a766 100644 --- a/src/proto_htx.c +++ b/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, points to '=', a delimitor or the end. + * is between and , 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[|[|]] + * + * 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 + * 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, points to the beginning of the set-cookie-av + * fields after the first semi-colon. The 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, points to '=', a delimitor or the end. + * is between and , 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 { + /* points to next comma, semi-colon or EOL */ + val_beg = val_end = next = equal; + } + + if (next < hdr_end) { + /* Set-Cookie2 supports multiple cookies, and 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, and + * 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. */