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
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
char *method;
char *uri;
char *version;
+ const char *hostname;
+ const char *scheme;
/* Parse the request line */
method = strsep(&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);
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);
}
/* 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)
{
}
/* 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) {
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);
}
TAILQ_INIT(&http->callbacks);
TAILQ_INIT(&http->connections);
TAILQ_INIT(&http->virtualhosts);
+ TAILQ_INIT(&http->aliases);
return (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) {
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);
}
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)
{
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);
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);
*/
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.
*
*/
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);
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 */
*/
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.
*/
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:
;
}
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;
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);
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);