returned. If no name is specified, the first cookie value is returned. When
used in ACLs, all matching names are iterated over until a value matches.
+req.cook_names([<delim>]) : string
+ This builds a string made from the concatenation of all cookie names as they
+ appear in the request (Cookie header) when the rule is evaluated. The default
+ delimiter is the comma (',') but it may be overridden as an optional argument
+ <delim>. In this case, only the first character of <delim> is considered.
+
cookie([<name>]) : string (deprecated)
This extracts the last occurrence of the cookie name <name> on a "Cookie"
header line from the request, or a "Set-Cookie" header from the response, and
It may be used in tcp-check based expect rules.
+res.cook_names([<delim>]) : string
+ This builds a string made from the concatenation of all cookie names as they
+ appear in the response (Set-Cookie headers) when the rule is evaluated. The
+ default delimiter is the comma (',') but it may be overridden as an optional
+ argument <delim>. In this case, only the first character of <delim> is
+ considered.
+
+ It may be used in tcp-check based expect rules.
+
res.fhdr([<name>[,<occ>]]) : string
This fetch works like the req.fhdr() fetch with the difference that it acts
on the headers within an HTTP response.
char *http_extract_cookie_value(char *hdr, const char *hdr_end,
char *cookie_name, size_t cookie_name_l,
int list, char **value, size_t *value_l);
+char *http_extract_next_cookie_name(char *hdr_beg, char *hdr_end, int is_req,
+ char **ptr, size_t *len);
int http_parse_qvalue(const char *qvalue, const char **end);
const char *http_find_url_param_pos(const char **chunks,
const char* url_param_name,
feature ignore_unknown_macro
+# TEST - 1
+# Cookie from request
server s1 {
rxreq
txresp
http-request set-var(txn.count) req.cook_cnt()
http-request set-var(txn.val) req.cook_val()
http-request set-var(txn.val_cook2) req.cook_val(cook2)
+ http-request set-var(txn.cook_names) req.cook_names
http-response set-header count %[var(txn.count)]
http-response set-header val %[var(txn.val)]
http-response set-header val_cook2 %[var(txn.val_cook2)]
+ http-response set-header cook_names %[var(txn.cook_names)]
default_backend be
expect resp.http.count == "3"
expect resp.http.val == "0"
expect resp.http.val_cook2 == "123"
+ expect resp.http.cook_names == "cook1,cook2,cook3"
+} -run
+
+# TEST - 2
+# Set-Cookie from response
+server s2 {
+ rxreq
+ txresp -hdr "Set-Cookie: cook1=0; cook2=123; cook3=22"
+} -start
+
+haproxy h2 -conf {
+ defaults
+ mode http
+
+ frontend fe
+ bind "fd@${fe}"
+ http-response set-var(txn.cook_names) res.cook_names
+ http-response set-header cook_names %[var(txn.cook_names)]
+
+ default_backend be
+
+ backend be
+ server srv2 ${s2_addr}:${s2_port}
+} -start
+
+client c2 -connect ${h2_fe_sock} {
+ txreq -url "/"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.cook_names == "cook1"
+} -run
+
+# TEST - 3
+# Multiple Cookie headers from request
+server s3 {
+ rxreq
+ txresp
+} -start
+
+haproxy h3 -conf {
+ defaults
+ mode http
+
+ frontend fe
+ bind "fd@${fe}"
+ http-request set-var(txn.cook_names) req.cook_names
+ http-response set-header cook_names %[var(txn.cook_names)]
+
+ default_backend be
+
+ backend be
+ server srv3 ${s3_addr}:${s3_port}
+} -start
+
+client c3 -connect ${h3_fe_sock} {
+ txreq -url "/" \
+ -hdr "cookie: cook1=0; cook2=123; cook3=22" \
+ -hdr "cookie: cook4=1; cook5=2; cook6=3"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.cook_names == "cook1,cook2,cook3,cook4,cook5,cook6"
+} -run
+
+# TEST - 4
+# Multiple Set-Cookie headers from response
+server s4 {
+ rxreq
+ txresp -hdr "Set-Cookie: cook1=0; cook2=123; cook3=22" \
+ -hdr "Set-Cookie: cook4=1; cook5=2; cook6=3"
+} -start
+
+haproxy h4 -conf {
+ defaults
+ mode http
+
+ frontend fe
+ bind "fd@${fe}"
+ http-response set-var(txn.cook_names) res.cook_names
+ http-response set-header cook_names %[var(txn.cook_names)]
+
+ default_backend be
+
+ backend be
+ server srv4 ${s4_addr}:${s4_port}
+} -start
+
+client c4 -connect ${h4_fe_sock} {
+ txreq -url "/"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.cook_names == "cook1,cook4"
} -run
return NULL;
}
+/* Try to find the next cookie name in a cookie header given a pointer
+ * <hdr_beg> to the starting position, a pointer <hdr_end> to the ending
+ * position to search in the cookie and a boolean <is_req> of type int that
+ * indicates if the stream direction is for request or response.
+ * The lookup begins at <hdr_beg>, which is assumed to be in
+ * Cookie / Set-Cookie header, and the function returns a pointer to the next
+ * position to search from if a valid cookie k-v pair is found for Cookie
+ * request header (<is_req> is non-zero) and <hdr_end> for Set-Cookie response
+ * header (<is_req> is zero). When the next cookie name is found, <ptr> will
+ * be pointing to the start of the cookie name, and <len> will be the length
+ * of the cookie name.
+ * Otherwise if there is no valid cookie k-v pair, NULL is returned.
+ * The <hdr_end> pointer must point to the first character
+ * not part of the Cookie / Set-Cookie header.
+ */
+char *http_extract_next_cookie_name(char *hdr_beg, char *hdr_end, int is_req,
+ char **ptr, size_t *len)
+{
+ char *equal, *att_end, *att_beg, *val_beg;
+ char *next;
+
+ /* We search a valid cookie name between hdr_beg and hdr_end,
+ * followed by an equal. For example for the following cookie:
+ * Cookie: NAME1 = VALUE 1 ; NAME2 = VALUE2 ; NAME3 = VALUE3\r\n
+ * We want to find NAME1, NAME2, or NAME3 depending on where we start our search
+ * according to <hdr_beg>
+ */
+ for (att_beg = hdr_beg; att_beg + 1 < hdr_end; att_beg = next + 1) {
+ 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 == ';')
+ break;
+ if (HTTP_IS_SPHT(*equal++))
+ continue;
+ att_end = equal;
+ }
+
+ /* Here, <equal> points to '=', a delimiter 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);
+ } else {
+ 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)
+ continue;
+
+ /* Now we have the cookie name between <att_beg> and <att_end>, and
+ * <next> points to the end of cookie value
+ */
+ *ptr = att_beg;
+ *len = att_end - att_beg;
+
+ /* Return next position for Cookie request header and <hdr_end> for
+ * Set-Cookie response header as each Set-Cookie header is assumed to
+ * contain only 1 cookie
+ */
+ if (is_req)
+ return next + 1;
+ return hdr_end;
+ }
+
+ return NULL;
+}
+
/* Parses a qvalue and returns it multiplied by 1000, from 0 to 1000. If the
* value is larger than 1000, it is bound to 1000. The parser consumes up to
* 1 digit, one dot and 3 digits and stops on the first invalid character.
return ret;
}
+/* Iterate over all cookies present in a message,
+ * and return the list of cookie names separated by
+ * the input argument character.
+ * If no input argument is provided,
+ * the default delimiter is ','.
+ * The returned sample is of type CSTR.
+ */
+static int smp_fetch_cookie_names(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+ /* possible keywords: req.cook_names, res.cook_names */
+ struct channel *chn = ((kw[2] == 'q') ? SMP_REQ_CHN(smp) : SMP_RES_CHN(smp));
+ struct check *check = ((kw[2] == 's') ? objt_check(smp->sess->origin) : NULL);
+ struct htx *htx = smp_prefetch_htx(smp, chn, check, 1);
+ struct http_hdr_ctx ctx;
+ struct ist hdr;
+ struct buffer *temp;
+ char del = ',';
+ char *ptr, *attr_beg, *attr_end;
+ size_t len = 0;
+ int is_req = !(check || (chn && chn->flags & CF_ISRESP));
+
+ if (!htx)
+ return 0;
+
+ if (args->type == ARGT_STR)
+ del = *args[0].data.str.area;
+
+ hdr = (is_req ? ist("Cookie") : ist("Set-Cookie"));
+ temp = get_trash_chunk();
+
+ smp->flags |= SMP_F_VOL_HDR;
+ attr_end = attr_beg = NULL;
+ ctx.blk = NULL;
+ /* Scan through all headers and extract all cookie names from
+ * 1. Cookie header(s) for request channel OR
+ * 2. Set-Cookie header(s) for response channel
+ */
+ while (1) {
+ /* Note: attr_beg == NULL every time we need to fetch a new header */
+ if (!attr_beg) {
+ /* For Set-Cookie, we need to fetch the entire header line (set flag to 1) */
+ if (!http_find_header(htx, hdr, &ctx, !is_req))
+ break;
+ attr_beg = ctx.value.ptr;
+ attr_end = attr_beg + ctx.value.len;
+ }
+
+ while (1) {
+ attr_beg = http_extract_next_cookie_name(attr_beg, attr_end, is_req, &ptr, &len);
+ if (!attr_beg)
+ break;
+
+ /* prepend delimiter if this is not the first cookie name found */
+ if (temp->data)
+ temp->area[temp->data++] = del;
+
+ /* At this point ptr should point to the start of the cookie name and len would be the length of the cookie name */
+ if (!chunk_memcat(temp, ptr, len))
+ return 0;
+ }
+ }
+ smp->data.type = SMP_T_STR;
+ smp->data.u.str = *temp;
+ return 1;
+}
+
/************************************************************************/
/* The code below is dedicated to sample fetches */
/************************************************************************/
{ "req.cook", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRQHV },
{ "req.cook_cnt", smp_fetch_cookie_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV },
{ "req.cook_val", smp_fetch_cookie_val, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV },
+ { "req.cook_names", smp_fetch_cookie_names, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRQHV },
{ "req.fhdr", smp_fetch_fhdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRQHV },
{ "req.fhdr_cnt", smp_fetch_fhdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRQHV },
{ "res.cook", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRSHV },
{ "res.cook_cnt", smp_fetch_cookie_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV },
{ "res.cook_val", smp_fetch_cookie_val, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV },
+ { "res.cook_names", smp_fetch_cookie_names, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRSHV },
{ "res.fhdr", smp_fetch_fhdr, ARG2(0,STR,SINT), val_hdr, SMP_T_STR, SMP_USE_HRSHV },
{ "res.fhdr_cnt", smp_fetch_fhdr_cnt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_HRSHV },