]> granicus.if.org Git - libevent/commitdiff
Add evhttp server alias interface, correct flagging of proxy requests.
authorChristopher Davis <chrisd@mangrin.org>
Fri, 5 Nov 2010 18:17:07 +0000 (11:17 -0700)
committerChristopher Davis <chrisd@torproject.org>
Fri, 26 Nov 2010 11:58:28 +0000 (03:58 -0800)
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.

http-internal.h
http.c
include/event2/http.h
include/event2/http_struct.h
test/regress_http.c

index e32511e38d176f054efedea44e47b4e2c57d476e..7a2446ffee2af7519e2b6b375f7618516409d724 100644 (file)
@@ -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;
 
diff --git a/http.c b/http.c
index b08e04c5b8dffea9b254726f292f3adf4b174ca4..2dfaea1fd4032cecd9ec4281db1ad78db07ef957 100644 (file)
--- a/http.c
+++ b/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);
index 6452628d24506a02615214b5e4efaf1b3b5f251c..527e085a470324e5203a693b3b005cae28ed7ab3 100644 (file)
@@ -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.
  */
index 21a8c188fecddb674aa6b33af0aa2e42a42ef16d..7a68d4e5c8ecedc424d06b18b29e47469b391569 100644 (file)
@@ -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 */
index cb145c9a0d59c77a50b4542e2a96cf38b209c77a..f959b83200e6be8791c48e8acbd681174b7dcdeb 100644 (file)
@@ -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);