* released 1.2.2 (1.1.29)
* fixed a bug where a TCP connection would be logged twice if the 'logasap'
  option was enabled without the 'tcplog' option.
* encode_string() would use hdr_encode_map instead of the map argument.
* the logged request is now encoded with '#XX' for unprintable characters
* new keywords 'capture request header' and 'capture response header' enable
  logging of arbitrary HTTP headers in requests and responses
* removed "-DSOLARIS" after replacing the last inet_aton() with inet_pton()
diff --git a/CHANGELOG b/CHANGELOG
index 0c2140a..84e0228 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,17 @@
 ChangeLog :
 ===========
+
+2004/10/18 : 1.2.2 (1.1.29)
+  - fixed a bug where a TCP connection would be logged twice if the 'logasap'
+    option was enabled without the 'tcplog' option.
+  - encode_string() would use hdr_encode_map instead of the map argument.
+
+2004/08/10 : (1.1.29-pre2)
+  - the logged request is now encoded with '#XX' for unprintable characters
+  - new keywords 'capture request header' and 'capture response header' enable
+    logging of arbitrary HTTP headers in requests and responses
+  - removed "-DSOLARIS" after replacing the last inet_aton() with inet_pton()
+
 2004/06/06 : 1.2.1 (1.1.28)
   - added the '-V' command line option to verbosely report errors even though
     the -q or 'quiet' options are specified. This is useful with '-c'.
diff --git a/TODO b/TODO
index d54fbd1..46e2912 100644
--- a/TODO
+++ b/TODO
@@ -136,6 +136,7 @@
 * listen [ip4.ip4.ip4.ip4]:port[-port]
 * listen [ip6::...ip6]/port[-port]
 - server xxx ipv4 | ipv4: | ipv4:port[-port] | ipv6/ | ipv6/port[-port]
-
+- appcookie
 - weighted round robin
+- option to shutdown(listen_sock) when max connections reached
 
diff --git a/doc/haproxy-en.txt b/doc/haproxy-en.txt
index 115be18..77e260e 100644
--- a/doc/haproxy-en.txt
+++ b/doc/haproxy-en.txt
@@ -1,9 +1,9 @@
 
          		     H A - P r o x y
          		     ---------------
-         		      version 1.2.1
+         		      version 1.2.2
 			      willy tarreau
-			       2004/06/06
+			       2004/10/18
 
 ============
 | Abstract |
@@ -227,7 +227,9 @@
 usage. Anyway, for very specific needs, the proxy can start several processes
 between which the operating system will spread the incoming connections. The
 number of processes is controlled by the 'nbproc' parameter in the 'global'
-section. It defaults to 1, and obviously works only in 'daemon' mode.
+section. It defaults to 1, and obviously works only in 'daemon' mode. One
+typical usage of this parameter has been to workaround the default per-process
+file-descriptor limit that Solaris imposes to user processes.
 
 Example :
 ---------
@@ -1154,6 +1156,67 @@
      connection to the server failed ('SC--') after 4 attemps of 2 seconds
      (config says 'retries 3'), then a 503 error code was sent to the client.
 
+4.2.6) Non-printable characters
+-------------------------------
+As of version 1.1.29, non-printable characters are not sent as-is into log
+files, but are converted to their two-digits hexadecimal representation,
+prefixed by the character '#'. The only characters that can now be logged
+without being escaped are between 32 and 126 (inclusive). Obviously, the
+escape character '#' is also encoded to avoid any ambiguity. It is the same for
+the character '"', as well as '{', '|' and '}' when logging headers.
+
+4.2.7) Logging HTTP headers
+---------------------------
+As of version 1.1.29, it is now possible to log HTTP headers extracts. It is
+both possible to include request headers and response headers. It is
+particularly useful to know what virtual server the client requested, to know
+the content length during a POST request, or a unique request ID set on a
+previous proxy. In the response, one can search for information about the
+response length, how the server asked the cache to behave, or an object location
+during a redirection. The syntax is :
+
+    capture request  header <name> len <max length>
+    capture response header <name> len <max length>
+
+Note: Header names are not case-sensitive.
+
+Examples:
+---------
+    # keep the name of the virtual server
+    capture request  header Host len 20
+    # keep the amount of data uploaded during a POST
+    capture request  header Content-Length len 10
+
+    # note the expected cache behaviour on the response
+    capture response header Cache-Control len 8
+    # note the URL location during a redirection
+    capture response header Location len 20
+
+Non-existant headers are logged as empty strings, and if one header appears more
+than once, only its last occurence will be kept. Request headers are grouped
+within braces '{' and '}' in the same order as they were declared, and delimited
+with a vertical bar '|' without any space. Response headers follow the same
+representation, but are displayed after a space following the request headers
+block. These blocks are displayed just before the HTTP request in the logs.
+Example :
+
+Config:
+
+     capture request  header Host len 20
+     capture request  header Content-Length len 10
+     capture request  header Referer len 20
+     capture response header Server len 20
+     capture response header Content-Length len 10
+     capture response header Cache-Control len 8
+     capture response header Via len 20
+     capture response header Location len 20
+
+Log :
+
+Aug  9 20:26:09 localhost haproxy[2022]: 127.0.0.1:34014 [09/Aug/2004:20:26:09] relais-http netcache 0/0/162/+162 200 +350 - - ---- {fr.adserver.yahoo.co||http://fr.f416.mail.} {|864|private||} "GET http://fr.adserver.yahoo.com/"
+Aug  9 20:30:46 localhost haproxy[2022]: 127.0.0.1:34020 [09/Aug/2004:20:30:46] relais-http netcache 0/0/182/+182 200 +279 - - ---- {w.ods.org||} {Formilux/0.1.8|3495|||} "GET http://w.ods.org/sytadin.html HTTP/1.1" 
+Aug  9 20:30:46 localhost haproxy[2022]: 127.0.0.1:34028 [09/Aug/2004:20:30:46] relais-http netcache 0/2/126/+128 200 +223 - - ---- {www.infotrafic.com||http://w.ods.org/syt} {Apache/2.0.40 (Red H|9068|||} "GET http://www.infotrafic.com/images/live/cartesidf/grandes/idf_ne.png HTTP/1.1" 
+
 4.3) HTTP header manipulation
 -----------------------------
 In HTTP mode, it is possible to rewrite, add or delete some of the request and
diff --git a/doc/haproxy-fr.txt b/doc/haproxy-fr.txt
index 8416ccb..c5f6eb6 100644
--- a/doc/haproxy-fr.txt
+++ b/doc/haproxy-fr.txt
@@ -1,9 +1,9 @@
 
          		     H A - P r o x y
          		     ---------------
-         		      version 1.2.1
+         		      version 1.2.2
 			      willy tarreau
-			       2004/06/06
+			       2004/10/18
 
 ================
 | Introduction |
@@ -240,7 +240,9 @@
 programme sait démarrer plusieurs processus se répartissant la charge de
 travail. Ce nombre de processus est spécifié par le paramètre 'nbproc' de la
 section 'global'. Sa valeur par défaut est naturellement 1. Ceci ne fonctionne
-qu'en mode 'daemon'.
+qu'en mode 'daemon'. Un usage déjà rencontré pour ce paramètre fut de dépasser
+la limite de nombre de descripteurs de fichiers allouée par processus sous
+Solaris.
 
 Exemple :
 ---------
@@ -1192,6 +1194,69 @@
      connexion ('SC--') vers le serveur échoue au bout de 4 tentatives de 2
      secondes (retries 3 dans la conf), puis un code 503 est retourné au client.
 
+4.2.6) Caractères non-imprimables
+---------------------------------
+Depuis la version 1.1.29, les caractères non-imprimables ne sont plus envoyés
+tels quels dans les lignes de logs, mais inscrits sous la forme de deux chiffres
+hexadécimaux, préfixés du caractère d'échappement '#'. Les seuls caractères
+dorénavant logués tels quels sont compris entre 32 et 126. Bien évidemment, le
+caractère d'échappement '#' est lui-même encodé afin de lever l'ambiguité. Il en
+est de même pour le caractère '"', ainsi que les caractères '{', '|' et '}' pour
+les en-têtes.
+
+4.2.7) Journalisation d'en-têtes
+--------------------------------
+Depuis la version 1.1.29, il est désormais possible de conserver des extraits
+d'en-têtes HTTP dans les logs. On peut aussi bien extraire des en-têtes de la
+requête que de la réponse. C'est particulièrement pratique pour savoir à quel
+serveur virtuel une requête s'adressait, pour connaitre la longueur des données
+émises lors d'un POST, ou encore loguer un identifiant unique de requête
+positionné en amont. Dans la réponse, on peut chercher également à conserver des
+informations relatives à la taille annoncée de la réponse, le fonctionnement
+attendu du cache, ou encore la localisation d'un objet en cas de redirection.
+La syntaxe est la suivante :
+
+    capture request  header <nom> len <longueur max>
+    capture response header <nom> len <longueur max>
+
+Note: Les noms d'en-têtes ne sont pas sensibles à la casse.
+
+Exemples:
+---------
+    # conserver le nom du serveur virtuel accédé par le client
+    capture request  header Host len 20
+    # noter la longueur des données envoyées dans un POST
+    capture request  header Content-Length len 10
+
+    # noter le fonctionnement attendu du cache par le serveur
+    capture response header Cache-Control len 8
+    # noter l'URL de redirection
+    capture response header Location len 20
+
+Les en-têtes non trouvés sont logués à vide, et si un en-tête apparait plusieurs
+fois, seule la dernière occurence sera conservée. Les en-têtes de requête sont
+regroupés entre deux accolades '{' et '}' dans l'ordre de leur déclaration, et
+chacun séparés par une barre verticale '|', sans aucun espace. Les en-têtes de
+réponse sont présentés de la même manière, mais après un espace suivant le bloc
+d'en-tête de requête. Le tout précède la requête HTTP. Exemple :
+
+Config:
+
+     capture request  header Host len 20
+     capture request  header Content-Length len 10
+     capture request  header Referer len 20
+     capture response header Server len 20
+     capture response header Content-Length len 10
+     capture response header Cache-Control len 8
+     capture response header Via len 20
+     capture response header Location len 20
+
+Log :
+
+Aug  9 20:26:09 localhost haproxy[2022]: 127.0.0.1:34014 [09/Aug/2004:20:26:09] relais-http netcache 0/0/162/+162 200 +350 - - ---- {fr.adserver.yahoo.co||http://fr.f416.mail.} {|864|private||} "GET http://fr.adserver.yahoo.com/"
+Aug  9 20:30:46 localhost haproxy[2022]: 127.0.0.1:34020 [09/Aug/2004:20:30:46] relais-http netcache 0/0/182/+182 200 +279 - - ---- {w.ods.org||} {Formilux/0.1.8|3495|||} "GET http://w.ods.org/sytadin.html HTTP/1.1" 
+Aug  9 20:30:46 localhost haproxy[2022]: 127.0.0.1:34028 [09/Aug/2004:20:30:46] relais-http netcache 0/2/126/+128 200 +223 - - ---- {www.infotrafic.com||http://w.ods.org/syt} {Apache/2.0.40 (Red H|9068|||} "GET http://www.infotrafic.com/images/live/cartesidf/grandes/idf_ne.png HTTP/1.1" 
+
 4.3) Modification des entêtes HTTP
 ----------------------------------
 En mode HTTP uniquement, il est possible de remplacer certains en-têtes dans la
diff --git a/examples/haproxy.spec b/examples/haproxy.spec
index 63ef708..9654655 100644
--- a/examples/haproxy.spec
+++ b/examples/haproxy.spec
@@ -1,6 +1,6 @@
 Summary: HA-Proxy is a TCP/HTTP reverse proxy for high availability environments
 Name: haproxy
-Version: 1.2.1
+Version: 1.2.3
 Release: 1
 License: GPL
 Group: System Environment/Daemons
@@ -9,8 +9,6 @@
 Vendor: Invoca Systems
 Distribution: Invoca Linux Server
 Source0: http://w.ods.org/tools/%{name}/%{name}-%{version}.tar.gz
-Source1: %{name}.cfg
-Source2: %{name}.init
 BuildRoot: %{_tmppath}/%{name}-%{version}-root
 BuildRequires: pcre-devel
 Prereq: /sbin/chkconfig
@@ -35,20 +33,18 @@
 %setup -q
 
 %build
-%{__make} REGEX=pcre DEBUG=""
+%{__make} REGEX=pcre DEBUG="" LIBS.pcre="-L\$(PCREDIR)/lib -Wl,-Bstatic -lpcreposix -lpcre -Wl,-Bdynamic"
 
 %install
 [ "%{buildroot}" != "/" ] && %{__rm} -rf %{buildroot}
  
 %{__install} -d %{buildroot}%{_sbindir}
 %{__install} -d %{buildroot}%{_sysconfdir}/rc.d/init.d
-%{__install} -d %{buildroot}%{_sysconfdir}/logrotate.d
 %{__install} -d %{buildroot}%{_sysconfdir}/%{name}
-%{__install} -d %{buildroot}%{_datadir}/%{name}
 
 %{__install} -s %{name} %{buildroot}%{_sbindir}/
-%{__install} -c -m 644 %{SOURCE1} %{buildroot}%{_sysconfdir}/%{name}/
-%{__install} -c -m 755 %{SOURCE2} %{buildroot}%{_sysconfdir}/rc.d/init.d/%{name}
+%{__install} -c -m 644 examples/%{name}.cfg %{buildroot}%{_sysconfdir}/%{name}/
+%{__install} -c -m 755 examples/%{name}.init %{buildroot}%{_sysconfdir}/rc.d/init.d/%{name}
  
 %clean
 [ "%{buildroot}" != "/" ] && %{__rm} -rf %{buildroot}
@@ -69,14 +65,21 @@
 
 %files
 %defattr(-,root,root)
-%doc CHANGELOG TODO examples
+%doc CHANGELOG TODO examples doc/haproxy-en.txt doc/haproxy-fr.txt doc/architecture.txt
 %attr(0755,root,root) %{_sbindir}/%{name}
 %dir %{_sysconfdir}/%{name}
 %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/%{name}/%{name}.cfg
 %attr(0755,root,root) %config %{_sysconfdir}/rc.d/init.d/%{name}
-%dir %{_datadir}/%{name}
 
 %changelog
+* Sat Jan 22 2005 Willy Tarreau <willy@w.ods.org>
+- updated to 1.2.3 (1.1.30)
+
+* Sun Nov 14 2004 Willy Tarreau <w@w.ods.org>
+- updated to 1.1.29
+- fixed path to config and init files
+- statically linked PCRE to increase portability to non-pcre systems
+
 * Sun Jun  6 2004 Willy Tarreau <willy@w.ods.org>
 - updated to 1.1.28
 - added config check support to the init script
diff --git a/haproxy.c b/haproxy.c
index c96aae4..426786e 100644
--- a/haproxy.c
+++ b/haproxy.c
@@ -29,6 +29,7 @@
  *   - fix client/server state transition when server is in connect or headers state
  *     and client suddenly disconnects. The server *should* switch to SHUT_WR, but
  *     still handle HTTP headers.
+ *   - remove MAX_NEWHDR
  *
  */
 
@@ -56,8 +57,8 @@
 #include <linux/netfilter_ipv4.h>
 #endif
 
-#define HAPROXY_VERSION "1.2.1"
-#define HAPROXY_DATE	"2004/06/06"
+#define HAPROXY_VERSION "1.2.2"
+#define HAPROXY_DATE	"2004/10/18"
 
 /* this is for libc5 for example */
 #ifndef TCP_NODELAY
@@ -145,6 +146,35 @@
     return dst - orig;
 }
 
+/*
+ * Returns a pointer to an area of <__len> bytes taken from the pool <pool> or
+ * dynamically allocated. In the first case, <__pool> is updated to point to
+ * the next element in the list.
+ */
+#define pool_alloc_from(__pool, __len) ({                                      \
+    void *__p;                                                                 \
+    if ((__p = (__pool)) == NULL)                                              \
+	__p = malloc(((__len) >= sizeof (void *)) ? (__len) : sizeof(void *)); \
+    else {                                                                     \
+	__pool = *(void **)(__pool);                                           \
+    }                                                                          \
+    __p;                                                                       \
+})
+
+/*
+ * Puts a memory area back to the corresponding pool.
+ * Items are chained directly through a pointer that
+ * is written in the beginning of the memory area, so
+ * there's no need for any carrier cell. This implies
+ * that each memory area is at least as big as one
+ * pointer.
+ */
+#define pool_free_to(__pool, __ptr) ({          \
+    *(void **)(__ptr) = (void *)(__pool);       \
+    __pool = (void *)(__ptr);                   \
+})
+
+
 #define MEM_OPTIM
 #ifdef	MEM_OPTIM
 /*
@@ -154,13 +184,13 @@
  * next element in the list.
  */
 #define pool_alloc(type) ({			\
-    void *p;					\
-    if ((p = pool_##type) == NULL)		\
-	p = malloc(sizeof_##type);		\
+    void *__p;					\
+    if ((__p = pool_##type) == NULL)		\
+	__p = malloc(sizeof_##type);		\
     else {					\
 	pool_##type = *(void **)pool_##type;	\
     }						\
-    p;						\
+    __p;					\
 })
 
 /*
@@ -332,6 +362,9 @@
 #define LW_PXIP		64	/* proxy IP */
 #define LW_PXID		128	/* proxy ID */
 #define LW_BYTES	256	/* bytes read from server */
+#define LW_COOKIE	512	/* captured cookie */
+#define LW_REQHDR	1024	/* request header(s) */
+#define LW_RSPHDR	2048	/* response header(s) */
 
 /*********************************************************************/
 
@@ -339,6 +372,15 @@
 
 /*********************************************************************/
 
+struct cap_hdr {
+    struct cap_hdr *next;
+    char *name;				/* header name, case insensitive */
+    int namelen;			/* length of the header name, to speed-up lookups */
+    int len;				/* capture length, not including terminal zero */
+    int index;				/* index in the output array */
+    void *pool;				/* pool of pre-allocated memory area of (len+1) bytes */
+};
+
 struct hdr_exp {
     struct hdr_exp *next;
     regex_t *preg;			/* expression to look for */
@@ -403,6 +445,8 @@
     struct sockaddr_storage cli_addr;	/* the client address */
     struct sockaddr_in srv_addr;	/* the address to connect to */
     struct server *srv;			/* the server being used */
+    char **req_cap;			/* array of captured request headers (may be NULL) */
+    char **rsp_cap;			/* array of captured response headers (may be NULL) */
     struct {
 	int logwait;			/* log fields waiting to be collected : LW_* */
 	struct timeval tv_accept;	/* date of the accept() (beginning of the session) */
@@ -456,6 +500,10 @@
     int nb_reqadd, nb_rspadd;
     struct hdr_exp *req_exp;		/* regular expressions for request headers */
     struct hdr_exp *rsp_exp;		/* regular expressions for response headers */
+    int nb_req_cap, nb_rsp_cap;		/* # of headers to be captured */
+    struct cap_hdr *req_cap;		/* chained list of request headers to be captured */
+    struct cap_hdr *rsp_cap;		/* chained list of response headers to be captured */
+    void *req_cap_pool, *rsp_cap_pool;	/* pools of pre-allocated char ** used to build the sessions */
     char *req_add[MAX_NEWHDR], *rsp_add[MAX_NEWHDR]; /* headers to be added */
     int grace;				/* grace time after stop request */
     char *check_req;			/* HTTP request to use if PR_O_HTTP_CHK is set, else NULL */
@@ -825,6 +873,7 @@
     int port, end;
 
     next = dupstr = strdup(str);
+    
     while (next && *next) {
 	struct sockaddr_storage ss;
 
@@ -895,6 +944,56 @@
     return tail;
 }
 
+
+#define FD_SETS_ARE_BITFIELDS
+#ifdef FD_SETS_ARE_BITFIELDS
+/*
+ * This map is used with all the FD_* macros to check whether a particular bit
+ * is set or not. Each bit represents an ACSII code. FD_SET() sets those bytes
+ * which should be encoded. When FD_ISSET() returns non-zero, it means that the
+ * byte should be encoded. Be careful to always pass bytes from 0 to 255
+ * exclusively to the macros.
+ */
+fd_set hdr_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))];
+fd_set url_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))];
+
+#else
+#error "Check if your OS uses bitfields for fd_sets"
+#endif
+
+/* will try to encode the string <string> replacing all characters tagged in
+ * <map> with the hexadecimal representation of their ASCII-code (2 digits)
+ * prefixed by <escape>, and will store the result between <start> (included
+ *) and <stop> (excluded), and will always terminate the string with a '\0'
+ * before <stop>. The position of the '\0' is returned if the conversion
+ * completes. If bytes are missing between <start> and <stop>, then the
+ * conversion will be incomplete and truncated. If <stop> <= <start>, the '\0'
+ * cannot even be stored so we return <start> without writing the 0.
+ * The input string must also be zero-terminated.
+ */
+char hextab[16] = "0123456789ABCDEF";
+char *encode_string(char *start, char *stop,
+		    const char escape, const fd_set *map,
+		    const char *string)
+{
+    if (start < stop) {
+	stop--; /* reserve one byte for the final '\0' */
+	while (start < stop && *string != 0) {
+	    if (!FD_ISSET((unsigned char)(*string), map))
+		*start++ = *string;
+	    else {
+		if (start + 3 >= stop)
+		    break;
+		*start++ = escape;
+		*start++ = hextab[(*string >> 4) & 15];
+		*start++ = hextab[*string & 15];
+	    }
+	    string++;
+	}
+	*start = '\0';
+    }
+    return start;
+}
 
 /*
  * This function sends a syslog message to both log servers of a proxy,
@@ -1422,6 +1521,24 @@
 	pool_free(buffer, s->req);
     if (s->rep)
 	pool_free(buffer, s->rep);
+
+    if (s->rsp_cap != NULL) {
+	struct cap_hdr *h;
+	for (h = s->proxy->rsp_cap; h; h = h->next) {
+	    if (s->rsp_cap[h->index] != NULL)
+		pool_free_to(h->pool, s->rsp_cap[h->index]);
+	}
+	pool_free_to(s->proxy->rsp_cap_pool, s->rsp_cap);
+    }
+    if (s->req_cap != NULL) {
+	struct cap_hdr *h;
+	for (h = s->proxy->req_cap; h; h = h->next) {
+	    if (s->req_cap[h->index] != NULL)
+		pool_free_to(h->pool, s->req_cap[h->index]);
+	}
+	pool_free_to(s->proxy->req_cap_pool, s->req_cap);
+    }
+
     if (s->logs.uri)
 	pool_free(requri, s->logs.uri);
     if (s->logs.cli_cookie)
@@ -1997,7 +2114,43 @@
 
     tm = localtime(&s->logs.tv_accept.tv_sec);
     if (p->to_log & LW_REQ) {
-	send_log(p, LOG_INFO, "%s:%d [%02d/%s/%04d:%02d:%02d:%02d] %s %s %d/%d/%d/%s%d %d %s%lld %s %s %c%c%c%c \"%s\"\n",
+	char tmpline[MAX_SYSLOG_LEN], *h;
+	int hdr;
+	
+	h = tmpline;
+	if (p->to_log & LW_REQHDR && (h < tmpline + sizeof(tmpline) - 10)) {
+	    *(h++) = ' ';
+	    *(h++) = '{';
+	    for (hdr = 0; hdr < p->nb_req_cap; hdr++) {
+		if (hdr)
+		    *(h++) = '|';
+		if (s->req_cap[hdr] != NULL)
+		    h = encode_string(h, tmpline + sizeof(tmpline) - 7, '#', hdr_encode_map, s->req_cap[hdr]);
+	    }
+	    *(h++) = '}';
+	}
+
+	if (p->to_log & LW_RSPHDR && (h < tmpline + sizeof(tmpline) - 7)) {
+	    *(h++) = ' ';
+	    *(h++) = '{';
+	    for (hdr = 0; hdr < p->nb_rsp_cap; hdr++) {
+		if (hdr)
+		    *(h++) = '|';
+		if (s->rsp_cap[hdr] != NULL)
+		    h = encode_string(h, tmpline + sizeof(tmpline) - 4, '#', hdr_encode_map, s->rsp_cap[hdr]);
+	    }
+	    *(h++) = '}';
+	}
+
+	if (h < tmpline + sizeof(tmpline) - 4) {
+	    *(h++) = ' ';
+	    *(h++) = '"';
+	    h = encode_string(h, tmpline + sizeof(tmpline) - 1, '#', url_encode_map, uri);
+	    *(h++) = '"';
+	}
+	*h = '\0';
+
+	send_log(p, LOG_INFO, "%s:%d [%02d/%s/%04d:%02d:%02d:%02d] %s %s %d/%d/%d/%s%d %d %s%lld %s %s %c%c%c%c%s\n",
 		 pn,
 		 (s->cli_addr.ss_family == AF_INET) ?
 		   ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port) :
@@ -2017,7 +2170,7 @@
 		 sess_fin_state[(s->flags & SN_FINST_MASK) >> SN_FINST_SHIFT],
 		 (p->options & PR_O_COOK_ANY) ? sess_cookie[(s->flags & SN_CK_MASK) >> SN_CK_SHIFT] : '-',
 		 (p->options & PR_O_COOK_ANY) ? sess_set_cookie[(s->flags & SN_SCK_MASK) >> SN_SCK_SHIFT] : '-',
-		 uri);
+		 tmpline);
     }
     else {
 	send_log(p, LOG_INFO, "%s:%d [%02d/%s/%04d:%02d:%02d:%02d] %s %s %d/%s%d %s%lld %c%c\n",
@@ -2125,6 +2278,36 @@
 
 	s->uniq_id = totalconn;
 
+	if (p->nb_req_cap > 0) {
+	    if ((s->req_cap =
+		 pool_alloc_from(p->req_cap_pool, p->nb_req_cap*sizeof(char *)))
+		== NULL) { /* no memory */
+		close(cfd); /* nothing can be done for this fd without memory */
+		pool_free(task, t);
+		pool_free(session, s);
+		return 0;
+	    }
+	    memset(s->req_cap, 0, p->nb_req_cap*sizeof(char *));
+	}
+	else
+	    s->req_cap = NULL;
+
+	if (p->nb_rsp_cap > 0) {
+	    if ((s->rsp_cap =
+		 pool_alloc_from(p->rsp_cap_pool, p->nb_rsp_cap*sizeof(char *)))
+		== NULL) { /* no memory */
+		if (s->req_cap != NULL)
+		    pool_free_to(p->req_cap_pool, s->req_cap);
+		close(cfd); /* nothing can be done for this fd without memory */
+		pool_free(task, t);
+		pool_free(session, s);
+		return 0;
+	    }
+	    memset(s->rsp_cap, 0, p->nb_rsp_cap*sizeof(char *));
+	}
+	else
+	    s->rsp_cap = NULL;
+
 	if ((p->mode == PR_MODE_TCP || p->mode == PR_MODE_HTTP)
 	    && (p->logfac1 >= 0 || p->logfac2 >= 0)) {
 	    struct sockaddr_storage sockname;
@@ -2201,11 +2384,16 @@
 	}
 
 	if ((s->req = pool_alloc(buffer)) == NULL) { /* no memory */
+	    if (s->rsp_cap != NULL)
+		pool_free_to(p->rsp_cap_pool, s->rsp_cap);
+	    if (s->req_cap != NULL)
+		pool_free_to(p->req_cap_pool, s->req_cap);
 	    close(cfd); /* nothing can be done for this fd without memory */
 	    pool_free(task, t);
 	    pool_free(session, s);
 	    return 0;
 	}
+
 	s->req->l = 0;
 	s->req->total = 0;
 	s->req->h = s->req->r = s->req->lr = s->req->w = s->req->data;	/* r and w will be reset further */
@@ -2215,6 +2403,10 @@
 
 	if ((s->rep = pool_alloc(buffer)) == NULL) { /* no memory */
 	    pool_free(buffer, s->req);
+	    if (s->rsp_cap != NULL)
+		pool_free_to(p->rsp_cap_pool, s->rsp_cap);
+	    if (s->req_cap != NULL)
+		pool_free_to(p->req_cap_pool, s->req_cap);
 	    close(cfd); /* nothing can be done for this fd without memory */
 	    pool_free(task, t);
 	    pool_free(session, s);
@@ -2609,6 +2801,27 @@
 		if (!(t->logs.logwait &= ~LW_REQ))
 		    sess_log(t);
 	    }
+	    else if (t->logs.logwait & LW_REQHDR) {
+		struct cap_hdr *h;
+		int len;
+		for (h = t->proxy->req_cap; h; h = h->next) {
+		    if ((h->namelen + 2 <= ptr - req->h) &&
+			(req->h[h->namelen] == ':') &&
+			(strncasecmp(req->h, h->name, h->namelen) == 0)) {
+
+			if (t->req_cap[h->index] == NULL)
+			    t->req_cap[h->index] = pool_alloc_from(h->pool, h->len + 1);
+
+			len = ptr - (req->h + h->namelen + 2);
+			if (len > h->len)
+			    len = h->len;
+
+			memcpy(t->req_cap[h->index], req->h + h->namelen + 2, len);
+			t->req_cap[h->index][len]=0;
+		    }
+		}
+		
+	    }
 
 	    delete_header = 0;
 
@@ -3234,7 +3447,7 @@
 
 		/* if the user wants to log as soon as possible, without counting
 		   bytes from the server, then this is the right moment. */
-		if (!(t->logs.logwait & LW_BYTES)) {
+		if (t->proxy->to_log && !(t->logs.logwait & LW_BYTES)) {
 		    t->logs.t_close = t->logs.t_connect; /* to get a valid end date */
 		    sess_log(t);
 		}
@@ -3349,7 +3562,7 @@
 
 		/* if the user wants to log as soon as possible, without counting
 		   bytes from the server, then this is the right moment. */
-		if (!(t->logs.logwait & LW_BYTES)) {
+		if (t->proxy->to_log && !(t->logs.logwait & LW_BYTES)) {
 		    t->logs.t_close = t->logs.t_data; /* to get a valid end date */
 		    t->logs.bytes = rep->h - rep->data;
 		    sess_log(t);
@@ -3412,6 +3625,27 @@
 		    default:
 			break;
 		}
+	    }
+	    else if (t->logs.logwait & LW_RSPHDR) {
+		struct cap_hdr *h;
+		int len;
+		for (h = t->proxy->rsp_cap; h; h = h->next) {
+		    if ((h->namelen + 2 <= ptr - rep->h) &&
+			(rep->h[h->namelen] == ':') &&
+			(strncasecmp(rep->h, h->name, h->namelen) == 0)) {
+
+			if (t->rsp_cap[h->index] == NULL)
+			    t->rsp_cap[h->index] = pool_alloc_from(h->pool, h->len + 1);
+
+			len = ptr - (rep->h + h->namelen + 2);
+			if (len > h->len)
+			    len = h->len;
+
+			memcpy(t->rsp_cap[h->index], rep->h + h->namelen + 2, len);
+			t->rsp_cap[h->index][len]=0;
+		    }
+		}
+		
 	    }
 
 	    delete_header = 0;
@@ -4730,7 +4964,7 @@
 	curproxy->logfac2 = defproxy.logfac2;
 	curproxy->logsrv2 = defproxy.logsrv2;
 	curproxy->loglev2 = defproxy.loglev2;
-	curproxy->to_log = defproxy.to_log;
+	curproxy->to_log = defproxy.to_log & ~LW_COOKIE & ~LW_REQHDR & ~ LW_RSPHDR;
 	curproxy->grace  = defproxy.grace;
 	curproxy->source_addr = defproxy.source_addr;
 	return 0;
@@ -4838,32 +5072,85 @@
 	    return -1;
 	}
     }
-    else if (!strcmp(args[0], "capture")) {  /* name of a cookie to capture */
-//	  if (curproxy == &defproxy) {
-//	      Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
-//	      return -1;
-//	  }
+    else if (!strcmp(args[0], "capture")) {
+	if (!strcmp(args[1], "cookie")) {  /* name of a cookie to capture */
+	    //	  if (curproxy == &defproxy) {
+	    //	      Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
+	    //	      return -1;
+	    //	  }
 
-	if (curproxy->capture_name != NULL) {
-//	      Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n",
-//		    file, linenum, args[0]);
-//	      return 0;
-	    free(curproxy->capture_name);
-	}
+	    if (curproxy->capture_name != NULL) {
+		//     Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n",
+		//           file, linenum, args[0]);
+		//     return 0;
+		free(curproxy->capture_name);
+	    }
 	
-	if (*(args[4]) == 0) {
-	    Alert("parsing [%s:%d] : '%s' expects 'cookie' <cookie_name> 'len' <len>.\n",
+	    if (*(args[4]) == 0) {
+		Alert("parsing [%s:%d] : '%s' expects 'cookie' <cookie_name> 'len' <len>.\n",
+		      file, linenum, args[0]);
+		return -1;
+	    }
+	    curproxy->capture_name = strdup(args[2]);
+	    curproxy->capture_namelen = strlen(curproxy->capture_name);
+	    curproxy->capture_len = atol(args[4]);
+	    if (curproxy->capture_len >= CAPTURE_LEN) {
+		Warning("parsing [%s:%d] : truncating capture length to %d bytes.\n",
+			file, linenum, CAPTURE_LEN - 1);
+		curproxy->capture_len = CAPTURE_LEN - 1;
+	    }
+	    curproxy->to_log |= LW_COOKIE;
+	}
+	else if (!strcmp(args[1], "request") && !strcmp(args[2], "header")) {
+	    struct cap_hdr *hdr;
+
+	    if (curproxy == &defproxy) {
+		Alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]);
+		return -1;
+	    }
+
+	    if (*(args[3]) == 0 || strcmp(args[4], "len") != 0 || *(args[5]) == 0) {
+		Alert("parsing [%s:%d] : '%s %s' expects 'header' <header_name> 'len' <len>.\n",
+		      file, linenum, args[0], args[1]);
+		return -1;
+	    }
+
+	    hdr = calloc(sizeof(struct cap_hdr), 1);
+	    hdr->next = curproxy->req_cap;
+	    hdr->name = strdup(args[3]);
+	    hdr->namelen = strlen(args[3]);
+	    hdr->len = atol(args[5]);
+	    hdr->index = curproxy->nb_req_cap++;
+	    curproxy->req_cap = hdr;
+	    curproxy->to_log |= LW_REQHDR;
+	}
+	else if (!strcmp(args[1], "response") && !strcmp(args[2], "header")) {
+	    struct cap_hdr *hdr;
+
+	    if (curproxy == &defproxy) {
+		Alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]);
+		return -1;
+	    }
+
+	    if (*(args[3]) == 0 || strcmp(args[4], "len") != 0 || *(args[5]) == 0) {
+		Alert("parsing [%s:%d] : '%s %s' expects 'header' <header_name> 'len' <len>.\n",
+		      file, linenum, args[0], args[1]);
+		return -1;
+	    }
+	    hdr = calloc(sizeof(struct cap_hdr), 1);
+	    hdr->next = curproxy->rsp_cap;
+	    hdr->name = strdup(args[3]);
+	    hdr->namelen = strlen(args[3]);
+	    hdr->len = atol(args[5]);
+	    hdr->index = curproxy->nb_rsp_cap++;
+	    curproxy->rsp_cap = hdr;
+	    curproxy->to_log |= LW_RSPHDR;
+	}
+	else {
+	    Alert("parsing [%s:%d] : '%s' expects 'cookie' or 'request header' or 'response header'.\n",
 		  file, linenum, args[0]);
 	    return -1;
 	}
-	curproxy->capture_name = strdup(args[2]);
-	curproxy->capture_namelen = strlen(curproxy->capture_name);
-	curproxy->capture_len = atol(args[4]);
-	if (curproxy->capture_len >= CAPTURE_LEN) {
-	    Warning("parsing [%s:%d] : truncating capture length to %d bytes.\n",
-		    file, linenum, CAPTURE_LEN - 1);
-	    curproxy->capture_len = CAPTURE_LEN - 1;
-	}
     }
     else if (!strcmp(args[0], "contimeout")) {  /* connect timeout */
 	if (curproxy->contimeout != defproxy.contimeout) {
@@ -5446,25 +5733,25 @@
 	    return -1;
 	}
 	
+	curproxy->req_add[curproxy->nb_reqadd++] = strdup(args[1]);
+    }
+    else if (!strcmp(args[0], "srvexp") || !strcmp(args[0], "rsprep")) {  /* replace response header from a regex */
+	regex_t *preg;
+	
-	    curproxy->req_add[curproxy->nb_reqadd++] = strdup(args[1]);
+	if (*(args[1]) == 0 || *(args[2]) == 0) {
+	    Alert("parsing [%s:%d] : '%s' expects <search> and <replace> as arguments.\n",
+		  file, linenum, args[0]);
+	    return -1;
 	}
-	else if (!strcmp(args[0], "srvexp") || !strcmp(args[0], "rsprep")) {  /* replace response header from a regex */
-	    regex_t *preg;
-
-	    if (*(args[1]) == 0 || *(args[2]) == 0) {
-		Alert("parsing [%s:%d] : '%s' expects <search> and <replace> as arguments.\n",
-		      file, linenum, args[0]);
-		return -1;
-	    }
-
-	    preg = calloc(1, sizeof(regex_t));
-	    if (regcomp(preg, args[1], REG_EXTENDED) != 0) {
-		Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]);
-		return -1;
-	    }
-	    
-	    chain_regex(&curproxy->rsp_exp, preg, ACT_REPLACE, strdup(args[2]));
+	
+	preg = calloc(1, sizeof(regex_t));
+	if (regcomp(preg, args[1], REG_EXTENDED) != 0) {
+	    Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]);
+	    return -1;
 	}
+	
+	chain_regex(&curproxy->rsp_exp, preg, ACT_REPLACE, strdup(args[2]));
+    }
     else if (!strcmp(args[0], "rspdel")) {  /* delete response header from a regex */
 	regex_t *preg;
 	if (curproxy == &defproxy) {
@@ -5923,6 +6210,34 @@
 	exit(1);
     }
 
+    /* initialize the log header encoding map : '{|}"#' should be encoded with
+     * '#' as prefix, as well as non-printable characters ( <32 or >= 127 ).
+     * URL encoding only requires '"', '#' to be encoded as well as non-
+     * printable characters above.
+     */
+    memset(hdr_encode_map, 0, sizeof(hdr_encode_map));
+    memset(url_encode_map, 0, sizeof(url_encode_map));
+    for (i = 0; i < 32; i++) {
+	FD_SET(i, hdr_encode_map);
+	FD_SET(i, url_encode_map);
+    }
+    for (i = 127; i < 256; i++) {
+	FD_SET(i, hdr_encode_map);
+	FD_SET(i, url_encode_map);
+    }
+
+    tmp = "\"#{|}";
+    while (*tmp) {
+	FD_SET(*tmp, hdr_encode_map);
+	tmp++;
+    }
+
+    tmp = "\"#";
+    while (*tmp) {
+	FD_SET(*tmp, url_encode_map);
+	tmp++;
+    }
+
     pid = getpid();
     progname = *argv;
     while ((tmp = strchr(progname, '/')) != NULL)