[MEDIUM] completely implement the server redirection method

Now when a server has "redir <prefix>" on its config line, any HEAD or GET
request addressing it will lead to a 302 with Location set to "<prefix>"
immediately followed by the relative URI of the incoming request. This makes
it very easy to send redirect to browsers to check remote static servers, as
well as to provide redirection for remote sites when the local one is down.
diff --git a/include/types/session.h b/include/types/session.h
index 46569a5..9437198 100644
--- a/include/types/session.h
+++ b/include/types/session.h
@@ -50,7 +50,7 @@
 #define SN_FRT_ADDR_SET	0x00000080	/* set if the frontend address has been filled */
 #define SN_REDISP	0x00000100	/* set if this session was redispatched from one server to another */
 #define SN_CONN_TAR	0x00000200	/* set if this session is turning around before reconnecting */
-/* unused:              0x00000400 */
+#define SN_REDIRECTABLE	0x00000400	/* set if this session is redirectable (GET or HEAD) */
 /* unused:              0x00000800 */
 
 /* session termination conditions, bits values 0x1000 to 0x7000 (0-7 shift 12) */
diff --git a/src/backend.c b/src/backend.c
index 5ea6590..20e3a04 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1059,6 +1059,13 @@
 		return SRV_STATUS_INTERNAL;
 
 	if (s->flags & SN_ASSIGNED) {
+		if ((s->flags & SN_REDIRECTABLE) && s->srv && s->srv->rdr_len) {
+			/* server scheduled for redirection, and already assigned. We
+			 * don't want to go further nor check the queue.
+			 */
+			return SRV_STATUS_OK;
+		}
+
 		if (s->srv && s->srv->maxqueue > 0 && s->srv->nbpend >= s->srv->maxqueue) {
 			s->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET);
 			s->srv = NULL;
@@ -1084,6 +1091,13 @@
 	err = assign_server(s);
 	switch (err) {
 	case SRV_STATUS_OK:
+		if ((s->flags & SN_REDIRECTABLE) && s->srv && s->srv->rdr_len) {
+			/* server supporting redirection and it is possible.
+			 * Let's report that and ignore maxconn !
+			 */
+			return SRV_STATUS_OK;
+		}
+
 		/* in balance mode, we might have servers with connection limits */
 		if (s->srv &&
 		    s->srv->maxconn && s->srv->cur_sess >= srv_dynamic_maxconn(s->srv)) {
diff --git a/src/proto_http.c b/src/proto_http.c
index 16b2f07..429f571 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -592,6 +592,54 @@
 
 }
 
+/* Parse the URI from the given transaction (which is assumed to be in request
+ * phase) and look for the "/" beginning the PATH. If not found, return NULL.
+ * It is returned otherwise.
+ */
+static char *
+http_get_path(struct http_txn *txn)
+{
+	char *ptr, *end;
+
+	ptr = txn->req.sol + txn->req.sl.rq.u;
+	end = ptr + txn->req.sl.rq.u_l;
+
+	if (ptr >= end)
+		return NULL;
+
+	/* RFC2616, par. 5.1.2 :
+	 * Request-URI = "*" | absuri | abspath | authority
+	 */
+
+	if (*ptr == '*')
+		return NULL;
+
+	if (isalpha((unsigned char)*ptr)) {
+		/* this is a scheme as described by RFC3986, par. 3.1 */
+		ptr++;
+		while (ptr < end &&
+		       (isalnum((unsigned char)*ptr) || *ptr == '+' || *ptr == '-' || *ptr == '.'))
+			ptr++;
+		/* skip '://' */
+		if (ptr == end || *ptr++ != ':')
+			return NULL;
+		if (ptr == end || *ptr++ != '/')
+			return NULL;
+		if (ptr == end || *ptr++ != '/')
+			return NULL;
+	}
+	/* skip [user[:passwd]@]host[:[port]] */
+
+	while (ptr < end && *ptr != '/')
+		ptr++;
+
+	if (ptr == end)
+		return NULL;
+
+	/* OK, we got the '/' ! */
+	return ptr;
+}
+
 /* Processes the client and server jobs of a session task, then
  * puts it back to the wait queue in a clean state, or
  * cleans up its resources if it must be deleted. Returns
@@ -2451,9 +2499,59 @@
 
 			do {
 				/* first, get a connection */
+				if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)
+					t->flags |= SN_REDIRECTABLE;
+
 				if (srv_redispatch_connect(t))
 					return t->srv_state != SV_STIDLE;
 
+				if ((t->flags & SN_REDIRECTABLE) && t->srv && t->srv->rdr_len) {
+					/* Server supporting redirection and it is possible.
+					 * Invalid requests are reported as such. It concerns all
+					 * the largest ones.
+					 */
+					struct chunk rdr;
+					char *path;
+					int len;
+
+					/* 1: create the response header */
+					rdr.len = strlen(HTTP_302);
+					rdr.str = trash;
+					memcpy(rdr.str, HTTP_302, rdr.len);
+
+					/* 2: add the server's prefix */
+					if (rdr.len + t->srv->rdr_len > sizeof(trash))
+						goto cancel_redir;
+
+					memcpy(rdr.str + rdr.len, t->srv->rdr_pfx, t->srv->rdr_len);
+					rdr.len += t->srv->rdr_len;
+
+					/* 3: add the request URI */
+					path = http_get_path(txn);
+					if (!path)
+						goto cancel_redir;
+					len = txn->req.sl.rq.u_l + (txn->req.sol+txn->req.sl.rq.u) - path;
+					if (rdr.len + len > sizeof(trash) - 4) /* 4 for CRLF-CRLF */
+						goto cancel_redir;
+
+					memcpy(rdr.str + rdr.len, path, len);
+					rdr.len += len;
+					memcpy(rdr.str + rdr.len, "\r\n\r\n", 4);
+					rdr.len += 4;
+
+					srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C, 302, &rdr);
+					/* FIXME: we should increase a counter of redirects per server and per backend. */
+					if (t->srv)
+						t->srv->cum_sess++;
+					return 1;
+				cancel_redir:
+					txn->status = 400;
+					t->fe->failed_req++;
+					srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C,
+							   400, error_message(t, HTTP_ERR_400));
+					return 1;
+				}
+
 				/* try to (re-)connect to the server, and fail if we expire the
 				 * number of retries.
 				 */
@@ -5226,39 +5324,9 @@
 		/* ensure the indexes are not affected */
 		return 0;
 
-	ptr = txn->req.sol + txn->req.sl.rq.u;
-	end = ptr + txn->req.sl.rq.u_l;
-
-	if (ptr >= end)
-		return 0;
-
-	/* RFC2616, par. 5.1.2 :
-	 * Request-URI = "*" | absuri | abspath | authority
-	 */
-
-	if (*ptr == '*')
-		return 0;
-
-	if (isalpha((unsigned char)*ptr)) {
-		/* this is a scheme as described by RFC3986, par. 3.1 */
-		ptr++;
-		while (ptr < end &&
-		       (isalnum((unsigned char)*ptr) || *ptr == '+' || *ptr == '-' || *ptr == '.'))
-			ptr++;
-		/* skip '://' */
-		if (ptr == end || *ptr++ != ':')
-			return 0;
-		if (ptr == end || *ptr++ != '/')
-			return 0;
-		if (ptr == end || *ptr++ != '/')
-			return 0;
-	}
-	/* skip [user[:passwd]@]host[:[port]] */
-
-	while (ptr < end && *ptr != '/')
-		ptr++;
-
-	if (ptr == end)
+	end = txn->req.sol + txn->req.sl.rq.u + txn->req.sl.rq.u_l;
+	ptr = http_get_path(txn);
+	if (!ptr)
 		return 0;
 
 	/* OK, we got the '/' ! */