[MAJOR] http: completely process the "connection" header

Up to now, we only had a flag in the session indicating if it had to
work in "connection: close" mode. This is not at all compatible with
keep-alive.

Now we ensure that both sides of a connection act independantly and
only relative to the transaction. The HTTP version of the request
and response is also correctly considered. The connection already
knows several modes :
  - tunnel (CONNECT or no option in the config)
  - keep-alive (when permitted by configuration)
  - server-close (close the server side, not the client)
  - close (close both sides)

This change carefully detects all situations to find whether a request
can be fully processed in its mode according to the configuration. Then
the response is also checked and tested to fix corner cases which can
happen with different HTTP versions on both sides (eg: a 1.0 client
asks for explicit keep-alive, and the server responds with 1.1 without
a header).

The mode is selected by a capability elimination algorithm which
automatically focuses on the least capable agent between the client,
the frontend, the backend and the server. This ensures we won't get
undesired situtations where one of the 4 "agents" is not able to
process a transaction.

No "Connection: close" header will be added anymore to HTTP/1.0 requests
or responses since they're already in close mode.

The server-close mode is still not completely implemented. The response
needs to be rewritten as keep-alive before being sent to the client if
the connection was already in server-close (which implies the request
was in keep-alive) and if the response has a content-length or a
transfer-encoding (but only if client supports 1.1).

A later improvement in server-close mode would probably be to detect
some situations where it's interesting to close the response (eg:
redirections with remote locations). But even then, the client might
close by itself.

It's also worth noting that in tunnel mode, no connection header is
affected in either direction. A tunnelled connection should theorically
be notified at the session level, but this is useless since by definition
there will not be any more requests on it. Thus, we don't need to add a
flag into the session right now.
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index 849b494..2fa0d59 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -80,9 +80,9 @@
 #define TX_CACHE_COOK	0x00002000	/* a cookie in the response is cacheable */
 #define TX_CACHE_SHIFT	12		/* bit shift */
 
-/* client and server keep-alive capability */
-#define TX_CLI_CONN_KA	0x00004000	/* the client-side connection is still keep-alive capable */
-#define TX_SRV_CONN_KA	0x00008000	/* the server-side connection is still keep-alive capable */
+/* request and response HTTP version */
+#define TX_REQ_VER_11	0x00004000	/* the request is HTTP/1.1 or above */
+#define TX_RES_VER_11	0x00008000	/* the response is HTTP/1.1 or above */
 
 /* report presence of transfer-encoding:chunked and content-length headers */
 #define TX_REQ_CNT_LEN	0x00010000	/* content-length present in the request */
@@ -90,6 +90,23 @@
 #define TX_RES_CNT_LEN	0x00040000	/* content-length present in the response */
 #define TX_RES_TE_CHNK	0x00080000	/* transfer-encoding: chunked present in the response */
 
+/* indicate how we *want* the connection to behave, regardless of what is in
+ * the headers. We have 4 possible values right now :
+ * - WANT_TUN : will be a tunnel (default when nothing configured or with CONNECT).
+ * - WANT_KAL : try to maintain keep-alive
+ * - WANT_SCL : enforce close on the server side
+ * - WANT_CLO : enforce close on both sides
+ */
+#define TX_CON_WANT_TUN 0x00000000	/* note: it's important that it is 0 (init) */
+#define TX_CON_WANT_KAL 0x00100000
+#define TX_CON_WANT_SCL 0x00200000
+#define TX_CON_WANT_CLO 0x00300000
+#define TX_CON_WANT_MSK 0x00300000	/* this is the mask to get the bits */
+
+#define TX_CON_HDR_PARS	0x00400000	/* "connection" header already parsed (req or res) */
+#define TX_REQ_CONN_CLO	0x00800000	/* request asks for "Connection: close" mode */
+
+
 /* The HTTP parser is more complex than it looks like, because we have to
  * support multi-line headers and any number of spaces between the colon and
  * the value.
diff --git a/include/types/session.h b/include/types/session.h
index 64ba186..1af52ce 100644
--- a/include/types/session.h
+++ b/include/types/session.h
@@ -1,23 +1,23 @@
 /*
-  include/types/session.h
-  This file defines everything related to sessions.
-
-  Copyright (C) 2000-2008 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
-*/
+ * include/types/session.h
+ * This file defines everything related to sessions.
+ *
+ * Copyright (C) 2000-2009 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 _TYPES_SESSION_H
 #define _TYPES_SESSION_H
@@ -45,7 +45,8 @@
 #define SN_ASSIGNED	0x00000002	/* no need to assign a server to this session */
 #define SN_ADDR_SET	0x00000004	/* this session's server address has been set */
 #define SN_BE_ASSIGNED	0x00000008	/* a backend was assigned. Conns are accounted. */
-#define SN_CONN_CLOSED	0x00000010	/* "Connection: close" was present or added */
+
+/* unused:              0x00000010 */
 #define SN_MONITOR	0x00000020	/* this session comes from a monitoring system */
 #define SN_CURR_SESS	0x00000040	/* a connection is currently being counted on the server */
 #define SN_FRT_ADDR_SET	0x00000080	/* set if the frontend address has been filled */
diff --git a/src/proto_http.c b/src/proto_http.c
index 4ba7c90..d232148 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -1747,6 +1747,63 @@
 	return 1;
 }
 
+/* Parse the Connection: headaer of an HTTP request, and set the transaction
+ * flag TX_REQ_CONN_CLO if a "close" mode is expected. The TX_CON_HDR_PARS flag
+ * is also set so that we don't parse a second time. If some dangerous values
+ * are encountered, we leave the status to indicate that the request might be
+ * interpreted as keep-alive, but we also set the connection flags to indicate
+ * that we WANT it to be a close, so that the header will be fixed. This
+ * function should only be called when we know we're interested in checking
+ * the request (not a CONNECT, and FE or BE mangles the header).
+ */
+void http_parse_connection_header(struct http_txn *txn)
+{
+	struct http_msg *msg = &txn->req;
+	struct hdr_ctx ctx;
+	int conn_cl, conn_ka;
+
+	if (txn->flags & TX_CON_HDR_PARS)
+		return;
+
+	conn_cl = 0;
+	conn_ka = 0;
+	ctx.idx = 0;
+
+	while (http_find_header2("Connection", 10, msg->sol, &txn->hdr_idx, &ctx)) {
+		if (ctx.vlen == 5 && strncasecmp(ctx.line + ctx.val, "close", 5) == 0)
+			conn_cl = 1;
+		else if (ctx.vlen == 10 && strncasecmp(ctx.line + ctx.val, "keep-alive", 10) == 0)
+			conn_ka = 1;
+	}
+
+	/* Determine if the client wishes keep-alive or close.
+	 * RFC2616 #8.1.2 and #14.10 state that HTTP/1.1 and above connections
+	 * are persistent unless "Connection: close" is explicitly specified.
+	 * RFC2616 #19.6.2 refers to RFC2068 for HTTP/1.0 persistent connections.
+	 * RFC2068 #19.7.1 states that HTTP/1.0 clients are not persistent unless
+	 * they explicitly specify "Connection: Keep-Alive", regardless of any
+	 * optional "Keep-Alive" header.
+	 * Note that if we find a request with both "Connection: close" and
+	 * "Connection: Keep-Alive", we indicate we want a close but don't have
+	 * it, so that it can be enforced later.
+	 */
+
+	if (txn->flags & TX_REQ_VER_11) {	/* HTTP/1.1 */
+		if (conn_cl) {
+			txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_CLO;
+			if (!conn_ka)
+				txn->flags |= TX_REQ_CONN_CLO;
+		}
+	} else {	/* HTTP/1.0 */
+		if (!conn_ka)
+			txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_CLO | TX_REQ_CONN_CLO;
+		else if (conn_cl)
+			txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_CLO;
+	}
+	txn->flags |= TX_CON_HDR_PARS;
+}
+
+
 /* This stream analyser waits for a complete HTTP request. It returns 1 if the
  * processing can continue on next analysers, or zero if it either needs more
  * data or wants to immediately abort the request (eg: timeout, error, ...). It
@@ -1781,7 +1838,6 @@
 	struct http_txn *txn = &s->txn;
 	struct http_msg *msg = &txn->req;
 	struct hdr_ctx ctx;
-	int conn_ka, conn_cl;
 
 	DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n",
 		now_ms, __FUNCTION__,
@@ -2012,6 +2068,16 @@
 	if (unlikely(msg->sl.rq.v_l == 0) && !http_upgrade_v09_to_v10(req, msg, txn))
 		goto return_bad_req;
 
+	/* ... and check if the request is HTTP/1.1 or above */
+	if ((msg->sl.rq.v_l == 8) &&
+	    ((req->data[msg->som + msg->sl.rq.v + 5] > '1') ||
+	     ((req->data[msg->som + msg->sl.rq.v + 5] == '1') &&
+	      (req->data[msg->som + msg->sl.rq.v + 7] >= '1'))))
+		txn->flags |= TX_REQ_VER_11;
+
+	/* "connection" has not been parsed yet */
+	txn->flags &= ~TX_CON_HDR_PARS;
+
 	/* 5: we may need to capture headers */
 	if (unlikely((s->logs.logwait & LW_REQHDR) && s->fe->req_cap))
 		capture_headers(req->data + msg->som, &txn->hdr_idx,
@@ -2065,45 +2131,6 @@
 		msg->hdr_content_len = cl;
 	}
 
-	/* Determine if the client wishes keep-alive or close.
-	 * RFC2616 #8.1.2 and #14.10 state that HTTP/1.1 and above connections
-	 * are persistent unless "Connection: close" is explicitly specified.
-	 * RFC2616 #19.6.2 refers to RFC2068 for HTTP/1.0 persistent connections.
-	 * RFC2068 #19.7.1 states that HTTP/1.0 clients are not persistent unless
-	 * they explicitly specify "Connection: keep-alive", regardless of any
-	 * optional "keep-alive" header.
-	 */
-
-	/* FIXME: should we also remove any header specified in "connection" ? */
-	conn_ka = conn_cl = 0;
-	ctx.idx = 0;
-	while (http_find_header2("Connection", 10, msg->sol, &txn->hdr_idx, &ctx)) {
-		if (ctx.vlen == 5 && strncasecmp(ctx.line + ctx.val, "close", 5) == 0)
-			conn_cl = 1;
-		else if (ctx.vlen == 10 && strncasecmp(ctx.line + ctx.val, "keep-alive", 10) == 0)
-			conn_ka = 1;
-	}
-
-	/* prepare flags for this transaction */
-	txn->flags |= (TX_CLI_CONN_KA | TX_SRV_CONN_KA);
-	txn->flags |= (TX_CLI_CONN_KA | TX_SRV_CONN_KA);
-
-	if ((msg->sl.rq.v_l == 8) &&
-	    (req->data[msg->som + msg->sl.rq.v + 5] == '1') &&
-	    (req->data[msg->som + msg->sl.rq.v + 7] == '0')) {
-		/* HTTP/1.0 */
-		if (!conn_ka)
-			txn->flags &= ~(TX_CLI_CONN_KA | TX_SRV_CONN_KA);
-	} else {
-		/* HTTP/1.1 */
-		if (conn_cl)
-			txn->flags &= ~(TX_CLI_CONN_KA | TX_SRV_CONN_KA);
-	}
-
-	/* we can mark the connection as non-persistent if needed */
-	if (!(txn->flags & TX_SRV_CONN_KA))
-		s->flags |= SN_CONN_CLOSED;
-
 	/* end of job, return OK */
 	req->analysers &= ~an_bit;
 	req->analyse_exp = TICK_ETERNITY;
@@ -2222,68 +2249,88 @@
 		}
 	}
 
-	/* We might have to check for "Connection:". The test for persistent
-	 * connections has already been performed, so we only enter here if
-	 * we are certain the connection is persistent.
+	/* Until set to anything else, the connection mode is set as TUNNEL. It will
+	 * only change if both the request and the config reference something else.
 	 */
-	if (((s->fe->options | s->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO)) &&
-	    !(s->flags & SN_CONN_CLOSED)) {
+
+	if ((s->txn.meth != HTTP_METH_CONNECT) &&
+	    ((s->fe->options|s->be->options) & (PR_O_KEEPALIVE|PR_O_HTTP_CLOSE|PR_O_FORCE_CLO))) {
+		int tmp = TX_CON_WANT_TUN;
+		if ((s->fe->options|s->be->options) & PR_O_KEEPALIVE)
+			tmp = TX_CON_WANT_KAL;
+		/* FIXME: for now, we don't support server-close mode */
+		if ((s->fe->options|s->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO))
+			tmp = TX_CON_WANT_CLO;
+
+		if (!(txn->flags & TX_CON_HDR_PARS))
+			http_parse_connection_header(txn);
+
+		if ((txn->flags & TX_CON_WANT_MSK) < tmp)
+			txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | tmp;
+	}
+
+	/* We're really certain of the connection mode (tunnel, close, keep-alive)
+	 * once we know the backend, because the tunnel mode can be implied by the
+	 * lack of any close/keepalive options in both the FE and the BE. Since
+	 * this information can evolve with time, we proceed by trying to make the
+	 * header status match the desired status. For this, we'll have to adjust
+	 * the "Connection" header. The test for persistent connections has already
+	 * been performed, so we only enter here if there is a risk the connection
+	 * is considered as persistent and we want it to be closed on the server
+	 * side. It would be nice if we could enter this place only when a
+	 * Connection header exists. Note that a CONNECT method will not enter
+	 * here.
+	 */
+	if (!(txn->flags & TX_REQ_CONN_CLO) && ((txn->flags & TX_CON_WANT_MSK) >= TX_CON_WANT_SCL)) {
 		char *cur_ptr, *cur_end, *cur_next;
 		int old_idx, delta, val;
+		int must_delete;
 		struct hdr_idx_elem *cur_hdr;
 
+		must_delete = !(txn->flags & TX_REQ_VER_11);
 		cur_next = req->data + txn->req.som + hdr_idx_first_pos(&txn->hdr_idx);
-		old_idx = 0;
 
-		while ((cur_idx = txn->hdr_idx.v[old_idx].next)) {
+		for (old_idx = 0; (cur_idx = txn->hdr_idx.v[old_idx].next); old_idx = cur_idx) {
 			cur_hdr  = &txn->hdr_idx.v[cur_idx];
 			cur_ptr  = cur_next;
 			cur_end  = cur_ptr + cur_hdr->len;
 			cur_next = cur_end + cur_hdr->cr + 1;
 
 			val = http_header_match2(cur_ptr, cur_end, "Connection", 10);
-			if (val) {
-				/* 3 possibilities :
-				 * - we have already set Connection: close,
-				 *   so we remove this line.
-				 * - we have not yet set Connection: close,
-				 *   but this line indicates close. We leave
-				 *   it untouched and set the flag.
-				 * - we have not yet set Connection: close,
-				 *   and this line indicates non-close. We
-				 *   replace it.
-				 */
-				if (s->flags & SN_CONN_CLOSED) {
-					delta = buffer_replace2(req, cur_ptr, cur_next, NULL, 0);
-					http_msg_move_end(&txn->req, delta);
+			if (!val)
+				continue;
+
+			/* 3 possibilities :
+			 * - we have already set "Connection: close" or we're in
+			 *   HTTP/1.0, so we remove this line.
+			 * - we have not yet set "Connection: close", but this line
+			 *   indicates close. We leave it untouched and set the flag.
+			 * - we have not yet set "Connection: close", and this line
+			 *   indicates non-close. We replace it and set the flag.
+			 */
+			if (must_delete) {
+				delta = buffer_replace2(req, cur_ptr, cur_next, NULL, 0);
+				http_msg_move_end(&txn->req, delta);
+				cur_next += delta;
+				txn->hdr_idx.v[old_idx].next = cur_hdr->next;
+				txn->hdr_idx.used--;
+				cur_hdr->len = 0;
+				txn->flags |= TX_REQ_CONN_CLO;
+			} else {
+				if (cur_end - cur_ptr - val != 5 ||
+				    strncasecmp(cur_ptr + val, "close", 5) != 0) {
+					delta = buffer_replace2(req, cur_ptr + val, cur_end,
+								"close", 5);
 					cur_next += delta;
-					txn->hdr_idx.v[old_idx].next = cur_hdr->next;
-					txn->hdr_idx.used--;
-					cur_hdr->len = 0;
-				} else {
-					if (strncasecmp(cur_ptr + val, "close", 5) != 0) {
-						delta = buffer_replace2(req, cur_ptr + val, cur_end,
-									"close", 5);
-						cur_next += delta;
-						cur_hdr->len += delta;
-						http_msg_move_end(&txn->req, delta);
-					}
-					s->flags |= SN_CONN_CLOSED;
-					txn->flags &= ~TX_SRV_CONN_KA; /* keep-alive closed on server side */
+					cur_hdr->len += delta;
+					http_msg_move_end(&txn->req, delta);
 				}
+				txn->flags |= TX_REQ_CONN_CLO;
+				must_delete = 1;
 			}
-			old_idx = cur_idx;
-		}
+		} /* for loop */
+	} /* if must close keep-alive */
 
-		/* if there is no "Connection: keep-alive" header left and we're
-		 * in HTTP/1.0, then non-persistent connection is implied */
-		if (!(s->flags & SN_CONN_CLOSED) && (msg->sl.rq.v_l == 8) &&
-		    (req->data[msg->som + msg->sl.rq.v + 5] == '1') &&
-		    (req->data[msg->som + msg->sl.rq.v + 7] == '0')) {
-			s->flags |= SN_CONN_CLOSED;
-			txn->flags &= ~TX_SRV_CONN_KA; /* keep-alive closed on server side */
-		}
-	}
 	/* add request headers from the rule sets in the same order */
 	for (cur_idx = 0; cur_idx < px->nb_reqadd; cur_idx++) {
 		if (unlikely(http_header_add_tail(req,
@@ -2408,9 +2455,11 @@
 		}
 	}
 
-	/* We can shut read side if "connection: close" && !abort_on_close && !content-length */
-	if ((s->flags & SN_CONN_CLOSED) && !(s->be->options & PR_O_ABRT_CLOSE) &&
-	    !(txn->flags & TX_REQ_TE_CHNK) && !txn->req.hdr_content_len && (txn->meth < HTTP_METH_CONNECT))
+	/* We can shut read side if "connection: close" && no-data && !tunnel && !abort_on_close */
+	if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_CLO &&
+	    !(txn->flags & TX_REQ_TE_CHNK) && !txn->req.hdr_content_len &&
+	    (txn->meth != HTTP_METH_CONNECT) &&
+	    !(s->be->options & PR_O_ABRT_CLOSE))
 		req->flags |= BF_DONT_READ;
 
 	/* that's OK for us now, let's move on to next analysers */
@@ -2617,22 +2666,12 @@
 		}
 	}
 
-
-	/* indicate in the session if it will be a tunnel-mode one or not. If
-	 * we don't intend to analyse contents after the first request, it's a
-	 * tunnel.
-	 */
-	if (s->txn.meth == HTTP_METH_CONNECT ||
-	    !((s->fe->options|s->be->options) & (PR_O_KEEPALIVE|PR_O_HTTP_CLOSE|PR_O_FORCE_CLO)))
-		s->flags |= SN_TUNNEL;
-
 	/* 11: add "Connection: close" if needed and not yet set. */
-	if (!(s->flags & SN_CONN_CLOSED) &&
-	    ((s->fe->options | s->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO))) {
+	if (!(txn->flags & TX_REQ_CONN_CLO) && ((txn->flags & TX_CON_WANT_MSK) >= TX_CON_WANT_SCL)) {
 		if (unlikely(http_header_add_tail2(req, &txn->req, &txn->hdr_idx,
 						   "Connection: close", 17)) < 0)
 			goto return_bad_req;
-		s->flags |= SN_CONN_CLOSED;
+		txn->flags |= TX_REQ_CONN_CLO;
 	}
 
 	/* If we have no server assigned yet and we're balancing on url_param
@@ -2868,7 +2907,6 @@
 	struct http_txn *txn = &s->txn;
 	struct http_msg *msg = &txn->rsp;
 	struct hdr_ctx ctx;
-	int conn_ka, conn_cl;
 	int cur_idx;
 	int n;
 
@@ -3073,6 +3111,16 @@
 		n = 0;
 	s->srv->counters.p.http.rsp[n]++;
 
+	/* check if the response is HTTP/1.1 or above */
+	if ((msg->sl.st.v_l == 8) &&
+	    ((rep->data[msg->som + 5] > '1') ||
+	     ((rep->data[msg->som + 5] == '1') &&
+	      (rep->data[msg->som + 7] >= '1'))))
+		txn->flags |= TX_RES_VER_11;
+
+	/* "connection" has not been parsed yet */
+	txn->flags &= ~TX_CON_HDR_PARS;
+
 	txn->status = strl2ui(rep->data + msg->sl.st.c, msg->sl.st.c_l);
 
 	if (txn->status >= 100 && txn->status < 500)
@@ -3177,41 +3225,6 @@
 	}
 
 skip_content_length:
-	/* Determine if the server wishes keep-alive or close.
-	 * RFC2616 #8.1.2 and #14.10 state that HTTP/1.1 and above connections
-	 * are persistent unless "Connection: close" is explicitly specified.
-	 * RFC2616 #19.6.2 refers to RFC2068 for HTTP/1.0 persistent connections.
-	 * RFC2068 #19.7.1 states that HTTP/1.0 clients are not persistent unless
-	 * they explicitly specify "Connection: keep-alive", regardless of any
-	 * optional "keep-alive" header.
-	 */
-
-	/* FIXME: should we also remove any header specified in "connection" ? */
-	conn_ka = conn_cl = 0;
-	ctx.idx = 0;
-	while (http_find_header2("Connection", 10, msg->sol, &txn->hdr_idx, &ctx)) {
-		if (ctx.vlen == 5 && strncasecmp(ctx.line + ctx.val, "close", 5) == 0)
-			conn_cl = 1;
-		else if (ctx.vlen == 10 && strncasecmp(ctx.line + ctx.val, "keep-alive", 10) == 0)
-			conn_ka = 1;
-	}
-
-	if ((msg->sl.st.v_l == 8) &&
-	    (rep->data[msg->som + 5] == '1') &&
-	    (rep->data[msg->som + 7] == '0')) {
-		/* HTTP/1.0 */
-		if (!conn_ka)
-			txn->flags &= ~TX_SRV_CONN_KA;
-	} else {
-		/* HTTP/1.1 */
-		if (conn_cl)
-			txn->flags &= ~TX_SRV_CONN_KA;;
-	}
-
-	/* we can mark the connection as non-persistent if needed */
-	s->flags &= ~SN_CONN_CLOSED; /* prepare for inspection */
-	if (!(txn->flags & TX_SRV_CONN_KA))
-		s->flags |= SN_CONN_CLOSED;
 
 	/* end of job, return OK */
 	rep->analysers &= ~an_bit;
@@ -3228,10 +3241,11 @@
 int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, struct proxy *px)
 {
 	struct http_txn *txn = &t->txn;
-	struct buffer *req = t->req;
 	struct http_msg *msg = &txn->rsp;
 	struct proxy *cur_proxy;
 	int cur_idx;
+	int conn_ka = 0, conn_cl = 0;
+	int must_close = 0;
 
 	DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n",
 		now_ms, __FUNCTION__,
@@ -3248,6 +3262,135 @@
 	rep->analysers &= ~an_bit;
 	rep->analyse_exp = TICK_ETERNITY;
 
+	/* Now we have to check if we need to modify the Connection header.
+	 * This is more difficult on the response than it is on the request,
+	 * because we can have two different HTTP versions and we don't know
+	 * how the client will interprete a response. For instance, let's say
+	 * that the client sends a keep-alive request in HTTP/1.0 and gets an
+	 * HTTP/1.1 response without any header. Maybe it will bound itself to
+	 * HTTP/1.0 because it only knows about it, and will consider the lack
+	 * of header as a close, or maybe it knows HTTP/1.1 and can consider
+	 * the lack of header as a keep-alive. Thus we will use two flags
+	 * indicating how a request MAY be understood by the client. In case
+	 * of multiple possibilities, we'll fix the header to be explicit. If
+	 * ambiguous cases such as both close and keepalive are seen, then we
+	 * will fall back to explicit close. Note that we won't take risks with
+	 * HTTP/1.0 clients which may not necessarily understand keep-alive.
+	 */
+
+	if ((txn->meth != HTTP_METH_CONNECT) &&
+	    (txn->status >= 200) &&
+	    (txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN &&
+	    !(txn->flags & TX_CON_HDR_PARS)) {
+		int may_keep = 0, may_close = 0; /* how it may be understood */
+		struct hdr_ctx ctx;
+
+		ctx.idx = 0;
+		while (http_find_header2("Connection", 10, msg->sol, &txn->hdr_idx, &ctx)) {
+			if (ctx.vlen == 5 && strncasecmp(ctx.line + ctx.val, "close", 5) == 0)
+				conn_cl = 1;
+			else if (ctx.vlen == 10 && strncasecmp(ctx.line + ctx.val, "keep-alive", 10) == 0)
+				conn_ka = 1;
+		}
+
+		if (conn_cl) {
+			/* close header present */
+			may_close = 1;
+			if (conn_ka)	/* we have both close and keep-alive */
+				may_keep = 1;
+		}
+		else if (conn_ka) {
+			/* keep-alive alone */
+			may_keep = 1;
+		}
+		else {
+			/* no close nor keep-alive header */
+			if (txn->flags & TX_RES_VER_11)
+				may_keep = 1;
+			else
+				may_close = 1;
+
+			if (txn->flags & TX_REQ_VER_11)
+				may_keep = 1;
+			else
+				may_close = 1;
+		}
+
+		/* let's update the transaction status to reflect any close.
+		 * Note that ambiguous cases with keep & close will also be
+		 * handled.
+		 */
+		if (may_close)
+			txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_CLO;
+
+		/* Now we must adjust the response header :
+		 *  - set "close" if may_keep and WANT_CLO
+		 *  - remove "close" if WANT_SCL and REQ_1.1 and may_close and (content-length or TE_CHNK)
+		 *  - add "keep-alive" if WANT_SCL and REQ_1.0 and may_close and content-length
+		 *
+		 * Until we support the server-close mode, we'll only support the set "close".
+		 */
+		if (may_keep && (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_CLO)
+			must_close = 1;
+
+		txn->flags |= TX_CON_HDR_PARS;
+	}
+
+	/* We might have to check for "Connection:" if the server
+	 * returns a connection status that is not compatible with
+	 * the client's or with the config.
+	 */
+	if ((txn->status >= 200) && must_close && (conn_cl|conn_ka)) {
+		char *cur_ptr, *cur_end, *cur_next;
+		int cur_idx, old_idx, delta, val;
+		int must_delete;
+		struct hdr_idx_elem *cur_hdr;
+
+		/* we just have to remove the headers if both sides are 1.0 */
+		must_delete = !(txn->flags & TX_REQ_VER_11) && !(txn->flags & TX_RES_VER_11);
+		cur_next = rep->data + txn->rsp.som + hdr_idx_first_pos(&txn->hdr_idx);
+
+		for (old_idx = 0; (cur_idx = txn->hdr_idx.v[old_idx].next); old_idx = cur_idx) {
+			cur_hdr  = &txn->hdr_idx.v[cur_idx];
+			cur_ptr  = cur_next;
+			cur_end  = cur_ptr + cur_hdr->len;
+			cur_next = cur_end + cur_hdr->cr + 1;
+
+			val = http_header_match2(cur_ptr, cur_end, "Connection", 10);
+			if (!val)
+				continue;
+
+			/* 3 possibilities :
+			 * - we have already set "Connection: close" or we're in
+			 *   HTTP/1.0, so we remove this line.
+			 * - we have not yet set "Connection: close", but this line
+			 *   indicates close. We leave it untouched and set the flag.
+			 * - we have not yet set "Connection: close", and this line
+			 *   indicates non-close. We replace it and set the flag.
+			 */
+			if (must_delete) {
+				delta = buffer_replace2(rep, cur_ptr, cur_next, NULL, 0);
+				http_msg_move_end(&txn->rsp, delta);
+				cur_next += delta;
+				txn->hdr_idx.v[old_idx].next = cur_hdr->next;
+				txn->hdr_idx.used--;
+				cur_hdr->len = 0;
+				must_close = 0;
+			} else {
+				if (cur_end - cur_ptr - val != 5 ||
+				    strncasecmp(cur_ptr + val, "close", 5) != 0) {
+					delta = buffer_replace2(rep, cur_ptr + val, cur_end,
+								"close", 5);
+					cur_next += delta;
+					cur_hdr->len += delta;
+					http_msg_move_end(&txn->rsp, delta);
+				}
+				must_delete = 1;
+				must_close = 0;
+			}
+		} /* for loop */
+	} /* if must close keep-alive */
+
 	if (1) {
 		/*
 		 * 3: we will have to evaluate the filters.
@@ -3296,66 +3439,6 @@
 				goto return_srv_prx_502;
 			}
 
-			/* We might have to check for "Connection:" */
-			if (((t->fe->options | t->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO)) &&
-			    !(t->flags & SN_CONN_CLOSED) &&
-			    txn->status >= 200) {
-				char *cur_ptr, *cur_end, *cur_next;
-				int cur_idx, old_idx, delta, val;
-				struct hdr_idx_elem *cur_hdr;
-
-				cur_next = rep->data + txn->rsp.som + hdr_idx_first_pos(&txn->hdr_idx);
-				old_idx = 0;
-
-				while ((cur_idx = txn->hdr_idx.v[old_idx].next)) {
-					cur_hdr  = &txn->hdr_idx.v[cur_idx];
-					cur_ptr  = cur_next;
-					cur_end  = cur_ptr + cur_hdr->len;
-					cur_next = cur_end + cur_hdr->cr + 1;
-
-					val = http_header_match2(cur_ptr, cur_end, "Connection", 10);
-					if (val) {
-						/* 3 possibilities :
-						 * - we have already set Connection: close,
-						 *   so we remove this line.
-						 * - we have not yet set Connection: close,
-						 *   but this line indicates close. We leave
-						 *   it untouched and set the flag.
-						 * - we have not yet set Connection: close,
-						 *   and this line indicates non-close. We
-						 *   replace it.
-						 */
-						if (t->flags & SN_CONN_CLOSED) {
-							delta = buffer_replace2(rep, cur_ptr, cur_next, NULL, 0);
-							http_msg_move_end(&txn->rsp, delta);
-							cur_next += delta;
-							txn->hdr_idx.v[old_idx].next = cur_hdr->next;
-							txn->hdr_idx.used--;
-							cur_hdr->len = 0;
-						} else {
-							if (strncasecmp(cur_ptr + val, "close", 5) != 0) {
-								delta = buffer_replace2(rep, cur_ptr + val, cur_end,
-											"close", 5);
-								cur_next += delta;
-								cur_hdr->len += delta;
-								http_msg_move_end(&txn->rsp, delta);
-							}
-							t->flags |= SN_CONN_CLOSED;
-						}
-					}
-					old_idx = cur_idx;
-				}
-			}
-
-			/* if there is no "Connection: keep-alive" header left and we're
-			 * in HTTP/1.0, then non-persistent connection is implied */
-			if (!(t->flags & SN_CONN_CLOSED) && (msg->sl.st.v_l == 8) &&
-			    (rep->data[msg->som + 5] == '1') &&
-			    (rep->data[msg->som + 7] == '0')) {
-				t->flags |= SN_CONN_CLOSED;
-				txn->flags &= ~TX_CLI_CONN_KA; /* keep-alive closed on server side */
-			}
-
 			/* add response headers from the rule sets in the same order */
 			for (cur_idx = 0; cur_idx < rule_set->nb_rspadd; cur_idx++) {
 				if (txn->status < 200)
@@ -3425,7 +3508,6 @@
 			}
 		}
 
-
 		/*
 		 * 7: check if result will be cacheable with a cookie.
 		 * We'll block the response if security checks have caught
@@ -3456,18 +3538,15 @@
 		}
 
 		/*
-		 * 8: add "Connection: close" if needed and not yet set.
-		 * Note that we do not need to add it in case of HTTP/1.0.
+		 * 8: add "Connection: close" if needed and not yet set. This is
+		 * only needed for 1.1 responses since we know there is no other
+		 * Connection header.
 		 */
-		if (!(t->flags & SN_CONN_CLOSED) &&
-		    ((t->fe->options | t->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO)) &&
-		    txn->status >= 200) {
-			if ((unlikely(msg->sl.st.v_l != 8) ||
-			     unlikely(req->data[msg->som + 7] != '0')) &&
-			    unlikely(http_header_add_tail2(rep, &txn->rsp, &txn->hdr_idx,
+		if (txn->status >= 200 && must_close && (txn->flags & TX_RES_VER_11)) {
+			if (unlikely(http_header_add_tail2(rep, &txn->rsp, &txn->hdr_idx,
 							   "Connection: close", 17)) < 0)
 				goto return_bad_resp;
-			t->flags |= SN_CONN_CLOSED;
+			must_close = 0;
 		}
 
 		/*