mirror of
https://github.com/cuberite/libevent.git
synced 2025-09-07 11:20:16 -04:00
Implement size limits on HTTP header length and body length.
Patch from Constantine Verutin, simplified a little. svn:r1500
This commit is contained in:
parent
86db1c851b
commit
47bad8abb7
@ -364,6 +364,7 @@ AC_CHECK_SIZEOF(long long)
|
||||
AC_CHECK_SIZEOF(long)
|
||||
AC_CHECK_SIZEOF(int)
|
||||
AC_CHECK_SIZEOF(short)
|
||||
AC_CHECK_SIZEOF(size_t)
|
||||
|
||||
AC_CHECK_TYPES([struct in6_addr, struct sockaddr_in6, sa_family_t], , ,
|
||||
[#include <sys/types.h>
|
||||
|
@ -24,7 +24,8 @@ enum message_read_status {
|
||||
ALL_DATA_READ = 1,
|
||||
MORE_DATA_EXPECTED = 0,
|
||||
DATA_CORRUPTED = -1,
|
||||
REQUEST_CANCELED = -2
|
||||
REQUEST_CANCELED = -2,
|
||||
DATA_TOO_LONG = -3
|
||||
};
|
||||
|
||||
enum evhttp_connection_error {
|
||||
@ -70,6 +71,9 @@ struct evhttp_connection {
|
||||
char *address; /* address to connect to */
|
||||
u_short port;
|
||||
|
||||
size_t max_headers_size;
|
||||
uint64_t max_body_size;
|
||||
|
||||
int flags;
|
||||
#define EVHTTP_CON_INCOMING 0x0001 /* only one request on it ever */
|
||||
#define EVHTTP_CON_OUTGOING 0x0002 /* multiple requests possible */
|
||||
@ -127,8 +131,11 @@ struct evhttp {
|
||||
/* NULL if this server is not a vhost */
|
||||
char *vhost_pattern;
|
||||
|
||||
int timeout;
|
||||
int timeout;
|
||||
|
||||
size_t default_max_headers_size;
|
||||
size_t default_max_body_size;
|
||||
|
||||
void (*gencb)(struct evhttp_request *req, void *);
|
||||
void *gencbarg;
|
||||
|
||||
|
102
http.c
102
http.c
@ -557,6 +557,25 @@ evhttp_make_header(struct evhttp_connection *evcon, struct evhttp_request *req)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
evhttp_connection_set_max_headers_size(struct evhttp_connection *evcon,
|
||||
ev_ssize_t new_max_headers_size)
|
||||
{
|
||||
if (new_max_headers_size<0)
|
||||
evcon->max_headers_size = EV_SIZE_MAX;
|
||||
else
|
||||
evcon->max_headers_size = new_max_headers_size;
|
||||
}
|
||||
void
|
||||
evhttp_connection_set_max_body_size(struct evhttp_connection* evcon,
|
||||
ev_ssize_t new_max_body_size)
|
||||
{
|
||||
if (new_max_body_size<0)
|
||||
evcon->max_body_size = EV_UINT64_MAX;
|
||||
else
|
||||
evcon->max_body_size = new_max_body_size;
|
||||
}
|
||||
|
||||
static int
|
||||
evhttp_connection_incoming_fail(struct evhttp_request *req,
|
||||
enum evhttp_connection_error error)
|
||||
@ -730,6 +749,8 @@ evhttp_connection_done(struct evhttp_connection *evcon)
|
||||
* data is corrupted
|
||||
* return REQUEST_CANCELED:
|
||||
* request was canceled by the user calling evhttp_cancel_request
|
||||
* return DATA_TOO_LONG:
|
||||
* ran over the maximum limit
|
||||
*/
|
||||
|
||||
static enum message_read_status
|
||||
@ -760,6 +781,12 @@ evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf)
|
||||
/* could not get chunk size */
|
||||
return (DATA_CORRUPTED);
|
||||
}
|
||||
if (req->body_size + ntoread > req->evcon->max_body_size) {
|
||||
/* failed body length test */
|
||||
event_debug(("Request body is too long"));
|
||||
return (DATA_TOO_LONG);
|
||||
}
|
||||
req->body_size += ntoread;
|
||||
req->ntoread = ntoread;
|
||||
if (req->ntoread == 0) {
|
||||
/* Last chunk */
|
||||
@ -798,6 +825,7 @@ evhttp_read_trailer(struct evhttp_connection *evcon, struct evhttp_request *req)
|
||||
|
||||
switch (evhttp_parse_headers(req, buf)) {
|
||||
case DATA_CORRUPTED:
|
||||
case DATA_TOO_LONG:
|
||||
evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER);
|
||||
break;
|
||||
case ALL_DATA_READ:
|
||||
@ -825,6 +853,7 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req)
|
||||
evhttp_read_trailer(evcon, req);
|
||||
return;
|
||||
case DATA_CORRUPTED:
|
||||
case DATA_TOO_LONG:/*separate error for this? XXX */
|
||||
/* corrupted data */
|
||||
evhttp_connection_fail(evcon,
|
||||
EVCON_HTTP_INVALID_HEADER);
|
||||
@ -840,14 +869,24 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req)
|
||||
} else if (req->ntoread < 0) {
|
||||
/* Read until connection close. */
|
||||
evbuffer_add_buffer(req->input_buffer, buf);
|
||||
req->body_size += evbuffer_get_length(buf);
|
||||
} else if (req->chunk_cb != NULL ||
|
||||
evbuffer_get_length(buf) >= req->ntoread) {
|
||||
/* We've postponed moving the data until now, but we're
|
||||
* about to use it. */
|
||||
req->ntoread -= evbuffer_get_length(buf);
|
||||
req->body_size += evbuffer_get_length(buf);
|
||||
evbuffer_add_buffer(req->input_buffer, buf);
|
||||
}
|
||||
|
||||
if (req->body_size > req->evcon->max_body_size) {
|
||||
/* failed body length test */
|
||||
event_debug(("Request body is too long"));
|
||||
evhttp_connection_fail(evcon,
|
||||
EVCON_HTTP_INVALID_HEADER);
|
||||
return;
|
||||
}
|
||||
|
||||
if (evbuffer_get_length(req->input_buffer) > 0 && req->chunk_cb != NULL) {
|
||||
req->flags |= EVHTTP_REQ_DEFER_FREE;
|
||||
(*req->chunk_cb)(req, req->cb_arg);
|
||||
@ -1452,9 +1491,24 @@ evhttp_parse_firstline(struct evhttp_request *req, struct evbuffer *buffer)
|
||||
char *line;
|
||||
enum message_read_status status = ALL_DATA_READ;
|
||||
|
||||
line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_CRLF);
|
||||
if (line == NULL)
|
||||
return (MORE_DATA_EXPECTED);
|
||||
size_t line_length;
|
||||
/* XXX try */
|
||||
line = evbuffer_readln(buffer, &line_length, EVBUFFER_EOL_CRLF);
|
||||
if (line == NULL) {
|
||||
if (req->evcon != NULL &&
|
||||
evbuffer_get_length(buffer) > req->evcon->max_headers_size)
|
||||
return (DATA_TOO_LONG);
|
||||
else
|
||||
return (MORE_DATA_EXPECTED);
|
||||
}
|
||||
|
||||
if (req->evcon != NULL &&
|
||||
line_length > req->evcon->max_headers_size) {
|
||||
mm_free(line);
|
||||
return (DATA_TOO_LONG);
|
||||
}
|
||||
|
||||
req->headers_size = line_length;
|
||||
|
||||
switch (req->kind) {
|
||||
case EVHTTP_REQUEST:
|
||||
@ -1499,14 +1553,24 @@ evhttp_append_to_last_header(struct evkeyvalq *headers, const char *line)
|
||||
enum message_read_status
|
||||
evhttp_parse_headers(struct evhttp_request *req, struct evbuffer* buffer)
|
||||
{
|
||||
enum message_read_status errcode = DATA_CORRUPTED;
|
||||
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))
|
||||
size_t line_length;
|
||||
while ((line = evbuffer_readln(buffer, &line_length, EVBUFFER_EOL_CRLF))
|
||||
!= NULL) {
|
||||
char *skey, *svalue;
|
||||
|
||||
req->headers_size += line_length;
|
||||
|
||||
if (req->evcon != NULL &&
|
||||
req->headers_size > req->evcon->max_headers_size) {
|
||||
errcode = DATA_TOO_LONG;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (*line == '\0') { /* Last header - Done */
|
||||
status = ALL_DATA_READ;
|
||||
mm_free(line);
|
||||
@ -1535,11 +1599,16 @@ evhttp_parse_headers(struct evhttp_request *req, struct evbuffer* buffer)
|
||||
mm_free(line);
|
||||
}
|
||||
|
||||
if (status == MORE_DATA_EXPECTED) {
|
||||
if (req->headers_size + evbuffer_get_length(buffer) > req->evcon->max_headers_size)
|
||||
return (DATA_TOO_LONG);
|
||||
}
|
||||
|
||||
return (status);
|
||||
|
||||
error:
|
||||
mm_free(line);
|
||||
return (DATA_CORRUPTED);
|
||||
return (errcode);
|
||||
}
|
||||
|
||||
static int
|
||||
@ -1615,7 +1684,7 @@ evhttp_read_firstline(struct evhttp_connection *evcon,
|
||||
enum message_read_status res;
|
||||
|
||||
res = evhttp_parse_firstline(req, bufferevent_get_input(evcon->bufev));
|
||||
if (res == DATA_CORRUPTED) {
|
||||
if (res == DATA_CORRUPTED || res == DATA_TOO_LONG) {
|
||||
/* Error while reading, terminate */
|
||||
event_debug(("%s: bad header lines on %d\n",
|
||||
__func__, evcon->fd));
|
||||
@ -1638,7 +1707,7 @@ evhttp_read_header(struct evhttp_connection *evcon,
|
||||
int fd = evcon->fd;
|
||||
|
||||
res = evhttp_parse_headers(req, bufferevent_get_input(evcon->bufev));
|
||||
if (res == DATA_CORRUPTED) {
|
||||
if (res == DATA_CORRUPTED || res == DATA_TOO_LONG) {
|
||||
/* Error while reading, terminate */
|
||||
event_debug(("%s: bad header lines on %d\n", __func__, fd));
|
||||
evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER);
|
||||
@ -1714,6 +1783,9 @@ evhttp_connection_base_new(struct event_base *base,
|
||||
evcon->fd = -1;
|
||||
evcon->port = port;
|
||||
|
||||
evcon->max_headers_size = EV_SIZE_MAX;
|
||||
evcon->max_body_size = EV_SIZE_MAX;
|
||||
|
||||
evcon->timeout = -1;
|
||||
evcon->retry_cnt = evcon->retry_max = 0;
|
||||
|
||||
@ -2481,6 +2553,8 @@ evhttp_new_object(void)
|
||||
}
|
||||
|
||||
http->timeout = -1;
|
||||
evhttp_set_max_headers_size(http, EV_SIZE_MAX);
|
||||
evhttp_set_max_body_size(http, EV_SIZE_MAX);
|
||||
|
||||
TAILQ_INIT(&http->sockets);
|
||||
TAILQ_INIT(&http->callbacks);
|
||||
@ -2598,6 +2672,14 @@ evhttp_set_timeout(struct evhttp* http, int timeout_in_secs)
|
||||
http->timeout = timeout_in_secs;
|
||||
}
|
||||
|
||||
void evhttp_set_max_headers_size(struct evhttp* http, ssize_t max_headers_size) {
|
||||
http->default_max_headers_size = max_headers_size;
|
||||
}
|
||||
|
||||
void evhttp_set_max_body_size(struct evhttp* http, ssize_t max_body_size) {
|
||||
http->default_max_body_size = max_body_size;
|
||||
}
|
||||
|
||||
int
|
||||
evhttp_set_cb(struct evhttp *http, const char *uri,
|
||||
void (*cb)(struct evhttp_request *, void *), void *cbarg)
|
||||
@ -2663,6 +2745,9 @@ evhttp_request_new(void (*cb)(struct evhttp_request *, void *), void *arg)
|
||||
goto error;
|
||||
}
|
||||
|
||||
req->headers_size = 0;
|
||||
req->body_size = 0;
|
||||
|
||||
req->kind = EVHTTP_RESPONSE;
|
||||
req->input_headers = mm_calloc(1, sizeof(struct evkeyvalq));
|
||||
if (req->input_headers == NULL) {
|
||||
@ -2815,6 +2900,9 @@ evhttp_get_request_connection(
|
||||
if (evcon == NULL)
|
||||
return (NULL);
|
||||
|
||||
evhttp_connection_set_max_headers_size(evcon, http->default_max_headers_size);
|
||||
evhttp_connection_set_max_body_size(evcon, http->default_max_body_size);
|
||||
|
||||
evcon->flags |= EVHTTP_CON_INCOMING;
|
||||
evcon->state = EVCON_READING_FIRSTLINE;
|
||||
|
||||
|
@ -172,6 +172,11 @@ evutil_socket_t evhttp_bound_socket_get_fd(struct evhttp_bound_socket *bound_soc
|
||||
*/
|
||||
void evhttp_free(struct evhttp* http);
|
||||
|
||||
/** XXX Document. */
|
||||
void evhttp_set_max_headers_size(struct evhttp* http, ev_ssize_t max_headers_size);
|
||||
/** XXX Document. */
|
||||
void evhttp_set_max_body_size(struct evhttp* http, ev_ssize_t max_body_size);
|
||||
|
||||
/**
|
||||
Set a callback for a specified URI
|
||||
|
||||
@ -366,6 +371,12 @@ void evhttp_request_own(struct evhttp_request *req);
|
||||
/** Returns 1 if the request is owned by the user */
|
||||
int evhttp_request_is_owned(struct evhttp_request *req);
|
||||
|
||||
void evhttp_connection_set_max_headers_size(struct evhttp_connection *evcon,
|
||||
ssize_t new_max_headers_size);
|
||||
|
||||
void evhttp_connection_set_max_body_size(struct evhttp_connection* evcon,
|
||||
ssize_t new_max_body_size);
|
||||
|
||||
/** Frees an http connection */
|
||||
void evhttp_connection_free(struct evhttp_connection *evcon);
|
||||
|
||||
|
@ -88,6 +88,9 @@ struct {
|
||||
enum evhttp_request_kind kind;
|
||||
enum evhttp_cmd_type type;
|
||||
|
||||
size_t headers_size;
|
||||
size_t body_size;
|
||||
|
||||
char *uri; /* uri after HTTP request was parsed */
|
||||
|
||||
char major; /* HTTP Major number */
|
||||
|
@ -2190,6 +2190,81 @@ http_negative_content_length_test(void)
|
||||
evhttp_free(http);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
http_data_length_constraints_test_done(struct evhttp_request *req, void *arg)
|
||||
{
|
||||
tt_assert(req);
|
||||
tt_int_op(req->response_code, ==, HTTP_BADREQUEST);
|
||||
end:
|
||||
event_loopexit(NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
http_data_length_constraints_test(void)
|
||||
{
|
||||
short port = -1;
|
||||
struct evhttp_connection *evcon = NULL;
|
||||
struct evhttp_request *req = NULL;
|
||||
char long_str[8192];
|
||||
|
||||
test_ok = 0;
|
||||
|
||||
http = http_setup(&port, NULL);
|
||||
|
||||
evcon = evhttp_connection_new("127.0.0.1", port);
|
||||
tt_assert(evcon);
|
||||
|
||||
/* also bind to local host */
|
||||
evhttp_connection_set_local_address(evcon, "127.0.0.1");
|
||||
|
||||
/*
|
||||
* At this point, we want to schedule an HTTP GET request
|
||||
* server using our make request method.
|
||||
*/
|
||||
|
||||
req = evhttp_request_new(http_data_length_constraints_test_done, NULL);
|
||||
tt_assert(req);
|
||||
|
||||
memset(long_str, 'a', 8192);
|
||||
long_str[8191] = '\0';
|
||||
/* Add the information that we care about */
|
||||
evhttp_set_max_headers_size(http, 8191);
|
||||
evhttp_add_header(req->output_headers, "Host", "somehost");
|
||||
evhttp_add_header(req->output_headers, "Longheader", long_str);
|
||||
|
||||
if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/?arg=val") == -1) {
|
||||
tt_abort_msg("Couldn't make request");
|
||||
}
|
||||
event_dispatch();
|
||||
|
||||
req = evhttp_request_new(http_data_length_constraints_test_done, NULL);
|
||||
tt_assert(req);
|
||||
evhttp_add_header(req->output_headers, "Host", "somehost");
|
||||
|
||||
/* GET /?arg=verylongvalue HTTP/1.1 */
|
||||
if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, long_str) == -1) {
|
||||
tt_abort_msg("Couldn't make request");
|
||||
}
|
||||
event_dispatch();
|
||||
|
||||
evhttp_set_max_body_size(http, 8190);
|
||||
req = evhttp_request_new(http_data_length_constraints_test_done, NULL);
|
||||
evhttp_add_header(req->output_headers, "Host", "somehost");
|
||||
evbuffer_add_printf(req->output_buffer, long_str);
|
||||
if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) {
|
||||
tt_abort_msg("Couldn't make request");
|
||||
}
|
||||
event_dispatch();
|
||||
|
||||
test_ok = 1;
|
||||
end:
|
||||
if (evcon)
|
||||
evhttp_connection_free(evcon);
|
||||
if (http)
|
||||
evhttp_free(http);
|
||||
}
|
||||
|
||||
#define HTTP_LEGACY(name) \
|
||||
{ #name, run_legacy_test_fn, TT_ISOLATED|TT_LEGACY, &legacy_setup, \
|
||||
http_##name##_test }
|
||||
@ -2224,6 +2299,7 @@ struct testcase_t http_testcases[] = {
|
||||
HTTP_LEGACY(stream_in_cancel),
|
||||
|
||||
HTTP_LEGACY(connection_retry),
|
||||
HTTP_LEGACY(data_length_constraints),
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
@ -36,6 +36,7 @@
|
||||
#ifdef _EVENT_HAVE_SYS_SOCKET_H
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
#include "event2/util.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@ -167,6 +168,41 @@ int evutil_resolve(int family, const char *hostname, struct sockaddr *sa,
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#ifdef UINT64_MAX
|
||||
#define EV_UINT64_MAX UINT64_MAX
|
||||
#elif defined(WIN32)
|
||||
#define EV_UINT64_MAX 0xffffffffffffffffui64
|
||||
#elif _EVENT_SIZEOF_LONG_LONG == 8
|
||||
#define EV_UINT64_MAX 0xffffffffffffffffull
|
||||
#elif _EVENT_SIZEOF_LONG == 8
|
||||
#define EV_UINT64_MAX 0xfffffffffffffffful
|
||||
#else
|
||||
/* Hope for a two's complement representation */
|
||||
#define EV_UINT64_MAX ((ev_uint64_t)-1)
|
||||
#endif
|
||||
|
||||
#ifdef UINT32_MAX
|
||||
#define EV_UINT32_MAX UINT32_MAX
|
||||
#elif defined(WIN32)
|
||||
#define EV_UINT32_MAX 0xffffffffui64
|
||||
#elif _EVENT_SIZEOF_INT == 4
|
||||
#define EV_UINT32_MAX 0xffffffffu
|
||||
#elif _EVENT_SIZEOF_LONG == 4
|
||||
#define EV_UINT32_MAX 0xfffffffful
|
||||
#else
|
||||
/* Hope for a two's complement representation */
|
||||
#define EV_UINT32_MAX ((ev_uint32_t)-1)
|
||||
#endif
|
||||
|
||||
#if _EVENT_SIZEOF_SIZE_T == 8
|
||||
#define EV_SIZE_MAX EV_UINT64_MAX
|
||||
#elif _EVENT_SIZEOF_SIZE_T == 4
|
||||
#define EV_SIZE_MAX EV_UINT32_MAX
|
||||
#else
|
||||
/* Hope for a two's complement representation */
|
||||
#define EV_SIZE_MAX ((size_t)-1)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user