]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: filters/http: Move body parsing of HTTP messages in dedicated functions
authorChristopher Faulet <cfaulet@qualys.com>
Wed, 2 Dec 2015 09:01:17 +0000 (10:01 +0100)
committerWilly Tarreau <w@1wt.eu>
Tue, 9 Feb 2016 13:53:15 +0000 (14:53 +0100)
Now body parsing is done in http_msg_forward_body and
http_msg_forward_chunked_body functions, regardless of whether we parse a
request or a response.
Parsing result is still handled in http_request_forward_body and
http_response_forward_body functions.

This patch will ease futur optimizations, mainly on filters.

src/proto_http.c

index fdb17b6cc3365f5c10786ad944da71c38ac432bf..b25d18a2f0323d13b2e7bb9ddd6a89fa5157b8e4 100644 (file)
@@ -275,6 +275,9 @@ fd_set http_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set
 
 static int http_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struct http_txn *txn);
 
+static int http_msg_forward_body(struct stream *s, struct http_msg *msg);
+static int http_msg_forward_chunked_body(struct stream *s, struct http_msg *msg);
+
 /* This function returns a reason associated with the HTTP status.
  * This function never fails, a message is always returned.
  */
@@ -5462,16 +5465,9 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit)
         * an "Expect: 100-continue" header.
         */
        if (msg->msg_state == HTTP_MSG_BODY) {
-               /* The previous analysers guarantee that the state is somewhere
-                * between MSG_BODY and the first MSG_DATA. So msg->sol and
-                * msg->next are always correct.
-                */
-               if (msg->msg_state < HTTP_MSG_CHUNK_SIZE) {
-                       if (msg->flags & HTTP_MSGF_TE_CHNK)
-                               msg->msg_state = HTTP_MSG_CHUNK_SIZE;
-                       else
-                               msg->msg_state = HTTP_MSG_DATA;
-               }
+               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
@@ -5488,7 +5484,7 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit)
                if (!(s->res.flags & CF_READ_ATTACHED)) {
                        channel_auto_connect(req);
                        req->flags |= CF_WAKE_CONNECT;
-                       goto missing_data;
+                       goto missing_data_or_waiting;
                }
                msg->flags &= ~HTTP_MSGF_WAIT_CONN;
        }
@@ -5499,177 +5495,62 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit)
        if (req->to_forward) {
                /* We can't process the buffer's contents yet */
                req->flags |= CF_WAKE_WRITE;
-               goto missing_data;
+               goto missing_data_or_waiting;
        }
 
-       while (1) {
-               if (msg->msg_state == HTTP_MSG_DATA) {
-                       /* must still forward */
-                       /* we may have some pending data starting at req->buf->p */
-                       ret = FLT_STRM_CB(s, flt_http_data(s, msg),
-                                         /* default_ret */ MIN(msg->chunk_len, req->buf->i - msg->next),
-                                         /* on_error    */ goto aborted_xfer);
-                       msg->next     += ret;
-                       msg->chunk_len -= ret;
-
-                       if (msg->chunk_len) {
-                               /* input empty or output full */
-                               if (req->buf->i > msg->next)
-                                       req->flags |= CF_WAKE_WRITE;
-                               goto missing_data;
-                       }
-
-                       /* nothing left to forward */
-                       if (msg->flags & HTTP_MSGF_TE_CHNK)
-                               msg->msg_state = HTTP_MSG_CHUNK_CRLF;
-                       else
-                               msg->msg_state = HTTP_MSG_ENDING;
-               }
-               else if (msg->msg_state == HTTP_MSG_CHUNK_SIZE) {
-                       /* read the chunk size and assign it to ->chunk_len, then
-                        * set ->next to point to the body and switch to DATA or
-                        * TRAILERS state.
-                        */
-                       ret = http_parse_chunk_size(msg);
-                       if (ret == 0)
-                               goto missing_data;
-                       else if (ret < 0) {
-                               stream_inc_http_err_ctr(s);
-                               if (msg->err_pos >= 0)
-                                       http_capture_bad_message(&sess->fe->invalid_req, s, msg, HTTP_MSG_CHUNK_SIZE, s->be);
-                               goto return_bad_req;
-                       }
-                       msg->next += msg->sol;
-                       msg->sol   = 0;
-                       msg->msg_state = msg->chunk_len ? HTTP_MSG_DATA : HTTP_MSG_TRAILERS;
-                       /* otherwise we're in HTTP_MSG_DATA or HTTP_MSG_TRAILERS state */
-               }
-               else if (msg->msg_state == HTTP_MSG_CHUNK_CRLF) {
-                       /* we want the CRLF after the data */
-                       ret = http_skip_chunk_crlf(msg);
-                       if (ret == 0)
-                               goto missing_data;
-                       else if (ret < 0) {
-                               stream_inc_http_err_ctr(s);
-                               if (msg->err_pos >= 0)
-                                       http_capture_bad_message(&sess->fe->invalid_req, s, msg, HTTP_MSG_CHUNK_CRLF, s->be);
-                               goto return_bad_req;
-                       }
-                       msg->next += msg->sol;
-                       msg->sol   = 0;
-                       msg->msg_state = HTTP_MSG_CHUNK_SIZE;
-                       /* we're in MSG_CHUNK_SIZE now */
-               }
-               else if (msg->msg_state == HTTP_MSG_TRAILERS) {
-                       ret = http_forward_trailers(msg);
-                       if (ret < 0) {
-                               stream_inc_http_err_ctr(s);
-                               if (msg->err_pos >= 0)
-                                       http_capture_bad_message(&sess->fe->invalid_req, s, msg, HTTP_MSG_TRAILERS, s->be);
-                               goto return_bad_req;
-                       }
-                       FLT_STRM_CB(s, flt_http_chunk_trailers(s, msg),
-                                   /* default_ret */ 1,
-                                   /* on_error    */ goto return_bad_req);
-                       msg->next += msg->sol;
-                       msg->sol   = 0;
-                       if (!ret)
-                               goto missing_data;
-                       msg->msg_state = HTTP_MSG_ENDING;
-               }
-               else if (msg->msg_state == HTTP_MSG_ENDING) {
-                       /* we don't want to forward closes on DONE except in
-                        * tunnel mode.
-                        */
-                       if ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN)
-                               channel_dont_close(req);
-                       /* we may have some pending data starting at req->buf->p
-                        * such as last chunk of data or trailers.
-                        */
-                       ret = FLT_STRM_CB(s, flt_http_forward_data(s, msg, msg->next),
-                                         /* default_ret */ msg->next,
-                                         /* on_error    */ goto return_bad_req);
-                       b_adv(req->buf, ret);
-                       msg->next -= ret;
-                       if (unlikely(!(s->req.flags & CF_WROTE_DATA) || msg->sov > 0))
-                               msg->sov -= ret;
-                       if (msg->next)
-                               goto skip_resync_states;
-
-                       FLT_STRM_CB(s, flt_http_end(s, msg),
-                                   /* default_ret */ 1,
-                                   /* on_error    */ goto return_bad_req,
-                                   /* on_wait     */ goto skip_resync_states);
-                       msg->msg_state = HTTP_MSG_DONE;
-               }
-               else {
-                       /* 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)
-                               channel_dont_close(req);
+       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;
+       }
 
-                       ret = msg->msg_state;
-                       if (http_resync_states(s)) {
-                               /* some state changes occurred, maybe the analyser
-                                * was disabled too.
-                                */
-                               if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) {
-                                       if (req->flags & CF_SHUTW) {
-                                               /* request errors are most likely due to
-                                                * the server aborting the transfer.
-                                                */
-                                               goto aborted_xfer;
-                                       }
-                                       if (msg->err_pos >= 0)
-                                               http_capture_bad_message(&sess->fe->invalid_req, s, msg, ret, s->be);
-                                       goto return_bad_req;
-                               }
-                               return 1;
-                       }
+       /* 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)
+               channel_dont_close(req);
 
-               skip_resync_states:
-                       /* If "option abortonclose" is set on the backend, we
-                        * want to monitor the client's connection and forward
-                        * any shutdown notification to the server, which will
-                        * decide whether to close or to go on processing the
-                        * request. We only do that in tunnel mode, and not in
-                        * other modes since it can be abused to exhaust source
-                        * ports.
-                        */
-                       if (s->be->options & PR_O_ABRT_CLOSE) {
-                               channel_auto_read(req);
-                               if ((req->flags & (CF_SHUTR|CF_READ_NULL)) &&
-                                   ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN))
-                                       s->si[1].flags |= SI_FL_NOLINGER;
-                               channel_auto_close(req);
-                       }
-                       else if (s->txn->meth == HTTP_METH_POST) {
-                               /* POST requests may require to read extra CRLF
-                                * sent by broken browsers and which could cause
-                                * an RST to be sent upon close on some systems
-                                * (eg: Linux).
-                                */
-                               channel_auto_read(req);
+       ret = msg->msg_state;
+       if (http_resync_states(s)) {
+               /* some state changes occurred, maybe the analyser
+                * was disabled too. */
+               if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) {
+                       if (req->flags & CF_SHUTW) {
+                               /* request errors are most likely due to the
+                                * server aborting the transfer. */
+                               goto aborted_xfer;
                        }
-
-                       return 0;
+                       if (msg->err_pos >= 0)
+                               http_capture_bad_message(&sess->fe->invalid_req, s, msg, ret, s->be);
+                       goto return_bad_req;
                }
+               return 1;
        }
 
- missing_data:
-       /* we may have some pending data starting at req->buf->p */
-       ret = FLT_STRM_CB(s, flt_http_forward_data(s, msg, msg->next),
-                         /* default_ret */ msg->next,
-                         /* on_error    */ goto return_bad_req);
-       b_adv(req->buf, ret);
-       msg->next -= ret;
-       if (unlikely(!(s->req.flags & CF_WROTE_DATA) || msg->sov > 0))
-               msg->sov -= ret;
-       if (!HAS_FILTERS(s))
-               msg->chunk_len -= channel_forward(req, msg->chunk_len);
+       /* If "option abortonclose" is set on the backend, we want to monitor
+        * the client's connection and forward any shutdown notification to the
+        * server, which will decide whether to close or to go on processing the
+        * request. We only do that in tunnel mode, and not in other modes since
+        * it can be abused to exhaust source ports. */
+       if (s->be->options & PR_O_ABRT_CLOSE) {
+               channel_auto_read(req);
+               if ((req->flags & (CF_SHUTR|CF_READ_NULL)) &&
+                   ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN))
+                       s->si[1].flags |= SI_FL_NOLINGER;
+               channel_auto_close(req);
+       }
+       else if (s->txn->meth == HTTP_METH_POST) {
+               /* POST requests may require to read extra CRLF sent by broken
+                * browsers and which could cause an RST to be sent upon close
+                * on some systems (eg: Linux). */
+               channel_auto_read(req);
+       }
+       return 0;
 
+ missing_data_or_waiting:
        /* stop waiting for data if the input is closed before the end */
        if (req->flags & CF_SHUTR) {
                if (!(s->flags & SF_ERR_MASK))
@@ -6738,7 +6619,6 @@ int http_process_res_common(struct stream *s, struct channel *rep, int an_bit, s
  * is performed at once on final states for all bytes parsed, or when leaving
  * on missing data.
  */
-
 int http_response_forward_body(struct stream *s, struct channel *res, int an_bit)
 {
        struct session *sess = s->sess;
@@ -6764,187 +6644,51 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit
        channel_auto_close(res);
 
        if (msg->msg_state == HTTP_MSG_BODY) {
-               /* The previous analysers guarantee that the state is somewhere
-                * between MSG_BODY and the first MSG_DATA. So msg->sol and
-                * msg->next are always correct.
-                */
-               if (msg->msg_state < HTTP_MSG_CHUNK_SIZE) {
-                       if (msg->flags & HTTP_MSGF_TE_CHNK)
-                               msg->msg_state = HTTP_MSG_CHUNK_SIZE;
-                       else
-                               msg->msg_state = HTTP_MSG_DATA;
-               }
+               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 */
+                /* We can't process the buffer's contents yet */
                res->flags |= CF_WAKE_WRITE;
-               goto missing_data;
+               goto missing_data_or_waiting;
        }
 
-       while (1) {
-               switch (msg->msg_state - HTTP_MSG_DATA) {
-               case HTTP_MSG_DATA - HTTP_MSG_DATA:     /* must still forward */
-                       /* we may have some pending data starting at res->buf->p */
-
-                         /* Neither content-length, nor transfer-encoding was
-                         * found, so we must read the body until the server
-                         * connection is closed. In that case, we eat data as
-                         * they come. */
-                       if (!(msg->flags & HTTP_MSGF_XFER_LEN)) {
-                               unsigned long long len = (res->buf->i - msg->next);
-                               msg->chunk_len += len;
-                               msg->body_len  += len;
-                       }
-                       ret = FLT_STRM_CB(s, flt_http_data(s, msg),
-                                         /* default_ret */ MIN(msg->chunk_len, res->buf->i - msg->next),
-                                         /* on_error    */ goto aborted_xfer);
-                       msg->next      += ret;
-                       msg->chunk_len -= ret;
-                       if (msg->chunk_len) {
-                               /* input empty or output full */
-                               if (res->buf->i > msg->next)
-                                       res->flags |= CF_WAKE_WRITE;
-                               goto missing_data;
-                       }
-
-                       /* nothing left to forward */
-                       if (msg->flags & HTTP_MSGF_TE_CHNK) {
-                               msg->msg_state = HTTP_MSG_CHUNK_CRLF;
-                       } else if (!(msg->flags & HTTP_MSGF_XFER_LEN) &&
-                                  !(res->flags & CF_SHUTR)) {
-                               /* The server still sending data */
-                               goto missing_data;
-                       } else {
-                               msg->msg_state = HTTP_MSG_ENDING;
-                               break;
-                       }
-                       /* fall through for HTTP_MSG_CHUNK_CRLF */
-
-               case HTTP_MSG_CHUNK_CRLF - HTTP_MSG_DATA:
-                       /* we want the CRLF after the data */
-                       ret = http_skip_chunk_crlf(msg);
-                       if (ret == 0)
-                               goto missing_data;
-                       else if (ret < 0) {
-                               if (msg->err_pos >= 0)
-                                       http_capture_bad_message(&s->be->invalid_rep, s, msg, HTTP_MSG_CHUNK_CRLF, sess->fe);
-                               goto return_bad_res;
-                       }
-                       msg->next += msg->sol;
-                       msg->sol   = 0;
-                       msg->msg_state = HTTP_MSG_CHUNK_SIZE;
-                       /* we're in MSG_CHUNK_SIZE now, fall through */
-
-               case HTTP_MSG_CHUNK_SIZE - HTTP_MSG_DATA:
-                       /* read the chunk size and assign it to ->chunk_len, then
-                        * set ->next to point to the body and switch to DATA or
-                        * TRAILERS state.
-                        */
-                       ret = http_parse_chunk_size(msg);
-                       if (ret == 0)
-                               goto missing_data;
-                       else if (ret < 0) {
-                               if (msg->err_pos >= 0)
-                                       http_capture_bad_message(&s->be->invalid_rep, s, msg, HTTP_MSG_CHUNK_SIZE, sess->fe);
-                               goto return_bad_res;
-                       }
-                       msg->next += msg->sol;
-                       msg->sol   = 0;
-                       if (msg->chunk_len) {
-                               msg->msg_state = HTTP_MSG_DATA;
-                               break;
-                       }
-                       msg->msg_state = HTTP_MSG_TRAILERS;
-                       /* fall through */
-
-               case HTTP_MSG_TRAILERS - HTTP_MSG_DATA:
-                       ret = http_forward_trailers(msg);
-                       if (ret < 0) {
-                               if (msg->err_pos >= 0)
-                                       http_capture_bad_message(&s->be->invalid_rep, s, msg, HTTP_MSG_TRAILERS, sess->fe);
-                               goto return_bad_res;
-                       }
-                       FLT_STRM_CB(s, flt_http_chunk_trailers(s, msg),
-                                   /* default_ret */ 1,
-                                   /* on_error    */ goto return_bad_res);
-                       msg->next += msg->sol;
-                       msg->sol   = 0;
-                       if (!ret)
-                               goto missing_data;
-                       msg->msg_state = HTTP_MSG_ENDING;
-                       /* fall through */
-
-               case HTTP_MSG_ENDING - HTTP_MSG_DATA:
-                       /* for keep-alive we don't want to forward closes on ENDING */
-                       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);
-
-                       /* we may have some pending data starting at res->buf->p
-                        * such as a last chunk of data or trailers.
-                        */
-                       ret = FLT_STRM_CB(s, flt_http_forward_data(s, msg, msg->next),
-                                         /* default_ret */ msg->next,
-                                         /* on_error    */ goto return_bad_res);
-                       b_adv(res->buf, ret);
-                       msg->next -= ret;
-                       if (msg->sov > 0)
-                               msg->sov -= ret;
-                       if (msg->next)
-                               goto skip_resync_states;
-
-                       FLT_STRM_CB(s, flt_http_end(s, msg),
-                                   /* default_ret */ 1,
-                                   /* on_error    */ goto return_bad_res,
-                                   /* on_wait     */ goto skip_resync_states);
-                       msg->msg_state = HTTP_MSG_DONE;
-                       /* fall through */
-
-               default:
-                       /* other states, DONE...TUNNEL */
+       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;
+       }
 
-                       /* 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);
+       /* 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);
 
-                       ret = msg->msg_state;
-                       if (http_resync_states(s)) {
-                               /* some state changes occurred, maybe the analyser
-                                * was disabled too.
-                                */
-                               if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) {
-                                       if (res->flags & CF_SHUTW) {
-                                               /* response errors are most likely due to
-                                                * the client aborting the transfer.
-                                                */
-                                               goto aborted_xfer;
-                                       }
-                                       if (msg->err_pos >= 0)
-                                               http_capture_bad_message(&s->be->invalid_rep, s, msg, ret, sess->fe);
-                                       goto return_bad_res;
-                               }
-                               return 1;
+       ret = msg->msg_state;
+       if (http_resync_states(s)) {
+               /* some state changes occurred, maybe the analyser was disabled
+                * too. */
+               if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) {
+                       if (res->flags & CF_SHUTW) {
+                               /* response errors are most likely due to the
+                                * client aborting the transfer. */
+                               goto aborted_xfer;
                        }
-
-               skip_resync_states:
-                       return 0;
+                       if (msg->err_pos >= 0)
+                               http_capture_bad_message(&s->be->invalid_rep, s, msg, ret, strm_fe(s));
+                       goto return_bad_res;
                }
+               return 1;
        }
 
- missing_data:
-       /* we may have some pending data starting at res->buf->p */
-       ret = FLT_STRM_CB(s, flt_http_forward_data(s, msg, msg->next),
-                         /* default_ret */ msg->next,
-                         /* on_error    */ goto return_bad_res);
-       b_adv(res->buf, ret);
-       msg->next -= ret;
-       if (msg->sov > 0)
-               msg->sov -= ret;
-       if (!HAS_FILTERS(s))
-               msg->chunk_len -= channel_forward(res, msg->chunk_len);
-
+  missing_data_or_waiting:
        if (res->flags & CF_SHUTW)
                goto aborted_xfer;
 
@@ -6971,13 +6715,14 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit
        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, with keep-alive on the client side, we don't want to forward a
-        * close.
+       /* 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 the body length is undefined, 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
         */
        if ((msg->flags & HTTP_MSGF_TE_CHNK) || !msg->body_len ||
-           (msg->flags & HTTP_MSGF_COMPRESSING) ||
+           HAS_FILTERS(s) ||
            (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL ||
            (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL)
                channel_dont_close(res);
@@ -7035,6 +6780,207 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit
        return 0;
 }
 
+
+static inline int
+http_msg_forward_body(struct stream *s, struct http_msg *msg)
+{
+       struct channel *chn = msg->chn;
+       int ret;
+
+       /* Here we have the guarantee to be in HTTP_MSG_DATA or HTTP_MSG_ENDING state */
+
+       if (msg->msg_state == HTTP_MSG_ENDING)
+               goto ending;
+
+       /* Neither content-length, nor transfer-encoding was found, so we must
+        * read the body until the server connection is closed. In that case, we
+        * eat data as they come. Of course, this happens for response only. */
+       if (!(msg->flags & HTTP_MSGF_XFER_LEN)) {
+               unsigned long long len = (chn->buf->i - msg->next);
+               msg->chunk_len += len;
+               msg->body_len  += len;
+       }
+       ret = FLT_STRM_CB(s, flt_http_data(s, msg),
+                         /* default_ret */ MIN(msg->chunk_len, chn->buf->i - msg->next),
+                         /* on_error    */ goto error);
+       msg->next     += ret;
+       msg->chunk_len -= ret;
+       if (msg->chunk_len) {
+               /* input empty or output full */
+               if (chn->buf->i > msg->next)
+                       chn->flags |= CF_WAKE_WRITE;
+               goto missing_data_or_waiting;
+       }
+
+       if (!(msg->flags & HTTP_MSGF_XFER_LEN) && !(chn->flags & CF_SHUTR)) {
+               /* The server still sending data */
+               goto missing_data_or_waiting;
+       }
+       msg->msg_state = HTTP_MSG_ENDING;
+
+  ending:
+       /* we may have some pending data starting at res->buf->p such as a last
+        * chunk of data or trailers. */
+       ret = FLT_STRM_CB(s, flt_http_forward_data(s, msg, msg->next),
+                         /* default_ret */ msg->next,
+                         /* on_error    */ goto error);
+       b_adv(chn->buf, ret);
+       msg->next -= ret;
+       if (msg->next)
+               goto missing_data_or_waiting;
+
+       FLT_STRM_CB(s, flt_http_end(s, msg),
+                   /* default_ret */ 1,
+                   /* on_error    */ goto error,
+                   /* on_wait     */ goto waiting);
+       msg->msg_state = HTTP_MSG_DONE;
+       return 1;
+
+  missing_data_or_waiting:
+       /* we may have some pending data starting at chn->buf->p */
+       ret = FLT_STRM_CB(s, flt_http_forward_data(s, msg, msg->next),
+                         /* default_ret */ msg->next,
+                         /* on_error    */ goto error);
+       b_adv(chn->buf, ret);
+       msg->next -= ret;
+       if (!(chn->flags & CF_WROTE_DATA) || msg->sov > 0)
+               msg->sov -= ret;
+       if (!HAS_FILTERS(s))
+               msg->chunk_len -= channel_forward(chn, msg->chunk_len);
+  waiting:
+       return 0;
+  error:
+       return -1;
+}
+
+static inline int
+http_msg_forward_chunked_body(struct stream *s, struct http_msg *msg)
+{
+       struct channel *chn = msg->chn;
+       int ret;
+
+       /* Here we have the guarantee to be in one of the following state:
+        * HTTP_MSG_DATA, HTTP_MSG_CHUNK_SIZE, HTTP_MSG_CHUNK_CRLF,
+        * HTTP_MSG_TRAILERS or HTTP_MSG_ENDING. */
+
+  switch_states:
+       switch (msg->msg_state) {
+               case HTTP_MSG_DATA:
+                       ret = FLT_STRM_CB(s, flt_http_data(s, msg),
+                                         /* default_ret */ MIN(msg->chunk_len, chn->buf->i - msg->next),
+                                         /* on_error    */ goto error);
+                       msg->next     += ret;
+                       msg->chunk_len -= ret;
+                       if (msg->chunk_len) {
+                               /* input empty or output full */
+                               if (chn->buf->i > msg->next)
+                                       chn->flags |= CF_WAKE_WRITE;
+                               goto missing_data_or_waiting;
+                       }
+
+                       /* nothing left to forward for this chunk*/
+                       msg->msg_state = HTTP_MSG_CHUNK_CRLF;
+                       /* fall through for HTTP_MSG_CHUNK_CRLF */
+
+               case HTTP_MSG_CHUNK_CRLF:
+                       /* we want the CRLF after the data */
+                       ret = http_skip_chunk_crlf(msg);
+                       if (ret == 0)
+                               goto missing_data_or_waiting;
+                       if (ret < 0)
+                               goto chunk_parsing_error;
+                       msg->next += msg->sol;
+                       msg->sol   = 0;
+                       msg->msg_state = HTTP_MSG_CHUNK_SIZE;
+                       /* fall through for HTTP_MSG_CHUNK_SIZE */
+
+               case HTTP_MSG_CHUNK_SIZE:
+                       /* read the chunk size and assign it to ->chunk_len,
+                        * then set ->next to point to the body and switch to
+                        * DATA or TRAILERS state.
+                        */
+                       ret = http_parse_chunk_size(msg);
+                       if (ret == 0)
+                               goto missing_data_or_waiting;
+                       if (ret < 0)
+                               goto chunk_parsing_error;
+                       msg->next += msg->sol;
+                       msg->sol   = 0;
+                       if (msg->chunk_len) {
+                               msg->msg_state = HTTP_MSG_DATA;
+                               goto switch_states;
+                       }
+                       msg->msg_state = HTTP_MSG_TRAILERS;
+                       /* fall through for HTTP_MSG_TRAILERS */
+
+               case HTTP_MSG_TRAILERS:
+                       ret = http_forward_trailers(msg);
+                       if (ret < 0)
+                               goto chunk_parsing_error;
+                       FLT_STRM_CB(s, flt_http_chunk_trailers(s, msg),
+                                   /* default_ret */ 1,
+                                   /* on_error    */ goto error);
+                       msg->next += msg->sol;
+                       msg->sol   = 0;
+                       if (!ret)
+                               goto missing_data_or_waiting;
+                       break;
+
+               case HTTP_MSG_ENDING:
+                       goto ending;
+
+               default:
+                       /* This should no happen in this function */
+                       goto error;
+       }
+
+       msg->msg_state = HTTP_MSG_ENDING;
+  ending:
+       /* we may have some pending data starting at res->buf->p such as a last
+        * chunk of data or trailers. */
+       ret = FLT_STRM_CB(s, flt_http_forward_data(s, msg, msg->next),
+                         /* default_ret */ msg->next,
+                         /* on_error    */ goto error);
+       b_adv(chn->buf, ret);
+       msg->next -= ret;
+       if (msg->next)
+               goto missing_data_or_waiting;
+
+       FLT_STRM_CB(s, flt_http_end(s, msg),
+                   /* default_ret */ 1,
+                   /* on_error    */ goto error,
+                   /* on_wait     */ goto waiting);
+       msg->msg_state = HTTP_MSG_DONE;
+       return 1;
+
+  missing_data_or_waiting:
+       /* we may have some pending data starting at chn->buf->p */
+       ret = FLT_STRM_CB(s, flt_http_forward_data(s, msg, msg->next),
+                         /* default_ret */ msg->next,
+                         /* on_error    */ goto error);
+       b_adv(chn->buf, ret);
+       msg->next -= ret;
+       if (!(chn->flags & CF_WROTE_DATA) || msg->sov > 0)
+               msg->sov -= ret;
+       if (!HAS_FILTERS(s))
+               msg->chunk_len -= channel_forward(chn, msg->chunk_len);
+  waiting:
+       return 0;
+
+  chunk_parsing_error:
+       if (msg->err_pos >= 0) {
+               if (chn->flags & CF_ISRESP)
+                       http_capture_bad_message(&s->be->invalid_rep, s, msg,
+                                                msg->msg_state, strm_fe(s));
+               else
+                       http_capture_bad_message(&strm_fe(s)->invalid_req, s,
+                                                msg, msg->msg_state, s->be);
+       }
+  error:
+       return -1;
+}
+
+
 /* Iterate the same filter through all request headers.
  * Returns 1 if this filter can be stopped upon return, otherwise 0.
  * Since it can manage the switch to another backend, it updates the per-proxy