diff --git a/src/proto_http.c b/src/proto_http.c index 487ed699c..6875f0aad 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -6321,132 +6321,259 @@ int apply_filters_to_response(struct session *s, struct buffer *rtr, struct prox } - /* * 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 + * 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, points to the beginning of the set-cookie-av + * fields after the first semi-colon. The pointer points + * either to the end of line (set-cookie) or next unquoted comma + * (set-cookie2). All of these headers are valid : + * + * 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; + } - p4 = p3; - while (p4 < cur_end && !isspace((unsigned char)*p4) && *p4 != ';') - p4++; - - /* here, we have the cookie name between p1 and p2, - * and its value between p3 and p4. - * we can process it. + /* here, points to '=', a delimitor or the end. + * is between and , both may be identical. */ - /* first, let's see if we want to capture it */ + /* 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 { + /* points to next comma, semi-colon or EOL */ + val_beg = val_end = next = equal; + } + + if (next < hdr_end) { + /* Set-Cookie2 supports multiple cookies, and points to + * a colon or semi-colon before the end. So skip all attr-value + * pairs and look for the next comma. For Set-Cookie, since + * commas are permitted in values, skip to the end. + */ + if (is_cookie2) + next = 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) { + stripped_before = buffer_replace2(res, att_end, equal, NULL, 0); + equal += stripped_before; + val_beg += stripped_before; + } + + 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, and + * 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; @@ -6454,14 +6581,16 @@ void manage_server_side_cookies(struct session *t, struct buffer *rtr) 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; } } @@ -6471,22 +6600,20 @@ void manage_server_side_cookies(struct session *t, struct buffer *rtr) 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"); @@ -6496,12 +6623,14 @@ void manage_server_side_cookies(struct session *t, struct buffer *rtr) 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; @@ -6540,17 +6669,9 @@ void manage_server_side_cookies(struct session *t, struct buffer *rtr) 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. */