MEDIUM: mux-h2: Don't emit DATA frame for bodyless responses

Some responses must not contain data. Reponses to HEAD requests and 204/304
responses. But there is no warranty that this will be really respected by
the senders or even if it is possible. For instance, the method may be
rewritten by an http-request rule (HEAD->GET). Thus, it is not really
possible to always strip these data from the response at the receive
stage. And the response may be emitted by an applet or an internal service
not strictly following the spec. All that to say that we may be prepared to
handle payload for bodyless responses on the sending path.

In addition, unlike the HTTP/1, it is not really clear that the trailers is
part of the payload or not. Thus, some clients may expect to have the
trailers, if any, in the response to a HEAD request. For instance, the GRPC
status is placed in a trailer and clients rely on it. But what happens for
204 responses then.  Read the following thread for details :

  https://lists.w3.org/Archives/Public/ietf-http-wg/2020OctDec/0040.html

So, thanks to previous patches, it is now possible to know on the sending
path if a response must be bodyless or not. So, for such responses, no DATA
frame is emitted, except eventually the last empty one carring the ES
flag. However, the TRAILERS frames are still emitted. The h2s_skip_data()
function is added to take care to remove HTX DATA blocks without emitting
any DATA frame expect the last one, if there is no trailers.
diff --git a/src/mux_h2.c b/src/mux_h2.c
index 93fb99c..8e7691e 100644
--- a/src/mux_h2.c
+++ b/src/mux_h2.c
@@ -5059,18 +5059,12 @@
 		/* Response already closed: add END_STREAM */
 		es_now = 1;
 	}
-	else if ((h2s->flags & H2_SF_BODY_TUNNEL) && h2s->status >= 200 && h2s->status < 300) {
-		/* Don't set ES if a tunnel is successfully established (2xx responses to a connect). */
-	}
-	else if (h2s->status == 204 || h2s->status == 304) {
-		/* no contents, claim c-len is present and set to zero */
-		es_now = 1;
-	}
 	else if ((htx->flags & HTX_FL_EOM) && htx_is_empty(htx) && h2s->status >= 200) {
 		/* EOM+empty: we may need to add END_STREAM except for 1xx
-		 * responses.
+		 * responses and tunneled response.
 		 */
-		es_now = 1;
+		if (!(h2s->flags & H2_SF_BODY_TUNNEL) || h2s->status >= 300)
+			es_now = 1;
 	}
 
 	if (es_now)
@@ -5754,6 +5748,82 @@
 	return total;
 }
 
+/* Skip the message payload (DATA blocks) and emit an empty DATA frame with the
+ * ES flag set for stream <h2s>. This function is called for response known to
+ * have no payload. Only DATA blocks are skipped. This means the trailers are
+ * still emited. The caller must check the stream's status to detect any error
+ * which might have happened subsequently to a successful send. Returns the
+ * number of data bytes consumed, or zero if nothing done.
+ */
+static size_t h2s_skip_data(struct h2s *h2s, struct buffer *buf, size_t count)
+{
+	struct h2c *h2c = h2s->h2c;
+	struct htx *htx;
+	int bsize; /* htx block size */
+	int fsize; /* h2 frame size  */
+	struct htx_blk *blk;
+	enum htx_blk_type type;
+	size_t total = 0;
+
+	TRACE_ENTER(H2_EV_TX_FRAME|H2_EV_TX_DATA, h2c->conn, h2s);
+
+	if (h2c_mux_busy(h2c, h2s)) {
+		TRACE_STATE("mux output busy", H2_EV_TX_FRAME|H2_EV_TX_DATA, h2c->conn, h2s);
+		h2s->flags |= H2_SF_BLK_MBUSY;
+		TRACE_LEAVE(H2_EV_TX_FRAME|H2_EV_TX_DATA, h2c->conn, h2s);
+		goto end;
+	}
+
+	htx = htx_from_buf(buf);
+
+ next_data:
+	if (!count || htx_is_empty(htx))
+		goto end;
+	blk   = htx_get_head_blk(htx);
+	type  = htx_get_blk_type(blk);
+	bsize = htx_get_blksz(blk);
+	fsize = bsize;
+	if (type != HTX_BLK_DATA)
+		goto end;
+
+	if (fsize > count)
+		fsize = count;
+
+	if (fsize != bsize)
+		goto skip_data;
+
+	if (!(htx->flags & HTX_FL_EOM) || !htx_is_unique_blk(htx, blk))
+		goto skip_data;
+
+	/* Here, it is the last block and it is also the end of the message. So
+	 * we can emit an empty DATA frame with the ES flag set
+	 */
+	if (h2_send_empty_data_es(h2s) <= 0)
+		goto end;
+
+	if (h2s->st == H2_SS_OPEN)
+		h2s->st = H2_SS_HLOC;
+	else
+		h2s_close(h2s);
+
+ skip_data:
+	/* consume incoming HTX block */
+	total += fsize;
+	if (fsize == bsize) {
+		TRACE_DEVEL("more data may be available, trying to skip another frame", H2_EV_TX_FRAME|H2_EV_TX_DATA, h2c->conn, h2s);
+		htx_remove_blk(htx, blk);
+		goto next_data;
+	}
+	else {
+		/* we've truncated this block */
+		htx_cut_data_blk(htx, blk, fsize);
+	}
+
+ end:
+	TRACE_LEAVE(H2_EV_TX_FRAME|H2_EV_TX_DATA, h2c->conn, h2s);
+	return total;
+}
+
 /* Try to send a HEADERS frame matching HTX_BLK_TLR series of blocks present in
  * HTX message <htx> for the H2 stream <h2s>. Returns the number of bytes
  * processed. The caller must check the stream's status to detect any error
@@ -6173,7 +6243,11 @@
 
 			case HTX_BLK_DATA:
 				/* all these cause the emission of a DATA frame (possibly empty) */
-				ret = h2s_make_data(h2s, buf, count);
+				if (!(h2s->h2c->flags & H2_CF_IS_BACK) &&
+				    (h2s->flags & (H2_SF_BODY_TUNNEL|H2_SF_BODYLESS_RESP)) == H2_SF_BODYLESS_RESP)
+					ret = h2s_skip_data(h2s, buf, count);
+				else
+					ret = h2s_make_data(h2s, buf, count);
 				if (ret > 0) {
 					htx = htx_from_buf(buf);
 					total += ret;