]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: proto_htx: Add functions to manage cookies on HTX messages
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 24 Oct 2018 09:56:22 +0000 (11:56 +0200)
committerWilly Tarreau <w@1wt.eu>
Sun, 18 Nov 2018 21:08:57 +0000 (22:08 +0100)
It is more or less the same than legacy versions but adapted to be called from
HTX analyzers.

src/proto_htx.c

index c0f9d07247b877afc6ea4b9bf3b8f217b7b7143c..af4862a7669093286aefd5844771cf7cb307e58c 100644 (file)
@@ -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.
  */