From 9768c2660e3a5facab7868c7442a365d128403e7 Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Mon, 22 Oct 2018 09:34:31 +0200 Subject: [PATCH] MAJOR: mux-h1/proto_htx: Switch mux-h1 and HTX analyzers on the HTX representation The mux-h1 now parses and formats HTTP/1 messages using the HTX representation. The HTX analyzers have been updated too. For now, only htx_wait_for_{request/response} and http_{request/response}_forward_body have been adapted. Others are disabled for now. Now, the HTTP messages are parsed by the mux on a side and then, after analysis, formatted on the other side. In the middle, in the stream, there is no more parsing. Among other things, the version parsing is now handled by the mux. During the data forwarding, depending the value of the "extra" field, we are able to know if the body length is known or not and if yes, how many bytes are still expected. --- src/mux_h1.c | 756 ++++++++++++++++++++++++---------- src/proto_http.c | 6 + src/proto_htx.c | 1010 +++++++++++++--------------------------------- 3 files changed, 848 insertions(+), 924 deletions(-) diff --git a/src/mux_h1.c b/src/mux_h1.c index 7d96b804d..456d703eb 100644 --- a/src/mux_h1.c +++ b/src/mux_h1.c @@ -18,6 +18,8 @@ #include #include +#include +#include #include #include #include @@ -241,7 +243,10 @@ static struct h1s *h1s_create(struct h1c *h1c, struct conn_stream *cs) h1s->send_wait = NULL; h1m_init_req(&h1s->req); + h1s->req.flags |= H1_MF_NO_PHDR; + h1m_init_res(&h1s->res); + h1s->res.flags |= H1_MF_NO_PHDR; h1s->status = 0; h1s->meth = HTTP_METH_OTHER; @@ -444,71 +449,115 @@ static void h1_cpy_error_message(struct h1c *h1c, struct buffer *dst, int status b_putblk(dst, b_head(err), b_data(err)); } -/* Remove all "Connection:" headers from the buffer , using the array of - * parsed headers . It returns the number of bytes removed. This should - * happen just after the headers parsing, so the buffer should not wrap. At the - * ends, all entries of reamin valid. +/* Parse the request version and set H1_MF_VER_11 on if the version is + * greater or equal to 1.1 */ -static int h1_remove_conn_hdrs(struct h1m *h1m, struct http_hdr *hdrs, struct buffer *buf) +static void h1_parse_req_vsn(struct h1m *h1m, const union htx_sl *sl) { - int src, dst, delta; + const char *p = sl->rq.l + sl->rq.m_len + sl->rq.u_len; - delta = 0; - for (src = 0, dst = 0; hdrs[src].n.len; src++) { - - if (hdrs[src].n.ptr >= buf->area && hdrs[src].n.ptr < buf->area + buf->size) - hdrs[src].n.ptr += delta; - hdrs[src].v.ptr += delta; - - if (!isteqi(hdrs[src].n, ist("Connection"))) { - if (src != dst) - hdrs[dst] = hdrs[src]; - dst++; - continue; - } - delta += b_rep_blk(buf, hdrs[src].n.ptr, hdrs[src+1].n.ptr+delta, NULL, 0); - } - - /* Don't forget to copy EOH */ - hdrs[src].n.ptr += delta; - hdrs[dst] = hdrs[src]; - - h1m->flags &= ~(H1_MF_CONN_KAL|H1_MF_CONN_CLO); - return delta; + if ((sl->rq.v_len == 8) && + (*(p + 5) > '1' || + (*(p + 5) == '1' && *(p + 7) >= '1'))) + h1m->flags |= H1_MF_VER_11; } -/* Add a "Connection:" header into the buffer . If is 0, the header - * is set to "keep-alive", otherwise it is set to "close", It returns the number - * of bytes added. This should happen just after the headers parsing, so the - * buffer should not wrap. At the ends, all entries of reamin valid. +/* Parse the response version and set H1_MF_VER_11 on if the version is + * greater or equal to 1.1 */ -static int h1_add_conn_hdrs(struct h1m *h1m, struct http_hdr *hdrs, struct buffer *buf, - int type) +static void h1_parse_res_vsn(struct h1m *h1m, const union htx_sl *sl) { - const char *conn_hdr; - size_t nlen, vlen; - int i, delta; + const char *p = sl->rq.l; - if (type == 0) { /* keep-alive */ - conn_hdr = "Connection: keep-alive\r\n"; - nlen = 10; vlen = 10; + if ((sl->st.v_len == 8) && + (*(p + 5) > '1' || + (*(p + 5) == '1' && *(p + 7) >= '1'))) + h1m->flags |= H1_MF_VER_11; +} + +/* + * Check the validity of the request version. If the version is valid, it + * returns 1. Otherwise, it returns 0. + */ +static int h1_process_req_vsn(struct h1s *h1s, struct h1m *h1m, union h1_sl sl) +{ + struct h1c *h1c = h1s->h1c; + + /* RFC7230#2.6 has enforced the format of the HTTP version string to be + * exactly one digit "." one digit. This check may be disabled using + * option accept-invalid-http-request. + */ + if (!(h1c->px->options2 & PR_O2_REQBUG_OK)) { + if (sl.rq.v.len != 8) + return 0; + + if (*(sl.rq.v.ptr + 4) != '/' || + !isdigit((unsigned char)*(sl.rq.v.ptr + 5)) || + *(sl.rq.v.ptr + 6) != '.' || + !isdigit((unsigned char)*(sl.rq.v.ptr + 7))) + return 0; } - else { /* close */ - conn_hdr = "Connection: close\r\n"; - nlen = 10; vlen = 5; + else if (!sl.rq.v.len) { + /* try to convert HTTP/0.9 requests to HTTP/1.0 */ + + /* RFC 1945 allows only GET for HTTP/0.9 requests */ + if (sl.rq.meth != HTTP_METH_GET) + return 0; + + /* HTTP/0.9 requests *must* have a request URI, per RFC 1945 */ + if (!sl.rq.u.len) + return 0; + + /* Add HTTP version */ + sl.rq.v = ist("HTTP/1.0"); } + return 1; +} - /* Find EOH*/ - for (i = 0; hdrs[i].n.len; i++); +/* + * Check the validity of the response version. If the version is valid, it + * returns 1. Otherwise, it returns 0. + */ +static int h1_process_res_vsn(struct h1s *h1s, struct h1m *h1m, union h1_sl sl) +{ + struct h1c *h1c = h1s->h1c; - /* Insert the "Connection: " header */ - delta = b_rep_blk(buf, hdrs[i].n.ptr, hdrs[i].n.ptr, conn_hdr, nlen+vlen+4); + /* RFC7230#2.6 has enforced the format of the HTTP version string to be + * exactly one digit "." one digit. This check may be disabled using + * option accept-invalid-http-request. + */ + if (!(h1c->px->options2 & PR_O2_RSPBUG_OK)) { + if (sl.st.v.len != 8) + return 0; - /* Update the header list */ - http_set_hdr(&hdrs[i], ist2(hdrs[i].n.ptr, nlen), ist2(hdrs[i].n.ptr+nlen+2, vlen)); - http_set_hdr(&hdrs[i+1], ist2(hdrs[i].n.ptr+delta, 0), ist("")); + if (*(sl.st.v.ptr + 4) != '/' || + !isdigit((unsigned char)*(sl.st.v.ptr + 5)) || + *(sl.st.v.ptr + 6) != '.' || + !isdigit((unsigned char)*(sl.st.v.ptr + 7))) + return 0; + } + return 1; +} +/* Remove all "Connection:" headers from the HTX message */ +static void h1_remove_conn_hdrs(struct h1m *h1m, struct htx *htx) +{ + struct ist hdr = {.ptr = "Connection", .len = 10}; + struct http_hdr_ctx ctx; - return delta; + while (http_find_header(htx, hdr, &ctx, 1)) + http_remove_header(htx, &ctx); + + h1m->flags &= ~(H1_MF_CONN_KAL|H1_MF_CONN_CLO); +} + +/* Add a "Connection:" header with the value into the HTX message + * . + */ +static void h1_add_conn_hdr(struct h1m *h1m, struct htx *htx, struct ist value) +{ + struct ist hdr = {.ptr = "Connection", .len = 10}; + + http_add_header(htx, hdr, value); } /* Deduce the connection mode of the client connection, depending on the @@ -615,61 +664,104 @@ static void h1_set_srv_conn_mode(struct h1s *h1s, struct h1m *h1m) h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO; } -static int h1_update_req_conn_hdr(struct h1s *h1s, struct h1m *h1m, - struct http_hdr *hdrs, struct buffer *buf) +static void h1_update_req_conn_hdr(struct h1s *h1s, struct h1m *h1m, + struct htx *htx, struct ist *conn_val) { struct proxy *px = h1s->h1c->px; - int ret = 0; /* Don't update "Connection:" header in TUNNEL mode or if "Upgrage" * token is found */ if (h1s->flags & H1S_F_WANT_TUN || h1m->flags & H1_MF_CONN_UPG) - goto end; + return; if (h1s->flags & H1S_F_WANT_KAL || px->options2 & PR_O2_FAKE_KA) { - if (h1m->flags & H1_MF_CONN_CLO) - ret += h1_remove_conn_hdrs(h1m, hdrs, buf); - if (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL))) - ret += h1_add_conn_hdrs(h1m, hdrs, buf, 0); + if (h1m->flags & H1_MF_CONN_CLO) { + if (conn_val) + *conn_val = ist(""); + if (htx) + h1_remove_conn_hdrs(h1m, htx); + } + if (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL))) { + if (conn_val) + *conn_val = ist("keep-alive"); + if (htx) + h1_add_conn_hdr(h1m, htx, ist("keep-alive")); + } } else { /* H1S_F_WANT_CLO && !PR_O2_FAKE_KA */ - if (h1m->flags & H1_MF_CONN_KAL) - ret += h1_remove_conn_hdrs(h1m, hdrs, buf); - if ((h1m->flags & (H1_MF_VER_11|H1_MF_CONN_CLO)) == H1_MF_VER_11) - ret += h1_add_conn_hdrs(h1m, hdrs, buf, 1); + if (h1m->flags & H1_MF_CONN_KAL) { + if (conn_val) + *conn_val = ist(""); + if (htx) + h1_remove_conn_hdrs(h1m, htx); + } + if ((h1m->flags & (H1_MF_VER_11|H1_MF_CONN_CLO)) == H1_MF_VER_11) { + if (conn_val) + *conn_val = ist("close"); + if (htx) + h1_add_conn_hdr(h1m, htx, ist("close")); + } } - - end: - return ret; } -static int h1_update_res_conn_hdr(struct h1s *h1s, struct h1m *h1m, - struct http_hdr *hdrs, struct buffer *buf) +static void h1_update_res_conn_hdr(struct h1s *h1s, struct h1m *h1m, + struct htx *htx, struct ist *conn_val) { - int ret = 0; - /* Don't update "Connection:" header in TUNNEL mode or if "Upgrage" * token is found */ if (h1s->flags & H1S_F_WANT_TUN || h1m->flags & H1_MF_CONN_UPG) - goto end; + return; if (h1s->flags & H1S_F_WANT_KAL) { - if (h1m->flags & H1_MF_CONN_CLO) - ret += h1_remove_conn_hdrs(h1m, hdrs, buf); - if (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL))) - ret += h1_add_conn_hdrs(h1m, hdrs, buf, 0); + if (h1m->flags & H1_MF_CONN_CLO) { + if (conn_val) + *conn_val = ist(""); + if (htx) + h1_remove_conn_hdrs(h1m, htx); + } + if (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL))) { + if (conn_val) + *conn_val = ist("keep-alive"); + if (htx) + h1_add_conn_hdr(h1m, htx, ist("keep-alive")); + } } else { /* H1S_F_WANT_CLO */ - if (h1m->flags & H1_MF_CONN_KAL) - ret += h1_remove_conn_hdrs(h1m, hdrs, buf); - if ((h1m->flags & (H1_MF_VER_11|H1_MF_CONN_CLO)) == H1_MF_VER_11) - ret += h1_add_conn_hdrs(h1m, hdrs, buf, 1); + if (h1m->flags & H1_MF_CONN_KAL) { + if (conn_val) + *conn_val = ist(""); + if (htx) + h1_remove_conn_hdrs(h1m, htx); + } + if ((h1m->flags & (H1_MF_VER_11|H1_MF_CONN_CLO)) == H1_MF_VER_11) { + if (conn_val) + *conn_val = ist("close"); + if (htx) + h1_add_conn_hdr(h1m, htx, ist("close")); + } } +} - end: - return ret; +/* Set the right connection mode and update "Connection:" header if + * needed. and can be NULL. When is not NULL, the HTX + * message is updated accordingly. When is not NULL, it is set with + * the new header value. + */ +static void h1_process_conn_mode(struct h1s *h1s, struct h1m *h1m, + struct htx *htx, struct ist *conn_val) +{ + if (!conn_is_back(h1s->h1c->conn)) { + h1_set_cli_conn_mode(h1s, h1m); + if (h1m->flags & H1_MF_RESP) + h1_update_res_conn_hdr(h1s, h1m, htx, conn_val); + } + else { + h1_set_srv_conn_mode(h1s, h1m); + if (!(h1m->flags & H1_MF_RESP)) + h1_update_req_conn_hdr(h1s, h1m, htx, conn_val); + } } /* @@ -678,7 +770,7 @@ static int h1_update_res_conn_hdr(struct h1s *h1s, struct h1m *h1m, * flag and filling h1s->err_pos and h1s->err_state fields. This functions is * responsibile to update the parser state . */ -static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, +static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, struct htx *htx, struct buffer *buf, size_t *ofs, size_t max) { struct http_hdr hdrs[MAX_HTTP_HDR]; @@ -708,14 +800,21 @@ static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, if (ret > (b_size(buf) - global.tune.maxrewrite)) goto error; - /* Save the request's method or the response's status and check if the - * body length is known */ + /* Save the request's method or the response's status, check if the body + * length is known and check the VSN validity */ if (!(h1m->flags & H1_MF_RESP)) { h1s->meth = sl.rq.meth; + /* Request have always a known length */ h1m->flags |= H1_MF_XFER_LEN; if (!(h1m->flags & H1_MF_CHNK) && !h1m->body_len) h1m->state = H1_MSG_DONE; + + if (!h1_process_req_vsn(h1s, h1m, sl)) { + h1m->err_pos = sl.rq.v.ptr - b_head(buf); + h1m->err_state = h1m->state; + goto vsn_error; + } } else { h1s->status = sl.st.status; @@ -736,26 +835,48 @@ static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, } else h1m->state = H1_MSG_TUNNEL; + + if (!h1_process_res_vsn(h1s, h1m, sl)) { + h1m->err_pos = sl.st.v.ptr - b_head(buf); + h1m->err_state = h1m->state; + goto vsn_error; + } } - *ofs += ret; - if (!conn_is_back(h1s->h1c->conn)) { - h1_set_cli_conn_mode(h1s, h1m); - if (h1m->flags & H1_MF_RESP) - *ofs += h1_update_res_conn_hdr(h1s, h1m, hdrs, buf); + // FIXME: check and set HTTP version + + if (!(h1m->flags & H1_MF_RESP)) { + if (!htx_add_reqline(htx, sl) || !htx_add_all_headers(htx, hdrs)) + goto error; } else { - h1_set_srv_conn_mode(h1s, h1m); - if (!(h1m->flags & H1_MF_RESP)) - *ofs += h1_update_req_conn_hdr(h1s, h1m, hdrs, buf); + if (!htx_add_resline(htx, sl) || !htx_add_all_headers(htx, hdrs)) + goto error; } + if (h1m->state == H1_MSG_DONE) + if (!htx_add_endof(htx, HTX_BLK_EOM)) + goto error; + + h1_process_conn_mode(h1s, h1m, htx, NULL); + + /* If body length cannot be determined, set htx->extra to + * ULLONG_MAX. This value is impossible in other cases. + */ + htx->extra = ((h1m->flags & H1_MF_XFER_LEN) ? h1m->curr_len : ULLONG_MAX); + + /* Recheck there is enough space to do headers rewritting */ + if (htx_used_space(htx) > b_size(buf) - global.tune.maxrewrite) + goto error; + + *ofs += ret; end: return ret; error: - h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR); h1m->err_state = h1m->state; h1m->err_pos = h1m->next; + vsn_error: + h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR); ret = 0; goto end; } @@ -766,9 +887,10 @@ static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, * and filling h1s->err_pos and h1s->err_state fields. This functions is * responsibile to update the parser state . */ -static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, +static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, struct htx *htx, struct buffer *buf, size_t *ofs, size_t max) { + uint32_t data_space = htx_free_data_space(htx); size_t total = 0; int ret = 0; @@ -776,13 +898,25 @@ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, if (h1m->flags & H1_MF_CLEN) { /* content-length: read only h2m->body_len */ ret = max; + if (ret > data_space) + ret = data_space; if ((uint64_t)ret > h1m->curr_len) ret = h1m->curr_len; - h1m->curr_len -= ret; - *ofs += ret; - total += ret; - if (!h1m->curr_len) + if (ret > b_contig_data(buf, *ofs)) + ret = b_contig_data(buf, *ofs); + if (ret) { + if (!htx_add_data(htx, ist2(b_peek(buf, *ofs), ret))) + goto end; + h1m->curr_len -= ret; + *ofs += ret; + total += ret; + } + + if (!h1m->curr_len) { + if (!htx_add_endof(htx, HTX_BLK_EOM)) + goto end; h1m->state = H1_MSG_DONE; + } } else if (h1m->flags & H1_MF_CHNK) { new_chunk: @@ -791,10 +925,11 @@ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, ret = h1_skip_chunk_crlf(buf, *ofs, *ofs + max); if (ret <= 0) goto end; + h1m->state = H1_MSG_CHUNK_SIZE; + max -= ret; *ofs += ret; total += ret; - h1m->state = H1_MSG_CHUNK_SIZE; } if (h1m->state == H1_MSG_CHUNK_SIZE) { @@ -803,37 +938,72 @@ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, ret = h1_parse_chunk_size(buf, *ofs, *ofs + max, &chksz); if (ret <= 0) goto end; + if (!chksz) { + if (!htx_add_endof(htx, HTX_BLK_EOD)) + goto end; + h1m->state = H1_MSG_TRAILERS; + } + else + h1m->state = H1_MSG_DATA; + h1m->curr_len = chksz; h1m->body_len += chksz; max -= ret; *ofs += ret; total += ret; - h1m->state = (!chksz ? H1_MSG_TRAILERS : H1_MSG_DATA); } if (h1m->state == H1_MSG_DATA) { ret = max; - if (!ret) - goto end; + if (ret > data_space) + ret = data_space; if ((uint64_t)ret > h1m->curr_len) ret = h1m->curr_len; - h1m->curr_len -= ret; - max -= ret; - *ofs += ret; - total += ret; - if (h1m->curr_len) - goto end; - h1m->state = H1_MSG_CHUNK_CRLF; - goto new_chunk; + if (ret > b_contig_data(buf, *ofs)) + ret = b_contig_data(buf, *ofs); + if (ret) { + if (!htx_add_data(htx, ist2(b_peek(buf, *ofs), ret))) + goto end; + h1m->curr_len -= ret; + max -= ret; + *ofs += ret; + total += ret; + } + if (!h1m->curr_len) { + h1m->state = H1_MSG_CHUNK_CRLF; + goto new_chunk; + } + goto end; } if (h1m->state == H1_MSG_TRAILERS) { ret = h1_measure_trailers(buf, *ofs, *ofs + max); + if (ret > data_space) + ret = (htx_is_empty(htx) ? -1 : 0); if (ret <= 0) goto end; + + /* Realing input buffer if tailers wrap. For now + * this is a workaroung. Because trailers are + * not split on CRLF, like headers, there is no + * way to know where to split it when trailers + * wrap. This is a limitation of + * h1_measure_trailers. + */ + if (b_peek(buf, *ofs) > b_peek(buf, *ofs + ret)) + b_slow_realign(buf, trash.area, 0); + + if (!htx_add_trailer(htx, ist2(b_peek(buf, *ofs), ret))) + goto end; + max -= ret; *ofs += ret; total += ret; + + /* FIXME: if it fails here, this is a problem, + * because there is no way to return here. */ + if (!htx_add_endof(htx, HTX_BLK_EOM)) + goto end; h1m->state = H1_MSG_DONE; } } @@ -841,13 +1011,25 @@ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, /* XFER_LEN is set but not CLEN nor CHNK, it means there * is no body. Switch the message in DONE state */ + if (!htx_add_endof(htx, HTX_BLK_EOM)) + goto end; h1m->state = H1_MSG_DONE; } } else { /* no content length, read till SHUTW */ - *ofs += max; - total = max; + ret = max; + if (ret > data_space) + ret = data_space; + if (ret > b_contig_data(buf, *ofs)) + ret = b_contig_data(buf, *ofs); + if (ret) { + if (!htx_add_data(htx, ist2(b_peek(buf, *ofs), ret))) + goto end; + + *ofs += max; + total = max; + } } end: @@ -857,7 +1039,9 @@ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, h1m->err_pos = *ofs + max + ret; return 0; } - + /* update htx->extra, only when the body length is known */ + if (h1m->flags & H1_MF_XFER_LEN) + htx->extra = h1m->curr_len; return total; } @@ -876,17 +1060,20 @@ static void h1_sync_messages(struct h1c *h1c) if (h1s->res.state == H1_MSG_DONE && (h1s->status < 200 && (h1s->status == 100 || h1s->status >= 102)) && - ((conn_is_back(h1c->conn) && !b_data(&h1c->obuf)) || !b_data(&h1s->rxbuf))) { + ((!conn_is_back(h1c->conn) && !b_data(&h1c->obuf)) || !b_data(&h1s->rxbuf))) { /* For 100-Continue response or any other informational 1xx * response which is non-final, don't reset the request, the * transaction is not finished. We take care the response was * transferred before. */ h1m_init_res(&h1s->res); + h1s->res.flags |= H1_MF_NO_PHDR; } else if (!b_data(&h1s->rxbuf) && !b_data(&h1c->obuf) && h1s->req.state == H1_MSG_DONE && h1s->res.state == H1_MSG_DONE) { if (h1s->flags & H1S_F_WANT_TUN) { + h1m_init_req(&h1s->req); + h1m_init_res(&h1s->res); h1s->req.state = H1_MSG_TUNNEL; h1s->res.state = H1_MSG_TUNNEL; } @@ -902,13 +1089,13 @@ static size_t h1_process_input(struct h1c *h1c, struct buffer *buf, size_t count { struct h1s *h1s = NULL; struct h1m *h1m; + struct htx *htx; size_t total = 0; size_t ret = 0; size_t max; int errflag; - if (h1c->flags & H1C_F_CS_ERROR) - goto end; + h1s = NULL; /* Create a new H1S without CS if not already done */ if (!h1c->h1s && !h1s_create(h1c, NULL)) @@ -926,10 +1113,7 @@ static size_t h1_process_input(struct h1c *h1c, struct buffer *buf, size_t count h1c->flags |= H1C_F_RX_ALLOC; goto end; } - - if (count > b_room(&h1s->rxbuf)) - count = b_room(&h1s->rxbuf); - max = count; + htx = htx_from_buf(&h1s->rxbuf); if (!conn_is_back(h1c->conn)) { h1m = &h1s->req; @@ -939,9 +1123,11 @@ static size_t h1_process_input(struct h1c *h1c, struct buffer *buf, size_t count h1m = &h1s->res; errflag = H1S_F_RES_ERROR; } + + max = count; while (!(h1s->flags & errflag) && max) { if (h1m->state <= H1_MSG_LAST_LF) { - ret = h1_process_headers(h1s, h1m, buf, &total, max); + ret = h1_process_headers(h1s, h1m, htx, buf, &total, max); if (!ret) break; @@ -958,16 +1144,16 @@ static size_t h1_process_input(struct h1c *h1c, struct buffer *buf, size_t count */ if (!(h1s->flags & H1S_F_MSG_XFERED)) break; - ret = h1_process_data(h1s, h1m, buf, &total, max); + ret = h1_process_data(h1s, h1m, htx, buf, &total, max); if (!ret) break; } else if (h1m->state == H1_MSG_DONE) break; else if (h1m->state == H1_MSG_TUNNEL) { - total += max; - max = 0; - break; + ret = h1_process_data(h1s, h1m, htx, buf, &total, max); + if (!ret) + break; } else { h1s->flags |= errflag; @@ -978,41 +1164,53 @@ static size_t h1_process_input(struct h1c *h1c, struct buffer *buf, size_t count } if (h1s->flags & errflag) { - /* For now, if an error occurred during the message parsing when - * a stream is already attached to the mux, we transfer - * everything to let the stream handle the error itself. We - * suppose the stream will detect the same error of - * course. Otherwise, we generate the error here. - */ - if (!h1s->cs) { - if (!h1_get_buf(h1c, &h1c->obuf)) { - h1c->flags |= H1C_F_OUT_ALLOC; - goto err; - } - h1_cpy_error_message(h1c, &h1c->obuf, 400); + if (conn_is_back(h1c->conn)) + goto err; + + // FIXME: Do following actions when an error is catched during + // the request parsing: + // + // * Do same than stream_inc_http_req_ctr, + // stream_inc_http_err_ctr and proxy_inc_fe_req_ctr + // * Capture bad message for snapshots + // * Increment fe->fe_counters.failed_req and + // listeners->counters->failed_req + // + // FIXME: Do following actions when an error is catched during + // the response parsing: + // + // * Capture bad message for snapshots + // * increment be->be_counters.failed_resp + // * increment srv->counters.failed_resp (if srv assigned) + if (!h1_get_buf(h1c, &h1c->obuf)) { + h1c->flags |= H1C_F_OUT_ALLOC; goto err; } - total += max; - max = 0; + h1_cpy_error_message(h1c, &h1c->obuf, 400); + goto err; } - b_xfer(&h1s->rxbuf, buf, total); + b_del(buf, total); - if (b_data(&h1s->rxbuf)) { - h1s->cs->flags |= CS_FL_RCV_MORE; - if (b_full(&h1s->rxbuf)) + if (htx_is_not_empty(htx)) { + b_set_data(&h1s->rxbuf, b_size(&h1s->rxbuf)); + if (!htx_free_data_space(htx)) h1c->flags |= H1C_F_RX_FULL; } + else + h1_release_buf(h1c, &h1s->rxbuf); + ret = count - max; + end: return ret; err: - h1s_destroy(h1s); + //h1s_destroy(h1s); h1c->flags |= H1C_F_CS_ERROR; - sess_log(h1c->conn->owner); - ret = 0; - goto end; + if (!h1s || !h1s->cs) + sess_log(h1c->conn->owner); + return 0; } /* @@ -1024,19 +1222,19 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun { struct h1s *h1s = h1c->h1s; struct h1m *h1m; - size_t max; + struct htx *chn_htx; + struct htx_blk *blk; + struct buffer *tmp; size_t total = 0; - size_t ret = 0; int errflag; + chn_htx = htx_from_buf(buf); + if (!h1_get_buf(h1c, &h1c->obuf)) { h1c->flags |= H1C_F_OUT_ALLOC; goto end; } - if (count > b_room(&h1c->obuf)) - count = b_room(&h1c->obuf); - max = count; if (!conn_is_back(h1c->conn)) { h1m = &h1s->res; errflag = H1S_F_RES_ERROR; @@ -1045,71 +1243,208 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun h1m = &h1s->req; errflag = H1S_F_REQ_ERROR; } - while (!(h1s->flags & errflag) && max) { - if (h1m->state <= H1_MSG_LAST_LF) { - ret = h1_process_headers(h1s, h1m, buf, &total, max); - if (!ret) { - /* incomplete or invalid response, this is abnormal coming from - * haproxy and may only result in a bad errorfile or bad Lua code - * so that won't be fixed, raise an error now. - */ - h1s->flags |= errflag; - break; - } - } - else if (h1m->state <= H1_MSG_TRAILERS) { - ret = h1_process_data(h1s, h1m, buf, &total, max); - if (!ret) - break; - } - else if (h1m->state == H1_MSG_DONE) - break; - else if (h1m->state == H1_MSG_TUNNEL) { - total += max; - max = 0; - break; - } - else { - h1s->flags |= errflag; - break; - } - max -= ret; + + tmp = get_trash_chunk(); + tmp->size = b_room(&h1c->obuf); + + blk = htx_get_head_blk(chn_htx); + while (!(h1s->flags & errflag) && blk) { + union htx_sl *sl; + struct ist n, v; + uint32_t sz = htx_get_blksz(blk); + + if (total + sz > count) + goto copy; + + switch (htx_get_blk_type(blk)) { + case HTX_BLK_UNUSED: + break; + + case HTX_BLK_REQ_SL: + sl = htx_get_blk_ptr(chn_htx, blk); + h1s->meth = sl->rq.meth; + h1_parse_req_vsn(h1m, sl); + if (!htx_reqline_to_str(sl, tmp)) + goto copy; + h1m->flags |= H1_MF_XFER_LEN; + h1m->state = H1_MSG_HDR_FIRST; + break; + + case HTX_BLK_RES_SL: + sl = htx_get_blk_ptr(chn_htx, blk); + h1s->status = sl->st.status; + h1_parse_res_vsn(h1m, sl); + if (!htx_stline_to_str(sl, tmp)) + goto copy; + if (chn_htx->extra != ULLONG_MAX) + h1m->flags |= H1_MF_XFER_LEN; + h1m->state = H1_MSG_HDR_FIRST; + break; + + case HTX_BLK_HDR: + if (h1m->state == H1_MSG_HDR_FIRST) { + struct http_hdr_ctx ctx; + + n = ist("Connection"); + v = ist(""); + + /* If there is no "Connection:" header, + * process conn_mode now and add the + * right one. + */ + ctx.blk = blk; + if (http_find_header(chn_htx, n, &ctx, 1)) + goto process_hdr; + h1_process_conn_mode(h1s, h1m, NULL, &v); + if (!v.len) + goto process_hdr; + + if (!htx_hdr_to_str(n, v, tmp)) + goto copy; + } + process_hdr: + h1m->state = H1_MSG_HDR_NAME; + n = htx_get_blk_name(chn_htx, blk); + v = htx_get_blk_value(chn_htx, blk); + + if (isteqi(n, ist("transfer-encoding"))) + h1_parse_xfer_enc_header(h1m, v); + else if (isteqi(n, ist("connection"))) { + h1_parse_connection_header(h1m, v); + h1_process_conn_mode(h1s, h1m, NULL, &v); + if (!v.len) + goto skip_hdr; + } + + if (!htx_hdr_to_str(n, v, tmp)) + goto copy; + skip_hdr: + h1m->state = H1_MSG_HDR_L2_LWS; + break; + + case HTX_BLK_PHDR: + /* not implemented yet */ + h1m->flags |= errflag; + break; + + case HTX_BLK_EOH: + h1m->state = H1_MSG_LAST_LF; + if (!chunk_memcat(tmp, "\r\n", 2)) + goto copy; + + h1m->state = H1_MSG_DATA; + break; + + case HTX_BLK_DATA: + v = htx_get_blk_value(chn_htx, blk); + if (!htx_data_to_str(v, tmp, !!(h1m->flags & H1_MF_CHNK))) + goto copy; + break; + + case HTX_BLK_EOD: + if (!chunk_memcat(tmp, "0\r\n", 3)) + goto copy; + h1m->state = H1_MSG_TRAILERS; + break; + + case HTX_BLK_TLR: + v = htx_get_blk_value(chn_htx, blk); + if (!htx_trailer_to_str(v, tmp)) + goto copy; + break; + + case HTX_BLK_EOM: + /* if ((h1m->flags & H1_MF_CHNK) && !chunk_memcat(tmp, "\r\n", 2)) */ + /* goto copy; */ + h1m->state = H1_MSG_DONE; + break; + + case HTX_BLK_OOB: + v = htx_get_blk_value(chn_htx, blk); + if (!chunk_memcat(tmp, v.ptr, v.len)) + goto copy; + break; + + default: + h1m->flags |= errflag; + break; + } + total += sz; + blk = htx_remove_blk(chn_htx, blk); } - // TODO: Handle H1S errors - b_xfer(&h1c->obuf, buf, total); + copy: + b_putblk(&h1c->obuf, tmp->area, tmp->data); if (b_full(&h1c->obuf)) h1c->flags |= H1C_F_OUT_FULL; - ret = count - max; - end: - return ret; + if (htx_is_empty(chn_htx)) { + htx_reset(chn_htx); + b_set_data(buf, 0); + } + + end: + return total; } /* * Transfer data from h1s->rxbuf into the channel buffer. It returns the number * of bytes transferred. */ -static size_t h1_xfer(struct h1s *h1s, struct buffer *buf, size_t count) +static size_t h1_xfer(struct h1s *h1s, struct buffer *buf, int flags) { struct h1c *h1c = h1s->h1c; + struct h1m *h1m; struct conn_stream *cs = h1s->cs; - size_t ret = 0; + struct htx *mux_htx, *chn_htx; + struct htx_ret htx_ret; + size_t count, ret = 0; - /* transfer possibly pending data to the upper layer */ - ret = b_xfer(buf, &h1s->rxbuf, count); + h1m = (!conn_is_back(h1c->conn) ? &h1s->req : &h1s->res); + mux_htx = htx_from_buf(&h1s->rxbuf); - if (b_data(&h1s->rxbuf)) { - if (!b_full(&h1s->rxbuf)) { - h1c->flags &= ~H1C_F_RX_FULL; - } + if (htx_is_empty(mux_htx)) + goto end; + + chn_htx = htx_from_buf(buf); + + count = htx_free_space(chn_htx); + if (flags & CO_RFL_KEEP_RSV) { + if (count < global.tune.maxrewrite) + goto end; + count -= global.tune.maxrewrite; + } + + // FIXME: if chn empty and count > htx => b_xfer ! + if (!(h1s->flags & H1S_F_MSG_XFERED)) { + htx_ret = htx_xfer_blks(chn_htx, mux_htx, count, + ((h1m->state == H1_MSG_DONE) ? HTX_BLK_EOM : HTX_BLK_EOH)); + ret = htx_ret.ret; + if (htx_ret.blk && htx_get_blk_type(htx_ret.blk) >= HTX_BLK_EOH) + h1s->flags |= H1S_F_MSG_XFERED; + } + else { + htx_ret = htx_xfer_blks(chn_htx, mux_htx, count, HTX_BLK_EOM); + ret = htx_ret.ret; + } + chn_htx->extra = mux_htx->extra; + if (h1m->flags & H1_MF_XFER_LEN) + chn_htx->extra += mux_htx->data; + + if (htx_is_not_empty(chn_htx)) + b_set_data(buf, b_size(buf)); + + end: + if (h1c->flags & H1C_F_RX_FULL && htx_free_data_space(mux_htx)) { + h1c->flags &= ~H1C_F_RX_FULL; + tasklet_wakeup(h1c->wait_event.task); + } + + if (htx_is_not_empty(mux_htx)) { cs->flags |= CS_FL_RCV_MORE; } else { - if (!(h1s->flags & H1S_F_MSG_XFERED)) - h1s->flags |= H1S_F_MSG_XFERED; - h1c->flags &= ~H1C_F_RX_FULL; h1_release_buf(h1c, &h1s->rxbuf); h1_sync_messages(h1c); @@ -1138,16 +1473,18 @@ static int h1_recv(struct h1c *h1c) if (!h1_recv_allowed(h1c)) { if (h1c->h1s && b_data(&h1c->h1s->rxbuf)) - return 1; - return 0; + rcvd = 1; + goto end; } - if (h1c->h1s && (h1c->h1s->flags & H1S_F_BUF_FLUSH)) - return 1; + if (h1c->h1s && (h1c->h1s->flags & H1S_F_BUF_FLUSH)) { + rcvd = 1; + goto end; + } if (!h1_get_buf(h1c, &h1c->ibuf)) { h1c->flags |= H1C_F_IN_ALLOC; - return 0; + goto end; } ret = 0; @@ -1162,6 +1499,7 @@ static int h1_recv(struct h1c *h1c) if (h1_recv_allowed(h1c)) conn->xprt->subscribe(conn, SUB_CAN_RECV, &h1c->wait_event); + end: if (!b_data(&h1c->ibuf)) h1_release_buf(h1c, &h1c->ibuf); else if (b_full(&h1c->ibuf)) @@ -1251,7 +1589,7 @@ static int h1_process(struct h1c * h1c) { struct connection *conn = h1c->conn; - if (b_data(&h1c->ibuf) && !(h1c->flags & (H1C_F_RX_FULL|H1C_F_RX_ALLOC))) { + if (b_data(&h1c->ibuf) && !(h1c->flags & (H1C_F_CS_ERROR|H1C_F_RX_FULL|H1C_F_RX_ALLOC))) { size_t ret; ret = h1_process_input(h1c, &h1c->ibuf, b_data(&h1c->ibuf)); @@ -1302,7 +1640,7 @@ static struct task *h1_io_cb(struct task *t, void *ctx, unsigned short status) ret = h1_send(h1c); if (!(h1c->wait_event.wait_reason & SUB_CAN_RECV)) ret |= h1_recv(h1c); - if (ret || b_data(&h1c->ibuf)) + if (ret || b_data(&h1c->ibuf) || (h1c->h1s && b_data(&h1c->h1s->rxbuf))) h1_process(h1c); return NULL; } @@ -1312,6 +1650,7 @@ static int h1_wake(struct connection *conn) { struct h1c *h1c = conn->mux_ctx; + //return 0; return (h1_process(h1c)); } @@ -1354,6 +1693,13 @@ static struct task *h1_timeout_task(struct task *t, void *context, unsigned shor if (!h1_get_buf(h1c, &h1c->obuf)) goto release; + // FIXME: Do the following: + // + // * Do same than stream_inc_http_req_ctr, + // stream_inc_http_err_ctr and proxy_inc_fe_req_ctr + // * Capture bad message for snapshots + // * Increment fe->fe_counters.failed_req and + // listeners->counters->failed_req h1_cpy_error_message(h1c, &h1c->obuf, 408); tasklet_wakeup(h1c->wait_event.task); sess_log(h1c->conn->owner); @@ -1574,7 +1920,7 @@ static size_t h1_rcv_buf(struct conn_stream *cs, struct buffer *buf, size_t coun return ret; if (!(h1s->h1c->flags & H1C_F_RX_ALLOC)) - ret = h1_xfer(h1s, buf, count); + ret = h1_xfer(h1s, buf, flags); if (flags & CO_RFL_BUF_FLUSH) h1s->flags |= H1S_F_BUF_FLUSH; diff --git a/src/proto_http.c b/src/proto_http.c index 348116003..0f71e83ed 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -393,6 +393,9 @@ int http_remove_header2(struct http_msg *msg, struct hdr_idx *idx, struct hdr_ct static void http_server_error(struct stream *s, struct stream_interface *si, int err, int finst, const struct buffer *msg) { + if (IS_HTX_STRM(s)) + return htx_server_error(s, si, err, finst, msg); + FLT_STRM_CB(s, flt_http_reply(s, s->txn->status, msg)); channel_auto_read(si_oc(si)); channel_abort(si_oc(si)); @@ -427,6 +430,9 @@ struct buffer *http_error_message(struct stream *s) void http_reply_and_close(struct stream *s, short status, struct buffer *msg) { + if (IS_HTX_STRM(s)) + return htx_reply_and_close(s, status, msg); + s->txn->flags &= ~TX_WAIT_NEXT_RQ; FLT_STRM_CB(s, flt_http_reply(s, status, msg)); stream_int_retnclose(&s->si[0], msg); diff --git a/src/proto_htx.c b/src/proto_htx.c index 8af28e606..595092443 100644 --- a/src/proto_htx.c +++ b/src/proto_htx.c @@ -51,32 +51,19 @@ static void htx_debug_hdr(const char *dir, struct stream *s, const struct ist n, */ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) { - /* - * We will parse the partial (or complete) lines. - * We will check the request syntax, and also join multi-line - * headers. An index of all the lines will be elaborated while - * parsing. - * - * For the parsing, we use a 28 states FSM. - * - * Here is the information we currently have : - * ci_head(req) = beginning of request - * ci_head(req) + msg->eoh = end of processed headers / start of current one - * ci_tail(req) = end of input data - * msg->eol = end of current header or line (LF or CRLF) - * msg->next = first non-visited byte - * - * At end of parsing, we may perform a capture of the error (if any), and - * we will set a few fields (txn->meth, sn->flags/SF_REDIRECTABLE). - * We also check for monitor-uri, logging, HTTP/0.9 to 1.0 conversion, and - * finally headers capture. - */ - int cur_idx; + /* + * We will analyze a complete HTTP request to check the its syntax. + * + * Once the start line and all headers are received, we may perform a + * capture of the error (if any), and we will set a few fields. We also + * check for monitor-uri, logging and finally headers capture. + */ struct session *sess = s->sess; struct http_txn *txn = s->txn; struct http_msg *msg = &txn->req; - struct hdr_ctx ctx; + struct htx *htx; + union h1_sl sl; DPRINTF(stderr,"[%u] %s: stream=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%lu analysers=%02x\n", now_ms, __FUNCTION__, @@ -87,6 +74,8 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) ci_data(req), req->analysers); + htx = htx_from_buf(&req->buf); + /* we're speaking HTTP here, so let's speak HTTP to the client */ s->srv_error = http_return_srv_error; @@ -94,56 +83,6 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) if (c_data(req) && s->logs.t_idle == -1) s->logs.t_idle = tv_ms_elapsed(&s->logs.tv_accept, &now) - s->logs.t_handshake; - /* There's a protected area at the end of the buffer for rewriting - * purposes. We don't want to start to parse the request if the - * protected area is affected, because we may have to move processed - * data later, which is much more complicated. - */ - if (c_data(req) && msg->msg_state < HTTP_MSG_ERROR) { - if (txn->flags & TX_NOT_FIRST) { - if (unlikely(!channel_is_rewritable(req))) { - if (req->flags & (CF_SHUTW|CF_SHUTW_NOW|CF_WRITE_ERROR|CF_WRITE_TIMEOUT)) - goto failed_keep_alive; - /* some data has still not left the buffer, wake us once that's done */ - channel_dont_connect(req); - req->flags |= CF_READ_DONTWAIT; /* try to get back here ASAP */ - req->flags |= CF_WAKE_WRITE; - return 0; - } - if (unlikely(ci_tail(req) < c_ptr(req, msg->next) || - ci_tail(req) > b_wrap(&req->buf) - global.tune.maxrewrite)) - channel_slow_realign(req, trash.area); - } - - if (likely(msg->next < ci_data(req))) /* some unparsed data are available */ - http_msg_analyzer(msg, &txn->hdr_idx); - } - - /* 1: we might have to print this header in debug mode */ - if (unlikely((global.mode & MODE_DEBUG) && - (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) && - msg->msg_state >= HTTP_MSG_BODY)) { - char *eol, *sol; - - sol = ci_head(req); - /* this is a bit complex : in case of error on the request line, - * we know that rq.l is still zero, so we display only the part - * up to the end of the line (truncated by debug_hdr). - */ - eol = sol + (msg->sl.rq.l ? msg->sl.rq.l : ci_data(req)); - debug_hdr("clireq", s, sol, eol); - - sol += hdr_idx_first_pos(&txn->hdr_idx); - cur_idx = hdr_idx_first_idx(&txn->hdr_idx); - - while (cur_idx) { - eol = sol + txn->hdr_idx.v[cur_idx].len; - debug_hdr("clihdr", s, sol, eol); - sol = eol + txn->hdr_idx.v[cur_idx].cr + 1; - cur_idx = txn->hdr_idx.v[cur_idx].next; - } - } - /* * Now we quickly check if we have found a full valid request. * If not so, we check the FD and buffer states before leaving. @@ -159,36 +98,9 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) * a timeout or connection reset is not counted as an error. However * a bad request is. */ - if (unlikely(msg->msg_state < HTTP_MSG_BODY)) { - /* - * First, let's catch bad requests. - */ - if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) { - stream_inc_http_req_ctr(s); - stream_inc_http_err_ctr(s); - proxy_inc_fe_req_ctr(sess->fe); - goto return_bad_req; - } - - /* 1: Since we are in header mode, if there's no space - * left for headers, we won't be able to free more - * later, so the stream will never terminate. We - * must terminate it now. - */ - if (unlikely(channel_full(req, global.tune.maxrewrite))) { - /* FIXME: check if URI is set and return Status - * 414 Request URI too long instead. - */ - stream_inc_http_req_ctr(s); - stream_inc_http_err_ctr(s); - proxy_inc_fe_req_ctr(sess->fe); - if (msg->err_pos < 0) - msg->err_pos = ci_data(req); - goto return_bad_req; - } - - /* 2: have we encountered a read error ? */ - else if (req->flags & CF_READ_ERROR) { + if (unlikely(htx_is_empty(htx) || htx_get_tail_type(htx) < HTX_BLK_EOH)) { + /* 1: have we encountered a read error ? */ + if (req->flags & CF_READ_ERROR) { if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_CLICL; @@ -198,29 +110,25 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) if (sess->fe->options & PR_O_IGNORE_PRB) goto failed_keep_alive; - /* we cannot return any message on error */ - if (msg->err_pos >= 0) { - http_capture_bad_message(sess->fe, s, msg, msg->err_state, sess->fe); - stream_inc_http_err_ctr(s); - } - - txn->status = 400; - msg->err_state = msg->msg_state; - msg->msg_state = HTTP_MSG_ERROR; - http_reply_and_close(s, txn->status, NULL); - req->analysers &= AN_REQ_FLT_END; + stream_inc_http_err_ctr(s); stream_inc_http_req_ctr(s); proxy_inc_fe_req_ctr(sess->fe); HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_req, 1); if (sess->listener->counters) HA_ATOMIC_ADD(&sess->listener->counters->failed_req, 1); + txn->status = 400; + msg->err_state = msg->msg_state; + msg->msg_state = HTTP_MSG_ERROR; + htx_reply_and_close(s, txn->status, NULL); + req->analysers &= AN_REQ_FLT_END; + if (!(s->flags & SF_FINST_MASK)) s->flags |= SF_FINST_R; return 0; } - /* 3: has the read timeout expired ? */ + /* 2: has the read timeout expired ? */ else if (req->flags & CF_READ_TIMEOUT || tick_is_expired(req->analyse_exp, now_ms)) { if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_CLITO; @@ -231,29 +139,25 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) if (sess->fe->options & PR_O_IGNORE_PRB) goto failed_keep_alive; - /* read timeout : give up with an error message. */ - if (msg->err_pos >= 0) { - http_capture_bad_message(sess->fe, s, msg, msg->err_state, sess->fe); - stream_inc_http_err_ctr(s); - } - txn->status = 408; - msg->err_state = msg->msg_state; - msg->msg_state = HTTP_MSG_ERROR; - http_reply_and_close(s, txn->status, http_error_message(s)); - req->analysers &= AN_REQ_FLT_END; - + stream_inc_http_err_ctr(s); stream_inc_http_req_ctr(s); proxy_inc_fe_req_ctr(sess->fe); HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_req, 1); if (sess->listener->counters) HA_ATOMIC_ADD(&sess->listener->counters->failed_req, 1); + txn->status = 408; + msg->err_state = msg->msg_state; + msg->msg_state = HTTP_MSG_ERROR; + htx_reply_and_close(s, txn->status, http_error_message(s)); + req->analysers &= AN_REQ_FLT_END; + if (!(s->flags & SF_FINST_MASK)) s->flags |= SF_FINST_R; return 0; } - /* 4: have we encountered a close ? */ + /* 3: have we encountered a close ? */ else if (req->flags & CF_SHUTR) { if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_CLICL; @@ -264,13 +168,6 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) if (sess->fe->options & PR_O_IGNORE_PRB) goto failed_keep_alive; - if (msg->err_pos >= 0) - http_capture_bad_message(sess->fe, s, msg, msg->err_state, sess->fe); - txn->status = 400; - msg->err_state = msg->msg_state; - msg->msg_state = HTTP_MSG_ERROR; - http_reply_and_close(s, txn->status, http_error_message(s)); - req->analysers &= AN_REQ_FLT_END; stream_inc_http_err_ctr(s); stream_inc_http_req_ctr(s); proxy_inc_fe_req_ctr(sess->fe); @@ -278,6 +175,12 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) if (sess->listener->counters) HA_ATOMIC_ADD(&sess->listener->counters->failed_req, 1); + txn->status = 400; + msg->err_state = msg->msg_state; + msg->msg_state = HTTP_MSG_ERROR; + htx_reply_and_close(s, txn->status, http_error_message(s)); + req->analysers &= AN_REQ_FLT_END; + if (!(s->flags & SF_FINST_MASK)) s->flags |= SF_FINST_R; return 0; @@ -287,7 +190,7 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) req->flags |= CF_READ_DONTWAIT; /* try to get back here ASAP */ s->res.flags &= ~CF_EXPECT_MORE; /* speed up sending a previous response */ #ifdef TCP_QUICKACK - if (sess->listener->options & LI_O_NOQUICKACK && ci_data(req) && + if (sess->listener->options & LI_O_NOQUICKACK && htx_is_not_empty(htx) && objt_conn(sess->origin) && conn_ctrl_ready(__objt_conn(sess->origin))) { /* We need more data, we have to re-enable quick-ack in case we * previously disabled it, otherwise we might cause the client @@ -330,48 +233,58 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) s->logs.logwait = 0; s->logs.level = 0; s->res.flags &= ~CF_EXPECT_MORE; /* speed up sending a previous response */ - http_reply_and_close(s, txn->status, NULL); + htx_reply_and_close(s, txn->status, NULL); return 0; } - /* OK now we have a complete HTTP request with indexed headers. Let's - * complete the request parsing by setting a few fields we will need - * later. At this point, we have the last CRLF at req->buf.data + msg->eoh. - * If the request is in HTTP/0.9 form, the rule is still true, and eoh - * points to the CRLF of the request line. msg->next points to the first - * byte after the last LF. msg->sov points to the first byte of data. - * msg->eol cannot be trusted because it may have been left uninitialized - * (for instance in the absence of headers). - */ - + msg->msg_state = HTTP_MSG_BODY; stream_inc_http_req_ctr(s); proxy_inc_fe_req_ctr(sess->fe); /* one more valid request for this FE */ - if (txn->flags & TX_WAIT_NEXT_RQ) { - /* kill the pending keep-alive timeout */ - txn->flags &= ~TX_WAIT_NEXT_RQ; - req->analyse_exp = TICK_ETERNITY; + /* kill the pending keep-alive timeout */ + txn->flags &= ~TX_WAIT_NEXT_RQ; + req->analyse_exp = TICK_ETERNITY; + + /* 0: we might have to print this header in debug mode */ + if (unlikely((global.mode & MODE_DEBUG) && + (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)))) { + int32_t pos; + + htx_debug_stline("clireq", s, http_find_stline(htx)); + + for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) { + struct htx_blk *blk = htx_get_blk(htx, pos); + enum htx_blk_type type = htx_get_blk_type(blk); + + if (type == HTX_BLK_EOH) + break; + if (type != HTX_BLK_HDR) + continue; + + htx_debug_hdr("clihdr", s, + htx_get_blk_name(htx, blk), + htx_get_blk_value(htx, blk)); + } } - - /* Maybe we found in invalid header name while we were configured not - * to block on that, so we have to capture it now. - */ - if (unlikely(msg->err_pos >= 0)) - http_capture_bad_message(sess->fe, s, msg, msg->err_state, sess->fe); - /* * 1: identify the method */ - txn->meth = find_http_meth(ci_head(req), msg->sl.rq.m_l); + sl = http_find_stline(htx); + txn->meth = sl.rq.meth; + msg->flags |= HTTP_MSGF_XFER_LEN; + + /* ... and check if the request is HTTP/1.1 or above */ + if ((sl.rq.v.len == 8) && + ((*(sl.rq.v.ptr + 5) > '1') || + ((*(sl.rq.v.ptr + 5) == '1') && (*(sl.rq.v.ptr + 7) >= '1')))) + msg->flags |= HTTP_MSGF_VER_11; /* we can make use of server redirect on GET and HEAD */ if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD) s->flags |= SF_REDIRECTABLE; - else if (txn->meth == HTTP_METH_OTHER && - msg->sl.rq.m_l == 3 && memcmp(ci_head(req), "PRI", 3) == 0) { + else if (txn->meth == HTTP_METH_OTHER && isteqi(sl.rq.m, ist("PRI"))) { /* PRI is reserved for the HTTP/2 preface */ - msg->err_pos = 0; goto return_bad_req; } @@ -381,10 +294,7 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) * the monitor-uri is defined by the frontend. */ if (unlikely((sess->fe->monitor_uri_len != 0) && - (sess->fe->monitor_uri_len == msg->sl.rq.u_l) && - !memcmp(ci_head(req) + msg->sl.rq.u, - sess->fe->monitor_uri, - sess->fe->monitor_uri_len))) { + isteqi(sl.rq.u, ist2(sess->fe->monitor_uri, sess->fe->monitor_uri_len)))) { /* * We have found the monitor URI */ @@ -404,7 +314,7 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) if (ret) { /* we fail this request, let's return 503 service unavail */ txn->status = 503; - http_reply_and_close(s, txn->status, http_error_message(s)); + htx_reply_and_close(s, txn->status, http_error_message(s)); if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_LOCAL; /* we don't want a real error here */ goto return_prx_cond; @@ -413,7 +323,7 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) /* nothing to fail, let's reply normaly */ txn->status = 200; - http_reply_and_close(s, txn->status, http_error_message(s)); + htx_reply_and_close(s, txn->status, http_error_message(s)); if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_LOCAL; /* we don't want a real error here */ goto return_prx_cond; @@ -427,12 +337,10 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) if (unlikely(s->logs.logwait & LW_REQ)) { /* we have a complete HTTP request that we must log */ if ((txn->uri = pool_alloc(pool_head_requri)) != NULL) { - int urilen = msg->sl.rq.l; + size_t len; - if (urilen >= global.tune.requri_len ) - urilen = global.tune.requri_len - 1; - memcpy(txn->uri, ci_head(req), urilen); - txn->uri[urilen] = 0; + len = htx_fmt_req_line(sl, txn->uri, global.tune.requri_len - 1); + txn->uri[len] = 0; if (!(s->logs.logwait &= ~(LW_REQ|LW_INIT))) s->do_log(s); @@ -441,37 +349,6 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) } } - /* RFC7230#2.6 has enforced the format of the HTTP version string to be - * exactly one digit "." one digit. This check may be disabled using - * option accept-invalid-http-request. - */ - if (!(sess->fe->options2 & PR_O2_REQBUG_OK)) { - if (msg->sl.rq.v_l != 8) { - msg->err_pos = msg->sl.rq.v; - goto return_bad_req; - } - - if (ci_head(req)[msg->sl.rq.v + 4] != '/' || - !isdigit((unsigned char)ci_head(req)[msg->sl.rq.v + 5]) || - ci_head(req)[msg->sl.rq.v + 6] != '.' || - !isdigit((unsigned char)ci_head(req)[msg->sl.rq.v + 7])) { - msg->err_pos = msg->sl.rq.v + 4; - goto return_bad_req; - } - } - else { - /* 4. We may have to convert HTTP/0.9 requests to HTTP/1.0 */ - if (unlikely(msg->sl.rq.v_l == 0) && !http_upgrade_v09_to_v10(txn)) - goto return_bad_req; - } - - /* ... and check if the request is HTTP/1.1 or above */ - if ((msg->sl.rq.v_l == 8) && - ((ci_head(req)[msg->sl.rq.v + 5] > '1') || - ((ci_head(req)[msg->sl.rq.v + 5] == '1') && - (ci_head(req)[msg->sl.rq.v + 7] >= '1')))) - msg->flags |= HTTP_MSGF_VER_11; - /* if the frontend has "option http-use-proxy-header", we'll check if * we have what looks like a proxied connection instead of a connection, * and in this case set the TX_USE_PX_CONN flag to use Proxy-connection. @@ -482,132 +359,12 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) * CONNECT ip:port. */ if ((sess->fe->options2 & PR_O2_USE_PXHDR) && - ci_head(req)[msg->sl.rq.u] != '/' && ci_head(req)[msg->sl.rq.u] != '*') + *(sl.rq.u.ptr) != '/' && *(sl.rq.u.ptr) != '*') txn->flags |= TX_USE_PX_CONN; - /* transfer length unknown*/ - msg->flags &= ~HTTP_MSGF_XFER_LEN; - /* 5: we may need to capture headers */ if (unlikely((s->logs.logwait & LW_REQHDR) && s->req_cap)) - http_capture_headers(ci_head(req), &txn->hdr_idx, - s->req_cap, sess->fe->req_cap); - - /* 6: determine the transfer-length according to RFC2616 #4.4, updated - * by RFC7230#3.3.3 : - * - * The length of a message body is determined by one of the following - * (in order of precedence): - * - * 1. Any response to a HEAD request and any response with a 1xx - * (Informational), 204 (No Content), or 304 (Not Modified) status - * code is always terminated by the first empty line after the - * header fields, regardless of the header fields present in the - * message, and thus cannot contain a message body. - * - * 2. Any 2xx (Successful) response to a CONNECT request implies that - * the connection will become a tunnel immediately after the empty - * line that concludes the header fields. A client MUST ignore any - * Content-Length or Transfer-Encoding header fields received in - * such a message. - * - * 3. If a Transfer-Encoding header field is present and the chunked - * transfer coding (Section 4.1) is the final encoding, the message - * body length is determined by reading and decoding the chunked - * data until the transfer coding indicates the data is complete. - * - * If a Transfer-Encoding header field is present in a response and - * the chunked transfer coding is not the final encoding, the - * message body length is determined by reading the connection until - * it is closed by the server. If a Transfer-Encoding header field - * is present in a request and the chunked transfer coding is not - * the final encoding, the message body length cannot be determined - * reliably; the server MUST respond with the 400 (Bad Request) - * status code and then close the connection. - * - * If a message is received with both a Transfer-Encoding and a - * Content-Length header field, the Transfer-Encoding overrides the - * Content-Length. Such a message might indicate an attempt to - * perform request smuggling (Section 9.5) or response splitting - * (Section 9.4) and ought to be handled as an error. A sender MUST - * remove the received Content-Length field prior to forwarding such - * a message downstream. - * - * 4. If a message is received without Transfer-Encoding and with - * either multiple Content-Length header fields having differing - * field-values or a single Content-Length header field having an - * invalid value, then the message framing is invalid and the - * recipient MUST treat it as an unrecoverable error. If this is a - * request message, the server MUST respond with a 400 (Bad Request) - * status code and then close the connection. If this is a response - * message received by a proxy, the proxy MUST close the connection - * to the server, discard the received response, and send a 502 (Bad - * Gateway) response to the client. If this is a response message - * received by a user agent, the user agent MUST close the - * connection to the server and discard the received response. - * - * 5. If a valid Content-Length header field is present without - * Transfer-Encoding, its decimal value defines the expected message - * body length in octets. If the sender closes the connection or - * the recipient times out before the indicated number of octets are - * received, the recipient MUST consider the message to be - * incomplete and close the connection. - * - * 6. If this is a request message and none of the above are true, then - * the message body length is zero (no message body is present). - * - * 7. Otherwise, this is a response message without a declared message - * body length, so the message body length is determined by the - * number of octets received prior to the server closing the - * connection. - */ - - ctx.idx = 0; - /* set TE_CHNK and XFER_LEN only if "chunked" is seen last */ - while (http_find_header2("Transfer-Encoding", 17, ci_head(req), &txn->hdr_idx, &ctx)) { - if (ctx.vlen == 7 && strncasecmp(ctx.line + ctx.val, "chunked", 7) == 0) - msg->flags |= HTTP_MSGF_TE_CHNK; - else if (msg->flags & HTTP_MSGF_TE_CHNK) { - /* chunked not last, return badreq */ - goto return_bad_req; - } - } - - /* Chunked requests must have their content-length removed */ - ctx.idx = 0; - if (msg->flags & HTTP_MSGF_TE_CHNK) { - while (http_find_header2("Content-Length", 14, ci_head(req), &txn->hdr_idx, &ctx)) - http_remove_header2(msg, &txn->hdr_idx, &ctx); - } - else while (http_find_header2("Content-Length", 14, ci_head(req), &txn->hdr_idx, &ctx)) { - signed long long cl; - - if (!ctx.vlen) { - msg->err_pos = ctx.line + ctx.val - ci_head(req); - goto return_bad_req; - } - - if (strl2llrc(ctx.line + ctx.val, ctx.vlen, &cl)) { - msg->err_pos = ctx.line + ctx.val - ci_head(req); - goto return_bad_req; /* parse failure */ - } - - if (cl < 0) { - msg->err_pos = ctx.line + ctx.val - ci_head(req); - goto return_bad_req; - } - - if ((msg->flags & HTTP_MSGF_CNT_LEN) && (msg->chunk_len != cl)) { - msg->err_pos = ctx.line + ctx.val - ci_head(req); - goto return_bad_req; /* already specified, was different */ - } - - msg->flags |= HTTP_MSGF_CNT_LEN; - msg->body_len = msg->chunk_len = cl; - } - - /* even bodyless requests have a known length */ - msg->flags |= HTTP_MSGF_XFER_LEN; + htx_capture_headers(htx, s->req_cap, sess->fe->req_cap); /* Until set to anything else, the connection mode is set as Keep-Alive. It will * only change if both the request and the config reference something else. @@ -623,8 +380,7 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) htx_adjust_conn_mode(s, txn); /* we may have to wait for the request's body */ - if ((s->be->options & PR_O_WREQ_BODY) && - (msg->body_len || (msg->flags & HTTP_MSGF_TE_CHNK))) + if (s->be->options & PR_O_WREQ_BODY) req->analysers |= AN_REQ_HTTP_BODY; /* @@ -650,22 +406,14 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) /* end of job, return OK */ req->analysers &= ~an_bit; req->analyse_exp = TICK_ETERNITY; + return 1; return_bad_req: - /* We centralize bad requests processing here */ - if (unlikely(msg->msg_state == HTTP_MSG_ERROR) || msg->err_pos >= 0) { - /* we detected a parsing error. We want to archive this request - * in the dedicated proxy area for later troubleshooting. - */ - http_capture_bad_message(sess->fe, s, msg, msg->err_state, sess->fe); - } - + txn->status = 400; txn->req.err_state = txn->req.msg_state; txn->req.msg_state = HTTP_MSG_ERROR; - txn->status = 400; - http_reply_and_close(s, txn->status, http_error_message(s)); - + htx_reply_and_close(s, txn->status, http_error_message(s)); HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_req, 1); if (sess->listener->counters) HA_ATOMIC_ADD(&sess->listener->counters->failed_req, 1); @@ -700,6 +448,11 @@ int htx_process_req_common(struct stream *s, struct channel *req, int an_bit, st int deny_status = HTTP_ERR_403; struct connection *conn = objt_conn(sess->origin); + // TODO: Disabled for now + req->analyse_exp = TICK_ETERNITY; + req->analysers &= ~an_bit; + return 1; + if (unlikely(msg->msg_state < HTTP_MSG_BODY)) { /* we need more data */ goto return_prx_yield; @@ -971,6 +724,13 @@ int htx_process_request(struct stream *s, struct channel *req, int an_bit) struct http_msg *msg = &txn->req; struct connection *cli_conn = objt_conn(strm_sess(s)->origin); + // TODO: Disabled for now + req->analysers &= ~AN_REQ_FLT_XFER_DATA; + req->analysers |= AN_REQ_HTTP_XFER_BODY; + req->analyse_exp = TICK_ETERNITY; + req->analysers &= ~an_bit; + return 1; + if (unlikely(msg->msg_state < HTTP_MSG_BODY)) { /* we need more data */ channel_dont_connect(req); @@ -1281,6 +1041,11 @@ int htx_process_tarpit(struct stream *s, struct channel *req, int an_bit) { struct http_txn *txn = s->txn; + // TODO: Disabled for now + req->analyse_exp = TICK_ETERNITY; + req->analysers &= ~an_bit; + return 1; + /* This connection is being tarpitted. The CLIENT side has * already set the connect expiration date to the right * timeout. We just have to check that the client is still @@ -1327,6 +1092,11 @@ int htx_wait_for_request_body(struct stream *s, struct channel *req, int an_bit) struct http_txn *txn = s->txn; struct http_msg *msg = &s->txn->req; + // TODO: Disabled for now + req->analyse_exp = TICK_ETERNITY; + req->analysers &= ~an_bit; + return 1; + /* 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 @@ -1488,8 +1258,9 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) { struct session *sess = s->sess; struct http_txn *txn = s->txn; - struct http_msg *msg = &s->txn->req; - int ret; + struct http_msg *msg = &txn->req; + struct htx *htx; + //int ret; DPRINTF(stderr,"[%u] %s: stream=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%lu analysers=%02x\n", now_ms, __FUNCTION__, @@ -1500,8 +1271,7 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) ci_data(req), req->analysers); - if (unlikely(msg->msg_state < HTTP_MSG_BODY)) - return 0; + htx = htx_from_buf(&req->buf); if ((req->flags & (CF_READ_ERROR|CF_READ_TIMEOUT|CF_WRITE_ERROR|CF_WRITE_TIMEOUT)) || ((req->flags & CF_SHUTW) && (req->to_forward || co_data(req)))) { @@ -1520,17 +1290,8 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) * decide whether to return 100, 417 or anything else in return of * an "Expect: 100-continue" header. */ - if (msg->msg_state == HTTP_MSG_BODY) { - msg->msg_state = ((msg->flags & HTTP_MSGF_TE_CHNK) - ? HTTP_MSG_CHUNK_SIZE - : HTTP_MSG_DATA); - - /* TODO/filters: when http-buffer-request option is set or if a - * rule on url_param exists, the first chunk size could be - * already parsed. In that case, msg->next is after the chunk - * size (including the CRLF after the size). So this case should - * be handled to */ - } + if (msg->msg_state == HTTP_MSG_BODY) + msg->msg_state = HTTP_MSG_DATA; /* Some post-connect processing might want us to refrain from starting to * forward data. Currently, the only reason for this is "balance url_param" @@ -1555,16 +1316,33 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) goto missing_data_or_waiting; } - if (msg->msg_state < HTTP_MSG_DONE) { - ret = ((msg->flags & HTTP_MSGF_TE_CHNK) - ? http_msg_forward_chunked_body(s, msg) - : http_msg_forward_body(s, msg)); - if (!ret) - goto missing_data_or_waiting; - if (ret < 0) - goto return_bad_req; - } + if (msg->msg_state >= HTTP_MSG_DONE) + goto done; + /* Forward all input data. We get it by removing all outgoing data not + * forwarded yet from HTX data size. + */ + c_adv(req, htx->data - co_data(req)); + + /* To let the function channel_forward work as expected we must update + * the channel's buffer to pretend there is no more input data. The + * right length is then restored. We must do that, because when an HTX + * message is stored into a buffer, it appears as full. + */ + b_set_data(&req->buf, co_data(req)); + if (htx->extra != ULLONG_MAX) + htx->extra -= channel_forward(req, htx->extra); + b_set_data(&req->buf, b_size(&req->buf)); + + /* Check if the end-of-message is reached and if so, switch the message + * in HTTP_MSG_DONE state. + */ + if (htx_get_tail_type(htx) != HTX_BLK_EOM) + goto missing_data_or_waiting; + + msg->msg_state = HTTP_MSG_DONE; + + done: /* other states, DONE...TUNNEL */ /* we don't want to forward closes on DONE except in tunnel mode. */ if ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) @@ -1579,8 +1357,6 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) * server aborting the transfer. */ goto aborted_xfer; } - if (msg->err_pos >= 0) - http_capture_bad_message(sess->fe, s, msg, msg->err_state, s->be); goto return_bad_req; } return 1; @@ -1608,7 +1384,7 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) missing_data_or_waiting: /* stop waiting for data if the input is closed before the end */ - if (msg->msg_state < HTTP_MSG_ENDING && req->flags & CF_SHUTR) { + if (msg->msg_state < HTTP_MSG_DONE && req->flags & CF_SHUTR) { if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_CLICL; if (!(s->flags & SF_FINST_MASK)) { @@ -1631,6 +1407,7 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) if (req->flags & CF_SHUTW) goto aborted_xfer; + /* When TE: chunked is used, we need to get there again to parse remaining * chunks even if the client has closed, so we don't want to set CF_DONTCLOSE. * And when content-length is used, we never want to let the possible @@ -1639,9 +1416,12 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) * prevent TIME_WAITs from accumulating on the backend side, and for * HTTP/2 where the last frame comes with a shutdown. */ - if (msg->flags & (HTTP_MSGF_TE_CHNK|HTTP_MSGF_CNT_LEN)) + if (msg->flags & HTTP_MSGF_XFER_LEN) channel_dont_close(req); +#if 0 // FIXME [Cf]: Probably not required now, but I need more time to think + // about if + /* We know that more data are expected, but we couldn't send more that * what we did. So we always set the CF_EXPECT_MORE flag so that the * system knows it must not set a PUSH on this first part. Interactive @@ -1652,6 +1432,7 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) */ if (msg->flags & HTTP_MSGF_TE_CHNK) req->flags |= CF_EXPECT_MORE; +#endif return 0; @@ -1663,12 +1444,12 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) return_bad_req_stats_ok: txn->req.err_state = txn->req.msg_state; txn->req.msg_state = HTTP_MSG_ERROR; - if (txn->status) { + if (txn->status > 0) { /* Note: we don't send any error if some data were already sent */ - http_reply_and_close(s, txn->status, NULL); + htx_reply_and_close(s, txn->status, NULL); } else { txn->status = 400; - http_reply_and_close(s, txn->status, http_error_message(s)); + htx_reply_and_close(s, txn->status, http_error_message(s)); } req->analysers &= AN_REQ_FLT_END; s->res.analysers &= AN_RES_FLT_END; /* we're in data phase, we want to abort both directions */ @@ -1686,12 +1467,12 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) aborted_xfer: txn->req.err_state = txn->req.msg_state; txn->req.msg_state = HTTP_MSG_ERROR; - if (txn->status) { + if (txn->status > 0) { /* Note: we don't send any error if some data were already sent */ - http_reply_and_close(s, txn->status, NULL); + htx_reply_and_close(s, txn->status, NULL); } else { txn->status = 502; - http_reply_and_close(s, txn->status, http_error_message(s)); + htx_reply_and_close(s, txn->status, http_error_message(s)); } req->analysers &= AN_REQ_FLT_END; s->res.analysers &= AN_RES_FLT_END; /* we're in data phase, we want to abort both directions */ @@ -1721,12 +1502,18 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) */ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) { + /* + * We will analyze a complete HTTP response to check the its syntax. + * + * Once the start line and all headers are received, we may perform a + * capture of the error (if any), and we will set a few fields. We also + * logging and finally headers capture. + */ struct session *sess = s->sess; struct http_txn *txn = s->txn; struct http_msg *msg = &txn->rsp; - struct hdr_ctx ctx; - int use_close_only; - int cur_idx; + struct htx *htx; + union h1_sl sl; int n; DPRINTF(stderr,"[%u] %s: stream=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%lu analysers=%02x\n", @@ -1738,67 +1525,7 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) ci_data(rep), rep->analysers); - /* - * Now parse the partial (or complete) lines. - * We will check the response syntax, and also join multi-line - * headers. An index of all the lines will be elaborated while - * parsing. - * - * For the parsing, we use a 28 states FSM. - * - * Here is the information we currently have : - * ci_head(rep) = beginning of response - * ci_head(rep) + msg->eoh = end of processed headers / start of current one - * ci_tail(rep) = end of input data - * msg->eol = end of current header or line (LF or CRLF) - * msg->next = first non-visited byte - */ - - next_one: - /* There's a protected area at the end of the buffer for rewriting - * purposes. We don't want to start to parse the request if the - * protected area is affected, because we may have to move processed - * data later, which is much more complicated. - */ - if (c_data(rep) && msg->msg_state < HTTP_MSG_ERROR) { - if (unlikely(!channel_is_rewritable(rep))) { - /* some data has still not left the buffer, wake us once that's done */ - if (rep->flags & (CF_SHUTW|CF_SHUTW_NOW|CF_WRITE_ERROR|CF_WRITE_TIMEOUT)) - goto abort_response; - channel_dont_close(rep); - rep->flags |= CF_READ_DONTWAIT; /* try to get back here ASAP */ - rep->flags |= CF_WAKE_WRITE; - return 0; - } - - if (unlikely(ci_tail(rep) < c_ptr(rep, msg->next) || - ci_tail(rep) > b_wrap(&rep->buf) - global.tune.maxrewrite)) - channel_slow_realign(rep, trash.area); - - if (likely(msg->next < ci_data(rep))) - http_msg_analyzer(msg, &txn->hdr_idx); - } - - /* 1: we might have to print this header in debug mode */ - if (unlikely((global.mode & MODE_DEBUG) && - (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) && - msg->msg_state >= HTTP_MSG_BODY)) { - char *eol, *sol; - - sol = ci_head(rep); - eol = sol + (msg->sl.st.l ? msg->sl.st.l : ci_data(rep)); - debug_hdr("srvrep", s, sol, eol); - - sol += hdr_idx_first_pos(&txn->hdr_idx); - cur_idx = hdr_idx_first_idx(&txn->hdr_idx); - - while (cur_idx) { - eol = sol + txn->hdr_idx.v[cur_idx].len; - debug_hdr("srvhdr", s, sol, eol); - sol = eol + txn->hdr_idx.v[cur_idx].cr + 1; - cur_idx = txn->hdr_idx.v[cur_idx].next; - } - } + htx = htx_from_buf(&rep->buf); /* * Now we quickly check if we have found a full valid response. @@ -1812,50 +1539,10 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) * we should only check for HTTP status there, and check I/O * errors somewhere else. */ - - if (unlikely(msg->msg_state < HTTP_MSG_BODY)) { - /* Invalid response */ - if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) { - /* we detected a parsing error. We want to archive this response - * in the dedicated proxy area for later troubleshooting. - */ - hdr_response_bad: - if (msg->msg_state == HTTP_MSG_ERROR || msg->err_pos >= 0) - http_capture_bad_message(s->be, s, msg, msg->err_state, sess->fe); - - HA_ATOMIC_ADD(&s->be->be_counters.failed_resp, 1); - if (objt_server(s->target)) { - HA_ATOMIC_ADD(&objt_server(s->target)->counters.failed_resp, 1); - health_adjust(objt_server(s->target), HANA_STATUS_HTTP_HDRRSP); - } - abort_response: - channel_auto_close(rep); - rep->analysers &= AN_RES_FLT_END; - txn->status = 502; - s->si[1].flags |= SI_FL_NOLINGER; - channel_truncate(rep); - http_reply_and_close(s, txn->status, http_error_message(s)); - - if (!(s->flags & SF_ERR_MASK)) - s->flags |= SF_ERR_PRXCOND; - if (!(s->flags & SF_FINST_MASK)) - s->flags |= SF_FINST_H; - - return 0; - } - - /* too large response does not fit in buffer. */ - else if (channel_full(rep, global.tune.maxrewrite)) { - if (msg->err_pos < 0) - msg->err_pos = ci_data(rep); - goto hdr_response_bad; - } - - /* read error */ - else if (rep->flags & CF_READ_ERROR) { - if (msg->err_pos >= 0) - http_capture_bad_message(s->be, s, msg, msg->err_state, sess->fe); - else if (txn->flags & TX_NOT_FIRST) + if (unlikely(htx_is_empty(htx) || htx_get_tail_type(htx) < HTX_BLK_EOH)) { + /* 1: have we encountered a read error ? */ + if (rep->flags & CF_READ_ERROR) { + if (txn->flags & TX_NOT_FIRST) goto abort_keep_alive; HA_ATOMIC_ADD(&s->be->be_counters.failed_resp, 1); @@ -1864,7 +1551,6 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) health_adjust(objt_server(s->target), HANA_STATUS_HTTP_READ_ERROR); } - channel_auto_close(rep); rep->analysers &= AN_RES_FLT_END; txn->status = 502; @@ -1879,8 +1565,7 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) } s->si[1].flags |= SI_FL_NOLINGER; - channel_truncate(rep); - http_reply_and_close(s, txn->status, http_error_message(s)); + htx_reply_and_close(s, txn->status, http_error_message(s)); if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_SRVCL; @@ -1889,23 +1574,18 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) return 0; } - /* read timeout : return a 504 to the client. */ + /* 2: read timeout : return a 504 to the client. */ else if (rep->flags & CF_READ_TIMEOUT) { - if (msg->err_pos >= 0) - http_capture_bad_message(s->be, s, msg, msg->err_state, sess->fe); - HA_ATOMIC_ADD(&s->be->be_counters.failed_resp, 1); if (objt_server(s->target)) { HA_ATOMIC_ADD(&objt_server(s->target)->counters.failed_resp, 1); health_adjust(objt_server(s->target), HANA_STATUS_HTTP_READ_TIMEOUT); } - channel_auto_close(rep); rep->analysers &= AN_RES_FLT_END; txn->status = 504; s->si[1].flags |= SI_FL_NOLINGER; - channel_truncate(rep); - http_reply_and_close(s, txn->status, http_error_message(s)); + htx_reply_and_close(s, txn->status, http_error_message(s)); if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_SRVTO; @@ -1914,7 +1594,7 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) return 0; } - /* client abort with an abortonclose */ + /* 3: client abort with an abortonclose */ else if ((rep->flags & CF_SHUTR) && ((s->req.flags & (CF_SHUTR|CF_SHUTW)) == (CF_SHUTR|CF_SHUTW))) { HA_ATOMIC_ADD(&sess->fe->fe_counters.cli_aborts, 1); HA_ATOMIC_ADD(&s->be->be_counters.cli_aborts, 1); @@ -1922,11 +1602,8 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) HA_ATOMIC_ADD(&objt_server(s->target)->counters.cli_aborts, 1); rep->analysers &= AN_RES_FLT_END; - channel_auto_close(rep); - txn->status = 400; - channel_truncate(rep); - http_reply_and_close(s, txn->status, http_error_message(s)); + htx_reply_and_close(s, txn->status, http_error_message(s)); if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_CLICL; @@ -1937,11 +1614,9 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) return 0; } - /* close from server, capture the response if the server has started to respond */ + /* 4: close from server, capture the response if the server has started to respond */ else if (rep->flags & CF_SHUTR) { - if (msg->msg_state >= HTTP_MSG_RPVER || msg->err_pos >= 0) - http_capture_bad_message(s->be, s, msg, msg->err_state, sess->fe); - else if (txn->flags & TX_NOT_FIRST) + if (txn->flags & TX_NOT_FIRST) goto abort_keep_alive; HA_ATOMIC_ADD(&s->be->be_counters.failed_resp, 1); @@ -1950,12 +1625,10 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) health_adjust(objt_server(s->target), HANA_STATUS_HTTP_BROKEN_PIPE); } - channel_auto_close(rep); rep->analysers &= AN_RES_FLT_END; txn->status = 502; s->si[1].flags |= SI_FL_NOLINGER; - channel_truncate(rep); - http_reply_and_close(s, txn->status, http_error_message(s)); + htx_reply_and_close(s, txn->status, http_error_message(s)); if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_SRVCL; @@ -1964,16 +1637,13 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) return 0; } - /* write error to client (we don't send any message then) */ + /* 5: write error to client (we don't send any message then) */ else if (rep->flags & CF_WRITE_ERROR) { - if (msg->err_pos >= 0) - http_capture_bad_message(s->be, s, msg, msg->err_state, sess->fe); - else if (txn->flags & TX_NOT_FIRST) + if (txn->flags & TX_NOT_FIRST) goto abort_keep_alive; HA_ATOMIC_ADD(&s->be->be_counters.failed_resp, 1); rep->analysers &= AN_RES_FLT_END; - channel_auto_close(rep); if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_CLICL; @@ -1994,15 +1664,46 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) * of each header's length, so we can parse them quickly. */ - if (unlikely(msg->err_pos >= 0)) - http_capture_bad_message(s->be, s, msg, msg->err_state, sess->fe); + msg->msg_state = HTTP_MSG_BODY; - /* - * 1: get the status code - */ - n = ci_head(rep)[msg->sl.st.c] - '0'; + /* 0: we might have to print this header in debug mode */ + if (unlikely((global.mode & MODE_DEBUG) && + (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)))) { + int32_t pos; + + htx_debug_stline("srvrep", s, http_find_stline(htx)); + + for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) { + struct htx_blk *blk = htx_get_blk(htx, pos); + enum htx_blk_type type = htx_get_blk_type(blk); + + if (type == HTX_BLK_EOH) + break; + if (type != HTX_BLK_HDR) + continue; + + htx_debug_hdr("srvhdr", s, + htx_get_blk_name(htx, blk), + htx_get_blk_value(htx, blk)); + } + } + + /* 1: get the status code */ + sl = http_find_stline(htx); + txn->status = sl.st.status; + if (htx->extra != ULLONG_MAX) + msg->flags |= HTTP_MSGF_XFER_LEN; + + /* ... and check if the request is HTTP/1.1 or above */ + if ((sl.st.v.len == 8) && + ((*(sl.st.v.ptr + 5) > '1') || + ((*(sl.st.v.ptr + 5) == '1') && (*(sl.st.v.ptr + 7) >= '1')))) + msg->flags |= HTTP_MSGF_VER_11; + + n = txn->status / 100; if (n < 1 || n > 5) n = 0; + /* when the client triggers a 4xx from the server, it's most often due * to a missing object or permission. These events should be tracked * because if they happen often, it may indicate a brute force or a @@ -2014,36 +1715,6 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) if (objt_server(s->target)) HA_ATOMIC_ADD(&objt_server(s->target)->counters.p.http.rsp[n], 1); - /* RFC7230#2.6 has enforced the format of the HTTP version string to be - * exactly one digit "." one digit. This check may be disabled using - * option accept-invalid-http-response. - */ - if (!(s->be->options2 & PR_O2_RSPBUG_OK)) { - if (msg->sl.st.v_l != 8) { - msg->err_pos = 0; - goto hdr_response_bad; - } - - if (ci_head(rep)[4] != '/' || - !isdigit((unsigned char)ci_head(rep)[5]) || - ci_head(rep)[6] != '.' || - !isdigit((unsigned char)ci_head(rep)[7])) { - msg->err_pos = 4; - goto hdr_response_bad; - } - } - - /* check if the response is HTTP/1.1 or above */ - if ((msg->sl.st.v_l == 8) && - ((ci_head(rep)[5] > '1') || - ((ci_head(rep)[5] == '1') && (ci_head(rep)[7] >= '1')))) - msg->flags |= HTTP_MSGF_VER_11; - - /* transfer length unknown*/ - msg->flags &= ~HTTP_MSGF_XFER_LEN; - - txn->status = strl2ui(ci_head(rep) + msg->sl.st.c, msg->sl.st.c_l); - /* Adjust server's health based on status code. Note: status codes 501 * and 505 are triggered on demand by client request, so we must not * count them as server failures. @@ -2064,13 +1735,12 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) */ if (txn->status < 200 && (txn->status == 100 || txn->status >= 102)) { - hdr_idx_init(&txn->hdr_idx); - msg->next -= channel_forward(rep, msg->next); + //FLT_STRM_CB(s, flt_htx_reset(s, http, htx)); + c_adv(rep, htx->data); msg->msg_state = HTTP_MSG_RPBEFORE; txn->status = 0; s->logs.t_data = -1; /* was not a response yet */ - FLT_STRM_CB(s, flt_http_reset(s, msg)); - goto next_one; + return 0; } /* @@ -2110,84 +1780,9 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) */ s->logs.logwait &= ~LW_RESP; if (unlikely((s->logs.logwait & LW_RSPHDR) && s->res_cap)) - http_capture_headers(ci_head(rep), &txn->hdr_idx, - s->res_cap, sess->fe->rsp_cap); + htx_capture_headers(htx, s->res_cap, sess->fe->rsp_cap); - /* 4: determine the transfer-length according to RFC2616 #4.4, updated - * by RFC7230#3.3.3 : - * - * The length of a message body is determined by one of the following - * (in order of precedence): - * - * 1. Any 2xx (Successful) response to a CONNECT request implies that - * the connection will become a tunnel immediately after the empty - * line that concludes the header fields. A client MUST ignore - * any Content-Length or Transfer-Encoding header fields received - * in such a message. Any 101 response (Switching Protocols) is - * managed in the same manner. - * - * 2. Any response to a HEAD request and any response with a 1xx - * (Informational), 204 (No Content), or 304 (Not Modified) status - * code is always terminated by the first empty line after the - * header fields, regardless of the header fields present in the - * message, and thus cannot contain a message body. - * - * 3. If a Transfer-Encoding header field is present and the chunked - * transfer coding (Section 4.1) is the final encoding, the message - * body length is determined by reading and decoding the chunked - * data until the transfer coding indicates the data is complete. - * - * If a Transfer-Encoding header field is present in a response and - * the chunked transfer coding is not the final encoding, the - * message body length is determined by reading the connection until - * it is closed by the server. If a Transfer-Encoding header field - * is present in a request and the chunked transfer coding is not - * the final encoding, the message body length cannot be determined - * reliably; the server MUST respond with the 400 (Bad Request) - * status code and then close the connection. - * - * If a message is received with both a Transfer-Encoding and a - * Content-Length header field, the Transfer-Encoding overrides the - * Content-Length. Such a message might indicate an attempt to - * perform request smuggling (Section 9.5) or response splitting - * (Section 9.4) and ought to be handled as an error. A sender MUST - * remove the received Content-Length field prior to forwarding such - * a message downstream. - * - * 4. If a message is received without Transfer-Encoding and with - * either multiple Content-Length header fields having differing - * field-values or a single Content-Length header field having an - * invalid value, then the message framing is invalid and the - * recipient MUST treat it as an unrecoverable error. If this is a - * request message, the server MUST respond with a 400 (Bad Request) - * status code and then close the connection. If this is a response - * message received by a proxy, the proxy MUST close the connection - * to the server, discard the received response, and send a 502 (Bad - * Gateway) response to the client. If this is a response message - * received by a user agent, the user agent MUST close the - * connection to the server and discard the received response. - * - * 5. If a valid Content-Length header field is present without - * Transfer-Encoding, its decimal value defines the expected message - * body length in octets. If the sender closes the connection or - * the recipient times out before the indicated number of octets are - * received, the recipient MUST consider the message to be - * incomplete and close the connection. - * - * 6. If this is a request message and none of the above are true, then - * the message body length is zero (no message body is present). - * - * 7. Otherwise, this is a response message without a declared message - * body length, so the message body length is determined by the - * number of octets received prior to the server closing the - * connection. - */ - - /* Skip parsing if no content length is possible. The response flags - * remain 0 as well as the chunk_len, which may or may not mirror - * the real header value, and we note that we know the response's length. - * FIXME: should we parse anyway and return an error on chunked encoding ? - */ + /* Skip parsing if no content length is possible. */ if (unlikely((txn->meth == HTTP_METH_CONNECT && txn->status == 200) || txn->status == 101)) { /* Either we've established an explicit tunnel, or we're @@ -2200,64 +1795,8 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) * responses with status 101 (eg: see RFC2817 about TLS). */ txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_TUN; - msg->flags |= HTTP_MSGF_XFER_LEN; - goto end; } - if (txn->meth == HTTP_METH_HEAD || - (txn->status >= 100 && txn->status < 200) || - txn->status == 204 || txn->status == 304) { - msg->flags |= HTTP_MSGF_XFER_LEN; - goto end; - } - - use_close_only = 0; - ctx.idx = 0; - while (http_find_header2("Transfer-Encoding", 17, ci_head(rep), &txn->hdr_idx, &ctx)) { - if (ctx.vlen == 7 && strncasecmp(ctx.line + ctx.val, "chunked", 7) == 0) - msg->flags |= (HTTP_MSGF_TE_CHNK | HTTP_MSGF_XFER_LEN); - else if (msg->flags & HTTP_MSGF_TE_CHNK) { - /* bad transfer-encoding (chunked followed by something else) */ - use_close_only = 1; - msg->flags &= ~(HTTP_MSGF_TE_CHNK | HTTP_MSGF_XFER_LEN); - break; - } - } - - /* Chunked responses must have their content-length removed */ - ctx.idx = 0; - if (use_close_only || (msg->flags & HTTP_MSGF_TE_CHNK)) { - while (http_find_header2("Content-Length", 14, ci_head(rep), &txn->hdr_idx, &ctx)) - http_remove_header2(msg, &txn->hdr_idx, &ctx); - } - else while (http_find_header2("Content-Length", 14, ci_head(rep), &txn->hdr_idx, &ctx)) { - signed long long cl; - - if (!ctx.vlen) { - msg->err_pos = ctx.line + ctx.val - ci_head(rep); - goto hdr_response_bad; - } - - if (strl2llrc(ctx.line + ctx.val, ctx.vlen, &cl)) { - msg->err_pos = ctx.line + ctx.val - ci_head(rep); - goto hdr_response_bad; /* parse failure */ - } - - if (cl < 0) { - msg->err_pos = ctx.line + ctx.val - ci_head(rep); - goto hdr_response_bad; - } - - if ((msg->flags & HTTP_MSGF_CNT_LEN) && (msg->chunk_len != cl)) { - msg->err_pos = ctx.line + ctx.val - ci_head(rep); - goto hdr_response_bad; /* already specified, was different */ - } - - msg->flags |= HTTP_MSGF_CNT_LEN | HTTP_MSGF_XFER_LEN; - msg->body_len = msg->chunk_len = cl; - } - - end: /* we want to have the response time before we start processing it */ s->logs.t_data = tv_ms_elapsed(&s->logs.tv_accept, &now); @@ -2275,12 +1814,10 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) txn->status = 0; rep->analysers &= AN_RES_FLT_END; s->req.analysers &= AN_REQ_FLT_END; - channel_auto_close(rep); s->logs.logwait = 0; s->logs.level = 0; s->res.flags &= ~CF_EXPECT_MORE; /* speed up sending a previous response */ - channel_truncate(rep); - http_reply_and_close(s, txn->status, NULL); + htx_reply_and_close(s, txn->status, NULL); return 0; } @@ -2298,6 +1835,13 @@ int htx_process_res_common(struct stream *s, struct channel *rep, int an_bit, st struct cond_wordlist *wl; enum rule_result ret = HTTP_RULE_RES_CONT; + // TODO: Disabled for now + rep->analysers &= ~AN_RES_FLT_XFER_DATA; + rep->analysers |= AN_RES_HTTP_XFER_BODY; + rep->analyse_exp = TICK_ETERNITY; + rep->analysers &= ~an_bit; + return 1; + DPRINTF(stderr,"[%u] %s: stream=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%lu analysers=%02x\n", now_ms, __FUNCTION__, s, @@ -2610,7 +2154,8 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) struct session *sess = s->sess; struct http_txn *txn = s->txn; struct http_msg *msg = &s->txn->rsp; - int ret; + struct htx *htx; + //int ret; DPRINTF(stderr,"[%u] %s: stream=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%lu analysers=%02x\n", now_ms, __FUNCTION__, @@ -2621,8 +2166,7 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) ci_data(res), res->analysers); - if (unlikely(msg->msg_state < HTTP_MSG_BODY)) - return 0; + htx = htx_from_buf(&res->buf); if ((res->flags & (CF_READ_ERROR|CF_READ_TIMEOUT|CF_WRITE_ERROR|CF_WRITE_TIMEOUT)) || ((res->flags & CF_SHUTW) && (res->to_forward || co_data(res)))) { @@ -2636,32 +2180,56 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) return 1; } + if (msg->msg_state == HTTP_MSG_BODY) + msg->msg_state = HTTP_MSG_DATA; + /* in most states, we should abort in case of early close */ channel_auto_close(res); - if (msg->msg_state == HTTP_MSG_BODY) { - msg->msg_state = ((msg->flags & HTTP_MSGF_TE_CHNK) - ? HTTP_MSG_CHUNK_SIZE - : HTTP_MSG_DATA); - } - if (res->to_forward) { /* We can't process the buffer's contents yet */ res->flags |= CF_WAKE_WRITE; goto missing_data_or_waiting; } - if (msg->msg_state < HTTP_MSG_DONE) { - ret = ((msg->flags & HTTP_MSGF_TE_CHNK) - ? http_msg_forward_chunked_body(s, msg) - : http_msg_forward_body(s, msg)); - if (!ret) - goto missing_data_or_waiting; - if (ret < 0) - goto return_bad_res; + if (msg->msg_state >= HTTP_MSG_DONE) + goto done; + + /* Forward all input data. We get it by removing all outgoing data not + * forwarded yet from HTX data size. + */ + c_adv(res, htx->data - co_data(res)); + + /* To let the function channel_forward work as expected we must update + * the channel's buffer to pretend there is no more input data. The + * right length is then restored. We must do that, because when an HTX + * message is stored into a buffer, it appears as full. + */ + b_set_data(&res->buf, co_data(res)); + if (htx->extra != ULLONG_MAX) + htx->extra -= channel_forward(res, htx->extra); + b_set_data(&res->buf, b_size(&res->buf)); + + if (!(msg->flags & HTTP_MSGF_XFER_LEN)) { + /* The server still sending data that should be filtered */ + if (res->flags & CF_SHUTR || !HAS_DATA_FILTERS(s, res)) { + msg->msg_state = HTTP_MSG_TUNNEL; + goto done; + } } + /* Check if the end-of-message is reached and if so, switch the message + * in HTTP_MSG_DONE state. + */ + if (htx_get_tail_type(htx) != HTX_BLK_EOM) + goto missing_data_or_waiting; + + msg->msg_state = HTTP_MSG_DONE; + + done: /* other states, DONE...TUNNEL */ + channel_dont_close(res); + htx_end_response(s); if (!(res->analysers & an_bit)) { htx_end_request(s); @@ -2671,8 +2239,6 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) * client aborting the transfer. */ goto aborted_xfer; } - if (msg->err_pos >= 0) - http_capture_bad_message(s->be, s, msg, msg->err_state, strm_fe(s)); goto return_bad_res; } return 1; @@ -2688,11 +2254,11 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) * so we don't want to count this as a server abort. Otherwise it's a * server abort. */ - if (msg->msg_state < HTTP_MSG_ENDING && res->flags & CF_SHUTR) { + if (msg->msg_state < HTTP_MSG_DONE && res->flags & CF_SHUTR) { if ((s->req.flags & (CF_SHUTR|CF_SHUTW)) == (CF_SHUTR|CF_SHUTW)) goto aborted_xfer; /* If we have some pending data, we continue the processing */ - if (!ci_data(res)) { + if (htx_is_empty(htx)) { if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_SRVCL; HA_ATOMIC_ADD(&s->be->be_counters.srv_aborts, 1); @@ -2704,12 +2270,16 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) /* When TE: chunked is used, we need to get there again to parse * remaining chunks even if the server has closed, so we don't want to - * set CF_DONTCLOSE. Similarly, if there are filters registered on the - * stream, we don't want to forward a close + * set CF_DONTCLOSE. Similarly when there is a content-leng or if there + * are filters registered on the stream, we don't want to forward a + * close */ - if ((msg->flags & HTTP_MSGF_TE_CHNK) || HAS_DATA_FILTERS(s, res)) + if ((msg->flags & HTTP_MSGF_XFER_LEN) || HAS_DATA_FILTERS(s, res)) channel_dont_close(res); +#if 0 // FIXME [Cf]: Probably not required now, but I need more time to think + // about if + /* We know that more data are expected, but we couldn't send more that * what we did. So we always set the CF_EXPECT_MORE flag so that the * system knows it must not set a PUSH on this first part. Interactive @@ -2720,6 +2290,7 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) */ if ((msg->flags & HTTP_MSGF_TE_CHNK) || (msg->flags & HTTP_MSGF_COMPRESSING)) res->flags |= CF_EXPECT_MORE; +#endif /* the stream handler will take care of timeouts and errors */ return 0; @@ -2733,7 +2304,7 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) txn->rsp.err_state = txn->rsp.msg_state; txn->rsp.msg_state = HTTP_MSG_ERROR; /* don't send any error message as we're in the body */ - http_reply_and_close(s, txn->status, NULL); + htx_reply_and_close(s, txn->status, NULL); res->analysers &= AN_RES_FLT_END; s->req.analysers &= AN_REQ_FLT_END; /* we're in data phase, we want to abort both directions */ if (objt_server(s->target)) @@ -2749,7 +2320,7 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) txn->rsp.err_state = txn->rsp.msg_state; txn->rsp.msg_state = HTTP_MSG_ERROR; /* don't send any error message as we're in the body */ - http_reply_and_close(s, txn->status, NULL); + htx_reply_and_close(s, txn->status, NULL); res->analysers &= AN_RES_FLT_END; s->req.analysers &= AN_REQ_FLT_END; /* we're in data phase, we want to abort both directions */ @@ -3117,26 +2688,28 @@ static void htx_end_request(struct stream *s) * poll for reads. */ channel_auto_read(chn); + if (b_data(&chn->buf)) + return; txn->req.msg_state = HTTP_MSG_TUNNEL; } else { /* we're not expecting any new data to come for this * transaction, so we can close it. - * However, there is an exception if the response length - * is undefined. In this case, we need to wait the close - * from the server. The response will be switched in - * TUNNEL mode until the end. + * + * However, there is an exception if the response + * length is undefined. In this case, we need to wait + * the close from the server. The response will be + * switched in TUNNEL mode until the end. */ if (!(txn->rsp.flags & HTTP_MSGF_XFER_LEN) && txn->rsp.msg_state != HTTP_MSG_CLOSED) - return; + goto check_channel_flags; if (!(chn->flags & (CF_SHUTW|CF_SHUTW_NOW))) { channel_shutr_now(chn); channel_shutw_now(chn); } } - goto check_channel_flags; } @@ -3159,11 +2732,9 @@ static void htx_end_request(struct stream *s) if (txn->req.msg_state == HTTP_MSG_CLOSED) { http_msg_closed: - /* if we don't know whether the server will close, we need to hard close */ if (txn->rsp.flags & HTTP_MSGF_XFER_LEN) s->si[1].flags |= SI_FL_NOLINGER; /* we want to close ASAP */ - /* see above in MSG_DONE why we only do this in these states */ if ((!(s->be->options & PR_O_ABRT_CLOSE) || (s->si[0].flags & SI_FL_CLEAN_ABRT))) channel_dont_read(chn); @@ -3201,8 +2772,8 @@ static void htx_end_response(struct stream *s) s->req.analysers, s->res.analysers); if (unlikely(txn->rsp.msg_state == HTTP_MSG_ERROR)) { - channel_abort(chn); channel_truncate(chn); + channel_abort(&s->req); goto end; } @@ -3234,6 +2805,8 @@ static void htx_end_response(struct stream *s) if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_TUN) { channel_auto_read(chn); chn->flags |= CF_NEVER_WAIT; + if (b_data(&chn->buf)) + return; txn->rsp.msg_state = HTTP_MSG_TUNNEL; } else { @@ -3272,8 +2845,7 @@ static void htx_end_response(struct stream *s) http_msg_closed: /* drop any pending data */ channel_truncate(chn); - channel_auto_close(chn); - channel_auto_read(chn); + channel_abort(&s->req); goto end; }