from trunk: correct handling of trailing headers in chunked replies; from Scott Lamb

svn:r892
This commit is contained in:
Niels Provos 2008-06-30 01:04:21 +00:00
parent 0690f0f555
commit efb50876fd
5 changed files with 503 additions and 100 deletions

View File

@ -1,6 +1,7 @@
Changes in 1.4.6-stable:
o evutil.h now includes <stdarg.h> directly
o switch all uses of [v]snprintf over to evutil
o Correct handling of trailing headers in chunked replies; from Scott Lamb.
Changes in 1.4.5-stable:
o Fix connection keep-alive behavior for HTTP/1.0

View File

@ -216,7 +216,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 */

View File

@ -17,6 +17,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,
@ -30,9 +37,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;
@ -124,10 +137,10 @@ void evhttp_get_request(struct evhttp *, int, struct sockaddr *, socklen_t);
int evhttp_hostportfile(char *, char **, u_short *, char **);
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(int, short, void *);
void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *);
void evhttp_write_buffer(struct evhttp_connection *,

290
http.c
View File

@ -207,6 +207,10 @@ 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);
void evhttp_read(int, short, void *);
void evhttp_write(int, short, void *);
@ -345,6 +349,24 @@ evhttp_write_buffer(struct evhttp_connection *evcon,
evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_WRITE_TIMEOUT);
}
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
*/
@ -687,23 +709,29 @@ evhttp_write(int fd, short what, 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 */
@ -713,10 +741,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);
@ -727,6 +754,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 */
@ -740,12 +773,17 @@ evhttp_connection_done(struct evhttp_connection *evcon)
/*
* Handles reading from a chunked request.
* return 1: all data has been read
* return 0: more data is expected
* return -1: data is corrupted
* return ALL_DATA_READ:
* all data has been read
* return MORE_DATA_EXPECTED:
* more data is expected
* return DATA_CORRUPTED:
* data is corrupted
* return REQUEST_CANCLED:
* request was canceled by the user calling evhttp_cancel_request
*/
static int
static enum message_read_status
evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf)
{
int len;
@ -768,18 +806,18 @@ evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf)
free(p);
if (error) {
/* could not get chunk size */
return (-1);
return (DATA_CORRUPTED);
}
if (req->ntoread == 0) {
/* Last chunk */
return (1);
return (ALL_DATA_READ);
}
continue;
}
/* don't have enough to complete a chunk; wait for more */
if (len < req->ntoread)
return (0);
return (MORE_DATA_EXPECTED);
/* Completed chunk */
evbuffer_add(req->input_buffer,
@ -793,7 +831,28 @@ evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf)
}
}
return (0);
return (MORE_DATA_EXPECTED);
}
static void
evhttp_read_trailer(struct evhttp_connection *evcon, struct evhttp_request *req)
{
struct evbuffer *buf = evcon->input_buffer;
switch (evhttp_parse_headers(req, buf)) {
case DATA_CORRUPTED:
evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER);
break;
case ALL_DATA_READ:
event_del(&evcon->ev);
evhttp_connection_done(evcon);
break;
case MORE_DATA_EXPECTED:
default:
evhttp_add_event(&evcon->ev, evcon->timeout,
HTTP_READ_TIMEOUT);
break;
}
}
static void
@ -802,16 +861,24 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req)
struct evbuffer *buf = evcon->input_buffer;
if (req->chunked) {
int res = evhttp_handle_chunked_read(req, buf);
if (res == 1) {
switch (evhttp_handle_chunked_read(req, buf)) {
case ALL_DATA_READ:
/* finished last chunk */
evhttp_connection_done(evcon);
evcon->state = EVCON_READING_TRAILER;
evhttp_read_trailer(evcon, req);
return;
} else if (res == -1) {
case DATA_CORRUPTED:
/* corrupted data */
evhttp_connection_fail(evcon,
EVCON_HTTP_INVALID_HEADER);
return;
case REQUEST_CANCELED:
/* request canceled */
evhttp_request_free(req);
return;
case MORE_DATA_EXPECTED:
default:
break;
}
} else if (req->ntoread < 0) {
/* Read until connection close. */
@ -863,7 +930,28 @@ evhttp_read(int fd, short what, void *arg)
evhttp_connection_done(evcon);
return;
}
evhttp_read_body(evcon, req);
switch (evcon->state) {
case EVCON_READING_FIRSTLINE:
evhttp_read_firstline(evcon, req);
break;
case EVCON_READING_HEADERS:
evhttp_read_header(evcon, req);
break;
case EVCON_READING_BODY:
evhttp_read_body(evcon, req);
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
@ -873,6 +961,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;
@ -890,7 +980,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);
}
@ -954,7 +1044,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);
@ -971,16 +1063,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;
}
static void
@ -1057,7 +1146,7 @@ evhttp_connectioncb(int fd, short what, void *arg)
/* Reset the retry count as we were successful in connecting */
evcon->retry_cnt = 0;
evcon->state = EVCON_CONNECTED;
evcon->state = EVCON_IDLE;
/* try to start requests that have queued up on this connection */
evhttp_request_dispatch(evcon);
@ -1295,63 +1384,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_readline(buffer);
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;
}
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_readline(buffer)) != NULL) {
while ((line = evbuffer_readline(buffer))
!= NULL) {
char *skey, *svalue;
if (*line == '\0') { /* Last header - Done */
done = 1;
free (line);
status = ALL_DATA_READ;
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;
free (line);
free(line);
}
return (done);
return (status);
error:
free (line);
return (-1);
free(line);
return (DATA_CORRUPTED);
}
static int
@ -1385,7 +1485,7 @@ evhttp_get_body_length(struct evhttp_request *req)
}
}
event_debug(("%s: bytes to read: %d (in buffer %d)\n",
event_debug(("%s: bytes to read: %lld (in buffer %ld)\n",
__func__, req->ntoread,
EVBUFFER_LENGTH(req->evcon->input_buffer)));
@ -1402,6 +1502,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;
@ -1416,38 +1517,42 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req)
evhttp_read_body(evcon, req);
}
void
evhttp_read_header(int fd, short what, void *arg)
static void
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 n, res;
enum message_read_status res;
if (what == EV_TIMEOUT) {
event_debug(("%s: timeout on %d\n", __func__, fd));
evhttp_connection_fail(evcon, EVCON_HTTP_TIMEOUT);
res = evhttp_parse_firstline(req, evcon->input_buffer);
if (res == DATA_CORRUPTED) {
/* Error while reading, terminate */
event_debug(("%s: bad header lines on %d\n",
__func__, evcon->fd));
evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER);
return;
} else if (res == MORE_DATA_EXPECTED) {
/* Need more header lines */
evhttp_add_event(&evcon->ev,
evcon->timeout, HTTP_READ_TIMEOUT);
return;
}
n = evbuffer_read(evcon->input_buffer, fd, -1);
if (n == 0) {
event_debug(("%s: no more data on %d", __func__, fd));
evhttp_connection_fail(evcon, EVCON_HTTP_EOF);
return;
}
if (n == -1) {
event_debug(("%s: bad read on %d", __func__, fd));
evhttp_connection_fail(evcon, EVCON_HTTP_EOF);
return;
}
evhttp_read_header(evcon, req);
}
res = evhttp_parse_lines(req, evcon->input_buffer);
if (res == -1) {
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_headers(req, evcon->input_buffer);
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 */
evhttp_add_event(&evcon->ev,
evcon->timeout, HTTP_READ_TIMEOUT);
@ -1640,7 +1745,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));
/*
@ -1665,10 +1770,11 @@ evhttp_start_read(struct evhttp_connection *evcon)
/* Set up an event to read the headers */
if (event_initialized(&evcon->ev))
event_del(&evcon->ev);
event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read_header, evcon);
event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read, evcon);
EVHTTP_BASE_SET(evcon, &evcon->ev);
evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_READ_TIMEOUT);
evcon->state = EVCON_READING_FIRSTLINE;
}
static void
@ -2322,7 +2428,7 @@ evhttp_request_set_chunked_cb(struct evhttp_request *req,
const char *
evhttp_request_uri(struct evhttp_request *req) {
if (req->uri == NULL)
event_debug(("%s: request %p has no uri\n", req));
event_debug(("%s: request %p has no uri\n", __func__, req));
return (req->uri);
}
@ -2351,7 +2457,7 @@ evhttp_get_request_connection(
evhttp_connection_set_base(evcon, http->base);
evcon->flags |= EVHTTP_CON_INCOMING;
evcon->state = EVCON_CONNECTED;
evcon->state = EVCON_READING_FIRSTLINE;
evcon->fd = fd;

View File

@ -67,6 +67,7 @@ static struct event_base *base;
void http_suite(void);
void http_basic_cb(struct evhttp_request *req, void *arg);
static void http_chunked_cb(struct evhttp_request *req, void *arg);
void http_post_cb(struct evhttp_request *req, void *arg);
void http_dispatcher_cb(struct evhttp_request *req, void *arg);
@ -91,6 +92,7 @@ http_setup(short *pport, struct event_base *base)
/* Register a callback for certain types of requests */
evhttp_set_cb(myhttp, "/test", http_basic_cb, NULL);
evhttp_set_cb(myhttp, "/chunked", http_chunked_cb, NULL);
evhttp_set_cb(myhttp, "/postit", http_post_cb, NULL);
evhttp_set_cb(myhttp, "/", http_dispatcher_cb, NULL);
@ -163,15 +165,23 @@ http_readcb(struct bufferevent *bev, void *arg)
if (evbuffer_find(bev->input,
(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, bev->input);
done = evhttp_parse_firstline(req, bev->input);
if (done != ALL_DATA_READ)
goto out;
done = evhttp_parse_headers(req, bev->input);
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)
@ -213,6 +223,55 @@ 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_trickle_cb(int fd, short events, void *arg)
{
struct evbuffer *evb = evbuffer_new();
struct chunk_req_state *state = arg;
struct timeval when = { 0, 0 };
evbuffer_add_printf(evb, "%s", CHUNKS[state->i]);
evhttp_send_reply_chunk(state->req, evb);
evbuffer_free(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);
}
}
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__));
memset(state, 0, sizeof(struct chunk_req_state));
state->req = req;
/* generate a chunked reply */
evhttp_send_reply_start(req, HTTP_OK, "Everything is fine");
/* 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
http_basic_test(void)
{
@ -937,6 +996,229 @@ http_base_test(void)
fprintf(stdout, "OK\n");
}
/*
* the server is going to reply with chunked data.
*/
static void
http_chunked_readcb(struct bufferevent *bev, void *arg)
{
/* nothing here */
}
static void
http_chunked_errorcb(struct bufferevent *bev, short what, void *arg)
{
if (!test_ok)
goto out;
test_ok = -1;
if ((what & EVBUFFER_EOF) != 0) {
struct evhttp_request *req = evhttp_request_new(NULL, NULL);
const char *header;
enum message_read_status done;
req->kind = EVHTTP_RESPONSE;
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;
header = evhttp_find_header(req->input_headers, "Transfer-Encoding");
if (header == NULL || strcmp(header, "chunked"))
goto out;
header = evhttp_find_header(req->input_headers, "Connection");
if (header == NULL || strcmp(header, "close"))
goto out;
header = evbuffer_readline(EVBUFFER_INPUT(bev));
if (header == NULL)
goto out;
/* 13 chars */
if (strcmp(header, "d"))
goto out;
free((char*)header);
if (strncmp((char *)EVBUFFER_DATA(EVBUFFER_INPUT(bev)),
"This is funny", 13))
goto out;
evbuffer_drain(EVBUFFER_INPUT(bev), 13 + 2);
header = evbuffer_readline(EVBUFFER_INPUT(bev));
if (header == NULL)
goto out;
/* 18 chars */
if (strcmp(header, "12"))
goto out;
free((char *)header);
if (strncmp((char *)EVBUFFER_DATA(EVBUFFER_INPUT(bev)),
"but not hilarious.", 18))
goto out;
evbuffer_drain(EVBUFFER_INPUT(bev), 18 + 2);
header = evbuffer_readline(EVBUFFER_INPUT(bev));
if (header == NULL)
goto out;
/* 8 chars */
if (strcmp(header, "8"))
goto out;
free((char *)header);
if (strncmp((char *)EVBUFFER_DATA(EVBUFFER_INPUT(bev)),
"bwv 1052.", 8))
goto out;
evbuffer_drain(EVBUFFER_INPUT(bev), 8 + 2);
header = evbuffer_readline(EVBUFFER_INPUT(bev));
if (header == NULL)
goto out;
/* 0 chars */
if (strcmp(header, "0"))
goto out;
free((char *)header);
test_ok = 2;
}
out:
event_loopexit(NULL);
}
static void
http_chunked_writecb(struct bufferevent *bev, void *arg)
{
if (EVBUFFER_LENGTH(EVBUFFER_OUTPUT(bev)) == 0) {
/* enable reading of the reply */
bufferevent_enable(bev, EV_READ);
test_ok++;
}
}
static void
http_chunked_request_done(struct evhttp_request *req, void *arg)
{
if (req->response_code != HTTP_OK) {
fprintf(stderr, "FAILED\n");
exit(1);
}
if (evhttp_find_header(req->input_headers,
"Transfer-Encoding") == NULL) {
fprintf(stderr, "FAILED\n");
exit(1);
}
if (EVBUFFER_LENGTH(req->input_buffer) != 13 + 18 + 8) {
fprintf(stderr, "FAILED\n");
exit(1);
}
if (strncmp((char *)EVBUFFER_DATA(req->input_buffer),
"This is funnybut not hilarious.bwv 1052",
13 + 18 + 8)) {
fprintf(stderr, "FAILED\n");
exit(1);
}
test_ok = 1;
event_loopexit(NULL);
}
static void
http_chunked_test(void)
{
struct bufferevent *bev;
int fd;
const char *http_request;
short port = -1;
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: ");
http = http_setup(&port, NULL);
fd = http_connect("127.0.0.1", port);
/* Stupid thing to send a request */
bev = bufferevent_new(fd,
http_chunked_readcb, http_chunked_writecb,
http_chunked_errorcb, NULL);
http_request =
"GET /chunked HTTP/1.1\r\n"
"Host: somehost\r\n"
"Connection: close\r\n"
"\r\n";
bufferevent_write(bev, http_request, strlen(http_request));
evutil_gettimeofday(&tv_start, NULL);
event_dispatch();
evutil_gettimeofday(&tv_end, NULL);
evutil_timersub(&tv_end, &tv_start, &tv_end);
if (tv_end.tv_sec >= 1) {
fprintf(stdout, "FAILED (time)\n");
exit (1);
}
if (test_ok != 2) {
fprintf(stdout, "FAILED\n");
exit(1);
}
/* now try again with the regular connection object */
evcon = evhttp_connection_new("127.0.0.1", port);
if (evcon == NULL) {
fprintf(stdout, "FAILED\n");
exit(1);
}
/* 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);
/* Add the information that we care about */
evhttp_add_header(req->output_headers, "Host", "somehost");
/* 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);
}
event_dispatch();
if (test_ok != 1) {
fprintf(stdout, "FAILED\n");
exit(1);
}
}
evhttp_connection_free(evcon);
evhttp_free(http);
fprintf(stdout, "OK\n");
}
void
http_suite(void)
{
@ -950,4 +1232,6 @@ http_suite(void)
http_failure_test();
http_highport_test();
http_dispatcher_test();
http_chunked_test();
}