mirror of
https://github.com/libevent/libevent.git
synced 2025-01-31 09:12:55 +08:00
Add evhttp server alias interface, correct flagging of proxy requests.
evhttp needs to be mindful of all hostnames and addresses that clients use to contact the main server and vhosts to know the difference between proxy requests and non-proxy requests.
This commit is contained in:
parent
2e5a175bf3
commit
aab8c38b76
@ -126,6 +126,11 @@ struct evhttp_bound_socket {
|
||||
struct evconnlistener *listener;
|
||||
};
|
||||
|
||||
struct evhttp_server_alias {
|
||||
TAILQ_ENTRY(evhttp_server_alias) next;
|
||||
char *alias;
|
||||
};
|
||||
|
||||
struct evhttp {
|
||||
/* Next vhost, if this is a vhost. */
|
||||
TAILQ_ENTRY(evhttp) next_vhost;
|
||||
@ -140,6 +145,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);
|
||||
@ -2639,24 +2658,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);
|
||||
}
|
||||
@ -2697,6 +2710,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)
|
||||
{
|
||||
@ -2720,16 +2805,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) {
|
||||
@ -2872,7 +2950,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);
|
||||
}
|
||||
@ -2914,6 +2993,7 @@ evhttp_new_object(void)
|
||||
TAILQ_INIT(&http->callbacks);
|
||||
TAILQ_INIT(&http->connections);
|
||||
TAILQ_INIT(&http->virtualhosts);
|
||||
TAILQ_INIT(&http->aliases);
|
||||
|
||||
return (http);
|
||||
}
|
||||
@ -2952,6 +3032,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) {
|
||||
@ -2982,6 +3063,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);
|
||||
}
|
||||
|
||||
@ -3017,6 +3104,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)
|
||||
{
|
||||
@ -3165,9 +3289,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);
|
||||
|
||||
@ -3225,6 +3353,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