diff options
-rw-r--r-- | http-internal.h | 7 | ||||
-rw-r--r-- | http.c | 226 | ||||
-rw-r--r-- | include/event2/http.h | 36 | ||||
-rw-r--r-- | include/event2/http_struct.h | 4 | ||||
-rw-r--r-- | test/regress_http.c | 93 |
5 files changed, 333 insertions, 33 deletions
diff --git a/http-internal.h b/http-internal.h index e32511e3..7a2446ff 100644 --- a/http-internal.h +++ b/http-internal.h @@ -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; @@ -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); diff --git a/include/event2/http.h b/include/event2/http.h index 6452628d..527e085a 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -264,6 +264,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. * * @param http an evhttp object @@ -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 */ @@ -670,11 +701,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. */ struct evhttp_uri *evhttp_uri_new(void); diff --git a/include/event2/http_struct.h b/include/event2/http_struct.h index 21a8c188..7a68d4e5 100644 --- a/include/event2/http_struct.h +++ b/include/event2/http_struct.h @@ -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 */ diff --git a/test/regress_http.c b/test/regress_http.c index cb145c9a..f959b832 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -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); |