]> granicus.if.org Git - libevent/blobdiff - http.c
Optimize arc4random_uniform() (by syncing with OpenBSD implementation)
[libevent] / http.c
diff --git a/http.c b/http.c
index 563d211aa3249ddb8cbe4338fc1e238f457e0c0c..43879f39152a449b70c3a0ea89c97b882a1ad5e4 100644 (file)
--- a/http.c
+++ b/http.c
@@ -28,6 +28,8 @@
 #include "event2/event-config.h"
 #include "evconfig-private.h"
 
+#define member_size(type, member) sizeof(((type *)0)->member)
+
 #ifdef EVENT__HAVE_SYS_PARAM_H
 #include <sys/param.h>
 #endif
 #include "event2/http_struct.h"
 #include "event2/http_compat.h"
 #include "event2/util.h"
+#include "event2/ws.h"
 #include "event2/listener.h"
 #include "log-internal.h"
 #include "util-internal.h"
@@ -195,7 +198,7 @@ static void evhttp_read_header(struct evhttp_connection *evcon,
 static int evhttp_add_header_internal(struct evkeyvalq *headers,
     const char *key, const char *value);
 static const char *evhttp_response_phrase_internal(int code);
-static void evhttp_get_request(struct evhttp *, evutil_socket_t, struct sockaddr *, ev_socklen_t);
+static void evhttp_get_request(struct evhttp *, evutil_socket_t, struct sockaddr *, ev_socklen_t, struct bufferevent *bev);
 static void evhttp_write_buffer(struct evhttp_connection *,
     void (*)(struct evhttp_connection *, void *), void *);
 static void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *);
@@ -498,7 +501,8 @@ evhttp_make_header_request(struct evhttp_connection *evcon,
     struct evhttp_request *req)
 {
        const char *method;
-       ev_uint16_t flags;
+       /* NOTE: some version of GCC reports a warning that flags may be uninitialized, hence assignment */
+       ev_uint16_t flags = 0;
 
        evhttp_remove_header(req->output_headers, "Proxy-Connection");
 
@@ -565,7 +569,7 @@ evhttp_maybe_add_date_header(struct evkeyvalq *headers)
 {
        if (evhttp_find_header(headers, "Date") == NULL) {
                char date[50];
-               if (sizeof(date) - evutil_date_rfc1123(date, sizeof(date), NULL) > 0) {
+               if ((signed)sizeof(date) > evutil_date_rfc1123(date, sizeof(date), NULL)) {
                        evhttp_add_header(headers, "Date", date);
                }
        }
@@ -737,7 +741,7 @@ evhttp_connection_incoming_fail(struct evhttp_request *req,
                 * case may happen when a browser keeps a persistent
                 * connection open and we timeout on the read.  when
                 * the request is still being used for sending, we
-                * need to disassociated it from the connection here.
+                * need to disassociate it from the connection here.
                 */
                if (!req->userdone) {
                        /* remove it so that it will not be freed */
@@ -1354,6 +1358,11 @@ evhttp_connection_free(struct evhttp_connection *evcon)
        if (evcon->address != NULL)
                mm_free(evcon->address);
 
+#ifndef _WIN32
+       if (evcon->unixsocket != NULL)
+               mm_free(evcon->unixsocket);
+#endif
+
        mm_free(evcon);
 }
 
@@ -1420,7 +1429,7 @@ evhttp_connection_reset_hard_(struct evhttp_connection *evcon)
        int err;
 
        /* XXXX This is not actually an optimal fix.  Instead we ought to have
-          an API for "stop connecting", or use bufferevent_setfd to turn off
+          an API for "stop connecting", or use bufferevent_replacefd to turn off
           connecting.  But for Libevent 2.0, this seems like a minimal change
           least likely to disrupt the rest of the bufferevent and http code.
 
@@ -1437,7 +1446,7 @@ evhttp_connection_reset_hard_(struct evhttp_connection *evcon)
                (*evcon->closecb)(evcon, evcon->closecb_arg);
 
        /** FIXME: manipulating with fd is unwanted */
-       err = bufferevent_setfd(evcon->bufev, -1);
+       err = bufferevent_replacefd(evcon->bufev, -1);
        EVUTIL_ASSERT(!err && "setfd");
 
        /* we need to clean up any buffered data */
@@ -1734,7 +1743,7 @@ evhttp_parse_http_version(const char *version, struct evhttp_request *req)
        int n = sscanf(version, "HTTP/%d.%d%c", &major, &minor, &ch);
        if (n != 2 || major > 1) {
                event_debug(("%s: bad version %s on message %p from %s",
-                       __func__, version, req, req->remote_host));
+                       __func__, version, (void *)req, req->remote_host));
                return (-1);
        }
        req->major = major;
@@ -2006,7 +2015,7 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line, size_t len)
 
        if (!type) {
                event_debug(("%s: bad method %s on request %p from %s",
-                           __func__, method, req, req->remote_host));
+                           __func__, method, (void *)req, req->remote_host));
                /* No error yet; we'll give a better error later when
                 * we see that req->type is unsupported. */
        }
@@ -2344,7 +2353,8 @@ evhttp_get_body_length(struct evhttp_request *req)
 static int
 evhttp_method_may_have_body_(struct evhttp_connection *evcon, enum evhttp_cmd_type type)
 {
-       ev_uint16_t flags;
+       /* NOTE: some version of GCC reports a warning that flags may be uninitialized, hence assignment */
+       ev_uint16_t flags = 0;
        evhttp_method_(evcon, type, &flags);
        return (flags & EVHTTP_METHOD_HAS_BODY) ? 1 : 0;
 }
@@ -2513,21 +2523,16 @@ evhttp_connection_new(const char *address, ev_uint16_t port)
        return (evhttp_connection_base_new(NULL, NULL, address, port));
 }
 
-struct evhttp_connection *
-evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev,
-    const char *address, ev_uint16_t port)
+static struct evhttp_connection *
+evhttp_connection_new_(struct event_base *base, struct bufferevent* bev)
 {
-       struct evhttp_connection *evcon = NULL;
-
-       event_debug(("Attempting connection to %s:%d\n", address, port));
+       struct evhttp_connection *evcon;
 
        if ((evcon = mm_calloc(1, sizeof(struct evhttp_connection))) == NULL) {
                event_warn("%s: calloc failed", __func__);
                goto error;
        }
 
-       evcon->port = port;
-
        evcon->max_headers_size = EV_SIZE_MAX;
        evcon->max_body_size = EV_SIZE_MAX;
 
@@ -2538,11 +2543,6 @@ evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_bas
 
        evcon->retry_cnt = evcon->retry_max = 0;
 
-       if ((evcon->address = mm_strdup(address)) == NULL) {
-               event_warn("%s: strdup failed", __func__);
-               goto error;
-       }
-
        if (bev == NULL) {
                if (!(bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE))) {
                        event_warn("%s: bufferevent_socket_new failed", __func__);
@@ -2567,7 +2567,6 @@ evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_bas
            bufferevent_get_priority(bev),
            evhttp_deferred_read_cb, evcon);
 
-       evcon->dns_base = dnsbase;
        evcon->ai_family = AF_UNSPEC;
 
        return (evcon);
@@ -2578,6 +2577,63 @@ evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_bas
        return (NULL);
 }
 
+#ifndef _WIN32
+struct evhttp_connection *
+evhttp_connection_base_bufferevent_unix_new(struct event_base *base, struct bufferevent* bev, const char *unixsocket)
+{
+       struct evhttp_connection *evcon;
+
+       if (strlen(unixsocket) >= member_size(struct sockaddr_un, sun_path)) {
+               event_warn("%s: unix socket too long", __func__);
+               return NULL;
+       }
+
+       evcon = evhttp_connection_new_(base, bev);
+       if (evcon == NULL)
+               goto error;
+
+       if ((evcon->unixsocket = mm_strdup(unixsocket)) == NULL) {
+               event_warn("%s: strdup failed", __func__);
+               goto error;
+       }
+
+       evcon->ai_family = AF_UNIX;
+
+       return (evcon);
+ error:
+       if (evcon != NULL)
+               evhttp_connection_free(evcon);
+       return (NULL);
+}
+#endif
+
+struct evhttp_connection *
+evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev,
+    const char *address, unsigned short port)
+{
+       struct evhttp_connection *evcon;
+
+       event_debug(("Attempting connection to %s:%d\n", address, port));
+
+       evcon = evhttp_connection_new_(base, bev);
+       if (evcon == NULL)
+               goto error;
+
+       if ((evcon->address = mm_strdup(address)) == NULL) {
+               event_warn("%s: strdup failed", __func__);
+               goto error;
+       }
+       evcon->port = port;
+       evcon->dns_base = dnsbase;
+
+       return (evcon);
+error:
+       if (evcon != NULL)
+               evhttp_connection_free(evcon);
+       return (NULL);
+}
+
+
 struct bufferevent* evhttp_connection_get_bufferevent(struct evhttp_connection *evcon)
 {
        return evcon->bufev;
@@ -2766,7 +2822,7 @@ evhttp_connection_connect_(struct evhttp_connection *evcon)
                        return (-1);
                }
 
-               if (bufferevent_setfd(evcon->bufev, fd))
+               if (bufferevent_replacefd(evcon->bufev, fd))
                        return (-1);
        }
 
@@ -2792,7 +2848,16 @@ evhttp_connection_connect_(struct evhttp_connection *evcon)
                        socklen = sizeof(struct sockaddr_in6);
                }
                ret = bufferevent_socket_connect(evcon->bufev, sa, socklen);
-       } else {
+       }
+#ifndef _WIN32
+       else if (evcon->unixsocket) {
+               struct sockaddr_un sockaddr;
+               sockaddr.sun_family = AF_UNIX;
+               strcpy(sockaddr.sun_path, evcon->unixsocket);
+               ret = bufferevent_socket_connect(evcon->bufev, (const struct sockaddr*)&sockaddr, sizeof(sockaddr));
+       }
+#endif
+       else {
                ret = bufferevent_socket_connect_hostname(evcon->bufev,
                                evcon->dns_base, evcon->ai_family, address, evcon->port);
        }
@@ -3127,6 +3192,31 @@ evhttp_send_reply_chunk_with_cb(struct evhttp_request *req, struct evbuffer *dat
        evhttp_write_buffer(evcon, cb, arg);
 }
 
+struct bufferevent *
+evhttp_start_ws_(struct evhttp_request *req)
+{
+       struct evhttp_connection *evcon = req->evcon;
+       struct bufferevent *bufev;
+
+       evhttp_response_code_(req, HTTP_SWITCH_PROTOCOLS, "Switching Protocols");
+
+       if (req->evcon == NULL)
+               return NULL;
+
+       evhttp_make_header(req->evcon, req);
+       evhttp_write_buffer(req->evcon, NULL, NULL);
+
+       TAILQ_REMOVE(&evcon->requests, req, next);
+
+       bufev = evcon->bufev;
+       evcon->bufev = NULL;
+       evcon->closecb = NULL;
+
+       evhttp_request_free(req);
+       evhttp_connection_free(evcon);
+       return bufev;
+}
+
 void
 evhttp_send_reply_chunk(struct evhttp_request *req, struct evbuffer *databuf)
 {
@@ -3465,7 +3555,6 @@ evhttp_parse_query_impl(const char *str, struct evkeyvalq *headers,
     int is_whole_uri, unsigned flags)
 {
        char *line=NULL;
-       char *argument;
        char *p;
        const char *query_part;
        int result = -1;
@@ -3493,13 +3582,12 @@ evhttp_parse_query_impl(const char *str, struct evkeyvalq *headers,
                goto error;
        }
 
-       p = argument = line;
+       p = line;
        while (p != NULL && *p != '\0') {
                char *key, *value, *decoded_value;
                int err;
-               argument = strsep(&p, "&");
 
-               value = argument;
+               value = strsep(&p, "&");
                key = strsep(&value, "=");
                if (flags & EVHTTP_URI_QUERY_NONCONFORMANT) {
                        if (value == NULL)
@@ -3731,9 +3819,15 @@ evhttp_handle_request(struct evhttp_request *req, void *arg)
 static void
 accept_socket_cb(struct evconnlistener *listener, evutil_socket_t nfd, struct sockaddr *peer_sa, int peer_socklen, void *arg)
 {
-       struct evhttp *http = arg;
+       struct evhttp_bound_socket *bound = arg;
 
-       evhttp_get_request(http, nfd, peer_sa, peer_socklen);
+       struct evhttp *http = bound->http;
+
+       struct bufferevent *bev = NULL;
+       if (bound->bevcb)
+               bev = bound->bevcb(http->base, bound->bevcbarg);
+
+       evhttp_get_request(http, nfd, peer_sa, peer_socklen, bev);
 }
 
 int
@@ -3829,9 +3923,11 @@ evhttp_bind_listener(struct evhttp *http, struct evconnlistener *listener)
                return (NULL);
 
        bound->listener = listener;
+       bound->bevcb = NULL;
+       bound->http = http;
        TAILQ_INSERT_TAIL(&http->sockets, bound, next);
 
-       evconnlistener_set_cb(listener, accept_socket_cb, http);
+       evconnlistener_set_cb(listener, accept_socket_cb, bound);
        return bound;
 }
 
@@ -3847,6 +3943,14 @@ evhttp_bound_socket_get_listener(struct evhttp_bound_socket *bound)
        return bound->listener;
 }
 
+void
+evhttp_bound_set_bevcb(struct evhttp_bound_socket *bound,
+    struct bufferevent* (*cb)(struct event_base *, void *), void *cbarg)
+{
+       bound->bevcb = cb;
+       bound->bevcbarg = cbarg;
+}
+
 void
 evhttp_del_accept_socket(struct evhttp *http, struct evhttp_bound_socket *bound)
 {
@@ -3881,6 +3985,7 @@ evhttp_new_object(void)
        TAILQ_INIT(&http->sockets);
        TAILQ_INIT(&http->callbacks);
        TAILQ_INIT(&http->connections);
+       TAILQ_INIT(&http->ws_sessions);
        TAILQ_INIT(&http->virtualhosts);
        TAILQ_INIT(&http->aliases);
 
@@ -3925,6 +4030,7 @@ evhttp_free(struct evhttp* http)
 {
        struct evhttp_cb *http_cb;
        struct evhttp_connection *evcon;
+       struct evws_connection *evws;
        struct evhttp_bound_socket *bound;
        struct evhttp* vhost;
        struct evhttp_server_alias *alias;
@@ -3943,6 +4049,10 @@ evhttp_free(struct evhttp* http)
                evhttp_connection_free(evcon);
        }
 
+       while ((evws = TAILQ_FIRST(&http->ws_sessions)) != NULL) {
+               evws_connection_free(evws);
+       }
+
        while ((http_cb = TAILQ_FIRST(&http->callbacks)) != NULL) {
                TAILQ_REMOVE(&http->callbacks, http_cb, next);
                mm_free(http_cb->what);
@@ -4353,7 +4463,7 @@ evhttp_request_set_on_complete_cb(struct evhttp_request *req,
 const char *
 evhttp_request_get_uri(const struct evhttp_request *req) {
        if (req->uri == NULL)
-               event_debug(("%s: request %p has no uri\n", __func__, req));
+               event_debug(("%s: request %p has no uri\n", __func__, (void *)req));
        return (req->uri);
 }
 
@@ -4361,7 +4471,7 @@ 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));
+                           __func__, (void *)req));
        return (req->uri_elems);
 }
 
@@ -4453,11 +4563,10 @@ struct evbuffer *evhttp_request_get_output_buffer(struct evhttp_request *req)
 static struct evhttp_connection*
 evhttp_get_request_connection(
        struct evhttp* http,
-       evutil_socket_t fd, struct sockaddr *sa, ev_socklen_t salen)
+       evutil_socket_t fd, struct sockaddr *sa, ev_socklen_t salen,
+       struct bufferevent* bev)
 {
        struct evhttp_connection *evcon;
-       char *hostname = NULL, *portname = NULL;
-       struct bufferevent* bev = NULL;
 
 #ifdef EVENT__HAVE_STRUCT_SOCKADDR_UN
        if (sa->sa_family == AF_UNIX) {
@@ -4466,24 +4575,45 @@ evhttp_get_request_connection(
        }
 #endif
 
-       name_from_addr(sa, salen, &hostname, &portname);
-       if (hostname == NULL || portname == NULL) {
-               if (hostname) mm_free(hostname);
-               if (portname) mm_free(portname);
-               return (NULL);
+#ifndef _WIN32
+       if (sa->sa_family == AF_UNIX) {
+               struct sockaddr_un *sockaddr = (struct sockaddr_un *)sa;
+
+               event_debug(("%s: new request from unix socket on "
+                       EV_SOCK_FMT"\n", __func__, EV_SOCK_ARG(fd)));
+
+               /* we need a connection object to put the http request on */
+               if (!bev && http->bevcb != NULL) {
+                       bev = (*http->bevcb)(http->base, http->bevcbarg);
+               }
+
+               evcon = evhttp_connection_base_bufferevent_unix_new(http->base,
+                       bev, sockaddr->sun_path);
        }
+       else
+#endif
+       {
+               char *hostname = NULL, *portname = NULL;
+
+               name_from_addr(sa, salen, &hostname, &portname);
+               if (hostname == NULL || portname == NULL) {
+                       if (hostname) mm_free(hostname);
+                       if (portname) mm_free(portname);
+                       return (NULL);
+               }
 
-       event_debug(("%s: new request from %s:%s on "EV_SOCK_FMT"\n",
-               __func__, hostname, portname, EV_SOCK_ARG(fd)));
+               event_debug(("%s: new request from %s:%s on "EV_SOCK_FMT"\n",
+                       __func__, hostname, portname, EV_SOCK_ARG(fd)));
 
-       /* we need a connection object to put the http request on */
-       if (http->bevcb != NULL) {
-               bev = (*http->bevcb)(http->base, http->bevcbarg);
+               /* we need a connection object to put the http request on */
+               if (!bev && http->bevcb != NULL) {
+                       bev = (*http->bevcb)(http->base, http->bevcbarg);
+               }
+               evcon = evhttp_connection_base_bufferevent_new(
+                       http->base, NULL, bev, hostname, atoi(portname));
+               mm_free(hostname);
+               mm_free(portname);
        }
-       evcon = evhttp_connection_base_bufferevent_new(
-               http->base, NULL, bev, hostname, atoi(portname));
-       mm_free(hostname);
-       mm_free(portname);
        if (evcon == NULL)
                return (NULL);
 
@@ -4495,7 +4625,7 @@ evhttp_get_request_connection(
        evcon->flags |= EVHTTP_CON_INCOMING;
        evcon->state = EVCON_READING_FIRSTLINE;
 
-       if (bufferevent_setfd(evcon->bufev, fd))
+       if (bufferevent_replacefd(evcon->bufev, fd))
                goto err;
        if (bufferevent_enable(evcon->bufev, EV_READ))
                goto err;
@@ -4518,10 +4648,12 @@ evhttp_associate_new_request_with_connection(struct evhttp_connection *evcon)
        if ((req = evhttp_request_new(evhttp_handle_request, http)) == NULL)
                return (-1);
 
-       if ((req->remote_host = mm_strdup(evcon->address)) == NULL) {
-               event_warn("%s: strdup", __func__);
-               evhttp_request_free(req);
-               return (-1);
+       if (evcon->address != NULL) {
+               if ((req->remote_host = mm_strdup(evcon->address)) == NULL) {
+                       event_warn("%s: strdup", __func__);
+                       evhttp_request_free(req);
+                       return (-1);
+               }
        }
        req->remote_port = evcon->port;
 
@@ -4550,11 +4682,12 @@ evhttp_associate_new_request_with_connection(struct evhttp_connection *evcon)
 
 static void
 evhttp_get_request(struct evhttp *http, evutil_socket_t fd,
-    struct sockaddr *sa, ev_socklen_t salen)
+    struct sockaddr *sa, ev_socklen_t salen,
+    struct bufferevent *bev)
 {
        struct evhttp_connection *evcon;
 
-       evcon = evhttp_get_request_connection(http, fd, sa, salen);
+       evcon = evhttp_get_request_connection(http, fd, sa, salen, bev);
        if (evcon == NULL) {
                event_sock_warn(fd, "%s: cannot get connection on "EV_SOCK_FMT,
                    __func__, EV_SOCK_ARG(fd));
@@ -4739,6 +4872,9 @@ struct evhttp_uri {
        char *userinfo; /* userinfo (typically username:pass), or NULL */
        char *host; /* hostname, IP address, or NULL */
        int port; /* port, or zero */
+#ifndef _WIN32
+       char *unixsocket; /* unix domain socket or NULL */
+#endif
        char *path; /* path, or "". */
        char *query; /* query, or NULL */
        char *fragment; /* fragment or NULL */
@@ -4910,6 +5046,20 @@ parse_authority(struct evhttp_uri *uri, char *s, char *eos, unsigned *flags)
        } else {
                cp = s;
        }
+
+#ifndef _WIN32
+       if (*flags & EVHTTP_URI_UNIX_SOCKET && !strncmp(cp, "unix:", 5)) {
+               char *e = strchr(cp + 5, ':');
+               if (e) {
+                       *e = '\0';
+                       uri->unixsocket = mm_strdup(cp + 5);
+                       return 0;
+               } else {
+                       return -1;
+               }
+       }
+#endif
+
        /* Optionally, we end with ":port" */
        for (port=eos-1; port >= cp && EVUTIL_ISDIGIT_(*port); --port)
                ;
@@ -5203,6 +5353,9 @@ evhttp_uri_free(struct evhttp_uri *uri)
        URI_FREE_STR_(scheme);
        URI_FREE_STR_(userinfo);
        URI_FREE_STR_(host);
+#ifndef _WIN32
+       URI_FREE_STR_(unixsocket);
+#endif
        URI_FREE_STR_(path);
        URI_FREE_STR_(query);
        URI_FREE_STR_(fragment);
@@ -5231,6 +5384,15 @@ evhttp_uri_join(struct evhttp_uri *uri, char *buf, size_t limit)
                URI_ADD_(scheme);
                evbuffer_add(tmp, ":", 1);
        }
+#ifndef _WIN32
+       if (uri->unixsocket) {
+               evbuffer_add(tmp, "//", 2);
+               if (uri->userinfo)
+                       evbuffer_add_printf(tmp, "%s@", uri->userinfo);
+               evbuffer_add_printf(tmp, "unix:%s:", uri->unixsocket);
+       }
+       else
+#endif
        if (uri->host) {
                evbuffer_add(tmp, "//", 2);
                if (uri->userinfo)
@@ -5296,6 +5458,13 @@ evhttp_uri_get_host(const struct evhttp_uri *uri)
 {
        return uri->host;
 }
+#ifndef _WIN32
+const char *
+evhttp_uri_get_unixsocket(const struct evhttp_uri *uri)
+{
+       return uri->unixsocket;
+}
+#endif
 int
 evhttp_uri_get_port(const struct evhttp_uri *uri)
 {
@@ -5385,6 +5554,14 @@ evhttp_uri_set_host(struct evhttp_uri *uri, const char *host)
 
        return 0;
 }
+#ifndef _WIN32
+int
+evhttp_uri_set_unixsocket(struct evhttp_uri *uri, const char *unixsocket)
+{
+       URI_SET_STR_(unixsocket);
+       return 0;
+}
+#endif
 int
 evhttp_uri_set_port(struct evhttp_uri *uri, int port)
 {