mirror of
https://github.com/cuberite/libevent.git
synced 2025-09-08 11:53:00 -04:00
Merge remote branch 'chrisd/http_fixes2'
This commit is contained in:
commit
a12839b083
@ -126,6 +126,13 @@ struct evhttp_bound_socket {
|
||||
struct evconnlistener *listener;
|
||||
};
|
||||
|
||||
/* server alias list item. */
|
||||
struct evhttp_server_alias {
|
||||
TAILQ_ENTRY(evhttp_server_alias) next;
|
||||
|
||||
char *alias; /* the server alias. */
|
||||
};
|
||||
|
||||
struct evhttp {
|
||||
/* Next vhost, if this is a vhost. */
|
||||
TAILQ_ENTRY(evhttp) next_vhost;
|
||||
@ -140,6 +147,8 @@ struct evhttp {
|
||||
|
||||
TAILQ_HEAD(vhostsq, evhttp) virtualhosts;
|
||||
|
||||
TAILQ_HEAD(aliasq, evhttp_server_alias) aliases;
|
||||
|
||||
/* NULL if this server is not a vhost */
|
||||
char *vhost_pattern;
|
||||
|
||||
|
226
http.c
226
http.c
@ -191,6 +191,8 @@ 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,
|
||||
char *ret, int decode_plus);
|
||||
static int evhttp_find_vhost(struct evhttp *http, struct evhttp **outhttp,
|
||||
const char *hostname);
|
||||
|
||||
#ifndef _EVENT_HAVE_STRSEP
|
||||
/* strsep replacement for platforms that lack it. Only works if
|
||||
@ -628,6 +630,10 @@ evhttp_connection_incoming_fail(struct evhttp_request *req,
|
||||
mm_free(req->uri);
|
||||
req->uri = NULL;
|
||||
}
|
||||
if (req->uri_elems) {
|
||||
evhttp_uri_free(req->uri_elems);
|
||||
req->uri_elems = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* the callback needs to send a reply, once the reply has
|
||||
@ -1391,6 +1397,8 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line)
|
||||
char *method;
|
||||
char *uri;
|
||||
char *version;
|
||||
const char *hostname;
|
||||
const char *scheme;
|
||||
|
||||
/* Parse the request line */
|
||||
method = strsep(&line, " ");
|
||||
@ -1436,8 +1444,19 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line)
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/* determine if it's a proxy request */
|
||||
if (strlen(req->uri) > 0 && req->uri[0] != '/')
|
||||
if ((req->uri_elems = evhttp_uri_parse(req->uri)) == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If we have an absolute-URI, check to see if it is an http request
|
||||
for a known vhost or server alias. If we don't know about this
|
||||
host, we consider it a proxy request. */
|
||||
scheme = evhttp_uri_get_scheme(req->uri_elems);
|
||||
hostname = evhttp_uri_get_host(req->uri_elems);
|
||||
if (scheme && (!evutil_ascii_strcasecmp(scheme, "http") ||
|
||||
!evutil_ascii_strcasecmp(scheme, "https")) &&
|
||||
hostname &&
|
||||
!evhttp_find_vhost(req->evcon->http_server, NULL, hostname))
|
||||
req->flags |= EVHTTP_PROXY_REQUEST;
|
||||
|
||||
return (0);
|
||||
@ -2667,24 +2686,18 @@ evhttp_dispatch_callback(struct httpcbq *callbacks, struct evhttp_request *req)
|
||||
struct evhttp_cb *cb;
|
||||
size_t offset = 0;
|
||||
char *translated;
|
||||
|
||||
const char *path;
|
||||
|
||||
/* Test for different URLs */
|
||||
char *p = req->uri;
|
||||
while (*p != '\0' && *p != '?')
|
||||
++p;
|
||||
offset = (size_t)(p - req->uri);
|
||||
|
||||
path = evhttp_uri_get_path(req->uri_elems);
|
||||
offset = strlen(path);
|
||||
if ((translated = mm_malloc(offset + 1)) == NULL)
|
||||
return (NULL);
|
||||
offset = evhttp_decode_uri_internal(req->uri, offset,
|
||||
translated, 0 /* decode_plus */);
|
||||
evhttp_decode_uri_internal(path, offset, translated,
|
||||
0 /* decode_plus */);
|
||||
|
||||
TAILQ_FOREACH(cb, callbacks, next) {
|
||||
int res = 0;
|
||||
res = ((strncmp(cb->what, translated, offset) == 0) &&
|
||||
(cb->what[offset] == '\0'));
|
||||
|
||||
if (res) {
|
||||
if (!strcmp(cb->what, translated)) {
|
||||
mm_free(translated);
|
||||
return (cb);
|
||||
}
|
||||
@ -2725,6 +2738,78 @@ prefix_suffix_match(const char *pattern, const char *name, int ignorecase)
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
/*
|
||||
Search the vhost hierarchy beginning with http for a server alias
|
||||
matching hostname. If a match is found, and outhttp is non-null,
|
||||
outhttp is set to the matching http object and 1 is returned.
|
||||
*/
|
||||
|
||||
static int
|
||||
evhttp_find_alias(struct evhttp *http, struct evhttp **outhttp,
|
||||
const char *hostname)
|
||||
{
|
||||
struct evhttp_server_alias *alias;
|
||||
struct evhttp *vhost;
|
||||
|
||||
TAILQ_FOREACH(alias, &http->aliases, next) {
|
||||
/* XXX Do we need to handle IP addresses? */
|
||||
if (!evutil_ascii_strcasecmp(alias->alias, hostname)) {
|
||||
if (outhttp)
|
||||
*outhttp = http;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* XXX It might be good to avoid recursion here, but I don't
|
||||
see a way to do that w/o a list. */
|
||||
TAILQ_FOREACH(vhost, &http->virtualhosts, next_vhost) {
|
||||
if (evhttp_find_alias(vhost, outhttp, hostname))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Attempts to find the best http object to handle a request for a hostname.
|
||||
All aliases for the root http object and vhosts are searched for an exact
|
||||
match. Then, the vhost hierarchy is traversed again for a matching
|
||||
pattern.
|
||||
|
||||
If an alias or vhost is matched, 1 is returned, and outhttp, if non-null,
|
||||
is set with the best matching http object. If there are no matches, the
|
||||
root http object is stored in outhttp and 0 is returned.
|
||||
*/
|
||||
|
||||
static int
|
||||
evhttp_find_vhost(struct evhttp *http, struct evhttp **outhttp,
|
||||
const char *hostname)
|
||||
{
|
||||
struct evhttp *vhost;
|
||||
struct evhttp *oldhttp;
|
||||
int match_found = 0;
|
||||
|
||||
if (evhttp_find_alias(http, outhttp, hostname))
|
||||
return 1;
|
||||
|
||||
do {
|
||||
oldhttp = http;
|
||||
TAILQ_FOREACH(vhost, &http->virtualhosts, next_vhost) {
|
||||
if (prefix_suffix_match(vhost->vhost_pattern,
|
||||
hostname, 1 /* ignorecase */)) {
|
||||
http = vhost;
|
||||
match_found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (oldhttp != http);
|
||||
|
||||
if (outhttp)
|
||||
*outhttp = http;
|
||||
|
||||
return match_found;
|
||||
}
|
||||
|
||||
static void
|
||||
evhttp_handle_request(struct evhttp_request *req, void *arg)
|
||||
{
|
||||
@ -2748,16 +2833,9 @@ evhttp_handle_request(struct evhttp_request *req, void *arg)
|
||||
}
|
||||
|
||||
/* handle potential virtual hosts */
|
||||
hostname = evhttp_find_header(req->input_headers, "Host");
|
||||
hostname = evhttp_request_get_host(req);
|
||||
if (hostname != NULL) {
|
||||
struct evhttp *vhost;
|
||||
TAILQ_FOREACH(vhost, &http->virtualhosts, next_vhost) {
|
||||
if (prefix_suffix_match(vhost->vhost_pattern, hostname,
|
||||
1 /* ignorecase */)) {
|
||||
evhttp_handle_request(req, vhost);
|
||||
return;
|
||||
}
|
||||
}
|
||||
evhttp_find_vhost(http, &http, hostname);
|
||||
}
|
||||
|
||||
if ((cb = evhttp_dispatch_callback(&http->callbacks, req)) != NULL) {
|
||||
@ -2900,7 +2978,8 @@ evhttp_bind_listener(struct evhttp *http, struct evconnlistener *listener)
|
||||
return bound;
|
||||
}
|
||||
|
||||
evutil_socket_t evhttp_bound_socket_get_fd(struct evhttp_bound_socket *bound)
|
||||
evutil_socket_t
|
||||
evhttp_bound_socket_get_fd(struct evhttp_bound_socket *bound)
|
||||
{
|
||||
return evconnlistener_get_fd(bound->listener);
|
||||
}
|
||||
@ -2942,6 +3021,7 @@ evhttp_new_object(void)
|
||||
TAILQ_INIT(&http->callbacks);
|
||||
TAILQ_INIT(&http->connections);
|
||||
TAILQ_INIT(&http->virtualhosts);
|
||||
TAILQ_INIT(&http->aliases);
|
||||
|
||||
return (http);
|
||||
}
|
||||
@ -2980,6 +3060,7 @@ evhttp_free(struct evhttp* http)
|
||||
struct evhttp_connection *evcon;
|
||||
struct evhttp_bound_socket *bound;
|
||||
struct evhttp* vhost;
|
||||
struct evhttp_server_alias *alias;
|
||||
|
||||
/* Remove the accepting part */
|
||||
while ((bound = TAILQ_FIRST(&http->sockets)) != NULL) {
|
||||
@ -3010,6 +3091,12 @@ evhttp_free(struct evhttp* http)
|
||||
if (http->vhost_pattern != NULL)
|
||||
mm_free(http->vhost_pattern);
|
||||
|
||||
while ((alias = TAILQ_FIRST(&http->aliases)) != NULL) {
|
||||
TAILQ_REMOVE(&http->aliases, alias, next);
|
||||
mm_free(alias->alias);
|
||||
mm_free(alias);
|
||||
}
|
||||
|
||||
mm_free(http);
|
||||
}
|
||||
|
||||
@ -3045,6 +3132,43 @@ evhttp_remove_virtual_host(struct evhttp* http, struct evhttp* vhost)
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
evhttp_add_server_alias(struct evhttp *http, const char *alias)
|
||||
{
|
||||
struct evhttp_server_alias *evalias;
|
||||
|
||||
evalias = mm_calloc(1, sizeof(*evalias));
|
||||
if (!evalias)
|
||||
return -1;
|
||||
|
||||
evalias->alias = mm_strdup(alias);
|
||||
if (!evalias->alias) {
|
||||
mm_free(evalias);
|
||||
return -1;
|
||||
}
|
||||
|
||||
TAILQ_INSERT_TAIL(&http->aliases, evalias, next);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
evhttp_remove_server_alias(struct evhttp *http, const char *alias)
|
||||
{
|
||||
struct evhttp_server_alias *evalias;
|
||||
|
||||
TAILQ_FOREACH(evalias, &http->aliases, next) {
|
||||
if (evutil_ascii_strcasecmp(evalias->alias, alias) == 0) {
|
||||
TAILQ_REMOVE(&http->aliases, evalias, next);
|
||||
mm_free(evalias->alias);
|
||||
mm_free(evalias);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
evhttp_set_timeout(struct evhttp* http, int timeout_in_secs)
|
||||
{
|
||||
@ -3193,9 +3317,13 @@ evhttp_request_free(struct evhttp_request *req)
|
||||
mm_free(req->remote_host);
|
||||
if (req->uri != NULL)
|
||||
mm_free(req->uri);
|
||||
if (req->uri_elems != NULL)
|
||||
evhttp_uri_free(req->uri_elems);
|
||||
if (req->response_code_line != NULL)
|
||||
mm_free(req->response_code_line);
|
||||
|
||||
if (req->host_cache != NULL)
|
||||
mm_free(req->host_cache);
|
||||
|
||||
evhttp_clear_headers(req->input_headers);
|
||||
mm_free(req->input_headers);
|
||||
|
||||
@ -3253,6 +3381,52 @@ evhttp_request_get_uri(const struct evhttp_request *req) {
|
||||
return (req->uri);
|
||||
}
|
||||
|
||||
const struct evhttp_uri *
|
||||
evhttp_request_get_evhttp_uri(const struct evhttp_request *req) {
|
||||
if (req->uri_elems == NULL)
|
||||
event_debug(("%s: request %p has no uri elems\n",
|
||||
__func__, req));
|
||||
return (req->uri_elems);
|
||||
}
|
||||
|
||||
const char *
|
||||
evhttp_request_get_host(struct evhttp_request *req)
|
||||
{
|
||||
const char *host = NULL;
|
||||
|
||||
if (req->host_cache)
|
||||
return req->host_cache;
|
||||
|
||||
if (req->uri_elems)
|
||||
host = evhttp_uri_get_host(req->uri_elems);
|
||||
if (!host && req->input_headers) {
|
||||
const char *p;
|
||||
size_t len;
|
||||
|
||||
host = evhttp_find_header(req->input_headers, "Host");
|
||||
/* The Host: header may include a port. Remove it here
|
||||
to be consistent with uri_elems case above. */
|
||||
if (host) {
|
||||
p = host + strlen(host) - 1;
|
||||
while (p > host && EVUTIL_ISDIGIT(*p))
|
||||
--p;
|
||||
if (p > host && *p == ':') {
|
||||
len = p - host;
|
||||
req->host_cache = mm_malloc(len + 1);
|
||||
if (!req->host_cache) {
|
||||
event_warn("%s: malloc", __func__);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(req->host_cache, host, len);
|
||||
req->host_cache[len] = '\0';
|
||||
host = req->host_cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
enum evhttp_cmd_type
|
||||
evhttp_request_get_command(const struct evhttp_request *req) {
|
||||
return (req->type);
|
||||
|
@ -263,6 +263,25 @@ int evhttp_add_virtual_host(struct evhttp* http, const char *pattern,
|
||||
*/
|
||||
int evhttp_remove_virtual_host(struct evhttp* http, struct evhttp* vhost);
|
||||
|
||||
/**
|
||||
Add a server alias to an http object. The http object can be a virtual
|
||||
host or the main server.
|
||||
|
||||
@param http the evhttp object
|
||||
@param alias the alias to add
|
||||
@see evhttp_add_remove_alias()
|
||||
*/
|
||||
int evhttp_add_server_alias(struct evhttp *http, const char *alias);
|
||||
|
||||
/**
|
||||
Remove a server alias from an http object.
|
||||
|
||||
@param http the evhttp object
|
||||
@param alias the alias to remove
|
||||
@see evhttp_add_server_alias()
|
||||
*/
|
||||
int evhttp_remove_server_alias(struct evhttp *http, const char *alias);
|
||||
|
||||
/**
|
||||
* Set the timeout for an HTTP request.
|
||||
*
|
||||
@ -492,8 +511,15 @@ int evhttp_make_request(struct evhttp_connection *evcon,
|
||||
*/
|
||||
void evhttp_cancel_request(struct evhttp_request *req);
|
||||
|
||||
/**
|
||||
* A structure to hold a parsed URI or Relative-Ref conforming to RFC3986.
|
||||
*/
|
||||
struct evhttp_uri;
|
||||
|
||||
/** Returns the request URI */
|
||||
const char *evhttp_request_get_uri(const struct evhttp_request *req);
|
||||
/** Returns the request URI (parsed) */
|
||||
const struct evhttp_uri *evhttp_request_get_evhttp_uri(const struct evhttp_request *req);
|
||||
/** Returns the request command */
|
||||
enum evhttp_cmd_type evhttp_request_get_command(const struct evhttp_request *req);
|
||||
|
||||
@ -507,6 +533,11 @@ struct evkeyvalq *evhttp_request_get_output_headers(struct evhttp_request *req);
|
||||
struct evbuffer *evhttp_request_get_input_buffer(struct evhttp_request *req);
|
||||
/** Returns the output buffer */
|
||||
struct evbuffer *evhttp_request_get_output_buffer(struct evhttp_request *req);
|
||||
/** Returns the host associated with the request. If a client sends an absolute
|
||||
URI, the host part of that is preferred. Otherwise, the input headers are
|
||||
searched for a Host: header. NULL is returned if no absolute URI or Host:
|
||||
header is provided. */
|
||||
const char *evhttp_request_get_host(struct evhttp_request *req);
|
||||
|
||||
/* Interfaces for dealing with HTTP headers */
|
||||
|
||||
@ -669,11 +700,6 @@ int evhttp_parse_query_str(const char *uri, struct evkeyvalq *headers);
|
||||
*/
|
||||
char *evhttp_htmlescape(const char *html);
|
||||
|
||||
/**
|
||||
* A structure to hold a parsed URI or Relative-Ref conforming to RFC3986.
|
||||
*/
|
||||
struct evhttp_uri;
|
||||
|
||||
/**
|
||||
* Return a new empty evhttp_uri with no fields set.
|
||||
*/
|
||||
|
@ -85,6 +85,9 @@ struct {
|
||||
char *remote_host;
|
||||
ev_uint16_t remote_port;
|
||||
|
||||
/* cache of the hostname for evhttp_request_get_host */
|
||||
char *host_cache;
|
||||
|
||||
enum evhttp_request_kind kind;
|
||||
enum evhttp_cmd_type type;
|
||||
|
||||
@ -92,6 +95,7 @@ struct {
|
||||
size_t body_size;
|
||||
|
||||
char *uri; /* uri after HTTP request was parsed */
|
||||
struct evhttp_uri *uri_elems; /* uri elements */
|
||||
|
||||
char major; /* HTTP Major number */
|
||||
char minor; /* HTTP Minor number */
|
||||
|
@ -431,12 +431,32 @@ http_basic_test(void *arg)
|
||||
|
||||
event_base_dispatch(data->base);
|
||||
|
||||
tt_assert(test_ok == 5);
|
||||
|
||||
/* Connect to the second port again. This time, send an absolute uri. */
|
||||
bufferevent_free(bev);
|
||||
evutil_closesocket(fd);
|
||||
|
||||
evhttp_free(http);
|
||||
fd = http_connect("127.0.0.1", port2);
|
||||
|
||||
tt_assert(test_ok == 5);
|
||||
/* Stupid thing to send a request */
|
||||
bev = bufferevent_socket_new(data->base, fd, 0);
|
||||
bufferevent_setcb(bev, http_readcb, http_writecb,
|
||||
http_errorcb, data->base);
|
||||
|
||||
http_request =
|
||||
"GET http://somehost.net/test HTTP/1.1\r\n"
|
||||
"Host: somehost\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
|
||||
bufferevent_write(bev, http_request, strlen(http_request));
|
||||
|
||||
event_base_dispatch(data->base);
|
||||
|
||||
tt_assert(test_ok == 7);
|
||||
|
||||
evhttp_free(http);
|
||||
end:
|
||||
;
|
||||
}
|
||||
@ -1163,6 +1183,9 @@ http_virtual_host_test(void *arg)
|
||||
struct evhttp_connection *evcon = NULL;
|
||||
struct evhttp_request *req = NULL;
|
||||
struct evhttp *second = NULL, *third = NULL;
|
||||
evutil_socket_t fd;
|
||||
struct bufferevent *bev;
|
||||
const char *http_request;
|
||||
|
||||
exit_base = data->base;
|
||||
|
||||
@ -1182,6 +1205,10 @@ http_virtual_host_test(void *arg)
|
||||
tt_abort_msg("Couldn't add wildcarded vhost");
|
||||
}
|
||||
|
||||
/* add some aliases to the vhosts */
|
||||
tt_assert(evhttp_add_server_alias(second, "manolito.info") == 0);
|
||||
tt_assert(evhttp_add_server_alias(third, "bonkers.org") == 0);
|
||||
|
||||
evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port);
|
||||
tt_assert(evcon);
|
||||
|
||||
@ -1238,6 +1265,68 @@ http_virtual_host_test(void *arg)
|
||||
|
||||
tt_assert(test_ok == 1)
|
||||
|
||||
test_ok = 0;
|
||||
|
||||
/* make a request with the right host and expect a response */
|
||||
req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
|
||||
|
||||
/* Add the information that we care about */
|
||||
evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "manolito.info");
|
||||
|
||||
/* We give ownership of the request to the connection */
|
||||
if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET,
|
||||
"/funnybunny") == -1) {
|
||||
tt_abort_msg("Couldn't make request");
|
||||
}
|
||||
|
||||
event_base_dispatch(data->base);
|
||||
|
||||
tt_assert(test_ok == 1)
|
||||
|
||||
test_ok = 0;
|
||||
|
||||
/* make a request with the right host and expect a response */
|
||||
req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
|
||||
|
||||
/* Add the Host header. This time with the optional port. */
|
||||
evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "bonkers.org:8000");
|
||||
|
||||
/* We give ownership of the request to the connection */
|
||||
if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET,
|
||||
"/blackcoffee") == -1) {
|
||||
tt_abort_msg("Couldn't make request");
|
||||
}
|
||||
|
||||
event_base_dispatch(data->base);
|
||||
|
||||
tt_assert(test_ok == 1)
|
||||
|
||||
test_ok = 0;
|
||||
|
||||
/* Now make a raw request with an absolute URI. */
|
||||
fd = http_connect("127.0.0.1", port);
|
||||
|
||||
/* Stupid thing to send a request */
|
||||
bev = bufferevent_socket_new(data->base, fd, 0);
|
||||
bufferevent_setcb(bev, http_readcb, http_writecb,
|
||||
http_errorcb, NULL);
|
||||
|
||||
/* The host in the URI should override the Host: header */
|
||||
http_request =
|
||||
"GET http://manolito.info/funnybunny HTTP/1.1\r\n"
|
||||
"Host: somehost\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
|
||||
bufferevent_write(bev, http_request, strlen(http_request));
|
||||
|
||||
event_base_dispatch(data->base);
|
||||
|
||||
tt_int_op(test_ok, ==, 2);
|
||||
|
||||
bufferevent_free(bev);
|
||||
evutil_closesocket(fd);
|
||||
|
||||
end:
|
||||
if (evcon)
|
||||
evhttp_connection_free(evcon);
|
||||
|
Loading…
x
Reference in New Issue
Block a user