#include <proto/connection.h>
#include <proto/h1.h>
+#include <proto/http_htx.h>
+#include <proto/htx.h>
#include <proto/log.h>
#include <proto/stream.h>
#include <proto/stream_interface.h>
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;
b_putblk(dst, b_head(err), b_data(err));
}
-/* Remove all "Connection:" headers from the buffer <buf>, using the array of
- * parsed headers <hdrs>. 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 <hdrs> reamin valid.
+/* Parse the request version and set H1_MF_VER_11 on <h1m> 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;
+
+ if ((sl->rq.v_len == 8) &&
+ (*(p + 5) > '1' ||
+ (*(p + 5) == '1' && *(p + 7) >= '1')))
+ h1m->flags |= H1_MF_VER_11;
+}
- delta = 0;
- for (src = 0, dst = 0; hdrs[src].n.len; src++) {
+/* Parse the response version and set H1_MF_VER_11 on <h1m> if the version is
+ * greater or equal to 1.1
+ */
+static void h1_parse_res_vsn(struct h1m *h1m, const union htx_sl *sl)
+{
+ const char *p = sl->rq.l;
- 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 ((sl->st.v_len == 8) &&
+ (*(p + 5) > '1' ||
+ (*(p + 5) == '1' && *(p + 7) >= '1')))
+ h1m->flags |= H1_MF_VER_11;
+}
- 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);
+/*
+ * 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 if (!sl.rq.v.len) {
+ /* try to convert HTTP/0.9 requests to HTTP/1.0 */
- /* Don't forget to copy EOH */
- hdrs[src].n.ptr += delta;
- hdrs[dst] = hdrs[src];
+ /* RFC 1945 allows only GET for HTTP/0.9 requests */
+ if (sl.rq.meth != HTTP_METH_GET)
+ return 0;
- h1m->flags &= ~(H1_MF_CONN_KAL|H1_MF_CONN_CLO);
- return delta;
+ /* 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;
}
-/* Add a "Connection:" header into the buffer <buf>. If <type> 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 <hdrs> reamin valid.
+/*
+ * Check the validity of the response version. If the version is valid, it
+ * returns 1. Otherwise, it returns 0.
*/
-static int h1_add_conn_hdrs(struct h1m *h1m, struct http_hdr *hdrs, struct buffer *buf,
- int type)
+static int h1_process_res_vsn(struct h1s *h1s, struct h1m *h1m, union h1_sl sl)
{
- const char *conn_hdr;
- size_t nlen, vlen;
- int i, delta;
+ struct h1c *h1c = h1s->h1c;
- if (type == 0) { /* keep-alive */
- conn_hdr = "Connection: keep-alive\r\n";
- nlen = 10; vlen = 10;
- }
- else { /* close */
- conn_hdr = "Connection: close\r\n";
- nlen = 10; vlen = 5;
+ /* 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;
+
+ 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 <htx> */
+static void h1_remove_conn_hdrs(struct h1m *h1m, struct htx *htx)
+{
+ struct ist hdr = {.ptr = "Connection", .len = 10};
+ struct http_hdr_ctx ctx;
- /* Find EOH*/
- for (i = 0; hdrs[i].n.len; i++);
+ while (http_find_header(htx, hdr, &ctx, 1))
+ http_remove_header(htx, &ctx);
- /* Insert the "Connection: " header */
- delta = b_rep_blk(buf, hdrs[i].n.ptr, hdrs[i].n.ptr, conn_hdr, nlen+vlen+4);
+ h1m->flags &= ~(H1_MF_CONN_KAL|H1_MF_CONN_CLO);
+}
- /* 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(""));
+/* Add a "Connection:" header with the value <value> into the HTX message
+ * <htx>.
+ */
+static void h1_add_conn_hdr(struct h1m *h1m, struct htx *htx, struct ist value)
+{
+ struct ist hdr = {.ptr = "Connection", .len = 10};
- return delta;
+ http_add_header(htx, hdr, value);
}
/* Deduce the connection mode of the client connection, depending on the
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. <htx> and <conn_val> can be NULL. When <htx> is not NULL, the HTX
+ * message is updated accordingly. When <conn_val> 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);
+ }
}
/*
* flag and filling h1s->err_pos and h1s->err_state fields. This functions is
* responsibile to update the parser state <h1m>.
*/
-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];
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;
}
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;
}
* and filling h1s->err_pos and h1s->err_state fields. This functions is
* responsibile to update the parser state <h1m>.
*/
-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;
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:
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) {
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;
}
}
/* 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:
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;
}
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;
}
{
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))
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;
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;
*/
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;
}
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;
}
/*
{
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;
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;
+
+
+ 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;
- }
- }
- else if (h1m->state <= H1_MSG_TRAILERS) {
- ret = h1_process_data(h1s, h1m, buf, &total, max);
- if (!ret)
+
+ 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;
- }
- 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;
+ 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;
- }
- cs->flags |= CS_FL_RCV_MORE;
+ 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;
}
- else {
- if (!(h1s->flags & H1S_F_MSG_XFERED))
+
+ // 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 {
h1c->flags &= ~H1C_F_RX_FULL;
h1_release_buf(h1c, &h1s->rxbuf);
h1_sync_messages(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;
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))
{
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));
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;
}
{
struct h1c *h1c = conn->mux_ctx;
+ //return 0;
return (h1_process(h1c));
}
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);
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;
*/
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
+ * We will analyze a complete HTTP request to check the its syntax.
*
- * 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.
+ * 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.
*/
-
- int cur_idx;
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__,
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;
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.
* 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;
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;
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;
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);
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;
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
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;
- /* 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);
+ 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));
+ }
+ }
/*
* 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;
}
* 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
*/
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;
/* 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;
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);
}
}
- /* 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.
* 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.
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;
/*
/* 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);
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;
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);
{
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
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
{
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__,
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)))) {
* 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"
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)
* 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;
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)) {
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
* 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
*/
if (msg->flags & HTTP_MSGF_TE_CHNK)
req->flags |= CF_EXPECT_MORE;
+#endif
return 0;
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 */
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 */
*/
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",
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.
* 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);
health_adjust(objt_server(s->target), HANA_STATUS_HTTP_READ_ERROR);
}
- 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;
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;
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);
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;
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);
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;
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;
* 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
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.
*/
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;
}
/*
*/
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);
-
- /* 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.
- */
+ htx_capture_headers(htx, s->res_cap, sess->fe->rsp_cap);
- /* 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
* 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);
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;
}
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,
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__,
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)))) {
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);
* 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;
* 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);
/* 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
*/
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;
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))
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 */
* 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;
}
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);
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;
}
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 {
http_msg_closed:
/* drop any pending data */
channel_truncate(chn);
- channel_auto_close(chn);
- channel_auto_read(chn);
+ channel_abort(&s->req);
goto end;
}