Fix decoding of multiple chunks in the same buffer

This commit is contained in:
Jeremy Lakeman 2017-05-23 15:23:28 +09:30
parent 2db8c24e15
commit c7de17b552
2 changed files with 44 additions and 42 deletions

View File

@ -122,7 +122,7 @@ void http_request_init(struct http_request *r, int sockfd)
r->reserved = r->buffer; r->reserved = r->buffer;
// Put aside a few bytes for reserving strings, so that the path and query parameters can be // Put aside a few bytes for reserving strings, so that the path and query parameters can be
// reserved ok. // reserved ok.
r->received = r->end_received = r->end = r->parsed = r->cursor = r->buffer + sizeof(void*) * (1 + NELS(r->query_parameters)); r->received = r->decode_ptr = r->end_received = r->end = r->parsed = r->cursor = r->buffer + sizeof(void*) * (1 + NELS(r->query_parameters));
r->parser = http_request_parse_verb; r->parser = http_request_parse_verb;
watch(&r->alarm); watch(&r->alarm);
http_request_set_idle_timeout(r); http_request_set_idle_timeout(r);
@ -1176,87 +1176,86 @@ malformed:
} }
static int http_request_decode_chunks(struct http_request *r){ static int http_request_decode_chunks(struct http_request *r){
if (r->end_received == r->end){ if (r->end_received == r->decode_ptr){
IDEBUGF(r->debug, "No chunk data to decode"); IDEBUGF(r->debug, "No chunk data to decode");
return 100; return 100;
} }
const char *ptr = r->end;
switch(r->chunk_state){ switch(r->chunk_state){
case CHUNK_NEWLINE:{ case CHUNK_NEWLINE:{
if (r->end_received - ptr < 2){ if (r->end_received - r->decode_ptr < 2){
IDEBUGF(r->debug, "Waiting for \\r\\n"); IDEBUGF(r->debug, "Waiting for \\r\\n");
return 100; return 100;
} }
if (ptr[0]!='\r' || ptr[1]!='\n') if (r->decode_ptr[0]!='\r' || r->decode_ptr[1]!='\n')
return WHYF("Expected \\r\\n, found %s", alloca_toprint(20, ptr, r->end_received - ptr)); return WHYF("Expected \\r\\n, found %s", alloca_toprint(20, r->decode_ptr, r->end_received - r->decode_ptr));
ptr+=2;
if (ptr == r->end_received && r->parsed == r->end){ r->decode_ptr += 2;
// if the client has flushed at the end of a chunk boundary, r->chunk_state = CHUNK_SIZE;
// and we've parsed every byte of the chunk if (r->request_content_remaining == 0){
// make sure our next read will overwrite the start of the buffer again r->decoder = NULL;
r->parsed = r->end = (char *)ptr; return 0;
r->chunk_state = CHUNK_SIZE;
return 100;
} }
// fall through // fall through
} }
case CHUNK_SIZE:{ case CHUNK_SIZE:{
const char *p; const char *p;
// TODO fail on non hex input // TODO fail on non hex input
int ret = strn_to_uint64(ptr, r->end_received - ptr, 16, &r->chunk_size, &p); int ret = strn_to_uint64(r->decode_ptr, r->end_received - r->decode_ptr, 16, &r->chunk_size, &p);
if (r->end_received - p < 2){ if (r->end_received - p < 2){
IDEBUGF(r->debug, "Waiting for [size]\\r\\n"); IDEBUGF(r->debug, "Waiting for [size]\\r\\n");
return 100; return 100;
} }
if (ret!=1 || p[0]!='\r' || p[1]!='\n') if (ret!=1 || p[0]!='\r' || p[1]!='\n')
return WHY("Expected [size]\r\n"); return WHY("Expected [size]\r\n");
ptr = p+2;
IDEBUGF(r->debug, "Chunk size %zu %s", r->chunk_size, alloca_toprint(20, r->end, ptr - r->end)); r->decode_ptr = (char*)p+2;
r->chunk_state = CHUNK_DATA;
IDEBUGF(r->debug, "Chunk size %zu (parsed %d, unparsed %d, heading %d, data %d) %s",
r->chunk_size,
(int)(r->parsed - r->received),
(int)(r->end - r->parsed),
(int)(r->decode_ptr - r->end),
(int)(r->end_received - r->decode_ptr),
alloca_toprint(20, r->end, r->decode_ptr - r->end));
if (r->chunk_size == 0){ if (r->chunk_size == 0){
// TODO if (r->end_received > ptr)?
// EOF
r->end_received = r->end;
r->decoder = NULL;
r->request_content_remaining = 0; r->request_content_remaining = 0;
IDEBUGF(r->debug, "EOF Chunk"); IDEBUGF(r->debug, "EOF Chunk");
return 0;
} }
r->chunk_state = CHUNK_DATA;
// fall through // fall through
} }
case CHUNK_DATA:{ case CHUNK_DATA:{
// skip the chunk heading if we can, to avoid a memmove // skip the chunk heading if we can, to avoid a memmove
if (r->end == r->parsed) if (r->end == r->parsed)
r->parsed = r->end = (char *)ptr; r->parsed = r->end = r->decode_ptr;
if (r->end_received == r->end){ if (r->decode_ptr > r->end){
IDEBUGF(r->debug, "Waiting for chunk data"); size_t used = r->end_received - r->decode_ptr;
return 100; size_t heading = r->decode_ptr - r->end;
IDEBUGF(r->debug, "Compacting %zu to cut out heading %zu",
used, heading);
memmove(r->end, r->decode_ptr, used);
r->end_received -= heading;
r->decode_ptr -= heading;
} }
if (ptr > r->end){ size_t len = r->end_received - r->decode_ptr;
size_t used = r->end_received - ptr;
IDEBUGF(r->debug, "Compacting %zu to cut out %zu",
used, ptr - r->end);
memmove(r->end, ptr, used);
r->end_received -= used;
}
size_t len = r->end_received - r->end;
if (len > r->chunk_size) if (len > r->chunk_size)
len = r->chunk_size; len = r->chunk_size;
r->chunk_size -= len; r->chunk_size -= len;
r->end += len; r->end += len;
r->decode_ptr = r->end;
if (r->chunk_size == 0){ if (r->chunk_size == 0){
r->chunk_state = CHUNK_NEWLINE; r->chunk_state = CHUNK_NEWLINE;
if (r->end_received - r->end == 2 && r->end[0]=='\r' && r->end[1]=='\n'){ if (r->end_received - r->decode_ptr == 2 && r->decode_ptr[0]=='\r' && r->decode_ptr[1]=='\n'){
// if we can cut the \r\n off the end, do it now // if we can cut the \r\n off the end, do it now
r->chunk_state = CHUNK_SIZE; r->chunk_state = CHUNK_SIZE;
r->end_received = r->end; r->end_received = r->end;
if (r->request_content_remaining == 0)
r->decoder = NULL;
} }
} }
// give the parser a chance to deal with this chunk so we can avoid memmove // give the parser a chance to deal with this chunk so we can avoid memmove
@ -1326,7 +1325,7 @@ static int http_request_start_body(struct http_request *r)
else if (r->verb == HTTP_VERB_POST) { else if (r->verb == HTTP_VERB_POST) {
if (r->request_header.chunked){ if (r->request_header.chunked){
r->decoder = http_request_decode_chunks; r->decoder = http_request_decode_chunks;
r->end = r->parsed; r->end = r->decode_ptr = r->parsed;
r->chunk_state = CHUNK_SIZE; r->chunk_state = CHUNK_SIZE;
}else if (r->request_header.content_length == CONTENT_LENGTH_UNKNOWN) { }else if (r->request_header.content_length == CONTENT_LENGTH_UNKNOWN) {
IDEBUGF(r->debug, "Malformed HTTP %s request: missing Content-Length or Transfer-Encoding: chunked header", r->verb); IDEBUGF(r->debug, "Malformed HTTP %s request: missing Content-Length or Transfer-Encoding: chunked header", r->verb);
@ -1764,12 +1763,13 @@ static void http_request_receive(struct http_request *r)
assert(r->phase == RECEIVE); assert(r->phase == RECEIVE);
const char *const bufend = r->buffer + sizeof r->buffer; const char *const bufend = r->buffer + sizeof r->buffer;
assert(r->end_received <= bufend); assert(r->end_received <= bufend);
assert(r->end <= r->end_received); assert(r->decode_ptr <= r->end_received);
assert(r->end <= r->decode_ptr);
assert(r->parsed <= r->end); assert(r->parsed <= r->end);
assert(r->received <= r->parsed); assert(r->received <= r->parsed);
// rewind buffer if everything has been parsed // rewind buffer if everything has been parsed
if (r->parsed == r->end_received){ if (r->parsed == r->end_received){
r->parsed = r->end = r->end_received = r->received; r->parsed = r->end = r->decode_ptr = r->end_received = r->received;
} }
// If the end of content falls within the buffer, then there is no need to make any more room, // If the end of content falls within the buffer, then there is no need to make any more room,
// just read up to the end of content. Otherwise, If buffer is running short on unused space, // just read up to the end of content. Otherwise, If buffer is running short on unused space,
@ -1784,6 +1784,7 @@ static void http_request_receive(struct http_request *r)
memmove((char *)r->received, r->parsed, unparsed); // memcpy() does not handle overlapping src and dst memmove((char *)r->received, r->parsed, unparsed); // memcpy() does not handle overlapping src and dst
r->parsed -= spare; r->parsed -= spare;
r->end -= spare; r->end -= spare;
r->decode_ptr -= spare;
r->end_received -= spare; r->end_received -= spare;
room = bufend - r->end_received; room = bufend - r->end_received;
if (r->request_content_remaining != CONTENT_LENGTH_UNKNOWN && room > r->request_content_remaining) if (r->request_content_remaining != CONTENT_LENGTH_UNKNOWN && room > r->request_content_remaining)
@ -1813,7 +1814,7 @@ static void http_request_receive(struct http_request *r)
RETURNVOID; RETURNVOID;
r->end_received += (size_t) bytes; r->end_received += (size_t) bytes;
if (!r->decoder) if (!r->decoder)
r->end = r->end_received; r->end = r->decode_ptr = r->end_received;
if (r->request_content_remaining != CONTENT_LENGTH_UNKNOWN) if (r->request_content_remaining != CONTENT_LENGTH_UNKNOWN)
r->request_content_remaining -= (size_t) bytes; r->request_content_remaining -= (size_t) bytes;
// We got some data, so reset the inactivity timer and invoke the parsing state machine to process // We got some data, so reset the inactivity timer and invoke the parsing state machine to process

View File

@ -229,6 +229,7 @@ struct http_request {
char *reserved; // end of reserved data in buffer[] char *reserved; // end of reserved data in buffer[]
char *received; // start of received data in buffer[] char *received; // start of received data in buffer[]
char *end; // end of decoded data in buffer[] char *end; // end of decoded data in buffer[]
char *decode_ptr; // end of received data in buffer[]
char *end_received; // end of received data in buffer[] char *end_received; // end of received data in buffer[]
char *parsed; // start of unparsed data in buffer[] char *parsed; // start of unparsed data in buffer[]
char *cursor; // for parsing char *cursor; // for parsing