From 9998c0cbc814decba91f90f3cdcda8da5cc1f5e3 Mon Sep 17 00:00:00 2001 From: Niels Provos Date: Thu, 26 Jun 2008 00:40:57 +0000 Subject: [PATCH] correct handling of trailing headers in chunked replies; from Scott Lamb. svn:r887 --- ChangeLog | 3 +- http-internal.h | 24 +++- http.c | 270 ++++++++++++++++++++++++----------- include/event2/http_struct.h | 1 - test/regress_http.c | 107 +++++++++----- 5 files changed, 278 insertions(+), 127 deletions(-) diff --git a/ChangeLog b/ChangeLog index 682839f6..127195b6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -111,7 +111,8 @@ Changes in current version: o Rename INPUT and OUTPUT to EVRPC_INPUT and EVRPC_OUTPUT. Retain INPUT/OUTPUT aliases on on-win32 platforms for backwards compatibility. o Do not use SO_REUSEADDR when connecting o Support 64-bit integers in RPC structs - + o Correct handling of trailing headers in chunked replies; from Scott Lamb. + Changes in 1.4.0: o allow \r or \n individually to separate HTTP headers instead of the standard "\r\n"; from Charles Kerr. o demote most http warnings to debug messages diff --git a/http-internal.h b/http-internal.h index d282206e..b1225a87 100644 --- a/http-internal.h +++ b/http-internal.h @@ -19,6 +19,13 @@ #define HTTP_PREFIX "http://" #define HTTP_DEFAULTPORT 80 +enum message_read_status { + ALL_DATA_READ = 1, + MORE_DATA_EXPECTED = 0, + DATA_CORRUPTED = -1, + REQUEST_CANCELED = -2 +}; + enum evhttp_connection_error { EVCON_HTTP_TIMEOUT, EVCON_HTTP_EOF, @@ -34,9 +41,15 @@ struct evhttp_request; /* A stupid connection object - maybe make this a bufferevent later */ enum evhttp_connection_state { - EVCON_DISCONNECTED, /* not currently connected not trying either */ - EVCON_CONNECTING, /* tries to currently connect */ - EVCON_CONNECTED /* connection is established */ + EVCON_DISCONNECTED, /**< not currently connected not trying either*/ + EVCON_CONNECTING, /**< tries to currently connect */ + EVCON_IDLE, /**< connection is established */ + EVCON_READING_FIRSTLINE,/**< reading Request-Line (incoming conn) or + **< Status-Line (outgoing conn) */ + EVCON_READING_HEADERS, /**< reading request/response headers */ + EVCON_READING_BODY, /**< reading request/response body */ + EVCON_READING_TRAILER, /**< reading request/response chunked trailer */ + EVCON_WRITING /**< writing request/response headers/body */ }; struct event_base; @@ -60,7 +73,6 @@ struct evhttp_connection { #define EVHTTP_CON_INCOMING 0x0001 /* only one request on it ever */ #define EVHTTP_CON_OUTGOING 0x0002 /* multiple requests possible */ #define EVHTTP_CON_CLOSEDETECT 0x0004 /* detecting if persistent close */ -#define EVHTTP_CON_GOTHEADERS 0x0008 /* done reading headers */ int timeout; /* timeout in seconds for events */ int retry_cnt; /* retry count */ @@ -134,10 +146,10 @@ void evhttp_connection_fail(struct evhttp_connection *, void evhttp_get_request(struct evhttp *, evutil_socket_t, struct sockaddr *, socklen_t); -int evhttp_parse_lines(struct evhttp_request *, struct evbuffer*); +int evhttp_parse_firstline(struct evhttp_request *, struct evbuffer*); +int evhttp_parse_headers(struct evhttp_request *, struct evbuffer*); void evhttp_start_read(struct evhttp_connection *); -void evhttp_read_header(evutil_socket_t, short, void *); void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *); void evhttp_write_buffer(struct evhttp_connection *, diff --git a/http.c b/http.c index ded43c9e..b6c697da 100644 --- a/http.c +++ b/http.c @@ -207,10 +207,13 @@ static void evhttp_connection_start_detectclose( static void evhttp_connection_stop_detectclose( struct evhttp_connection *evcon); static void evhttp_request_dispatch(struct evhttp_connection* evcon); +static void evhttp_read_firstline(struct evhttp_connection *evcon, + struct evhttp_request *req); +static void evhttp_read_header(struct evhttp_connection *evcon, + struct evhttp_request *req); /* callbacks for bufferevent */ static void evhttp_read_cb(struct bufferevent *, void *); -static void evhttp_read_header_cb(struct bufferevent *bufev, void *arg); static void evhttp_write_cb(struct bufferevent *, void *); static void evhttp_error_cb(struct bufferevent *bufev, short what, void *arg); static int evhttp_decode_uri_internal(const char *uri, size_t length, @@ -351,6 +354,24 @@ evhttp_write_buffer(struct evhttp_connection *evcon, bufferevent_enable(evcon->bufev, EV_WRITE); } +static int +evhttp_connected(struct evhttp_connection *evcon) +{ + switch (evcon->state) { + case EVCON_DISCONNECTED: + case EVCON_CONNECTING: + return (0); + case EVCON_IDLE: + case EVCON_READING_FIRSTLINE: + case EVCON_READING_HEADERS: + case EVCON_READING_BODY: + case EVCON_READING_TRAILER: + case EVCON_WRITING: + default: + return (1); + } +} + /* * Create the headers need for an HTTP request */ @@ -621,23 +642,29 @@ evhttp_write_cb(struct bufferevent *bufev, void *arg) (*evcon->cb)(evcon, evcon->cb_arg); } +/** + * Advance the connection state. + * - If this is an outgoing connection, we've just processed the response; + * idle or close the connection. + * - If this is an incoming connection, we've just processed the request; + * respond. + */ static void evhttp_connection_done(struct evhttp_connection *evcon) { struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); int con_outgoing = evcon->flags & EVHTTP_CON_OUTGOING; - /* - * if this is an incoming connection, we need to leave the request - * on the connection, so that we can reply to it. - */ if (con_outgoing) { + /* idle or close the connection */ int need_close; TAILQ_REMOVE(&evcon->requests, req, next); req->evcon = NULL; + evcon->state = EVCON_IDLE; + need_close = - evhttp_is_connection_close(req->flags, req->input_headers) || + evhttp_is_connection_close(req->flags, req->input_headers)|| evhttp_is_connection_close(req->flags, req->output_headers); /* check if we got asked to close the connection */ @@ -647,10 +674,9 @@ evhttp_connection_done(struct evhttp_connection *evcon) if (TAILQ_FIRST(&evcon->requests) != NULL) { /* * We have more requests; reset the connection - * and deal with the next request. xxx: no - * persistent connection right now + * and deal with the next request. */ - if (evcon->state != EVCON_CONNECTED) + if (!evhttp_connected(evcon)) evhttp_connection_connect(evcon); else evhttp_request_dispatch(evcon); @@ -661,6 +687,12 @@ evhttp_connection_done(struct evhttp_connection *evcon) */ evhttp_connection_start_detectclose(evcon); } + } else { + /* + * incoming connection - we need to leave the request on the + * connection so that we can reply to it. + */ + evcon->state = EVCON_WRITING; } /* notify the user of the request */ @@ -686,14 +718,7 @@ evhttp_connection_done(struct evhttp_connection *evcon) * request was canceled by the user calling evhttp_cancel_request */ -enum chunked_read_status { - ALL_DATA_READ = 1, - MORE_DATA_EXPECTED = 0, - DATA_CORRUPTED = -1, - REQUEST_CANCELED = -2 -}; - -static enum chunked_read_status +static enum message_read_status evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf) { int len; @@ -747,6 +772,26 @@ evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf) return (MORE_DATA_EXPECTED); } +static void +evhttp_read_trailer(struct evhttp_connection *evcon, struct evhttp_request *req) +{ + struct evbuffer *buf = bufferevent_get_input(evcon->bufev); + + switch (evhttp_parse_headers(req, buf)) { + case DATA_CORRUPTED: + evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); + break; + case ALL_DATA_READ: + bufferevent_disable(evcon->bufev, EV_READ); + evhttp_connection_done(evcon); + break; + case MORE_DATA_EXPECTED: + default: + bufferevent_enable(evcon->bufev, EV_READ); + break; + } +} + static void evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req) { @@ -756,8 +801,8 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req) switch (evhttp_handle_chunked_read(req, buf)) { case ALL_DATA_READ: /* finished last chunk */ - bufferevent_disable(evcon->bufev, EV_READ); - evhttp_connection_done(evcon); + evcon->state = EVCON_READING_TRAILER; + evhttp_read_trailer(evcon, req); return; case DATA_CORRUPTED: /* corrupted data */ @@ -803,11 +848,6 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req) } /* Read more! */ - bufferevent_setcb(evcon->bufev, - evhttp_read_cb, - evhttp_write_cb, - evhttp_error_cb, - evcon); bufferevent_enable(evcon->bufev, EV_READ); } @@ -821,8 +861,33 @@ evhttp_read_cb(struct bufferevent *bufev, void *arg) struct evhttp_connection *evcon = arg; struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); - evhttp_read_body(evcon, req); - /* note the request may have been freed in evhttp_read_body */ + switch (evcon->state) { + case EVCON_READING_FIRSTLINE: + evhttp_read_firstline(evcon, req); + /* note the request may have been freed in + * evhttp_read_body */ + break; + case EVCON_READING_HEADERS: + evhttp_read_header(evcon, req); + /* note the request may have been freed in + * evhttp_read_body */ + break; + case EVCON_READING_BODY: + evhttp_read_body(evcon, req); + /* note the request may have been freed in + * evhttp_read_body */ + break; + case EVCON_READING_TRAILER: + evhttp_read_trailer(evcon, req); + break; + case EVCON_DISCONNECTED: + case EVCON_CONNECTING: + case EVCON_IDLE: + case EVCON_WRITING: + default: + event_errx(1, "%s: illegal connection state %d", + __func__, evcon->state); + } } static void @@ -832,6 +897,8 @@ evhttp_write_connectioncb(struct evhttp_connection *evcon, void *arg) struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); assert(req != NULL); + assert(evcon->state == EVCON_WRITING); + /* We are done writing our header and are now expecting the response */ req->kind = EVHTTP_RESPONSE; @@ -849,7 +916,7 @@ evhttp_connection_free(struct evhttp_connection *evcon) /* notify interested parties that this connection is going down */ if (evcon->fd != -1) { - if (evcon->state == EVCON_CONNECTED && evcon->closecb != NULL) + if (evhttp_connected(evcon) && evcon->closecb != NULL) (*evcon->closecb)(evcon, evcon->closecb_arg); } @@ -910,7 +977,9 @@ evhttp_request_dispatch(struct evhttp_connection* evcon) evhttp_connection_stop_detectclose(evcon); /* we assume that the connection is connected already */ - assert(evcon->state == EVCON_CONNECTED); + assert(evcon->state == EVCON_IDLE); + + evcon->state = EVCON_WRITING; /* Create the header from the store arguments */ evhttp_make_header(evcon, req); @@ -926,16 +995,13 @@ evhttp_connection_reset(struct evhttp_connection *evcon) if (evcon->fd != -1) { /* inform interested parties about connection close */ - if (evcon->state == EVCON_CONNECTED && evcon->closecb != NULL) + if (evhttp_connected(evcon) && evcon->closecb != NULL) (*evcon->closecb)(evcon, evcon->closecb_arg); EVUTIL_CLOSESOCKET(evcon->fd); evcon->fd = -1; } evcon->state = EVCON_DISCONNECTED; - - /* remove unneeded flags */ - evcon->flags &= ~(EVHTTP_CON_CLOSEDETECT|EVHTTP_CON_GOTHEADERS); } static void @@ -1004,6 +1070,7 @@ static void evhttp_error_cb(struct bufferevent *bufev, short what, void *arg) { struct evhttp_connection *evcon = arg; + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); switch (evcon->state) { case EVCON_CONNECTING: @@ -1016,9 +1083,9 @@ evhttp_error_cb(struct bufferevent *bufev, short what, void *arg) } break; - case EVCON_CONNECTED: - if (what == (EVBUFFER_READ|EVBUFFER_EOF) && - (evcon->flags & EVHTTP_CON_GOTHEADERS)) { + case EVCON_READING_BODY: + if (!req->chunked && req->ntoread < 0 + && what == (EVBUFFER_READ|EVBUFFER_EOF)) { /* EOF on read can be benign */ evhttp_connection_done(evcon); return; @@ -1026,6 +1093,11 @@ evhttp_error_cb(struct bufferevent *bufev, short what, void *arg) break; case EVCON_DISCONNECTED: + case EVCON_IDLE: + case EVCON_READING_FIRSTLINE: + case EVCON_READING_HEADERS: + case EVCON_READING_TRAILER: + case EVCON_WRITING: default: break; } @@ -1071,12 +1143,11 @@ evhttp_connection_cb(struct bufferevent *bufev, void *arg) /* Reset the retry count as we were successful in connecting */ evcon->retry_cnt = 0; - evcon->state = EVCON_CONNECTED; - evcon->flags &= ~EVHTTP_CON_GOTHEADERS; + evcon->state = EVCON_IDLE; /* reset the bufferevent cbs */ bufferevent_setcb(evcon->bufev, - evhttp_read_header_cb, + evhttp_read_cb, evhttp_write_cb, evhttp_error_cb, evcon); @@ -1309,63 +1380,74 @@ evhttp_add_header(struct evkeyvalq *headers, * request object given an event buffer. * * Returns - * -1 on error - * 0 when we need to read more headers - * 1 when all headers have been read. + * DATA_CORRUPTED on error + * MORE_DATA_EXPECTED when we need to read more headers + * ALL_DATA_READ when all headers have been read. */ -int -evhttp_parse_lines(struct evhttp_request *req, struct evbuffer* buffer) +enum message_read_status +evhttp_parse_firstline(struct evhttp_request *req, struct evbuffer *buffer) { char *line; - int done = 0; + enum message_read_status status = ALL_DATA_READ; + + line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_CRLF); + if (line == NULL) + return (MORE_DATA_EXPECTED); + + switch (req->kind) { + case EVHTTP_REQUEST: + if (evhttp_parse_request_line(req, line) == -1) + status = DATA_CORRUPTED; + break; + case EVHTTP_RESPONSE: + if (evhttp_parse_response_line(req, line) == -1) + status = DATA_CORRUPTED; + break; + default: + status = DATA_CORRUPTED; + } + + mm_free(line); + return (status); +} + +enum message_read_status +evhttp_parse_headers(struct evhttp_request *req, struct evbuffer* buffer) +{ + char *line; + enum message_read_status status = MORE_DATA_EXPECTED; struct evkeyvalq* headers = req->input_headers; - while ((line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_CRLF)) != NULL) { + while ((line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_CRLF)) + != NULL) { char *skey, *svalue; if (*line == '\0') { /* Last header - Done */ - done = 1; + status = ALL_DATA_READ; mm_free(line); break; } /* Processing of header lines */ - if (req->got_firstline == 0) { - switch (req->kind) { - case EVHTTP_REQUEST: - if (evhttp_parse_request_line(req, line) == -1) - goto error; - break; - case EVHTTP_RESPONSE: - if (evhttp_parse_response_line(req, line) == -1) - goto error; - break; - default: - goto error; - } - req->got_firstline = 1; - } else { - /* Regular header */ - svalue = line; - skey = strsep(&svalue, ":"); - if (svalue == NULL) - goto error; + svalue = line; + skey = strsep(&svalue, ":"); + if (svalue == NULL) + goto error; - svalue += strspn(svalue, " "); + svalue += strspn(svalue, " "); - if (evhttp_add_header(headers, skey, svalue) == -1) - goto error; - } + if (evhttp_add_header(headers, skey, svalue) == -1) + goto error; mm_free(line); } - return (done); + return (status); error: mm_free(line); - return (-1); + return (DATA_CORRUPTED); } static int @@ -1417,6 +1499,7 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req) evhttp_connection_done(evcon); return; } + evcon->state = EVCON_READING_BODY; xfer_enc = evhttp_find_header(req->input_headers, "Transfer-Encoding"); if (xfer_enc != NULL && strcasecmp(xfer_enc, "chunked") == 0) { req->chunked = 1; @@ -1433,20 +1516,39 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req) } static void -evhttp_read_header_cb(struct bufferevent *bufev, void *arg) +evhttp_read_firstline(struct evhttp_connection *evcon, + struct evhttp_request *req) { - struct evhttp_connection *evcon = arg; - struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); - int res; + enum message_read_status res; + + res = evhttp_parse_firstline(req, bufferevent_get_input(evcon->bufev)); + if (res == DATA_CORRUPTED) { + /* Error while reading, terminate */ + event_debug(("%s: bad header lines on %d\n", __func__, fd)); + evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); + return; + } else if (res == MORE_DATA_EXPECTED) { + /* Need more header lines */ + return; + } + + evhttp_read_header(evcon, req); +} + +static void +evhttp_read_header(struct evhttp_connection *evcon, + struct evhttp_request *req) +{ + enum message_read_status res; int fd = evcon->fd; - res = evhttp_parse_lines(req, bufferevent_get_input(evcon->bufev)); - if (res == -1) { + res = evhttp_parse_headers(req, bufferevent_get_input(evcon->bufev)); + if (res == DATA_CORRUPTED) { /* Error while reading, terminate */ event_debug(("%s: bad header lines on %d\n", __func__, fd)); evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); return; - } else if (res == 0) { + } else if (res == MORE_DATA_EXPECTED) { /* Need more header lines */ return; } @@ -1454,9 +1556,6 @@ evhttp_read_header_cb(struct bufferevent *bufev, void *arg) /* Disable reading for now */ bufferevent_disable(evcon->bufev, EV_READ); - /* we got all headers */ - evcon->flags |= EVHTTP_CON_GOTHEADERS; - /* Done reading headers, do the real work */ switch (req->kind) { case EVHTTP_REQUEST: @@ -1531,7 +1630,7 @@ evhttp_connection_base_new(struct event_base *base, } if ((evcon->bufev = bufferevent_new(-1, - evhttp_read_header_cb, + evhttp_read_cb, evhttp_write_cb, evhttp_error_cb, evcon)) == NULL) { event_warn("%s: bufferevent_new failed", __func__); @@ -1674,7 +1773,7 @@ evhttp_make_request(struct evhttp_connection *evcon, TAILQ_INSERT_TAIL(&evcon->requests, req, next); /* If the connection object is not connected; make it so */ - if (evcon->state != EVCON_CONNECTED) + if (!evhttp_connected(evcon)) return (evhttp_connection_connect(evcon)); /* @@ -1725,6 +1824,7 @@ evhttp_start_read(struct evhttp_connection *evcon) /* Set up an event to read the headers */ bufferevent_disable(evcon->bufev, EV_WRITE); bufferevent_enable(evcon->bufev, EV_READ); + evcon->state = EVCON_READING_FIRSTLINE; } static void @@ -2560,7 +2660,7 @@ evhttp_get_request_connection( return (NULL); evcon->flags |= EVHTTP_CON_INCOMING; - evcon->state = EVCON_CONNECTED; + evcon->state = EVCON_READING_FIRSTLINE; evcon->fd = fd; diff --git a/include/event2/http_struct.h b/include/event2/http_struct.h index 6d972893..6e16c7dc 100644 --- a/include/event2/http_struct.h +++ b/include/event2/http_struct.h @@ -93,7 +93,6 @@ struct { char major; /* HTTP Major number */ char minor; /* HTTP Minor number */ - int got_firstline; int response_code; /* HTTP Response code */ char *response_code_line; /* Readable response */ diff --git a/test/regress_http.c b/test/regress_http.c index dcd98b4c..f45e070a 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -181,15 +181,23 @@ http_readcb(struct bufferevent *bev, void *arg) if (evbuffer_find(EVBUFFER_INPUT(bev), (const unsigned char*) what, strlen(what)) != NULL) { struct evhttp_request *req = evhttp_request_new(NULL, NULL); - int done; + enum message_read_status done; req->kind = EVHTTP_RESPONSE; - done = evhttp_parse_lines(req, EVBUFFER_INPUT(bev)); + done = evhttp_parse_firstline(req, EVBUFFER_INPUT(bev)); + if (done != ALL_DATA_READ) + goto out; + + done = evhttp_parse_headers(req, EVBUFFER_INPUT(bev)); + if (done != ALL_DATA_READ) + goto out; if (done == 1 && evhttp_find_header(req->input_headers, "Content-Type") != NULL) test_ok++; + + out: evhttp_request_free(req); bufferevent_disable(bev, EV_READ); if (base) @@ -231,31 +239,53 @@ http_basic_cb(struct evhttp_request *req, void *arg) evbuffer_free(evb); } +static char const* const CHUNKS[] = { + "This is funny", + "but not hilarious.", + "bwv 1052" +}; + +struct chunk_req_state { + struct evhttp_request *req; + int i; +}; + static void -http_chunked_cb(struct evhttp_request *req, void *arg) +http_chunked_trickle_cb(evutil_socket_t fd, short events, void *arg) { struct evbuffer *evb = evbuffer_new(); - event_debug(("%s: called\n", __func__)); + struct chunk_req_state *state = arg; + struct timeval when = { 0, 0 }; - /* generate a chunked reply */ - evhttp_send_reply_start(req, HTTP_OK, "Everything is fine"); + evbuffer_add_printf(evb, CHUNKS[state->i]); + evhttp_send_reply_chunk(state->req, evb); + evbuffer_free(evb); - /* first chunk */ - evbuffer_add_printf(evb, "This is funny"); - evhttp_send_reply_chunk(req, evb); + if (++state->i < sizeof(CHUNKS)/sizeof(CHUNKS[0])) { + event_once(-1, EV_TIMEOUT, + http_chunked_trickle_cb, state, &when); + } else { + evhttp_send_reply_end(state->req); + free(state); + } +} - /* second chunk */ - evbuffer_add_printf(evb, "but not hilarious."); - evhttp_send_reply_chunk(req, evb); +static void +http_chunked_cb(struct evhttp_request *req, void *arg) +{ + struct timeval when = { 0, 0 }; + struct chunk_req_state *state = malloc(sizeof(struct chunk_req_state)); + event_debug(("%s: called\n", __func__)); - /* third and last chunk */ - evbuffer_add_printf(evb, "bwv 1052"); - evhttp_send_reply_chunk(req, evb); + memset(state, 0, sizeof(struct chunk_req_state)); + state->req = req; - /* finish request */ - evhttp_send_reply_end(req); + /* generate a chunked reply */ + evhttp_send_reply_start(req, HTTP_OK, "Everything is fine"); - evbuffer_free(evb); + /* but trickle it across several iterations to ensure we're not + * assuming it comes all at once */ + event_once(-1, EV_TIMEOUT, http_chunked_trickle_cb, state, &when); } static void @@ -1555,12 +1585,15 @@ http_chunked_errorcb(struct bufferevent *bev, short what, void *arg) if ((what & EVBUFFER_EOF) != 0) { struct evhttp_request *req = evhttp_request_new(NULL, NULL); const char *header; - int done; + enum message_read_status done; req->kind = EVHTTP_RESPONSE; - done = evhttp_parse_lines(req, EVBUFFER_INPUT(bev)); + done = evhttp_parse_firstline(req, EVBUFFER_INPUT(bev)); + if (done != ALL_DATA_READ) + goto out; - if (done != 1) + done = evhttp_parse_headers(req, EVBUFFER_INPUT(bev)); + if (done != ALL_DATA_READ) goto out; header = evhttp_find_header(req->input_headers, "Transfer-Encoding"); @@ -1678,6 +1711,7 @@ http_chunked_test(void) struct timeval tv_start, tv_end; struct evhttp_connection *evcon = NULL; struct evhttp_request *req = NULL; + int i; test_ok = 0; fprintf(stdout, "Testing Chunked HTTP Reply: "); @@ -1723,23 +1757,28 @@ http_chunked_test(void) fprintf(stdout, "FAILED\n"); exit(1); } - req = evhttp_request_new(http_chunked_request_done, NULL); - /* Add the information that we care about */ - evhttp_add_header(req->output_headers, "Host", "somehost"); + /* make two requests to check the keepalive behavior */ + for (i = 0; i < 2; i++) { + test_ok = 0; + req = evhttp_request_new(http_chunked_request_done, NULL); - /* We give ownership of the request to the connection */ - if (evhttp_make_request(evcon, req, - EVHTTP_REQ_GET, "/chunked") == -1) { - fprintf(stdout, "FAILED\n"); - exit(1); - } + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); - event_dispatch(); + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, + EVHTTP_REQ_GET, "/chunked") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } - if (test_ok != 1) { - fprintf(stdout, "FAILED\n"); - exit(1); + event_dispatch(); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } } evhttp_connection_free(evcon); -- 2.50.0