}
-
/*
* Manage server-side cookies. It can impact performance by about 2% so it is
- * desirable to call it only when needed.
+ * 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).
*/
-void manage_server_side_cookies(struct session *t, struct buffer *rtr)
+void manage_server_side_cookies(struct session *t, struct buffer *res)
{
struct http_txn *txn = &t->txn;
- char *p1, *p2, *p3, *p4;
-
- char *cur_ptr, *cur_end, *cur_next;
+ int is_cookie2;
int cur_idx, old_idx, delta;
+ char *hdr_beg, *hdr_end, *hdr_next;
+ char *prev, *att_beg, *att_end, *equal, *val_beg, *val_end, *next;
/* Iterate through the headers.
* we start with the start line.
*/
old_idx = 0;
- cur_next = txn->rsp.sol + hdr_idx_first_pos(&txn->hdr_idx);
+ hdr_next = txn->rsp.sol + hdr_idx_first_pos(&txn->hdr_idx);
while ((cur_idx = txn->hdr_idx.v[old_idx].next)) {
struct hdr_idx_elem *cur_hdr;
int val;
cur_hdr = &txn->hdr_idx.v[cur_idx];
- cur_ptr = cur_next;
- cur_end = cur_ptr + cur_hdr->len;
- cur_next = cur_end + cur_hdr->cr + 1;
+ hdr_beg = hdr_next;
+ hdr_end = hdr_beg + cur_hdr->len;
+ hdr_next = hdr_end + cur_hdr->cr + 1;
- /* We have one full header between cur_ptr and cur_end, and the
- * next header starts at cur_next. We're only interested in
- * "Cookie:" headers.
+ /* We have one full header between hdr_beg and hdr_end, and the
+ * next header starts at hdr_next. We're only interested in
+ * "Set-Cookie" and "Set-Cookie2" headers.
*/
- val = http_header_match2(cur_ptr, cur_end, "Set-Cookie", 10);
+ is_cookie2 = 0;
+ prev = hdr_beg + 10;
+ val = http_header_match2(hdr_beg, hdr_end, "Set-Cookie", 10);
if (!val) {
- old_idx = cur_idx;
- continue;
+ val = http_header_match2(hdr_beg, hdr_end, "Set-Cookie2", 11);
+ if (!val) {
+ old_idx = cur_idx;
+ continue;
+ }
+ is_cookie2 = 1;
+ prev = hdr_beg + 11;
}
- /* OK, right now we know we have a set-cookie at cur_ptr */
+ /* OK, right now we know we have a Set-Cookie* at hdr_beg, and
+ * <prev> points to the colon.
+ */
txn->flags |= TX_SCK_ANY;
-
- /* maybe we only wanted to see if there was a set-cookie. Note that
- * the cookie capture is declared in the fronend.
+ /* 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 (t->be->cookie_name == NULL &&
t->be->appsession_name == NULL &&
t->fe->capture_name == NULL)
return;
- p1 = cur_ptr + val; /* first non-space char after 'Set-Cookie:' */
-
- while (p1 < cur_end) { /* in fact, we'll break after the first cookie */
- if (p1 == cur_end || *p1 == ';') /* end of cookie */
- 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 :
+ *
+ * Set-Cookie: NAME1 = VALUE 1 ; Secure; Path="/"\r\n
+ * Set-Cookie:NAME=VALUE; Secure; Expires=Thu, 01-Jan-1970 00:00:01 GMT\r\n
+ * Set-Cookie: NAME = VALUE ; Secure; Expires=Thu, 01-Jan-1970 00:00:01 GMT\r\n
+ * Set-Cookie2: NAME1 = VALUE 1 ; Max-Age=0, NAME2=VALUE2; Discard\r\n
+ * | | | | | | | | | |
+ * | | | | | | | | +-> next hdr_end <--+
+ * | | | | | | | +------------> scav
+ * | | | | | | +--------------> val_end
+ * | | | | | +--------------------> val_beg
+ * | | | | +----------------------> equal
+ * | | | +------------------------> att_end
+ * | | +----------------------------> att_beg
+ * | +------------------------------> prev
+ * +-----------------------------------------> hdr_beg
+ */
- /* p1 is at the beginning of the cookie name */
- p2 = p1;
+ for (; prev < hdr_end; prev = next) {
+ /* Iterate through all cookies on this line */
- while (p2 < cur_end && *p2 != '=' && *p2 != ';')
- p2++;
+ /* find att_beg */
+ att_beg = prev + 1;
+ while (att_beg < hdr_end && http_is_spht[(unsigned char)*att_beg])
+ att_beg++;
- if (p2 == cur_end || *p2 == ';') /* next cookie */
- break;
+ /* 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;
- p3 = p2 + 1; /* skip the '=' sign */
- if (p3 == cur_end)
- break;
+ while (equal < hdr_end) {
+ if (*equal == '=' || *equal == ';' || (is_cookie2 && *equal == ','))
+ break;
+ if (http_is_spht[(unsigned char)*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[(unsigned char)*val_beg])
+ val_beg++;
+
+ /* find the end of the value, respecting quotes */
+ next = 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[(unsigned char)*(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 = find_hdr_value_end(next, hdr_end);
+ else
+ next = hdr_end;
+ }
- p4 = p3;
- while (p4 < cur_end && !isspace((unsigned char)*p4) && *p4 != ';')
- p4++;
+ /* Now everything is as on the diagram above */
- /* here, we have the cookie name between p1 and p2,
- * and its value between p3 and p4.
- * we can process it.
+ /* 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) {
+ stripped_before = buffer_replace2(res, att_end, equal, NULL, 0);
+ equal += stripped_before;
+ val_beg += stripped_before;
+ }
- /* first, let's see if we want to capture it */
+ if (val_beg > equal + 1) {
+ stripped_after = buffer_replace2(res, equal + 1, val_beg, NULL, 0);
+ val_beg += stripped_after;
+ stripped_before += stripped_after;
+ }
+
+ val_end += stripped_before;
+ next += stripped_before;
+ hdr_end += stripped_before;
+ hdr_next += stripped_before;
+ cur_hdr->len += stripped_before;
+ http_msg_move_end(&txn->req, stripped_before);
+ }
+
+ /* 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 (t->fe->capture_name != NULL &&
txn->srv_cookie == NULL &&
- (p4 - p1 >= t->fe->capture_namelen) &&
- memcmp(p1, t->fe->capture_name, t->fe->capture_namelen) == 0) {
- int log_len = p4 - p1;
-
+ (val_end - att_beg >= t->fe->capture_namelen) &&
+ memcmp(att_beg, t->fe->capture_name, t->fe->capture_namelen) == 0) {
+ int log_len = val_end - att_beg;
if ((txn->srv_cookie = pool_alloc2(pool2_capture)) == NULL) {
Alert("HTTP logging : out of memory.\n");
}
if (log_len > t->fe->capture_len)
log_len = t->fe->capture_len;
- memcpy(txn->srv_cookie, p1, log_len);
+ memcpy(txn->srv_cookie, att_beg, log_len);
txn->srv_cookie[log_len] = 0;
}
/* now check if we need to process it for persistence */
- if (!(t->flags & SN_IGNORE_PRST) && (p2 - p1 == t->be->cookie_len) && (t->be->cookie_name != NULL) &&
- (memcmp(p1, t->be->cookie_name, p2 - p1) == 0)) {
- /* Cool... it's the right one */
+ if (!(t->flags & SN_IGNORE_PRST) &&
+ (att_end - att_beg == t->be->cookie_len) && (t->be->cookie_name != NULL) &&
+ (memcmp(att_beg, t->be->cookie_name, att_end - att_beg) == 0)) {
txn->flags |= TX_SCK_SEEN;
/* 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. */
+ * a direct access.
+ */
if (((t->srv) && (t->be->options & PR_O_COOK_INS)) ||
((t->flags & SN_DIRECT) && (t->be->options & PR_O_COOK_IND))) {
- /* this header must be deleted */
- delta = buffer_replace2(rtr, cur_ptr, cur_next, NULL, 0);
- txn->hdr_idx.v[old_idx].next = cur_hdr->next;
- txn->hdr_idx.used--;
- cur_hdr->len = 0;
- cur_next += delta;
- http_msg_move_end(&txn->rsp, delta);
-
+ /* this cookie must be deleted */
+ if (*prev == ':' && next == hdr_end) {
+ /* whole header */
+ delta = buffer_replace2(res, hdr_beg, hdr_next, NULL, 0);
+ txn->hdr_idx.v[old_idx].next = cur_hdr->next;
+ txn->hdr_idx.used--;
+ cur_hdr->len = 0;
+ hdr_next += delta;
+ http_msg_move_end(&txn->rsp, delta);
+ /* 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 = del_hdr_value(res, &prev, next);
+ next = prev;
+ hdr_end += delta;
+ hdr_next += delta;
+ cur_hdr->len += delta;
+ http_msg_move_end(&txn->rsp, delta);
+ }
txn->flags |= TX_SCK_DELETED;
+ /* and go on with next cookie */
}
else if ((t->srv) && (t->srv->cookie) &&
(t->be->options & PR_O_COOK_RW)) {
- /* replace bytes p3->p4 with the cookie name associated
+ /* replace bytes val_beg->val_end with the cookie name associated
* with this server since we know it.
*/
- delta = buffer_replace2(rtr, p3, p4, t->srv->cookie, t->srv->cklen);
+ delta = buffer_replace2(res, val_beg, val_end, t->srv->cookie, t->srv->cklen);
+ next += delta;
+ hdr_end += delta;
+ hdr_next += delta;
cur_hdr->len += delta;
- cur_next += delta;
http_msg_move_end(&txn->rsp, delta);
txn->flags |= TX_SCK_INSERTED | TX_SCK_DELETED;
else if ((t->srv) && (t->srv->cookie) &&
(t->be->options & PR_O_COOK_PFX)) {
/* insert the cookie name associated with this server
- * before existing cookie, and insert a delimitor between them..
+ * before existing cookie, and insert a delimiter between them..
*/
- delta = buffer_replace2(rtr, p3, p3, t->srv->cookie, t->srv->cklen + 1);
+ delta = buffer_replace2(res, val_beg, val_beg, t->srv->cookie, t->srv->cklen + 1);
+ next += delta;
+ hdr_end += delta;
+ hdr_next += delta;
cur_hdr->len += delta;
- cur_next += delta;
http_msg_move_end(&txn->rsp, delta);
- p3[t->srv->cklen] = COOKIE_DELIM;
+ val_beg[t->srv->cklen] = COOKIE_DELIM;
txn->flags |= TX_SCK_INSERTED | TX_SCK_DELETED;
}
}
char *value_begin;
if (t->be->options2 & PR_O2_AS_PFX) {
- cmp_len = MIN(p4 - p1, t->be->appsession_name_len);
- value_begin = p1 + t->be->appsession_name_len;
- value_len = MIN(t->be->appsession_len, p4 - p1 - t->be->appsession_name_len);
+ cmp_len = MIN(val_end - att_beg, t->be->appsession_name_len);
+ value_begin = att_beg + t->be->appsession_name_len;
+ value_len = MIN(t->be->appsession_len, val_end - att_beg - t->be->appsession_name_len);
} else {
- cmp_len = p2 - p1;
- value_begin = p3;
- value_len = MIN(t->be->appsession_len, p4 - p3);
+ cmp_len = att_end - att_beg;
+ value_begin = val_beg;
+ value_len = MIN(t->be->appsession_len, val_end - val_beg);
}
if ((cmp_len == t->be->appsession_name_len) &&
- (memcmp(p1, t->be->appsession_name, t->be->appsession_name_len) == 0)) {
- /* Cool... it's the right one */
- if (txn->sessid != NULL) {
- /* free previously allocated memory as we don't need it anymore */
- pool_free2(apools.sessid, txn->sessid);
- }
+ (memcmp(att_beg, t->be->appsession_name, t->be->appsession_name_len) == 0)) {
+ /* free a possibly previously allocated memory */
+ pool_free2(apools.sessid, txn->sessid);
+
/* Store the sessid in the session for future use */
if ((txn->sessid = pool_alloc2(apools.sessid)) == NULL) {
Alert("Not enough Memory process_srv():asession->sessid:malloc().\n");
memcpy(txn->sessid, value_begin, value_len);
txn->sessid[value_len] = 0;
}
- } /* end if ((t->be->appsession_name != NULL) ... */
- break; /* we don't want to loop again since there cannot be another cookie on the same line */
- } /* we're now at the end of the cookie value */
- /* keep the link from this header to next one */
+ }
+ /* that's done for this cookie, check the next one on the same
+ * line when next != hdr_end (only if is_cookie2).
+ */
+ }
+ /* check next header */
old_idx = cur_idx;
- } /* end of cookie processing on this header */
+ }
if (txn->sessid != NULL) {
appsess *asession = NULL;
asession->expire = tick_add_ifset(now_ms, t->be->timeout.appsession);
asession->request_count++;
}
-
-#if defined(DEBUG_HASH)
- if (t->be->appsession_name) {
- Alert("manage_server_side_cookies\n");
- appsession_hash_dump(&(t->be->htbl_proxy));
- }
-#endif
}
-
/*
* Check if response is cacheable or not. Updates t->flags.
*/