#include <common/cfgparse.h>
#include <common/config.h>
+#include <types/proxy.h>
+#include <types/session.h>
+
#include <proto/connection.h>
#include <proto/h1.h>
#include <proto/log.h>
#define H1C_F_CS_SHUTW_NOW 0x00002000 /* connection must be shut down for writes ASAP */
#define H1C_F_CS_SHUTW 0x00004000 /* connection is already shut down */
+#define H1C_F_WAIT_NEXT_REQ 0x00010000 /* waiting for the next request to start, use keep-alive timeout */
/*
* H1 Stream flags (32 bits)
*/
#define H1S_F_NONE 0x00000000
#define H1S_F_ERROR 0x00000001 /* An error occurred on the H1 stream */
-#define H1S_F_MSG_XFERED 0x00000002 /* current message was transferred to the data layer */
+#define H1S_F_REQ_ERROR 0x00000002 /* An error occurred during the request parsing/xfer */
+#define H1S_F_RES_ERROR 0x00000004 /* An error occurred during the response parsing/xfer */
+#define H1S_F_MSG_XFERED 0x00000008 /* current message was transferred to the data layer */
+#define H1S_F_WANT_KAL 0x00000010
+#define H1S_F_WANT_TUN 0x00000020
+#define H1S_F_WANT_CLO 0x00000040
+#define H1S_F_WANT_MSK 0x00000070
+#define H1S_F_NOT_FIRST 0x00000080 /* The H1 stream is not the first one */
/* H1 connection descriptor */
/*****************************************************************/
/* functions below are dedicated to the mux setup and management */
/*****************************************************************/
-static struct h1s *h1s_create(struct h1c *h1c)
+static struct h1s *h1s_create(struct h1c *h1c, struct conn_stream *cs)
{
struct h1s *h1s;
if (!conn_is_back(h1c->conn)) {
if (h1c->px->options2 & PR_O2_REQBUG_OK)
h1s->req.err_pos = -1;
+
+ if (h1c->flags & H1C_F_WAIT_NEXT_REQ)
+ h1s->flags |= H1S_F_NOT_FIRST;
+ h1c->flags &= ~H1C_F_WAIT_NEXT_REQ;
+ h1c->http_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpreq);
}
else {
if (h1c->px->options2 & PR_O2_RSPBUG_OK)
h1s->res.err_pos = -1;
}
+
+ /* If a conn_stream already exists, attach it to this H1S */
+ if (cs) {
+ cs->ctx = h1s;
+ h1s->cs = cs;
+ }
end:
return h1s;
}
static void h1s_destroy(struct h1s *h1s)
{
- struct h1c *h1c = h1s->h1c;
+ if (h1s) {
+ struct h1c *h1c = h1s->h1c;
- h1c->h1s = NULL;
- h1c->flags &= ~H1C_F_RX_FULL;
+ h1c->h1s = NULL;
+ h1c->flags &= ~(H1C_F_RX_FULL|H1C_F_RX_ALLOC);
- if (h1s->recv_wait != NULL)
- h1s->recv_wait->wait_reason &= ~SUB_CAN_RECV;
- if (h1s->send_wait != NULL)
- h1s->send_wait->wait_reason &= ~SUB_CAN_SEND;
+ if (h1s->recv_wait != NULL)
+ h1s->recv_wait->wait_reason &= ~SUB_CAN_RECV;
+ if (h1s->send_wait != NULL)
+ h1s->send_wait->wait_reason &= ~SUB_CAN_SEND;
- h1_release_buf(h1c, &h1s->rxbuf);
- pool_free(pool_head_h1s, h1s);
+ if (!conn_is_back(h1c->conn)) {
+ h1c->flags |= H1C_F_WAIT_NEXT_REQ;
+ h1c->http_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpka);
+ }
+
+ h1_release_buf(h1c, &h1s->rxbuf);
+ cs_free(h1s->cs);
+ pool_free(pool_head_h1s, h1s);
+ }
+}
+
+static struct conn_stream *h1s_new_cs(struct h1s *h1s)
+{
+ struct conn_stream *cs;
+
+ cs = cs_new(h1s->h1c->conn);
+ if (!cs)
+ goto err;
+ h1s->cs = cs;
+ cs->ctx = h1s;
+
+ if (h1s->flags & H1S_F_NOT_FIRST)
+ cs->flags |= CS_FL_NOT_FIRST;
+
+ if (stream_create_from_cs(cs) < 0)
+ goto err;
+ return cs;
+
+ err:
+ cs_free(cs);
+ h1s->cs = NULL;
+ return NULL;
}
/*
*/
static int h1_init(struct connection *conn, struct proxy *proxy)
{
- struct conn_stream *cs = conn->mux_ctx;
struct h1c *h1c;
struct task *t = NULL;
h1c->wait_event.task->context = h1c;
h1c->wait_event.wait_reason = 0;
-
- /* For backend mux connection, the CS already exists. In such case,
- * create h1s and attached the cs to it.
- */
- if (cs) {
- struct h1s *h1s = cs->ctx;
-
- if (!h1s) {
- h1s = h1s_create(h1c);
- if (!h1s)
- goto fail;
- cs->ctx = h1s;
- h1s->cs = cs;
- }
- }
+ /* Always Create a new H1S */
+ if (!h1s_create(h1c, conn->mux_ctx))
+ goto fail;
conn->mux_ctx = h1c;
task_wakeup(t, TASK_WOKEN_INIT);
if (h1c->wait_event.task)
tasklet_free(h1c->wait_event.task);
- if (h1c->h1s)
- h1s_destroy(h1c->h1s);
-
+ h1s_destroy(h1c->h1s);
if (h1c->wait_event.wait_reason != 0)
conn->xprt->unsubscribe(conn, h1c->wait_event.wait_reason,
&h1c->wait_event);
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.
+ */
+static int h1_remove_conn_hdrs(struct h1m *h1m, struct http_hdr *hdrs, struct buffer *buf)
+{
+ int src, dst, delta;
+
+ 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;
+}
+
+/* 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.
+ */
+static int h1_add_conn_hdrs(struct h1m *h1m, struct http_hdr *hdrs, struct buffer *buf,
+ int type)
+{
+ const char *conn_hdr;
+ size_t nlen, vlen;
+ int i, delta;
+
+ 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;
+ }
+
+ /* Find EOH*/
+ for (i = 0; hdrs[i].n.len; i++);
+
+ /* Insert the "Connection: " header */
+ delta = b_rep_blk(buf, hdrs[i].n.ptr, hdrs[i].n.ptr, conn_hdr, nlen+vlen+4);
+
+ /* 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(""));
+
+ return delta;
+}
+
+/* Deduce the connection mode of the client connection, depending on the
+ * configuration and the H1 message flags. This function is called twice, the
+ * first time when the request is parsed and the second time when the response
+ * is parsed.
+ */
+static void h1_set_cli_conn_mode(struct h1s *h1s, struct h1m *h1m)
+{
+ struct proxy *fe = h1s->h1c->px;
+ int flag = H1S_F_WANT_KAL; /* For client connection: server-close == keepalive */
+
+ /* Tunnel mode can only by set on the frontend */
+ if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN)
+ flag = H1S_F_WANT_TUN;
+ else if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_CLO)
+ flag = H1S_F_WANT_CLO;
+
+ /* flags order: CLO > SCL > TUN > KAL */
+ if ((h1s->flags & H1S_F_WANT_MSK) < flag)
+ h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | flag;
+
+ if (h1m->flags & H1_MF_RESP) {
+ /* Either we've established an explicit tunnel, or we're
+ * switching the protocol. In both cases, we're very unlikely to
+ * understand the next protocols. We have to switch to tunnel
+ * mode, so that we transfer the request and responses then let
+ * this protocol pass unmodified. When we later implement
+ * specific parsers for such protocols, we'll want to check the
+ * Upgrade header which contains information about that protocol
+ * for responses with status 101 (eg: see RFC2817 about TLS).
+ */
+ if ((h1s->meth == HTTP_METH_CONNECT && h1s->status == 200) ||
+ h1s->status == 101)
+ h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_TUN;
+ else if (!(h1m->flags & H1_MF_XFER_LEN)) /* no length known => close */
+ h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+ }
+ else {
+ if (h1s->flags & H1S_F_WANT_KAL &&
+ (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL)) || /* no KA in HTTP/1.0 */
+ h1m->flags & H1_MF_CONN_CLO)) /* explicit close */
+ h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+ }
+
+ /* If KAL, check if the frontend is stopping. If yes, switch in CLO mode */
+ if (h1s->flags & H1S_F_WANT_KAL && fe->state == PR_STSTOPPED)
+ h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+}
+
+/* Deduce the connection mode of the client connection, depending on the
+ * configuration and the H1 message flags. This function is called twice, the
+ * first time when the request is parsed and the second time when the response
+ * is parsed.
+ */
+static void h1_set_srv_conn_mode(struct h1s *h1s, struct h1m *h1m)
+{
+ struct proxy *be = h1s->h1c->px;
+ struct proxy *fe = strm_fe(si_strm(h1s->cs->data));
+ int flag = H1S_F_WANT_KAL;
+
+ /* Tunnel mode can only by set on the frontend */
+ if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN)
+ flag = H1S_F_WANT_TUN;
+
+ /* For the server connection: server-close == httpclose */
+ if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_SCL ||
+ (be->options & PR_O_HTTP_MODE) == PR_O_HTTP_SCL ||
+ (fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_CLO ||
+ (be->options & PR_O_HTTP_MODE) == PR_O_HTTP_CLO)
+ flag = H1S_F_WANT_CLO;
+
+ /* flags order: CLO > SCL > TUN > KAL */
+ if ((h1s->flags & H1S_F_WANT_MSK) < flag)
+ h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | flag;
+
+ if (h1m->flags & H1_MF_RESP) {
+ /* Either we've established an explicit tunnel, or we're
+ * switching the protocol. In both cases, we're very unlikely to
+ * understand the next protocols. We have to switch to tunnel
+ * mode, so that we transfer the request and responses then let
+ * this protocol pass unmodified. When we later implement
+ * specific parsers for such protocols, we'll want to check the
+ * Upgrade header which contains information about that protocol
+ * for responses with status 101 (eg: see RFC2817 about TLS).
+ */
+ if ((h1s->meth == HTTP_METH_CONNECT && h1s->status == 200) ||
+ h1s->status == 101)
+ h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_TUN;
+ else if (!(h1m->flags & H1_MF_XFER_LEN)) /* no length known => close */
+ h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+ else if (h1s->flags & H1S_F_WANT_KAL &&
+ (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL)) || /* no KA in HTTP/1.0 */
+ h1m->flags & H1_MF_CONN_CLO)) /* explicit close */
+ h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+ }
+
+ /* If KAL, check if the backend is stopping. If yes, switch in CLO mode */
+ if (h1s->flags & H1S_F_WANT_KAL && be->state == PR_STSTOPPED)
+ h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO;
+
+ /* TODO: For now on the server-side, we disable keep-alive */
+ if (h1s->flags & H1S_F_WANT_KAL)
+ 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)
+{
+ 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;
+
+ 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);
+ }
+ 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);
+ }
+
+ end:
+ return ret;
+}
+
+static int h1_update_res_conn_hdr(struct h1s *h1s, struct h1m *h1m,
+ struct http_hdr *hdrs, struct buffer *buf)
+{
+ 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;
+
+ 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);
+ }
+ 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);
+ }
+
+ end:
+ return ret;
+}
+
/*
* Parse HTTP/1 headers. It returns the number of bytes parsed if > 0, or 0 if
- * it couldn't proceed. Parsing errors are reported by setting H1S_F_ERROR flag
- * and filling h1s->err_pos and h1s->err_state fields. This functions is
+ * it couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR
+ * 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,
- struct buffer *buf, size_t ofs, size_t max)
+ struct buffer *buf, size_t *ofs, size_t max)
{
struct http_hdr hdrs[MAX_HTTP_HDR];
union h1_sl sl;
if (b_head(buf) + b_data(buf) > b_wrap(buf))
b_slow_realign(buf, trash.area, 0);
- ret = h1_headers_to_hdr_list(b_peek(buf, ofs), b_peek(buf, ofs) + max,
+ ret = h1_headers_to_hdr_list(b_peek(buf, *ofs), b_peek(buf, *ofs) + max,
hdrs, sizeof(hdrs)/sizeof(hdrs[0]), h1m, &sl);
if (ret <= 0) {
/* Incomplete or invalid message. If the buffer is full, it's an
* error because headers are too large to be handled by the
* parser. */
- if (ret < 0 || (!ret && b_full(buf))) {
- h1s->flags |= H1S_F_ERROR;
- h1m->err_state = h1m->state;
- h1m->err_pos = h1m->next;
- ret = 0;
- }
+ if (ret < 0 || (!ret && b_full(buf)))
+ goto error;
goto end;
}
*/
/* Be sure to keep some space to do headers rewritting */
- if (ret > (b_size(buf) - global.tune.maxrewrite)) {
- h1s->flags |= H1S_F_ERROR;
- h1m->err_state = h1m->state;
- h1m->err_pos = h1m->next;
- ret = 0;
- goto end;
- }
+ 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 */
h1m->state = H1_MSG_TUNNEL;
}
+ *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);
+ }
+ else {
+ h1_set_srv_conn_mode(h1s, h1m);
+ if (!(h1m->flags & H1_MF_RESP))
+ *ofs += h1_update_req_conn_hdr(h1s, h1m, hdrs, buf);
+ }
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;
+ ret = 0;
+ goto end;
}
/*
- * Parse HTTP/1 body. It returns the number of bytes parsed if > 0, or 0 if
- * it couldn't proceed. Parsing errors are reported by setting H1S_F_ERROR flag
+ * Parse HTTP/1 body. It returns the number of bytes parsed if > 0, or 0 if it
+ * couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR 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_data(struct h1s *h1s, struct h1m *h1m,
- struct buffer *buf, size_t ofs, size_t max)
+ struct buffer *buf, size_t *ofs, size_t max)
{
size_t total = 0;
int ret = 0;
if ((uint64_t)ret > h1m->curr_len)
ret = h1m->curr_len;
h1m->curr_len -= ret;
+ *ofs += ret;
total += ret;
if (!h1m->curr_len)
h1m->state = H1_MSG_DONE;
new_chunk:
/* te:chunked : parse chunks */
if (h1m->state == H1_MSG_CHUNK_CRLF) {
- ret = h1_skip_chunk_crlf(buf, ofs, ofs + max);
+ ret = h1_skip_chunk_crlf(buf, *ofs, *ofs + max);
if (ret <= 0)
goto end;
max -= ret;
- ofs += ret;
+ *ofs += ret;
total += ret;
h1m->state = H1_MSG_CHUNK_SIZE;
}
if (h1m->state == H1_MSG_CHUNK_SIZE) {
unsigned int chksz;
- ret = h1_parse_chunk_size(buf, ofs, ofs + max, &chksz);
+ ret = h1_parse_chunk_size(buf, *ofs, *ofs + max, &chksz);
if (ret <= 0)
goto end;
h1m->curr_len = chksz;
h1m->body_len += chksz;
max -= ret;
- ofs += ret;
+ *ofs += ret;
total += ret;
h1m->state = (!chksz ? H1_MSG_TRAILERS : H1_MSG_DATA);
}
ret = h1m->curr_len;
h1m->curr_len -= ret;
max -= ret;
- ofs += ret;
+ *ofs += ret;
total += ret;
if (h1m->curr_len)
goto end;
}
if (h1m->state == H1_MSG_TRAILERS) {
- ret = h1_measure_trailers(buf, ofs, ofs + max);
+ ret = h1_measure_trailers(buf, *ofs, *ofs + max);
if (ret <= 0)
goto end;
max -= ret;
- ofs += ret;
+ *ofs += ret;
total += ret;
h1m->state = H1_MSG_DONE;
}
}
else {
/* no content length, read till SHUTW */
+ *ofs += max;
total = max;
}
end:
if (ret < 0) {
- h1s->flags |= H1S_F_ERROR;
+ h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR);
h1m->err_state = h1m->state;
- h1m->err_pos = ofs + max + ret;
+ h1m->err_pos = *ofs + max + ret;
return 0;
}
*/
static void h1_sync_messages(struct h1c *h1c)
{
- if (!h1c->h1s)
+ struct h1s *h1s = h1c->h1s;
+
+ if (!h1s)
return;
- if (h1c->h1s->res.state >= H1_MSG_DONE &&
- (h1c->h1s->status < 200 && (h1c->h1s->status == 100 || h1c->h1s->status >= 102)) &&
- ((conn_is_back(h1c->conn) && !b_data(&h1c->obuf)) || !b_data(&h1c->h1s->rxbuf))) {
+ 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))) {
/* 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(&h1c->h1s->res);
+ h1m_init_res(&h1s->res);
}
- else if (!b_data(&h1c->h1s->rxbuf) && !b_data(&h1c->obuf) &&
- h1c->h1s->req.state >= H1_MSG_DONE && h1c->h1s->res.state >= H1_MSG_DONE) {
- h1m_init_req(&h1c->h1s->req);
- h1m_init_res(&h1c->h1s->res);
-
- // TODO: For now, the Keep-alive timeout is handled by the stream.
- //if (h1c->task && !conn_is_back(h1c->conn))
- // h1c->http_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpka);
+ 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) {
+ h1s->req.state = H1_MSG_TUNNEL;
+ h1s->res.state = H1_MSG_TUNNEL;
+ }
}
}
*/
static size_t h1_process_input(struct h1c *h1c, struct buffer *buf, size_t count)
{
- struct h1s *h1s = h1c->h1s;
- struct conn_stream *cs = NULL;
+ struct h1s *h1s = NULL;
struct h1m *h1m;
size_t total = 0;
size_t ret = 0;
+ size_t max;
+ int errflag;
if (h1c->flags & H1C_F_CS_ERROR)
goto end;
- if (!h1s) {
- h1s = h1s_create(h1c);
- if (h1s == NULL)
- goto err;
- }
+ /* Create a new H1S without CS if not already done */
+ if (!h1c->h1s && !h1s_create(h1c, NULL))
+ goto err;
+ h1s = h1c->h1s;
+
+#if 0
+ // FIXME: Use a proxy option to enable early creation of the CS
+ /* Create the CS if not already attached to the H1S */
+ if (!h1s->cs && !h1s_new_cs(h1s))
+ goto err;
+#endif
if (!h1_get_buf(h1c, &h1s->rxbuf)) {
h1c->flags |= H1C_F_RX_ALLOC;
if (count > b_room(&h1s->rxbuf))
count = b_room(&h1s->rxbuf);
+ max = count;
- h1m = (!conn_is_back(h1c->conn) ? &h1s->req : &h1s->res);
- while (h1m->state < H1_MSG_DONE && count) {
+ if (!conn_is_back(h1c->conn)) {
+ h1m = &h1s->req;
+ errflag = H1S_F_REQ_ERROR;
+ }
+ else {
+ h1m = &h1s->res;
+ errflag = H1S_F_RES_ERROR;
+ }
+ while (!(h1s->flags & errflag) && max) {
if (h1m->state <= H1_MSG_LAST_LF) {
- if (h1m->state == H1_MSG_RQBEFORE) {
- if (h1c->task && !conn_is_back(h1c->conn))
- if (!h1s->cs)
- h1c->http_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpreq);
- }
- ret = h1_process_headers(h1s, h1m, buf, total, count);
+ ret = h1_process_headers(h1s, h1m, buf, &total, max);
if (!ret)
break;
- /* Create the CS if not already attached to the H1S */
- if (!h1s->cs) {
- cs = cs_new(h1c->conn);
- if (!cs)
- goto err;
- h1s->cs = cs;
- cs->ctx = h1s;
- if (stream_create_from_cs(cs) < 0)
- goto err;
- }
+ /* Reset request timeout */
+ h1s->h1c->http_exp = TICK_ETERNITY;
- if (h1c->task && !conn_is_back(h1c->conn))
- h1c->http_exp = TICK_ETERNITY;
+ /* Create the CS if not already attached to the H1S */
+ if (!h1s->cs && !h1s_new_cs(h1s))
+ goto err;
}
else if (h1m->state <= H1_MSG_TRAILERS) {
/* Do not parse the body if the header part is not yet
*/
if (!(h1s->flags & H1S_F_MSG_XFERED))
break;
- ret = h1_process_data(h1s, h1m, buf, total, count);
+ 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 |= H1S_F_ERROR;
+ h1s->flags |= errflag;
break;
}
- total += ret;
- count -= ret;
-
- if ((h1s->flags & H1S_F_ERROR))
- break;
+ max -= ret;
}
- if (h1s->flags & H1S_F_ERROR) {
+ 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
h1_cpy_error_message(h1c, &h1c->obuf, 400);
goto err;
}
- total += count;
+ total += max;
+ max = 0;
}
- ret = b_xfer(&h1s->rxbuf, buf, total);
+ b_xfer(&h1s->rxbuf, buf, total);
if (b_data(&h1s->rxbuf)) {
h1s->cs->flags |= CS_FL_RCV_MORE;
if (b_full(&h1s->rxbuf))
h1c->flags |= H1C_F_RX_FULL;
}
-
+ ret = count - max;
end:
return ret;
err:
- if (cs)
- cs_free(cs);
- if (h1s)
- h1s_destroy(h1s);
+ h1s_destroy(h1s);
h1c->flags |= H1C_F_CS_ERROR;
sess_log(h1c->conn->owner);
ret = 0;
{
struct h1s *h1s = h1c->h1s;
struct h1m *h1m;
+ size_t max;
size_t total = 0;
size_t ret = 0;
+ int errflag;
if (!h1_get_buf(h1c, &h1c->obuf)) {
h1c->flags |= H1C_F_OUT_ALLOC;
if (count > b_room(&h1c->obuf))
count = b_room(&h1c->obuf);
- h1m = (!conn_is_back(h1c->conn) ? &h1s->res : &h1s->req);
- while (h1m->state < H1_MSG_DONE && count) {
+ max = count;
+ if (!conn_is_back(h1c->conn)) {
+ h1m = &h1s->res;
+ errflag = H1S_F_RES_ERROR;
+ }
+ else {
+ 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, count);
+ 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 |= H1S_F_ERROR;
+ h1s->flags |= errflag;
break;
}
}
else if (h1m->state <= H1_MSG_TRAILERS) {
- ret = h1_process_data(h1s, h1m, buf, total, count);
+ 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 |= H1S_F_ERROR;
+ h1s->flags |= errflag;
break;
}
- total += ret;
- count -= ret;
-
- if ((h1s->flags & H1S_F_ERROR))
- break;
+ max -= ret;
}
// TODO: Handle H1S errors
- ret = b_xfer(&h1c->obuf, buf, total);
+ b_xfer(&h1c->obuf, buf, total);
if (b_full(&h1c->obuf))
h1c->flags |= H1C_F_OUT_FULL;
- end:
+ ret = count - max;
+ end:
return ret;
}
if (ret > 0) {
h1c->flags &= ~H1C_F_OUT_FULL;
b_del(&h1c->obuf, ret);
- h1_sync_messages(h1c);
sent = 1;
}
/* We're done, no more to send */
if (!b_data(&h1c->obuf)) {
h1_release_buf(h1c, &h1c->obuf);
+ h1_sync_messages(h1c);
if (h1c->flags & H1C_F_CS_SHUTW_NOW)
h1_shutw_conn(conn);
}
}
}
- if (h1c->task && !conn_is_back(conn)) {
- if (!h1c->h1s || !h1c->h1s->cs)
- h1c->idle_exp = tick_add_ifset(now_ms, h1c->px->timeout.client);
- else
- h1c->idle_exp = TICK_ETERNITY;
- h1c->task->expire = tick_first(h1c->http_exp, h1c->idle_exp);
+ /* If there is a stream attached to the mux, let it
+ * handle the timeout.
+ */
+ if (h1c->h1s && h1c->h1s->cs)
+ h1c->idle_exp = TICK_ETERNITY;
+ else {
+ int tout = (!conn_is_back(conn)
+ ? h1c->px->timeout.client
+ : h1c->px->timeout.server);
+ h1c->idle_exp = tick_add_ifset(now_ms, tout);
}
+ h1c->task->expire = tick_first(h1c->http_exp, h1c->idle_exp);
+ if (tick_isset(h1c->task->expire))
+ task_queue(h1c->task);
return 0;
}
goto end;
if (!expired) {
- /* For now, do not handle timeout for server-side mux */
- if (!conn_is_back(h1c->conn))
- t->expire = tick_first(t->expire, tick_first(h1c->idle_exp, h1c->http_exp));
+ t->expire = tick_first(t->expire, tick_first(h1c->idle_exp, h1c->http_exp));
return t;
}
- if (!(h1c->px->options & PR_O_IGNORE_PRB) && h1_get_buf(h1c, &h1c->obuf)) {
- // TODO: do not send error if ka timeout
- h1_cpy_error_message(h1c, &h1c->obuf, 408);
- h1c->flags |= H1C_F_CS_ERROR;
- h1c->idle_exp = TICK_ETERNITY;
- h1c->http_exp = TICK_ETERNITY;
- t->expire = TICK_ETERNITY;
+ h1c->flags |= H1C_F_CS_ERROR;
+ h1c->idle_exp = TICK_ETERNITY;
+ h1c->http_exp = TICK_ETERNITY;
+ t->expire = TICK_ETERNITY;
+
+ /* Don't try send error message on the server-side */
+ if (conn_is_back(h1c->conn))
+ goto release;
+
+ /* Don't send error message if no input data is pending _AND_ if null
+ * requests is ignored or it's not the first request.
+ */
+ if (!b_data(&h1c->ibuf) && (h1c->px->options & PR_O_IGNORE_PRB ||
+ h1c->flags & H1C_F_WAIT_NEXT_REQ))
+ goto release;
+
+ /* Try to allocate output buffer to store the error message. If
+ * allocation fails, just go away.
+ */
+ if (!h1_get_buf(h1c, &h1c->obuf))
+ goto release;
+
+ h1_cpy_error_message(h1c, &h1c->obuf, 408);
+ tasklet_wakeup(h1c->wait_event.task);
+ sess_log(h1c->conn->owner);
+ return t;
+
+ release:
+ if (h1c->h1s) {
tasklet_wakeup(h1c->wait_event.task);
- sess_log(h1c->conn->owner);
return t;
}
-
h1c->task = NULL;
- if (!h1c->h1s || !h1c->h1s->cs)
- h1_release(h1c->conn);
+ h1_release(h1c->conn);
end:
task_delete(t);
task_free(t);
if (!cs)
goto end;
- h1s = h1s_create(h1c);
+ h1s = h1s_create(h1c, cs);
if (h1s == NULL)
goto end;
if (!h1s)
return;
+ if ((h1s->flags & H1S_F_WANT_KAL) && !(cs->flags & (CS_FL_REOS|CS_FL_EOS)))
+ return;
+
/* NOTE: Be sure to handle abort (cf. h2_shutr) */
if (cs->flags & CS_FL_SHR)
return;
return;
h1c = h1s->h1c;
+ if ((h1s->flags & H1S_F_WANT_KAL) &&
+ !(cs->flags & (CS_FL_REOS|CS_FL_EOS)) &&
+ h1s->req.state == H1_MSG_DONE && h1s->res.state == H1_MSG_DONE)
+ return;
+
h1c->flags |= H1C_F_CS_SHUTW_NOW;
if ((cs->flags & CS_FL_SHW) || b_data(&h1c->obuf))
return;
#include <proto/stream_interface.h>
#include <proto/stats.h>
+
+static void htx_end_request(struct stream *s);
+static void htx_end_response(struct stream *s);
+
/* This stream analyser waits for a complete HTTP request. It returns 1 if the
* processing can continue on next analysers, or zero if it either needs more
* data or wants to immediately abort the request (eg: timeout, error, ...). It
}
}
-
/*
* 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.
(ci_head(req)[msg->sl.rq.v + 7] >= '1'))))
msg->flags |= HTTP_MSGF_VER_11;
- /* "connection" has not been parsed yet */
- txn->flags &= ~(TX_HDR_CONN_PRS | TX_HDR_CONN_CLO | TX_HDR_CONN_KAL | TX_HDR_CONN_UPG);
-
/* 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.
* one is non-null, or one of them is non-null and we are there for the first
* time.
*/
- if (!(txn->flags & TX_HDR_CONN_PRS) ||
- ((sess->fe->options & PR_O_HTTP_MODE) != (s->be->options & PR_O_HTTP_MODE)))
- http_adjust_conn_mode(s, txn, msg);
+ if ((sess->fe->options & PR_O_HTTP_MODE) != (s->be->options & PR_O_HTTP_MODE))
+ htx_adjust_conn_mode(s, txn, msg);
/* we may have to wait for the request's body */
if ((s->be->options & PR_O_WREQ_BODY) &&
if (!ret)
continue;
}
- if (!http_apply_redirect_rule(rule, s, txn))
+ if (!htx_apply_redirect_rule(rule, s, txn))
goto return_bad_req;
goto done;
}
}
}
- /* 11: add "Connection: close" or "Connection: keep-alive" if needed and not yet set.
- * If an "Upgrade" token is found, the header is left untouched in order not to have
- * to deal with some servers bugs : some of them fail an Upgrade if anything but
- * "Upgrade" is present in the Connection header.
- */
- if (!(txn->flags & TX_HDR_CONN_UPG) && (txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) {
- unsigned int want_flags = 0;
-
- if (msg->flags & HTTP_MSGF_VER_11) {
- if ((txn->flags & TX_CON_WANT_MSK) >= TX_CON_WANT_SCL &&
- !((sess->fe->options2|s->be->options2) & PR_O2_FAKE_KA))
- want_flags |= TX_CON_CLO_SET;
- } else {
- if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL ||
- ((sess->fe->options2|s->be->options2) & PR_O2_FAKE_KA))
- want_flags |= TX_CON_KAL_SET;
- }
-
- if (want_flags != (txn->flags & (TX_CON_CLO_SET|TX_CON_KAL_SET)))
- http_change_connection_header(txn, msg, want_flags);
- }
-
-
/* If we have no server assigned yet and we're balancing on url_param
* with a POST request, we may be interested in checking the body for
* that parameter. This will be done in another analyser.
*/
msg->err_state = msg->msg_state;
msg->msg_state = HTTP_MSG_ERROR;
- http_resync_states(s);
+ htx_end_request(s);
+ htx_end_response(s);
return 1;
}
if ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN)
channel_dont_close(req);
- http_resync_states(s);
+ htx_end_request(s);
if (!(req->analysers & an_bit)) {
+ htx_end_response(s);
if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) {
if (req->flags & CF_SHUTW) {
/* request errors are most likely due to the
((ci_head(rep)[5] == '1') && (ci_head(rep)[7] >= '1'))))
msg->flags |= HTTP_MSGF_VER_11;
- /* "connection" has not been parsed yet */
- txn->flags &= ~(TX_HDR_CONN_PRS|TX_HDR_CONN_CLO|TX_HDR_CONN_KAL|TX_HDR_CONN_UPG|TX_CON_CLO_SET|TX_CON_KAL_SET);
-
/* transfer length unknown*/
msg->flags &= ~HTTP_MSGF_XFER_LEN;
(txn->status >= 100 && txn->status < 200) ||
txn->status == 204 || txn->status == 304) {
msg->flags |= HTTP_MSGF_XFER_LEN;
- goto skip_content_length;
+ goto end;
}
use_close_only = 0;
msg->body_len = msg->chunk_len = cl;
}
- skip_content_length:
- /* Now we have to check if we need to modify the Connection header.
- * This is more difficult on the response than it is on the request,
- * because we can have two different HTTP versions and we don't know
- * how the client will interprete a response. For instance, let's say
- * that the client sends a keep-alive request in HTTP/1.0 and gets an
- * HTTP/1.1 response without any header. Maybe it will bound itself to
- * HTTP/1.0 because it only knows about it, and will consider the lack
- * of header as a close, or maybe it knows HTTP/1.1 and can consider
- * the lack of header as a keep-alive. Thus we will use two flags
- * indicating how a request MAY be understood by the client. In case
- * of multiple possibilities, we'll fix the header to be explicit. If
- * ambiguous cases such as both close and keepalive are seen, then we
- * will fall back to explicit close. Note that we won't take risks with
- * HTTP/1.0 clients which may not necessarily understand keep-alive.
- * See doc/internals/connection-header.txt for the complete matrix.
- */
- if ((txn->status >= 200) && !(txn->flags & TX_HDR_CONN_PRS) &&
- (txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) {
- int to_del = 0;
-
- /* on unknown transfer length, we must close */
- if (!(msg->flags & HTTP_MSGF_XFER_LEN))
- txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_CLO;
-
- /* now adjust header transformations depending on current state */
- if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_CLO) {
- to_del |= 2; /* remove "keep-alive" on any response */
- if (!(msg->flags & HTTP_MSGF_VER_11))
- to_del |= 1; /* remove "close" for HTTP/1.0 responses */
- }
- else { /* SCL / KAL */
- to_del |= 1; /* remove "close" on any response */
- if (txn->req.flags & msg->flags & HTTP_MSGF_VER_11)
- to_del |= 2; /* remove "keep-alive" on pure 1.1 responses */
- }
-
- /* Parse and remove some headers from the connection header */
- http_parse_connection_header(txn, msg, to_del);
-
- /* Some keep-alive responses are converted to Server-close if
- * the server wants to close.
- */
- if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL) {
- if ((txn->flags & TX_HDR_CONN_CLO) ||
- (!(txn->flags & TX_HDR_CONN_KAL) && !(msg->flags & HTTP_MSGF_VER_11)))
- txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_SCL;
- }
- }
-
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);
if (unlikely(objt_applet(s->target) == &http_stats_applet)) {
rep->analysers &= ~an_bit;
rep->analyse_exp = TICK_ETERNITY;
- goto skip_filters;
+ goto end;
}
/*
/* OK that's all we can do for 1xx responses */
if (unlikely(txn->status < 200 && txn->status != 101))
- goto skip_header_mangling;
+ goto end;
/*
* Now check for a server cookie.
goto return_srv_prx_502;
}
- skip_filters:
- /*
- * Adjust "Connection: close" or "Connection: keep-alive" if needed.
- * If an "Upgrade" token is found, the header is left untouched in order
- * not to have to deal with some client bugs : some of them fail an upgrade
- * if anything but "Upgrade" is present in the Connection header. We don't
- * want to touch any 101 response either since it's switching to another
- * protocol.
- */
- if ((txn->status != 101) && !(txn->flags & TX_HDR_CONN_UPG) &&
- (txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) {
- unsigned int want_flags = 0;
-
- if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL ||
- (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) {
- /* we want a keep-alive response here. Keep-alive header
- * required if either side is not 1.1.
- */
- if (!(txn->req.flags & msg->flags & HTTP_MSGF_VER_11))
- want_flags |= TX_CON_KAL_SET;
- }
- else { /* CLO */
- /* we want a close response here. Close header required if
- * the server is 1.1, regardless of the client.
- */
- if (msg->flags & HTTP_MSGF_VER_11)
- want_flags |= TX_CON_CLO_SET;
- }
-
- if (want_flags != (txn->flags & (TX_CON_CLO_SET|TX_CON_KAL_SET)))
- http_change_connection_header(txn, msg, want_flags);
- }
-
- skip_header_mangling:
+ end:
/* Always enter in the body analyzer */
rep->analysers &= ~AN_RES_FLT_XFER_DATA;
rep->analysers |= AN_RES_HTTP_XFER_BODY;
return 0;
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))) ||
- !s->req.analysers) {
+ ((res->flags & CF_SHUTW) && (res->to_forward || co_data(res)))) {
/* Output closed while we were sending data. We must abort and
* wake the other side up.
*/
msg->err_state = msg->msg_state;
msg->msg_state = HTTP_MSG_ERROR;
- http_resync_states(s);
+ htx_end_response(s);
+ htx_end_request(s);
return 1;
}
}
/* other states, DONE...TUNNEL */
- /* for keep-alive we don't want to forward closes on DONE */
- if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL ||
- (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL)
- channel_dont_close(res);
-
- http_resync_states(s);
+ htx_end_response(s);
if (!(res->analysers & an_bit)) {
+ htx_end_request(s);
if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) {
if (res->flags & CF_SHUTW) {
/* response errors are most likely due to the
}
}
- /* we need to obey the req analyser, so if it leaves, we must too */
- if (!s->req.analysers)
- goto return_bad_res;
-
/* 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 keep-alive is set on the client side
- * or if there are filters registered on the stream, we don't want to
- * forward a close
+ * set CF_DONTCLOSE. Similarly, 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) ||
- (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL ||
- (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL)
+ if ((msg->flags & HTTP_MSGF_TE_CHNK) || HAS_DATA_FILTERS(s, res))
channel_dont_close(res);
/* We know that more data are expected, but we couldn't send more that
return 0;
}
+void htx_adjust_conn_mode(struct stream *s, struct http_txn *txn, struct http_msg *msg)
+{
+ struct proxy *fe = strm_fe(s);
+ int tmp = TX_CON_WANT_CLO;
+
+ if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN)
+ tmp = TX_CON_WANT_TUN;
+
+ if ((txn->flags & TX_CON_WANT_MSK) < tmp)
+ txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_CLO;
+}
+
+/* Perform an HTTP redirect based on the information in <rule>. The function
+ * returns non-zero on success, or zero in case of a, irrecoverable error such
+ * as too large a request to build a valid response.
+ */
+int htx_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struct http_txn *txn)
+{
+ struct http_msg *req = &txn->req;
+ struct http_msg *res = &txn->rsp;
+ const char *msg_fmt;
+ struct buffer *chunk;
+ int ret = 0;
+
+ chunk = alloc_trash_chunk();
+ if (!chunk)
+ goto leave;
+
+ /* build redirect message */
+ switch(rule->code) {
+ case 308:
+ msg_fmt = HTTP_308;
+ break;
+ case 307:
+ msg_fmt = HTTP_307;
+ break;
+ case 303:
+ msg_fmt = HTTP_303;
+ break;
+ case 301:
+ msg_fmt = HTTP_301;
+ break;
+ case 302:
+ default:
+ msg_fmt = HTTP_302;
+ break;
+ }
+
+ if (unlikely(!chunk_strcpy(chunk, msg_fmt)))
+ goto leave;
+
+ switch(rule->type) {
+ case REDIRECT_TYPE_SCHEME: {
+ const char *path;
+ const char *host;
+ struct hdr_ctx ctx;
+ int pathlen;
+ int hostlen;
+
+ host = "";
+ hostlen = 0;
+ ctx.idx = 0;
+ if (http_find_header2("Host", 4, ci_head(req->chn), &txn->hdr_idx, &ctx)) {
+ host = ctx.line + ctx.val;
+ hostlen = ctx.vlen;
+ }
+
+ path = http_txn_get_path(txn);
+ /* build message using path */
+ if (path) {
+ pathlen = req->sl.rq.u_l + (ci_head(req->chn) + req->sl.rq.u) - path;
+ if (rule->flags & REDIRECT_FLAG_DROP_QS) {
+ int qs = 0;
+ while (qs < pathlen) {
+ if (path[qs] == '?') {
+ pathlen = qs;
+ break;
+ }
+ qs++;
+ }
+ }
+ } else {
+ path = "/";
+ pathlen = 1;
+ }
+
+ if (rule->rdr_str) { /* this is an old "redirect" rule */
+ /* check if we can add scheme + "://" + host + path */
+ if (chunk->data + rule->rdr_len + 3 + hostlen + pathlen > chunk->size - 4)
+ goto leave;
+
+ /* add scheme */
+ memcpy(chunk->area + chunk->data, rule->rdr_str,
+ rule->rdr_len);
+ chunk->data += rule->rdr_len;
+ }
+ else {
+ /* add scheme with executing log format */
+ chunk->data += build_logline(s,
+ chunk->area + chunk->data,
+ chunk->size - chunk->data,
+ &rule->rdr_fmt);
+
+ /* check if we can add scheme + "://" + host + path */
+ if (chunk->data + 3 + hostlen + pathlen > chunk->size - 4)
+ goto leave;
+ }
+ /* add "://" */
+ memcpy(chunk->area + chunk->data, "://", 3);
+ chunk->data += 3;
+
+ /* add host */
+ memcpy(chunk->area + chunk->data, host, hostlen);
+ chunk->data += hostlen;
+
+ /* add path */
+ memcpy(chunk->area + chunk->data, path, pathlen);
+ chunk->data += pathlen;
+
+ /* append a slash at the end of the location if needed and missing */
+ if (chunk->data && chunk->area[chunk->data - 1] != '/' &&
+ (rule->flags & REDIRECT_FLAG_APPEND_SLASH)) {
+ if (chunk->data > chunk->size - 5)
+ goto leave;
+ chunk->area[chunk->data] = '/';
+ chunk->data++;
+ }
+
+ break;
+ }
+ case REDIRECT_TYPE_PREFIX: {
+ const char *path;
+ int pathlen;
+
+ path = http_txn_get_path(txn);
+ /* build message using path */
+ if (path) {
+ pathlen = req->sl.rq.u_l + (ci_head(req->chn) + req->sl.rq.u) - path;
+ if (rule->flags & REDIRECT_FLAG_DROP_QS) {
+ int qs = 0;
+ while (qs < pathlen) {
+ if (path[qs] == '?') {
+ pathlen = qs;
+ break;
+ }
+ qs++;
+ }
+ }
+ } else {
+ path = "/";
+ pathlen = 1;
+ }
+
+ if (rule->rdr_str) { /* this is an old "redirect" rule */
+ if (chunk->data + rule->rdr_len + pathlen > chunk->size - 4)
+ goto leave;
+
+ /* add prefix. Note that if prefix == "/", we don't want to
+ * add anything, otherwise it makes it hard for the user to
+ * configure a self-redirection.
+ */
+ if (rule->rdr_len != 1 || *rule->rdr_str != '/') {
+ memcpy(chunk->area + chunk->data,
+ rule->rdr_str, rule->rdr_len);
+ chunk->data += rule->rdr_len;
+ }
+ }
+ else {
+ /* add prefix with executing log format */
+ chunk->data += build_logline(s,
+ chunk->area + chunk->data,
+ chunk->size - chunk->data,
+ &rule->rdr_fmt);
+
+ /* Check length */
+ if (chunk->data + pathlen > chunk->size - 4)
+ goto leave;
+ }
+
+ /* add path */
+ memcpy(chunk->area + chunk->data, path, pathlen);
+ chunk->data += pathlen;
+
+ /* append a slash at the end of the location if needed and missing */
+ if (chunk->data && chunk->area[chunk->data - 1] != '/' &&
+ (rule->flags & REDIRECT_FLAG_APPEND_SLASH)) {
+ if (chunk->data > chunk->size - 5)
+ goto leave;
+ chunk->area[chunk->data] = '/';
+ chunk->data++;
+ }
+
+ break;
+ }
+ case REDIRECT_TYPE_LOCATION:
+ default:
+ if (rule->rdr_str) { /* this is an old "redirect" rule */
+ if (chunk->data + rule->rdr_len > chunk->size - 4)
+ goto leave;
+
+ /* add location */
+ memcpy(chunk->area + chunk->data, rule->rdr_str,
+ rule->rdr_len);
+ chunk->data += rule->rdr_len;
+ }
+ else {
+ /* add location with executing log format */
+ chunk->data += build_logline(s,
+ chunk->area + chunk->data,
+ chunk->size - chunk->data,
+ &rule->rdr_fmt);
+
+ /* Check left length */
+ if (chunk->data > chunk->size - 4)
+ goto leave;
+ }
+ break;
+ }
+
+ if (rule->cookie_len) {
+ memcpy(chunk->area + chunk->data, "\r\nSet-Cookie: ", 14);
+ chunk->data += 14;
+ memcpy(chunk->area + chunk->data, rule->cookie_str,
+ rule->cookie_len);
+ chunk->data += rule->cookie_len;
+ }
+
+ /* add end of headers and the keep-alive/close status. */
+ txn->status = rule->code;
+ /* let's log the request time */
+ s->logs.tv_request = now;
+
+ if (((!(req->flags & HTTP_MSGF_TE_CHNK) && !req->body_len) || (req->msg_state == HTTP_MSG_DONE))) {
+ /* keep-alive possible */
+ if (!(req->flags & HTTP_MSGF_VER_11)) {
+ if (unlikely(txn->flags & TX_USE_PX_CONN)) {
+ memcpy(chunk->area + chunk->data,
+ "\r\nProxy-Connection: keep-alive", 30);
+ chunk->data += 30;
+ } else {
+ memcpy(chunk->area + chunk->data,
+ "\r\nConnection: keep-alive", 24);
+ chunk->data += 24;
+ }
+ }
+ memcpy(chunk->area + chunk->data, "\r\n\r\n", 4);
+ chunk->data += 4;
+ FLT_STRM_CB(s, flt_http_reply(s, txn->status, chunk));
+ co_inject(res->chn, chunk->area, chunk->data);
+ /* "eat" the request */
+ b_del(&req->chn->buf, req->sov);
+ req->next -= req->sov;
+ req->sov = 0;
+ s->req.analysers = AN_REQ_HTTP_XFER_BODY | (s->req.analysers & AN_REQ_FLT_END);
+ s->res.analysers = AN_RES_HTTP_XFER_BODY | (s->res.analysers & AN_RES_FLT_END);
+ req->msg_state = HTTP_MSG_CLOSED;
+ res->msg_state = HTTP_MSG_DONE;
+ /* Trim any possible response */
+ b_set_data(&res->chn->buf, co_data(res->chn));
+ res->next = res->sov = 0;
+ /* let the server side turn to SI_ST_CLO */
+ channel_shutw_now(req->chn);
+ } else {
+ /* keep-alive not possible */
+ if (unlikely(txn->flags & TX_USE_PX_CONN)) {
+ memcpy(chunk->area + chunk->data,
+ "\r\nProxy-Connection: close\r\n\r\n", 29);
+ chunk->data += 29;
+ } else {
+ memcpy(chunk->area + chunk->data,
+ "\r\nConnection: close\r\n\r\n", 23);
+ chunk->data += 23;
+ }
+ http_reply_and_close(s, txn->status, chunk);
+ req->chn->analysers &= AN_REQ_FLT_END;
+ }
+
+ if (!(s->flags & SF_ERR_MASK))
+ s->flags |= SF_ERR_LOCAL;
+ if (!(s->flags & SF_FINST_MASK))
+ s->flags |= SF_FINST_R;
+
+ ret = 1;
+ leave:
+ free_trash_chunk(chunk);
+ return ret;
+}
+
+/* This function terminates the request because it was completly analyzed or
+ * because an error was triggered during the body forwarding.
+ */
+static void htx_end_request(struct stream *s)
+{
+ struct channel *chn = &s->req;
+ struct http_txn *txn = s->txn;
+
+ DPRINTF(stderr,"[%u] %s: stream=%p states=%s,%s req->analysers=0x%08x res->analysers=0x%08x\n",
+ now_ms, __FUNCTION__, s,
+ h1_msg_state_str(txn->req.msg_state), h1_msg_state_str(txn->rsp.msg_state),
+ s->req.analysers, s->res.analysers);
+
+ if (unlikely(txn->req.msg_state == HTTP_MSG_ERROR)) {
+ channel_abort(chn);
+ channel_truncate(chn);
+ goto end;
+ }
+
+ if (unlikely(txn->req.msg_state < HTTP_MSG_DONE))
+ return;
+
+ if (txn->req.msg_state == HTTP_MSG_DONE) {
+ if (txn->rsp.msg_state < HTTP_MSG_DONE) {
+ /* The server has not finished to respond, so we
+ * don't want to move in order not to upset it.
+ */
+ return;
+ }
+
+ /* No need to read anymore, the request was completely parsed.
+ * We can shut the read side unless we want to abort_on_close,
+ * or we have a POST request. The issue with POST requests is
+ * that some browsers still send a CRLF after the request, and
+ * this CRLF must be read so that it does not remain in the kernel
+ * buffers, otherwise a close could cause an RST on some systems
+ * (eg: Linux).
+ */
+ if ((!(s->be->options & PR_O_ABRT_CLOSE) || (s->si[0].flags & SI_FL_CLEAN_ABRT)) &&
+ txn->meth != HTTP_METH_POST)
+ channel_dont_read(chn);
+
+ /* if the server closes the connection, we want to immediately react
+ * and close the socket to save packets and syscalls.
+ */
+ s->si[1].flags |= SI_FL_NOHALF;
+
+ /* In any case we've finished parsing the request so we must
+ * disable Nagle when sending data because 1) we're not going
+ * to shut this side, and 2) the server is waiting for us to
+ * send pending data.
+ */
+ chn->flags |= CF_NEVER_WAIT;
+
+ /* When we get here, it means that both the request and the
+ * response have finished receiving. Depending on the connection
+ * mode, we'll have to wait for the last bytes to leave in either
+ * direction, and sometimes for a close to be effective.
+ */
+ if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_TUN) {
+ /* Tunnel mode will not have any analyser so it needs to
+ * poll for reads.
+ */
+ channel_auto_read(chn);
+ 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.
+ */
+ if (!(txn->rsp.flags & HTTP_MSGF_XFER_LEN) &&
+ txn->rsp.msg_state != HTTP_MSG_CLOSED)
+ return;
+
+ 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_CLOSING) {
+ http_msg_closing:
+ /* nothing else to forward, just waiting for the output buffer
+ * to be empty and for the shutw_now to take effect.
+ */
+ if (channel_is_empty(chn)) {
+ txn->req.msg_state = HTTP_MSG_CLOSED;
+ goto http_msg_closed;
+ }
+ else if (chn->flags & CF_SHUTW) {
+ txn->req.err_state = txn->req.msg_state;
+ txn->req.msg_state = HTTP_MSG_ERROR;
+ goto end;
+ }
+ return;
+ }
+
+ 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);
+ goto end;
+ }
+
+ check_channel_flags:
+ /* Here, we are in HTTP_MSG_DONE or HTTP_MSG_TUNNEL */
+ if (chn->flags & (CF_SHUTW|CF_SHUTW_NOW)) {
+ /* if we've just closed an output, let's switch */
+ txn->req.msg_state = HTTP_MSG_CLOSING;
+ goto http_msg_closing;
+ }
+
+ end:
+ chn->analysers &= AN_REQ_FLT_END;
+ if (txn->req.msg_state == HTTP_MSG_TUNNEL && HAS_REQ_DATA_FILTERS(s))
+ chn->analysers |= AN_REQ_FLT_XFER_DATA;
+ channel_auto_close(chn);
+ channel_auto_read(chn);
+}
+
+
+/* This function terminates the response because it was completly analyzed or
+ * because an error was triggered during the body forwarding.
+ */
+static void htx_end_response(struct stream *s)
+{
+ struct channel *chn = &s->res;
+ struct http_txn *txn = s->txn;
+
+ DPRINTF(stderr,"[%u] %s: stream=%p states=%s,%s req->analysers=0x%08x res->analysers=0x%08x\n",
+ now_ms, __FUNCTION__, s,
+ h1_msg_state_str(txn->req.msg_state), h1_msg_state_str(txn->rsp.msg_state),
+ s->req.analysers, s->res.analysers);
+
+ if (unlikely(txn->rsp.msg_state == HTTP_MSG_ERROR)) {
+ channel_abort(chn);
+ channel_truncate(chn);
+ goto end;
+ }
+
+ if (unlikely(txn->rsp.msg_state < HTTP_MSG_DONE))
+ return;
+
+ if (txn->rsp.msg_state == HTTP_MSG_DONE) {
+ /* In theory, we don't need to read anymore, but we must
+ * still monitor the server connection for a possible close
+ * while the request is being uploaded, so we don't disable
+ * reading.
+ */
+ /* channel_dont_read(chn); */
+
+ if (txn->req.msg_state < HTTP_MSG_DONE) {
+ /* The client seems to still be sending data, probably
+ * because we got an error response during an upload.
+ * We have the choice of either breaking the connection
+ * or letting it pass through. Let's do the later.
+ */
+ return;
+ }
+
+ /* When we get here, it means that both the request and the
+ * response have finished receiving. Depending on the connection
+ * mode, we'll have to wait for the last bytes to leave in either
+ * direction, and sometimes for a close to be effective.
+ */
+ if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_TUN) {
+ channel_auto_read(chn);
+ chn->flags |= CF_NEVER_WAIT;
+ txn->rsp.msg_state = HTTP_MSG_TUNNEL;
+ }
+ else {
+ /* we're not expecting any new data to come for this
+ * transaction, so we can close it.
+ */
+ if (!(chn->flags & (CF_SHUTW|CF_SHUTW_NOW))) {
+ channel_shutr_now(chn);
+ channel_shutw_now(chn);
+ }
+ }
+ goto check_channel_flags;
+ }
+
+ if (txn->rsp.msg_state == HTTP_MSG_CLOSING) {
+ http_msg_closing:
+ /* nothing else to forward, just waiting for the output buffer
+ * to be empty and for the shutw_now to take effect.
+ */
+ if (channel_is_empty(chn)) {
+ txn->rsp.msg_state = HTTP_MSG_CLOSED;
+ goto http_msg_closed;
+ }
+ else if (chn->flags & CF_SHUTW) {
+ txn->rsp.err_state = txn->rsp.msg_state;
+ txn->rsp.msg_state = HTTP_MSG_ERROR;
+ HA_ATOMIC_ADD(&s->be->be_counters.cli_aborts, 1);
+ if (objt_server(s->target))
+ HA_ATOMIC_ADD(&objt_server(s->target)->counters.cli_aborts, 1);
+ goto end;
+ }
+ return;
+ }
+
+ if (txn->rsp.msg_state == HTTP_MSG_CLOSED) {
+ http_msg_closed:
+ /* drop any pending data */
+ channel_truncate(chn);
+ channel_auto_close(chn);
+ channel_auto_read(chn);
+ goto end;
+ }
+
+ check_channel_flags:
+ /* Here, we are in HTTP_MSG_DONE or HTTP_MSG_TUNNEL */
+ if (chn->flags & (CF_SHUTW|CF_SHUTW_NOW)) {
+ /* if we've just closed an output, let's switch */
+ txn->rsp.msg_state = HTTP_MSG_CLOSING;
+ goto http_msg_closing;
+ }
+
+ end:
+ chn->analysers &= AN_RES_FLT_END;
+ if (txn->rsp.msg_state == HTTP_MSG_TUNNEL && HAS_RSP_DATA_FILTERS(s))
+ chn->analysers |= AN_RES_FLT_XFER_DATA;
+ channel_auto_close(chn);
+ channel_auto_read(chn);
+}
+
__attribute__((constructor))
static void __htx_protocol_init(void)
{