From 6bce4e08ca3498f9d9e7269c955960caa6d95f4c Mon Sep 17 00:00:00 2001 From: Julien BLACHE Date: Sat, 2 May 2009 16:20:10 +0200 Subject: [PATCH] Import evhttp from libevent 1.4.10 --- src/evhttp/evhttp.h | 371 +++++ src/evhttp/http-internal.h | 154 ++ src/evhttp/http.c | 2830 +++++++++++++++++++++++++++++++++ src/evhttp/log.c | 185 +++ src/evhttp/log.h | 51 + src/evhttp/strlcpy-internal.h | 21 + src/evhttp/strlcpy.c | 74 + 7 files changed, 3686 insertions(+) create mode 100644 src/evhttp/evhttp.h create mode 100644 src/evhttp/http-internal.h create mode 100644 src/evhttp/http.c create mode 100644 src/evhttp/log.c create mode 100644 src/evhttp/log.h create mode 100644 src/evhttp/strlcpy-internal.h create mode 100644 src/evhttp/strlcpy.c diff --git a/src/evhttp/evhttp.h b/src/evhttp/evhttp.h new file mode 100644 index 00000000..99d16a2f --- /dev/null +++ b/src/evhttp/evhttp.h @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _EVHTTP_H_ +#define _EVHTTP_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#undef WIN32_LEAN_AND_MEAN +#endif + +/** @file evhttp.h + * + * Basic support for HTTP serving. + * + * As libevent is a library for dealing with event notification and most + * interesting applications are networked today, I have often found the + * need to write HTTP code. The following prototypes and definitions provide + * an application with a minimal interface for making HTTP requests and for + * creating a very simple HTTP server. + */ + +/* Response codes */ +#define HTTP_OK 200 +#define HTTP_NOCONTENT 204 +#define HTTP_MOVEPERM 301 +#define HTTP_MOVETEMP 302 +#define HTTP_NOTMODIFIED 304 +#define HTTP_BADREQUEST 400 +#define HTTP_NOTFOUND 404 +#define HTTP_SERVUNAVAIL 503 + +struct evhttp; +struct evhttp_request; +struct evkeyvalq; + +/** Create a new HTTP server + * + * @param base (optional) the event base to receive the HTTP events + * @return a pointer to a newly initialized evhttp server structure + */ +struct evhttp *evhttp_new(struct event_base *base); + +/** + * Binds an HTTP server on the specified address and port. + * + * Can be called multiple times to bind the same http server + * to multiple different ports. + * + * @param http a pointer to an evhttp object + * @param address a string containing the IP address to listen(2) on + * @param port the port number to listen on + * @return a newly allocated evhttp struct + * @see evhttp_free() + */ +int evhttp_bind_socket(struct evhttp *http, const char *address, u_short port); + +/** + * Makes an HTTP server accept connections on the specified socket + * + * This may be useful to create a socket and then fork multiple instances + * of an http server, or when a socket has been communicated via file + * descriptor passing in situations where an http servers does not have + * permissions to bind to a low-numbered port. + * + * Can be called multiple times to have the http server listen to + * multiple different sockets. + * + * @param http a pointer to an evhttp object + * @param fd a socket fd that is ready for accepting connections + * @return 0 on success, -1 on failure. + * @see evhttp_free(), evhttp_bind_socket() + */ +int evhttp_accept_socket(struct evhttp *http, int fd); + +/** + * Free the previously created HTTP server. + * + * Works only if no requests are currently being served. + * + * @param http the evhttp server object to be freed + * @see evhttp_start() + */ +void evhttp_free(struct evhttp* http); + +/** Set a callback for a specified URI */ +void evhttp_set_cb(struct evhttp *, const char *, + void (*)(struct evhttp_request *, void *), void *); + +/** Removes the callback for a specified URI */ +int evhttp_del_cb(struct evhttp *, const char *); + +/** Set a callback for all requests that are not caught by specific callbacks + */ +void evhttp_set_gencb(struct evhttp *, + void (*)(struct evhttp_request *, void *), void *); + +/** + * Set the timeout for an HTTP request. + * + * @param http an evhttp object + * @param timeout_in_secs the timeout, in seconds + */ +void evhttp_set_timeout(struct evhttp *, int timeout_in_secs); + +/* Request/Response functionality */ + +/** + * Send an HTML error message to the client. + * + * @param req a request object + * @param error the HTTP error code + * @param reason a brief explanation of the error + */ +void evhttp_send_error(struct evhttp_request *req, int error, + const char *reason); + +/** + * Send an HTML reply to the client. + * + * @param req a request object + * @param code the HTTP response code to send + * @param reason a brief message to send with the response code + * @param databuf the body of the response + */ +void evhttp_send_reply(struct evhttp_request *req, int code, + const char *reason, struct evbuffer *databuf); + +/* Low-level response interface, for streaming/chunked replies */ +void evhttp_send_reply_start(struct evhttp_request *, int, const char *); +void evhttp_send_reply_chunk(struct evhttp_request *, struct evbuffer *); +void evhttp_send_reply_end(struct evhttp_request *); + +/** + * Start an HTTP server on the specified address and port + * + * DEPRECATED: it does not allow an event base to be specified + * + * @param address the address to which the HTTP server should be bound + * @param port the port number on which the HTTP server should listen + * @return an struct evhttp object + */ +struct evhttp *evhttp_start(const char *address, u_short port); + +/* + * Interfaces for making requests + */ +enum evhttp_cmd_type { EVHTTP_REQ_GET, EVHTTP_REQ_POST, EVHTTP_REQ_HEAD }; + +enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE }; + +/** + * the request structure that a server receives. + * WARNING: expect this structure to change. I will try to provide + * reasonable accessors. + */ +struct evhttp_request { +#if defined(TAILQ_ENTRY) + TAILQ_ENTRY(evhttp_request) next; +#else +struct { + struct evhttp_request *tqe_next; + struct evhttp_request **tqe_prev; +} next; +#endif + + /* the connection object that this request belongs to */ + struct evhttp_connection *evcon; + int flags; +#define EVHTTP_REQ_OWN_CONNECTION 0x0001 +#define EVHTTP_PROXY_REQUEST 0x0002 + + struct evkeyvalq *input_headers; + struct evkeyvalq *output_headers; + + /* address of the remote host and the port connection came from */ + char *remote_host; + u_short remote_port; + + enum evhttp_request_kind kind; + enum evhttp_cmd_type type; + + char *uri; /* uri after HTTP request was parsed */ + + char major; /* HTTP Major number */ + char minor; /* HTTP Minor number */ + + int response_code; /* HTTP Response code */ + char *response_code_line; /* Readable response */ + + struct evbuffer *input_buffer; /* read data */ + ev_int64_t ntoread; + int chunked; + + struct evbuffer *output_buffer; /* outgoing post or data */ + + /* Callback */ + void (*cb)(struct evhttp_request *, void *); + void *cb_arg; + + /* + * Chunked data callback - call for each completed chunk if + * specified. If not specified, all the data is delivered via + * the regular callback. + */ + void (*chunk_cb)(struct evhttp_request *, void *); +}; + +/** + * Creates a new request object that needs to be filled in with the request + * parameters. The callback is executed when the request completed or an + * error occurred. + */ +struct evhttp_request *evhttp_request_new( + void (*cb)(struct evhttp_request *, void *), void *arg); + +/** enable delivery of chunks to requestor */ +void evhttp_request_set_chunked_cb(struct evhttp_request *, + void (*cb)(struct evhttp_request *, void *)); + +/** Frees the request object and removes associated events. */ +void evhttp_request_free(struct evhttp_request *req); + +/** + * A connection object that can be used to for making HTTP requests. The + * connection object tries to establish the connection when it is given an + * http request object. + */ +struct evhttp_connection *evhttp_connection_new( + const char *address, unsigned short port); + +/** Frees an http connection */ +void evhttp_connection_free(struct evhttp_connection *evcon); + +/** sets the ip address from which http connections are made */ +void evhttp_connection_set_local_address(struct evhttp_connection *evcon, + const char *address); + +/** sets the local port from which http connections are made */ +void evhttp_connection_set_local_port(struct evhttp_connection *evcon, + unsigned short port); + +/** Sets the timeout for events related to this connection */ +void evhttp_connection_set_timeout(struct evhttp_connection *evcon, + int timeout_in_secs); + +/** Sets the retry limit for this connection - -1 repeats indefnitely */ +void evhttp_connection_set_retries(struct evhttp_connection *evcon, + int retry_max); + +/** Set a callback for connection close. */ +void evhttp_connection_set_closecb(struct evhttp_connection *evcon, + void (*)(struct evhttp_connection *, void *), void *); + +/** + * Associates an event base with the connection - can only be called + * on a freshly created connection object that has not been used yet. + */ +void evhttp_connection_set_base(struct evhttp_connection *evcon, + struct event_base *base); + +/** Get the remote address and port associated with this connection. */ +void evhttp_connection_get_peer(struct evhttp_connection *evcon, + char **address, u_short *port); + +/** The connection gets ownership of the request */ +int evhttp_make_request(struct evhttp_connection *evcon, + struct evhttp_request *req, + enum evhttp_cmd_type type, const char *uri); + +const char *evhttp_request_uri(struct evhttp_request *req); + +/* Interfaces for dealing with HTTP headers */ + +const char *evhttp_find_header(const struct evkeyvalq *, const char *); +int evhttp_remove_header(struct evkeyvalq *, const char *); +int evhttp_add_header(struct evkeyvalq *, const char *, const char *); +void evhttp_clear_headers(struct evkeyvalq *); + +/* Miscellaneous utility functions */ + + +/** + Helper function to encode a URI. + + The returned string must be freed by the caller. + + @param uri an unencoded URI + @return a newly allocated URI-encoded string + */ +char *evhttp_encode_uri(const char *uri); + + +/** + Helper function to decode a URI. + + The returned string must be freed by the caller. + + @param uri an encoded URI + @return a newly allocated unencoded URI + */ +char *evhttp_decode_uri(const char *uri); + + +/** + * Helper function to parse out arguments in a query. + * + * Parsing a uri like + * + * http://foo.com/?q=test&s=some+thing + * + * will result in two entries in the key value queue. + + * The first entry is: key="q", value="test" + * The second entry is: key="s", value="some thing" + * + * @param uri the request URI + * @param headers the head of the evkeyval queue + */ +void evhttp_parse_query(const char *uri, struct evkeyvalq *headers); + + +/** + * Escape HTML character entities in a string. + * + * Replaces <, >, ", ' and & with <, >, ", + * ' and & correspondingly. + * + * The returned string needs to be freed by the caller. + * + * @param html an unescaped HTML string + * @return an escaped HTML string + */ +char *evhttp_htmlescape(const char *html); + +#ifdef __cplusplus +} +#endif + +#endif /* _EVHTTP_H_ */ diff --git a/src/evhttp/http-internal.h b/src/evhttp/http-internal.h new file mode 100644 index 00000000..9cd03cdd --- /dev/null +++ b/src/evhttp/http-internal.h @@ -0,0 +1,154 @@ +/* + * Copyright 2001 Niels Provos + * All rights reserved. + * + * This header file contains definitions for dealing with HTTP requests + * that are internal to libevent. As user of the library, you should not + * need to know about these. + */ + +#ifndef _HTTP_H_ +#define _HTTP_H_ + +#define HTTP_CONNECT_TIMEOUT 45 +#define HTTP_WRITE_TIMEOUT 50 +#define HTTP_READ_TIMEOUT 50 + +#define HTTP_PREFIX "http://" +#define HTTP_DEFAULTPORT 80 + +enum message_read_status { + ALL_DATA_READ = 1, + MORE_DATA_EXPECTED = 0, + DATA_CORRUPTED = -1, + REQUEST_CANCELED = -2 +}; + +enum evhttp_connection_error { + EVCON_HTTP_TIMEOUT, + EVCON_HTTP_EOF, + EVCON_HTTP_INVALID_HEADER +}; + +struct evbuffer; +struct addrinfo; +struct evhttp_request; + +/* A stupid connection object - maybe make this a bufferevent later */ + +enum evhttp_connection_state { + EVCON_DISCONNECTED, /**< not currently connected not trying either*/ + EVCON_CONNECTING, /**< tries to currently connect */ + EVCON_IDLE, /**< connection is established */ + EVCON_READING_FIRSTLINE,/**< reading Request-Line (incoming conn) or + **< Status-Line (outgoing conn) */ + EVCON_READING_HEADERS, /**< reading request/response headers */ + EVCON_READING_BODY, /**< reading request/response body */ + EVCON_READING_TRAILER, /**< reading request/response chunked trailer */ + EVCON_WRITING /**< writing request/response headers/body */ +}; + +struct event_base; + +struct evhttp_connection { + /* we use tailq only if they were created for an http server */ + TAILQ_ENTRY(evhttp_connection) (next); + + int fd; + struct event ev; + struct event close_ev; + struct evbuffer *input_buffer; + struct evbuffer *output_buffer; + + char *bind_address; /* address to use for binding the src */ + u_short bind_port; /* local port for binding the src */ + + char *address; /* address to connect to */ + u_short port; + + int flags; +#define EVHTTP_CON_INCOMING 0x0001 /* only one request on it ever */ +#define EVHTTP_CON_OUTGOING 0x0002 /* multiple requests possible */ +#define EVHTTP_CON_CLOSEDETECT 0x0004 /* detecting if persistent close */ + + int timeout; /* timeout in seconds for events */ + int retry_cnt; /* retry count */ + int retry_max; /* maximum number of retries */ + + enum evhttp_connection_state state; + + /* for server connections, the http server they are connected with */ + struct evhttp *http_server; + + TAILQ_HEAD(evcon_requestq, evhttp_request) requests; + + void (*cb)(struct evhttp_connection *, void *); + void *cb_arg; + + void (*closecb)(struct evhttp_connection *, void *); + void *closecb_arg; + + struct event_base *base; +}; + +struct evhttp_cb { + TAILQ_ENTRY(evhttp_cb) next; + + char *what; + + void (*cb)(struct evhttp_request *req, void *); + void *cbarg; +}; + +/* both the http server as well as the rpc system need to queue connections */ +TAILQ_HEAD(evconq, evhttp_connection); + +/* each bound socket is stored in one of these */ +struct evhttp_bound_socket { + TAILQ_ENTRY(evhttp_bound_socket) (next); + + struct event bind_ev; +}; + +struct evhttp { + TAILQ_HEAD(boundq, evhttp_bound_socket) sockets; + + TAILQ_HEAD(httpcbq, evhttp_cb) callbacks; + struct evconq connections; + + int timeout; + + void (*gencb)(struct evhttp_request *req, void *); + void *gencbarg; + + struct event_base *base; +}; + +/* resets the connection; can be reused for more requests */ +void evhttp_connection_reset(struct evhttp_connection *); + +/* connects if necessary */ +int evhttp_connection_connect(struct evhttp_connection *); + +/* notifies the current request that it failed; resets connection */ +void evhttp_connection_fail(struct evhttp_connection *, + enum evhttp_connection_error error); + +void evhttp_get_request(struct evhttp *, int, struct sockaddr *, socklen_t); + +int evhttp_hostportfile(char *, char **, u_short *, char **); + +int evhttp_parse_firstline(struct evhttp_request *, struct evbuffer*); +int evhttp_parse_headers(struct evhttp_request *, struct evbuffer*); + +void evhttp_start_read(struct evhttp_connection *); +void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *); + +void evhttp_write_buffer(struct evhttp_connection *, + void (*)(struct evhttp_connection *, void *), void *); + +/* response sending HTML the data in the buffer */ +void evhttp_response_code(struct evhttp_request *, int, const char *); +void evhttp_send_page(struct evhttp_request *, struct evbuffer *); + +#endif /* _HTTP_H */ diff --git a/src/evhttp/http.c b/src/evhttp/http.c new file mode 100644 index 00000000..2f62ec2f --- /dev/null +++ b/src/evhttp/http.c @@ -0,0 +1,2830 @@ +/* + * Copyright (c) 2002-2006 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#ifdef _EVENT_HAVE_SYS_PARAM_H +#include +#endif +#ifdef _EVENT_HAVE_SYS_TYPES_H +#include +#endif + +#ifdef _EVENT_HAVE_SYS_TIME_H +#include +#endif +#ifdef _EVENT_HAVE_SYS_IOCCOM_H +#include +#endif + +#ifndef WIN32 +#include +#include +#include +#include +#endif + +#include + +#ifndef WIN32 +#include +#include +#endif + +#ifdef WIN32 +#include +#endif + +#include +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#endif +#include +#include +#ifdef _EVENT_HAVE_UNISTD_H +#include +#endif +#ifdef _EVENT_HAVE_FCNTL_H +#include +#endif + +#undef timeout_pending +#undef timeout_initialized + +#include "strlcpy-internal.h" +#include +#include "evhttp.h" +#include +#include "log.h" +#include "http-internal.h" + +#ifdef WIN32 +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#define strdup _strdup +#endif + +#ifndef _EVENT_HAVE_GETNAMEINFO +#define NI_MAXSERV 32 +#define NI_MAXHOST 1025 + +#define NI_NUMERICHOST 1 +#define NI_NUMERICSERV 2 + +int +fake_getnameinfo(const struct sockaddr *sa, size_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags) +{ + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + + if (serv != NULL) { + char tmpserv[16]; + evutil_snprintf(tmpserv, sizeof(tmpserv), + "%d", ntohs(sin->sin_port)); + if (strlcpy(serv, tmpserv, servlen) >= servlen) + return (-1); + } + + if (host != NULL) { + if (flags & NI_NUMERICHOST) { + if (strlcpy(host, inet_ntoa(sin->sin_addr), + hostlen) >= hostlen) + return (-1); + else + return (0); + } else { + struct hostent *hp; + hp = gethostbyaddr((char *)&sin->sin_addr, + sizeof(struct in_addr), AF_INET); + if (hp == NULL) + return (-2); + + if (strlcpy(host, hp->h_name, hostlen) >= hostlen) + return (-1); + else + return (0); + } + } + return (0); +} + +#endif + +#ifndef _EVENT_HAVE_GETADDRINFO +struct addrinfo { + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + struct sockaddr *ai_addr; + struct addrinfo *ai_next; +}; +static int +fake_getaddrinfo(const char *hostname, struct addrinfo *ai) +{ + struct hostent *he = NULL; + struct sockaddr_in *sa; + if (hostname) { + he = gethostbyname(hostname); + if (!he) + return (-1); + } + ai->ai_family = he ? he->h_addrtype : AF_INET; + ai->ai_socktype = SOCK_STREAM; + ai->ai_protocol = 0; + ai->ai_addrlen = sizeof(struct sockaddr_in); + if (NULL == (ai->ai_addr = malloc(ai->ai_addrlen))) + return (-1); + sa = (struct sockaddr_in*)ai->ai_addr; + memset(sa, 0, ai->ai_addrlen); + if (he) { + sa->sin_family = he->h_addrtype; + memcpy(&sa->sin_addr, he->h_addr_list[0], he->h_length); + } else { + sa->sin_family = AF_INET; + sa->sin_addr.s_addr = INADDR_ANY; + } + ai->ai_next = NULL; + return (0); +} +static void +fake_freeaddrinfo(struct addrinfo *ai) +{ + free(ai->ai_addr); +} +#endif + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +/* wrapper for setting the base from the http server */ +#define EVHTTP_BASE_SET(x, y) do { \ + if ((x)->base != NULL) event_base_set((x)->base, y); \ +} while (0) + +extern int debug; + +static int socket_connect(int fd, const char *address, unsigned short port); +static int bind_socket_ai(struct addrinfo *, int reuse); +static int bind_socket(const char *, u_short, int reuse); +static void name_from_addr(struct sockaddr *, socklen_t, char **, char **); +static int evhttp_associate_new_request_with_connection( + struct evhttp_connection *evcon); +static void evhttp_connection_start_detectclose( + struct evhttp_connection *evcon); +static void evhttp_connection_stop_detectclose( + struct evhttp_connection *evcon); +static void evhttp_request_dispatch(struct evhttp_connection* evcon); +static void evhttp_read_firstline(struct evhttp_connection *evcon, + struct evhttp_request *req); +static void evhttp_read_header(struct evhttp_connection *evcon, + struct evhttp_request *req); +static int evhttp_add_header_internal(struct evkeyvalq *headers, + const char *key, const char *value); +static int evhttp_decode_uri_internal(const char *uri, size_t length, + char *ret, int always_decode_plus); + +void evhttp_read(int, short, void *); +void evhttp_write(int, short, void *); + +#ifndef _EVENT_HAVE_STRSEP +/* strsep replacement for platforms that lack it. Only works if + * del is one character long. */ +static char * +strsep(char **s, const char *del) +{ + char *d, *tok; + assert(strlen(del) == 1); + if (!s || !*s) + return NULL; + tok = *s; + d = strstr(tok, del); + if (d) { + *d = '\0'; + *s = d + 1; + } else + *s = NULL; + return tok; +} +#endif + +static const char * +html_replace(char ch, char *buf) +{ + switch (ch) { + case '<': + return "<"; + case '>': + return ">"; + case '"': + return """; + case '\'': + return "'"; + case '&': + return "&"; + default: + break; + } + + /* Echo the character back */ + buf[0] = ch; + buf[1] = '\0'; + + return buf; +} + +/* + * Replaces <, >, ", ' and & with <, >, ", + * ' and & correspondingly. + * + * The returned string needs to be freed by the caller. + */ + +char * +evhttp_htmlescape(const char *html) +{ + int i, new_size = 0, old_size = strlen(html); + char *escaped_html, *p; + char scratch_space[2]; + + for (i = 0; i < old_size; ++i) + new_size += strlen(html_replace(html[i], scratch_space)); + + p = escaped_html = malloc(new_size + 1); + if (escaped_html == NULL) + event_err(1, "%s: malloc(%d)", __func__, new_size + 1); + for (i = 0; i < old_size; ++i) { + const char *replaced = html_replace(html[i], scratch_space); + /* this is length checked */ + strcpy(p, replaced); + p += strlen(replaced); + } + + *p = '\0'; + + return (escaped_html); +} + +static const char * +evhttp_method(enum evhttp_cmd_type type) +{ + const char *method; + + switch (type) { + case EVHTTP_REQ_GET: + method = "GET"; + break; + case EVHTTP_REQ_POST: + method = "POST"; + break; + case EVHTTP_REQ_HEAD: + method = "HEAD"; + break; + default: + method = NULL; + break; + } + + return (method); +} + +static void +evhttp_add_event(struct event *ev, int timeout, int default_timeout) +{ + if (timeout != 0) { + struct timeval tv; + + evutil_timerclear(&tv); + tv.tv_sec = timeout != -1 ? timeout : default_timeout; + event_add(ev, &tv); + } else { + event_add(ev, NULL); + } +} + +void +evhttp_write_buffer(struct evhttp_connection *evcon, + void (*cb)(struct evhttp_connection *, void *), void *arg) +{ + event_debug(("%s: preparing to write buffer\n", __func__)); + + /* Set call back */ + evcon->cb = cb; + evcon->cb_arg = arg; + + /* check if the event is already pending */ + if (event_pending(&evcon->ev, EV_WRITE|EV_TIMEOUT, NULL)) + event_del(&evcon->ev); + + event_set(&evcon->ev, evcon->fd, EV_WRITE, evhttp_write, evcon); + EVHTTP_BASE_SET(evcon, &evcon->ev); + evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_WRITE_TIMEOUT); +} + +static int +evhttp_connected(struct evhttp_connection *evcon) +{ + switch (evcon->state) { + case EVCON_DISCONNECTED: + case EVCON_CONNECTING: + return (0); + case EVCON_IDLE: + case EVCON_READING_FIRSTLINE: + case EVCON_READING_HEADERS: + case EVCON_READING_BODY: + case EVCON_READING_TRAILER: + case EVCON_WRITING: + default: + return (1); + } +} + +/* + * Create the headers needed for an HTTP request + */ +static void +evhttp_make_header_request(struct evhttp_connection *evcon, + struct evhttp_request *req) +{ + char line[1024]; + const char *method; + + evhttp_remove_header(req->output_headers, "Proxy-Connection"); + + /* Generate request line */ + method = evhttp_method(req->type); + evutil_snprintf(line, sizeof(line), "%s %s HTTP/%d.%d\r\n", + method, req->uri, req->major, req->minor); + evbuffer_add(evcon->output_buffer, line, strlen(line)); + + /* Add the content length on a post request if missing */ + if (req->type == EVHTTP_REQ_POST && + evhttp_find_header(req->output_headers, "Content-Length") == NULL){ + char size[12]; + evutil_snprintf(size, sizeof(size), "%ld", + (long)EVBUFFER_LENGTH(req->output_buffer)); + evhttp_add_header(req->output_headers, "Content-Length", size); + } +} + +static int +evhttp_is_connection_close(int flags, struct evkeyvalq* headers) +{ + if (flags & EVHTTP_PROXY_REQUEST) { + /* proxy connection */ + const char *connection = evhttp_find_header(headers, "Proxy-Connection"); + return (connection == NULL || strcasecmp(connection, "keep-alive") != 0); + } else { + const char *connection = evhttp_find_header(headers, "Connection"); + return (connection != NULL && strcasecmp(connection, "close") == 0); + } +} + +static int +evhttp_is_connection_keepalive(struct evkeyvalq* headers) +{ + const char *connection = evhttp_find_header(headers, "Connection"); + return (connection != NULL + && strncasecmp(connection, "keep-alive", 10) == 0); +} + +static void +evhttp_maybe_add_date_header(struct evkeyvalq *headers) +{ + if (evhttp_find_header(headers, "Date") == NULL) { + char date[50]; +#ifndef WIN32 + struct tm cur; +#endif + struct tm *cur_p; + time_t t = time(NULL); +#ifdef WIN32 + cur_p = gmtime(&t); +#else + gmtime_r(&t, &cur); + cur_p = &cur; +#endif + if (strftime(date, sizeof(date), + "%a, %d %b %Y %H:%M:%S GMT", cur_p) != 0) { + evhttp_add_header(headers, "Date", date); + } + } +} + +static void +evhttp_maybe_add_content_length_header(struct evkeyvalq *headers, + long content_length) +{ + if (evhttp_find_header(headers, "Transfer-Encoding") == NULL && + evhttp_find_header(headers, "Content-Length") == NULL) { + char len[12]; + evutil_snprintf(len, sizeof(len), "%ld", content_length); + evhttp_add_header(headers, "Content-Length", len); + } +} + +/* + * Create the headers needed for an HTTP reply + */ + +static void +evhttp_make_header_response(struct evhttp_connection *evcon, + struct evhttp_request *req) +{ + int is_keepalive = evhttp_is_connection_keepalive(req->input_headers); + char line[1024]; + evutil_snprintf(line, sizeof(line), "HTTP/%d.%d %d %s\r\n", + req->major, req->minor, req->response_code, + req->response_code_line); + evbuffer_add(evcon->output_buffer, line, strlen(line)); + + if (req->major == 1) { + if (req->minor == 1) + evhttp_maybe_add_date_header(req->output_headers); + + /* + * if the protocol is 1.0; and the connection was keep-alive + * we need to add a keep-alive header, too. + */ + if (req->minor == 0 && is_keepalive) + evhttp_add_header(req->output_headers, + "Connection", "keep-alive"); + + if (req->minor == 1 || is_keepalive) { + /* + * we need to add the content length if the + * user did not give it, this is required for + * persistent connections to work. + */ + evhttp_maybe_add_content_length_header( + req->output_headers, + (long)EVBUFFER_LENGTH(req->output_buffer)); + } + } + + /* Potentially add headers for unidentified content. */ + if (EVBUFFER_LENGTH(req->output_buffer)) { + if (evhttp_find_header(req->output_headers, + "Content-Type") == NULL) { + evhttp_add_header(req->output_headers, + "Content-Type", "text/html; charset=ISO-8859-1"); + } + } + + /* if the request asked for a close, we send a close, too */ + if (evhttp_is_connection_close(req->flags, req->input_headers)) { + evhttp_remove_header(req->output_headers, "Connection"); + if (!(req->flags & EVHTTP_PROXY_REQUEST)) + evhttp_add_header(req->output_headers, "Connection", "close"); + evhttp_remove_header(req->output_headers, "Proxy-Connection"); + } +} + +void +evhttp_make_header(struct evhttp_connection *evcon, struct evhttp_request *req) +{ + char line[1024]; + struct evkeyval *header; + + /* + * Depending if this is a HTTP request or response, we might need to + * add some new headers or remove existing headers. + */ + if (req->kind == EVHTTP_REQUEST) { + evhttp_make_header_request(evcon, req); + } else { + evhttp_make_header_response(evcon, req); + } + + TAILQ_FOREACH(header, req->output_headers, next) { + evutil_snprintf(line, sizeof(line), "%s: %s\r\n", + header->key, header->value); + evbuffer_add(evcon->output_buffer, line, strlen(line)); + } + evbuffer_add(evcon->output_buffer, "\r\n", 2); + + if (EVBUFFER_LENGTH(req->output_buffer) > 0) { + /* + * For a request, we add the POST data, for a reply, this + * is the regular data. + */ + evbuffer_add_buffer(evcon->output_buffer, req->output_buffer); + } +} + +/* Separated host, port and file from URI */ + +int +evhttp_hostportfile(char *url, char **phost, u_short *pport, char **pfile) +{ + /* XXX not threadsafe. */ + static char host[1024]; + static char file[1024]; + char *p; + const char *p2; + int len; + u_short port; + + len = strlen(HTTP_PREFIX); + if (strncasecmp(url, HTTP_PREFIX, len)) + return (-1); + + url += len; + + /* We might overrun */ + if (strlcpy(host, url, sizeof (host)) >= sizeof(host)) + return (-1); + + p = strchr(host, '/'); + if (p != NULL) { + *p = '\0'; + p2 = p + 1; + } else + p2 = NULL; + + if (pfile != NULL) { + /* Generate request file */ + if (p2 == NULL) + p2 = ""; + evutil_snprintf(file, sizeof(file), "/%s", p2); + } + + p = strchr(host, ':'); + if (p != NULL) { + *p = '\0'; + port = atoi(p + 1); + + if (port == 0) + return (-1); + } else + port = HTTP_DEFAULTPORT; + + if (phost != NULL) + *phost = host; + if (pport != NULL) + *pport = port; + if (pfile != NULL) + *pfile = file; + + return (0); +} + +static int +evhttp_connection_incoming_fail(struct evhttp_request *req, + enum evhttp_connection_error error) +{ + switch (error) { + case EVCON_HTTP_TIMEOUT: + case EVCON_HTTP_EOF: + /* + * these are cases in which we probably should just + * close the connection and not send a reply. this + * case may happen when a browser keeps a persistent + * connection open and we timeout on the read. + */ + return (-1); + case EVCON_HTTP_INVALID_HEADER: + default: /* xxx: probably should just error on default */ + /* the callback looks at the uri to determine errors */ + if (req->uri) { + free(req->uri); + req->uri = NULL; + } + + /* + * the callback needs to send a reply, once the reply has + * been send, the connection should get freed. + */ + (*req->cb)(req, req->cb_arg); + } + + return (0); +} + +void +evhttp_connection_fail(struct evhttp_connection *evcon, + enum evhttp_connection_error error) +{ + struct evhttp_request* req = TAILQ_FIRST(&evcon->requests); + void (*cb)(struct evhttp_request *, void *); + void *cb_arg; + assert(req != NULL); + + if (evcon->flags & EVHTTP_CON_INCOMING) { + /* + * for incoming requests, there are two different + * failure cases. it's either a network level error + * or an http layer error. for problems on the network + * layer like timeouts we just drop the connections. + * For HTTP problems, we might have to send back a + * reply before the connection can be freed. + */ + if (evhttp_connection_incoming_fail(req, error) == -1) + evhttp_connection_free(evcon); + return; + } + + /* save the callback for later; the cb might free our object */ + cb = req->cb; + cb_arg = req->cb_arg; + + TAILQ_REMOVE(&evcon->requests, req, next); + evhttp_request_free(req); + + /* xxx: maybe we should fail all requests??? */ + + /* reset the connection */ + evhttp_connection_reset(evcon); + + /* We are trying the next request that was queued on us */ + if (TAILQ_FIRST(&evcon->requests) != NULL) + evhttp_connection_connect(evcon); + + /* inform the user */ + if (cb != NULL) + (*cb)(NULL, cb_arg); +} + +void +evhttp_write(int fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + int n; + + if (what == EV_TIMEOUT) { + evhttp_connection_fail(evcon, EVCON_HTTP_TIMEOUT); + return; + } + + n = evbuffer_write(evcon->output_buffer, fd); + if (n == -1) { + event_debug(("%s: evbuffer_write", __func__)); + evhttp_connection_fail(evcon, EVCON_HTTP_EOF); + return; + } + + if (n == 0) { + event_debug(("%s: write nothing", __func__)); + evhttp_connection_fail(evcon, EVCON_HTTP_EOF); + return; + } + + if (EVBUFFER_LENGTH(evcon->output_buffer) != 0) { + evhttp_add_event(&evcon->ev, + evcon->timeout, HTTP_WRITE_TIMEOUT); + return; + } + + /* Activate our call back */ + if (evcon->cb != NULL) + (*evcon->cb)(evcon, evcon->cb_arg); +} + +/** + * Advance the connection state. + * - If this is an outgoing connection, we've just processed the response; + * idle or close the connection. + * - If this is an incoming connection, we've just processed the request; + * respond. + */ +static void +evhttp_connection_done(struct evhttp_connection *evcon) +{ + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + int con_outgoing = evcon->flags & EVHTTP_CON_OUTGOING; + + if (con_outgoing) { + /* idle or close the connection */ + int need_close; + TAILQ_REMOVE(&evcon->requests, req, next); + req->evcon = NULL; + + evcon->state = EVCON_IDLE; + + need_close = + evhttp_is_connection_close(req->flags, req->input_headers)|| + evhttp_is_connection_close(req->flags, req->output_headers); + + /* check if we got asked to close the connection */ + if (need_close) + evhttp_connection_reset(evcon); + + if (TAILQ_FIRST(&evcon->requests) != NULL) { + /* + * We have more requests; reset the connection + * and deal with the next request. + */ + if (!evhttp_connected(evcon)) + evhttp_connection_connect(evcon); + else + evhttp_request_dispatch(evcon); + } else if (!need_close) { + /* + * The connection is going to be persistent, but we + * need to detect if the other side closes it. + */ + evhttp_connection_start_detectclose(evcon); + } + } else { + /* + * incoming connection - we need to leave the request on the + * connection so that we can reply to it. + */ + evcon->state = EVCON_WRITING; + } + + /* notify the user of the request */ + (*req->cb)(req, req->cb_arg); + + /* if this was an outgoing request, we own and it's done. so free it */ + if (con_outgoing) { + evhttp_request_free(req); + } +} + +/* + * Handles reading from a chunked request. + * return ALL_DATA_READ: + * all data has been read + * return MORE_DATA_EXPECTED: + * more data is expected + * return DATA_CORRUPTED: + * data is corrupted + * return REQUEST_CANCLED: + * request was canceled by the user calling evhttp_cancel_request + */ + +static enum message_read_status +evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf) +{ + int len; + + while ((len = EVBUFFER_LENGTH(buf)) > 0) { + if (req->ntoread < 0) { + /* Read chunk size */ + ev_int64_t ntoread; + char *p = evbuffer_readline(buf); + char *endp; + int error; + if (p == NULL) + break; + /* the last chunk is on a new line? */ + if (strlen(p) == 0) { + free(p); + continue; + } + ntoread = evutil_strtoll(p, &endp, 16); + error = (*p == '\0' || + (*endp != '\0' && *endp != ' ') || + ntoread < 0); + free(p); + if (error) { + /* could not get chunk size */ + return (DATA_CORRUPTED); + } + req->ntoread = ntoread; + if (req->ntoread == 0) { + /* Last chunk */ + return (ALL_DATA_READ); + } + continue; + } + + /* don't have enough to complete a chunk; wait for more */ + if (len < req->ntoread) + return (MORE_DATA_EXPECTED); + + /* Completed chunk */ + evbuffer_add(req->input_buffer, + EVBUFFER_DATA(buf), req->ntoread); + evbuffer_drain(buf, req->ntoread); + req->ntoread = -1; + if (req->chunk_cb != NULL) { + (*req->chunk_cb)(req, req->cb_arg); + evbuffer_drain(req->input_buffer, + EVBUFFER_LENGTH(req->input_buffer)); + } + } + + return (MORE_DATA_EXPECTED); +} + +static void +evhttp_read_trailer(struct evhttp_connection *evcon, struct evhttp_request *req) +{ + struct evbuffer *buf = evcon->input_buffer; + + switch (evhttp_parse_headers(req, buf)) { + case DATA_CORRUPTED: + evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); + break; + case ALL_DATA_READ: + event_del(&evcon->ev); + evhttp_connection_done(evcon); + break; + case MORE_DATA_EXPECTED: + default: + evhttp_add_event(&evcon->ev, evcon->timeout, + HTTP_READ_TIMEOUT); + break; + } +} + +static void +evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req) +{ + struct evbuffer *buf = evcon->input_buffer; + + if (req->chunked) { + switch (evhttp_handle_chunked_read(req, buf)) { + case ALL_DATA_READ: + /* finished last chunk */ + evcon->state = EVCON_READING_TRAILER; + evhttp_read_trailer(evcon, req); + return; + case DATA_CORRUPTED: + /* corrupted data */ + evhttp_connection_fail(evcon, + EVCON_HTTP_INVALID_HEADER); + return; + case REQUEST_CANCELED: + /* request canceled */ + evhttp_request_free(req); + return; + case MORE_DATA_EXPECTED: + default: + break; + } + } else if (req->ntoread < 0) { + /* Read until connection close. */ + evbuffer_add_buffer(req->input_buffer, buf); + } else if (EVBUFFER_LENGTH(buf) >= req->ntoread) { + /* Completed content length */ + evbuffer_add(req->input_buffer, EVBUFFER_DATA(buf), + req->ntoread); + evbuffer_drain(buf, req->ntoread); + req->ntoread = 0; + evhttp_connection_done(evcon); + return; + } + /* Read more! */ + event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read, evcon); + EVHTTP_BASE_SET(evcon, &evcon->ev); + evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_READ_TIMEOUT); +} + +/* + * Reads data into a buffer structure until no more data + * can be read on the file descriptor or we have read all + * the data that we wanted to read. + * Execute callback when done. + */ + +void +evhttp_read(int fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + struct evbuffer *buf = evcon->input_buffer; + int n, len; + + if (what == EV_TIMEOUT) { + evhttp_connection_fail(evcon, EVCON_HTTP_TIMEOUT); + return; + } + n = evbuffer_read(buf, fd, -1); + len = EVBUFFER_LENGTH(buf); + event_debug(("%s: got %d on %d\n", __func__, n, fd)); + + if (n == -1) { + if (errno != EINTR && errno != EAGAIN) { + event_debug(("%s: evbuffer_read", __func__)); + evhttp_connection_fail(evcon, EVCON_HTTP_EOF); + } else { + evhttp_add_event(&evcon->ev, evcon->timeout, + HTTP_READ_TIMEOUT); + } + return; + } else if (n == 0) { + /* Connection closed */ + evhttp_connection_done(evcon); + return; + } + + switch (evcon->state) { + case EVCON_READING_FIRSTLINE: + evhttp_read_firstline(evcon, req); + break; + case EVCON_READING_HEADERS: + evhttp_read_header(evcon, req); + break; + case EVCON_READING_BODY: + evhttp_read_body(evcon, req); + break; + case EVCON_READING_TRAILER: + evhttp_read_trailer(evcon, req); + break; + case EVCON_DISCONNECTED: + case EVCON_CONNECTING: + case EVCON_IDLE: + case EVCON_WRITING: + default: + event_errx(1, "%s: illegal connection state %d", + __func__, evcon->state); + } +} + +static void +evhttp_write_connectioncb(struct evhttp_connection *evcon, void *arg) +{ + /* This is after writing the request to the server */ + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + assert(req != NULL); + + assert(evcon->state == EVCON_WRITING); + + /* We are done writing our header and are now expecting the response */ + req->kind = EVHTTP_RESPONSE; + + evhttp_start_read(evcon); +} + +/* + * Clean up a connection object + */ + +void +evhttp_connection_free(struct evhttp_connection *evcon) +{ + struct evhttp_request *req; + + /* notify interested parties that this connection is going down */ + if (evcon->fd != -1) { + if (evhttp_connected(evcon) && evcon->closecb != NULL) + (*evcon->closecb)(evcon, evcon->closecb_arg); + } + + /* remove all requests that might be queued on this connection */ + while ((req = TAILQ_FIRST(&evcon->requests)) != NULL) { + TAILQ_REMOVE(&evcon->requests, req, next); + evhttp_request_free(req); + } + + if (evcon->http_server != NULL) { + struct evhttp *http = evcon->http_server; + TAILQ_REMOVE(&http->connections, evcon, next); + } + + if (event_initialized(&evcon->close_ev)) + event_del(&evcon->close_ev); + + if (event_initialized(&evcon->ev)) + event_del(&evcon->ev); + + if (evcon->fd != -1) + EVUTIL_CLOSESOCKET(evcon->fd); + + if (evcon->bind_address != NULL) + free(evcon->bind_address); + + if (evcon->address != NULL) + free(evcon->address); + + if (evcon->input_buffer != NULL) + evbuffer_free(evcon->input_buffer); + + if (evcon->output_buffer != NULL) + evbuffer_free(evcon->output_buffer); + + free(evcon); +} + +void +evhttp_connection_set_local_address(struct evhttp_connection *evcon, + const char *address) +{ + assert(evcon->state == EVCON_DISCONNECTED); + if (evcon->bind_address) + free(evcon->bind_address); + if ((evcon->bind_address = strdup(address)) == NULL) + event_err(1, "%s: strdup", __func__); +} + +void +evhttp_connection_set_local_port(struct evhttp_connection *evcon, + unsigned short port) +{ + assert(evcon->state == EVCON_DISCONNECTED); + evcon->bind_port = port; +} + +static void +evhttp_request_dispatch(struct evhttp_connection* evcon) +{ + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + + /* this should not usually happy but it's possible */ + if (req == NULL) + return; + + /* delete possible close detection events */ + evhttp_connection_stop_detectclose(evcon); + + /* we assume that the connection is connected already */ + assert(evcon->state == EVCON_IDLE); + + evcon->state = EVCON_WRITING; + + /* Create the header from the store arguments */ + evhttp_make_header(evcon, req); + + evhttp_write_buffer(evcon, evhttp_write_connectioncb, NULL); +} + +/* Reset our connection state */ +void +evhttp_connection_reset(struct evhttp_connection *evcon) +{ + if (event_initialized(&evcon->ev)) + event_del(&evcon->ev); + + if (evcon->fd != -1) { + /* inform interested parties about connection close */ + if (evhttp_connected(evcon) && evcon->closecb != NULL) + (*evcon->closecb)(evcon, evcon->closecb_arg); + + EVUTIL_CLOSESOCKET(evcon->fd); + evcon->fd = -1; + } + evcon->state = EVCON_DISCONNECTED; + + evbuffer_drain(evcon->input_buffer, + EVBUFFER_LENGTH(evcon->input_buffer)); + evbuffer_drain(evcon->output_buffer, + EVBUFFER_LENGTH(evcon->output_buffer)); +} + +static void +evhttp_detect_close_cb(int fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + evhttp_connection_reset(evcon); +} + +static void +evhttp_connection_start_detectclose(struct evhttp_connection *evcon) +{ + evcon->flags |= EVHTTP_CON_CLOSEDETECT; + + if (event_initialized(&evcon->close_ev)) + event_del(&evcon->close_ev); + event_set(&evcon->close_ev, evcon->fd, EV_READ, + evhttp_detect_close_cb, evcon); + EVHTTP_BASE_SET(evcon, &evcon->close_ev); + event_add(&evcon->close_ev, NULL); +} + +static void +evhttp_connection_stop_detectclose(struct evhttp_connection *evcon) +{ + evcon->flags &= ~EVHTTP_CON_CLOSEDETECT; + event_del(&evcon->close_ev); +} + +static void +evhttp_connection_retry(int fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + + evcon->state = EVCON_DISCONNECTED; + evhttp_connection_connect(evcon); +} + +/* + * Call back for asynchronous connection attempt. + */ + +static void +evhttp_connectioncb(int fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + int error; + socklen_t errsz = sizeof(error); + + if (what == EV_TIMEOUT) { + event_debug(("%s: connection timeout for \"%s:%d\" on %d", + __func__, evcon->address, evcon->port, evcon->fd)); + goto cleanup; + } + + /* Check if the connection completed */ + if (getsockopt(evcon->fd, SOL_SOCKET, SO_ERROR, (void*)&error, + &errsz) == -1) { + event_debug(("%s: getsockopt for \"%s:%d\" on %d", + __func__, evcon->address, evcon->port, evcon->fd)); + goto cleanup; + } + + if (error) { + event_debug(("%s: connect failed for \"%s:%d\" on %d: %s", + __func__, evcon->address, evcon->port, evcon->fd, + strerror(error))); + goto cleanup; + } + + /* We are connected to the server now */ + event_debug(("%s: connected to \"%s:%d\" on %d\n", + __func__, evcon->address, evcon->port, evcon->fd)); + + /* Reset the retry count as we were successful in connecting */ + evcon->retry_cnt = 0; + evcon->state = EVCON_IDLE; + + /* try to start requests that have queued up on this connection */ + evhttp_request_dispatch(evcon); + return; + + cleanup: + if (evcon->retry_max < 0 || evcon->retry_cnt < evcon->retry_max) { + evtimer_set(&evcon->ev, evhttp_connection_retry, evcon); + EVHTTP_BASE_SET(evcon, &evcon->ev); + evhttp_add_event(&evcon->ev, MIN(3600, 2 << evcon->retry_cnt), + HTTP_CONNECT_TIMEOUT); + evcon->retry_cnt++; + return; + } + evhttp_connection_reset(evcon); + + /* for now, we just signal all requests by executing their callbacks */ + while (TAILQ_FIRST(&evcon->requests) != NULL) { + struct evhttp_request *request = TAILQ_FIRST(&evcon->requests); + TAILQ_REMOVE(&evcon->requests, request, next); + request->evcon = NULL; + + /* we might want to set an error here */ + request->cb(request, request->cb_arg); + evhttp_request_free(request); + } +} + +/* + * Check if we got a valid response code. + */ + +static int +evhttp_valid_response_code(int code) +{ + if (code == 0) + return (0); + + return (1); +} + +/* Parses the status line of a web server */ + +static int +evhttp_parse_response_line(struct evhttp_request *req, char *line) +{ + char *protocol; + char *number; + char *readable; + + protocol = strsep(&line, " "); + if (line == NULL) + return (-1); + number = strsep(&line, " "); + if (line == NULL) + return (-1); + readable = line; + + if (strcmp(protocol, "HTTP/1.0") == 0) { + req->major = 1; + req->minor = 0; + } else if (strcmp(protocol, "HTTP/1.1") == 0) { + req->major = 1; + req->minor = 1; + } else { + event_debug(("%s: bad protocol \"%s\"", + __func__, protocol)); + return (-1); + } + + req->response_code = atoi(number); + if (!evhttp_valid_response_code(req->response_code)) { + event_debug(("%s: bad response code \"%s\"", + __func__, number)); + return (-1); + } + + if ((req->response_code_line = strdup(readable)) == NULL) + event_err(1, "%s: strdup", __func__); + + return (0); +} + +/* Parse the first line of a HTTP request */ + +static int +evhttp_parse_request_line(struct evhttp_request *req, char *line) +{ + char *method; + char *uri; + char *version; + + /* Parse the request line */ + method = strsep(&line, " "); + if (line == NULL) + return (-1); + uri = strsep(&line, " "); + if (line == NULL) + return (-1); + version = strsep(&line, " "); + if (line != NULL) + return (-1); + + /* First line */ + if (strcmp(method, "GET") == 0) { + req->type = EVHTTP_REQ_GET; + } else if (strcmp(method, "POST") == 0) { + req->type = EVHTTP_REQ_POST; + } else if (strcmp(method, "HEAD") == 0) { + req->type = EVHTTP_REQ_HEAD; + } else { + event_debug(("%s: bad method %s on request %p from %s", + __func__, method, req, req->remote_host)); + return (-1); + } + + if (strcmp(version, "HTTP/1.0") == 0) { + req->major = 1; + req->minor = 0; + } else if (strcmp(version, "HTTP/1.1") == 0) { + req->major = 1; + req->minor = 1; + } else { + event_debug(("%s: bad version %s on request %p from %s", + __func__, version, req, req->remote_host)); + return (-1); + } + + if ((req->uri = strdup(uri)) == NULL) { + event_debug(("%s: evhttp_decode_uri", __func__)); + return (-1); + } + + /* determine if it's a proxy request */ + if (strlen(req->uri) > 0 && req->uri[0] != '/') + req->flags |= EVHTTP_PROXY_REQUEST; + + return (0); +} + +const char * +evhttp_find_header(const struct evkeyvalq *headers, const char *key) +{ + struct evkeyval *header; + + TAILQ_FOREACH(header, headers, next) { + if (strcasecmp(header->key, key) == 0) + return (header->value); + } + + return (NULL); +} + +void +evhttp_clear_headers(struct evkeyvalq *headers) +{ + struct evkeyval *header; + + for (header = TAILQ_FIRST(headers); + header != NULL; + header = TAILQ_FIRST(headers)) { + TAILQ_REMOVE(headers, header, next); + free(header->key); + free(header->value); + free(header); + } +} + +/* + * Returns 0, if the header was successfully removed. + * Returns -1, if the header could not be found. + */ + +int +evhttp_remove_header(struct evkeyvalq *headers, const char *key) +{ + struct evkeyval *header; + + TAILQ_FOREACH(header, headers, next) { + if (strcasecmp(header->key, key) == 0) + break; + } + + if (header == NULL) + return (-1); + + /* Free and remove the header that we found */ + TAILQ_REMOVE(headers, header, next); + free(header->key); + free(header->value); + free(header); + + return (0); +} + +static int +evhttp_header_is_valid_value(const char *value) +{ + const char *p = value; + + while ((p = strpbrk(p, "\r\n")) != NULL) { + /* we really expect only one new line */ + p += strspn(p, "\r\n"); + /* we expect a space or tab for continuation */ + if (*p != ' ' && *p != '\t') + return (0); + } + return (1); +} + +int +evhttp_add_header(struct evkeyvalq *headers, + const char *key, const char *value) +{ + event_debug(("%s: key: %s val: %s\n", __func__, key, value)); + + if (strchr(key, '\r') != NULL || strchr(key, '\n') != NULL) { + /* drop illegal headers */ + event_debug(("%s: dropping illegal header key\n", __func__)); + return (-1); + } + + if (!evhttp_header_is_valid_value(value)) { + event_debug(("%s: dropping illegal header value\n", __func__)); + return (-1); + } + + return (evhttp_add_header_internal(headers, key, value)); +} + +static int +evhttp_add_header_internal(struct evkeyvalq *headers, + const char *key, const char *value) +{ + struct evkeyval *header = calloc(1, sizeof(struct evkeyval)); + if (header == NULL) { + event_warn("%s: calloc", __func__); + return (-1); + } + if ((header->key = strdup(key)) == NULL) { + free(header); + event_warn("%s: strdup", __func__); + return (-1); + } + if ((header->value = strdup(value)) == NULL) { + free(header->key); + free(header); + event_warn("%s: strdup", __func__); + return (-1); + } + + TAILQ_INSERT_TAIL(headers, header, next); + + return (0); +} + +/* + * Parses header lines from a request or a response into the specified + * request object given an event buffer. + * + * Returns + * DATA_CORRUPTED on error + * MORE_DATA_EXPECTED when we need to read more headers + * ALL_DATA_READ when all headers have been read. + */ + +enum message_read_status +evhttp_parse_firstline(struct evhttp_request *req, struct evbuffer *buffer) +{ + char *line; + enum message_read_status status = ALL_DATA_READ; + + line = evbuffer_readline(buffer); + if (line == NULL) + return (MORE_DATA_EXPECTED); + + switch (req->kind) { + case EVHTTP_REQUEST: + if (evhttp_parse_request_line(req, line) == -1) + status = DATA_CORRUPTED; + break; + case EVHTTP_RESPONSE: + if (evhttp_parse_response_line(req, line) == -1) + status = DATA_CORRUPTED; + break; + default: + status = DATA_CORRUPTED; + } + + free(line); + return (status); +} + +static int +evhttp_append_to_last_header(struct evkeyvalq *headers, const char *line) +{ + struct evkeyval *header = TAILQ_LAST(headers, evkeyvalq); + char *newval; + size_t old_len, line_len; + + if (header == NULL) + return (-1); + + old_len = strlen(header->value); + line_len = strlen(line); + + newval = realloc(header->value, old_len + line_len + 1); + if (newval == NULL) + return (-1); + + memcpy(newval + old_len, line, line_len + 1); + header->value = newval; + + return (0); +} + +enum message_read_status +evhttp_parse_headers(struct evhttp_request *req, struct evbuffer* buffer) +{ + char *line; + enum message_read_status status = MORE_DATA_EXPECTED; + + struct evkeyvalq* headers = req->input_headers; + while ((line = evbuffer_readline(buffer)) + != NULL) { + char *skey, *svalue; + + if (*line == '\0') { /* Last header - Done */ + status = ALL_DATA_READ; + free(line); + break; + } + + /* Check if this is a continuation line */ + if (*line == ' ' || *line == '\t') { + if (evhttp_append_to_last_header(headers, line) == -1) + goto error; + free(line); + continue; + } + + /* Processing of header lines */ + svalue = line; + skey = strsep(&svalue, ":"); + if (svalue == NULL) + goto error; + + svalue += strspn(svalue, " "); + + if (evhttp_add_header(headers, skey, svalue) == -1) + goto error; + + free(line); + } + + return (status); + + error: + free(line); + return (DATA_CORRUPTED); +} + +static int +evhttp_get_body_length(struct evhttp_request *req) +{ + struct evkeyvalq *headers = req->input_headers; + const char *content_length; + const char *connection; + + content_length = evhttp_find_header(headers, "Content-Length"); + connection = evhttp_find_header(headers, "Connection"); + + if (content_length == NULL && connection == NULL) + req->ntoread = -1; + else if (content_length == NULL && + strcasecmp(connection, "Close") != 0) { + /* Bad combination, we don't know when it will end */ + event_warnx("%s: we got no content length, but the " + "server wants to keep the connection open: %s.", + __func__, connection); + return (-1); + } else if (content_length == NULL) { + req->ntoread = -1; + } else { + char *endp; + ev_int64_t ntoread = evutil_strtoll(content_length, &endp, 10); + if (*content_length == '\0' || *endp != '\0' || ntoread < 0) { + event_debug(("%s: illegal content length: %s", + __func__, content_length)); + return (-1); + } + req->ntoread = ntoread; + } + + event_debug(("%s: bytes to read: %lld (in buffer %ld)\n", + __func__, req->ntoread, + EVBUFFER_LENGTH(req->evcon->input_buffer))); + + return (0); +} + +static void +evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req) +{ + const char *xfer_enc; + + /* If this is a request without a body, then we are done */ + if (req->kind == EVHTTP_REQUEST && req->type != EVHTTP_REQ_POST) { + evhttp_connection_done(evcon); + return; + } + evcon->state = EVCON_READING_BODY; + xfer_enc = evhttp_find_header(req->input_headers, "Transfer-Encoding"); + if (xfer_enc != NULL && strcasecmp(xfer_enc, "chunked") == 0) { + req->chunked = 1; + req->ntoread = -1; + } else { + if (evhttp_get_body_length(req) == -1) { + evhttp_connection_fail(evcon, + EVCON_HTTP_INVALID_HEADER); + return; + } + } + evhttp_read_body(evcon, req); +} + +static void +evhttp_read_firstline(struct evhttp_connection *evcon, + struct evhttp_request *req) +{ + enum message_read_status res; + + res = evhttp_parse_firstline(req, evcon->input_buffer); + if (res == DATA_CORRUPTED) { + /* Error while reading, terminate */ + event_debug(("%s: bad header lines on %d\n", + __func__, evcon->fd)); + evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); + return; + } else if (res == MORE_DATA_EXPECTED) { + /* Need more header lines */ + evhttp_add_event(&evcon->ev, + evcon->timeout, HTTP_READ_TIMEOUT); + return; + } + + evcon->state = EVCON_READING_HEADERS; + evhttp_read_header(evcon, req); +} + +static void +evhttp_read_header(struct evhttp_connection *evcon, struct evhttp_request *req) +{ + enum message_read_status res; + int fd = evcon->fd; + + res = evhttp_parse_headers(req, evcon->input_buffer); + if (res == DATA_CORRUPTED) { + /* Error while reading, terminate */ + event_debug(("%s: bad header lines on %d\n", __func__, fd)); + evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); + return; + } else if (res == MORE_DATA_EXPECTED) { + /* Need more header lines */ + evhttp_add_event(&evcon->ev, + evcon->timeout, HTTP_READ_TIMEOUT); + return; + } + + /* Done reading headers, do the real work */ + switch (req->kind) { + case EVHTTP_REQUEST: + event_debug(("%s: checking for post data on %d\n", + __func__, fd)); + evhttp_get_body(evcon, req); + break; + + case EVHTTP_RESPONSE: + if (req->response_code == HTTP_NOCONTENT || + req->response_code == HTTP_NOTMODIFIED || + (req->response_code >= 100 && req->response_code < 200)) { + event_debug(("%s: skipping body for code %d\n", + __func__, req->response_code)); + evhttp_connection_done(evcon); + } else { + event_debug(("%s: start of read body for %s on %d\n", + __func__, req->remote_host, fd)); + evhttp_get_body(evcon, req); + } + break; + + default: + event_warnx("%s: bad header on %d", __func__, fd); + evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); + break; + } +} + +/* + * Creates a TCP connection to the specified port and executes a callback + * when finished. Failure or sucess is indicate by the passed connection + * object. + * + * Although this interface accepts a hostname, it is intended to take + * only numeric hostnames so that non-blocking DNS resolution can + * happen elsewhere. + */ + +struct evhttp_connection * +evhttp_connection_new(const char *address, unsigned short port) +{ + struct evhttp_connection *evcon = NULL; + + event_debug(("Attempting connection to %s:%d\n", address, port)); + + if ((evcon = calloc(1, sizeof(struct evhttp_connection))) == NULL) { + event_warn("%s: calloc failed", __func__); + goto error; + } + + evcon->fd = -1; + evcon->port = port; + + evcon->timeout = -1; + evcon->retry_cnt = evcon->retry_max = 0; + + if ((evcon->address = strdup(address)) == NULL) { + event_warn("%s: strdup failed", __func__); + goto error; + } + + if ((evcon->input_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new failed", __func__); + goto error; + } + + if ((evcon->output_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new failed", __func__); + goto error; + } + + evcon->state = EVCON_DISCONNECTED; + TAILQ_INIT(&evcon->requests); + + return (evcon); + + error: + if (evcon != NULL) + evhttp_connection_free(evcon); + return (NULL); +} + +void evhttp_connection_set_base(struct evhttp_connection *evcon, + struct event_base *base) +{ + assert(evcon->base == NULL); + assert(evcon->state == EVCON_DISCONNECTED); + evcon->base = base; +} + +void +evhttp_connection_set_timeout(struct evhttp_connection *evcon, + int timeout_in_secs) +{ + evcon->timeout = timeout_in_secs; +} + +void +evhttp_connection_set_retries(struct evhttp_connection *evcon, + int retry_max) +{ + evcon->retry_max = retry_max; +} + +void +evhttp_connection_set_closecb(struct evhttp_connection *evcon, + void (*cb)(struct evhttp_connection *, void *), void *cbarg) +{ + evcon->closecb = cb; + evcon->closecb_arg = cbarg; +} + +void +evhttp_connection_get_peer(struct evhttp_connection *evcon, + char **address, u_short *port) +{ + *address = evcon->address; + *port = evcon->port; +} + +int +evhttp_connection_connect(struct evhttp_connection *evcon) +{ + if (evcon->state == EVCON_CONNECTING) + return (0); + + evhttp_connection_reset(evcon); + + assert(!(evcon->flags & EVHTTP_CON_INCOMING)); + evcon->flags |= EVHTTP_CON_OUTGOING; + + evcon->fd = bind_socket( + evcon->bind_address, evcon->bind_port, 0 /*reuse*/); + if (evcon->fd == -1) { + event_debug(("%s: failed to bind to \"%s\"", + __func__, evcon->bind_address)); + return (-1); + } + + if (socket_connect(evcon->fd, evcon->address, evcon->port) == -1) { + EVUTIL_CLOSESOCKET(evcon->fd); evcon->fd = -1; + return (-1); + } + + /* Set up a callback for successful connection setup */ + event_set(&evcon->ev, evcon->fd, EV_WRITE, evhttp_connectioncb, evcon); + EVHTTP_BASE_SET(evcon, &evcon->ev); + evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_CONNECT_TIMEOUT); + + evcon->state = EVCON_CONNECTING; + + return (0); +} + +/* + * Starts an HTTP request on the provided evhttp_connection object. + * If the connection object is not connected to the web server already, + * this will start the connection. + */ + +int +evhttp_make_request(struct evhttp_connection *evcon, + struct evhttp_request *req, + enum evhttp_cmd_type type, const char *uri) +{ + /* We are making a request */ + req->kind = EVHTTP_REQUEST; + req->type = type; + if (req->uri != NULL) + free(req->uri); + if ((req->uri = strdup(uri)) == NULL) + event_err(1, "%s: strdup", __func__); + + /* Set the protocol version if it is not supplied */ + if (!req->major && !req->minor) { + req->major = 1; + req->minor = 1; + } + + assert(req->evcon == NULL); + req->evcon = evcon; + assert(!(req->flags & EVHTTP_REQ_OWN_CONNECTION)); + + TAILQ_INSERT_TAIL(&evcon->requests, req, next); + + /* If the connection object is not connected; make it so */ + if (!evhttp_connected(evcon)) + return (evhttp_connection_connect(evcon)); + + /* + * If it's connected already and we are the first in the queue, + * then we can dispatch this request immediately. Otherwise, it + * will be dispatched once the pending requests are completed. + */ + if (TAILQ_FIRST(&evcon->requests) == req) + evhttp_request_dispatch(evcon); + + return (0); +} + +/* + * Reads data from file descriptor into request structure + * Request structure needs to be set up correctly. + */ + +void +evhttp_start_read(struct evhttp_connection *evcon) +{ + /* Set up an event to read the headers */ + if (event_initialized(&evcon->ev)) + event_del(&evcon->ev); + event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read, evcon); + EVHTTP_BASE_SET(evcon, &evcon->ev); + + evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_READ_TIMEOUT); + evcon->state = EVCON_READING_FIRSTLINE; +} + +static void +evhttp_send_done(struct evhttp_connection *evcon, void *arg) +{ + int need_close; + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + TAILQ_REMOVE(&evcon->requests, req, next); + + /* delete possible close detection events */ + evhttp_connection_stop_detectclose(evcon); + + need_close = + (req->minor == 0 && + !evhttp_is_connection_keepalive(req->input_headers))|| + evhttp_is_connection_close(req->flags, req->input_headers) || + evhttp_is_connection_close(req->flags, req->output_headers); + + assert(req->flags & EVHTTP_REQ_OWN_CONNECTION); + evhttp_request_free(req); + + if (need_close) { + evhttp_connection_free(evcon); + return; + } + + /* we have a persistent connection; try to accept another request. */ + if (evhttp_associate_new_request_with_connection(evcon) == -1) + evhttp_connection_free(evcon); +} + +/* + * Returns an error page. + */ + +void +evhttp_send_error(struct evhttp_request *req, int error, const char *reason) +{ +#define ERR_FORMAT "\n" \ + "%d %s\n" \ + "\n" \ + "

Method Not Implemented

\n" \ + "Invalid method in request

\n" \ + "\n" + + struct evbuffer *buf = evbuffer_new(); + + /* close the connection on error */ + evhttp_add_header(req->output_headers, "Connection", "close"); + + evhttp_response_code(req, error, reason); + + evbuffer_add_printf(buf, ERR_FORMAT, error, reason); + + evhttp_send_page(req, buf); + + evbuffer_free(buf); +#undef ERR_FORMAT +} + +/* Requires that headers and response code are already set up */ + +static inline void +evhttp_send(struct evhttp_request *req, struct evbuffer *databuf) +{ + struct evhttp_connection *evcon = req->evcon; + + assert(TAILQ_FIRST(&evcon->requests) == req); + + /* xxx: not sure if we really should expose the data buffer this way */ + if (databuf != NULL) + evbuffer_add_buffer(req->output_buffer, databuf); + + /* Adds headers to the response */ + evhttp_make_header(evcon, req); + + evhttp_write_buffer(evcon, evhttp_send_done, NULL); +} + +void +evhttp_send_reply(struct evhttp_request *req, int code, const char *reason, + struct evbuffer *databuf) +{ + evhttp_response_code(req, code, reason); + + evhttp_send(req, databuf); +} + +void +evhttp_send_reply_start(struct evhttp_request *req, int code, + const char *reason) +{ + evhttp_response_code(req, code, reason); + if (req->major == 1 && req->minor == 1) { + /* use chunked encoding for HTTP/1.1 */ + evhttp_add_header(req->output_headers, "Transfer-Encoding", + "chunked"); + req->chunked = 1; + } + evhttp_make_header(req->evcon, req); + evhttp_write_buffer(req->evcon, NULL, NULL); +} + +void +evhttp_send_reply_chunk(struct evhttp_request *req, struct evbuffer *databuf) +{ + if (req->chunked) { + evbuffer_add_printf(req->evcon->output_buffer, "%x\r\n", + (unsigned)EVBUFFER_LENGTH(databuf)); + } + evbuffer_add_buffer(req->evcon->output_buffer, databuf); + if (req->chunked) { + evbuffer_add(req->evcon->output_buffer, "\r\n", 2); + } + evhttp_write_buffer(req->evcon, NULL, NULL); +} + +void +evhttp_send_reply_end(struct evhttp_request *req) +{ + struct evhttp_connection *evcon = req->evcon; + + if (req->chunked) { + evbuffer_add(req->evcon->output_buffer, "0\r\n\r\n", 5); + evhttp_write_buffer(req->evcon, evhttp_send_done, NULL); + req->chunked = 0; + } else if (!event_pending(&evcon->ev, EV_WRITE|EV_TIMEOUT, NULL)) { + /* let the connection know that we are done with the request */ + evhttp_send_done(evcon, NULL); + } else { + /* make the callback execute after all data has been written */ + evcon->cb = evhttp_send_done; + evcon->cb_arg = NULL; + } +} + +void +evhttp_response_code(struct evhttp_request *req, int code, const char *reason) +{ + req->kind = EVHTTP_RESPONSE; + req->response_code = code; + if (req->response_code_line != NULL) + free(req->response_code_line); + req->response_code_line = strdup(reason); +} + +void +evhttp_send_page(struct evhttp_request *req, struct evbuffer *databuf) +{ + if (!req->major || !req->minor) { + req->major = 1; + req->minor = 1; + } + + if (req->kind != EVHTTP_RESPONSE) + evhttp_response_code(req, 200, "OK"); + + evhttp_clear_headers(req->output_headers); + evhttp_add_header(req->output_headers, "Content-Type", "text/html"); + evhttp_add_header(req->output_headers, "Connection", "close"); + + evhttp_send(req, databuf); +} + +static const char uri_chars[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, + /* 64 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, + /* 128 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 192 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +/* + * Helper functions to encode/decode a URI. + * The returned string must be freed by the caller. + */ +char * +evhttp_encode_uri(const char *uri) +{ + struct evbuffer *buf = evbuffer_new(); + char *p; + + for (p = (char *)uri; *p != '\0'; p++) { + if (uri_chars[(u_char)(*p)]) { + evbuffer_add(buf, p, 1); + } else { + evbuffer_add_printf(buf, "%%%02X", (u_char)(*p)); + } + } + evbuffer_add(buf, "", 1); + p = strdup((char *)EVBUFFER_DATA(buf)); + evbuffer_free(buf); + + return (p); +} + +/* + * @param always_decode_plus: when true we transform plus to space even + * if we have not seen a ?. + */ +static int +evhttp_decode_uri_internal( + const char *uri, size_t length, char *ret, int always_decode_plus) +{ + char c; + int i, j, in_query = always_decode_plus; + + for (i = j = 0; uri[i] != '\0'; i++) { + c = uri[i]; + if (c == '?') { + in_query = 1; + } else if (c == '+' && in_query) { + c = ' '; + } else if (c == '%' && isxdigit((unsigned char)uri[i+1]) && + isxdigit((unsigned char)uri[i+2])) { + char tmp[] = { uri[i+1], uri[i+2], '\0' }; + c = (char)strtol(tmp, NULL, 16); + i += 2; + } + ret[j++] = c; + } + ret[j] = '\0'; + + return (j); +} + +char * +evhttp_decode_uri(const char *uri) +{ + char *ret; + + if ((ret = malloc(strlen(uri) + 1)) == NULL) + event_err(1, "%s: malloc(%lu)", __func__, + (unsigned long)(strlen(uri) + 1)); + + evhttp_decode_uri_internal(uri, strlen(uri), + ret, 0 /*always_decode_plus*/); + + return (ret); +} + +/* + * Helper function to parse out arguments in a query. + * The arguments are separated by key and value. + */ + +void +evhttp_parse_query(const char *uri, struct evkeyvalq *headers) +{ + char *line; + char *argument; + char *p; + + TAILQ_INIT(headers); + + /* No arguments - we are done */ + if (strchr(uri, '?') == NULL) + return; + + if ((line = strdup(uri)) == NULL) + event_err(1, "%s: strdup", __func__); + + + argument = line; + + /* We already know that there has to be a ? */ + strsep(&argument, "?"); + + p = argument; + while (p != NULL && *p != '\0') { + char *key, *value, *decoded_value; + argument = strsep(&p, "&"); + + value = argument; + key = strsep(&value, "="); + if (value == NULL) + goto error; + + if ((decoded_value = malloc(strlen(value) + 1)) == NULL) + event_err(1, "%s: malloc", __func__); + + evhttp_decode_uri_internal(value, strlen(value), + decoded_value, 1 /*always_decode_plus*/); + event_debug(("Query Param: %s -> %s\n", key, decoded_value)); + evhttp_add_header_internal(headers, key, decoded_value); + free(decoded_value); + } + + error: + free(line); +} + +static struct evhttp_cb * +evhttp_dispatch_callback(struct httpcbq *callbacks, struct evhttp_request *req) +{ + struct evhttp_cb *cb; + size_t offset = 0; + + /* Test for different URLs */ + char *p = strchr(req->uri, '?'); + if (p != NULL) + offset = (size_t)(p - req->uri); + + TAILQ_FOREACH(cb, callbacks, next) { + int res = 0; + if (p == NULL) { + res = strcmp(cb->what, req->uri) == 0; + } else { + res = ((strncmp(cb->what, req->uri, offset) == 0) && + (cb->what[offset] == '\0')); + } + + if (res) + return (cb); + } + + return (NULL); +} + +static void +evhttp_handle_request(struct evhttp_request *req, void *arg) +{ + struct evhttp *http = arg; + struct evhttp_cb *cb = NULL; + + if (req->uri == NULL) { + evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request"); + return; + } + + if ((cb = evhttp_dispatch_callback(&http->callbacks, req)) != NULL) { + (*cb->cb)(req, cb->cbarg); + return; + } + + /* Generic call back */ + if (http->gencb) { + (*http->gencb)(req, http->gencbarg); + return; + } else { + /* We need to send a 404 here */ +#define ERR_FORMAT "" \ + "404 Not Found" \ + "" \ + "

Not Found

" \ + "

The requested URL %s was not found on this server.

"\ + "\n" + + char *escaped_html = evhttp_htmlescape(req->uri); + struct evbuffer *buf = evbuffer_new(); + + evhttp_response_code(req, HTTP_NOTFOUND, "Not Found"); + + evbuffer_add_printf(buf, ERR_FORMAT, escaped_html); + + free(escaped_html); + + evhttp_send_page(req, buf); + + evbuffer_free(buf); +#undef ERR_FORMAT + } +} + +static void +accept_socket(int fd, short what, void *arg) +{ + struct evhttp *http = arg; + struct sockaddr_storage ss; + socklen_t addrlen = sizeof(ss); + int nfd; + + if ((nfd = accept(fd, (struct sockaddr *)&ss, &addrlen)) == -1) { + if (errno != EAGAIN && errno != EINTR) + event_warn("%s: bad accept", __func__); + return; + } + if (evutil_make_socket_nonblocking(nfd) < 0) + return; + + evhttp_get_request(http, nfd, (struct sockaddr *)&ss, addrlen); +} + +int +evhttp_bind_socket(struct evhttp *http, const char *address, u_short port) +{ + int fd; + int res; + + if ((fd = bind_socket(address, port, 1 /*reuse*/)) == -1) + return (-1); + + if (listen(fd, 128) == -1) { + event_warn("%s: listen", __func__); + EVUTIL_CLOSESOCKET(fd); + return (-1); + } + + res = evhttp_accept_socket(http, fd); + + if (res != -1) + event_debug(("Bound to port %d - Awaiting connections ... ", + port)); + + return (res); +} + +int +evhttp_accept_socket(struct evhttp *http, int fd) +{ + struct evhttp_bound_socket *bound; + struct event *ev; + int res; + + bound = malloc(sizeof(struct evhttp_bound_socket)); + if (bound == NULL) + return (-1); + + ev = &bound->bind_ev; + + /* Schedule the socket for accepting */ + event_set(ev, fd, EV_READ | EV_PERSIST, accept_socket, http); + EVHTTP_BASE_SET(http, ev); + + res = event_add(ev, NULL); + + if (res == -1) { + free(bound); + return (-1); + } + + TAILQ_INSERT_TAIL(&http->sockets, bound, next); + + return (0); +} + +static struct evhttp* +evhttp_new_object(void) +{ + struct evhttp *http = NULL; + + if ((http = calloc(1, sizeof(struct evhttp))) == NULL) { + event_warn("%s: calloc", __func__); + return (NULL); + } + + http->timeout = -1; + + TAILQ_INIT(&http->sockets); + TAILQ_INIT(&http->callbacks); + TAILQ_INIT(&http->connections); + + return (http); +} + +struct evhttp * +evhttp_new(struct event_base *base) +{ + struct evhttp *http = evhttp_new_object(); + + http->base = base; + + return (http); +} + +/* + * Start a web server on the specified address and port. + */ + +struct evhttp * +evhttp_start(const char *address, u_short port) +{ + struct evhttp *http = evhttp_new_object(); + + if (evhttp_bind_socket(http, address, port) == -1) { + free(http); + return (NULL); + } + + return (http); +} + +void +evhttp_free(struct evhttp* http) +{ + struct evhttp_cb *http_cb; + struct evhttp_connection *evcon; + struct evhttp_bound_socket *bound; + int fd; + + /* Remove the accepting part */ + while ((bound = TAILQ_FIRST(&http->sockets)) != NULL) { + TAILQ_REMOVE(&http->sockets, bound, next); + + fd = bound->bind_ev.ev_fd; + event_del(&bound->bind_ev); + EVUTIL_CLOSESOCKET(fd); + + free(bound); + } + + while ((evcon = TAILQ_FIRST(&http->connections)) != NULL) { + /* evhttp_connection_free removes the connection */ + evhttp_connection_free(evcon); + } + + while ((http_cb = TAILQ_FIRST(&http->callbacks)) != NULL) { + TAILQ_REMOVE(&http->callbacks, http_cb, next); + free(http_cb->what); + free(http_cb); + } + + free(http); +} + +void +evhttp_set_timeout(struct evhttp* http, int timeout_in_secs) +{ + http->timeout = timeout_in_secs; +} + +void +evhttp_set_cb(struct evhttp *http, const char *uri, + void (*cb)(struct evhttp_request *, void *), void *cbarg) +{ + struct evhttp_cb *http_cb; + + if ((http_cb = calloc(1, sizeof(struct evhttp_cb))) == NULL) + event_err(1, "%s: calloc", __func__); + + http_cb->what = strdup(uri); + http_cb->cb = cb; + http_cb->cbarg = cbarg; + + TAILQ_INSERT_TAIL(&http->callbacks, http_cb, next); +} + +int +evhttp_del_cb(struct evhttp *http, const char *uri) +{ + struct evhttp_cb *http_cb; + + TAILQ_FOREACH(http_cb, &http->callbacks, next) { + if (strcmp(http_cb->what, uri) == 0) + break; + } + if (http_cb == NULL) + return (-1); + + TAILQ_REMOVE(&http->callbacks, http_cb, next); + free(http_cb->what); + free(http_cb); + + return (0); +} + +void +evhttp_set_gencb(struct evhttp *http, + void (*cb)(struct evhttp_request *, void *), void *cbarg) +{ + http->gencb = cb; + http->gencbarg = cbarg; +} + +/* + * Request related functions + */ + +struct evhttp_request * +evhttp_request_new(void (*cb)(struct evhttp_request *, void *), void *arg) +{ + struct evhttp_request *req = NULL; + + /* Allocate request structure */ + if ((req = calloc(1, sizeof(struct evhttp_request))) == NULL) { + event_warn("%s: calloc", __func__); + goto error; + } + + req->kind = EVHTTP_RESPONSE; + req->input_headers = calloc(1, sizeof(struct evkeyvalq)); + if (req->input_headers == NULL) { + event_warn("%s: calloc", __func__); + goto error; + } + TAILQ_INIT(req->input_headers); + + req->output_headers = calloc(1, sizeof(struct evkeyvalq)); + if (req->output_headers == NULL) { + event_warn("%s: calloc", __func__); + goto error; + } + TAILQ_INIT(req->output_headers); + + if ((req->input_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new", __func__); + goto error; + } + + if ((req->output_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new", __func__); + goto error; + } + + req->cb = cb; + req->cb_arg = arg; + + return (req); + + error: + if (req != NULL) + evhttp_request_free(req); + return (NULL); +} + +void +evhttp_request_free(struct evhttp_request *req) +{ + if (req->remote_host != NULL) + free(req->remote_host); + if (req->uri != NULL) + free(req->uri); + if (req->response_code_line != NULL) + free(req->response_code_line); + + evhttp_clear_headers(req->input_headers); + free(req->input_headers); + + evhttp_clear_headers(req->output_headers); + free(req->output_headers); + + if (req->input_buffer != NULL) + evbuffer_free(req->input_buffer); + + if (req->output_buffer != NULL) + evbuffer_free(req->output_buffer); + + free(req); +} + +void +evhttp_request_set_chunked_cb(struct evhttp_request *req, + void (*cb)(struct evhttp_request *, void *)) +{ + req->chunk_cb = cb; +} + +/* + * Allows for inspection of the request URI + */ + +const char * +evhttp_request_uri(struct evhttp_request *req) { + if (req->uri == NULL) + event_debug(("%s: request %p has no uri\n", __func__, req)); + return (req->uri); +} + +/* + * Takes a file descriptor to read a request from. + * The callback is executed once the whole request has been read. + */ + +static struct evhttp_connection* +evhttp_get_request_connection( + struct evhttp* http, + int fd, struct sockaddr *sa, socklen_t salen) +{ + struct evhttp_connection *evcon; + char *hostname = NULL, *portname = NULL; + + name_from_addr(sa, salen, &hostname, &portname); + if (hostname == NULL || portname == NULL) { + if (hostname) free(hostname); + if (portname) free(portname); + return (NULL); + } + + event_debug(("%s: new request from %s:%s on %d\n", + __func__, hostname, portname, fd)); + + /* we need a connection object to put the http request on */ + evcon = evhttp_connection_new(hostname, atoi(portname)); + free(hostname); + free(portname); + if (evcon == NULL) + return (NULL); + + /* associate the base if we have one*/ + evhttp_connection_set_base(evcon, http->base); + + evcon->flags |= EVHTTP_CON_INCOMING; + evcon->state = EVCON_READING_FIRSTLINE; + + evcon->fd = fd; + + return (evcon); +} + +static int +evhttp_associate_new_request_with_connection(struct evhttp_connection *evcon) +{ + struct evhttp *http = evcon->http_server; + struct evhttp_request *req; + if ((req = evhttp_request_new(evhttp_handle_request, http)) == NULL) + return (-1); + + req->evcon = evcon; /* the request ends up owning the connection */ + req->flags |= EVHTTP_REQ_OWN_CONNECTION; + + TAILQ_INSERT_TAIL(&evcon->requests, req, next); + + req->kind = EVHTTP_REQUEST; + + if ((req->remote_host = strdup(evcon->address)) == NULL) + event_err(1, "%s: strdup", __func__); + req->remote_port = evcon->port; + + evhttp_start_read(evcon); + + return (0); +} + +void +evhttp_get_request(struct evhttp *http, int fd, + struct sockaddr *sa, socklen_t salen) +{ + struct evhttp_connection *evcon; + + evcon = evhttp_get_request_connection(http, fd, sa, salen); + if (evcon == NULL) + return; + + /* the timeout can be used by the server to close idle connections */ + if (http->timeout != -1) + evhttp_connection_set_timeout(evcon, http->timeout); + + /* + * if we want to accept more than one request on a connection, + * we need to know which http server it belongs to. + */ + evcon->http_server = http; + TAILQ_INSERT_TAIL(&http->connections, evcon, next); + + if (evhttp_associate_new_request_with_connection(evcon) == -1) + evhttp_connection_free(evcon); +} + + +/* + * Network helper functions that we do not want to export to the rest of + * the world. + */ +#if 0 /* Unused */ +static struct addrinfo * +addr_from_name(char *address) +{ +#ifdef _EVENT_HAVE_GETADDRINFO + struct addrinfo ai, *aitop; + int ai_result; + + memset(&ai, 0, sizeof(ai)); + ai.ai_family = AF_INET; + ai.ai_socktype = SOCK_RAW; + ai.ai_flags = 0; + if ((ai_result = getaddrinfo(address, NULL, &ai, &aitop)) != 0) { + if ( ai_result == EAI_SYSTEM ) + event_warn("getaddrinfo"); + else + event_warnx("getaddrinfo: %s", gai_strerror(ai_result)); + } + + return (aitop); +#else + assert(0); + return NULL; /* XXXXX Use gethostbyname, if this function is ever used. */ +#endif +} +#endif + +static void +name_from_addr(struct sockaddr *sa, socklen_t salen, + char **phost, char **pport) +{ + char ntop[NI_MAXHOST]; + char strport[NI_MAXSERV]; + int ni_result; + +#ifdef _EVENT_HAVE_GETNAMEINFO + ni_result = getnameinfo(sa, salen, + ntop, sizeof(ntop), strport, sizeof(strport), + NI_NUMERICHOST|NI_NUMERICSERV); + + if (ni_result != 0) { + if (ni_result == EAI_SYSTEM) + event_err(1, "getnameinfo failed"); + else + event_errx(1, "getnameinfo failed: %s", gai_strerror(ni_result)); + return; + } +#else + ni_result = fake_getnameinfo(sa, salen, + ntop, sizeof(ntop), strport, sizeof(strport), + NI_NUMERICHOST|NI_NUMERICSERV); + if (ni_result != 0) + return; +#endif + *phost = strdup(ntop); + *pport = strdup(strport); +} + +/* Create a non-blocking socket and bind it */ +/* todo: rename this function */ +static int +bind_socket_ai(struct addrinfo *ai, int reuse) +{ + int fd, on = 1, r; + int serrno; + + /* Create listen socket */ + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + event_warn("socket"); + return (-1); + } + + if (evutil_make_socket_nonblocking(fd) < 0) + goto out; + +#ifndef WIN32 + if (fcntl(fd, F_SETFD, 1) == -1) { + event_warn("fcntl(F_SETFD)"); + goto out; + } +#endif + + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)); + if (reuse) { + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + (void *)&on, sizeof(on)); + } + + if (ai != NULL) { + r = bind(fd, ai->ai_addr, ai->ai_addrlen); + if (r == -1) + goto out; + } + + return (fd); + + out: + serrno = EVUTIL_SOCKET_ERROR(); + EVUTIL_CLOSESOCKET(fd); + EVUTIL_SET_SOCKET_ERROR(serrno); + return (-1); +} + +static struct addrinfo * +make_addrinfo(const char *address, u_short port) +{ + struct addrinfo *aitop = NULL; + +#ifdef _EVENT_HAVE_GETADDRINFO + struct addrinfo ai; + char strport[NI_MAXSERV]; + int ai_result; + + memset(&ai, 0, sizeof(ai)); + ai.ai_family = AF_INET; + ai.ai_socktype = SOCK_STREAM; + ai.ai_flags = AI_PASSIVE; /* turn NULL host name into INADDR_ANY */ + evutil_snprintf(strport, sizeof(strport), "%d", port); + if ((ai_result = getaddrinfo(address, strport, &ai, &aitop)) != 0) { + if ( ai_result == EAI_SYSTEM ) + event_warn("getaddrinfo"); + else + event_warnx("getaddrinfo: %s", gai_strerror(ai_result)); + return (NULL); + } +#else + static int cur; + static struct addrinfo ai[2]; /* We will be returning the address of some of this memory so it has to last even after this call. */ + if (++cur == 2) cur = 0; /* allow calling this function twice */ + + if (fake_getaddrinfo(address, &ai[cur]) < 0) { + event_warn("fake_getaddrinfo"); + return (NULL); + } + aitop = &ai[cur]; + ((struct sockaddr_in *) aitop->ai_addr)->sin_port = htons(port); +#endif + + return (aitop); +} + +static int +bind_socket(const char *address, u_short port, int reuse) +{ + int fd; + struct addrinfo *aitop = NULL; + + /* just create an unbound socket */ + if (address == NULL && port == 0) + return bind_socket_ai(NULL, 0); + + aitop = make_addrinfo(address, port); + + if (aitop == NULL) + return (-1); + + fd = bind_socket_ai(aitop, reuse); + +#ifdef _EVENT_HAVE_GETADDRINFO + freeaddrinfo(aitop); +#else + fake_freeaddrinfo(aitop); +#endif + + return (fd); +} + +static int +socket_connect(int fd, const char *address, unsigned short port) +{ + struct addrinfo *ai = make_addrinfo(address, port); + int res = -1; + + if (ai == NULL) { + event_debug(("%s: make_addrinfo: \"%s:%d\"", + __func__, address, port)); + return (-1); + } + + if (connect(fd, ai->ai_addr, ai->ai_addrlen) == -1) { +#ifdef WIN32 + int tmp_error = WSAGetLastError(); + if (tmp_error != WSAEWOULDBLOCK && tmp_error != WSAEINVAL && + tmp_error != WSAEINPROGRESS) { + goto out; + } +#else + if (errno != EINPROGRESS) { + goto out; + } +#endif + } + + /* everything is fine */ + res = 0; + +out: +#ifdef _EVENT_HAVE_GETADDRINFO + freeaddrinfo(ai); +#else + fake_freeaddrinfo(ai); +#endif + + return (res); +} diff --git a/src/evhttp/log.c b/src/evhttp/log.c new file mode 100644 index 00000000..a5eec04d --- /dev/null +++ b/src/evhttp/log.c @@ -0,0 +1,185 @@ +/* $OpenBSD: err.c,v 1.2 2002/06/25 15:50:15 mickey Exp $ */ + +/* + * log.c + * + * Based on err.c, which was adapted from OpenBSD libc *err* *warn* code. + * + * Copyright (c) 2005 Nick Mathewson + * + * Copyright (c) 2000 Dug Song + * + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +#endif +#include +#ifdef _EVENT_HAVE_SYS_TIME_H +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include "event.h" + +#include "log.h" +#include + +static void _warn_helper(int severity, int log_errno, const char *fmt, + va_list ap); +static void event_log(int severity, const char *msg); + +void +event_err(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _warn_helper(_EVENT_LOG_ERR, errno, fmt, ap); + va_end(ap); + exit(eval); +} + +void +event_warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _warn_helper(_EVENT_LOG_WARN, errno, fmt, ap); + va_end(ap); +} + +void +event_errx(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _warn_helper(_EVENT_LOG_ERR, -1, fmt, ap); + va_end(ap); + exit(eval); +} + +void +event_warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _warn_helper(_EVENT_LOG_WARN, -1, fmt, ap); + va_end(ap); +} + +void +event_msgx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _warn_helper(_EVENT_LOG_MSG, -1, fmt, ap); + va_end(ap); +} + +void +_event_debugx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _warn_helper(_EVENT_LOG_DEBUG, -1, fmt, ap); + va_end(ap); +} + +static void +_warn_helper(int severity, int log_errno, const char *fmt, va_list ap) +{ + char buf[1024]; + size_t len; + + if (fmt != NULL) + evutil_vsnprintf(buf, sizeof(buf), fmt, ap); + else + buf[0] = '\0'; + + if (log_errno >= 0) { + len = strlen(buf); + if (len < sizeof(buf) - 3) { + evutil_snprintf(buf + len, sizeof(buf) - len, ": %s", + strerror(log_errno)); + } + } + + event_log(severity, buf); +} + +static event_log_cb log_fn = NULL; + +void +event_set_log_callback(event_log_cb cb) +{ + log_fn = cb; +} + +static void +event_log(int severity, const char *msg) +{ + if (log_fn) + log_fn(severity, msg); + else { + const char *severity_str; + switch (severity) { + case _EVENT_LOG_DEBUG: + severity_str = "debug"; + break; + case _EVENT_LOG_MSG: + severity_str = "msg"; + break; + case _EVENT_LOG_WARN: + severity_str = "warn"; + break; + case _EVENT_LOG_ERR: + severity_str = "err"; + break; + default: + severity_str = "???"; + break; + } + (void)fprintf(stderr, "[%s] %s\n", severity_str, msg); + } +} diff --git a/src/evhttp/log.h b/src/evhttp/log.h new file mode 100644 index 00000000..7bc6632b --- /dev/null +++ b/src/evhttp/log.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _LOG_H_ +#define _LOG_H_ + +#ifdef __GNUC__ +#define EV_CHECK_FMT(a,b) __attribute__((format(printf, a, b))) +#else +#define EV_CHECK_FMT(a,b) +#endif + +void event_err(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3); +void event_warn(const char *fmt, ...) EV_CHECK_FMT(1,2); +void event_errx(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3); +void event_warnx(const char *fmt, ...) EV_CHECK_FMT(1,2); +void event_msgx(const char *fmt, ...) EV_CHECK_FMT(1,2); +void _event_debugx(const char *fmt, ...) EV_CHECK_FMT(1,2); + +#ifdef USE_DEBUG +#define event_debug(x) _event_debugx x +#else +#define event_debug(x) do {;} while (0) +#endif + +#undef EV_CHECK_FMT + +#endif diff --git a/src/evhttp/strlcpy-internal.h b/src/evhttp/strlcpy-internal.h new file mode 100644 index 00000000..40242264 --- /dev/null +++ b/src/evhttp/strlcpy-internal.h @@ -0,0 +1,21 @@ +#ifndef _STRLCPY_INTERNAL_H_ +#define _STRLCPY_INTERNAL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifndef _EVENT_HAVE_STRLCPY +#include +size_t _event_strlcpy(char *dst, const char *src, size_t siz); +#define strlcpy _event_strlcpy +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/evhttp/strlcpy.c b/src/evhttp/strlcpy.c new file mode 100644 index 00000000..72343e70 --- /dev/null +++ b/src/evhttp/strlcpy.c @@ -0,0 +1,74 @@ +/* $OpenBSD: strlcpy.c,v 1.5 2001/05/13 15:40:16 deraadt Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char *rcsid = "$OpenBSD: strlcpy.c,v 1.5 2001/05/13 15:40:16 deraadt Exp $"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include + +#ifndef _EVENT_HAVE_STRLCPY +#include "strlcpy-internal.h" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +_event_strlcpy(dst, src, siz) + char *dst; + const char *src; + size_t siz; +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} +#endif