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.
*/