[BUG] http: fix computation of message body length after forwarding has started

Till now, the forwarding code was making use of the hdr_content_len member
to hold the size of the last chunk parsed. As such, it was reset after being
scheduled for forwarding. The issue is that this entry was reset before the
data could be viewed by backend.c in order to parse a POST body, so the
"balance url_param check_post" did not work anymore.

In order to fix this, we need two things :
  - the chunk size (reset upon every forward)
  - the total body size (not reset)

hdr_content_len was thus replaced by the former (hence the size of the patch)
as it makes more sense to have it stored that way than the way around.

This patch should be backported to 1.4 with care, considering that it affects
the forwarding code.
This commit is contained in:
Willy Tarreau 2011-03-01 20:30:48 +01:00
parent acd20f80c1
commit 124d99181c
3 changed files with 32 additions and 28 deletions

View File

@ -300,7 +300,8 @@ struct http_msg {
int r, r_l; /* REASON, length */
} st; /* status line : field, length */
} sl; /* start line */
unsigned long long hdr_content_len; /* cache for parsed header value or for chunk-size if present */
unsigned long long chunk_len; /* cache for last chunk size or content-length header value */
unsigned long long body_len; /* total known length of the body, excluding encoding */
char **cap; /* array of captured headers (may be NULL) */
};

View File

@ -251,7 +251,7 @@ struct server *get_server_ph_post(struct session *s)
struct http_msg *msg = &txn->req;
struct proxy *px = s->be;
unsigned int plen = px->url_param_len;
unsigned long len = msg->hdr_content_len;
unsigned long len = msg->body_len;
const char *params = req->data + msg->sov;
const char *p = params;

View File

@ -2148,7 +2148,8 @@ int http_parse_chunk_size(struct buffer *buf, struct http_msg *msg)
*/
msg->sov += ptr - buf->lr;
buf->lr = ptr;
msg->hdr_content_len = chunk;
msg->chunk_len = chunk;
msg->body_len += chunk;
msg->msg_state = chunk ? HTTP_MSG_DATA : HTTP_MSG_TRAILERS;
return 1;
error:
@ -2809,11 +2810,11 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
if (cl < 0)
goto return_bad_req;
if ((txn->flags & TX_REQ_CNT_LEN) && (msg->hdr_content_len != cl))
if ((txn->flags & TX_REQ_CNT_LEN) && (msg->chunk_len != cl))
goto return_bad_req; /* already specified, was different */
txn->flags |= TX_REQ_CNT_LEN | TX_REQ_XFER_LEN;
msg->hdr_content_len = cl;
msg->body_len = msg->chunk_len = cl;
}
/* bodyless requests have a known length */
@ -2869,7 +2870,7 @@ int http_process_req_stat_post(struct session *s, struct buffer *req)
char *first_param, *cur_param, *next_param, *end_params;
first_param = req->data + txn->req.eoh + 2;
end_params = first_param + txn->req.hdr_content_len;
end_params = first_param + txn->req.body_len;
cur_param = next_param = end_params;
@ -3348,7 +3349,7 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
if (rule->rdr_len >= 1 && *rule->rdr_str == '/' &&
(txn->flags & TX_REQ_XFER_LEN) &&
!(txn->flags & TX_REQ_TE_CHNK) && !txn->req.hdr_content_len &&
!(txn->flags & TX_REQ_TE_CHNK) && !txn->req.body_len &&
((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL ||
(txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL)) {
/* keep-alive possible */
@ -3769,7 +3770,7 @@ int http_process_request_body(struct session *s, struct buffer *req, int an_bit)
}
if (msg->msg_state == HTTP_MSG_CHUNK_SIZE) {
/* read the chunk size and assign it to ->hdr_content_len, then
/* read the chunk size and assign it to ->chunk_len, then
* set ->sov and ->lr to point to the body and switch to DATA or
* TRAILERS state.
*/
@ -3790,8 +3791,8 @@ int http_process_request_body(struct session *s, struct buffer *req, int an_bit)
* We're waiting for at least <url_param_post_limit> bytes after msg->sov.
*/
if (msg->hdr_content_len < limit)
limit = msg->hdr_content_len;
if (msg->body_len < limit)
limit = msg->body_len;
if (req->l - (msg->sov - msg->som) >= limit) /* we have enough bytes now */
goto http_end;
@ -4311,7 +4312,7 @@ int http_resync_states(struct session *s)
* remaining data and to resync after end of body. It expects the msg_state to
* be between MSG_BODY and MSG_DONE (inclusive). It returns zero if it needs to
* read more data, or 1 once we can go on with next request or end the session.
* When in MSG_DATA or MSG_TRAILERS, it will automatically forward hdr_content_len
* When in MSG_DATA or MSG_TRAILERS, it will automatically forward chunk_len
* bytes of pending data + the headers if not already done (between som and sov).
* It eventually adjusts som to match sov after the data in between have been sent.
*/
@ -4359,12 +4360,12 @@ int http_request_forward_body(struct session *s, struct buffer *req, int an_bit)
while (1) {
http_silent_debug(__LINE__, s);
/* we may have some data pending */
if (msg->hdr_content_len || msg->som != msg->sov) {
if (msg->chunk_len || msg->som != msg->sov) {
int bytes = msg->sov - msg->som;
if (bytes < 0) /* sov may have wrapped at the end */
bytes += req->size;
buffer_forward(req, bytes + msg->hdr_content_len);
msg->hdr_content_len = 0; /* don't forward that again */
buffer_forward(req, bytes + msg->chunk_len);
msg->chunk_len = 0; /* don't forward that again */
msg->som = msg->sov;
}
@ -4380,7 +4381,7 @@ int http_request_forward_body(struct session *s, struct buffer *req, int an_bit)
msg->msg_state = HTTP_MSG_DONE;
}
else if (msg->msg_state == HTTP_MSG_CHUNK_SIZE) {
/* read the chunk size and assign it to ->hdr_content_len, then
/* read the chunk size and assign it to ->chunk_len, then
* set ->sov and ->lr to point to the body and switch to DATA or
* TRAILERS state.
*/
@ -4935,7 +4936,7 @@ int http_wait_for_response(struct session *s, struct buffer *rep, int an_bit)
*/
/* Skip parsing if no content length is possible. The response flags
* remain 0 as well as the hdr_content_len, which may or may not mirror
* 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 ?
*/
@ -4975,11 +4976,11 @@ int http_wait_for_response(struct session *s, struct buffer *rep, int an_bit)
if (cl < 0)
goto hdr_response_bad;
if ((txn->flags & TX_RES_CNT_LEN) && (msg->hdr_content_len != cl))
if ((txn->flags & TX_RES_CNT_LEN) && (msg->chunk_len != cl))
goto hdr_response_bad; /* already specified, was different */
txn->flags |= TX_RES_CNT_LEN | TX_RES_XFER_LEN;
msg->hdr_content_len = cl;
msg->body_len = msg->chunk_len = cl;
}
/* FIXME: we should also implement the multipart/byterange method.
@ -5360,7 +5361,7 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
* remaining data and to resync after end of body. It expects the msg_state to
* be between MSG_BODY and MSG_DONE (inclusive). It returns zero if it needs to
* read more data, or 1 once we can go on with next request or end the session.
* When in MSG_DATA or MSG_TRAILERS, it will automatically forward hdr_content_len
* When in MSG_DATA or MSG_TRAILERS, it will automatically forward chunk_len
* bytes of pending data + the headers if not already done (between som and sov).
* It eventually adjusts som to match sov after the data in between have been sent.
*/
@ -5403,12 +5404,12 @@ int http_response_forward_body(struct session *s, struct buffer *res, int an_bit
while (1) {
http_silent_debug(__LINE__, s);
/* we may have some data pending */
if (msg->hdr_content_len || msg->som != msg->sov) {
if (msg->chunk_len || msg->som != msg->sov) {
int bytes = msg->sov - msg->som;
if (bytes < 0) /* sov may have wrapped at the end */
bytes += res->size;
buffer_forward(res, bytes + msg->hdr_content_len);
msg->hdr_content_len = 0; /* don't forward that again */
buffer_forward(res, bytes + msg->chunk_len);
msg->chunk_len = 0; /* don't forward that again */
msg->som = msg->sov;
}
@ -5424,7 +5425,7 @@ int http_response_forward_body(struct session *s, struct buffer *res, int an_bit
msg->msg_state = HTTP_MSG_DONE;
}
else if (msg->msg_state == HTTP_MSG_CHUNK_SIZE) {
/* read the chunk size and assign it to ->hdr_content_len, then
/* read the chunk size and assign it to ->chunk_len, then
* set ->sov to point to the body and switch to DATA or TRAILERS state.
*/
int ret = http_parse_chunk_size(res, msg);
@ -5521,12 +5522,12 @@ int http_response_forward_body(struct session *s, struct buffer *res, int an_bit
goto return_bad_res;
/* forward the chunk size as well as any pending data */
if (msg->hdr_content_len || msg->som != msg->sov) {
if (msg->chunk_len || msg->som != msg->sov) {
int bytes = msg->sov - msg->som;
if (bytes < 0) /* sov may have wrapped at the end */
bytes += res->size;
buffer_forward(res, msg->sov - msg->som + msg->hdr_content_len);
msg->hdr_content_len = 0; /* don't forward that again */
buffer_forward(res, msg->sov - msg->som + msg->chunk_len);
msg->chunk_len = 0; /* don't forward that again */
msg->som = msg->sov;
}
@ -7455,8 +7456,10 @@ void http_init_txn(struct session *s)
txn->req.som = txn->req.eoh = 0; /* relative to the buffer */
txn->rsp.sol = txn->rsp.eol = NULL;
txn->rsp.som = txn->rsp.eoh = 0; /* relative to the buffer */
txn->req.hdr_content_len = 0LL;
txn->rsp.hdr_content_len = 0LL;
txn->req.chunk_len = 0LL;
txn->req.body_len = 0LL;
txn->rsp.chunk_len = 0LL;
txn->rsp.body_len = 0LL;
txn->req.msg_state = HTTP_MSG_RQBEFORE; /* at the very beginning of the request */
txn->rsp.msg_state = HTTP_MSG_RPBEFORE; /* at the very beginning of the response */