REORG: http: move the code to different files

The current proto_http.c file is huge and contains different processing
domains making it very difficult to work on an alternative representation.
This commit moves some parts to other files :

  - ACL registration code => http_acl.c
    This code only creates some ACL mappings and doesn't know anything
    about HTTP nor about the representation. This code could even have
    moved to acl.c but it was not worth polluting it again.

  - HTTP sample conversion => http_conv.c
    This code doesn't depend on the internal representation but definitely
    manipulates some HTTP elements, such as dates. It also has access to
    captures.

  - HTTP sample fetching => http_fetch.c
    This code does depend entirely on the internal representation but is
    totally independent on the analysers. Placing it into a different
    file will ease the transition to the new representation and the
    creation of a wrapper if required. An include file was created due
    to CHECK_HTTP_MESSAGE_FIRST() being used at various places.

  - HTTP action registration => http_act.c
    This code doesn't directly interact with the messages nor the
    transaction but it does so via some exported http functions like
    http_replace_req_line() or http_set_status() so it will be easier
    to change only this after the conversion.

  - a few very generic parts were found and moved to http.{c,h} as
    relevant.

It is worth noting that the functions moved to these new files are not
referenced anywhere outside of the files and are only called as registered
callbacks, so these files do not even require associated include files.
diff --git a/Makefile b/Makefile
index 074e016..3140a4a 100644
--- a/Makefile
+++ b/Makefile
@@ -894,6 +894,7 @@
        src/time.o src/proto_udp.o src/arg.o src/signal.o                \
        src/protocol.o src/lru.o src/hdr_idx.o src/hpack-huff.o          \
        src/mailers.o src/h2.o src/base64.o src/hash.o src/http.o	\
+       src/http_acl.o src/http_fetch.o src/http_conv.o src/http_act.o   \
        src/proto_sockpair.o
 
 EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o $(EBTREE_DIR)/eb32sctree.o \
diff --git a/include/common/http.h b/include/common/http.h
index 3377db4..9da2af8 100644
--- a/include/common/http.h
+++ b/include/common/http.h
@@ -161,6 +161,32 @@
 	return c == '&' || c == ';' || c == delim;
 }
 
+/* Match language range with language tag. RFC2616 14.4:
+ *
+ *    A language-range matches a language-tag if it exactly equals
+ *    the tag, or if it exactly equals a prefix of the tag such
+ *    that the first tag character following the prefix is "-".
+ *
+ * Return 1 if the strings match, else return 0.
+ */
+static inline int http_language_range_match(const char *range, int range_len,
+                                            const char *tag, int tag_len)
+{
+	const char *end = range + range_len;
+	const char *tend = tag + tag_len;
+
+	while (range < end) {
+		if (*range == '-' && tag == tend)
+			return 1;
+		if (*range != *tag || tag == tend)
+			return 0;
+		range++;
+		tag++;
+	}
+	/* Return true only if the last char of the tag is matched. */
+	return tag == tend;
+}
+
 
 #endif /* _COMMON_HTTP_H */
 
diff --git a/include/proto/http_fetch.h b/include/proto/http_fetch.h
new file mode 100644
index 0000000..efd188a
--- /dev/null
+++ b/include/proto/http_fetch.h
@@ -0,0 +1,52 @@
+/*
+ * include/proto/http_fetch.h
+ * This file contains the minimally required http sample fetch declarations.
+ *
+ * Copyright (C) 2000-2018 Willy Tarreau - w@1wt.eu
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _PROTO_HTTP_FETCH_H
+#define _PROTO_HTTP_FETCH_H
+
+#include <common/config.h>
+#include <common/mini-clist.h>
+#include <types/action.h>
+#include <types/proxy.h>
+
+/* Note: these functions *do* modify the sample. Even in case of success, at
+ * least the type and uint value are modified.
+ */
+#define CHECK_HTTP_MESSAGE_FIRST() \
+	do { int r = smp_prefetch_http(smp->px, smp->strm, smp->opt, args, smp, 1); if (r <= 0) return r; } while (0)
+
+#define CHECK_HTTP_MESSAGE_FIRST_PERM() \
+	do { int r = smp_prefetch_http(smp->px, smp->strm, smp->opt, args, smp, 0); if (r <= 0) return r; } while (0)
+
+int smp_prefetch_http(struct proxy *px, struct stream *s, unsigned int opt,
+                  const struct arg *args, struct sample *smp, int req_vol);
+
+int val_hdr(struct arg *arg, char **err_msg);
+
+
+#endif /* _PROTO_HTTP_FETCH_H */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ */
diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h
index 1b8da06..da6d0c7 100644
--- a/include/proto/proto_http.h
+++ b/include/proto/proto_http.h
@@ -44,6 +44,8 @@
  *   ver_token           = 'H', 'P', 'T', '/', '.', and digits.
  */
 
+extern struct pool_head *pool_head_uniqueid;
+
 int process_cli(struct stream *s);
 int process_srv_data(struct stream *s);
 int process_srv_conn(struct stream *s);
@@ -57,6 +59,7 @@
 int http_process_res_common(struct stream *s, struct channel *rep, int an_bit, struct proxy *px);
 int http_request_forward_body(struct stream *s, struct channel *req, int an_bit);
 int http_response_forward_body(struct stream *s, struct channel *res, int an_bit);
+int http_upgrade_v09_to_v10(struct http_txn *txn);
 void http_msg_analyzer(struct http_msg *msg, struct hdr_idx *idx);
 void http_txn_reset_req(struct http_txn *txn);
 void http_txn_reset_res(struct http_txn *txn);
@@ -100,6 +103,9 @@
 unsigned int http_get_hdr(const struct http_msg *msg, const char *hname, int hlen,
 			  struct hdr_idx *idx, int occ,
 			  struct hdr_ctx *ctx, char **vptr, size_t *vlen);
+unsigned int http_get_fhdr(const struct http_msg *msg, const char *hname, int hlen,
+			   struct hdr_idx *idx, int occ,
+			   struct hdr_ctx *ctx, char **vptr, size_t *vlen);
 char *http_txn_get_path(const struct http_txn *txn);
 
 struct http_txn *http_alloc_txn(struct stream *s);
@@ -117,31 +123,9 @@
 struct buffer *http_error_message(struct stream *s);
 struct redirect_rule *http_parse_redirect_rule(const char *file, int linenum, struct proxy *curproxy,
                                                const char **args, char **errmsg, int use_fmt, int dir);
-int smp_fetch_cookie(const struct arg *args, struct sample *smp, const char *kw, void *private);
-int smp_fetch_base32(const struct arg *args, struct sample *smp, const char *kw, void *private);
 
 struct action_kw *action_http_req_custom(const char *kw);
 struct action_kw *action_http_res_custom(const char *kw);
-int val_hdr(struct arg *arg, char **err_msg);
-
-int smp_prefetch_http(struct proxy *px, struct stream *s, unsigned int opt,
-                  const struct arg *args, struct sample *smp, int req_vol);
-
-enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px,
-                                              struct session *sess, struct stream *s, int flags);
-enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
-                                              struct session *sess, struct stream *s, int flags);
-
-int parse_qvalue(const char *qvalue, const char **end);
-
-/* Note: these functions *do* modify the sample. Even in case of success, at
- * least the type and uint value are modified.
- */
-#define CHECK_HTTP_MESSAGE_FIRST() \
-	do { int r = smp_prefetch_http(smp->px, smp->strm, smp->opt, args, smp, 1); if (r <= 0) return r; } while (0)
-
-#define CHECK_HTTP_MESSAGE_FIRST_PERM() \
-	do { int r = smp_prefetch_http(smp->px, smp->strm, smp->opt, args, smp, 0); if (r <= 0) return r; } while (0)
 
 static inline void http_req_keywords_register(struct action_kw_list *kw_list)
 {
diff --git a/src/51d.c b/src/51d.c
index 687528e..e092c5b 100644
--- a/src/51d.c
+++ b/src/51d.c
@@ -5,6 +5,7 @@
 #include <common/buffer.h>
 #include <common/errors.h>
 #include <proto/arg.h>
+#include <proto/http_fetch.h>
 #include <proto/log.h>
 #include <proto/proto_http.h>
 #include <proto/sample.h>
diff --git a/src/da.c b/src/da.c
index 23da04a..c653848 100644
--- a/src/da.c
+++ b/src/da.c
@@ -3,6 +3,7 @@
 #include <common/cfgparse.h>
 #include <common/errors.h>
 #include <proto/arg.h>
+#include <proto/http_fetch.h>
 #include <proto/log.h>
 #include <proto/proto_http.h>
 #include <proto/sample.h>
diff --git a/src/hlua.c b/src/hlua.c
index 759e7b2..c2be5f9 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -42,6 +42,7 @@
 #include <proto/hdr_idx.h>
 #include <proto/hlua.h>
 #include <proto/hlua_fcn.h>
+#include <proto/http_fetch.h>
 #include <proto/map.h>
 #include <proto/obj_type.h>
 #include <proto/queue.h>
diff --git a/src/http_acl.c b/src/http_acl.c
new file mode 100644
index 0000000..7864df8
--- /dev/null
+++ b/src/http_acl.c
@@ -0,0 +1,194 @@
+/*
+ * HTTP ACLs declaration
+ *
+ * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+#include <common/chunk.h>
+#include <common/compat.h>
+#include <common/config.h>
+#include <common/debug.h>
+#include <common/http.h>
+#include <common/memory.h>
+#include <common/standard.h>
+#include <common/version.h>
+
+#include <types/global.h>
+
+#include <proto/acl.h>
+#include <proto/arg.h>
+#include <proto/auth.h>
+#include <proto/pattern.h>
+
+
+/* We use the pre-parsed method if it is known, and store its number as an
+ * integer. If it is unknown, we use the pointer and the length.
+ */
+static int pat_parse_meth(const char *text, struct pattern *pattern, int mflags, char **err)
+{
+	int len, meth;
+
+	len  = strlen(text);
+	meth = find_http_meth(text, len);
+
+	pattern->val.i = meth;
+	if (meth == HTTP_METH_OTHER) {
+		pattern->ptr.str = (char *)text;
+		pattern->len = len;
+	}
+	else {
+		pattern->ptr.str = NULL;
+		pattern->len = 0;
+	}
+	return 1;
+}
+
+/* See above how the method is stored in the global pattern */
+static struct pattern *pat_match_meth(struct sample *smp, struct pattern_expr *expr, int fill)
+{
+	int icase;
+	struct pattern_list *lst;
+	struct pattern *pattern;
+
+	list_for_each_entry(lst, &expr->patterns, list) {
+		pattern = &lst->pat;
+
+		/* well-known method */
+		if (pattern->val.i != HTTP_METH_OTHER) {
+			if (smp->data.u.meth.meth == pattern->val.i)
+				return pattern;
+			else
+				continue;
+		}
+
+		/* Other method, we must compare the strings */
+		if (pattern->len != smp->data.u.meth.str.data)
+			continue;
+
+		icase = expr->mflags & PAT_MF_IGNORE_CASE;
+		if ((icase && strncasecmp(pattern->ptr.str, smp->data.u.meth.str.area, smp->data.u.meth.str.data) == 0) ||
+		    (!icase && strncmp(pattern->ptr.str, smp->data.u.meth.str.area, smp->data.u.meth.str.data) == 0))
+			return pattern;
+	}
+	return NULL;
+}
+
+/************************************************************************/
+/*          All supported ACL keywords must be declared here.           */
+/************************************************************************/
+
+/* Note: must not be declared <const> as its list will be overwritten.
+ * Please take care of keeping this list alphabetically sorted.
+ */
+static struct acl_kw_list acl_kws = {ILH, {
+	{ "base",            "base",     PAT_MATCH_STR },
+	{ "base_beg",        "base",     PAT_MATCH_BEG },
+	{ "base_dir",        "base",     PAT_MATCH_DIR },
+	{ "base_dom",        "base",     PAT_MATCH_DOM },
+	{ "base_end",        "base",     PAT_MATCH_END },
+	{ "base_len",        "base",     PAT_MATCH_LEN },
+	{ "base_reg",        "base",     PAT_MATCH_REG },
+	{ "base_sub",        "base",     PAT_MATCH_SUB },
+
+	{ "cook",            "req.cook", PAT_MATCH_STR },
+	{ "cook_beg",        "req.cook", PAT_MATCH_BEG },
+	{ "cook_dir",        "req.cook", PAT_MATCH_DIR },
+	{ "cook_dom",        "req.cook", PAT_MATCH_DOM },
+	{ "cook_end",        "req.cook", PAT_MATCH_END },
+	{ "cook_len",        "req.cook", PAT_MATCH_LEN },
+	{ "cook_reg",        "req.cook", PAT_MATCH_REG },
+	{ "cook_sub",        "req.cook", PAT_MATCH_SUB },
+
+	{ "hdr",             "req.hdr",  PAT_MATCH_STR },
+	{ "hdr_beg",         "req.hdr",  PAT_MATCH_BEG },
+	{ "hdr_dir",         "req.hdr",  PAT_MATCH_DIR },
+	{ "hdr_dom",         "req.hdr",  PAT_MATCH_DOM },
+	{ "hdr_end",         "req.hdr",  PAT_MATCH_END },
+	{ "hdr_len",         "req.hdr",  PAT_MATCH_LEN },
+	{ "hdr_reg",         "req.hdr",  PAT_MATCH_REG },
+	{ "hdr_sub",         "req.hdr",  PAT_MATCH_SUB },
+
+	/* these two declarations uses strings with list storage (in place
+	 * of tree storage). The basic match is PAT_MATCH_STR, but the indexation
+	 * and delete functions are relative to the list management. The parse
+	 * and match method are related to the corresponding fetch methods. This
+	 * is very particular ACL declaration mode.
+	 */
+	{ "http_auth_group", NULL,       PAT_MATCH_STR, NULL,  pat_idx_list_str, pat_del_list_ptr, NULL, pat_match_auth },
+	{ "method",          NULL,       PAT_MATCH_STR, pat_parse_meth, pat_idx_list_str, pat_del_list_ptr, NULL, pat_match_meth },
+
+	{ "path",            "path",     PAT_MATCH_STR },
+	{ "path_beg",        "path",     PAT_MATCH_BEG },
+	{ "path_dir",        "path",     PAT_MATCH_DIR },
+	{ "path_dom",        "path",     PAT_MATCH_DOM },
+	{ "path_end",        "path",     PAT_MATCH_END },
+	{ "path_len",        "path",     PAT_MATCH_LEN },
+	{ "path_reg",        "path",     PAT_MATCH_REG },
+	{ "path_sub",        "path",     PAT_MATCH_SUB },
+
+	{ "req_ver",         "req.ver",  PAT_MATCH_STR },
+	{ "resp_ver",        "res.ver",  PAT_MATCH_STR },
+
+	{ "scook",           "res.cook", PAT_MATCH_STR },
+	{ "scook_beg",       "res.cook", PAT_MATCH_BEG },
+	{ "scook_dir",       "res.cook", PAT_MATCH_DIR },
+	{ "scook_dom",       "res.cook", PAT_MATCH_DOM },
+	{ "scook_end",       "res.cook", PAT_MATCH_END },
+	{ "scook_len",       "res.cook", PAT_MATCH_LEN },
+	{ "scook_reg",       "res.cook", PAT_MATCH_REG },
+	{ "scook_sub",       "res.cook", PAT_MATCH_SUB },
+
+	{ "shdr",            "res.hdr",  PAT_MATCH_STR },
+	{ "shdr_beg",        "res.hdr",  PAT_MATCH_BEG },
+	{ "shdr_dir",        "res.hdr",  PAT_MATCH_DIR },
+	{ "shdr_dom",        "res.hdr",  PAT_MATCH_DOM },
+	{ "shdr_end",        "res.hdr",  PAT_MATCH_END },
+	{ "shdr_len",        "res.hdr",  PAT_MATCH_LEN },
+	{ "shdr_reg",        "res.hdr",  PAT_MATCH_REG },
+	{ "shdr_sub",        "res.hdr",  PAT_MATCH_SUB },
+
+	{ "url",             "url",      PAT_MATCH_STR },
+	{ "url_beg",         "url",      PAT_MATCH_BEG },
+	{ "url_dir",         "url",      PAT_MATCH_DIR },
+	{ "url_dom",         "url",      PAT_MATCH_DOM },
+	{ "url_end",         "url",      PAT_MATCH_END },
+	{ "url_len",         "url",      PAT_MATCH_LEN },
+	{ "url_reg",         "url",      PAT_MATCH_REG },
+	{ "url_sub",         "url",      PAT_MATCH_SUB },
+
+	{ "urlp",            "urlp",     PAT_MATCH_STR },
+	{ "urlp_beg",        "urlp",     PAT_MATCH_BEG },
+	{ "urlp_dir",        "urlp",     PAT_MATCH_DIR },
+	{ "urlp_dom",        "urlp",     PAT_MATCH_DOM },
+	{ "urlp_end",        "urlp",     PAT_MATCH_END },
+	{ "urlp_len",        "urlp",     PAT_MATCH_LEN },
+	{ "urlp_reg",        "urlp",     PAT_MATCH_REG },
+	{ "urlp_sub",        "urlp",     PAT_MATCH_SUB },
+
+	{ /* END */ },
+}};
+
+__attribute__((constructor))
+static void __http_acl_init(void)
+{
+	acl_register_keywords(&acl_kws);
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ */
diff --git a/src/http_act.c b/src/http_act.c
new file mode 100644
index 0000000..6ed4852
--- /dev/null
+++ b/src/http_act.c
@@ -0,0 +1,608 @@
+/*
+ * HTTP actions
+ *
+ * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+#include <common/chunk.h>
+#include <common/compat.h>
+#include <common/config.h>
+#include <common/debug.h>
+#include <common/http.h>
+#include <common/memory.h>
+#include <common/standard.h>
+#include <common/version.h>
+
+#include <types/capture.h>
+#include <types/global.h>
+
+#include <proto/acl.h>
+#include <proto/arg.h>
+#include <proto/log.h>
+#include <proto/proto_http.h>
+
+
+/* This function executes one of the set-{method,path,query,uri} actions. It
+ * builds a string in the trash from the specified format string. It finds
+ * the action to be performed in <http.action>, previously filled by function
+ * parse_set_req_line(). The replacement action is excuted by the function
+ * http_action_set_req_line(). It always returns ACT_RET_CONT. If an error
+ * occurs the action is canceled, but the rule processing continue.
+ */
+static enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px,
+                                                struct session *sess, struct stream *s, int flags)
+{
+	struct buffer *replace;
+	enum act_return ret = ACT_RET_ERR;
+
+	replace = alloc_trash_chunk();
+	if (!replace)
+		goto leave;
+
+	/* If we have to create a query string, prepare a '?'. */
+	if (rule->arg.http.action == 2)
+		replace->area[replace->data++] = '?';
+	replace->data += build_logline(s, replace->area + replace->data,
+				       replace->size - replace->data,
+				       &rule->arg.http.logfmt);
+
+	http_replace_req_line(rule->arg.http.action, replace->area,
+			      replace->data, px, s);
+
+	ret = ACT_RET_CONT;
+
+leave:
+	free_trash_chunk(replace);
+	return ret;
+}
+
+/* parse an http-request action among :
+ *   set-method
+ *   set-path
+ *   set-query
+ *   set-uri
+ *
+ * All of them accept a single argument of type string representing a log-format.
+ * The resulting rule makes use of arg->act.p[0..1] to store the log-format list
+ * head, and p[2] to store the action as an int (0=method, 1=path, 2=query, 3=uri).
+ * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
+ */
+static enum act_parse_ret parse_set_req_line(const char **args, int *orig_arg, struct proxy *px,
+                                             struct act_rule *rule, char **err)
+{
+	int cur_arg = *orig_arg;
+
+	rule->action = ACT_CUSTOM;
+
+	switch (args[0][4]) {
+	case 'm' :
+		rule->arg.http.action = 0;
+		rule->action_ptr = http_action_set_req_line;
+		break;
+	case 'p' :
+		rule->arg.http.action = 1;
+		rule->action_ptr = http_action_set_req_line;
+		break;
+	case 'q' :
+		rule->arg.http.action = 2;
+		rule->action_ptr = http_action_set_req_line;
+		break;
+	case 'u' :
+		rule->arg.http.action = 3;
+		rule->action_ptr = http_action_set_req_line;
+		break;
+	default:
+		memprintf(err, "internal error: unhandled action '%s'", args[0]);
+		return ACT_RET_PRS_ERR;
+	}
+
+	if (!*args[cur_arg] ||
+	    (*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) {
+		memprintf(err, "expects exactly 1 argument <format>");
+		return ACT_RET_PRS_ERR;
+	}
+
+	LIST_INIT(&rule->arg.http.logfmt);
+	px->conf.args.ctx = ARGC_HRQ;
+	if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.logfmt, LOG_OPT_HTTP,
+	                            (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) {
+		return ACT_RET_PRS_ERR;
+	}
+
+	(*orig_arg)++;
+	return ACT_RET_PRS_OK;
+}
+
+/* This function is just a compliant action wrapper for "set-status". */
+static enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px,
+                                              struct session *sess, struct stream *s, int flags)
+{
+	http_set_status(rule->arg.status.code, rule->arg.status.reason, s);
+	return ACT_RET_CONT;
+}
+
+/* parse set-status action:
+ * This action accepts a single argument of type int representing
+ * an http status code. It returns ACT_RET_PRS_OK on success,
+ * ACT_RET_PRS_ERR on error.
+ */
+static enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struct proxy *px,
+                                                struct act_rule *rule, char **err)
+{
+	char *error;
+
+	rule->action = ACT_CUSTOM;
+	rule->action_ptr = action_http_set_status;
+
+	/* Check if an argument is available */
+	if (!*args[*orig_arg]) {
+		memprintf(err, "expects 1 argument: <status>; or 3 arguments: <status> reason <fmt>");
+		return ACT_RET_PRS_ERR;
+	}
+
+	/* convert status code as integer */
+	rule->arg.status.code = strtol(args[*orig_arg], &error, 10);
+	if (*error != '\0' || rule->arg.status.code < 100 || rule->arg.status.code > 999) {
+		memprintf(err, "expects an integer status code between 100 and 999");
+		return ACT_RET_PRS_ERR;
+	}
+
+	(*orig_arg)++;
+
+	/* set custom reason string */
+	rule->arg.status.reason = NULL; // If null, we use the default reason for the status code.
+	if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 &&
+	    (*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) {
+		(*orig_arg)++;
+		rule->arg.status.reason = strdup(args[*orig_arg]);
+		(*orig_arg)++;
+	}
+
+	return ACT_RET_PRS_OK;
+}
+
+/* This function executes the "reject" HTTP action. It clears the request and
+ * response buffer without sending any response. It can be useful as an HTTP
+ * alternative to the silent-drop action to defend against DoS attacks, and may
+ * also be used with HTTP/2 to close a connection instead of just a stream.
+ * The txn status is unchanged, indicating no response was sent. The termination
+ * flags will indicate "PR". It always returns ACT_RET_STOP.
+ */
+static enum act_return http_action_reject(struct act_rule *rule, struct proxy *px,
+                                          struct session *sess, struct stream *s, int flags)
+{
+	channel_abort(&s->req);
+	channel_abort(&s->res);
+	s->req.analysers = 0;
+	s->res.analysers = 0;
+
+	HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
+	HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1);
+	if (sess->listener && sess->listener->counters)
+		HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
+
+	if (!(s->flags & SF_ERR_MASK))
+		s->flags |= SF_ERR_PRXCOND;
+	if (!(s->flags & SF_FINST_MASK))
+		s->flags |= SF_FINST_R;
+
+	return ACT_RET_CONT;
+}
+
+/* parse the "reject" action:
+ * This action takes no argument and returns ACT_RET_PRS_OK on success,
+ * ACT_RET_PRS_ERR on error.
+ */
+static enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px,
+                                                   struct act_rule *rule, char **err)
+{
+	rule->action = ACT_CUSTOM;
+	rule->action_ptr = http_action_reject;
+	return ACT_RET_PRS_OK;
+}
+
+/* This function executes the "capture" action. It executes a fetch expression,
+ * turns the result into a string and puts it in a capture slot. It always
+ * returns 1. If an error occurs the action is cancelled, but the rule
+ * processing continues.
+ */
+static enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px,
+                                               struct session *sess, struct stream *s, int flags)
+{
+	struct sample *key;
+	struct cap_hdr *h = rule->arg.cap.hdr;
+	char **cap = s->req_cap;
+	int len;
+
+	key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR);
+	if (!key)
+		return ACT_RET_CONT;
+
+	if (cap[h->index] == NULL)
+		cap[h->index] = pool_alloc(h->pool);
+
+	if (cap[h->index] == NULL) /* no more capture memory */
+		return ACT_RET_CONT;
+
+	len = key->data.u.str.data;
+	if (len > h->len)
+		len = h->len;
+
+	memcpy(cap[h->index], key->data.u.str.area, len);
+	cap[h->index][len] = 0;
+	return ACT_RET_CONT;
+}
+
+/* This function executes the "capture" action and store the result in a
+ * capture slot if exists. It executes a fetch expression, turns the result
+ * into a string and puts it in a capture slot. It always returns 1. If an
+ * error occurs the action is cancelled, but the rule processing continues.
+ */
+static enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px,
+                                                     struct session *sess, struct stream *s, int flags)
+{
+	struct sample *key;
+	struct cap_hdr *h;
+	char **cap = s->req_cap;
+	struct proxy *fe = strm_fe(s);
+	int len;
+	int i;
+
+	/* Look for the original configuration. */
+	for (h = fe->req_cap, i = fe->nb_req_cap - 1;
+	     h != NULL && i != rule->arg.capid.idx ;
+	     i--, h = h->next);
+	if (!h)
+		return ACT_RET_CONT;
+
+	key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
+	if (!key)
+		return ACT_RET_CONT;
+
+	if (cap[h->index] == NULL)
+		cap[h->index] = pool_alloc(h->pool);
+
+	if (cap[h->index] == NULL) /* no more capture memory */
+		return ACT_RET_CONT;
+
+	len = key->data.u.str.data;
+	if (len > h->len)
+		len = h->len;
+
+	memcpy(cap[h->index], key->data.u.str.area, len);
+	cap[h->index][len] = 0;
+	return ACT_RET_CONT;
+}
+
+/* Check an "http-request capture" action.
+ *
+ * The function returns 1 in success case, otherwise, it returns 0 and err is
+ * filled.
+ */
+static int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err)
+{
+	if (rule->action_ptr != http_action_req_capture_by_id)
+		return 1;
+
+	if (rule->arg.capid.idx >= px->nb_req_cap) {
+		memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule",
+			  rule->arg.capid.idx);
+		return 0;
+	}
+
+	return 1;
+}
+
+/* parse an "http-request capture" action. It takes a single argument which is
+ * a sample fetch expression. It stores the expression into arg->act.p[0] and
+ * the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1].
+ * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
+ */
+static enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px,
+                                                 struct act_rule *rule, char **err)
+{
+	struct sample_expr *expr;
+	struct cap_hdr *hdr;
+	int cur_arg;
+	int len = 0;
+
+	for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
+		if (strcmp(args[cur_arg], "if") == 0 ||
+		    strcmp(args[cur_arg], "unless") == 0)
+			break;
+
+	if (cur_arg < *orig_arg + 3) {
+		memprintf(err, "expects <expression> [ 'len' <length> | id <idx> ]");
+		return ACT_RET_PRS_ERR;
+	}
+
+	cur_arg = *orig_arg;
+	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
+	if (!expr)
+		return ACT_RET_PRS_ERR;
+
+	if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) {
+		memprintf(err,
+			  "fetch method '%s' extracts information from '%s', none of which is available here",
+			  args[cur_arg-1], sample_src_names(expr->fetch->use));
+		free(expr);
+		return ACT_RET_PRS_ERR;
+	}
+
+	if (!args[cur_arg] || !*args[cur_arg]) {
+		memprintf(err, "expects 'len or 'id'");
+		free(expr);
+		return ACT_RET_PRS_ERR;
+	}
+
+	if (strcmp(args[cur_arg], "len") == 0) {
+		cur_arg++;
+
+		if (!(px->cap & PR_CAP_FE)) {
+			memprintf(err, "proxy '%s' has no frontend capability", px->id);
+			return ACT_RET_PRS_ERR;
+		}
+
+		px->conf.args.ctx = ARGC_CAP;
+
+		if (!args[cur_arg]) {
+			memprintf(err, "missing length value");
+			free(expr);
+			return ACT_RET_PRS_ERR;
+		}
+		/* we copy the table name for now, it will be resolved later */
+		len = atoi(args[cur_arg]);
+		if (len <= 0) {
+			memprintf(err, "length must be > 0");
+			free(expr);
+			return ACT_RET_PRS_ERR;
+		}
+		cur_arg++;
+
+		if (!len) {
+			memprintf(err, "a positive 'len' argument is mandatory");
+			free(expr);
+			return ACT_RET_PRS_ERR;
+		}
+
+		hdr = calloc(1, sizeof(*hdr));
+		hdr->next = px->req_cap;
+		hdr->name = NULL; /* not a header capture */
+		hdr->namelen = 0;
+		hdr->len = len;
+		hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
+		hdr->index = px->nb_req_cap++;
+
+		px->req_cap = hdr;
+		px->to_log |= LW_REQHDR;
+
+		rule->action       = ACT_CUSTOM;
+		rule->action_ptr   = http_action_req_capture;
+		rule->arg.cap.expr = expr;
+		rule->arg.cap.hdr  = hdr;
+	}
+
+	else if (strcmp(args[cur_arg], "id") == 0) {
+		int id;
+		char *error;
+
+		cur_arg++;
+
+		if (!args[cur_arg]) {
+			memprintf(err, "missing id value");
+			free(expr);
+			return ACT_RET_PRS_ERR;
+		}
+
+		id = strtol(args[cur_arg], &error, 10);
+		if (*error != '\0') {
+			memprintf(err, "cannot parse id '%s'", args[cur_arg]);
+			free(expr);
+			return ACT_RET_PRS_ERR;
+		}
+		cur_arg++;
+
+		px->conf.args.ctx = ARGC_CAP;
+
+		rule->action       = ACT_CUSTOM;
+		rule->action_ptr   = http_action_req_capture_by_id;
+		rule->check_ptr    = check_http_req_capture;
+		rule->arg.capid.expr = expr;
+		rule->arg.capid.idx  = id;
+	}
+
+	else {
+		memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
+		free(expr);
+		return ACT_RET_PRS_ERR;
+	}
+
+	*orig_arg = cur_arg;
+	return ACT_RET_PRS_OK;
+}
+
+/* This function executes the "capture" action and store the result in a
+ * capture slot if exists. It executes a fetch expression, turns the result
+ * into a string and puts it in a capture slot. It always returns 1. If an
+ * error occurs the action is cancelled, but the rule processing continues.
+ */
+static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
+                                                     struct session *sess, struct stream *s, int flags)
+{
+	struct sample *key;
+	struct cap_hdr *h;
+	char **cap = s->res_cap;
+	struct proxy *fe = strm_fe(s);
+	int len;
+	int i;
+
+	/* Look for the original configuration. */
+	for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
+	     h != NULL && i != rule->arg.capid.idx ;
+	     i--, h = h->next);
+	if (!h)
+		return ACT_RET_CONT;
+
+	key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
+	if (!key)
+		return ACT_RET_CONT;
+
+	if (cap[h->index] == NULL)
+		cap[h->index] = pool_alloc(h->pool);
+
+	if (cap[h->index] == NULL) /* no more capture memory */
+		return ACT_RET_CONT;
+
+	len = key->data.u.str.data;
+	if (len > h->len)
+		len = h->len;
+
+	memcpy(cap[h->index], key->data.u.str.area, len);
+	cap[h->index][len] = 0;
+	return ACT_RET_CONT;
+}
+
+/* Check an "http-response capture" action.
+ *
+ * The function returns 1 in success case, otherwise, it returns 0 and err is
+ * filled.
+ */
+static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
+{
+	if (rule->action_ptr != http_action_res_capture_by_id)
+		return 1;
+
+	if (rule->arg.capid.idx >= px->nb_rsp_cap) {
+		memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
+			  rule->arg.capid.idx);
+		return 0;
+	}
+
+	return 1;
+}
+
+/* parse an "http-response capture" action. It takes a single argument which is
+ * a sample fetch expression. It stores the expression into arg->act.p[0] and
+ * the allocated hdr_cap struct od the preallocated id into arg->act.p[1].
+ * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
+ */
+static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
+                                                 struct act_rule *rule, char **err)
+{
+	struct sample_expr *expr;
+	int cur_arg;
+	int id;
+	char *error;
+
+	for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
+		if (strcmp(args[cur_arg], "if") == 0 ||
+		    strcmp(args[cur_arg], "unless") == 0)
+			break;
+
+	if (cur_arg < *orig_arg + 3) {
+		memprintf(err, "expects <expression> id <idx>");
+		return ACT_RET_PRS_ERR;
+	}
+
+	cur_arg = *orig_arg;
+	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
+	if (!expr)
+		return ACT_RET_PRS_ERR;
+
+	if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
+		memprintf(err,
+			  "fetch method '%s' extracts information from '%s', none of which is available here",
+			  args[cur_arg-1], sample_src_names(expr->fetch->use));
+		free(expr);
+		return ACT_RET_PRS_ERR;
+	}
+
+	if (!args[cur_arg] || !*args[cur_arg]) {
+		memprintf(err, "expects 'id'");
+		free(expr);
+		return ACT_RET_PRS_ERR;
+	}
+
+	if (strcmp(args[cur_arg], "id") != 0) {
+		memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
+		free(expr);
+		return ACT_RET_PRS_ERR;
+	}
+
+	cur_arg++;
+
+	if (!args[cur_arg]) {
+		memprintf(err, "missing id value");
+		free(expr);
+		return ACT_RET_PRS_ERR;
+	}
+
+	id = strtol(args[cur_arg], &error, 10);
+	if (*error != '\0') {
+		memprintf(err, "cannot parse id '%s'", args[cur_arg]);
+		free(expr);
+		return ACT_RET_PRS_ERR;
+	}
+	cur_arg++;
+
+	px->conf.args.ctx = ARGC_CAP;
+
+	rule->action       = ACT_CUSTOM;
+	rule->action_ptr   = http_action_res_capture_by_id;
+	rule->check_ptr    = check_http_res_capture;
+	rule->arg.capid.expr = expr;
+	rule->arg.capid.idx  = id;
+
+	*orig_arg = cur_arg;
+	return ACT_RET_PRS_OK;
+}
+
+/************************************************************************/
+/*   All supported http-request action keywords must be declared here.  */
+/************************************************************************/
+
+static struct action_kw_list http_req_actions = {
+	.kw = {
+		{ "capture",    parse_http_req_capture },
+		{ "reject",     parse_http_action_reject },
+		{ "set-method", parse_set_req_line },
+		{ "set-path",   parse_set_req_line },
+		{ "set-query",  parse_set_req_line },
+		{ "set-uri",    parse_set_req_line },
+		{ NULL, NULL }
+	}
+};
+
+static struct action_kw_list http_res_actions = {
+	.kw = {
+		{ "capture",    parse_http_res_capture },
+		{ "set-status", parse_http_set_status },
+		{ NULL, NULL }
+	}
+};
+
+__attribute__((constructor))
+static void __http_act_init(void)
+{
+	http_req_keywords_register(&http_req_actions);
+	http_res_keywords_register(&http_res_actions);
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ */
diff --git a/src/http_conv.c b/src/http_conv.c
new file mode 100644
index 0000000..42a7616
--- /dev/null
+++ b/src/http_conv.c
@@ -0,0 +1,349 @@
+/*
+ * HTTP sample conversion
+ *
+ * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+#include <common/chunk.h>
+#include <common/compat.h>
+#include <common/config.h>
+#include <common/debug.h>
+#include <common/http.h>
+#include <common/memory.h>
+#include <common/standard.h>
+#include <common/version.h>
+
+#include <types/capture.h>
+#include <types/global.h>
+
+#include <proto/arg.h>
+#include <proto/sample.h>
+#include <proto/stream.h>
+
+
+/* takes an UINT value on input supposed to represent the time since EPOCH,
+ * adds an optional offset found in args[0] and emits a string representing
+ * the date in RFC-1123/5322 format.
+ */
+static int sample_conv_http_date(const struct arg *args, struct sample *smp, void *private)
+{
+	const char day[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+	const char mon[12][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+	struct buffer *temp;
+	struct tm *tm;
+	/* With high numbers, the date returned can be negative, the 55 bits mask prevent this. */
+	time_t curr_date = smp->data.u.sint & 0x007fffffffffffffLL;
+
+	/* add offset */
+	if (args && (args[0].type == ARGT_SINT))
+		curr_date += args[0].data.sint;
+
+	tm = gmtime(&curr_date);
+	if (!tm)
+		return 0;
+
+	temp = get_trash_chunk();
+	temp->data = snprintf(temp->area, temp->size - temp->data,
+			      "%s, %02d %s %04d %02d:%02d:%02d GMT",
+			      day[tm->tm_wday], tm->tm_mday, mon[tm->tm_mon],
+			      1900+tm->tm_year,
+			      tm->tm_hour, tm->tm_min, tm->tm_sec);
+
+	smp->data.u.str = *temp;
+	smp->data.type = SMP_T_STR;
+	return 1;
+}
+
+/* Arguments: The list of expected value, the number of parts returned and the separator */
+static int sample_conv_q_preferred(const struct arg *args, struct sample *smp, void *private)
+{
+	const char *al = smp->data.u.str.area;
+	const char *end = al + smp->data.u.str.data;
+	const char *token;
+	int toklen;
+	int qvalue;
+	const char *str;
+	const char *w;
+	int best_q = 0;
+
+	/* Set the constant to the sample, because the output of the
+	 * function will be peek in the constant configuration string.
+	 */
+	smp->flags |= SMP_F_CONST;
+	smp->data.u.str.size = 0;
+	smp->data.u.str.area = "";
+	smp->data.u.str.data = 0;
+
+	/* Parse the accept language */
+	while (1) {
+
+		/* Jump spaces, quit if the end is detected. */
+		while (al < end && isspace((unsigned char)*al))
+			al++;
+		if (al >= end)
+			break;
+
+		/* Start of the fisrt word. */
+		token = al;
+
+		/* Look for separator: isspace(), ',' or ';'. Next value if 0 length word. */
+		while (al < end && *al != ';' && *al != ',' && !isspace((unsigned char)*al))
+			al++;
+		if (al == token)
+			goto expect_comma;
+
+		/* Length of the token. */
+		toklen = al - token;
+		qvalue = 1000;
+
+		/* Check if the token exists in the list. If the token not exists,
+		 * jump to the next token.
+		 */
+		str = args[0].data.str.area;
+		w = str;
+		while (1) {
+			if (*str == ';' || *str == '\0') {
+				if (http_language_range_match(token, toklen, w, str - w))
+					goto look_for_q;
+				if (*str == '\0')
+					goto expect_comma;
+				w = str + 1;
+			}
+			str++;
+		}
+		goto expect_comma;
+
+look_for_q:
+
+		/* Jump spaces, quit if the end is detected. */
+		while (al < end && isspace((unsigned char)*al))
+			al++;
+		if (al >= end)
+			goto process_value;
+
+		/* If ',' is found, process the result */
+		if (*al == ',')
+			goto process_value;
+
+		/* If the character is different from ';', look
+		 * for the end of the header part in best effort.
+		 */
+		if (*al != ';')
+			goto expect_comma;
+
+		/* Assumes that the char is ';', now expect "q=". */
+		al++;
+
+		/* Jump spaces, process value if the end is detected. */
+		while (al < end && isspace((unsigned char)*al))
+			al++;
+		if (al >= end)
+			goto process_value;
+
+		/* Expect 'q'. If no 'q', continue in best effort */
+		if (*al != 'q')
+			goto process_value;
+		al++;
+
+		/* Jump spaces, process value if the end is detected. */
+		while (al < end && isspace((unsigned char)*al))
+			al++;
+		if (al >= end)
+			goto process_value;
+
+		/* Expect '='. If no '=', continue in best effort */
+		if (*al != '=')
+			goto process_value;
+		al++;
+
+		/* Jump spaces, process value if the end is detected. */
+		while (al < end && isspace((unsigned char)*al))
+			al++;
+		if (al >= end)
+			goto process_value;
+
+		/* Parse the q value. */
+		qvalue = http_parse_qvalue(al, &al);
+
+process_value:
+
+		/* If the new q value is the best q value, then store the associated
+		 * language in the response. If qvalue is the biggest value (1000),
+		 * break the process.
+		 */
+		if (qvalue > best_q) {
+			smp->data.u.str.area = (char *)w;
+			smp->data.u.str.data = str - w;
+			if (qvalue >= 1000)
+				break;
+			best_q = qvalue;
+		}
+
+expect_comma:
+
+		/* Expect comma or end. If the end is detected, quit the loop. */
+		while (al < end && *al != ',')
+			al++;
+		if (al >= end)
+			break;
+
+		/* Comma is found, jump it and restart the analyzer. */
+		al++;
+	}
+
+	/* Set default value if required. */
+	if (smp->data.u.str.data == 0 && args[1].type == ARGT_STR) {
+		smp->data.u.str.area = args[1].data.str.area;
+		smp->data.u.str.data = args[1].data.str.data;
+	}
+
+	/* Return true only if a matching language was found. */
+	return smp->data.u.str.data != 0;
+}
+
+/* This fetch url-decode any input string. */
+static int sample_conv_url_dec(const struct arg *args, struct sample *smp, void *private)
+{
+	int len;
+
+	/* If the constant flag is set or if not size is avalaible at
+	 * the end of the buffer, copy the string in other buffer
+	  * before decoding.
+	 */
+	if (smp->flags & SMP_F_CONST || smp->data.u.str.size <= smp->data.u.str.data) {
+		struct buffer *str = get_trash_chunk();
+		memcpy(str->area, smp->data.u.str.area, smp->data.u.str.data);
+		smp->data.u.str.area = str->area;
+		smp->data.u.str.size = str->size;
+		smp->flags &= ~SMP_F_CONST;
+	}
+
+	/* Add final \0 required by url_decode(), and convert the input string. */
+	smp->data.u.str.area[smp->data.u.str.data] = '\0';
+	len = url_decode(smp->data.u.str.area);
+	if (len < 0)
+		return 0;
+	smp->data.u.str.data = len;
+	return 1;
+}
+
+static int smp_conv_req_capture(const struct arg *args, struct sample *smp, void *private)
+{
+	struct proxy *fe = strm_fe(smp->strm);
+	int idx, i;
+	struct cap_hdr *hdr;
+	int len;
+
+	if (!args || args->type != ARGT_SINT)
+		return 0;
+
+	idx = args->data.sint;
+
+	/* Check the availibity of the capture id. */
+	if (idx > fe->nb_req_cap - 1)
+		return 0;
+
+	/* Look for the original configuration. */
+	for (hdr = fe->req_cap, i = fe->nb_req_cap - 1;
+	     hdr != NULL && i != idx ;
+	     i--, hdr = hdr->next);
+	if (!hdr)
+		return 0;
+
+	/* check for the memory allocation */
+	if (smp->strm->req_cap[hdr->index] == NULL)
+		smp->strm->req_cap[hdr->index] = pool_alloc(hdr->pool);
+	if (smp->strm->req_cap[hdr->index] == NULL)
+		return 0;
+
+	/* Check length. */
+	len = smp->data.u.str.data;
+	if (len > hdr->len)
+		len = hdr->len;
+
+	/* Capture input data. */
+	memcpy(smp->strm->req_cap[idx], smp->data.u.str.area, len);
+	smp->strm->req_cap[idx][len] = '\0';
+
+	return 1;
+}
+
+static int smp_conv_res_capture(const struct arg *args, struct sample *smp, void *private)
+{
+	struct proxy *fe = strm_fe(smp->strm);
+	int idx, i;
+	struct cap_hdr *hdr;
+	int len;
+
+	if (!args || args->type != ARGT_SINT)
+		return 0;
+
+	idx = args->data.sint;
+
+	/* Check the availibity of the capture id. */
+	if (idx > fe->nb_rsp_cap - 1)
+		return 0;
+
+	/* Look for the original configuration. */
+	for (hdr = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
+	     hdr != NULL && i != idx ;
+	     i--, hdr = hdr->next);
+	if (!hdr)
+		return 0;
+
+	/* check for the memory allocation */
+	if (smp->strm->res_cap[hdr->index] == NULL)
+		smp->strm->res_cap[hdr->index] = pool_alloc(hdr->pool);
+	if (smp->strm->res_cap[hdr->index] == NULL)
+		return 0;
+
+	/* Check length. */
+	len = smp->data.u.str.data;
+	if (len > hdr->len)
+		len = hdr->len;
+
+	/* Capture input data. */
+	memcpy(smp->strm->res_cap[idx], smp->data.u.str.area, len);
+	smp->strm->res_cap[idx][len] = '\0';
+
+	return 1;
+}
+
+/************************************************************************/
+/*        All supported converter keywords must be declared here.       */
+/************************************************************************/
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_conv_kw_list sample_conv_kws = {ILH, {
+	{ "http_date",      sample_conv_http_date,    ARG1(0,SINT),     NULL,   SMP_T_SINT, SMP_T_STR},
+	{ "language",       sample_conv_q_preferred,  ARG2(1,STR,STR),  NULL,   SMP_T_STR,  SMP_T_STR},
+	{ "capture-req",    smp_conv_req_capture,     ARG1(1,SINT),     NULL,   SMP_T_STR,  SMP_T_STR},
+	{ "capture-res",    smp_conv_res_capture,     ARG1(1,SINT),     NULL,   SMP_T_STR,  SMP_T_STR},
+	{ "url_dec",        sample_conv_url_dec,      0,                NULL,   SMP_T_STR,  SMP_T_STR},
+	{ NULL, NULL, 0, 0, 0 },
+}};
+
+__attribute__((constructor))
+static void __http_conv_init(void)
+{
+	sample_register_convs(&sample_conv_kws);
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ */
diff --git a/src/http_fetch.c b/src/http_fetch.c
new file mode 100644
index 0000000..40381cc
--- /dev/null
+++ b/src/http_fetch.c
@@ -0,0 +1,1930 @@
+/*
+ * HTTP samples fetching
+ *
+ * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+#include <common/base64.h>
+#include <common/chunk.h>
+#include <common/compat.h>
+#include <common/config.h>
+#include <common/debug.h>
+#include <common/http.h>
+#include <common/memory.h>
+#include <common/standard.h>
+#include <common/version.h>
+
+#include <types/global.h>
+
+#include <proto/arg.h>
+#include <proto/auth.h>
+#include <proto/http_fetch.h>
+#include <proto/log.h>
+#include <proto/obj_type.h>
+#include <proto/proto_http.h>
+#include <proto/sample.h>
+#include <proto/stream.h>
+
+
+/* this struct is used between calls to smp_fetch_hdr() or smp_fetch_cookie() */
+static THREAD_LOCAL struct hdr_ctx static_hdr_ctx;
+
+/*
+ * Returns the data from Authorization header. Function may be called more
+ * than once so data is stored in txn->auth_data. When no header is found
+ * or auth method is unknown auth_method is set to HTTP_AUTH_WRONG to avoid
+ * searching again for something we are unable to find anyway. However, if
+ * the result if valid, the cache is not reused because we would risk to
+ * have the credentials overwritten by another stream in parallel.
+ */
+
+static int get_http_auth(struct stream *s)
+{
+
+	struct http_txn *txn = s->txn;
+	struct buffer auth_method;
+	struct hdr_ctx ctx;
+	char *h, *p;
+	int len;
+
+#ifdef DEBUG_AUTH
+	printf("Auth for stream %p: %d\n", s, txn->auth.method);
+#endif
+
+	if (txn->auth.method == HTTP_AUTH_WRONG)
+		return 0;
+
+	txn->auth.method = HTTP_AUTH_WRONG;
+
+	ctx.idx = 0;
+
+	if (txn->flags & TX_USE_PX_CONN) {
+		h = "Proxy-Authorization";
+		len = strlen(h);
+	} else {
+		h = "Authorization";
+		len = strlen(h);
+	}
+
+	if (!http_find_header2(h, len, ci_head(&s->req), &txn->hdr_idx, &ctx))
+		return 0;
+
+	h = ctx.line + ctx.val;
+
+	p = memchr(h, ' ', ctx.vlen);
+	len = p - h;
+	if (!p || len <= 0)
+		return 0;
+
+	if (chunk_initlen(&auth_method, h, 0, len) != 1)
+		return 0;
+
+	chunk_initlen(&txn->auth.method_data, p + 1, 0, ctx.vlen - len - 1);
+
+	if (!strncasecmp("Basic", auth_method.area, auth_method.data)) {
+		struct buffer *http_auth = get_trash_chunk();
+
+		len = base64dec(txn->auth.method_data.area,
+				txn->auth.method_data.data,
+				http_auth->area, global.tune.bufsize - 1);
+
+		if (len < 0)
+			return 0;
+
+
+		http_auth->area[len] = '\0';
+
+		p = strchr(http_auth->area, ':');
+
+		if (!p)
+			return 0;
+
+		txn->auth.user = http_auth->area;
+		*p = '\0';
+		txn->auth.pass = p+1;
+
+		txn->auth.method = HTTP_AUTH_BASIC;
+		return 1;
+	}
+
+	return 0;
+}
+
+/* This function ensures that the prerequisites for an L7 fetch are ready,
+ * which means that a request or response is ready. If some data is missing,
+ * a parsing attempt is made. This is useful in TCP-based ACLs which are able
+ * to extract data from L7. If <req_vol> is non-null during a request prefetch,
+ * another test is made to ensure the required information is not gone.
+ *
+ * The function returns :
+ *   0 with SMP_F_MAY_CHANGE in the sample flags if some data is missing to
+ *     decide whether or not an HTTP message is present ;
+ *   0 if the requested data cannot be fetched or if it is certain that
+ *     we'll never have any HTTP message there ;
+ *   1 if an HTTP message is ready
+ */
+int smp_prefetch_http(struct proxy *px, struct stream *s, unsigned int opt,
+                      const struct arg *args, struct sample *smp, int req_vol)
+{
+	struct http_txn *txn;
+	struct http_msg *msg;
+
+	/* Note: it is possible that <s> is NULL when called before stream
+	 * initialization (eg: tcp-request connection), so this function is the
+	 * one responsible for guarding against this case for all HTTP users.
+	 */
+	if (!s)
+		return 0;
+
+	if (!s->txn) {
+		if (unlikely(!http_alloc_txn(s)))
+			return 0; /* not enough memory */
+		http_init_txn(s);
+	}
+	txn = s->txn;
+	msg = &txn->req;
+
+	/* Check for a dependency on a request */
+	smp->data.type = SMP_T_BOOL;
+
+	if ((opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) {
+		/* If the buffer does not leave enough free space at the end,
+		 * we must first realign it.
+		 */
+		if (ci_head(&s->req) > b_orig(&s->req.buf) &&
+		    ci_head(&s->req) + ci_data(&s->req) > b_wrap(&s->req.buf) - global.tune.maxrewrite)
+			channel_slow_realign(&s->req, trash.area);
+
+		if (unlikely(txn->req.msg_state < HTTP_MSG_BODY)) {
+			if (msg->msg_state == HTTP_MSG_ERROR)
+				return 0;
+
+			/* Try to decode HTTP request */
+			if (likely(msg->next < ci_data(&s->req)))
+				http_msg_analyzer(msg, &txn->hdr_idx);
+
+			/* Still no valid request ? */
+			if (unlikely(msg->msg_state < HTTP_MSG_BODY)) {
+				if ((msg->msg_state == HTTP_MSG_ERROR) ||
+				    channel_full(&s->req, global.tune.maxrewrite)) {
+					return 0;
+				}
+				/* wait for final state */
+				smp->flags |= SMP_F_MAY_CHANGE;
+				return 0;
+			}
+
+			/* OK we just got a valid HTTP request. We have some minor
+			 * preparation to perform so that further checks can rely
+			 * on HTTP tests.
+			 */
+
+			/* If the request was parsed but was too large, we must absolutely
+			 * return an error so that it is not processed. At the moment this
+			 * cannot happen, but if the parsers are to change in the future,
+			 * we want this check to be maintained.
+			 */
+			if (unlikely(ci_head(&s->req) + ci_data(&s->req) >
+				     b_wrap(&s->req.buf) - global.tune.maxrewrite)) {
+				msg->err_state = msg->msg_state;
+				msg->msg_state = HTTP_MSG_ERROR;
+				smp->data.u.sint = 1;
+				return 1;
+			}
+
+			txn->meth = find_http_meth(ci_head(msg->chn), msg->sl.rq.m_l);
+			if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)
+				s->flags |= SF_REDIRECTABLE;
+
+			if (unlikely(msg->sl.rq.v_l == 0) && !http_upgrade_v09_to_v10(txn))
+				return 0;
+		}
+
+		if (req_vol && txn->rsp.msg_state != HTTP_MSG_RPBEFORE) {
+			return 0;  /* data might have moved and indexes changed */
+		}
+
+		/* otherwise everything's ready for the request */
+	}
+	else {
+		/* Check for a dependency on a response */
+		if (txn->rsp.msg_state < HTTP_MSG_BODY) {
+			smp->flags |= SMP_F_MAY_CHANGE;
+			return 0;
+		}
+	}
+
+	/* everything's OK */
+	smp->data.u.sint = 1;
+	return 1;
+}
+
+/* This function fetches the method of current HTTP request and stores
+ * it in the global pattern struct as a chunk. There are two possibilities :
+ *   - if the method is known (not HTTP_METH_OTHER), its identifier is stored
+ *     in <len> and <ptr> is NULL ;
+ *   - if the method is unknown (HTTP_METH_OTHER), <ptr> points to the text and
+ *     <len> to its length.
+ * This is intended to be used with pat_match_meth() only.
+ */
+static int smp_fetch_meth(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	int meth;
+	struct http_txn *txn;
+
+	CHECK_HTTP_MESSAGE_FIRST_PERM();
+
+	txn = smp->strm->txn;
+	meth = txn->meth;
+	smp->data.type = SMP_T_METH;
+	smp->data.u.meth.meth = meth;
+	if (meth == HTTP_METH_OTHER) {
+		if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE)
+			/* ensure the indexes are not affected */
+			return 0;
+		smp->flags |= SMP_F_CONST;
+		smp->data.u.meth.str.data = txn->req.sl.rq.m_l;
+		smp->data.u.meth.str.area = ci_head(txn->req.chn);
+	}
+	smp->flags |= SMP_F_VOL_1ST;
+	return 1;
+}
+
+static int smp_fetch_rqver(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+	char *ptr;
+	int len;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	len = txn->req.sl.rq.v_l;
+	ptr = ci_head(txn->req.chn) + txn->req.sl.rq.v;
+
+	while ((len-- > 0) && (*ptr++ != '/'));
+	if (len <= 0)
+		return 0;
+
+	smp->data.type = SMP_T_STR;
+	smp->data.u.str.area = ptr;
+	smp->data.u.str.data = len;
+
+	smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
+	return 1;
+}
+
+static int smp_fetch_stver(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+	char *ptr;
+	int len;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	if (txn->rsp.msg_state < HTTP_MSG_BODY)
+		return 0;
+
+	len = txn->rsp.sl.st.v_l;
+	ptr = ci_head(txn->rsp.chn);
+
+	while ((len-- > 0) && (*ptr++ != '/'));
+	if (len <= 0)
+		return 0;
+
+	smp->data.type = SMP_T_STR;
+	smp->data.u.str.area = ptr;
+	smp->data.u.str.data = len;
+
+	smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
+	return 1;
+}
+
+/* 3. Check on Status Code. We manipulate integers here. */
+static int smp_fetch_stcode(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+	char *ptr;
+	int len;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	if (txn->rsp.msg_state < HTTP_MSG_BODY)
+		return 0;
+
+	len = txn->rsp.sl.st.c_l;
+	ptr = ci_head(txn->rsp.chn) + txn->rsp.sl.st.c;
+
+	smp->data.type = SMP_T_SINT;
+	smp->data.u.sint = __strl2ui(ptr, len);
+	smp->flags = SMP_F_VOL_1ST;
+	return 1;
+}
+
+static int smp_fetch_uniqueid(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	if (LIST_ISEMPTY(&smp->sess->fe->format_unique_id))
+		return 0;
+
+	if (!smp->strm->unique_id) {
+		if ((smp->strm->unique_id = pool_alloc(pool_head_uniqueid)) == NULL)
+			return 0;
+		smp->strm->unique_id[0] = '\0';
+	}
+	smp->data.u.str.data = build_logline(smp->strm, smp->strm->unique_id,
+	                                    UNIQUEID_LEN, &smp->sess->fe->format_unique_id);
+
+	smp->data.type = SMP_T_STR;
+	smp->data.u.str.area = smp->strm->unique_id;
+	smp->flags = SMP_F_CONST;
+	return 1;
+}
+
+/* Returns a string block containing all headers including the
+ * empty line wich separes headers from the body. This is useful
+ * form some headers analysis.
+ */
+static int smp_fetch_hdrs(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_msg *msg;
+	struct hdr_idx *idx;
+	struct http_txn *txn;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	idx = &txn->hdr_idx;
+	msg = &txn->req;
+
+	smp->data.type = SMP_T_STR;
+	smp->data.u.str.area = ci_head(msg->chn) + hdr_idx_first_pos(idx);
+	smp->data.u.str.data = msg->eoh - hdr_idx_first_pos(idx) + 1 +
+	                      (ci_head(msg->chn)[msg->eoh] == '\r');
+
+	return 1;
+}
+
+/* Returns the header request in a length/value encoded format.
+ * This is useful for exchanges with the SPOE.
+ *
+ * A "length value" is a multibyte code encoding numbers. It uses the
+ * SPOE format. The encoding is the following:
+ *
+ * Each couple "header name" / "header value" is composed
+ * like this:
+ *    "length value" "header name bytes"
+ *    "length value" "header value bytes"
+ * When the last header is reached, the header name and the header
+ * value are empty. Their length are 0
+ */
+static int smp_fetch_hdrs_bin(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_msg *msg;
+	struct buffer *temp;
+	struct hdr_idx *idx;
+	const char *cur_ptr, *cur_next, *p;
+	int old_idx, cur_idx;
+	struct hdr_idx_elem *cur_hdr;
+	const char *hn, *hv;
+	int hnl, hvl;
+	int ret;
+	struct http_txn *txn;
+	char *buf;
+	char *end;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	temp = get_trash_chunk();
+	buf = temp->area;
+	end = temp->area + temp->size;
+
+	txn = smp->strm->txn;
+	idx = &txn->hdr_idx;
+	msg = &txn->req;
+
+	/* Build array of headers. */
+	old_idx = 0;
+	cur_next = ci_head(msg->chn) + hdr_idx_first_pos(idx);
+	while (1) {
+		cur_idx = idx->v[old_idx].next;
+		if (!cur_idx)
+			break;
+		old_idx = cur_idx;
+
+		cur_hdr  = &idx->v[cur_idx];
+		cur_ptr  = cur_next;
+		cur_next = cur_ptr + cur_hdr->len + cur_hdr->cr + 1;
+
+		/* Now we have one full header at cur_ptr of len cur_hdr->len,
+		 * and the next header starts at cur_next. We'll check
+		 * this header in the list as well as against the default
+		 * rule.
+		 */
+
+		/* look for ': *'. */
+		hn = cur_ptr;
+		for (p = cur_ptr; p < cur_ptr + cur_hdr->len && *p != ':'; p++);
+		if (p >= cur_ptr+cur_hdr->len)
+			continue;
+		hnl = p - hn;
+		p++;
+		while (p < cur_ptr + cur_hdr->len && (*p == ' ' || *p == '\t'))
+			p++;
+		if (p >= cur_ptr + cur_hdr->len)
+			continue;
+		hv = p;
+		hvl = cur_ptr + cur_hdr->len-p;
+
+		/* encode the header name. */
+		ret = encode_varint(hnl, &buf, end);
+		if (ret == -1)
+			return 0;
+		if (buf + hnl > end)
+			return 0;
+		memcpy(buf, hn, hnl);
+		buf += hnl;
+
+		/* encode and copy the value. */
+		ret = encode_varint(hvl, &buf, end);
+		if (ret == -1)
+			return 0;
+		if (buf + hvl > end)
+			return 0;
+		memcpy(buf, hv, hvl);
+		buf += hvl;
+	}
+
+	/* encode the end of the header list with empty
+	 * header name and header value.
+	 */
+	ret = encode_varint(0, &buf, end);
+	if (ret == -1)
+		return 0;
+	ret = encode_varint(0, &buf, end);
+	if (ret == -1)
+		return 0;
+
+	/* Initialise sample data which will be filled. */
+	smp->data.type = SMP_T_BIN;
+	smp->data.u.str.area = temp->area;
+	smp->data.u.str.data = buf - temp->area;
+	smp->data.u.str.size = temp->size;
+
+	return 1;
+}
+
+/* returns the longest available part of the body. This requires that the body
+ * has been waited for using http-buffer-request.
+ */
+static int smp_fetch_body(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_msg *msg;
+	unsigned long len;
+	unsigned long block1;
+	char *body;
+	struct buffer *temp;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ)
+		msg = &smp->strm->txn->req;
+	else
+		msg = &smp->strm->txn->rsp;
+
+	len  = http_body_bytes(msg);
+	body = c_ptr(msg->chn, -http_data_rewind(msg));
+
+	block1 = len;
+	if (block1 > b_wrap(&msg->chn->buf) - body)
+		block1 = b_wrap(&msg->chn->buf) - body;
+
+	if (block1 == len) {
+		/* buffer is not wrapped (or empty) */
+		smp->data.type = SMP_T_BIN;
+		smp->data.u.str.area = body;
+		smp->data.u.str.data = len;
+		smp->flags = SMP_F_VOL_TEST | SMP_F_CONST;
+	}
+	else {
+		/* buffer is wrapped, we need to defragment it */
+		temp = get_trash_chunk();
+		memcpy(temp->area, body, block1);
+		memcpy(temp->area + block1, b_orig(&msg->chn->buf),
+		       len - block1);
+		smp->data.type = SMP_T_BIN;
+		smp->data.u.str.area = temp->area;
+		smp->data.u.str.data = len;
+		smp->flags = SMP_F_VOL_TEST;
+	}
+	return 1;
+}
+
+
+/* returns the available length of the body. This requires that the body
+ * has been waited for using http-buffer-request.
+ */
+static int smp_fetch_body_len(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_msg *msg;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ)
+		msg = &smp->strm->txn->req;
+	else
+		msg = &smp->strm->txn->rsp;
+
+	smp->data.type = SMP_T_SINT;
+	smp->data.u.sint = http_body_bytes(msg);
+
+	smp->flags = SMP_F_VOL_TEST;
+	return 1;
+}
+
+
+/* returns the advertised length of the body, or the advertised size of the
+ * chunks available in the buffer. This requires that the body has been waited
+ * for using http-buffer-request.
+ */
+static int smp_fetch_body_size(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_msg *msg;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ)
+		msg = &smp->strm->txn->req;
+	else
+		msg = &smp->strm->txn->rsp;
+
+	smp->data.type = SMP_T_SINT;
+	smp->data.u.sint = msg->body_len;
+
+	smp->flags = SMP_F_VOL_TEST;
+	return 1;
+}
+
+
+/* 4. Check on URL/URI. A pointer to the URI is stored. */
+static int smp_fetch_url(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	smp->data.type = SMP_T_STR;
+	smp->data.u.str.data = txn->req.sl.rq.u_l;
+	smp->data.u.str.area = ci_head(txn->req.chn) + txn->req.sl.rq.u;
+	smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
+	return 1;
+}
+
+static int smp_fetch_url_ip(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+	struct sockaddr_storage addr;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	url2sa(ci_head(txn->req.chn) + txn->req.sl.rq.u, txn->req.sl.rq.u_l, &addr, NULL);
+	if (((struct sockaddr_in *)&addr)->sin_family != AF_INET)
+		return 0;
+
+	smp->data.type = SMP_T_IPV4;
+	smp->data.u.ipv4 = ((struct sockaddr_in *)&addr)->sin_addr;
+	smp->flags = 0;
+	return 1;
+}
+
+static int smp_fetch_url_port(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+	struct sockaddr_storage addr;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	url2sa(ci_head(txn->req.chn) + txn->req.sl.rq.u, txn->req.sl.rq.u_l, &addr, NULL);
+	if (((struct sockaddr_in *)&addr)->sin_family != AF_INET)
+		return 0;
+
+	smp->data.type = SMP_T_SINT;
+	smp->data.u.sint = ntohs(((struct sockaddr_in *)&addr)->sin_port);
+	smp->flags = 0;
+	return 1;
+}
+
+/* Fetch an HTTP header. A pointer to the beginning of the value is returned.
+ * Accepts an optional argument of type string containing the header field name,
+ * and an optional argument of type signed or unsigned integer to request an
+ * explicit occurrence of the header. Note that in the event of a missing name,
+ * headers are considered from the first one. It does not stop on commas and
+ * returns full lines instead (useful for User-Agent or Date for example).
+ */
+static int smp_fetch_fhdr(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct hdr_idx *idx;
+	struct hdr_ctx *ctx = smp->ctx.a[0];
+	const struct http_msg *msg;
+	int occ = 0;
+	const char *name_str = NULL;
+	int name_len = 0;
+
+	if (!ctx) {
+		/* first call */
+		ctx = &static_hdr_ctx;
+		ctx->idx = 0;
+		smp->ctx.a[0] = ctx;
+	}
+
+	if (args) {
+		if (args[0].type != ARGT_STR)
+			return 0;
+		name_str = args[0].data.str.area;
+		name_len = args[0].data.str.data;
+
+		if (args[1].type == ARGT_SINT)
+			occ = args[1].data.sint;
+	}
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	idx = &smp->strm->txn->hdr_idx;
+	msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp;
+
+	if (ctx && !(smp->flags & SMP_F_NOT_LAST))
+		/* search for header from the beginning */
+		ctx->idx = 0;
+
+	if (!occ && !(smp->opt & SMP_OPT_ITERATE))
+		/* no explicit occurrence and single fetch => last header by default */
+		occ = -1;
+
+	if (!occ)
+		/* prepare to report multiple occurrences for ACL fetches */
+		smp->flags |= SMP_F_NOT_LAST;
+
+	smp->data.type = SMP_T_STR;
+	smp->flags |= SMP_F_VOL_HDR | SMP_F_CONST;
+	if (http_get_fhdr(msg, name_str, name_len, idx, occ, ctx, &smp->data.u.str.area, &smp->data.u.str.data))
+		return 1;
+
+	smp->flags &= ~SMP_F_NOT_LAST;
+	return 0;
+}
+
+/* 6. Check on HTTP header count. The number of occurrences is returned.
+ * Accepts exactly 1 argument of type string. It does not stop on commas and
+ * returns full lines instead (useful for User-Agent or Date for example).
+ */
+static int smp_fetch_fhdr_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct hdr_idx *idx;
+	struct hdr_ctx ctx;
+	const struct http_msg *msg;
+	int cnt;
+	const char *name = NULL;
+	int len = 0;
+
+	if (args && args->type == ARGT_STR) {
+		name = args->data.str.area;
+		len = args->data.str.data;
+	}
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	idx = &smp->strm->txn->hdr_idx;
+	msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp;
+
+	ctx.idx = 0;
+	cnt = 0;
+	while (http_find_full_header2(name, len, ci_head(msg->chn), idx, &ctx))
+		cnt++;
+
+	smp->data.type = SMP_T_SINT;
+	smp->data.u.sint = cnt;
+	smp->flags = SMP_F_VOL_HDR;
+	return 1;
+}
+
+static int smp_fetch_hdr_names(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct hdr_idx *idx;
+	struct hdr_ctx ctx;
+	const struct http_msg *msg;
+	struct buffer *temp;
+	char del = ',';
+
+	if (args && args->type == ARGT_STR)
+		del = *args[0].data.str.area;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	idx = &smp->strm->txn->hdr_idx;
+	msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp;
+
+	temp = get_trash_chunk();
+
+	ctx.idx = 0;
+	while (http_find_next_header(ci_head(msg->chn), idx, &ctx)) {
+		if (temp->data)
+			temp->area[temp->data++] = del;
+		memcpy(temp->area + temp->data, ctx.line, ctx.del);
+		temp->data += ctx.del;
+	}
+
+	smp->data.type = SMP_T_STR;
+	smp->data.u.str.area = temp->area;
+	smp->data.u.str.data = temp->data;
+	smp->flags = SMP_F_VOL_HDR;
+	return 1;
+}
+
+/* Fetch an HTTP header. A pointer to the beginning of the value is returned.
+ * Accepts an optional argument of type string containing the header field name,
+ * and an optional argument of type signed or unsigned integer to request an
+ * explicit occurrence of the header. Note that in the event of a missing name,
+ * headers are considered from the first one.
+ */
+static int smp_fetch_hdr(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct hdr_idx *idx;
+	struct hdr_ctx *ctx = smp->ctx.a[0];
+	const struct http_msg *msg;
+	int occ = 0;
+	const char *name_str = NULL;
+	int name_len = 0;
+
+	if (!ctx) {
+		/* first call */
+		ctx = &static_hdr_ctx;
+		ctx->idx = 0;
+		smp->ctx.a[0] = ctx;
+	}
+
+	if (args) {
+		if (args[0].type != ARGT_STR)
+			return 0;
+		name_str = args[0].data.str.area;
+		name_len = args[0].data.str.data;
+
+		if (args[1].type == ARGT_SINT)
+			occ = args[1].data.sint;
+	}
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	idx = &smp->strm->txn->hdr_idx;
+	msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp;
+
+	if (ctx && !(smp->flags & SMP_F_NOT_LAST))
+		/* search for header from the beginning */
+		ctx->idx = 0;
+
+	if (!occ && !(smp->opt & SMP_OPT_ITERATE))
+		/* no explicit occurrence and single fetch => last header by default */
+		occ = -1;
+
+	if (!occ)
+		/* prepare to report multiple occurrences for ACL fetches */
+		smp->flags |= SMP_F_NOT_LAST;
+
+	smp->data.type = SMP_T_STR;
+	smp->flags |= SMP_F_VOL_HDR | SMP_F_CONST;
+	if (http_get_hdr(msg, name_str, name_len, idx, occ, ctx, &smp->data.u.str.area, &smp->data.u.str.data))
+		return 1;
+
+	smp->flags &= ~SMP_F_NOT_LAST;
+	return 0;
+}
+
+/* 6. Check on HTTP header count. The number of occurrences is returned.
+ * Accepts exactly 1 argument of type string.
+ */
+static int smp_fetch_hdr_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct hdr_idx *idx;
+	struct hdr_ctx ctx;
+	const struct http_msg *msg;
+	int cnt;
+	const char *name = NULL;
+	int len = 0;
+
+	if (args && args->type == ARGT_STR) {
+		name = args->data.str.area;
+		len = args->data.str.data;
+	}
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	idx = &smp->strm->txn->hdr_idx;
+	msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp;
+
+	ctx.idx = 0;
+	cnt = 0;
+	while (http_find_header2(name, len, ci_head(msg->chn), idx, &ctx))
+		cnt++;
+
+	smp->data.type = SMP_T_SINT;
+	smp->data.u.sint = cnt;
+	smp->flags = SMP_F_VOL_HDR;
+	return 1;
+}
+
+/* Fetch an HTTP header's integer value. The integer value is returned. It
+ * takes a mandatory argument of type string and an optional one of type int
+ * to designate a specific occurrence. It returns an unsigned integer, which
+ * may or may not be appropriate for everything.
+ */
+static int smp_fetch_hdr_val(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	int ret = smp_fetch_hdr(args, smp, kw, private);
+
+	if (ret > 0) {
+		smp->data.type = SMP_T_SINT;
+		smp->data.u.sint = strl2ic(smp->data.u.str.area,
+					   smp->data.u.str.data);
+	}
+
+	return ret;
+}
+
+/* Fetch an HTTP header's IP value. takes a mandatory argument of type string
+ * and an optional one of type int to designate a specific occurrence.
+ * It returns an IPv4 or IPv6 address.
+ */
+static int smp_fetch_hdr_ip(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	int ret;
+
+	while ((ret = smp_fetch_hdr(args, smp, kw, private)) > 0) {
+		if (url2ipv4((char *) smp->data.u.str.area, &smp->data.u.ipv4)) {
+			smp->data.type = SMP_T_IPV4;
+			break;
+		} else {
+			struct buffer *temp = get_trash_chunk();
+			if (smp->data.u.str.data < temp->size - 1) {
+				memcpy(temp->area, smp->data.u.str.area,
+				       smp->data.u.str.data);
+				temp->area[smp->data.u.str.data] = '\0';
+				if (inet_pton(AF_INET6, temp->area, &smp->data.u.ipv6)) {
+					smp->data.type = SMP_T_IPV6;
+					break;
+				}
+			}
+		}
+
+		/* if the header doesn't match an IP address, fetch next one */
+		if (!(smp->flags & SMP_F_NOT_LAST))
+			return 0;
+	}
+	return ret;
+}
+
+/* 8. Check on URI PATH. A pointer to the PATH is stored. The path starts at
+ * the first '/' after the possible hostname, and ends before the possible '?'.
+ */
+static int smp_fetch_path(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+	char *ptr, *end;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l;
+	ptr = http_txn_get_path(txn);
+	if (!ptr)
+		return 0;
+
+	/* OK, we got the '/' ! */
+	smp->data.type = SMP_T_STR;
+	smp->data.u.str.area = ptr;
+
+	while (ptr < end && *ptr != '?')
+		ptr++;
+
+	smp->data.u.str.data = ptr - smp->data.u.str.area;
+	smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
+	return 1;
+}
+
+/* This produces a concatenation of the first occurrence of the Host header
+ * followed by the path component if it begins with a slash ('/'). This means
+ * that '*' will not be added, resulting in exactly the first Host entry.
+ * If no Host header is found, then the path is returned as-is. The returned
+ * value is stored in the trash so it does not need to be marked constant.
+ * The returned sample is of type string.
+ */
+static int smp_fetch_base(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+	char *ptr, *end, *beg;
+	struct hdr_ctx ctx;
+	struct buffer *temp;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	ctx.idx = 0;
+	if (!http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx) || !ctx.vlen)
+		return smp_fetch_path(args, smp, kw, private);
+
+	/* OK we have the header value in ctx.line+ctx.val for ctx.vlen bytes */
+	temp = get_trash_chunk();
+	memcpy(temp->area, ctx.line + ctx.val, ctx.vlen);
+	smp->data.type = SMP_T_STR;
+	smp->data.u.str.area = temp->area;
+	smp->data.u.str.data = ctx.vlen;
+
+	/* now retrieve the path */
+	end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l;
+	beg = http_txn_get_path(txn);
+	if (!beg)
+		beg = end;
+
+	for (ptr = beg; ptr < end && *ptr != '?'; ptr++);
+
+	if (beg < ptr && *beg == '/') {
+		memcpy(smp->data.u.str.area + smp->data.u.str.data, beg,
+		       ptr - beg);
+		smp->data.u.str.data += ptr - beg;
+	}
+
+	smp->flags = SMP_F_VOL_1ST;
+	return 1;
+}
+
+/* This produces a 32-bit hash of the concatenation of the first occurrence of
+ * the Host header followed by the path component if it begins with a slash ('/').
+ * This means that '*' will not be added, resulting in exactly the first Host
+ * entry. If no Host header is found, then the path is used. The resulting value
+ * is hashed using the path hash followed by a full avalanche hash and provides a
+ * 32-bit integer value. This fetch is useful for tracking per-path activity on
+ * high-traffic sites without having to store whole paths.
+ */
+static int smp_fetch_base32(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+	struct hdr_ctx ctx;
+	unsigned int hash = 0;
+	char *ptr, *beg, *end;
+	int len;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	ctx.idx = 0;
+	if (http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx)) {
+		/* OK we have the header value in ctx.line+ctx.val for ctx.vlen bytes */
+		ptr = ctx.line + ctx.val;
+		len = ctx.vlen;
+		while (len--)
+			hash = *(ptr++) + (hash << 6) + (hash << 16) - hash;
+	}
+
+	/* now retrieve the path */
+	end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l;
+	beg = http_txn_get_path(txn);
+	if (!beg)
+		beg = end;
+
+	for (ptr = beg; ptr < end && *ptr != '?'; ptr++);
+
+	if (beg < ptr && *beg == '/') {
+		while (beg < ptr)
+			hash = *(beg++) + (hash << 6) + (hash << 16) - hash;
+	}
+	hash = full_hash(hash);
+
+	smp->data.type = SMP_T_SINT;
+	smp->data.u.sint = hash;
+	smp->flags = SMP_F_VOL_1ST;
+	return 1;
+}
+
+/* This concatenates the source address with the 32-bit hash of the Host and
+ * path as returned by smp_fetch_base32(). The idea is to have per-source and
+ * per-path counters. The result is a binary block from 8 to 20 bytes depending
+ * on the source address length. The path hash is stored before the address so
+ * that in environments where IPv6 is insignificant, truncating the output to
+ * 8 bytes would still work.
+ */
+static int smp_fetch_base32_src(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct buffer *temp;
+	struct connection *cli_conn = objt_conn(smp->sess->origin);
+
+	if (!cli_conn)
+		return 0;
+
+	if (!smp_fetch_base32(args, smp, kw, private))
+		return 0;
+
+	temp = get_trash_chunk();
+	*(unsigned int *) temp->area = htonl(smp->data.u.sint);
+	temp->data += sizeof(unsigned int);
+
+	switch (cli_conn->addr.from.ss_family) {
+	case AF_INET:
+		memcpy(temp->area + temp->data,
+		       &((struct sockaddr_in *)&cli_conn->addr.from)->sin_addr,
+		       4);
+		temp->data += 4;
+		break;
+	case AF_INET6:
+		memcpy(temp->area + temp->data,
+		       &((struct sockaddr_in6 *)&cli_conn->addr.from)->sin6_addr,
+		       16);
+		temp->data += 16;
+		break;
+	default:
+		return 0;
+	}
+
+	smp->data.u.str = *temp;
+	smp->data.type = SMP_T_BIN;
+	return 1;
+}
+
+/* Extracts the query string, which comes after the question mark '?'. If no
+ * question mark is found, nothing is returned. Otherwise it returns a sample
+ * of type string carrying the whole query string.
+ */
+static int smp_fetch_query(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+	char *ptr, *end;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	ptr = ci_head(txn->req.chn) + txn->req.sl.rq.u;
+	end = ptr + txn->req.sl.rq.u_l;
+
+	/* look up the '?' */
+	do {
+		if (ptr == end)
+			return 0;
+	} while (*ptr++ != '?');
+
+	smp->data.type = SMP_T_STR;
+	smp->data.u.str.area = ptr;
+	smp->data.u.str.data = end - ptr;
+	smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
+	return 1;
+}
+
+static int smp_fetch_proto_http(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	/* Note: hdr_idx.v cannot be NULL in this ACL because the ACL is tagged
+	 * as a layer7 ACL, which involves automatic allocation of hdr_idx.
+	 */
+
+	CHECK_HTTP_MESSAGE_FIRST_PERM();
+
+	smp->data.type = SMP_T_BOOL;
+	smp->data.u.sint = 1;
+	return 1;
+}
+
+/* return a valid test if the current request is the first one on the connection */
+static int smp_fetch_http_first_req(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	smp->data.type = SMP_T_BOOL;
+	smp->data.u.sint = !(smp->strm->txn->flags & TX_NOT_FIRST);
+	return 1;
+}
+
+/* Accepts exactly 1 argument of type userlist */
+static int smp_fetch_http_auth(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+
+	if (!args || args->type != ARGT_USR)
+		return 0;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	if (!get_http_auth(smp->strm))
+		return 0;
+
+	smp->data.type = SMP_T_BOOL;
+	smp->data.u.sint = check_user(args->data.usr, smp->strm->txn->auth.user,
+	                            smp->strm->txn->auth.pass);
+	return 1;
+}
+
+/* Accepts exactly 1 argument of type userlist */
+static int smp_fetch_http_auth_grp(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	if (!args || args->type != ARGT_USR)
+		return 0;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	if (!get_http_auth(smp->strm))
+		return 0;
+
+	/* if the user does not belong to the userlist or has a wrong password,
+	 * report that it unconditionally does not match. Otherwise we return
+	 * a string containing the username.
+	 */
+	if (!check_user(args->data.usr, smp->strm->txn->auth.user,
+	                smp->strm->txn->auth.pass))
+		return 0;
+
+	/* pat_match_auth() will need the user list */
+	smp->ctx.a[0] = args->data.usr;
+
+	smp->data.type = SMP_T_STR;
+	smp->flags = SMP_F_CONST;
+	smp->data.u.str.area = smp->strm->txn->auth.user;
+	smp->data.u.str.data = strlen(smp->strm->txn->auth.user);
+
+	return 1;
+}
+
+/* Fetch a captured HTTP request header. The index is the position of
+ * the "capture" option in the configuration file
+ */
+static int smp_fetch_capture_req_hdr(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct proxy *fe = strm_fe(smp->strm);
+	int idx;
+
+	if (!args || args->type != ARGT_SINT)
+		return 0;
+
+	idx = args->data.sint;
+
+	if (idx > (fe->nb_req_cap - 1) || smp->strm->req_cap == NULL || smp->strm->req_cap[idx] == NULL)
+		return 0;
+
+	smp->data.type = SMP_T_STR;
+	smp->flags |= SMP_F_CONST;
+	smp->data.u.str.area = smp->strm->req_cap[idx];
+	smp->data.u.str.data = strlen(smp->strm->req_cap[idx]);
+
+	return 1;
+}
+
+/* Fetch a captured HTTP response header. The index is the position of
+ * the "capture" option in the configuration file
+ */
+static int smp_fetch_capture_res_hdr(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct proxy *fe = strm_fe(smp->strm);
+	int idx;
+
+	if (!args || args->type != ARGT_SINT)
+		return 0;
+
+	idx = args->data.sint;
+
+	if (idx > (fe->nb_rsp_cap - 1) || smp->strm->res_cap == NULL || smp->strm->res_cap[idx] == NULL)
+		return 0;
+
+	smp->data.type = SMP_T_STR;
+	smp->flags |= SMP_F_CONST;
+	smp->data.u.str.area = smp->strm->res_cap[idx];
+	smp->data.u.str.data = strlen(smp->strm->res_cap[idx]);
+
+	return 1;
+}
+
+/* Extracts the METHOD in the HTTP request, the txn->uri should be filled before the call */
+static int smp_fetch_capture_req_method(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct buffer *temp;
+	struct http_txn *txn = smp->strm->txn;
+	char *ptr;
+
+	if (!txn || !txn->uri)
+		return 0;
+
+	ptr = txn->uri;
+
+	while (*ptr != ' ' && *ptr != '\0')  /* find first space */
+		ptr++;
+
+	temp = get_trash_chunk();
+	temp->area = txn->uri;
+	temp->data = ptr - txn->uri;
+	smp->data.u.str = *temp;
+	smp->data.type = SMP_T_STR;
+	smp->flags = SMP_F_CONST;
+
+	return 1;
+
+}
+
+/* Extracts the path in the HTTP request, the txn->uri should be filled before the call  */
+static int smp_fetch_capture_req_uri(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn = smp->strm->txn;
+	struct ist path;
+	const char *ptr;
+
+	if (!txn || !txn->uri)
+		return 0;
+
+	ptr = txn->uri;
+
+	while (*ptr != ' ' && *ptr != '\0')  /* find first space */
+		ptr++;
+
+	if (!*ptr)
+		return 0;
+
+	ptr++;  /* skip the space */
+
+	path = http_get_path(ist(ptr));
+	if (!path.ptr)
+		return 0;
+
+	smp->data.u.str.area = path.ptr;
+	smp->data.u.str.data = path.len;
+	smp->data.type = SMP_T_STR;
+	smp->flags = SMP_F_CONST;
+
+	return 1;
+}
+
+/* Retrieves the HTTP version from the request (either 1.0 or 1.1) and emits it
+ * as a string (either "HTTP/1.0" or "HTTP/1.1").
+ */
+static int smp_fetch_capture_req_ver(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn = smp->strm->txn;
+
+	if (!txn || txn->req.msg_state < HTTP_MSG_HDR_FIRST)
+		return 0;
+
+	if (txn->req.flags & HTTP_MSGF_VER_11)
+		smp->data.u.str.area = "HTTP/1.1";
+	else
+		smp->data.u.str.area = "HTTP/1.0";
+
+	smp->data.u.str.data = 8;
+	smp->data.type  = SMP_T_STR;
+	smp->flags = SMP_F_CONST;
+	return 1;
+
+}
+
+/* Retrieves the HTTP version from the response (either 1.0 or 1.1) and emits it
+ * as a string (either "HTTP/1.0" or "HTTP/1.1").
+ */
+static int smp_fetch_capture_res_ver(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn = smp->strm->txn;
+
+	if (!txn || txn->rsp.msg_state < HTTP_MSG_HDR_FIRST)
+		return 0;
+
+	if (txn->rsp.flags & HTTP_MSGF_VER_11)
+		smp->data.u.str.area = "HTTP/1.1";
+	else
+		smp->data.u.str.area = "HTTP/1.0";
+
+	smp->data.u.str.data = 8;
+	smp->data.type  = SMP_T_STR;
+	smp->flags = SMP_F_CONST;
+	return 1;
+
+}
+
+/* Iterate over all cookies present in a message. The context is stored in
+ * smp->ctx.a[0] for the in-header position, smp->ctx.a[1] for the
+ * end-of-header-value, and smp->ctx.a[2] for the hdr_ctx. Depending on
+ * the direction, multiple cookies may be parsed on the same line or not.
+ * The cookie name is in args and the name length in args->data.str.len.
+ * Accepts exactly 1 argument of type string. If the input options indicate
+ * that no iterating is desired, then only last value is fetched if any.
+ * The returned sample is of type CSTR. Can be used to parse cookies in other
+ * files.
+ */
+static int smp_fetch_cookie(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+	struct hdr_idx *idx;
+	struct hdr_ctx *ctx = smp->ctx.a[2];
+	const struct http_msg *msg;
+	const char *hdr_name;
+	int hdr_name_len;
+	char *sol;
+	int occ = 0;
+	int found = 0;
+
+	if (!args || args->type != ARGT_STR)
+		return 0;
+
+	if (!ctx) {
+		/* first call */
+		ctx = &static_hdr_ctx;
+		ctx->idx = 0;
+		smp->ctx.a[2] = ctx;
+	}
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	idx = &smp->strm->txn->hdr_idx;
+
+	if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) {
+		msg = &txn->req;
+		hdr_name = "Cookie";
+		hdr_name_len = 6;
+	} else {
+		msg = &txn->rsp;
+		hdr_name = "Set-Cookie";
+		hdr_name_len = 10;
+	}
+
+	if (!occ && !(smp->opt & SMP_OPT_ITERATE))
+		/* no explicit occurrence and single fetch => last cookie by default */
+		occ = -1;
+
+	/* OK so basically here, either we want only one value and it's the
+	 * last one, or we want to iterate over all of them and we fetch the
+	 * next one.
+	 */
+
+	sol = ci_head(msg->chn);
+	if (!(smp->flags & SMP_F_NOT_LAST)) {
+		/* search for the header from the beginning, we must first initialize
+		 * the search parameters.
+		 */
+		smp->ctx.a[0] = NULL;
+		ctx->idx = 0;
+	}
+
+	smp->flags |= SMP_F_VOL_HDR;
+
+	while (1) {
+		/* Note: smp->ctx.a[0] == NULL every time we need to fetch a new header */
+		if (!smp->ctx.a[0]) {
+			if (!http_find_header2(hdr_name, hdr_name_len, sol, idx, ctx))
+				goto out;
+
+			if (ctx->vlen < args->data.str.data + 1)
+				continue;
+
+			smp->ctx.a[0] = ctx->line + ctx->val;
+			smp->ctx.a[1] = smp->ctx.a[0] + ctx->vlen;
+		}
+
+		smp->data.type = SMP_T_STR;
+		smp->flags |= SMP_F_CONST;
+		smp->ctx.a[0] = http_extract_cookie_value(smp->ctx.a[0], smp->ctx.a[1],
+		                                         args->data.str.area, args->data.str.data,
+		                                         (smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ,
+		                                         &smp->data.u.str.area, &smp->data.u.str.data);
+		if (smp->ctx.a[0]) {
+			found = 1;
+			if (occ >= 0) {
+				/* one value was returned into smp->data.u.str.{str,len} */
+				smp->flags |= SMP_F_NOT_LAST;
+				return 1;
+			}
+		}
+		/* if we're looking for last occurrence, let's loop */
+	}
+	/* all cookie headers and values were scanned. If we're looking for the
+	 * last occurrence, we may return it now.
+	 */
+ out:
+	smp->flags &= ~SMP_F_NOT_LAST;
+	return found;
+}
+
+/* Iterate over all cookies present in a request to count how many occurrences
+ * match the name in args and args->data.str.len. If <multi> is non-null, then
+ * multiple cookies may be parsed on the same line. The returned sample is of
+ * type UINT. Accepts exactly 1 argument of type string.
+ */
+static int smp_fetch_cookie_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+	struct hdr_idx *idx;
+	struct hdr_ctx ctx;
+	const struct http_msg *msg;
+	const char *hdr_name;
+	int hdr_name_len;
+	int cnt;
+	char *val_beg, *val_end;
+	char *sol;
+
+	if (!args || args->type != ARGT_STR)
+		return 0;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	idx = &smp->strm->txn->hdr_idx;
+
+	if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) {
+		msg = &txn->req;
+		hdr_name = "Cookie";
+		hdr_name_len = 6;
+	} else {
+		msg = &txn->rsp;
+		hdr_name = "Set-Cookie";
+		hdr_name_len = 10;
+	}
+
+	sol = ci_head(msg->chn);
+	val_end = val_beg = NULL;
+	ctx.idx = 0;
+	cnt = 0;
+
+	while (1) {
+		/* Note: val_beg == NULL every time we need to fetch a new header */
+		if (!val_beg) {
+			if (!http_find_header2(hdr_name, hdr_name_len, sol, idx, &ctx))
+				break;
+
+			if (ctx.vlen < args->data.str.data + 1)
+				continue;
+
+			val_beg = ctx.line + ctx.val;
+			val_end = val_beg + ctx.vlen;
+		}
+
+		smp->data.type = SMP_T_STR;
+		smp->flags |= SMP_F_CONST;
+		while ((val_beg = http_extract_cookie_value(val_beg, val_end,
+		                                            args->data.str.area, args->data.str.data,
+		                                            (smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ,
+		                                            &smp->data.u.str.area, &smp->data.u.str.data))) {
+			cnt++;
+		}
+	}
+
+	smp->data.type = SMP_T_SINT;
+	smp->data.u.sint = cnt;
+	smp->flags |= SMP_F_VOL_HDR;
+	return 1;
+}
+
+/* Fetch an cookie's integer value. The integer value is returned. It
+ * takes a mandatory argument of type string. It relies on smp_fetch_cookie().
+ */
+static int smp_fetch_cookie_val(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	int ret = smp_fetch_cookie(args, smp, kw, private);
+
+	if (ret > 0) {
+		smp->data.type = SMP_T_SINT;
+		smp->data.u.sint = strl2ic(smp->data.u.str.area,
+					   smp->data.u.str.data);
+	}
+
+	return ret;
+}
+
+/************************************************************************/
+/*           The code below is dedicated to sample fetches              */
+/************************************************************************/
+
+/* This scans a URL-encoded query string. It takes an optionally wrapping
+ * string whose first contigous chunk has its beginning in ctx->a[0] and end
+ * in ctx->a[1], and the optional second part in (ctx->a[2]..ctx->a[3]). The
+ * pointers are updated for next iteration before leaving.
+ */
+static int smp_fetch_param(char delim, const char *name, int name_len, const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	const char *vstart, *vend;
+	struct buffer *temp;
+	const char **chunks = (const char **)smp->ctx.a;
+
+	if (!http_find_next_url_param(chunks, name, name_len,
+	                         &vstart, &vend, delim))
+		return 0;
+
+	/* Create sample. If the value is contiguous, return the pointer as CONST,
+	 * if the value is wrapped, copy-it in a buffer.
+	 */
+	smp->data.type = SMP_T_STR;
+	if (chunks[2] &&
+	    vstart >= chunks[0] && vstart <= chunks[1] &&
+	    vend >= chunks[2] && vend <= chunks[3]) {
+		/* Wrapped case. */
+		temp = get_trash_chunk();
+		memcpy(temp->area, vstart, chunks[1] - vstart);
+		memcpy(temp->area + ( chunks[1] - vstart ), chunks[2],
+		       vend - chunks[2]);
+		smp->data.u.str.area = temp->area;
+		smp->data.u.str.data = ( chunks[1] - vstart ) + ( vend - chunks[2] );
+	} else {
+		/* Contiguous case. */
+		smp->data.u.str.area = (char *)vstart;
+		smp->data.u.str.data = vend - vstart;
+		smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
+	}
+
+	/* Update context, check wrapping. */
+	chunks[0] = vend;
+	if (chunks[2] && vend >= chunks[2] && vend <= chunks[3]) {
+		chunks[1] = chunks[3];
+		chunks[2] = NULL;
+	}
+
+	if (chunks[0] < chunks[1])
+		smp->flags |= SMP_F_NOT_LAST;
+
+	return 1;
+}
+
+/* This function iterates over each parameter of the query string. It uses
+ * ctx->a[0] and ctx->a[1] to store the beginning and end of the current
+ * parameter. Since it uses smp_fetch_param(), ctx->a[2..3] are both NULL.
+ * An optional parameter name is passed in args[0], otherwise any parameter is
+ * considered. It supports an optional delimiter argument for the beginning of
+ * the string in args[1], which defaults to "?".
+ */
+static int smp_fetch_url_param(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_msg *msg;
+	char delim = '?';
+	const char *name;
+	int name_len;
+
+	if (!args ||
+	    (args[0].type && args[0].type != ARGT_STR) ||
+	    (args[1].type && args[1].type != ARGT_STR))
+		return 0;
+
+	name = "";
+	name_len = 0;
+	if (args->type == ARGT_STR) {
+		name     = args->data.str.area;
+		name_len = args->data.str.data;
+	}
+
+	if (args[1].type)
+		delim = *args[1].data.str.area;
+
+	if (!smp->ctx.a[0]) { // first call, find the query string
+		CHECK_HTTP_MESSAGE_FIRST();
+
+		msg = &smp->strm->txn->req;
+
+		smp->ctx.a[0] = http_find_param_list(ci_head(msg->chn) + msg->sl.rq.u,
+		                                     msg->sl.rq.u_l, delim);
+		if (!smp->ctx.a[0])
+			return 0;
+
+		smp->ctx.a[1] = ci_head(msg->chn) + msg->sl.rq.u + msg->sl.rq.u_l;
+
+		/* Assume that the context is filled with NULL pointer
+		 * before the first call.
+		 * smp->ctx.a[2] = NULL;
+		 * smp->ctx.a[3] = NULL;
+		 */
+	}
+
+	return smp_fetch_param(delim, name, name_len, args, smp, kw, private);
+}
+
+/* This function iterates over each parameter of the body. This requires
+ * that the body has been waited for using http-buffer-request. It uses
+ * ctx->a[0] and ctx->a[1] to store the beginning and end of the first
+ * contigous part of the body, and optionally ctx->a[2..3] to reference the
+ * optional second part if the body wraps at the end of the buffer. An optional
+ * parameter name is passed in args[0], otherwise any parameter is considered.
+ */
+static int smp_fetch_body_param(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_msg *msg;
+	unsigned long len;
+	unsigned long block1;
+	char *body;
+	const char *name;
+	int name_len;
+
+	if (!args || (args[0].type && args[0].type != ARGT_STR))
+		return 0;
+
+	name = "";
+	name_len = 0;
+	if (args[0].type == ARGT_STR) {
+		name     = args[0].data.str.area;
+		name_len = args[0].data.str.data;
+	}
+
+	if (!smp->ctx.a[0]) { // first call, find the query string
+		CHECK_HTTP_MESSAGE_FIRST();
+
+		if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ)
+			msg = &smp->strm->txn->req;
+		else
+			msg = &smp->strm->txn->rsp;
+
+		len  = http_body_bytes(msg);
+		body = c_ptr(msg->chn, -http_data_rewind(msg));
+
+		block1 = len;
+		if (block1 > b_wrap(&msg->chn->buf) - body)
+			block1 = b_wrap(&msg->chn->buf) - body;
+
+		if (block1 == len) {
+			/* buffer is not wrapped (or empty) */
+			smp->ctx.a[0] = body;
+			smp->ctx.a[1] = body + len;
+
+			/* Assume that the context is filled with NULL pointer
+			 * before the first call.
+			 * smp->ctx.a[2] = NULL;
+			 * smp->ctx.a[3] = NULL;
+			*/
+		}
+		else {
+			/* buffer is wrapped, we need to defragment it */
+			smp->ctx.a[0] = body;
+			smp->ctx.a[1] = body + block1;
+			smp->ctx.a[2] = b_orig(&msg->chn->buf);
+			smp->ctx.a[3] = b_orig(&msg->chn->buf) + ( len - block1 );
+		}
+	}
+	return smp_fetch_param('&', name, name_len, args, smp, kw, private);
+}
+
+/* Return the signed integer value for the specified url parameter (see url_param
+ * above).
+ */
+static int smp_fetch_url_param_val(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	int ret = smp_fetch_url_param(args, smp, kw, private);
+
+	if (ret > 0) {
+		smp->data.type = SMP_T_SINT;
+		smp->data.u.sint = strl2ic(smp->data.u.str.area,
+					   smp->data.u.str.data);
+	}
+
+	return ret;
+}
+
+/* This produces a 32-bit hash of the concatenation of the first occurrence of
+ * the Host header followed by the path component if it begins with a slash ('/').
+ * This means that '*' will not be added, resulting in exactly the first Host
+ * entry. If no Host header is found, then the path is used. The resulting value
+ * is hashed using the url hash followed by a full avalanche hash and provides a
+ * 32-bit integer value. This fetch is useful for tracking per-URL activity on
+ * high-traffic sites without having to store whole paths.
+ * this differs from the base32 functions in that it includes the url parameters
+ * as well as the path
+ */
+static int smp_fetch_url32(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct http_txn *txn;
+	struct hdr_ctx ctx;
+	unsigned int hash = 0;
+	char *ptr, *beg, *end;
+	int len;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	txn = smp->strm->txn;
+	ctx.idx = 0;
+	if (http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx)) {
+		/* OK we have the header value in ctx.line+ctx.val for ctx.vlen bytes */
+		ptr = ctx.line + ctx.val;
+		len = ctx.vlen;
+		while (len--)
+			hash = *(ptr++) + (hash << 6) + (hash << 16) - hash;
+	}
+
+	/* now retrieve the path */
+	end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l;
+	beg = http_txn_get_path(txn);
+	if (!beg)
+		beg = end;
+
+	for (ptr = beg; ptr < end ; ptr++);
+
+	if (beg < ptr && *beg == '/') {
+		while (beg < ptr)
+			hash = *(beg++) + (hash << 6) + (hash << 16) - hash;
+	}
+	hash = full_hash(hash);
+
+	smp->data.type = SMP_T_SINT;
+	smp->data.u.sint = hash;
+	smp->flags = SMP_F_VOL_1ST;
+	return 1;
+}
+
+/* This concatenates the source address with the 32-bit hash of the Host and
+ * URL as returned by smp_fetch_base32(). The idea is to have per-source and
+ * per-url counters. The result is a binary block from 8 to 20 bytes depending
+ * on the source address length. The URL hash is stored before the address so
+ * that in environments where IPv6 is insignificant, truncating the output to
+ * 8 bytes would still work.
+ */
+static int smp_fetch_url32_src(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	struct buffer *temp;
+	struct connection *cli_conn = objt_conn(smp->sess->origin);
+
+	if (!cli_conn)
+		return 0;
+
+	if (!smp_fetch_url32(args, smp, kw, private))
+		return 0;
+
+	temp = get_trash_chunk();
+	*(unsigned int *) temp->area = htonl(smp->data.u.sint);
+	temp->data += sizeof(unsigned int);
+
+	switch (cli_conn->addr.from.ss_family) {
+	case AF_INET:
+		memcpy(temp->area + temp->data,
+		       &((struct sockaddr_in *)&cli_conn->addr.from)->sin_addr,
+		       4);
+		temp->data += 4;
+		break;
+	case AF_INET6:
+		memcpy(temp->area + temp->data,
+		       &((struct sockaddr_in6 *)&cli_conn->addr.from)->sin6_addr,
+		       16);
+		temp->data += 16;
+		break;
+	default:
+		return 0;
+	}
+
+	smp->data.u.str = *temp;
+	smp->data.type = SMP_T_BIN;
+	return 1;
+}
+
+/************************************************************************/
+/*                          Other utility functions                     */
+/************************************************************************/
+
+/* This function is used to validate the arguments passed to any "hdr" fetch
+ * keyword. These keywords support an optional positive or negative occurrence
+ * number. We must ensure that the number is greater than -MAX_HDR_HISTORY. It
+ * is assumed that the types are already the correct ones. Returns 0 on error,
+ * non-zero if OK. If <err> is not NULL, it will be filled with a pointer to an
+ * error message in case of error, that the caller is responsible for freeing.
+ * The initial location must either be freeable or NULL.
+ * Note: this function's pointer is checked from Lua.
+ */
+int val_hdr(struct arg *arg, char **err_msg)
+{
+	if (arg && arg[1].type == ARGT_SINT && arg[1].data.sint < -MAX_HDR_HISTORY) {
+		memprintf(err_msg, "header occurrence must be >= %d", -MAX_HDR_HISTORY);
+		return 0;
+	}
+	return 1;
+}
+
+/************************************************************************/
+/*      All supported sample fetch keywords must be declared here.      */
+/************************************************************************/
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
+	{ "base",               smp_fetch_base,               0,                NULL,   SMP_T_STR,  SMP_USE_HRQHV },
+	{ "base32",             smp_fetch_base32,             0,                NULL,   SMP_T_SINT, SMP_USE_HRQHV },
+	{ "base32+src",         smp_fetch_base32_src,         0,                NULL,   SMP_T_BIN,  SMP_USE_HRQHV },
+
+	/* capture are allocated and are permanent in the stream */
+	{ "capture.req.hdr",    smp_fetch_capture_req_hdr,    ARG1(1,SINT),     NULL,   SMP_T_STR,  SMP_USE_HRQHP },
+
+	/* retrieve these captures from the HTTP logs */
+	{ "capture.req.method", smp_fetch_capture_req_method, 0,                NULL,   SMP_T_STR,  SMP_USE_HRQHP },
+	{ "capture.req.uri",    smp_fetch_capture_req_uri,    0,                NULL,   SMP_T_STR,  SMP_USE_HRQHP },
+	{ "capture.req.ver",    smp_fetch_capture_req_ver,    0,                NULL,   SMP_T_STR,  SMP_USE_HRQHP },
+
+	{ "capture.res.hdr",    smp_fetch_capture_res_hdr,    ARG1(1,SINT),     NULL,   SMP_T_STR,  SMP_USE_HRSHP },
+	{ "capture.res.ver",    smp_fetch_capture_res_ver,    0,                NULL,   SMP_T_STR,  SMP_USE_HRQHP },
+
+	/* cookie is valid in both directions (eg: for "stick ...") but cook*
+	 * are only here to match the ACL's name, are request-only and are used
+	 * for ACL compatibility only.
+	 */
+	{ "cook",               smp_fetch_cookie,             ARG1(0,STR),      NULL,    SMP_T_STR,  SMP_USE_HRQHV },
+	{ "cookie",             smp_fetch_cookie,             ARG1(0,STR),      NULL,    SMP_T_STR,  SMP_USE_HRQHV|SMP_USE_HRSHV },
+	{ "cook_cnt",           smp_fetch_cookie_cnt,         ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRQHV },
+	{ "cook_val",           smp_fetch_cookie_val,         ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRQHV },
+
+	/* hdr is valid in both directions (eg: for "stick ...") but hdr_* are
+	 * only here to match the ACL's name, are request-only and are used for
+	 * ACL compatibility only.
+	 */
+	{ "hdr",                smp_fetch_hdr,                ARG2(0,STR,SINT), val_hdr, SMP_T_STR,  SMP_USE_HRQHV|SMP_USE_HRSHV },
+	{ "hdr_cnt",            smp_fetch_hdr_cnt,            ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRQHV },
+	{ "hdr_ip",             smp_fetch_hdr_ip,             ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRQHV },
+	{ "hdr_val",            smp_fetch_hdr_val,            ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRQHV },
+
+	{ "http_auth",          smp_fetch_http_auth,          ARG1(1,USR),      NULL,    SMP_T_BOOL, SMP_USE_HRQHV },
+	{ "http_auth_group",    smp_fetch_http_auth_grp,      ARG1(1,USR),      NULL,    SMP_T_STR,  SMP_USE_HRQHV },
+	{ "http_first_req",     smp_fetch_http_first_req,     0,                NULL,    SMP_T_BOOL, SMP_USE_HRQHP },
+	{ "method",             smp_fetch_meth,               0,                NULL,    SMP_T_METH, SMP_USE_HRQHP },
+	{ "path",               smp_fetch_path,               0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
+	{ "query",              smp_fetch_query,              0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
+
+	/* HTTP protocol on the request path */
+	{ "req.proto_http",     smp_fetch_proto_http,         0,                NULL,    SMP_T_BOOL, SMP_USE_HRQHP },
+	{ "req_proto_http",     smp_fetch_proto_http,         0,                NULL,    SMP_T_BOOL, SMP_USE_HRQHP },
+
+	/* HTTP version on the request path */
+	{ "req.ver",            smp_fetch_rqver,              0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
+	{ "req_ver",            smp_fetch_rqver,              0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
+
+	{ "req.body",           smp_fetch_body,               0,                NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
+	{ "req.body_len",       smp_fetch_body_len,           0,                NULL,    SMP_T_SINT, SMP_USE_HRQHV },
+	{ "req.body_size",      smp_fetch_body_size,          0,                NULL,    SMP_T_SINT, SMP_USE_HRQHV },
+	{ "req.body_param",     smp_fetch_body_param,         ARG1(0,STR),      NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
+
+	{ "req.hdrs",           smp_fetch_hdrs,               0,                NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
+	{ "req.hdrs_bin",       smp_fetch_hdrs_bin,           0,                NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
+
+	/* HTTP version on the response path */
+	{ "res.ver",            smp_fetch_stver,              0,                NULL,    SMP_T_STR,  SMP_USE_HRSHV },
+	{ "resp_ver",           smp_fetch_stver,              0,                NULL,    SMP_T_STR,  SMP_USE_HRSHV },
+
+	/* explicit req.{cook,hdr} are used to force the fetch direction to be request-only */
+	{ "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.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 },
+	{ "req.hdr",            smp_fetch_hdr,                ARG2(0,STR,SINT), val_hdr, SMP_T_STR,  SMP_USE_HRQHV },
+	{ "req.hdr_cnt",        smp_fetch_hdr_cnt,            ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRQHV },
+	{ "req.hdr_ip",         smp_fetch_hdr_ip,             ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRQHV },
+	{ "req.hdr_names",      smp_fetch_hdr_names,          ARG1(0,STR),      NULL,    SMP_T_STR,  SMP_USE_HRQHV },
+	{ "req.hdr_val",        smp_fetch_hdr_val,            ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRQHV },
+
+	/* explicit req.{cook,hdr} are used to force the fetch direction to be response-only */
+	{ "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.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 },
+	{ "res.hdr",            smp_fetch_hdr,                ARG2(0,STR,SINT), val_hdr, SMP_T_STR,  SMP_USE_HRSHV },
+	{ "res.hdr_cnt",        smp_fetch_hdr_cnt,            ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRSHV },
+	{ "res.hdr_ip",         smp_fetch_hdr_ip,             ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRSHV },
+	{ "res.hdr_names",      smp_fetch_hdr_names,          ARG1(0,STR),      NULL,    SMP_T_STR,  SMP_USE_HRSHV },
+	{ "res.hdr_val",        smp_fetch_hdr_val,            ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRSHV },
+
+	/* scook is valid only on the response and is used for ACL compatibility */
+	{ "scook",              smp_fetch_cookie,             ARG1(0,STR),      NULL,    SMP_T_STR,  SMP_USE_HRSHV },
+	{ "scook_cnt",          smp_fetch_cookie_cnt,         ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRSHV },
+	{ "scook_val",          smp_fetch_cookie_val,         ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRSHV },
+	{ "set-cookie",         smp_fetch_cookie,             ARG1(0,STR),      NULL,    SMP_T_STR,  SMP_USE_HRSHV }, /* deprecated */
+
+	/* shdr is valid only on the response and is used for ACL compatibility */
+	{ "shdr",               smp_fetch_hdr,                ARG2(0,STR,SINT), val_hdr, SMP_T_STR,  SMP_USE_HRSHV },
+	{ "shdr_cnt",           smp_fetch_hdr_cnt,            ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRSHV },
+	{ "shdr_ip",            smp_fetch_hdr_ip,             ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRSHV },
+	{ "shdr_val",           smp_fetch_hdr_val,            ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRSHV },
+
+	{ "status",             smp_fetch_stcode,             0,                NULL,    SMP_T_SINT, SMP_USE_HRSHP },
+	{ "unique-id",          smp_fetch_uniqueid,           0,                NULL,    SMP_T_STR,  SMP_SRC_L4SRV },
+	{ "url",                smp_fetch_url,                0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
+	{ "url32",              smp_fetch_url32,              0,                NULL,    SMP_T_SINT, SMP_USE_HRQHV },
+	{ "url32+src",          smp_fetch_url32_src,          0,                NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
+	{ "url_ip",             smp_fetch_url_ip,             0,                NULL,    SMP_T_IPV4, SMP_USE_HRQHV },
+	{ "url_port",           smp_fetch_url_port,           0,                NULL,    SMP_T_SINT, SMP_USE_HRQHV },
+	{ "url_param",          smp_fetch_url_param,          ARG2(0,STR,STR),  NULL,    SMP_T_STR,  SMP_USE_HRQHV },
+	{ "urlp"     ,          smp_fetch_url_param,          ARG2(0,STR,STR),  NULL,    SMP_T_STR,  SMP_USE_HRQHV },
+	{ "urlp_val",           smp_fetch_url_param_val,      ARG2(0,STR,STR),  NULL,    SMP_T_SINT, SMP_USE_HRQHV },
+	{ /* END */ },
+}};
+
+
+__attribute__((constructor))
+static void __http_fetch_init(void)
+{
+	sample_register_fetches(&sample_fetch_keywords);
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ */
diff --git a/src/proto_http.c b/src/proto_http.c
index 9f2ccc0..94e672d 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -98,9 +98,6 @@
        .list = LIST_HEAD_INIT(http_res_keywords.list)
 };
 
-/* this struct is used between calls to smp_fetch_hdr() or smp_fetch_cookie() */
-static THREAD_LOCAL struct hdr_ctx static_hdr_ctx;
-
 static int http_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struct http_txn *txn);
 
 static inline int http_msg_forward_body(struct stream *s, struct http_msg *msg);
@@ -664,96 +661,13 @@
 		sol = eol + idx->v[cur_idx].cr + 1;
 		cur_idx = idx->v[cur_idx].next;
 	}
-}
-
-/*
- * Returns the data from Authorization header. Function may be called more
- * than once so data is stored in txn->auth_data. When no header is found
- * or auth method is unknown auth_method is set to HTTP_AUTH_WRONG to avoid
- * searching again for something we are unable to find anyway. However, if
- * the result if valid, the cache is not reused because we would risk to
- * have the credentials overwritten by another stream in parallel.
- */
-
-int
-get_http_auth(struct stream *s)
-{
-
-	struct http_txn *txn = s->txn;
-	struct buffer auth_method;
-	struct hdr_ctx ctx;
-	char *h, *p;
-	int len;
-
-#ifdef DEBUG_AUTH
-	printf("Auth for stream %p: %d\n", s, txn->auth.method);
-#endif
-
-	if (txn->auth.method == HTTP_AUTH_WRONG)
-		return 0;
-
-	txn->auth.method = HTTP_AUTH_WRONG;
-
-	ctx.idx = 0;
-
-	if (txn->flags & TX_USE_PX_CONN) {
-		h = "Proxy-Authorization";
-		len = strlen(h);
-	} else {
-		h = "Authorization";
-		len = strlen(h);
-	}
-
-	if (!http_find_header2(h, len, ci_head(&s->req), &txn->hdr_idx, &ctx))
-		return 0;
-
-	h = ctx.line + ctx.val;
-
-	p = memchr(h, ' ', ctx.vlen);
-	len = p - h;
-	if (!p || len <= 0)
-		return 0;
-
-	if (chunk_initlen(&auth_method, h, 0, len) != 1)
-		return 0;
-
-	chunk_initlen(&txn->auth.method_data, p + 1, 0, ctx.vlen - len - 1);
-
-	if (!strncasecmp("Basic", auth_method.area, auth_method.data)) {
-		struct buffer *http_auth = get_trash_chunk();
-
-		len = base64dec(txn->auth.method_data.area,
-				txn->auth.method_data.data,
-				http_auth->area, global.tune.bufsize - 1);
-
-		if (len < 0)
-			return 0;
-
-
-		http_auth->area[len] = '\0';
-
-		p = strchr(http_auth->area, ':');
-
-		if (!p)
-			return 0;
-
-		txn->auth.user = http_auth->area;
-		*p = '\0';
-		txn->auth.pass = p+1;
-
-		txn->auth.method = HTTP_AUTH_BASIC;
-		return 1;
-	}
-
-	return 0;
 }
 
-
 /* convert an HTTP/0.9 request into an HTTP/1.0 request. Returns 1 if the
  * conversion succeeded, 0 in case of error. If the request was already 1.X,
  * nothing is done and 1 is returned.
  */
-static int http_upgrade_v09_to_v10(struct http_txn *txn)
+int http_upgrade_v09_to_v10(struct http_txn *txn)
 {
 	int delta;
 	char *cur_end;
@@ -8888,3039 +8802,164 @@
 	return NULL;
 }
 
-/************************************************************************/
-/*        The code below is dedicated to ACL parsing and matching       */
-/************************************************************************/
-
-
-/* This function ensures that the prerequisites for an L7 fetch are ready,
- * which means that a request or response is ready. If some data is missing,
- * a parsing attempt is made. This is useful in TCP-based ACLs which are able
- * to extract data from L7. If <req_vol> is non-null during a request prefetch,
- * another test is made to ensure the required information is not gone.
+/* This function executes one of the set-{method,path,query,uri} actions. It
+ * takes the string from the variable 'replace' with length 'len', then modifies
+ * the relevant part of the request line accordingly. Then it updates various
+ * pointers to the next elements which were moved, and the total buffer length.
+ * It finds the action to be performed in p[2], previously filled by function
+ * parse_set_req_line(). It returns 0 in case of success, -1 in case of internal
+ * error, though this can be revisited when this code is finally exploited.
+ *
+ * 'action' can be '0' to replace method, '1' to replace path, '2' to replace
+ * query string and 3 to replace uri.
  *
- * The function returns :
- *   0 with SMP_F_MAY_CHANGE in the sample flags if some data is missing to
- *     decide whether or not an HTTP message is present ;
- *   0 if the requested data cannot be fetched or if it is certain that
- *     we'll never have any HTTP message there ;
- *   1 if an HTTP message is ready
+ * In query string case, the mark question '?' must be set at the start of the
+ * string by the caller, event if the replacement query string is empty.
  */
-int smp_prefetch_http(struct proxy *px, struct stream *s, unsigned int opt,
-                  const struct arg *args, struct sample *smp, int req_vol)
+int http_replace_req_line(int action, const char *replace, int len,
+                          struct proxy *px, struct stream *s)
 {
-	struct http_txn *txn;
-	struct http_msg *msg;
-
-	/* Note: it is possible that <s> is NULL when called before stream
-	 * initialization (eg: tcp-request connection), so this function is the
-	 * one responsible for guarding against this case for all HTTP users.
-	 */
-	if (!s)
-		return 0;
-
-	if (!s->txn) {
-		if (unlikely(!http_alloc_txn(s)))
-			return 0; /* not enough memory */
-		http_init_txn(s);
-	}
-	txn = s->txn;
-	msg = &txn->req;
+	struct http_txn *txn = s->txn;
+	char *cur_ptr, *cur_end;
+	int offset = 0;
+	int delta;
 
-	/* Check for a dependency on a request */
-	smp->data.type = SMP_T_BOOL;
+	switch (action) {
+	case 0: // method
+		cur_ptr = ci_head(&s->req);
+		cur_end = cur_ptr + txn->req.sl.rq.m_l;
 
-	if ((opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) {
-		/* If the buffer does not leave enough free space at the end,
-		 * we must first realign it.
-		 */
-		if (ci_head(&s->req) > b_orig(&s->req.buf) &&
-		    ci_head(&s->req) + ci_data(&s->req) > b_wrap(&s->req.buf) - global.tune.maxrewrite)
-			channel_slow_realign(&s->req, trash.area);
+		/* adjust req line offsets and lengths */
+		delta = len - offset - (cur_end - cur_ptr);
+		txn->req.sl.rq.m_l += delta;
+		txn->req.sl.rq.u   += delta;
+		txn->req.sl.rq.v   += delta;
+		break;
 
-		if (unlikely(txn->req.msg_state < HTTP_MSG_BODY)) {
-			if (msg->msg_state == HTTP_MSG_ERROR)
-				return 0;
+	case 1: // path
+		cur_ptr = http_txn_get_path(txn);
+		if (!cur_ptr)
+			cur_ptr = ci_head(&s->req) + txn->req.sl.rq.u;
 
-			/* Try to decode HTTP request */
-			if (likely(msg->next < ci_data(&s->req)))
-				http_msg_analyzer(msg, &txn->hdr_idx);
+		cur_end = cur_ptr;
+		while (cur_end < ci_head(&s->req) + txn->req.sl.rq.u + txn->req.sl.rq.u_l && *cur_end != '?')
+			cur_end++;
 
-			/* Still no valid request ? */
-			if (unlikely(msg->msg_state < HTTP_MSG_BODY)) {
-				if ((msg->msg_state == HTTP_MSG_ERROR) ||
-				    channel_full(&s->req, global.tune.maxrewrite)) {
-					return 0;
-				}
-				/* wait for final state */
-				smp->flags |= SMP_F_MAY_CHANGE;
-				return 0;
-			}
+		/* adjust req line offsets and lengths */
+		delta = len - offset - (cur_end - cur_ptr);
+		txn->req.sl.rq.u_l += delta;
+		txn->req.sl.rq.v   += delta;
+		break;
 
-			/* OK we just got a valid HTTP request. We have some minor
-			 * preparation to perform so that further checks can rely
-			 * on HTTP tests.
-			 */
+	case 2: // query
+		offset = 1;
+		cur_ptr = ci_head(&s->req) + txn->req.sl.rq.u;
+		cur_end = cur_ptr + txn->req.sl.rq.u_l;
+		while (cur_ptr < cur_end && *cur_ptr != '?')
+			cur_ptr++;
 
-			/* If the request was parsed but was too large, we must absolutely
-			 * return an error so that it is not processed. At the moment this
-			 * cannot happen, but if the parsers are to change in the future,
-			 * we want this check to be maintained.
-			 */
-			if (unlikely(ci_head(&s->req) + ci_data(&s->req) >
-				     b_wrap(&s->req.buf) - global.tune.maxrewrite)) {
-				msg->err_state = msg->msg_state;
-				msg->msg_state = HTTP_MSG_ERROR;
-				smp->data.u.sint = 1;
-				return 1;
-			}
+		/* skip the question mark or indicate that we must insert it
+		 * (but only if the format string is not empty then).
+		 */
+		if (cur_ptr < cur_end)
+			cur_ptr++;
+		else if (len > 1)
+			offset = 0;
 
-			txn->meth = find_http_meth(ci_head(msg->chn), msg->sl.rq.m_l);
-			if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)
-				s->flags |= SF_REDIRECTABLE;
+		/* adjust req line offsets and lengths */
+		delta = len - offset - (cur_end - cur_ptr);
+		txn->req.sl.rq.u_l += delta;
+		txn->req.sl.rq.v   += delta;
+		break;
 
-			if (unlikely(msg->sl.rq.v_l == 0) && !http_upgrade_v09_to_v10(txn))
-				return 0;
-		}
+	case 3: // uri
+		cur_ptr = ci_head(&s->req) + txn->req.sl.rq.u;
+		cur_end = cur_ptr + txn->req.sl.rq.u_l;
 
-		if (req_vol && txn->rsp.msg_state != HTTP_MSG_RPBEFORE) {
-			return 0;  /* data might have moved and indexes changed */
-		}
+		/* adjust req line offsets and lengths */
+		delta = len - offset - (cur_end - cur_ptr);
+		txn->req.sl.rq.u_l += delta;
+		txn->req.sl.rq.v   += delta;
+		break;
 
-		/* otherwise everything's ready for the request */
-	}
-	else {
-		/* Check for a dependency on a response */
-		if (txn->rsp.msg_state < HTTP_MSG_BODY) {
-			smp->flags |= SMP_F_MAY_CHANGE;
-			return 0;
-		}
+	default:
+		return -1;
 	}
 
-	/* everything's OK */
-	smp->data.u.sint = 1;
-	return 1;
-}
-
-/* 1. Check on METHOD
- * We use the pre-parsed method if it is known, and store its number as an
- * integer. If it is unknown, we use the pointer and the length.
- */
-static int pat_parse_meth(const char *text, struct pattern *pattern, int mflags, char **err)
-{
-	int len, meth;
-
-	len  = strlen(text);
-	meth = find_http_meth(text, len);
-
-	pattern->val.i = meth;
-	if (meth == HTTP_METH_OTHER) {
-		pattern->ptr.str = (char *)text;
-		pattern->len = len;
-	}
-	else {
-		pattern->ptr.str = NULL;
-		pattern->len = 0;
-	}
-	return 1;
+	/* commit changes and adjust end of message */
+	delta = b_rep_blk(&s->req.buf, cur_ptr, cur_end, replace + offset, len - offset);
+	txn->req.sl.rq.l += delta;
+	txn->hdr_idx.v[0].len += delta;
+	http_msg_move_end(&txn->req, delta);
+	return 0;
 }
 
-/* This function fetches the method of current HTTP request and stores
- * it in the global pattern struct as a chunk. There are two possibilities :
- *   - if the method is known (not HTTP_METH_OTHER), its identifier is stored
- *     in <len> and <ptr> is NULL ;
- *   - if the method is unknown (HTTP_METH_OTHER), <ptr> points to the text and
- *     <len> to its length.
- * This is intended to be used with pat_match_meth() only.
+/* This function replace the HTTP status code and the associated message. The
+ * variable <status> contains the new status code. This function never fails.
  */
-static int
-smp_fetch_meth(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	int meth;
-	struct http_txn *txn;
-
-	CHECK_HTTP_MESSAGE_FIRST_PERM();
-
-	txn = smp->strm->txn;
-	meth = txn->meth;
-	smp->data.type = SMP_T_METH;
-	smp->data.u.meth.meth = meth;
-	if (meth == HTTP_METH_OTHER) {
-		if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE)
-			/* ensure the indexes are not affected */
-			return 0;
-		smp->flags |= SMP_F_CONST;
-		smp->data.u.meth.str.data = txn->req.sl.rq.m_l;
-		smp->data.u.meth.str.area = ci_head(txn->req.chn);
-	}
-	smp->flags |= SMP_F_VOL_1ST;
-	return 1;
-}
-
-/* See above how the method is stored in the global pattern */
-static struct pattern *pat_match_meth(struct sample *smp, struct pattern_expr *expr, int fill)
-{
-	int icase;
-	struct pattern_list *lst;
-	struct pattern *pattern;
-
-	list_for_each_entry(lst, &expr->patterns, list) {
-		pattern = &lst->pat;
-
-		/* well-known method */
-		if (pattern->val.i != HTTP_METH_OTHER) {
-			if (smp->data.u.meth.meth == pattern->val.i)
-				return pattern;
-			else
-				continue;
-		}
-
-		/* Other method, we must compare the strings */
-		if (pattern->len != smp->data.u.meth.str.data)
-			continue;
-
-		icase = expr->mflags & PAT_MF_IGNORE_CASE;
-		if ((icase && strncasecmp(pattern->ptr.str, smp->data.u.meth.str.area, smp->data.u.meth.str.data) == 0) ||
-		    (!icase && strncmp(pattern->ptr.str, smp->data.u.meth.str.area, smp->data.u.meth.str.data) == 0))
-			return pattern;
-	}
-	return NULL;
-}
-
-static int
-smp_fetch_rqver(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn;
-	char *ptr;
-	int len;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	len = txn->req.sl.rq.v_l;
-	ptr = ci_head(txn->req.chn) + txn->req.sl.rq.v;
-
-	while ((len-- > 0) && (*ptr++ != '/'));
-	if (len <= 0)
-		return 0;
-
-	smp->data.type = SMP_T_STR;
-	smp->data.u.str.area = ptr;
-	smp->data.u.str.data = len;
-
-	smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
-	return 1;
-}
-
-static int
-smp_fetch_stver(const struct arg *args, struct sample *smp, const char *kw, void *private)
+void http_set_status(unsigned int status, const char *reason, struct stream *s)
 {
-	struct http_txn *txn;
-	char *ptr;
-	int len;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	if (txn->rsp.msg_state < HTTP_MSG_BODY)
-		return 0;
-
-	len = txn->rsp.sl.st.v_l;
-	ptr = ci_head(txn->rsp.chn);
+	struct http_txn *txn = s->txn;
+	char *cur_ptr, *cur_end;
+	int delta;
+	char *res;
+	int c_l;
+	const char *msg = reason;
+	int msg_len;
 
-	while ((len-- > 0) && (*ptr++ != '/'));
-	if (len <= 0)
-		return 0;
+	chunk_reset(&trash);
 
-	smp->data.type = SMP_T_STR;
-	smp->data.u.str.area = ptr;
-	smp->data.u.str.data = len;
+	res = ultoa_o(status, trash.area, trash.size);
+	c_l = res - trash.area;
 
-	smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
-	return 1;
-}
+	trash.area[c_l] = ' ';
+	trash.data = c_l + 1;
 
-/* 3. Check on Status Code. We manipulate integers here. */
-static int
-smp_fetch_stcode(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn;
-	char *ptr;
-	int len;
+	/* Do we have a custom reason format string? */
+	if (msg == NULL)
+		msg = http_get_reason(status);
+	msg_len = strlen(msg);
+	strncpy(&trash.area[trash.data], msg, trash.size - trash.data);
+	trash.data += msg_len;
 
-	CHECK_HTTP_MESSAGE_FIRST();
+	cur_ptr = ci_head(&s->res) + txn->rsp.sl.st.c;
+	cur_end = ci_head(&s->res) + txn->rsp.sl.st.r + txn->rsp.sl.st.r_l;
 
-	txn = smp->strm->txn;
-	if (txn->rsp.msg_state < HTTP_MSG_BODY)
-		return 0;
+	/* commit changes and adjust message */
+	delta = b_rep_blk(&s->res.buf, cur_ptr, cur_end, trash.area,
+			  trash.data);
 
-	len = txn->rsp.sl.st.c_l;
-	ptr = ci_head(txn->rsp.chn) + txn->rsp.sl.st.c;
+	/* adjust res line offsets and lengths */
+	txn->rsp.sl.st.r += c_l - txn->rsp.sl.st.c_l;
+	txn->rsp.sl.st.c_l = c_l;
+	txn->rsp.sl.st.r_l = msg_len;
 
-	smp->data.type = SMP_T_SINT;
-	smp->data.u.sint = __strl2ui(ptr, len);
-	smp->flags = SMP_F_VOL_1ST;
-	return 1;
+	delta = trash.data - (cur_end - cur_ptr);
+	txn->rsp.sl.st.l += delta;
+	txn->hdr_idx.v[0].len += delta;
+	http_msg_move_end(&txn->rsp, delta);
 }
 
-static int
-smp_fetch_uniqueid(const struct arg *args, struct sample *smp, const char *kw, void *private)
+/*
+ * Return the struct http_req_action_kw associated to a keyword.
+ */
+struct action_kw *action_http_req_custom(const char *kw)
 {
-	if (LIST_ISEMPTY(&smp->sess->fe->format_unique_id))
-		return 0;
-
-	if (!smp->strm->unique_id) {
-		if ((smp->strm->unique_id = pool_alloc(pool_head_uniqueid)) == NULL)
-			return 0;
-		smp->strm->unique_id[0] = '\0';
-	}
-	smp->data.u.str.data = build_logline(smp->strm, smp->strm->unique_id,
-	                                    UNIQUEID_LEN, &smp->sess->fe->format_unique_id);
-
-	smp->data.type = SMP_T_STR;
-	smp->data.u.str.area = smp->strm->unique_id;
-	smp->flags = SMP_F_CONST;
-	return 1;
+	return action_lookup(&http_req_keywords.list, kw);
 }
 
-/* Returns a string block containing all headers including the
- * empty line wich separes headers from the body. This is useful
- * form some headers analysis.
+/*
+ * Return the struct http_res_action_kw associated to a keyword.
  */
-static int
-smp_fetch_hdrs(const struct arg *args, struct sample *smp, const char *kw, void *private)
+struct action_kw *action_http_res_custom(const char *kw)
 {
-	struct http_msg *msg;
-	struct hdr_idx *idx;
-	struct http_txn *txn;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	idx = &txn->hdr_idx;
-	msg = &txn->req;
+	return action_lookup(&http_res_keywords.list, kw);
+}
 
-	smp->data.type = SMP_T_STR;
-	smp->data.u.str.area = ci_head(msg->chn) + hdr_idx_first_pos(idx);
-	smp->data.u.str.data = msg->eoh - hdr_idx_first_pos(idx) + 1 +
-	                      (ci_head(msg->chn)[msg->eoh] == '\r');
-
-	return 1;
-}
-
-/* Returns the header request in a length/value encoded format.
- * This is useful for exchanges with the SPOE.
- *
- * A "length value" is a multibyte code encoding numbers. It uses the
- * SPOE format. The encoding is the following:
- *
- * Each couple "header name" / "header value" is composed
- * like this:
- *    "length value" "header name bytes"
- *    "length value" "header value bytes"
- * When the last header is reached, the header name and the header
- * value are empty. Their length are 0
- */
-static int
-smp_fetch_hdrs_bin(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_msg *msg;
-	struct buffer *temp;
-	struct hdr_idx *idx;
-	const char *cur_ptr, *cur_next, *p;
-	int old_idx, cur_idx;
-	struct hdr_idx_elem *cur_hdr;
-	const char *hn, *hv;
-	int hnl, hvl;
-	int ret;
-	struct http_txn *txn;
-	char *buf;
-	char *end;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	temp = get_trash_chunk();
-	buf = temp->area;
-	end = temp->area + temp->size;
-
-	txn = smp->strm->txn;
-	idx = &txn->hdr_idx;
-	msg = &txn->req;
-
-	/* Build array of headers. */
-	old_idx = 0;
-	cur_next = ci_head(msg->chn) + hdr_idx_first_pos(idx);
-	while (1) {
-		cur_idx = idx->v[old_idx].next;
-		if (!cur_idx)
-			break;
-		old_idx = cur_idx;
-
-		cur_hdr  = &idx->v[cur_idx];
-		cur_ptr  = cur_next;
-		cur_next = cur_ptr + cur_hdr->len + cur_hdr->cr + 1;
-
-		/* Now we have one full header at cur_ptr of len cur_hdr->len,
-		 * and the next header starts at cur_next. We'll check
-		 * this header in the list as well as against the default
-		 * rule.
-		 */
-
-		/* look for ': *'. */
-		hn = cur_ptr;
-		for (p = cur_ptr; p < cur_ptr + cur_hdr->len && *p != ':'; p++);
-		if (p >= cur_ptr+cur_hdr->len)
-			continue;
-		hnl = p - hn;
-		p++;
-		while (p < cur_ptr + cur_hdr->len && (*p == ' ' || *p == '\t'))
-			p++;
-		if (p >= cur_ptr + cur_hdr->len)
-			continue;
-		hv = p;
-		hvl = cur_ptr + cur_hdr->len-p;
-
-		/* encode the header name. */
-		ret = encode_varint(hnl, &buf, end);
-		if (ret == -1)
-			return 0;
-		if (buf + hnl > end)
-			return 0;
-		memcpy(buf, hn, hnl);
-		buf += hnl;
-
-		/* encode and copy the value. */
-		ret = encode_varint(hvl, &buf, end);
-		if (ret == -1)
-			return 0;
-		if (buf + hvl > end)
-			return 0;
-		memcpy(buf, hv, hvl);
-		buf += hvl;
-	}
-
-	/* encode the end of the header list with empty
-	 * header name and header value.
-	 */
-	ret = encode_varint(0, &buf, end);
-	if (ret == -1)
-		return 0;
-	ret = encode_varint(0, &buf, end);
-	if (ret == -1)
-		return 0;
-
-	/* Initialise sample data which will be filled. */
-	smp->data.type = SMP_T_BIN;
-	smp->data.u.str.area = temp->area;
-	smp->data.u.str.data = buf - temp->area;
-	smp->data.u.str.size = temp->size;
-
-	return 1;
-}
-
-/* returns the longest available part of the body. This requires that the body
- * has been waited for using http-buffer-request.
- */
-static int
-smp_fetch_body(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_msg *msg;
-	unsigned long len;
-	unsigned long block1;
-	char *body;
-	struct buffer *temp;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ)
-		msg = &smp->strm->txn->req;
-	else
-		msg = &smp->strm->txn->rsp;
-
-	len  = http_body_bytes(msg);
-	body = c_ptr(msg->chn, -http_data_rewind(msg));
-
-	block1 = len;
-	if (block1 > b_wrap(&msg->chn->buf) - body)
-		block1 = b_wrap(&msg->chn->buf) - body;
-
-	if (block1 == len) {
-		/* buffer is not wrapped (or empty) */
-		smp->data.type = SMP_T_BIN;
-		smp->data.u.str.area = body;
-		smp->data.u.str.data = len;
-		smp->flags = SMP_F_VOL_TEST | SMP_F_CONST;
-	}
-	else {
-		/* buffer is wrapped, we need to defragment it */
-		temp = get_trash_chunk();
-		memcpy(temp->area, body, block1);
-		memcpy(temp->area + block1, b_orig(&msg->chn->buf),
-		       len - block1);
-		smp->data.type = SMP_T_BIN;
-		smp->data.u.str.area = temp->area;
-		smp->data.u.str.data = len;
-		smp->flags = SMP_F_VOL_TEST;
-	}
-	return 1;
-}
-
-
-/* returns the available length of the body. This requires that the body
- * has been waited for using http-buffer-request.
- */
-static int
-smp_fetch_body_len(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_msg *msg;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ)
-		msg = &smp->strm->txn->req;
-	else
-		msg = &smp->strm->txn->rsp;
-
-	smp->data.type = SMP_T_SINT;
-	smp->data.u.sint = http_body_bytes(msg);
-
-	smp->flags = SMP_F_VOL_TEST;
-	return 1;
-}
-
-
-/* returns the advertised length of the body, or the advertised size of the
- * chunks available in the buffer. This requires that the body has been waited
- * for using http-buffer-request.
- */
-static int
-smp_fetch_body_size(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_msg *msg;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ)
-		msg = &smp->strm->txn->req;
-	else
-		msg = &smp->strm->txn->rsp;
-
-	smp->data.type = SMP_T_SINT;
-	smp->data.u.sint = msg->body_len;
-
-	smp->flags = SMP_F_VOL_TEST;
-	return 1;
-}
-
-
-/* 4. Check on URL/URI. A pointer to the URI is stored. */
-static int
-smp_fetch_url(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	smp->data.type = SMP_T_STR;
-	smp->data.u.str.data = txn->req.sl.rq.u_l;
-	smp->data.u.str.area = ci_head(txn->req.chn) + txn->req.sl.rq.u;
-	smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
-	return 1;
-}
-
-static int
-smp_fetch_url_ip(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn;
-	struct sockaddr_storage addr;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	url2sa(ci_head(txn->req.chn) + txn->req.sl.rq.u, txn->req.sl.rq.u_l, &addr, NULL);
-	if (((struct sockaddr_in *)&addr)->sin_family != AF_INET)
-		return 0;
-
-	smp->data.type = SMP_T_IPV4;
-	smp->data.u.ipv4 = ((struct sockaddr_in *)&addr)->sin_addr;
-	smp->flags = 0;
-	return 1;
-}
-
-static int
-smp_fetch_url_port(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn;
-	struct sockaddr_storage addr;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	url2sa(ci_head(txn->req.chn) + txn->req.sl.rq.u, txn->req.sl.rq.u_l, &addr, NULL);
-	if (((struct sockaddr_in *)&addr)->sin_family != AF_INET)
-		return 0;
-
-	smp->data.type = SMP_T_SINT;
-	smp->data.u.sint = ntohs(((struct sockaddr_in *)&addr)->sin_port);
-	smp->flags = 0;
-	return 1;
-}
-
-/* Fetch an HTTP header. A pointer to the beginning of the value is returned.
- * Accepts an optional argument of type string containing the header field name,
- * and an optional argument of type signed or unsigned integer to request an
- * explicit occurrence of the header. Note that in the event of a missing name,
- * headers are considered from the first one. It does not stop on commas and
- * returns full lines instead (useful for User-Agent or Date for example).
- */
-static int
-smp_fetch_fhdr(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct hdr_idx *idx;
-	struct hdr_ctx *ctx = smp->ctx.a[0];
-	const struct http_msg *msg;
-	int occ = 0;
-	const char *name_str = NULL;
-	int name_len = 0;
-
-	if (!ctx) {
-		/* first call */
-		ctx = &static_hdr_ctx;
-		ctx->idx = 0;
-		smp->ctx.a[0] = ctx;
-	}
-
-	if (args) {
-		if (args[0].type != ARGT_STR)
-			return 0;
-		name_str = args[0].data.str.area;
-		name_len = args[0].data.str.data;
-
-		if (args[1].type == ARGT_SINT)
-			occ = args[1].data.sint;
-	}
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	idx = &smp->strm->txn->hdr_idx;
-	msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp;
-
-	if (ctx && !(smp->flags & SMP_F_NOT_LAST))
-		/* search for header from the beginning */
-		ctx->idx = 0;
-
-	if (!occ && !(smp->opt & SMP_OPT_ITERATE))
-		/* no explicit occurrence and single fetch => last header by default */
-		occ = -1;
-
-	if (!occ)
-		/* prepare to report multiple occurrences for ACL fetches */
-		smp->flags |= SMP_F_NOT_LAST;
-
-	smp->data.type = SMP_T_STR;
-	smp->flags |= SMP_F_VOL_HDR | SMP_F_CONST;
-	if (http_get_fhdr(msg, name_str, name_len, idx, occ, ctx, &smp->data.u.str.area, &smp->data.u.str.data))
-		return 1;
-
-	smp->flags &= ~SMP_F_NOT_LAST;
-	return 0;
-}
-
-/* 6. Check on HTTP header count. The number of occurrences is returned.
- * Accepts exactly 1 argument of type string. It does not stop on commas and
- * returns full lines instead (useful for User-Agent or Date for example).
- */
-static int
-smp_fetch_fhdr_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct hdr_idx *idx;
-	struct hdr_ctx ctx;
-	const struct http_msg *msg;
-	int cnt;
-	const char *name = NULL;
-	int len = 0;
-
-	if (args && args->type == ARGT_STR) {
-		name = args->data.str.area;
-		len = args->data.str.data;
-	}
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	idx = &smp->strm->txn->hdr_idx;
-	msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp;
-
-	ctx.idx = 0;
-	cnt = 0;
-	while (http_find_full_header2(name, len, ci_head(msg->chn), idx, &ctx))
-		cnt++;
-
-	smp->data.type = SMP_T_SINT;
-	smp->data.u.sint = cnt;
-	smp->flags = SMP_F_VOL_HDR;
-	return 1;
-}
-
-static int
-smp_fetch_hdr_names(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct hdr_idx *idx;
-	struct hdr_ctx ctx;
-	const struct http_msg *msg;
-	struct buffer *temp;
-	char del = ',';
-
-	if (args && args->type == ARGT_STR)
-		del = *args[0].data.str.area;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	idx = &smp->strm->txn->hdr_idx;
-	msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp;
-
-	temp = get_trash_chunk();
-
-	ctx.idx = 0;
-	while (http_find_next_header(ci_head(msg->chn), idx, &ctx)) {
-		if (temp->data)
-			temp->area[temp->data++] = del;
-		memcpy(temp->area + temp->data, ctx.line, ctx.del);
-		temp->data += ctx.del;
-	}
-
-	smp->data.type = SMP_T_STR;
-	smp->data.u.str.area = temp->area;
-	smp->data.u.str.data = temp->data;
-	smp->flags = SMP_F_VOL_HDR;
-	return 1;
-}
-
-/* Fetch an HTTP header. A pointer to the beginning of the value is returned.
- * Accepts an optional argument of type string containing the header field name,
- * and an optional argument of type signed or unsigned integer to request an
- * explicit occurrence of the header. Note that in the event of a missing name,
- * headers are considered from the first one.
- */
-static int
-smp_fetch_hdr(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct hdr_idx *idx;
-	struct hdr_ctx *ctx = smp->ctx.a[0];
-	const struct http_msg *msg;
-	int occ = 0;
-	const char *name_str = NULL;
-	int name_len = 0;
-
-	if (!ctx) {
-		/* first call */
-		ctx = &static_hdr_ctx;
-		ctx->idx = 0;
-		smp->ctx.a[0] = ctx;
-	}
-
-	if (args) {
-		if (args[0].type != ARGT_STR)
-			return 0;
-		name_str = args[0].data.str.area;
-		name_len = args[0].data.str.data;
-
-		if (args[1].type == ARGT_SINT)
-			occ = args[1].data.sint;
-	}
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	idx = &smp->strm->txn->hdr_idx;
-	msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp;
-
-	if (ctx && !(smp->flags & SMP_F_NOT_LAST))
-		/* search for header from the beginning */
-		ctx->idx = 0;
-
-	if (!occ && !(smp->opt & SMP_OPT_ITERATE))
-		/* no explicit occurrence and single fetch => last header by default */
-		occ = -1;
-
-	if (!occ)
-		/* prepare to report multiple occurrences for ACL fetches */
-		smp->flags |= SMP_F_NOT_LAST;
-
-	smp->data.type = SMP_T_STR;
-	smp->flags |= SMP_F_VOL_HDR | SMP_F_CONST;
-	if (http_get_hdr(msg, name_str, name_len, idx, occ, ctx, &smp->data.u.str.area, &smp->data.u.str.data))
-		return 1;
-
-	smp->flags &= ~SMP_F_NOT_LAST;
-	return 0;
-}
-
-/* 6. Check on HTTP header count. The number of occurrences is returned.
- * Accepts exactly 1 argument of type string.
- */
-static int
-smp_fetch_hdr_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct hdr_idx *idx;
-	struct hdr_ctx ctx;
-	const struct http_msg *msg;
-	int cnt;
-	const char *name = NULL;
-	int len = 0;
-
-	if (args && args->type == ARGT_STR) {
-		name = args->data.str.area;
-		len = args->data.str.data;
-	}
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	idx = &smp->strm->txn->hdr_idx;
-	msg = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) ? &smp->strm->txn->req : &smp->strm->txn->rsp;
-
-	ctx.idx = 0;
-	cnt = 0;
-	while (http_find_header2(name, len, ci_head(msg->chn), idx, &ctx))
-		cnt++;
-
-	smp->data.type = SMP_T_SINT;
-	smp->data.u.sint = cnt;
-	smp->flags = SMP_F_VOL_HDR;
-	return 1;
-}
-
-/* Fetch an HTTP header's integer value. The integer value is returned. It
- * takes a mandatory argument of type string and an optional one of type int
- * to designate a specific occurrence. It returns an unsigned integer, which
- * may or may not be appropriate for everything.
- */
-static int
-smp_fetch_hdr_val(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	int ret = smp_fetch_hdr(args, smp, kw, private);
-
-	if (ret > 0) {
-		smp->data.type = SMP_T_SINT;
-		smp->data.u.sint = strl2ic(smp->data.u.str.area,
-					   smp->data.u.str.data);
-	}
-
-	return ret;
-}
-
-/* Fetch an HTTP header's IP value. takes a mandatory argument of type string
- * and an optional one of type int to designate a specific occurrence.
- * It returns an IPv4 or IPv6 address.
- */
-static int
-smp_fetch_hdr_ip(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	int ret;
-
-	while ((ret = smp_fetch_hdr(args, smp, kw, private)) > 0) {
-		if (url2ipv4((char *) smp->data.u.str.area, &smp->data.u.ipv4)) {
-			smp->data.type = SMP_T_IPV4;
-			break;
-		} else {
-			struct buffer *temp = get_trash_chunk();
-			if (smp->data.u.str.data < temp->size - 1) {
-				memcpy(temp->area, smp->data.u.str.area,
-				       smp->data.u.str.data);
-				temp->area[smp->data.u.str.data] = '\0';
-				if (inet_pton(AF_INET6, temp->area, &smp->data.u.ipv6)) {
-					smp->data.type = SMP_T_IPV6;
-					break;
-				}
-			}
-		}
-
-		/* if the header doesn't match an IP address, fetch next one */
-		if (!(smp->flags & SMP_F_NOT_LAST))
-			return 0;
-	}
-	return ret;
-}
-
-/* 8. Check on URI PATH. A pointer to the PATH is stored. The path starts at
- * the first '/' after the possible hostname, and ends before the possible '?'.
- */
-static int
-smp_fetch_path(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn;
-	char *ptr, *end;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l;
-	ptr = http_txn_get_path(txn);
-	if (!ptr)
-		return 0;
-
-	/* OK, we got the '/' ! */
-	smp->data.type = SMP_T_STR;
-	smp->data.u.str.area = ptr;
-
-	while (ptr < end && *ptr != '?')
-		ptr++;
-
-	smp->data.u.str.data = ptr - smp->data.u.str.area;
-	smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
-	return 1;
-}
-
-/* This produces a concatenation of the first occurrence of the Host header
- * followed by the path component if it begins with a slash ('/'). This means
- * that '*' will not be added, resulting in exactly the first Host entry.
- * If no Host header is found, then the path is returned as-is. The returned
- * value is stored in the trash so it does not need to be marked constant.
- * The returned sample is of type string.
- */
-static int
-smp_fetch_base(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn;
-	char *ptr, *end, *beg;
-	struct hdr_ctx ctx;
-	struct buffer *temp;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	ctx.idx = 0;
-	if (!http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx) || !ctx.vlen)
-		return smp_fetch_path(args, smp, kw, private);
-
-	/* OK we have the header value in ctx.line+ctx.val for ctx.vlen bytes */
-	temp = get_trash_chunk();
-	memcpy(temp->area, ctx.line + ctx.val, ctx.vlen);
-	smp->data.type = SMP_T_STR;
-	smp->data.u.str.area = temp->area;
-	smp->data.u.str.data = ctx.vlen;
-
-	/* now retrieve the path */
-	end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l;
-	beg = http_txn_get_path(txn);
-	if (!beg)
-		beg = end;
-
-	for (ptr = beg; ptr < end && *ptr != '?'; ptr++);
-
-	if (beg < ptr && *beg == '/') {
-		memcpy(smp->data.u.str.area + smp->data.u.str.data, beg,
-		       ptr - beg);
-		smp->data.u.str.data += ptr - beg;
-	}
-
-	smp->flags = SMP_F_VOL_1ST;
-	return 1;
-}
-
-/* This produces a 32-bit hash of the concatenation of the first occurrence of
- * the Host header followed by the path component if it begins with a slash ('/').
- * This means that '*' will not be added, resulting in exactly the first Host
- * entry. If no Host header is found, then the path is used. The resulting value
- * is hashed using the path hash followed by a full avalanche hash and provides a
- * 32-bit integer value. This fetch is useful for tracking per-path activity on
- * high-traffic sites without having to store whole paths.
- */
-int
-smp_fetch_base32(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn;
-	struct hdr_ctx ctx;
-	unsigned int hash = 0;
-	char *ptr, *beg, *end;
-	int len;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	ctx.idx = 0;
-	if (http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx)) {
-		/* OK we have the header value in ctx.line+ctx.val for ctx.vlen bytes */
-		ptr = ctx.line + ctx.val;
-		len = ctx.vlen;
-		while (len--)
-			hash = *(ptr++) + (hash << 6) + (hash << 16) - hash;
-	}
-
-	/* now retrieve the path */
-	end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l;
-	beg = http_txn_get_path(txn);
-	if (!beg)
-		beg = end;
-
-	for (ptr = beg; ptr < end && *ptr != '?'; ptr++);
-
-	if (beg < ptr && *beg == '/') {
-		while (beg < ptr)
-			hash = *(beg++) + (hash << 6) + (hash << 16) - hash;
-	}
-	hash = full_hash(hash);
-
-	smp->data.type = SMP_T_SINT;
-	smp->data.u.sint = hash;
-	smp->flags = SMP_F_VOL_1ST;
-	return 1;
-}
-
-/* This concatenates the source address with the 32-bit hash of the Host and
- * path as returned by smp_fetch_base32(). The idea is to have per-source and
- * per-path counters. The result is a binary block from 8 to 20 bytes depending
- * on the source address length. The path hash is stored before the address so
- * that in environments where IPv6 is insignificant, truncating the output to
- * 8 bytes would still work.
- */
-static int
-smp_fetch_base32_src(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct buffer *temp;
-	struct connection *cli_conn = objt_conn(smp->sess->origin);
-
-	if (!cli_conn)
-		return 0;
-
-	if (!smp_fetch_base32(args, smp, kw, private))
-		return 0;
-
-	temp = get_trash_chunk();
-	*(unsigned int *) temp->area = htonl(smp->data.u.sint);
-	temp->data += sizeof(unsigned int);
-
-	switch (cli_conn->addr.from.ss_family) {
-	case AF_INET:
-		memcpy(temp->area + temp->data,
-		       &((struct sockaddr_in *)&cli_conn->addr.from)->sin_addr,
-		       4);
-		temp->data += 4;
-		break;
-	case AF_INET6:
-		memcpy(temp->area + temp->data,
-		       &((struct sockaddr_in6 *)&cli_conn->addr.from)->sin6_addr,
-		       16);
-		temp->data += 16;
-		break;
-	default:
-		return 0;
-	}
-
-	smp->data.u.str = *temp;
-	smp->data.type = SMP_T_BIN;
-	return 1;
-}
-
-/* Extracts the query string, which comes after the question mark '?'. If no
- * question mark is found, nothing is returned. Otherwise it returns a sample
- * of type string carrying the whole query string.
- */
-static int
-smp_fetch_query(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn;
-	char *ptr, *end;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	ptr = ci_head(txn->req.chn) + txn->req.sl.rq.u;
-	end = ptr + txn->req.sl.rq.u_l;
-
-	/* look up the '?' */
-	do {
-		if (ptr == end)
-			return 0;
-	} while (*ptr++ != '?');
-
-	smp->data.type = SMP_T_STR;
-	smp->data.u.str.area = ptr;
-	smp->data.u.str.data = end - ptr;
-	smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
-	return 1;
-}
-
-static int
-smp_fetch_proto_http(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	/* Note: hdr_idx.v cannot be NULL in this ACL because the ACL is tagged
-	 * as a layer7 ACL, which involves automatic allocation of hdr_idx.
-	 */
-
-	CHECK_HTTP_MESSAGE_FIRST_PERM();
-
-	smp->data.type = SMP_T_BOOL;
-	smp->data.u.sint = 1;
-	return 1;
-}
-
-/* return a valid test if the current request is the first one on the connection */
-static int
-smp_fetch_http_first_req(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	smp->data.type = SMP_T_BOOL;
-	smp->data.u.sint = !(smp->strm->txn->flags & TX_NOT_FIRST);
-	return 1;
-}
-
-/* Accepts exactly 1 argument of type userlist */
-static int
-smp_fetch_http_auth(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-
-	if (!args || args->type != ARGT_USR)
-		return 0;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	if (!get_http_auth(smp->strm))
-		return 0;
-
-	smp->data.type = SMP_T_BOOL;
-	smp->data.u.sint = check_user(args->data.usr, smp->strm->txn->auth.user,
-	                            smp->strm->txn->auth.pass);
-	return 1;
-}
-
-/* Accepts exactly 1 argument of type userlist */
-static int
-smp_fetch_http_auth_grp(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	if (!args || args->type != ARGT_USR)
-		return 0;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	if (!get_http_auth(smp->strm))
-		return 0;
-
-	/* if the user does not belong to the userlist or has a wrong password,
-	 * report that it unconditionally does not match. Otherwise we return
-	 * a string containing the username.
-	 */
-	if (!check_user(args->data.usr, smp->strm->txn->auth.user,
-	                smp->strm->txn->auth.pass))
-		return 0;
-
-	/* pat_match_auth() will need the user list */
-	smp->ctx.a[0] = args->data.usr;
-
-	smp->data.type = SMP_T_STR;
-	smp->flags = SMP_F_CONST;
-	smp->data.u.str.area = smp->strm->txn->auth.user;
-	smp->data.u.str.data = strlen(smp->strm->txn->auth.user);
-
-	return 1;
-}
-
-/* Fetch a captured HTTP request header. The index is the position of
- * the "capture" option in the configuration file
- */
-static int
-smp_fetch_capture_header_req(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct proxy *fe = strm_fe(smp->strm);
-	int idx;
-
-	if (!args || args->type != ARGT_SINT)
-		return 0;
-
-	idx = args->data.sint;
-
-	if (idx > (fe->nb_req_cap - 1) || smp->strm->req_cap == NULL || smp->strm->req_cap[idx] == NULL)
-		return 0;
-
-	smp->data.type = SMP_T_STR;
-	smp->flags |= SMP_F_CONST;
-	smp->data.u.str.area = smp->strm->req_cap[idx];
-	smp->data.u.str.data = strlen(smp->strm->req_cap[idx]);
-
-	return 1;
-}
-
-/* Fetch a captured HTTP response header. The index is the position of
- * the "capture" option in the configuration file
- */
-static int
-smp_fetch_capture_header_res(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct proxy *fe = strm_fe(smp->strm);
-	int idx;
-
-	if (!args || args->type != ARGT_SINT)
-		return 0;
-
-	idx = args->data.sint;
-
-	if (idx > (fe->nb_rsp_cap - 1) || smp->strm->res_cap == NULL || smp->strm->res_cap[idx] == NULL)
-		return 0;
-
-	smp->data.type = SMP_T_STR;
-	smp->flags |= SMP_F_CONST;
-	smp->data.u.str.area = smp->strm->res_cap[idx];
-	smp->data.u.str.data = strlen(smp->strm->res_cap[idx]);
-
-	return 1;
-}
-
-/* Extracts the METHOD in the HTTP request, the txn->uri should be filled before the call */
-static int
-smp_fetch_capture_req_method(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct buffer *temp;
-	struct http_txn *txn = smp->strm->txn;
-	char *ptr;
-
-	if (!txn || !txn->uri)
-		return 0;
-
-	ptr = txn->uri;
-
-	while (*ptr != ' ' && *ptr != '\0')  /* find first space */
-		ptr++;
-
-	temp = get_trash_chunk();
-	temp->area = txn->uri;
-	temp->data = ptr - txn->uri;
-	smp->data.u.str = *temp;
-	smp->data.type = SMP_T_STR;
-	smp->flags = SMP_F_CONST;
-
-	return 1;
-
-}
-
-/* Extracts the path in the HTTP request, the txn->uri should be filled before the call  */
-static int
-smp_fetch_capture_req_uri(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn = smp->strm->txn;
-	struct ist path;
-	const char *ptr;
-
-	if (!txn || !txn->uri)
-		return 0;
-
-	ptr = txn->uri;
-
-	while (*ptr != ' ' && *ptr != '\0')  /* find first space */
-		ptr++;
-
-	if (!*ptr)
-		return 0;
-
-	ptr++;  /* skip the space */
-
-	path = http_get_path(ist(ptr));
-	if (!path.ptr)
-		return 0;
-
-	smp->data.u.str.area = path.ptr;
-	smp->data.u.str.data = path.len;
-	smp->data.type = SMP_T_STR;
-	smp->flags = SMP_F_CONST;
-
-	return 1;
-}
-
-/* Retrieves the HTTP version from the request (either 1.0 or 1.1) and emits it
- * as a string (either "HTTP/1.0" or "HTTP/1.1").
- */
-static int
-smp_fetch_capture_req_ver(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn = smp->strm->txn;
-
-	if (!txn || txn->req.msg_state < HTTP_MSG_HDR_FIRST)
-		return 0;
-
-	if (txn->req.flags & HTTP_MSGF_VER_11)
-		smp->data.u.str.area = "HTTP/1.1";
-	else
-		smp->data.u.str.area = "HTTP/1.0";
-
-	smp->data.u.str.data = 8;
-	smp->data.type  = SMP_T_STR;
-	smp->flags = SMP_F_CONST;
-	return 1;
-
-}
-
-/* Retrieves the HTTP version from the response (either 1.0 or 1.1) and emits it
- * as a string (either "HTTP/1.0" or "HTTP/1.1").
- */
-static int
-smp_fetch_capture_res_ver(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn = smp->strm->txn;
-
-	if (!txn || txn->rsp.msg_state < HTTP_MSG_HDR_FIRST)
-		return 0;
-
-	if (txn->rsp.flags & HTTP_MSGF_VER_11)
-		smp->data.u.str.area = "HTTP/1.1";
-	else
-		smp->data.u.str.area = "HTTP/1.0";
-
-	smp->data.u.str.data = 8;
-	smp->data.type  = SMP_T_STR;
-	smp->flags = SMP_F_CONST;
-	return 1;
-
-}
-
-
-/* Iterate over all cookies present in a message. The context is stored in
- * smp->ctx.a[0] for the in-header position, smp->ctx.a[1] for the
- * end-of-header-value, and smp->ctx.a[2] for the hdr_ctx. Depending on
- * the direction, multiple cookies may be parsed on the same line or not.
- * The cookie name is in args and the name length in args->data.str.len.
- * Accepts exactly 1 argument of type string. If the input options indicate
- * that no iterating is desired, then only last value is fetched if any.
- * The returned sample is of type CSTR. Can be used to parse cookies in other
- * files.
- */
-int smp_fetch_cookie(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn;
-	struct hdr_idx *idx;
-	struct hdr_ctx *ctx = smp->ctx.a[2];
-	const struct http_msg *msg;
-	const char *hdr_name;
-	int hdr_name_len;
-	char *sol;
-	int occ = 0;
-	int found = 0;
-
-	if (!args || args->type != ARGT_STR)
-		return 0;
-
-	if (!ctx) {
-		/* first call */
-		ctx = &static_hdr_ctx;
-		ctx->idx = 0;
-		smp->ctx.a[2] = ctx;
-	}
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	idx = &smp->strm->txn->hdr_idx;
-
-	if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) {
-		msg = &txn->req;
-		hdr_name = "Cookie";
-		hdr_name_len = 6;
-	} else {
-		msg = &txn->rsp;
-		hdr_name = "Set-Cookie";
-		hdr_name_len = 10;
-	}
-
-	if (!occ && !(smp->opt & SMP_OPT_ITERATE))
-		/* no explicit occurrence and single fetch => last cookie by default */
-		occ = -1;
-
-	/* OK so basically here, either we want only one value and it's the
-	 * last one, or we want to iterate over all of them and we fetch the
-	 * next one.
-	 */
-
-	sol = ci_head(msg->chn);
-	if (!(smp->flags & SMP_F_NOT_LAST)) {
-		/* search for the header from the beginning, we must first initialize
-		 * the search parameters.
-		 */
-		smp->ctx.a[0] = NULL;
-		ctx->idx = 0;
-	}
-
-	smp->flags |= SMP_F_VOL_HDR;
-
-	while (1) {
-		/* Note: smp->ctx.a[0] == NULL every time we need to fetch a new header */
-		if (!smp->ctx.a[0]) {
-			if (!http_find_header2(hdr_name, hdr_name_len, sol, idx, ctx))
-				goto out;
-
-			if (ctx->vlen < args->data.str.data + 1)
-				continue;
-
-			smp->ctx.a[0] = ctx->line + ctx->val;
-			smp->ctx.a[1] = smp->ctx.a[0] + ctx->vlen;
-		}
-
-		smp->data.type = SMP_T_STR;
-		smp->flags |= SMP_F_CONST;
-		smp->ctx.a[0] = http_extract_cookie_value(smp->ctx.a[0], smp->ctx.a[1],
-		                                         args->data.str.area, args->data.str.data,
-		                                         (smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ,
-		                                         &smp->data.u.str.area, &smp->data.u.str.data);
-		if (smp->ctx.a[0]) {
-			found = 1;
-			if (occ >= 0) {
-				/* one value was returned into smp->data.u.str.{str,len} */
-				smp->flags |= SMP_F_NOT_LAST;
-				return 1;
-			}
-		}
-		/* if we're looking for last occurrence, let's loop */
-	}
-	/* all cookie headers and values were scanned. If we're looking for the
-	 * last occurrence, we may return it now.
-	 */
- out:
-	smp->flags &= ~SMP_F_NOT_LAST;
-	return found;
-}
-
-/* Iterate over all cookies present in a request to count how many occurrences
- * match the name in args and args->data.str.len. If <multi> is non-null, then
- * multiple cookies may be parsed on the same line. The returned sample is of
- * type UINT. Accepts exactly 1 argument of type string.
- */
-static int
-smp_fetch_cookie_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn;
-	struct hdr_idx *idx;
-	struct hdr_ctx ctx;
-	const struct http_msg *msg;
-	const char *hdr_name;
-	int hdr_name_len;
-	int cnt;
-	char *val_beg, *val_end;
-	char *sol;
-
-	if (!args || args->type != ARGT_STR)
-		return 0;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	idx = &smp->strm->txn->hdr_idx;
-
-	if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) {
-		msg = &txn->req;
-		hdr_name = "Cookie";
-		hdr_name_len = 6;
-	} else {
-		msg = &txn->rsp;
-		hdr_name = "Set-Cookie";
-		hdr_name_len = 10;
-	}
-
-	sol = ci_head(msg->chn);
-	val_end = val_beg = NULL;
-	ctx.idx = 0;
-	cnt = 0;
-
-	while (1) {
-		/* Note: val_beg == NULL every time we need to fetch a new header */
-		if (!val_beg) {
-			if (!http_find_header2(hdr_name, hdr_name_len, sol, idx, &ctx))
-				break;
-
-			if (ctx.vlen < args->data.str.data + 1)
-				continue;
-
-			val_beg = ctx.line + ctx.val;
-			val_end = val_beg + ctx.vlen;
-		}
-
-		smp->data.type = SMP_T_STR;
-		smp->flags |= SMP_F_CONST;
-		while ((val_beg = http_extract_cookie_value(val_beg, val_end,
-		                                            args->data.str.area, args->data.str.data,
-		                                            (smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ,
-		                                            &smp->data.u.str.area, &smp->data.u.str.data))) {
-			cnt++;
-		}
-	}
-
-	smp->data.type = SMP_T_SINT;
-	smp->data.u.sint = cnt;
-	smp->flags |= SMP_F_VOL_HDR;
-	return 1;
-}
-
-/* Fetch an cookie's integer value. The integer value is returned. It
- * takes a mandatory argument of type string. It relies on smp_fetch_cookie().
- */
-static int
-smp_fetch_cookie_val(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	int ret = smp_fetch_cookie(args, smp, kw, private);
-
-	if (ret > 0) {
-		smp->data.type = SMP_T_SINT;
-		smp->data.u.sint = strl2ic(smp->data.u.str.area,
-					   smp->data.u.str.data);
-	}
-
-	return ret;
-}
-
-/************************************************************************/
-/*           The code below is dedicated to sample fetches              */
-/************************************************************************/
-
-/* This scans a URL-encoded query string. It takes an optionally wrapping
- * string whose first contigous chunk has its beginning in ctx->a[0] and end
- * in ctx->a[1], and the optional second part in (ctx->a[2]..ctx->a[3]). The
- * pointers are updated for next iteration before leaving.
- */
-static int
-smp_fetch_param(char delim, const char *name, int name_len, const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	const char *vstart, *vend;
-	struct buffer *temp;
-	const char **chunks = (const char **)smp->ctx.a;
-
-	if (!http_find_next_url_param(chunks, name, name_len,
-	                         &vstart, &vend, delim))
-		return 0;
-
-	/* Create sample. If the value is contiguous, return the pointer as CONST,
-	 * if the value is wrapped, copy-it in a buffer.
-	 */
-	smp->data.type = SMP_T_STR;
-	if (chunks[2] &&
-	    vstart >= chunks[0] && vstart <= chunks[1] &&
-	    vend >= chunks[2] && vend <= chunks[3]) {
-		/* Wrapped case. */
-		temp = get_trash_chunk();
-		memcpy(temp->area, vstart, chunks[1] - vstart);
-		memcpy(temp->area + ( chunks[1] - vstart ), chunks[2],
-		       vend - chunks[2]);
-		smp->data.u.str.area = temp->area;
-		smp->data.u.str.data = ( chunks[1] - vstart ) + ( vend - chunks[2] );
-	} else {
-		/* Contiguous case. */
-		smp->data.u.str.area = (char *)vstart;
-		smp->data.u.str.data = vend - vstart;
-		smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
-	}
-
-	/* Update context, check wrapping. */
-	chunks[0] = vend;
-	if (chunks[2] && vend >= chunks[2] && vend <= chunks[3]) {
-		chunks[1] = chunks[3];
-		chunks[2] = NULL;
-	}
-
-	if (chunks[0] < chunks[1])
-		smp->flags |= SMP_F_NOT_LAST;
-
-	return 1;
-}
-
-/* This function iterates over each parameter of the query string. It uses
- * ctx->a[0] and ctx->a[1] to store the beginning and end of the current
- * parameter. Since it uses smp_fetch_param(), ctx->a[2..3] are both NULL.
- * An optional parameter name is passed in args[0], otherwise any parameter is
- * considered. It supports an optional delimiter argument for the beginning of
- * the string in args[1], which defaults to "?".
- */
-static int
-smp_fetch_url_param(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_msg *msg;
-	char delim = '?';
-	const char *name;
-	int name_len;
-
-	if (!args ||
-	    (args[0].type && args[0].type != ARGT_STR) ||
-	    (args[1].type && args[1].type != ARGT_STR))
-		return 0;
-
-	name = "";
-	name_len = 0;
-	if (args->type == ARGT_STR) {
-		name     = args->data.str.area;
-		name_len = args->data.str.data;
-	}
-
-	if (args[1].type)
-		delim = *args[1].data.str.area;
-
-	if (!smp->ctx.a[0]) { // first call, find the query string
-		CHECK_HTTP_MESSAGE_FIRST();
-
-		msg = &smp->strm->txn->req;
-
-		smp->ctx.a[0] = http_find_param_list(ci_head(msg->chn) + msg->sl.rq.u,
-		                                     msg->sl.rq.u_l, delim);
-		if (!smp->ctx.a[0])
-			return 0;
-
-		smp->ctx.a[1] = ci_head(msg->chn) + msg->sl.rq.u + msg->sl.rq.u_l;
-
-		/* Assume that the context is filled with NULL pointer
-		 * before the first call.
-		 * smp->ctx.a[2] = NULL;
-		 * smp->ctx.a[3] = NULL;
-		 */
-	}
-
-	return smp_fetch_param(delim, name, name_len, args, smp, kw, private);
-}
-
-/* This function iterates over each parameter of the body. This requires
- * that the body has been waited for using http-buffer-request. It uses
- * ctx->a[0] and ctx->a[1] to store the beginning and end of the first
- * contigous part of the body, and optionally ctx->a[2..3] to reference the
- * optional second part if the body wraps at the end of the buffer. An optional
- * parameter name is passed in args[0], otherwise any parameter is considered.
- */
-static int
-smp_fetch_body_param(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_msg *msg;
-	unsigned long len;
-	unsigned long block1;
-	char *body;
-	const char *name;
-	int name_len;
-
-	if (!args || (args[0].type && args[0].type != ARGT_STR))
-		return 0;
-
-	name = "";
-	name_len = 0;
-	if (args[0].type == ARGT_STR) {
-		name     = args[0].data.str.area;
-		name_len = args[0].data.str.data;
-	}
-
-	if (!smp->ctx.a[0]) { // first call, find the query string
-		CHECK_HTTP_MESSAGE_FIRST();
-
-		if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ)
-			msg = &smp->strm->txn->req;
-		else
-			msg = &smp->strm->txn->rsp;
-
-		len  = http_body_bytes(msg);
-		body = c_ptr(msg->chn, -http_data_rewind(msg));
-
-		block1 = len;
-		if (block1 > b_wrap(&msg->chn->buf) - body)
-			block1 = b_wrap(&msg->chn->buf) - body;
-
-		if (block1 == len) {
-			/* buffer is not wrapped (or empty) */
-			smp->ctx.a[0] = body;
-			smp->ctx.a[1] = body + len;
-
-			/* Assume that the context is filled with NULL pointer
-			 * before the first call.
-			 * smp->ctx.a[2] = NULL;
-			 * smp->ctx.a[3] = NULL;
-			*/
-		}
-		else {
-			/* buffer is wrapped, we need to defragment it */
-			smp->ctx.a[0] = body;
-			smp->ctx.a[1] = body + block1;
-			smp->ctx.a[2] = b_orig(&msg->chn->buf);
-			smp->ctx.a[3] = b_orig(&msg->chn->buf) + ( len - block1 );
-		}
-	}
-	return smp_fetch_param('&', name, name_len, args, smp, kw, private);
-}
-
-/* Return the signed integer value for the specified url parameter (see url_param
- * above).
- */
-static int
-smp_fetch_url_param_val(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	int ret = smp_fetch_url_param(args, smp, kw, private);
-
-	if (ret > 0) {
-		smp->data.type = SMP_T_SINT;
-		smp->data.u.sint = strl2ic(smp->data.u.str.area,
-					   smp->data.u.str.data);
-	}
-
-	return ret;
-}
-
-/* This produces a 32-bit hash of the concatenation of the first occurrence of
- * the Host header followed by the path component if it begins with a slash ('/').
- * This means that '*' will not be added, resulting in exactly the first Host
- * entry. If no Host header is found, then the path is used. The resulting value
- * is hashed using the url hash followed by a full avalanche hash and provides a
- * 32-bit integer value. This fetch is useful for tracking per-URL activity on
- * high-traffic sites without having to store whole paths.
- * this differs from the base32 functions in that it includes the url parameters
- * as well as the path
- */
-static int
-smp_fetch_url32(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct http_txn *txn;
-	struct hdr_ctx ctx;
-	unsigned int hash = 0;
-	char *ptr, *beg, *end;
-	int len;
-
-	CHECK_HTTP_MESSAGE_FIRST();
-
-	txn = smp->strm->txn;
-	ctx.idx = 0;
-	if (http_find_header2("Host", 4, ci_head(txn->req.chn), &txn->hdr_idx, &ctx)) {
-		/* OK we have the header value in ctx.line+ctx.val for ctx.vlen bytes */
-		ptr = ctx.line + ctx.val;
-		len = ctx.vlen;
-		while (len--)
-			hash = *(ptr++) + (hash << 6) + (hash << 16) - hash;
-	}
-
-	/* now retrieve the path */
-	end = ci_head(txn->req.chn) + txn->req.sl.rq.u + txn->req.sl.rq.u_l;
-	beg = http_txn_get_path(txn);
-	if (!beg)
-		beg = end;
-
-	for (ptr = beg; ptr < end ; ptr++);
-
-	if (beg < ptr && *beg == '/') {
-		while (beg < ptr)
-			hash = *(beg++) + (hash << 6) + (hash << 16) - hash;
-	}
-	hash = full_hash(hash);
-
-	smp->data.type = SMP_T_SINT;
-	smp->data.u.sint = hash;
-	smp->flags = SMP_F_VOL_1ST;
-	return 1;
-}
-
-/* This concatenates the source address with the 32-bit hash of the Host and
- * URL as returned by smp_fetch_base32(). The idea is to have per-source and
- * per-url counters. The result is a binary block from 8 to 20 bytes depending
- * on the source address length. The URL hash is stored before the address so
- * that in environments where IPv6 is insignificant, truncating the output to
- * 8 bytes would still work.
- */
-static int
-smp_fetch_url32_src(const struct arg *args, struct sample *smp, const char *kw, void *private)
-{
-	struct buffer *temp;
-	struct connection *cli_conn = objt_conn(smp->sess->origin);
-
-	if (!cli_conn)
-		return 0;
-
-	if (!smp_fetch_url32(args, smp, kw, private))
-		return 0;
-
-	temp = get_trash_chunk();
-	*(unsigned int *) temp->area = htonl(smp->data.u.sint);
-	temp->data += sizeof(unsigned int);
-
-	switch (cli_conn->addr.from.ss_family) {
-	case AF_INET:
-		memcpy(temp->area + temp->data,
-		       &((struct sockaddr_in *)&cli_conn->addr.from)->sin_addr,
-		       4);
-		temp->data += 4;
-		break;
-	case AF_INET6:
-		memcpy(temp->area + temp->data,
-		       &((struct sockaddr_in6 *)&cli_conn->addr.from)->sin6_addr,
-		       16);
-		temp->data += 16;
-		break;
-	default:
-		return 0;
-	}
-
-	smp->data.u.str = *temp;
-	smp->data.type = SMP_T_BIN;
-	return 1;
-}
-
-/* This function is used to validate the arguments passed to any "hdr" fetch
- * keyword. These keywords support an optional positive or negative occurrence
- * number. We must ensure that the number is greater than -MAX_HDR_HISTORY. It
- * is assumed that the types are already the correct ones. Returns 0 on error,
- * non-zero if OK. If <err> is not NULL, it will be filled with a pointer to an
- * error message in case of error, that the caller is responsible for freeing.
- * The initial location must either be freeable or NULL.
- */
-int val_hdr(struct arg *arg, char **err_msg)
-{
-	if (arg && arg[1].type == ARGT_SINT && arg[1].data.sint < -MAX_HDR_HISTORY) {
-		memprintf(err_msg, "header occurrence must be >= %d", -MAX_HDR_HISTORY);
-		return 0;
-	}
-	return 1;
-}
-
-/* takes an UINT value on input supposed to represent the time since EPOCH,
- * adds an optional offset found in args[0] and emits a string representing
- * the date in RFC-1123/5322 format.
- */
-static int sample_conv_http_date(const struct arg *args, struct sample *smp, void *private)
-{
-	const char day[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
-	const char mon[12][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
-	struct buffer *temp;
-	struct tm *tm;
-	/* With high numbers, the date returned can be negative, the 55 bits mask prevent this. */
-	time_t curr_date = smp->data.u.sint & 0x007fffffffffffffLL;
-
-	/* add offset */
-	if (args && (args[0].type == ARGT_SINT))
-		curr_date += args[0].data.sint;
-
-	tm = gmtime(&curr_date);
-	if (!tm)
-		return 0;
-
-	temp = get_trash_chunk();
-	temp->data = snprintf(temp->area, temp->size - temp->data,
-			      "%s, %02d %s %04d %02d:%02d:%02d GMT",
-			      day[tm->tm_wday], tm->tm_mday, mon[tm->tm_mon],
-			      1900+tm->tm_year,
-			      tm->tm_hour, tm->tm_min, tm->tm_sec);
-
-	smp->data.u.str = *temp;
-	smp->data.type = SMP_T_STR;
-	return 1;
-}
-
-/* Match language range with language tag. RFC2616 14.4:
- *
- *    A language-range matches a language-tag if it exactly equals
- *    the tag, or if it exactly equals a prefix of the tag such
- *    that the first tag character following the prefix is "-".
- *
- * Return 1 if the strings match, else return 0.
- */
-static inline int language_range_match(const char *range, int range_len,
-                                       const char *tag, int tag_len)
-{
-	const char *end = range + range_len;
-	const char *tend = tag + tag_len;
-	while (range < end) {
-		if (*range == '-' && tag == tend)
-			return 1;
-		if (*range != *tag || tag == tend)
-			return 0;
-		range++;
-		tag++;
-	}
-	/* Return true only if the last char of the tag is matched. */
-	return tag == tend;
-}
-
-/* Arguments: The list of expected value, the number of parts returned and the separator */
-static int sample_conv_q_prefered(const struct arg *args, struct sample *smp, void *private)
-{
-	const char *al = smp->data.u.str.area;
-	const char *end = al + smp->data.u.str.data;
-	const char *token;
-	int toklen;
-	int qvalue;
-	const char *str;
-	const char *w;
-	int best_q = 0;
-
-	/* Set the constant to the sample, because the output of the
-	 * function will be peek in the constant configuration string.
-	 */
-	smp->flags |= SMP_F_CONST;
-	smp->data.u.str.size = 0;
-	smp->data.u.str.area = "";
-	smp->data.u.str.data = 0;
-
-	/* Parse the accept language */
-	while (1) {
-
-		/* Jump spaces, quit if the end is detected. */
-		while (al < end && isspace((unsigned char)*al))
-			al++;
-		if (al >= end)
-			break;
-
-		/* Start of the fisrt word. */
-		token = al;
-
-		/* Look for separator: isspace(), ',' or ';'. Next value if 0 length word. */
-		while (al < end && *al != ';' && *al != ',' && !isspace((unsigned char)*al))
-			al++;
-		if (al == token)
-			goto expect_comma;
-
-		/* Length of the token. */
-		toklen = al - token;
-		qvalue = 1000;
-
-		/* Check if the token exists in the list. If the token not exists,
-		 * jump to the next token.
-		 */
-		str = args[0].data.str.area;
-		w = str;
-		while (1) {
-			if (*str == ';' || *str == '\0') {
-				if (language_range_match(token, toklen, w, str-w))
-					goto look_for_q;
-				if (*str == '\0')
-					goto expect_comma;
-				w = str + 1;
-			}
-			str++;
-		}
-		goto expect_comma;
-
-look_for_q:
-
-		/* Jump spaces, quit if the end is detected. */
-		while (al < end && isspace((unsigned char)*al))
-			al++;
-		if (al >= end)
-			goto process_value;
-
-		/* If ',' is found, process the result */
-		if (*al == ',')
-			goto process_value;
-
-		/* If the character is different from ';', look
-		 * for the end of the header part in best effort.
-		 */
-		if (*al != ';')
-			goto expect_comma;
-
-		/* Assumes that the char is ';', now expect "q=". */
-		al++;
-
-		/* Jump spaces, process value if the end is detected. */
-		while (al < end && isspace((unsigned char)*al))
-			al++;
-		if (al >= end)
-			goto process_value;
-
-		/* Expect 'q'. If no 'q', continue in best effort */
-		if (*al != 'q')
-			goto process_value;
-		al++;
-
-		/* Jump spaces, process value if the end is detected. */
-		while (al < end && isspace((unsigned char)*al))
-			al++;
-		if (al >= end)
-			goto process_value;
-
-		/* Expect '='. If no '=', continue in best effort */
-		if (*al != '=')
-			goto process_value;
-		al++;
-
-		/* Jump spaces, process value if the end is detected. */
-		while (al < end && isspace((unsigned char)*al))
-			al++;
-		if (al >= end)
-			goto process_value;
-
-		/* Parse the q value. */
-		qvalue = http_parse_qvalue(al, &al);
-
-process_value:
-
-		/* If the new q value is the best q value, then store the associated
-		 * language in the response. If qvalue is the biggest value (1000),
-		 * break the process.
-		 */
-		if (qvalue > best_q) {
-			smp->data.u.str.area = (char *)w;
-			smp->data.u.str.data = str - w;
-			if (qvalue >= 1000)
-				break;
-			best_q = qvalue;
-		}
-
-expect_comma:
-
-		/* Expect comma or end. If the end is detected, quit the loop. */
-		while (al < end && *al != ',')
-			al++;
-		if (al >= end)
-			break;
-
-		/* Comma is found, jump it and restart the analyzer. */
-		al++;
-	}
-
-	/* Set default value if required. */
-	if (smp->data.u.str.data == 0 && args[1].type == ARGT_STR) {
-		smp->data.u.str.area = args[1].data.str.area;
-		smp->data.u.str.data = args[1].data.str.data;
-	}
-
-	/* Return true only if a matching language was found. */
-	return smp->data.u.str.data != 0;
-}
-
-/* This fetch url-decode any input string. */
-static int sample_conv_url_dec(const struct arg *args, struct sample *smp, void *private)
-{
-	int len;
-
-	/* If the constant flag is set or if not size is avalaible at
-	 * the end of the buffer, copy the string in other buffer
-	  * before decoding.
-	 */
-	if (smp->flags & SMP_F_CONST || smp->data.u.str.size <= smp->data.u.str.data) {
-		struct buffer *str = get_trash_chunk();
-		memcpy(str->area, smp->data.u.str.area, smp->data.u.str.data);
-		smp->data.u.str.area = str->area;
-		smp->data.u.str.size = str->size;
-		smp->flags &= ~SMP_F_CONST;
-	}
-
-	/* Add final \0 required by url_decode(), and convert the input string. */
-	smp->data.u.str.area[smp->data.u.str.data] = '\0';
-	len = url_decode(smp->data.u.str.area);
-	if (len < 0)
-		return 0;
-	smp->data.u.str.data = len;
-	return 1;
-}
-
-static int smp_conv_req_capture(const struct arg *args, struct sample *smp, void *private)
-{
-	struct proxy *fe = strm_fe(smp->strm);
-	int idx, i;
-	struct cap_hdr *hdr;
-	int len;
-
-	if (!args || args->type != ARGT_SINT)
-		return 0;
-
-	idx = args->data.sint;
-
-	/* Check the availibity of the capture id. */
-	if (idx > fe->nb_req_cap - 1)
-		return 0;
-
-	/* Look for the original configuration. */
-	for (hdr = fe->req_cap, i = fe->nb_req_cap - 1;
-	     hdr != NULL && i != idx ;
-	     i--, hdr = hdr->next);
-	if (!hdr)
-		return 0;
-
-	/* check for the memory allocation */
-	if (smp->strm->req_cap[hdr->index] == NULL)
-		smp->strm->req_cap[hdr->index] = pool_alloc(hdr->pool);
-	if (smp->strm->req_cap[hdr->index] == NULL)
-		return 0;
-
-	/* Check length. */
-	len = smp->data.u.str.data;
-	if (len > hdr->len)
-		len = hdr->len;
-
-	/* Capture input data. */
-	memcpy(smp->strm->req_cap[idx], smp->data.u.str.area, len);
-	smp->strm->req_cap[idx][len] = '\0';
-
-	return 1;
-}
-
-static int smp_conv_res_capture(const struct arg *args, struct sample *smp, void *private)
-{
-	struct proxy *fe = strm_fe(smp->strm);
-	int idx, i;
-	struct cap_hdr *hdr;
-	int len;
-
-	if (!args || args->type != ARGT_SINT)
-		return 0;
-
-	idx = args->data.sint;
-
-	/* Check the availibity of the capture id. */
-	if (idx > fe->nb_rsp_cap - 1)
-		return 0;
-
-	/* Look for the original configuration. */
-	for (hdr = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
-	     hdr != NULL && i != idx ;
-	     i--, hdr = hdr->next);
-	if (!hdr)
-		return 0;
-
-	/* check for the memory allocation */
-	if (smp->strm->res_cap[hdr->index] == NULL)
-		smp->strm->res_cap[hdr->index] = pool_alloc(hdr->pool);
-	if (smp->strm->res_cap[hdr->index] == NULL)
-		return 0;
-
-	/* Check length. */
-	len = smp->data.u.str.data;
-	if (len > hdr->len)
-		len = hdr->len;
-
-	/* Capture input data. */
-	memcpy(smp->strm->res_cap[idx], smp->data.u.str.area, len);
-	smp->strm->res_cap[idx][len] = '\0';
-
-	return 1;
-}
-
-/* This function executes one of the set-{method,path,query,uri} actions. It
- * takes the string from the variable 'replace' with length 'len', then modifies
- * the relevant part of the request line accordingly. Then it updates various
- * pointers to the next elements which were moved, and the total buffer length.
- * It finds the action to be performed in p[2], previously filled by function
- * parse_set_req_line(). It returns 0 in case of success, -1 in case of internal
- * error, though this can be revisited when this code is finally exploited.
- *
- * 'action' can be '0' to replace method, '1' to replace path, '2' to replace
- * query string and 3 to replace uri.
- *
- * In query string case, the mark question '?' must be set at the start of the
- * string by the caller, event if the replacement query string is empty.
- */
-int http_replace_req_line(int action, const char *replace, int len,
-                          struct proxy *px, struct stream *s)
-{
-	struct http_txn *txn = s->txn;
-	char *cur_ptr, *cur_end;
-	int offset = 0;
-	int delta;
-
-	switch (action) {
-	case 0: // method
-		cur_ptr = ci_head(&s->req);
-		cur_end = cur_ptr + txn->req.sl.rq.m_l;
-
-		/* adjust req line offsets and lengths */
-		delta = len - offset - (cur_end - cur_ptr);
-		txn->req.sl.rq.m_l += delta;
-		txn->req.sl.rq.u   += delta;
-		txn->req.sl.rq.v   += delta;
-		break;
-
-	case 1: // path
-		cur_ptr = http_txn_get_path(txn);
-		if (!cur_ptr)
-			cur_ptr = ci_head(&s->req) + txn->req.sl.rq.u;
-
-		cur_end = cur_ptr;
-		while (cur_end < ci_head(&s->req) + txn->req.sl.rq.u + txn->req.sl.rq.u_l && *cur_end != '?')
-			cur_end++;
-
-		/* adjust req line offsets and lengths */
-		delta = len - offset - (cur_end - cur_ptr);
-		txn->req.sl.rq.u_l += delta;
-		txn->req.sl.rq.v   += delta;
-		break;
-
-	case 2: // query
-		offset = 1;
-		cur_ptr = ci_head(&s->req) + txn->req.sl.rq.u;
-		cur_end = cur_ptr + txn->req.sl.rq.u_l;
-		while (cur_ptr < cur_end && *cur_ptr != '?')
-			cur_ptr++;
-
-		/* skip the question mark or indicate that we must insert it
-		 * (but only if the format string is not empty then).
-		 */
-		if (cur_ptr < cur_end)
-			cur_ptr++;
-		else if (len > 1)
-			offset = 0;
-
-		/* adjust req line offsets and lengths */
-		delta = len - offset - (cur_end - cur_ptr);
-		txn->req.sl.rq.u_l += delta;
-		txn->req.sl.rq.v   += delta;
-		break;
-
-	case 3: // uri
-		cur_ptr = ci_head(&s->req) + txn->req.sl.rq.u;
-		cur_end = cur_ptr + txn->req.sl.rq.u_l;
-
-		/* adjust req line offsets and lengths */
-		delta = len - offset - (cur_end - cur_ptr);
-		txn->req.sl.rq.u_l += delta;
-		txn->req.sl.rq.v   += delta;
-		break;
-
-	default:
-		return -1;
-	}
-
-	/* commit changes and adjust end of message */
-	delta = b_rep_blk(&s->req.buf, cur_ptr, cur_end, replace + offset, len - offset);
-	txn->req.sl.rq.l += delta;
-	txn->hdr_idx.v[0].len += delta;
-	http_msg_move_end(&txn->req, delta);
-	return 0;
-}
-
-/* This function replace the HTTP status code and the associated message. The
- * variable <status> contains the new status code. This function never fails.
- */
-void http_set_status(unsigned int status, const char *reason, struct stream *s)
-{
-	struct http_txn *txn = s->txn;
-	char *cur_ptr, *cur_end;
-	int delta;
-	char *res;
-	int c_l;
-	const char *msg = reason;
-	int msg_len;
-
-	chunk_reset(&trash);
-
-	res = ultoa_o(status, trash.area, trash.size);
-	c_l = res - trash.area;
-
-	trash.area[c_l] = ' ';
-	trash.data = c_l + 1;
-
-	/* Do we have a custom reason format string? */
-	if (msg == NULL)
-		msg = http_get_reason(status);
-	msg_len = strlen(msg);
-	strncpy(&trash.area[trash.data], msg, trash.size - trash.data);
-	trash.data += msg_len;
-
-	cur_ptr = ci_head(&s->res) + txn->rsp.sl.st.c;
-	cur_end = ci_head(&s->res) + txn->rsp.sl.st.r + txn->rsp.sl.st.r_l;
-
-	/* commit changes and adjust message */
-	delta = b_rep_blk(&s->res.buf, cur_ptr, cur_end, trash.area,
-			  trash.data);
-
-	/* adjust res line offsets and lengths */
-	txn->rsp.sl.st.r += c_l - txn->rsp.sl.st.c_l;
-	txn->rsp.sl.st.c_l = c_l;
-	txn->rsp.sl.st.r_l = msg_len;
-
-	delta = trash.data - (cur_end - cur_ptr);
-	txn->rsp.sl.st.l += delta;
-	txn->hdr_idx.v[0].len += delta;
-	http_msg_move_end(&txn->rsp, delta);
-}
-
-/* This function executes one of the set-{method,path,query,uri} actions. It
- * builds a string in the trash from the specified format string. It finds
- * the action to be performed in <http.action>, previously filled by function
- * parse_set_req_line(). The replacement action is excuted by the function
- * http_action_set_req_line(). It always returns ACT_RET_CONT. If an error
- * occurs the action is canceled, but the rule processing continue.
- */
-enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px,
-                                         struct session *sess, struct stream *s, int flags)
-{
-	struct buffer *replace;
-	enum act_return ret = ACT_RET_ERR;
-
-	replace = alloc_trash_chunk();
-	if (!replace)
-		goto leave;
-
-	/* If we have to create a query string, prepare a '?'. */
-	if (rule->arg.http.action == 2)
-		replace->area[replace->data++] = '?';
-	replace->data += build_logline(s, replace->area + replace->data,
-				       replace->size - replace->data,
-				       &rule->arg.http.logfmt);
-
-	http_replace_req_line(rule->arg.http.action, replace->area,
-			      replace->data, px, s);
-
-	ret = ACT_RET_CONT;
-
-leave:
-	free_trash_chunk(replace);
-	return ret;
-}
-
-/* This function is just a compliant action wrapper for "set-status". */
-enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px,
-                                       struct session *sess, struct stream *s, int flags)
-{
-	http_set_status(rule->arg.status.code, rule->arg.status.reason, s);
-	return ACT_RET_CONT;
-}
-
-/* parse an http-request action among :
- *   set-method
- *   set-path
- *   set-query
- *   set-uri
- *
- * All of them accept a single argument of type string representing a log-format.
- * The resulting rule makes use of arg->act.p[0..1] to store the log-format list
- * head, and p[2] to store the action as an int (0=method, 1=path, 2=query, 3=uri).
- * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
- */
-enum act_parse_ret parse_set_req_line(const char **args, int *orig_arg, struct proxy *px,
-                                      struct act_rule *rule, char **err)
-{
-	int cur_arg = *orig_arg;
-
-	rule->action = ACT_CUSTOM;
-
-	switch (args[0][4]) {
-	case 'm' :
-		rule->arg.http.action = 0;
-		rule->action_ptr = http_action_set_req_line;
-		break;
-	case 'p' :
-		rule->arg.http.action = 1;
-		rule->action_ptr = http_action_set_req_line;
-		break;
-	case 'q' :
-		rule->arg.http.action = 2;
-		rule->action_ptr = http_action_set_req_line;
-		break;
-	case 'u' :
-		rule->arg.http.action = 3;
-		rule->action_ptr = http_action_set_req_line;
-		break;
-	default:
-		memprintf(err, "internal error: unhandled action '%s'", args[0]);
-		return ACT_RET_PRS_ERR;
-	}
-
-	if (!*args[cur_arg] ||
-	    (*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) {
-		memprintf(err, "expects exactly 1 argument <format>");
-		return ACT_RET_PRS_ERR;
-	}
-
-	LIST_INIT(&rule->arg.http.logfmt);
-	px->conf.args.ctx = ARGC_HRQ;
-	if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.logfmt, LOG_OPT_HTTP,
-	                            (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) {
-		return ACT_RET_PRS_ERR;
-	}
-
-	(*orig_arg)++;
-	return ACT_RET_PRS_OK;
-}
-
-/* parse set-status action:
- * This action accepts a single argument of type int representing
- * an http status code. It returns ACT_RET_PRS_OK on success,
- * ACT_RET_PRS_ERR on error.
- */
-enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struct proxy *px,
-                                         struct act_rule *rule, char **err)
-{
-	char *error;
-
-	rule->action = ACT_CUSTOM;
-	rule->action_ptr = action_http_set_status;
-
-	/* Check if an argument is available */
-	if (!*args[*orig_arg]) {
-		memprintf(err, "expects 1 argument: <status>; or 3 arguments: <status> reason <fmt>");
-		return ACT_RET_PRS_ERR;
-	}
-
-	/* convert status code as integer */
-	rule->arg.status.code = strtol(args[*orig_arg], &error, 10);
-	if (*error != '\0' || rule->arg.status.code < 100 || rule->arg.status.code > 999) {
-		memprintf(err, "expects an integer status code between 100 and 999");
-		return ACT_RET_PRS_ERR;
-	}
-
-	(*orig_arg)++;
-
-	/* set custom reason string */
-	rule->arg.status.reason = NULL; // If null, we use the default reason for the status code.
-	if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 &&
-	    (*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) {
-		(*orig_arg)++;
-		rule->arg.status.reason = strdup(args[*orig_arg]);
-		(*orig_arg)++;
-	}
-
-	return ACT_RET_PRS_OK;
-}
-
-/* This function executes the "reject" HTTP action. It clears the request and
- * response buffer without sending any response. It can be useful as an HTTP
- * alternative to the silent-drop action to defend against DoS attacks, and may
- * also be used with HTTP/2 to close a connection instead of just a stream.
- * The txn status is unchanged, indicating no response was sent. The termination
- * flags will indicate "PR". It always returns ACT_RET_STOP.
- */
-enum act_return http_action_reject(struct act_rule *rule, struct proxy *px,
-                                   struct session *sess, struct stream *s, int flags)
-{
-	channel_abort(&s->req);
-	channel_abort(&s->res);
-	s->req.analysers = 0;
-	s->res.analysers = 0;
-
-	HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
-	HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1);
-	if (sess->listener && sess->listener->counters)
-		HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
-
-	if (!(s->flags & SF_ERR_MASK))
-		s->flags |= SF_ERR_PRXCOND;
-	if (!(s->flags & SF_FINST_MASK))
-		s->flags |= SF_FINST_R;
-
-	return ACT_RET_CONT;
-}
-
-/* parse the "reject" action:
- * This action takes no argument and returns ACT_RET_PRS_OK on success,
- * ACT_RET_PRS_ERR on error.
- */
-enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px,
-                                            struct act_rule *rule, char **err)
-{
-	rule->action = ACT_CUSTOM;
-	rule->action_ptr = http_action_reject;
-	return ACT_RET_PRS_OK;
-}
-
-/* This function executes the "capture" action. It executes a fetch expression,
- * turns the result into a string and puts it in a capture slot. It always
- * returns 1. If an error occurs the action is cancelled, but the rule
- * processing continues.
- */
-enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px,
-                                        struct session *sess, struct stream *s, int flags)
-{
-	struct sample *key;
-	struct cap_hdr *h = rule->arg.cap.hdr;
-	char **cap = s->req_cap;
-	int len;
-
-	key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR);
-	if (!key)
-		return ACT_RET_CONT;
-
-	if (cap[h->index] == NULL)
-		cap[h->index] = pool_alloc(h->pool);
-
-	if (cap[h->index] == NULL) /* no more capture memory */
-		return ACT_RET_CONT;
-
-	len = key->data.u.str.data;
-	if (len > h->len)
-		len = h->len;
-
-	memcpy(cap[h->index], key->data.u.str.area, len);
-	cap[h->index][len] = 0;
-	return ACT_RET_CONT;
-}
-
-/* This function executes the "capture" action and store the result in a
- * capture slot if exists. It executes a fetch expression, turns the result
- * into a string and puts it in a capture slot. It always returns 1. If an
- * error occurs the action is cancelled, but the rule processing continues.
- */
-enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px,
-                                              struct session *sess, struct stream *s, int flags)
-{
-	struct sample *key;
-	struct cap_hdr *h;
-	char **cap = s->req_cap;
-	struct proxy *fe = strm_fe(s);
-	int len;
-	int i;
-
-	/* Look for the original configuration. */
-	for (h = fe->req_cap, i = fe->nb_req_cap - 1;
-	     h != NULL && i != rule->arg.capid.idx ;
-	     i--, h = h->next);
-	if (!h)
-		return ACT_RET_CONT;
-
-	key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
-	if (!key)
-		return ACT_RET_CONT;
-
-	if (cap[h->index] == NULL)
-		cap[h->index] = pool_alloc(h->pool);
-
-	if (cap[h->index] == NULL) /* no more capture memory */
-		return ACT_RET_CONT;
-
-	len = key->data.u.str.data;
-	if (len > h->len)
-		len = h->len;
-
-	memcpy(cap[h->index], key->data.u.str.area, len);
-	cap[h->index][len] = 0;
-	return ACT_RET_CONT;
-}
-
-/* Check an "http-request capture" action.
- *
- * The function returns 1 in success case, otherwise, it returns 0 and err is
- * filled.
- */
-int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err)
-{
-	if (rule->action_ptr != http_action_req_capture_by_id)
-		return 1;
-
-	if (rule->arg.capid.idx >= px->nb_req_cap) {
-		memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule",
-			  rule->arg.capid.idx);
-		return 0;
-	}
-
-	return 1;
-}
-
-/* parse an "http-request capture" action. It takes a single argument which is
- * a sample fetch expression. It stores the expression into arg->act.p[0] and
- * the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1].
- * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
- */
-enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px,
-                                          struct act_rule *rule, char **err)
-{
-	struct sample_expr *expr;
-	struct cap_hdr *hdr;
-	int cur_arg;
-	int len = 0;
-
-	for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
-		if (strcmp(args[cur_arg], "if") == 0 ||
-		    strcmp(args[cur_arg], "unless") == 0)
-			break;
-
-	if (cur_arg < *orig_arg + 3) {
-		memprintf(err, "expects <expression> [ 'len' <length> | id <idx> ]");
-		return ACT_RET_PRS_ERR;
-	}
-
-	cur_arg = *orig_arg;
-	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
-	if (!expr)
-		return ACT_RET_PRS_ERR;
-
-	if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) {
-		memprintf(err,
-			  "fetch method '%s' extracts information from '%s', none of which is available here",
-			  args[cur_arg-1], sample_src_names(expr->fetch->use));
-		free(expr);
-		return ACT_RET_PRS_ERR;
-	}
-
-	if (!args[cur_arg] || !*args[cur_arg]) {
-		memprintf(err, "expects 'len or 'id'");
-		free(expr);
-		return ACT_RET_PRS_ERR;
-	}
-
-	if (strcmp(args[cur_arg], "len") == 0) {
-		cur_arg++;
-
-		if (!(px->cap & PR_CAP_FE)) {
-			memprintf(err, "proxy '%s' has no frontend capability", px->id);
-			return ACT_RET_PRS_ERR;
-		}
-
-		px->conf.args.ctx = ARGC_CAP;
-
-		if (!args[cur_arg]) {
-			memprintf(err, "missing length value");
-			free(expr);
-			return ACT_RET_PRS_ERR;
-		}
-		/* we copy the table name for now, it will be resolved later */
-		len = atoi(args[cur_arg]);
-		if (len <= 0) {
-			memprintf(err, "length must be > 0");
-			free(expr);
-			return ACT_RET_PRS_ERR;
-		}
-		cur_arg++;
-
-		if (!len) {
-			memprintf(err, "a positive 'len' argument is mandatory");
-			free(expr);
-			return ACT_RET_PRS_ERR;
-		}
-
-		hdr = calloc(1, sizeof(*hdr));
-		hdr->next = px->req_cap;
-		hdr->name = NULL; /* not a header capture */
-		hdr->namelen = 0;
-		hdr->len = len;
-		hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
-		hdr->index = px->nb_req_cap++;
-
-		px->req_cap = hdr;
-		px->to_log |= LW_REQHDR;
-
-		rule->action       = ACT_CUSTOM;
-		rule->action_ptr   = http_action_req_capture;
-		rule->arg.cap.expr = expr;
-		rule->arg.cap.hdr  = hdr;
-	}
-
-	else if (strcmp(args[cur_arg], "id") == 0) {
-		int id;
-		char *error;
-
-		cur_arg++;
-
-		if (!args[cur_arg]) {
-			memprintf(err, "missing id value");
-			free(expr);
-			return ACT_RET_PRS_ERR;
-		}
-
-		id = strtol(args[cur_arg], &error, 10);
-		if (*error != '\0') {
-			memprintf(err, "cannot parse id '%s'", args[cur_arg]);
-			free(expr);
-			return ACT_RET_PRS_ERR;
-		}
-		cur_arg++;
-
-		px->conf.args.ctx = ARGC_CAP;
-
-		rule->action       = ACT_CUSTOM;
-		rule->action_ptr   = http_action_req_capture_by_id;
-		rule->check_ptr    = check_http_req_capture;
-		rule->arg.capid.expr = expr;
-		rule->arg.capid.idx  = id;
-	}
-
-	else {
-		memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
-		free(expr);
-		return ACT_RET_PRS_ERR;
-	}
-
-	*orig_arg = cur_arg;
-	return ACT_RET_PRS_OK;
-}
-
-/* This function executes the "capture" action and store the result in a
- * capture slot if exists. It executes a fetch expression, turns the result
- * into a string and puts it in a capture slot. It always returns 1. If an
- * error occurs the action is cancelled, but the rule processing continues.
- */
-enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
-                                              struct session *sess, struct stream *s, int flags)
-{
-	struct sample *key;
-	struct cap_hdr *h;
-	char **cap = s->res_cap;
-	struct proxy *fe = strm_fe(s);
-	int len;
-	int i;
-
-	/* Look for the original configuration. */
-	for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
-	     h != NULL && i != rule->arg.capid.idx ;
-	     i--, h = h->next);
-	if (!h)
-		return ACT_RET_CONT;
-
-	key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
-	if (!key)
-		return ACT_RET_CONT;
-
-	if (cap[h->index] == NULL)
-		cap[h->index] = pool_alloc(h->pool);
-
-	if (cap[h->index] == NULL) /* no more capture memory */
-		return ACT_RET_CONT;
-
-	len = key->data.u.str.data;
-	if (len > h->len)
-		len = h->len;
-
-	memcpy(cap[h->index], key->data.u.str.area, len);
-	cap[h->index][len] = 0;
-	return ACT_RET_CONT;
-}
-
-/* Check an "http-response capture" action.
- *
- * The function returns 1 in success case, otherwise, it returns 0 and err is
- * filled.
- */
-int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
-{
-	if (rule->action_ptr != http_action_res_capture_by_id)
-		return 1;
-
-	if (rule->arg.capid.idx >= px->nb_rsp_cap) {
-		memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
-			  rule->arg.capid.idx);
-		return 0;
-	}
-
-	return 1;
-}
-
-/* parse an "http-response capture" action. It takes a single argument which is
- * a sample fetch expression. It stores the expression into arg->act.p[0] and
- * the allocated hdr_cap struct od the preallocated id into arg->act.p[1].
- * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
- */
-enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
-                                          struct act_rule *rule, char **err)
-{
-	struct sample_expr *expr;
-	int cur_arg;
-	int id;
-	char *error;
-
-	for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
-		if (strcmp(args[cur_arg], "if") == 0 ||
-		    strcmp(args[cur_arg], "unless") == 0)
-			break;
-
-	if (cur_arg < *orig_arg + 3) {
-		memprintf(err, "expects <expression> id <idx>");
-		return ACT_RET_PRS_ERR;
-	}
-
-	cur_arg = *orig_arg;
-	expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
-	if (!expr)
-		return ACT_RET_PRS_ERR;
-
-	if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
-		memprintf(err,
-			  "fetch method '%s' extracts information from '%s', none of which is available here",
-			  args[cur_arg-1], sample_src_names(expr->fetch->use));
-		free(expr);
-		return ACT_RET_PRS_ERR;
-	}
-
-	if (!args[cur_arg] || !*args[cur_arg]) {
-		memprintf(err, "expects 'id'");
-		free(expr);
-		return ACT_RET_PRS_ERR;
-	}
-
-	if (strcmp(args[cur_arg], "id") != 0) {
-		memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
-		free(expr);
-		return ACT_RET_PRS_ERR;
-	}
-
-	cur_arg++;
-
-	if (!args[cur_arg]) {
-		memprintf(err, "missing id value");
-		free(expr);
-		return ACT_RET_PRS_ERR;
-	}
-
-	id = strtol(args[cur_arg], &error, 10);
-	if (*error != '\0') {
-		memprintf(err, "cannot parse id '%s'", args[cur_arg]);
-		free(expr);
-		return ACT_RET_PRS_ERR;
-	}
-	cur_arg++;
-
-	px->conf.args.ctx = ARGC_CAP;
-
-	rule->action       = ACT_CUSTOM;
-	rule->action_ptr   = http_action_res_capture_by_id;
-	rule->check_ptr    = check_http_res_capture;
-	rule->arg.capid.expr = expr;
-	rule->arg.capid.idx  = id;
-
-	*orig_arg = cur_arg;
-	return ACT_RET_PRS_OK;
-}
-
-/*
- * Return the struct http_req_action_kw associated to a keyword.
- */
-struct action_kw *action_http_req_custom(const char *kw)
-{
-	return action_lookup(&http_req_keywords.list, kw);
-}
-
-/*
- * Return the struct http_res_action_kw associated to a keyword.
- */
-struct action_kw *action_http_res_custom(const char *kw)
-{
-	return action_lookup(&http_res_keywords.list, kw);
-}
-
-
-/************************************************************************/
-/*          All supported ACL keywords must be declared here.           */
-/************************************************************************/
-
-/* Note: must not be declared <const> as its list will be overwritten.
- * Please take care of keeping this list alphabetically sorted.
- */
-static struct acl_kw_list acl_kws = {ILH, {
-	{ "base",            "base",     PAT_MATCH_STR },
-	{ "base_beg",        "base",     PAT_MATCH_BEG },
-	{ "base_dir",        "base",     PAT_MATCH_DIR },
-	{ "base_dom",        "base",     PAT_MATCH_DOM },
-	{ "base_end",        "base",     PAT_MATCH_END },
-	{ "base_len",        "base",     PAT_MATCH_LEN },
-	{ "base_reg",        "base",     PAT_MATCH_REG },
-	{ "base_sub",        "base",     PAT_MATCH_SUB },
-
-	{ "cook",            "req.cook", PAT_MATCH_STR },
-	{ "cook_beg",        "req.cook", PAT_MATCH_BEG },
-	{ "cook_dir",        "req.cook", PAT_MATCH_DIR },
-	{ "cook_dom",        "req.cook", PAT_MATCH_DOM },
-	{ "cook_end",        "req.cook", PAT_MATCH_END },
-	{ "cook_len",        "req.cook", PAT_MATCH_LEN },
-	{ "cook_reg",        "req.cook", PAT_MATCH_REG },
-	{ "cook_sub",        "req.cook", PAT_MATCH_SUB },
-
-	{ "hdr",             "req.hdr",  PAT_MATCH_STR },
-	{ "hdr_beg",         "req.hdr",  PAT_MATCH_BEG },
-	{ "hdr_dir",         "req.hdr",  PAT_MATCH_DIR },
-	{ "hdr_dom",         "req.hdr",  PAT_MATCH_DOM },
-	{ "hdr_end",         "req.hdr",  PAT_MATCH_END },
-	{ "hdr_len",         "req.hdr",  PAT_MATCH_LEN },
-	{ "hdr_reg",         "req.hdr",  PAT_MATCH_REG },
-	{ "hdr_sub",         "req.hdr",  PAT_MATCH_SUB },
-
-	/* these two declarations uses strings with list storage (in place
-	 * of tree storage). The basic match is PAT_MATCH_STR, but the indexation
-	 * and delete functions are relative to the list management. The parse
-	 * and match method are related to the corresponding fetch methods. This
-	 * is very particular ACL declaration mode.
-	 */
-	{ "http_auth_group", NULL,       PAT_MATCH_STR, NULL,  pat_idx_list_str, pat_del_list_ptr, NULL, pat_match_auth },
-	{ "method",          NULL,       PAT_MATCH_STR, pat_parse_meth, pat_idx_list_str, pat_del_list_ptr, NULL, pat_match_meth },
-
-	{ "path",            "path",     PAT_MATCH_STR },
-	{ "path_beg",        "path",     PAT_MATCH_BEG },
-	{ "path_dir",        "path",     PAT_MATCH_DIR },
-	{ "path_dom",        "path",     PAT_MATCH_DOM },
-	{ "path_end",        "path",     PAT_MATCH_END },
-	{ "path_len",        "path",     PAT_MATCH_LEN },
-	{ "path_reg",        "path",     PAT_MATCH_REG },
-	{ "path_sub",        "path",     PAT_MATCH_SUB },
-
-	{ "req_ver",         "req.ver",  PAT_MATCH_STR },
-	{ "resp_ver",        "res.ver",  PAT_MATCH_STR },
-
-	{ "scook",           "res.cook", PAT_MATCH_STR },
-	{ "scook_beg",       "res.cook", PAT_MATCH_BEG },
-	{ "scook_dir",       "res.cook", PAT_MATCH_DIR },
-	{ "scook_dom",       "res.cook", PAT_MATCH_DOM },
-	{ "scook_end",       "res.cook", PAT_MATCH_END },
-	{ "scook_len",       "res.cook", PAT_MATCH_LEN },
-	{ "scook_reg",       "res.cook", PAT_MATCH_REG },
-	{ "scook_sub",       "res.cook", PAT_MATCH_SUB },
-
-	{ "shdr",            "res.hdr",  PAT_MATCH_STR },
-	{ "shdr_beg",        "res.hdr",  PAT_MATCH_BEG },
-	{ "shdr_dir",        "res.hdr",  PAT_MATCH_DIR },
-	{ "shdr_dom",        "res.hdr",  PAT_MATCH_DOM },
-	{ "shdr_end",        "res.hdr",  PAT_MATCH_END },
-	{ "shdr_len",        "res.hdr",  PAT_MATCH_LEN },
-	{ "shdr_reg",        "res.hdr",  PAT_MATCH_REG },
-	{ "shdr_sub",        "res.hdr",  PAT_MATCH_SUB },
-
-	{ "url",             "url",      PAT_MATCH_STR },
-	{ "url_beg",         "url",      PAT_MATCH_BEG },
-	{ "url_dir",         "url",      PAT_MATCH_DIR },
-	{ "url_dom",         "url",      PAT_MATCH_DOM },
-	{ "url_end",         "url",      PAT_MATCH_END },
-	{ "url_len",         "url",      PAT_MATCH_LEN },
-	{ "url_reg",         "url",      PAT_MATCH_REG },
-	{ "url_sub",         "url",      PAT_MATCH_SUB },
-
-	{ "urlp",            "urlp",     PAT_MATCH_STR },
-	{ "urlp_beg",        "urlp",     PAT_MATCH_BEG },
-	{ "urlp_dir",        "urlp",     PAT_MATCH_DIR },
-	{ "urlp_dom",        "urlp",     PAT_MATCH_DOM },
-	{ "urlp_end",        "urlp",     PAT_MATCH_END },
-	{ "urlp_len",        "urlp",     PAT_MATCH_LEN },
-	{ "urlp_reg",        "urlp",     PAT_MATCH_REG },
-	{ "urlp_sub",        "urlp",     PAT_MATCH_SUB },
-
-	{ /* END */ },
-}};
-
-/************************************************************************/
-/*         All supported pattern keywords must be declared here.        */
-/************************************************************************/
-/* Note: must not be declared <const> as its list will be overwritten */
-static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
-	{ "base",            smp_fetch_base,           0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
-	{ "base32",          smp_fetch_base32,         0,                NULL,    SMP_T_SINT, SMP_USE_HRQHV },
-	{ "base32+src",      smp_fetch_base32_src,     0,                NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
-
-	/* capture are allocated and are permanent in the stream */
-	{ "capture.req.hdr", smp_fetch_capture_header_req, ARG1(1,SINT), NULL,   SMP_T_STR,  SMP_USE_HRQHP },
-
-	/* retrieve these captures from the HTTP logs */
-	{ "capture.req.method", smp_fetch_capture_req_method, 0,         NULL,   SMP_T_STR,  SMP_USE_HRQHP },
-	{ "capture.req.uri",    smp_fetch_capture_req_uri,    0,         NULL,   SMP_T_STR,  SMP_USE_HRQHP },
-	{ "capture.req.ver",    smp_fetch_capture_req_ver,    0,         NULL,   SMP_T_STR,  SMP_USE_HRQHP },
-
-	{ "capture.res.hdr", smp_fetch_capture_header_res, ARG1(1,SINT), NULL,   SMP_T_STR,  SMP_USE_HRSHP },
-	{ "capture.res.ver", smp_fetch_capture_res_ver,       0,         NULL,   SMP_T_STR,  SMP_USE_HRQHP },
-
-	/* cookie is valid in both directions (eg: for "stick ...") but cook*
-	 * are only here to match the ACL's name, are request-only and are used
-	 * for ACL compatibility only.
-	 */
-	{ "cook",            smp_fetch_cookie,         ARG1(0,STR),      NULL,    SMP_T_STR,  SMP_USE_HRQHV },
-	{ "cookie",          smp_fetch_cookie,         ARG1(0,STR),      NULL,    SMP_T_STR,  SMP_USE_HRQHV|SMP_USE_HRSHV },
-	{ "cook_cnt",        smp_fetch_cookie_cnt,     ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRQHV },
-	{ "cook_val",        smp_fetch_cookie_val,     ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRQHV },
-
-	/* hdr is valid in both directions (eg: for "stick ...") but hdr_* are
-	 * only here to match the ACL's name, are request-only and are used for
-	 * ACL compatibility only.
-	 */
-	{ "hdr",             smp_fetch_hdr,            ARG2(0,STR,SINT), val_hdr, SMP_T_STR,  SMP_USE_HRQHV|SMP_USE_HRSHV },
-	{ "hdr_cnt",         smp_fetch_hdr_cnt,        ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRQHV },
-	{ "hdr_ip",          smp_fetch_hdr_ip,         ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRQHV },
-	{ "hdr_val",         smp_fetch_hdr_val,        ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRQHV },
-
-	{ "http_auth",       smp_fetch_http_auth,      ARG1(1,USR),      NULL,    SMP_T_BOOL, SMP_USE_HRQHV },
-	{ "http_auth_group", smp_fetch_http_auth_grp,  ARG1(1,USR),      NULL,    SMP_T_STR,  SMP_USE_HRQHV },
-	{ "http_first_req",  smp_fetch_http_first_req, 0,                NULL,    SMP_T_BOOL, SMP_USE_HRQHP },
-	{ "method",          smp_fetch_meth,           0,                NULL,    SMP_T_METH, SMP_USE_HRQHP },
-	{ "path",            smp_fetch_path,           0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
-	{ "query",           smp_fetch_query,          0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
-
-	/* HTTP protocol on the request path */
-	{ "req.proto_http",  smp_fetch_proto_http,     0,                NULL,    SMP_T_BOOL, SMP_USE_HRQHP },
-	{ "req_proto_http",  smp_fetch_proto_http,     0,                NULL,    SMP_T_BOOL, SMP_USE_HRQHP },
-
-	/* HTTP version on the request path */
-	{ "req.ver",         smp_fetch_rqver,          0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
-	{ "req_ver",         smp_fetch_rqver,          0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
-
-	{ "req.body",        smp_fetch_body,           0,                NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
-	{ "req.body_len",    smp_fetch_body_len,       0,                NULL,    SMP_T_SINT, SMP_USE_HRQHV },
-	{ "req.body_size",   smp_fetch_body_size,      0,                NULL,    SMP_T_SINT, SMP_USE_HRQHV },
-	{ "req.body_param",  smp_fetch_body_param,     ARG1(0,STR),      NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
-
-	{ "req.hdrs",        smp_fetch_hdrs,           0,                NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
-	{ "req.hdrs_bin",    smp_fetch_hdrs_bin,       0,                NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
-
-	/* HTTP version on the response path */
-	{ "res.ver",         smp_fetch_stver,          0,                NULL,    SMP_T_STR,  SMP_USE_HRSHV },
-	{ "resp_ver",        smp_fetch_stver,          0,                NULL,    SMP_T_STR,  SMP_USE_HRSHV },
-
-	/* explicit req.{cook,hdr} are used to force the fetch direction to be request-only */
-	{ "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.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 },
-	{ "req.hdr",         smp_fetch_hdr,            ARG2(0,STR,SINT), val_hdr, SMP_T_STR,  SMP_USE_HRQHV },
-	{ "req.hdr_cnt",     smp_fetch_hdr_cnt,        ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRQHV },
-	{ "req.hdr_ip",      smp_fetch_hdr_ip,         ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRQHV },
-	{ "req.hdr_names",   smp_fetch_hdr_names,      ARG1(0,STR),      NULL,    SMP_T_STR,  SMP_USE_HRQHV },
-	{ "req.hdr_val",     smp_fetch_hdr_val,        ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRQHV },
-
-	/* explicit req.{cook,hdr} are used to force the fetch direction to be response-only */
-	{ "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.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 },
-	{ "res.hdr",         smp_fetch_hdr,            ARG2(0,STR,SINT), val_hdr, SMP_T_STR,  SMP_USE_HRSHV },
-	{ "res.hdr_cnt",     smp_fetch_hdr_cnt,        ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRSHV },
-	{ "res.hdr_ip",      smp_fetch_hdr_ip,         ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRSHV },
-	{ "res.hdr_names",   smp_fetch_hdr_names,      ARG1(0,STR),      NULL,    SMP_T_STR,  SMP_USE_HRSHV },
-	{ "res.hdr_val",     smp_fetch_hdr_val,        ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRSHV },
-
-	/* scook is valid only on the response and is used for ACL compatibility */
-	{ "scook",           smp_fetch_cookie,         ARG1(0,STR),      NULL,    SMP_T_STR,  SMP_USE_HRSHV },
-	{ "scook_cnt",       smp_fetch_cookie_cnt,     ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRSHV },
-	{ "scook_val",       smp_fetch_cookie_val,     ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRSHV },
-	{ "set-cookie",      smp_fetch_cookie,         ARG1(0,STR),      NULL,    SMP_T_STR,  SMP_USE_HRSHV }, /* deprecated */
-
-	/* shdr is valid only on the response and is used for ACL compatibility */
-	{ "shdr",            smp_fetch_hdr,            ARG2(0,STR,SINT), val_hdr, SMP_T_STR,  SMP_USE_HRSHV },
-	{ "shdr_cnt",        smp_fetch_hdr_cnt,        ARG1(0,STR),      NULL,    SMP_T_SINT, SMP_USE_HRSHV },
-	{ "shdr_ip",         smp_fetch_hdr_ip,         ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRSHV },
-	{ "shdr_val",        smp_fetch_hdr_val,        ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRSHV },
-
-	{ "status",          smp_fetch_stcode,         0,                NULL,    SMP_T_SINT, SMP_USE_HRSHP },
-	{ "unique-id",       smp_fetch_uniqueid,       0,                NULL,    SMP_T_STR,  SMP_SRC_L4SRV },
-	{ "url",             smp_fetch_url,            0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
-	{ "url32",           smp_fetch_url32,          0,                NULL,    SMP_T_SINT, SMP_USE_HRQHV },
-	{ "url32+src",       smp_fetch_url32_src,      0,                NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
-	{ "url_ip",          smp_fetch_url_ip,         0,                NULL,    SMP_T_IPV4, SMP_USE_HRQHV },
-	{ "url_port",        smp_fetch_url_port,       0,                NULL,    SMP_T_SINT, SMP_USE_HRQHV },
-	{ "url_param",       smp_fetch_url_param,      ARG2(0,STR,STR),  NULL,    SMP_T_STR,  SMP_USE_HRQHV },
-	{ "urlp"     ,       smp_fetch_url_param,      ARG2(0,STR,STR),  NULL,    SMP_T_STR,  SMP_USE_HRQHV },
-	{ "urlp_val",        smp_fetch_url_param_val,  ARG2(0,STR,STR),  NULL,    SMP_T_SINT, SMP_USE_HRQHV },
-	{ /* END */ },
-}};
-
-
-/************************************************************************/
-/*        All supported converter keywords must be declared here.       */
-/************************************************************************/
-/* Note: must not be declared <const> as its list will be overwritten */
-static struct sample_conv_kw_list sample_conv_kws = {ILH, {
-	{ "http_date", sample_conv_http_date,  ARG1(0,SINT),     NULL, SMP_T_SINT, SMP_T_STR},
-	{ "language",  sample_conv_q_prefered, ARG2(1,STR,STR),  NULL, SMP_T_STR,  SMP_T_STR},
-	{ "capture-req", smp_conv_req_capture, ARG1(1,SINT),     NULL, SMP_T_STR,  SMP_T_STR},
-	{ "capture-res", smp_conv_res_capture, ARG1(1,SINT),     NULL, SMP_T_STR,  SMP_T_STR},
-	{ "url_dec",   sample_conv_url_dec,    0,                NULL, SMP_T_STR,  SMP_T_STR},
-	{ NULL, NULL, 0, 0, 0 },
-}};
-
-
-/************************************************************************/
-/*   All supported http-request action keywords must be declared here.  */
-/************************************************************************/
-struct action_kw_list http_req_actions = {
-	.kw = {
-		{ "capture",    parse_http_req_capture },
-		{ "reject",     parse_http_action_reject },
-		{ "set-method", parse_set_req_line },
-		{ "set-path",   parse_set_req_line },
-		{ "set-query",  parse_set_req_line },
-		{ "set-uri",    parse_set_req_line },
-		{ NULL, NULL }
-	}
-};
-
-struct action_kw_list http_res_actions = {
-	.kw = {
-		{ "capture",    parse_http_res_capture },
-		{ "set-status", parse_http_set_status },
-		{ NULL, NULL }
-	}
-};
 
 __attribute__((constructor))
 static void __http_protocol_init(void)
 {
-	acl_register_keywords(&acl_kws);
-	sample_register_fetches(&sample_fetch_keywords);
-	sample_register_convs(&sample_conv_kws);
-	http_req_keywords_register(&http_req_actions);
-	http_res_keywords_register(&http_res_actions);
 }