MEDIUM: stats: reimplement HTTP keep-alive on the stats page

This basically reimplements commit f3221f9 ("MEDIUM: stats: add support
for HTTP keep-alive on the stats page") which was reverted by commit
51437d2 after Igor Chan reported a broken stats page caused by the bug
fix by previous commit.
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index 78ff6f7..61e39bf 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -31,6 +31,7 @@
 #define STAT_HIDE_DOWN  0x00000008	/* hide 'down' servers in the stats page */
 #define STAT_NO_REFRESH 0x00000010	/* do not automatically refresh the stats page */
 #define STAT_ADMIN      0x00000020	/* indicate a stats admin level */
+#define STAT_CHUNKED    0x00000040      /* use chunked encoding (HTTP/1.1) */
 #define STAT_BOUND      0x00800000	/* bound statistics to selected proxies/types/services */
 
 #define STATS_TYPE_FE  0
diff --git a/src/dumpstats.c b/src/dumpstats.c
index d20682e..b35a5a8 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -4357,17 +4357,23 @@
 	struct appctx *appctx = objt_appctx(si->end);
 
 	chunk_printf(&trash,
-		     "HTTP/1.0 200 OK\r\n"
+		     "HTTP/1.%c 200 OK\r\n"
 		     "Cache-Control: no-cache\r\n"
 		     "Connection: close\r\n"
 		     "Content-Type: %s\r\n",
+		     (appctx->ctx.stats.flags & STAT_CHUNKED) ? '1' : '0',
 		     (appctx->ctx.stats.flags & STAT_FMT_HTML) ? "text/html" : "text/plain");
 
 	if (uri->refresh > 0 && !(appctx->ctx.stats.flags & STAT_NO_REFRESH))
 		chunk_appendf(&trash, "Refresh: %d\r\n",
 			      uri->refresh);
 
-	chunk_appendf(&trash, "\r\n");
+	/* we don't send the CRLF in chunked mode, it will be sent with the first chunk's size */
+
+	if (appctx->ctx.stats.flags & STAT_CHUNKED)
+		chunk_appendf(&trash, "Transfer-Encoding: chunked\r\n");
+	else
+		chunk_appendf(&trash, "\r\n");
 
 	s->txn.status = 200;
 	s->logs.tv_request = now;
@@ -4453,8 +4459,53 @@
 	}
 
 	if (appctx->st0 == STAT_HTTP_DUMP) {
+		unsigned int prev_len = si->ib->buf->i;
+		unsigned int data_len;
+		unsigned int last_len;
+		unsigned int last_fwd;
+
+		if (appctx->ctx.stats.flags & STAT_CHUNKED) {
+			/* One difficulty we're facing is that we must prevent
+			 * the input data from being automatically forwarded to
+			 * the output area. For this, we temporarily disable
+			 * forwarding on the channel.
+			 */
+			last_fwd = si->ib->to_forward;
+			si->ib->to_forward = 0;
+			chunk_printf(&trash, "\r\n000000\r\n");
+			if (bi_putchk(si->ib, &trash) == -1) {
+				si->ib->to_forward = last_fwd;
+				goto fail;
+			}
+		}
+
+		data_len = si->ib->buf->i;
 		if (stats_dump_stat_to_buffer(si, s->be->uri_auth))
 			appctx->st0 = STAT_HTTP_DONE;
+
+		last_len = si->ib->buf->i;
+
+		/* Now we must either adjust or remove the chunk size. This is
+		 * not easy because the chunk size might wrap at the end of the
+		 * buffer, so we pretend we have nothing in the buffer, we write
+		 * the size, then restore the buffer's contents. Note that we can
+		 * only do that because no forwarding is scheduled on the stats
+		 * applet.
+		 */
+		if (appctx->ctx.stats.flags & STAT_CHUNKED) {
+			si->ib->total  -= (last_len - prev_len);
+			si->ib->buf->i -= (last_len - prev_len);
+
+			if (last_len != data_len) {
+				chunk_printf(&trash, "\r\n%06x\r\n", (last_len - data_len));
+				bi_putchk(si->ib, &trash);
+
+				si->ib->total  += (last_len - data_len);
+				si->ib->buf->i += (last_len - data_len);
+			}
+			/* now re-enable forwarding */
+			channel_forward(si->ib, last_fwd);
+		}
 	}
 
 	if (appctx->st0 == STAT_HTTP_POST) {
@@ -4469,8 +4520,17 @@
 			appctx->st0 = STAT_HTTP_DONE;
 	}
 
-	if (appctx->st0 == STAT_HTTP_DONE)
-		si_shutw(si);
+	if (appctx->st0 == STAT_HTTP_DONE) {
+		if (appctx->ctx.stats.flags & STAT_CHUNKED) {
+			chunk_printf(&trash, "\r\n0\r\n\r\n");
+			if (bi_putchk(si->ib, &trash) == -1)
+				goto fail;
+		}
+		/* eat the whole request */
+		bo_skip(si->ob, si->ob->buf->o);
+		res->flags |= CF_READ_NULL;
+		si_shutr(si);
+	}
 
 	if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST))
 		si_shutw(si);
@@ -4482,6 +4542,7 @@
 		}
 	}
 
+ fail:
 	/* update all other flags and resync with the other side */
 	si_update(si);
 
diff --git a/src/proto_http.c b/src/proto_http.c
index 5e14d58..f813e1c 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -2962,6 +2962,8 @@
 	appctx->st1 = appctx->st2 = 0;
 	appctx->ctx.stats.st_code = STAT_STATUS_INIT;
 	appctx->ctx.stats.flags |= STAT_FMT_HTML; /* assume HTML mode by default */
+	if ((msg->flags & HTTP_MSGF_VER_11) && (s->txn.meth != HTTP_METH_HEAD))
+		appctx->ctx.stats.flags |= STAT_CHUNKED;
 
 	uri = msg->chn->buf->p + msg->sl.rq.u;
 	lookup = uri + uri_auth->uri_len;
@@ -3798,7 +3800,7 @@
 			s->flags |= SN_FINST_R;
 
 		req->analyse_exp = TICK_ETERNITY;
-		req->analysers = 0;
+		req->analysers = AN_REQ_HTTP_XFER_BODY;
 		return 1;
 	}