diff --git a/src/mux_h2.c b/src/mux_h2.c index a19a31988..f7b6517f9 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -3520,6 +3520,228 @@ static size_t h2s_frt_make_resp_data(struct h2s *h2s, const struct buffer *buf, return total; } +/* Try to send a HEADERS frame matching HTX response present in HTX message + * for the H2 stream . Returns the number of bytes sent. The caller + * must check the stream's status to detect any error which might have happened + * subsequently to a successful send. The htx blocks are automatically removed + * from the message. The htx message is assumed to be valid since produced from + * the internal code, hence it contains a start line, an optional series of + * header blocks and an end of header, otherwise an invalid frame could be + * emitted and the resulting htx message could be left in an inconsistent state. + */ +static size_t h2s_htx_frt_make_resp_headers(struct h2s *h2s, struct htx *htx) +{ + struct http_hdr list[MAX_HTTP_HDR]; + struct h2c *h2c = h2s->h2c; + struct htx_blk *blk; + struct htx_blk *blk_end; + struct buffer outbuf; + struct htx_sl *sl; + enum htx_blk_type type; + int es_now = 0; + int ret = 0; + int hdr; + int idx; + + if (h2c_mux_busy(h2c, h2s)) { + h2s->flags |= H2_SF_BLK_MBUSY; + return 0; + } + + if (!h2_get_buf(h2c, &h2c->mbuf)) { + h2c->flags |= H2_CF_MUX_MALLOC; + h2s->flags |= H2_SF_BLK_MROOM; + return 0; + } + + /* determine the first block which must not be deleted, blk_end may + * be NULL if all blocks have to be deleted. + */ + idx = htx_get_head(htx); + blk_end = NULL; + while (idx != -1) { + type = htx_get_blk_type(htx_get_blk(htx, idx)); + idx = htx_get_next(htx, idx); + if (type == HTX_BLK_EOH) { + if (idx != -1) + blk_end = htx_get_blk(htx, idx); + break; + } + } + + /* get the start line, we do have one */ + blk = htx_get_blk(htx, htx->sl_off); + sl = htx_get_blk_ptr(htx, blk); + h2s->status = sl->info.res.status; + + /* and the rest of the headers, that we dump starting at header 0 */ + hdr = 0; + + idx = htx_get_next(htx, htx->sl_off); + while ((idx = htx_get_next(htx, idx)) != -1) { + blk = htx_get_blk(htx, idx); + type = htx_get_blk_type(blk); + + if (type == HTX_BLK_UNUSED) + continue; + + if (type != HTX_BLK_HDR) + break; + + if (unlikely(hdr >= sizeof(list)/sizeof(list[0]) - 1)) + goto fail; + + list[hdr].n = htx_get_blk_name(htx, blk); + list[hdr].v = htx_get_blk_value(htx, blk); + +#if 1 + { + /* FIXME: header names MUST be lower case in H2. For now it's + * not granted by HTX so let's force them now. + */ + char *p; + for (p = list[hdr].n.ptr; p != list[hdr].n.ptr + list[hdr].n.len; p++) + if (unlikely(isupper(*p))) + *p = tolower(*p); + } +#endif + hdr++; + } + + /* marker for end of headers */ + list[hdr].n = ist(""); + + if (h2s->status == 204 || h2s->status == 304) { + /* no contents, claim c-len is present and set to zero */ + es_now = 1; + } + + chunk_reset(&outbuf); + + while (1) { + outbuf.area = b_tail(&h2c->mbuf); + outbuf.size = b_contig_space(&h2c->mbuf); + outbuf.data = 0; + + if (outbuf.size >= 9 || !b_space_wraps(&h2c->mbuf)) + break; + realign_again: + b_slow_realign(&h2c->mbuf, trash.area, b_data(&h2c->mbuf)); + } + + if (outbuf.size < 9) + goto full; + + /* len: 0x000000 (fill later), type: 1(HEADERS), flags: ENDH=4 */ + memcpy(outbuf.area, "\x00\x00\x00\x01\x04", 5); + write_n32(outbuf.area + 5, h2s->id); // 4 bytes + outbuf.data = 9; + + /* encode status, which necessarily is the first one */ + if (outbuf.data < outbuf.size && h2s->status == 200) + outbuf.area[outbuf.data++] = 0x88; // indexed field : idx[08]=(":status", "200") + else if (outbuf.data < outbuf.size && h2s->status == 304) + outbuf.area[outbuf.data++] = 0x8b; // indexed field : idx[11]=(":status", "304") + else if (unlikely(h2s->status < 100 || h2s->status > 999)) { + /* this is an unparsable response */ + goto fail; + } + else if (unlikely(outbuf.data + 2 + 3 <= outbuf.size)) { + /* basic encoding of the status code */ + outbuf.area[outbuf.data++] = 0x48; // indexed name -- name=":status" (idx 8) + outbuf.area[outbuf.data++] = 0x03; // 3 bytes status + outbuf.area[outbuf.data++] = '0' + h2s->status / 100; + outbuf.area[outbuf.data++] = '0' + h2s->status / 10 % 10; + outbuf.area[outbuf.data++] = '0' + h2s->status % 10; + } + else { + if (b_space_wraps(&h2c->mbuf)) + goto realign_again; + goto full; + } + + /* encode all headers, stop at empty name */ + for (hdr = 0; hdr < sizeof(list)/sizeof(list[0]); hdr++) { + /* these ones do not exist in H2 and must be dropped. */ + if (isteq(list[hdr].n, ist("connection")) || + isteq(list[hdr].n, ist("proxy-connection")) || + isteq(list[hdr].n, ist("keep-alive")) || + isteq(list[hdr].n, ist("upgrade")) || + isteq(list[hdr].n, ist("transfer-encoding"))) + continue; + + if (isteq(list[hdr].n, ist(""))) + break; // end + + if (!hpack_encode_header(&outbuf, list[hdr].n, list[hdr].v)) { + /* output full */ + if (b_space_wraps(&h2c->mbuf)) + goto realign_again; + goto full; + } + } + + /* we may need to add END_STREAM. + * FIXME: we should also set it when we know for sure that the + * content-length is zero as well as on 204/304 + */ + if (blk_end && htx_get_blk_type(blk_end) == HTX_BLK_EOM) + es_now = 1; + + if (h2s->cs->flags & CS_FL_SHW) + es_now = 1; + + /* update the frame's size */ + h2_set_frame_size(outbuf.area, outbuf.data - 9); + + if (es_now) + outbuf.area[4] |= H2_F_HEADERS_END_STREAM; + + /* commit the H2 response */ + b_add(&h2c->mbuf, outbuf.data); + h2s->flags |= H2_SF_HEADERS_SENT; + + /* for now we don't implemented CONTINUATION, so we wait for a + * body or directly end in TRL2. + */ + if (es_now) { + h2s->flags |= H2_SF_ES_SENT; + if (h2s->st == H2_SS_OPEN) + h2s->st = H2_SS_HLOC; + else + h2s_close(h2s); + } + + /* OK we could properly deliver the response */ + + /* remove all header blocks including the EOH and compute the + * corresponding size. + * + * FIXME: We should remove everything when es_now is set. + */ + ret = 0; + idx = htx_get_head(htx); + blk = htx_get_blk(htx, idx); + while (blk != blk_end) { + ret += htx_get_blksz(blk); + blk = htx_remove_blk(htx, blk); + } + end: + return ret; + full: + h2c->flags |= H2_CF_MUX_MFULL; + h2s->flags |= H2_SF_BLK_MROOM; + ret = 0; + goto end; + fail: + /* unparsable HTX messages, too large ones to be produced in the local + * list etc go here (unrecoverable errors). + */ + h2s_error(h2s, H2_ERR_INTERNAL_ERROR); + ret = 0; + goto end; +} + /* Called from the upper layer, to subscribe to events, such as being able to send */ static int h2_subscribe(struct conn_stream *cs, int event_type, void *param) { @@ -3697,6 +3919,17 @@ static size_t h2_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun bsize = htx_get_blksz(blk); switch (btype) { + case HTX_BLK_RES_SL: + /* start-line before headers */ + ret = h2s_htx_frt_make_resp_headers(h2s, htx); + if (ret > 0) { + total += ret; + count -= ret; + if (ret < bsize) + goto done; + } + break; + default: htx_remove_blk(htx, blk); total += bsize;