[core] track Content-Length from backend (fixes #3046)

track Content-Length from backend in r->resp_body_scratchpad

x-ref:
  "Failure on second request in http proxy backend"
  https://redmine.lighttpd.net/issues/3046
This commit is contained in:
Glenn Strauss 2020-12-15 00:32:24 -05:00
parent e9309ae6e6
commit 903024d711
5 changed files with 72 additions and 3 deletions

View File

@ -2560,6 +2560,7 @@ h2_con_upgrade_h2c (request_st * const h2r, const buffer * const http2_settings)
#if 0 /* expect empty request body */
r->reqbody_length = h2r->reqbody_length; /* currently always 0 */
r->te_chunked = h2r->te_chunked; /* must be 0 */
r->resp_body_scratchpad = h2r->resp_body_scratchpad; /*(not started yet)*/
swap(&r->reqbody_queue,&h2r->reqbody_queue);/*currently always empty queue*/
#endif
r->http_host = h2r->http_host;

View File

@ -249,6 +249,7 @@ int http_response_handle_cachable(request_st * const r, const buffer * const mti
void http_response_body_clear (request_st * const r, int preserve_length) {
r->resp_send_chunked = 0;
r->resp_body_scratchpad = -1;
if (light_btst(r->resp_htags, HTTP_HEADER_TRANSFER_ENCODING)) {
http_header_response_unset(r, HTTP_HEADER_TRANSFER_ENCODING,
CONST_STR_LEN("Transfer-Encoding"));
@ -283,6 +284,7 @@ static void http_response_header_clear (request_st * const r) {
* Transfer-Encoding: chunked set, then other items need to be reset */
r->resp_send_chunked = 0;
r->resp_decode_chunked = 0;
r->resp_body_scratchpad = -1;
if (r->gw_dechunk) {
free(r->gw_dechunk->b.ptr);
free(r->gw_dechunk);
@ -1181,8 +1183,37 @@ static int http_response_process_headers(request_st * const r, http_response_opt
break;
case HTTP_HEADER_CONTENT_LENGTH:
if (*value == '+') ++value;
if (!r->resp_decode_chunked
&& !light_btst(r->resp_htags, HTTP_HEADER_CONTENT_LENGTH)) {
const char *err = ns;
if (err[-1] == '\0') --err; /*(skip one '\0', trailing whitespace)*/
while (err > value && (err[-1] == ' ' || err[-1] == '\t')) --err;
if (err <= value) continue; /*(might error 502 Bad Gateway)*/
uint32_t vlen = (uint32_t)(err - value);
r->resp_body_scratchpad =
(off_t)li_restricted_strtoint64(value, vlen, &err);
if (err != value + vlen) {
/*(invalid Content-Length value from backend;
* read from backend until backend close, hope for the best)
*(might choose to treat this as 502 Bad Gateway) */
r->resp_body_scratchpad = -1;
}
}
else {
/* ignore Content-Length if Transfer-Encoding: chunked
* ignore subsequent (multiple) Content-Length
* (might choose to treat this as 502 Bad Gateway) */
continue;
}
break;
case HTTP_HEADER_TRANSFER_ENCODING:
if (light_btst(r->resp_htags, HTTP_HEADER_CONTENT_LENGTH)) {
/* ignore Content-Length if Transfer-Encoding: chunked
* (might choose to treat this as 502 Bad Gateway) */
r->resp_body_scratchpad = -1;
http_header_response_unset(r, HTTP_HEADER_CONTENT_LENGTH,
CONST_STR_LEN("Content-Length"));
}
/*(assumes "Transfer-Encoding: chunked"; does not verify)*/
r->resp_decode_chunked = 1;
r->gw_dechunk = calloc(1, sizeof(response_dechunk));

View File

@ -483,8 +483,26 @@ int http_chunk_decode_append_buffer(request_st * const r, buffer * const mem)
/*(called by funcs receiving data from backends, which might be chunked)*/
/*(separate from http_chunk_append_buffer() called by numerous others)*/
if (!r->resp_decode_chunked)
if (!r->resp_decode_chunked) {
if (r->resp_body_scratchpad > 0) {
off_t len = (off_t)buffer_string_length(mem);
r->resp_body_scratchpad -= len;
if (r->resp_body_scratchpad <= 0) {
r->resp_body_finished = 1;
if (r->resp_body_scratchpad < 0) {
/*(silently truncate if data exceeds Content-Length)*/
len += r->resp_body_scratchpad;
r->resp_body_scratchpad = 0;
buffer_string_set_length(mem, (uint32_t)len);
}
}
}
else if (0 == r->resp_body_scratchpad) {
/*(silently truncate if data exceeds Content-Length)*/
return 0;
}
return http_chunk_append_buffer(r, mem);
}
/* might avoid copy by transferring buffer if buffer is all data that is
* part of large chunked block, but choosing to *not* expand that out here*/
@ -508,12 +526,28 @@ int http_chunk_decode_append_buffer(request_st * const r, buffer * const mem)
return 0;
}
int http_chunk_decode_append_mem(request_st * const r, const char * const mem, const size_t len)
int http_chunk_decode_append_mem(request_st * const r, const char * const mem, size_t len)
{
/*(called by funcs receiving data from backends, which might be chunked)*/
/*(separate from http_chunk_append_mem() called by numerous others)*/
if (!r->resp_decode_chunked)
if (!r->resp_decode_chunked) {
if (r->resp_body_scratchpad > 0) {
r->resp_body_scratchpad -= (off_t)len;
if (r->resp_body_scratchpad <= 0) {
r->resp_body_finished = 1;
if (r->resp_body_scratchpad < 0) {
/*(silently truncate if data exceeds Content-Length)*/
len = (size_t)(r->resp_body_scratchpad + (off_t)len);
r->resp_body_scratchpad = 0;
}
}
}
else if (0 == r->resp_body_scratchpad) {
/*(silently truncate if data exceeds Content-Length)*/
return 0;
}
return http_chunk_append_mem(r, mem, len);
}
if (0 != http_chunk_decode_append_data(r, mem, (off_t)len))
return -1;

View File

@ -31,6 +31,7 @@ request_init_data (request_st * const r, connection * const con, server * const
r->loops_per_request = 0;
r->con = con;
r->tmp_buf = srv->tmp_buf;
r->resp_body_scratchpad = -1;
/* init plugin-specific per-request structures */
r->plugin_ctx = calloc(1, (srv->plugins.used + 1) * sizeof(void *));
@ -68,6 +69,7 @@ request_reset (request_st * const r)
r->http_host = NULL;
r->reqbody_length = 0;
r->te_chunked = 0;
r->resp_body_scratchpad = -1;
r->rqst_htags = 0;
r->async_callback = 0;

View File

@ -153,6 +153,7 @@ struct request_st {
off_t reqbody_length; /* request Content-Length */
off_t te_chunked;
off_t resp_body_scratchpad;
buffer *http_host; /* copy of array value buffer ptr; not alloc'ed */
const buffer *server_name;