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 c0f9d07..af4862a 100644
--- a/src/proto_htx.c
+++ b/src/proto_htx.c
@@ -52,6 +52,9 @@
 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 @@
 	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.
  */