[MAJOR] get rid of SV_STANALYZE (step 2)

The SV_STANALYZE state was installed on the server side but was really
meant to be processed with the rest of the request on the client side.
It suffered from several issues, mostly related to the way timeouts were
handled while waiting for data.

All known issues related to timeouts during a request - and specifically
a request involving body processing - have been raised and fixed. At this
point, the code is a bit dirty but works fine, so next steps might be
cleanups with an ability to come back to the current state in case of
trouble.
diff --git a/src/backend.c b/src/backend.c
index 48859b0..2d5be17 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1209,7 +1209,7 @@
 
 	/* if the message is chunked, we skip the chunk size, but use the value as len */
 	http_find_header2("Transfer-Encoding", 17, msg->sol, &txn->hdr_idx, &ctx);
-	if ( ctx.idx && strncasecmp(ctx.line+ctx.val,"chunked",ctx.vlen)==0) {
+	if (ctx.idx && ctx.vlen >= 7 && strncasecmp(ctx.line+ctx.val, "chunked", 7) == 0) {
 		unsigned int chunk = 0;
 		while ( params < req->rlim && !HTTP_IS_CRLF(*params)) {
 			char c = *params;
diff --git a/src/proto_http.c b/src/proto_http.c
index 83406b6..df037bd 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -366,7 +366,7 @@
 
 #ifdef DEBUG_FULL
 static char *cli_stnames[4] = { "DAT", "SHR", "SHW", "CLS" };
-static char *srv_stnames[8] = { "IDL", "ANA", "CON", "HDR", "DAT", "SHR", "SHW", "CLS" };
+static char *srv_stnames[7] = { "IDL", "CON", "HDR", "DAT", "SHR", "SHW", "CLS" };
 #endif
 
 static void http_sess_log(struct session *s);
@@ -545,9 +545,9 @@
 			int status, const struct chunk *msg)
 {
 	t->srv_state = SV_STCLOSE;
+	t->rep->flags |= BF_MAY_FORWARD;
 	buffer_shutw_done(t->req);
 	buffer_shutr_done(t->rep);
-	t->rep->flags |= BF_MAY_FORWARD;
 	if (status > 0 && msg) {
 		t->txn.status = status;
 		if (t->fe->mode == PR_MODE_HTTP)
@@ -1762,7 +1762,8 @@
 			}
 
 			/* 4: do we need to re-enable the read socket ? */
-			else if (unlikely(EV_FD_COND_S(t->cli_fd, DIR_RD))) {
+			else if (!tick_isset(req->rex)) {
+				EV_FD_COND_S(t->cli_fd, DIR_RD);
 				/* fd in DIR_RD was disabled, perhaps because of a previous buffer
 				 * full. We cannot loop here since stream_sock_read will disable it only if
 				 * req->l == rlim-data
@@ -2321,7 +2322,7 @@
 				/* Expect is allowed in 1.1, look for it */
 				http_find_header2("Expect", 6, msg->sol, &txn->hdr_idx, &ctx);
 				if (ctx.idx != 0  &&
-                                    unlikely(ctx.vlen == 12 && strncasecmp(ctx.line+ctx.val,"100-continue",12)==0))
+                                    unlikely(ctx.vlen == 12 && strncasecmp(ctx.line+ctx.val, "100-continue", 12) == 0))
 					/* We can't reliablly stall and wait for data, because of
 					 * .NET clients that don't conform to rfc2616; so, no need for
 					 * the next block to check length expectations.
@@ -2332,7 +2333,8 @@
 					 */
 					goto end_check_maybe_wait_for_body;
 			}
-			if ( likely(len > t->be->url_param_post_limit) ) {
+
+			if (likely(len > t->be->url_param_post_limit)) {
 				/* nothing to do, we got enough */
 			} else {
 				/* limit implies we are supposed to need this many bytes
@@ -2342,32 +2344,29 @@
 				struct hdr_ctx ctx;
 				ctx.idx = 0;
 				http_find_header2("Transfer-Encoding", 17, msg->sol, &txn->hdr_idx, &ctx);
-				if (unlikely(ctx.idx && strncasecmp(ctx.line+ctx.val,"chunked",7)==0)) {
-					t->srv_state = SV_STANALYZE;
+				if (ctx.idx && ctx.vlen >= 7 && strncasecmp(ctx.line+ctx.val, "chunked", 7) == 0)
 					t->analysis |= AN_REQ_HTTP_BODY;
-				} else {
+				else {
 					ctx.idx = 0;
 					http_find_header2("Content-Length", 14, msg->sol, &txn->hdr_idx, &ctx);
 					/* now if we have a length, we'll take the hint */
-					if ( ctx.idx ) {
+					if (ctx.idx) {
 						/* We have Content-Length */
-						if ( strl2llrc(ctx.line+ctx.val,ctx.vlen, &hint) )
+						if (strl2llrc(ctx.line+ctx.val,ctx.vlen, &hint))
 							hint = 0;         /* parse failure, untrusted client */
 						else {
-							if ( hint > 0 )
+							if (hint > 0)
 								msg->hdr_content_len = hint;
 							else
 								hint = 0; /* bad client, sent negative length */
 						}
 					}
 					/* but limited to what we care about, maybe we don't expect any entity data (hint == 0) */
-					if ( t->be->url_param_post_limit < hint )
+					if (t->be->url_param_post_limit < hint)
 						hint = t->be->url_param_post_limit;
 					/* now do we really need to buffer more data? */
-					if ( len < hint ) {
-						t->srv_state = SV_STANALYZE;
+					if (len < hint)
 						t->analysis |= AN_REQ_HTTP_BODY;
-					}
 					/* else... There are no body bytes to wait for */
 				}
 			}
@@ -2379,24 +2378,23 @@
 		 * could. Let's switch to the DATA state.                    *
 		 ************************************************************/
 
-		req->flags |= BF_MAY_CONNECT | BF_MAY_FORWARD;
-		req->rlim = req->data + BUFSIZE; /* no more rewrite needed */
+		if (!(t->analysis & AN_REQ_ANY))
+			req->flags |= BF_MAY_CONNECT | BF_MAY_FORWARD;
 
+		req->rlim = req->data + BUFSIZE; /* no more rewrite needed */
 		t->logs.tv_request = now;
 
-		if (!t->fe->timeout.client ||
-		    (!(rep->flags & BF_MAY_FORWARD) && t->be->timeout.server)) {
-			/* If the client has no timeout, or if the server is not ready yet,
-			 * and we know for sure that it can expire, then it's cleaner to
-			 * disable the timeout on the client side so that too low values
-			 * cannot make the sessions abort too early.
-			 *
-			 * FIXME-20050705: the server needs a way to re-enable this time-out
-			 * when it switches its state, otherwise a client can stay connected
-			 * indefinitely. This now seems to be OK.
-			 */
+		/* This is a bit tricky. We don't want the client timeout to strike
+		 * while intially waiting for the server to respond. So we rely
+		 * entirely on the server timeout to ensure that we cannot wait
+		 * indefinitely.
+		 */
+		if (t->fe->timeout.client || req->l >= req->rlim - req->data)
 			req->rex = TICK_ETERNITY;
-		}
+		else if (t->analysis || !t->be->timeout.server || rep->flags & BF_MAY_FORWARD)
+			req->rex = tick_add(now_ms, t->fe->timeout.client);
+		else
+			req->rex = TICK_ETERNITY;
 
 		/* When a connection is tarpitted, we use the tarpit timeout,
 		 * which may be the same as the connect timeout if unspecified.
@@ -2414,11 +2412,12 @@
 		}
 
 		/* OK let's go on with the BODY now */
-		goto process_data;
+		goto end_of_headers;
 
 	return_bad_req: /* let's centralize all bad requests */
 		txn->req.msg_state = HTTP_MSG_ERROR;
 		txn->status = 400;
+		t->analysis &= ~AN_REQ_ANY;
 		client_retnclose(t, error_message(t, HTTP_ERR_400));
 		t->fe->failed_req++;
 	return_prx_cond:
@@ -2427,10 +2426,110 @@
 		if (!(t->flags & SN_FINST_MASK))
 			t->flags |= SN_FINST_R;
 		return 1;
+	end_of_headers:
+		; // to make gcc happy
+	}
+
+	if (t->analysis & AN_REQ_HTTP_BODY) {
+		/* We have to parse the HTTP request body to find any required data.
+		 * "balance url_param check_post" should have been the only way to get
+		 * into this. We were brought here after HTTP header analysis, so all
+		 * related structures are ready.
+		 */
+		struct http_msg *msg = &t->txn.req;
+		unsigned long body = msg->sol[msg->eoh] == '\r' ? msg->eoh + 2 : msg->eoh + 1;
+		long long limit = t->be->url_param_post_limit;
+		struct hdr_ctx ctx;
+
+		ctx.idx = 0;
+
+		/* now if we have a length, we'll take the hint */
+		http_find_header2("Transfer-Encoding", 17, msg->sol, &t->txn.hdr_idx, &ctx);
+		if (ctx.idx && ctx.vlen >= 7 && strncasecmp(ctx.line+ctx.val, "chunked", 7) == 0) {
+			unsigned int chunk = 0;
+			while (body < req->l && !HTTP_IS_CRLF(msg->sol[body])) {
+				char c = msg->sol[body];
+				if (ishex(c)) {
+					unsigned int hex = toupper(c) - '0';
+					if (hex > 9)
+						hex -= 'A' - '9' - 1;
+					chunk = (chunk << 4) | hex;
+				} else
+					break;
+				body++;
+			}
+			if (body + 2 >= req->l) /* we want CRLF too */
+				goto http_body_incomplete; /* end of buffer? data missing! */
+
+			if (memcmp(msg->sol+body, "\r\n", 2) != 0)
+				goto http_body_incomplete; /* chunked encoding len ends with CRLF, and we don't have it yet */
+
+			body += 2; // skip CRLF
+
+			/* if we support more then one chunk here, we have to do it again when assigning server
+			 * 1. how much entity data do we have? new var
+			 * 2. should save entity_start, entity_cursor, elen & rlen in req; so we don't repeat scanning here
+			 * 3. test if elen > limit, or set new limit to elen if 0 (end of entity found)
+			 */
+
+			if (chunk < limit)
+				limit = chunk;                  /* only reading one chunk */
+		} else {
+			if (msg->hdr_content_len < limit)
+				limit = msg->hdr_content_len;
+		}
+
+		if (req->l - body >= limit) {
+			/* we have enough bytes ! */
+			t->logs.tv_request = now;  /* update the request timer to reflect full request */
+			t->analysis &= ~AN_REQ_HTTP_BODY;
+			req->flags |= BF_MAY_CONNECT | BF_MAY_FORWARD;
+
+			/* This is a bit tricky. We don't want the client timeout to strike
+			 * while intially waiting for the server to respond. So we rely
+			 * entirely on the server timeout to ensure that we cannot wait
+			 * indefinitely.
+			 */
+			if (t->fe->timeout.client || req->l >= req->rlim - req->data)
+				req->rex = TICK_ETERNITY;
+			else if (t->analysis || !t->be->timeout.server || rep->flags & BF_MAY_FORWARD)
+				req->rex = tick_add(now_ms, t->fe->timeout.client);
+			else
+				req->rex = TICK_ETERNITY;
+			return 1;
+		}
+
+	http_body_incomplete:
+		if (req->l >= req->rlim - req->data ||
+		    req->flags & (BF_READ_ERROR | BF_READ_NULL) ||
+		    tick_is_expired(req->rex, now_ms)) {
+			/* The situation will not evolve, so let's give up on the analysis. */
+			t->logs.tv_request = now;  /* update the request timer to reflect full request */
+			t->analysis &= ~AN_REQ_HTTP_BODY;
+			req->flags |= BF_MAY_CONNECT | BF_MAY_FORWARD;
+
+			/* This is a bit tricky. We don't want the client timeout to strike
+			 * while intially waiting for the server to respond. So we rely
+			 * entirely on the server timeout to ensure that we cannot wait
+			 * indefinitely.
+			 */
+			if (t->fe->timeout.client || req->l >= req->rlim - req->data)
+				req->rex = TICK_ETERNITY;
+			else if (t->analysis || !t->be->timeout.server || rep->flags & BF_MAY_FORWARD)
+				req->rex = tick_add(now_ms, t->fe->timeout.client);
+			else
+				req->rex = TICK_ETERNITY;
+			return 1;
+		}
+
+		if (!tick_isset(req->rex)) {
+			EV_FD_COND_S(t->cli_fd, DIR_RD);
+			req->rex = tick_add_ifset(now_ms, t->fe->timeout.client);
+		}
+		return 0;
 	}
 
 	if (c == CL_STDATA) {
-	process_data:
 		/* FIXME: this error handling is partly buggy because we always report
 		 * a 'DATA' phase while we don't know if the server was in IDLE, CONN
 		 * or HEADER phase. BTW, it's not logical to expire the client while
@@ -2459,6 +2558,9 @@
 			EV_FD_CLR(t->cli_fd, DIR_RD);
 			buffer_shutr(req);
 			t->cli_state = CL_STSHUTR;
+			/* we must allow immediate connection if not already done */
+			if (!req->flags & (BF_MAY_CONNECT | BF_MAY_FORWARD))
+				req->flags |= BF_MAY_CONNECT | BF_MAY_FORWARD;
 			return 1;
 		}	
 		/* last server read and buffer empty */
@@ -2523,18 +2625,19 @@
 			}
 		} else {
 			/* there's still some space in the buffer */
-			if (EV_FD_COND_S(t->cli_fd, DIR_RD)) {
-				if (!t->fe->timeout.client ||
-				    (!(rep->flags & BF_MAY_FORWARD) && t->be->timeout.server))
-					/* If the client has no timeout, or if the server not ready yet, and we
-					 * know for sure that it can expire, then it's cleaner to disable the
-					 * timeout on the client side so that too low values cannot make the
-					 * sessions abort too early. NB: we should only do this in HTTP states
-					 * before HEADERS.
-					 */
+			if (!tick_isset(req->rex)) {
+				EV_FD_COND_S(t->cli_fd, DIR_RD);
+				/* This is a bit tricky. We don't want the client timeout to strike
+				 * while intially waiting for the server to respond. So we rely
+				 * entirely on the server timeout to ensure that we cannot wait
+				 * indefinitely.
+				 */
+				if (t->fe->timeout.client)
 					req->rex = TICK_ETERNITY;
-				else
+				else if (t->analysis || !t->be->timeout.server || rep->flags & BF_MAY_FORWARD)
 					req->rex = tick_add(now_ms, t->fe->timeout.client);
+				else
+					req->rex = TICK_ETERNITY;
 			}
 		}
 
@@ -2546,10 +2649,11 @@
 			}
 		} else {
 			/* buffer not empty */
-			if (EV_FD_COND_S(t->cli_fd, DIR_WR)) {
+			if (!tick_isset(rep->wex)) {
+				EV_FD_COND_S(t->cli_fd, DIR_WR);
 				/* restart writing */
 				rep->wex = tick_add_ifset(now_ms, t->fe->timeout.client);
-				if (rep->wex) {
+				if (tick_isset(rep->wex)) {
 					/* FIXME: to prevent the client from expiring read timeouts during writes,
 					 * we refresh it. */
 					req->rex = rep->wex;
@@ -2616,7 +2720,8 @@
 			}
 		} else {
 			/* buffer not empty */
-			if (EV_FD_COND_S(t->cli_fd, DIR_WR)) {
+			if (!tick_isset(rep->wex)) {
+				EV_FD_COND_S(t->cli_fd, DIR_WR);
 				/* restart writing */
 				rep->wex = tick_add_ifset(now_ms, t->fe->timeout.client);
 			}
@@ -2675,7 +2780,8 @@
 			}
 		} else {
 			/* there's still some space in the buffer */
-			if (EV_FD_COND_S(t->cli_fd, DIR_RD)) {
+			if (!tick_isset(req->rex)) {
+				EV_FD_COND_S(t->cli_fd, DIR_RD);
 				req->rex = tick_add_ifset(now_ms, t->fe->timeout.client);
 			}
 		}
@@ -2711,11 +2817,6 @@
 		rep->rex, req->wex);
 
 	if (s == SV_STIDLE) {
-		/* NOTE: The client processor may switch to SV_STANALYZE, which switches back SV_STIDLE.
-		 *       This is logcially after CL_STHEADERS completed, CL_STDATA has started, but
-		 *       we need to defer server selection until more data arrives, if possible.
-                 *       This is rare, and only if balancing on parameter hash with values in the entity of a POST
-                 */
 		if ((rep->flags & BF_SHUTW_STATUS) ||
 			 ((req->flags & BF_SHUTR_STATUS) &&
 			  (req->l == 0 || t->be->options & PR_O_ABRT_CLOSE))) { /* give up */
@@ -2951,7 +3052,7 @@
 			} else  /* need the right to write */ {
 				EV_FD_SET(t->srv_fd, DIR_WR);
 				req->wex = tick_add_ifset(now_ms, t->be->timeout.server);
-				if (req->wex) {
+				if (tick_isset(req->wex)) {
 					/* FIXME: to prevent the server from expiring read timeouts during writes,
 					 * we refresh it. */
 					rep->rex = req->wex;
@@ -3037,7 +3138,8 @@
 		}
 
 
-		if ((rep->l < rep->rlim - rep->data) && EV_FD_COND_S(t->srv_fd, DIR_RD)) {
+		if ((rep->l < rep->rlim - rep->data) && !tick_isset(rep->rex)) {
+			EV_FD_COND_S(t->srv_fd, DIR_RD);
 			/* fd in DIR_RD was disabled, perhaps because of a previous buffer
 			 * full. We cannot loop here since stream_sock_read will disable it only if
 			 * rep->l == rlim-data
@@ -3075,6 +3177,7 @@
 				}
 				t->be->failed_resp++;
 				t->srv_state = SV_STCLOSE;
+				rep->flags |= BF_MAY_FORWARD;
 				txn->status = 502;
 				client_return(t, error_message(t, HTTP_ERR_502));
 				if (!(t->flags & SN_ERR_MASK))
@@ -3116,6 +3219,7 @@
 				}
 				t->be->failed_resp++;
 				t->srv_state = SV_STCLOSE;
+				rep->flags |= BF_MAY_FORWARD;
 				txn->status = 504;
 				client_return(t, error_message(t, HTTP_ERR_504));
 				if (!(t->flags & SN_ERR_MASK))
@@ -3186,10 +3290,11 @@
 			 * long posts.
 			 */
 			else if (likely(req->l)) {
-				if (EV_FD_COND_S(t->srv_fd, DIR_WR)) {
+				if (!tick_isset(req->wex)) {
+					EV_FD_COND_S(t->srv_fd, DIR_WR);
 					/* restart writing */
 					req->wex = tick_add_ifset(now_ms, t->be->timeout.server);
-					if (req->wex) {
+					if (tick_isset(req->wex)) {
 						/* FIXME: to prevent the server from expiring read timeouts during writes,
 						 * we refresh it. */
 						rep->rex = req->wex;
@@ -3289,6 +3394,7 @@
 					buffer_shutw_done(req);
 					fd_delete(t->srv_fd);
 					t->srv_state = SV_STCLOSE;
+					rep->flags |= BF_MAY_FORWARD;
 					txn->status = 502;
 					client_return(t, error_message(t, HTTP_ERR_502));
 					if (!(t->flags & SN_ERR_MASK))
@@ -3541,6 +3647,7 @@
 			}
 			t->be->failed_resp++;
 			t->srv_state = SV_STCLOSE;
+			rep->flags |= BF_MAY_FORWARD;
 			if (!(t->flags & SN_ERR_MASK))
 				t->flags |= SN_ERR_SRVCL;
 			if (!(t->flags & SN_FINST_MASK))
@@ -3610,10 +3717,11 @@
 			}
 		}
 		else { /* buffer not empty, there are still data to be transferred */
-			if (EV_FD_COND_S(t->srv_fd, DIR_WR)) {
+			if (!tick_isset(req->wex)) {
+				EV_FD_COND_S(t->srv_fd, DIR_WR);
 				/* restart writing */
 				req->wex = tick_add_ifset(now_ms, t->be->timeout.server);
-				if (req->wex) {
+				if (tick_isset(req->wex)) {
 					/* FIXME: to prevent the server from expiring read timeouts during writes,
 					 * we refresh it. */
 					rep->rex = req->wex;
@@ -3628,7 +3736,8 @@
 			}
 		}
 		else {
-			if (EV_FD_COND_S(t->srv_fd, DIR_RD)) {
+			if (!tick_isset(rep->rex)) {
+				EV_FD_COND_S(t->srv_fd, DIR_RD);
 				rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
 			}
 		}
@@ -3648,6 +3757,7 @@
 			t->be->failed_resp++;
 			//close(t->srv_fd);
 			t->srv_state = SV_STCLOSE;
+			rep->flags |= BF_MAY_FORWARD;
 			if (!(t->flags & SN_ERR_MASK))
 				t->flags |= SN_ERR_SRVCL;
 			if (!(t->flags & SN_FINST_MASK))
@@ -3670,6 +3780,7 @@
 			}
 			//close(t->srv_fd);
 			t->srv_state = SV_STCLOSE;
+			rep->flags |= BF_MAY_FORWARD;
 			/* We used to have a free connection slot. Since we'll never use it,
 			 * we have to inform the server that it may be used by another session.
 			 */
@@ -3688,6 +3799,7 @@
 			}
 			//close(t->srv_fd);
 			t->srv_state = SV_STCLOSE;
+			rep->flags |= BF_MAY_FORWARD;
 			if (!(t->flags & SN_ERR_MASK))
 				t->flags |= SN_ERR_SRVTO;
 			if (!(t->flags & SN_FINST_MASK))
@@ -3707,7 +3819,8 @@
 			}
 		}
 		else { /* buffer not empty */
-			if (EV_FD_COND_S(t->srv_fd, DIR_WR)) {
+			if (!tick_isset(req->wex)) {
+				EV_FD_COND_S(t->srv_fd, DIR_WR);
 				/* restart writing */
 				req->wex = tick_add_ifset(now_ms, t->be->timeout.server);
 			}
@@ -3727,6 +3840,7 @@
 			t->be->failed_resp++;
 			//close(t->srv_fd);
 			t->srv_state = SV_STCLOSE;
+			rep->flags |= BF_MAY_FORWARD;
 			if (!(t->flags & SN_ERR_MASK))
 				t->flags |= SN_ERR_SRVCL;
 			if (!(t->flags & SN_FINST_MASK))
@@ -3749,6 +3863,7 @@
 			}
 			//close(t->srv_fd);
 			t->srv_state = SV_STCLOSE;
+			rep->flags |= BF_MAY_FORWARD;
 			/* We used to have a free connection slot. Since we'll never use it,
 			 * we have to inform the server that it may be used by another session.
 			 */
@@ -3767,6 +3882,7 @@
 			}
 			//close(t->srv_fd);
 			t->srv_state = SV_STCLOSE;
+			rep->flags |= BF_MAY_FORWARD;
 			if (!(t->flags & SN_ERR_MASK))
 				t->flags |= SN_ERR_SRVTO;
 			if (!(t->flags & SN_FINST_MASK))
@@ -3785,66 +3901,13 @@
 			}
 		}
 		else {
-			if (EV_FD_COND_S(t->srv_fd, DIR_RD)) {
+			if (!tick_isset(rep->rex)) {
+				EV_FD_COND_S(t->srv_fd, DIR_RD);
 				rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
 			}
 		}
 		return 0;
 	}
-	else if (s == SV_STANALYZE){
-		/* this server state is set by the client to study the body for server assignment */
-
-		/* Have we been through this long enough to timeout? */
-		if (!tick_is_expired(req->rex, now_ms)) {
-			/* balance url_param check_post should have been the only to get into this.
-			 * just wait for data, check to compare how much
-			 */
-			struct http_msg * msg = &t->txn.req;
-                        unsigned long body = msg->sol[msg->eoh] == '\r' ? msg->eoh + 2 :msg->eoh + 1;
-                        unsigned long len  = req->l - body;
-			long long limit = t->be->url_param_post_limit;
-			struct hdr_ctx ctx;
-			ctx.idx = 0;
-			/* now if we have a length, we'll take the hint */
-			http_find_header2("Transfer-Encoding", 17, msg->sol, &txn->hdr_idx, &ctx);
-			if ( ctx.idx && strncasecmp(ctx.line+ctx.val,"chunked",ctx.vlen)==0) {
-				unsigned int chunk = 0;
-			        while ( body < req->l && !HTTP_IS_CRLF(msg->sol[body])) {
-					char c = msg->sol[body];
-					if (ishex(c)) {
-						unsigned int hex = toupper(c) - '0';
-						if ( hex > 9 )
-							hex -= 'A' - '9' - 1;
-						chunk = (chunk << 4) | hex;
-					}
-					else break;
-					body++;
-					len--;
-				}
-				if ( body + 2 >= req->l )
-					return 0; /* end of buffer? data missing! */
-
-				if ( memcmp(msg->sol+body, "\r\n", 2) != 0 )
-					return 0; /* chunked encoding len ends with CRLF, and we don't have it yet */
-
-				/* if we support more then one chunk here, we have to do it again when assigning server
-				   1. how much entity data do we have? new var
-				   2. should save entity_start, entity_cursor, elen & rlen in req; so we don't repeat scanning here
-				   3. test if elen > limit, or set new limit to elen if 0 (end of entity found)
-				*/
-
-				if ( chunk < limit )
-					limit = chunk;                  /* only reading one chunk */
-			} else {
-				if ( msg->hdr_content_len < limit )
-					limit = msg->hdr_content_len;
-			}
-			if ( len < limit )
-				return 0;
-		}
-		t->srv_state = SV_STIDLE;
-		return 1;
-	}
 	else { /* SV_STCLOSE : nothing to do */
 		if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
 			int len;
diff --git a/src/proto_uxst.c b/src/proto_uxst.c
index f3938ea..9053805 100644
--- a/src/proto_uxst.c
+++ b/src/proto_uxst.c
@@ -1376,7 +1376,7 @@
 			fsm_resync |= 1;
 			break;
 
-		case SV_STCONN: /* will change to SV_STANALYZE */
+		case SV_STCONN: /* should be changed to SV_STHEADERS or something more obvious */
 			/* stats initialized, but waiting for the command */
 			line = s->req->data;
 			p = memchr(line, '\n', s->req->l);
diff --git a/src/senddata.c b/src/senddata.c
index 5d6174c..fbbf681 100644
--- a/src/senddata.c
+++ b/src/senddata.c
@@ -52,6 +52,7 @@
 	EV_FD_SET(s->cli_fd, DIR_WR);
 	buffer_shutr(s->req);
 	s->rep->wex = tick_add_ifset(now_ms, s->rep->wto);
+	s->rep->flags |= BF_MAY_FORWARD;
 	s->cli_state = CL_STSHUTR;
 	buffer_flush(s->rep);
 	if (msg && msg->len)
diff --git a/src/stream_sock.c b/src/stream_sock.c
index 10688e9..da2004d 100644
--- a/src/stream_sock.c
+++ b/src/stream_sock.c
@@ -217,7 +217,7 @@
 	 * have at least read something.
 	 */
 
-	if (b->rex && b->flags & BF_PARTIAL_READ)
+	if (tick_isset(b->rex) && b->flags & BF_PARTIAL_READ)
 		b->rex = tick_add_ifset(now_ms, b->rto);
 
  out_wakeup:
@@ -373,14 +373,14 @@
 	 * written something.
 	 */
 
-	if (b->wex && b->flags & BF_PARTIAL_WRITE) {
+	if (tick_isset(b->wex) && b->flags & BF_PARTIAL_WRITE) {
 		b->wex = tick_add_ifset(now_ms, b->wto);
-		if (b->wex) {
+		if (tick_isset(b->wex)) {
 			/* FIXME: to prevent the client from expiring read timeouts during writes,
 			 * we refresh it. A solution would be to merge read+write timeouts into a
 			 * unique one, although that needs some study particularly on full-duplex
 			 * TCP connections. */
-			if (b->rex && !(b->flags & BF_SHUTR_STATUS))
+			if (tick_isset(b->rex) && !(b->flags & BF_SHUTR_STATUS))
 				b->rex = b->wex;
 		}
 	}