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; }