michael@0: /* michael@0: * Copyright (c) 2002-2007 Niels Provos michael@0: * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson michael@0: * michael@0: * Redistribution and use in source and binary forms, with or without michael@0: * modification, are permitted provided that the following conditions michael@0: * are met: michael@0: * 1. Redistributions of source code must retain the above copyright michael@0: * notice, this list of conditions and the following disclaimer. michael@0: * 2. Redistributions in binary form must reproduce the above copyright michael@0: * notice, this list of conditions and the following disclaimer in the michael@0: * documentation and/or other materials provided with the distribution. michael@0: * 3. The name of the author may not be used to endorse or promote products michael@0: * derived from this software without specific prior written permission. michael@0: * michael@0: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR michael@0: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES michael@0: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. michael@0: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, michael@0: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT michael@0: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF michael@0: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: */ michael@0: michael@0: #include "event2/event-config.h" michael@0: michael@0: #ifdef _EVENT_HAVE_SYS_PARAM_H michael@0: #include michael@0: #endif michael@0: #ifdef _EVENT_HAVE_SYS_TYPES_H michael@0: #include michael@0: #endif michael@0: michael@0: #ifdef _EVENT_HAVE_SYS_TIME_H michael@0: #include michael@0: #endif michael@0: #ifdef HAVE_SYS_IOCCOM_H michael@0: #include michael@0: #endif michael@0: michael@0: #ifndef WIN32 michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #else michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: #include michael@0: michael@0: #ifdef _EVENT_HAVE_NETINET_IN_H michael@0: #include michael@0: #endif michael@0: #ifdef _EVENT_HAVE_ARPA_INET_H michael@0: #include michael@0: #endif michael@0: #ifdef _EVENT_HAVE_NETDB_H michael@0: #include michael@0: #endif michael@0: michael@0: #ifdef WIN32 michael@0: #include michael@0: #endif michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #ifndef WIN32 michael@0: #include michael@0: #endif michael@0: #include michael@0: #include michael@0: #ifdef _EVENT_HAVE_UNISTD_H michael@0: #include michael@0: #endif michael@0: #ifdef _EVENT_HAVE_FCNTL_H michael@0: #include michael@0: #endif michael@0: michael@0: #undef timeout_pending michael@0: #undef timeout_initialized michael@0: michael@0: #include "strlcpy-internal.h" michael@0: #include "event2/http.h" michael@0: #include "event2/event.h" michael@0: #include "event2/buffer.h" michael@0: #include "event2/bufferevent.h" michael@0: #include "event2/bufferevent_compat.h" michael@0: #include "event2/http_struct.h" michael@0: #include "event2/http_compat.h" michael@0: #include "event2/util.h" michael@0: #include "event2/listener.h" michael@0: #include "log-internal.h" michael@0: #include "util-internal.h" michael@0: #include "http-internal.h" michael@0: #include "mm-internal.h" michael@0: #include "bufferevent-internal.h" michael@0: michael@0: #ifndef _EVENT_HAVE_GETNAMEINFO michael@0: #define NI_MAXSERV 32 michael@0: #define NI_MAXHOST 1025 michael@0: michael@0: #ifndef NI_NUMERICHOST michael@0: #define NI_NUMERICHOST 1 michael@0: #endif michael@0: michael@0: #ifndef NI_NUMERICSERV michael@0: #define NI_NUMERICSERV 2 michael@0: #endif michael@0: michael@0: static int michael@0: fake_getnameinfo(const struct sockaddr *sa, size_t salen, char *host, michael@0: size_t hostlen, char *serv, size_t servlen, int flags) michael@0: { michael@0: struct sockaddr_in *sin = (struct sockaddr_in *)sa; michael@0: michael@0: if (serv != NULL) { michael@0: char tmpserv[16]; michael@0: evutil_snprintf(tmpserv, sizeof(tmpserv), michael@0: "%d", ntohs(sin->sin_port)); michael@0: if (strlcpy(serv, tmpserv, servlen) >= servlen) michael@0: return (-1); michael@0: } michael@0: michael@0: if (host != NULL) { michael@0: if (flags & NI_NUMERICHOST) { michael@0: if (strlcpy(host, inet_ntoa(sin->sin_addr), michael@0: hostlen) >= hostlen) michael@0: return (-1); michael@0: else michael@0: return (0); michael@0: } else { michael@0: struct hostent *hp; michael@0: hp = gethostbyaddr((char *)&sin->sin_addr, michael@0: sizeof(struct in_addr), AF_INET); michael@0: if (hp == NULL) michael@0: return (-2); michael@0: michael@0: if (strlcpy(host, hp->h_name, hostlen) >= hostlen) michael@0: return (-1); michael@0: else michael@0: return (0); michael@0: } michael@0: } michael@0: return (0); michael@0: } michael@0: michael@0: #endif michael@0: michael@0: #define REQ_VERSION_BEFORE(req, major_v, minor_v) \ michael@0: ((req)->major < (major_v) || \ michael@0: ((req)->major == (major_v) && (req)->minor < (minor_v))) michael@0: michael@0: #define REQ_VERSION_ATLEAST(req, major_v, minor_v) \ michael@0: ((req)->major > (major_v) || \ michael@0: ((req)->major == (major_v) && (req)->minor >= (minor_v))) michael@0: michael@0: #ifndef MIN michael@0: #define MIN(a,b) (((a)<(b))?(a):(b)) michael@0: #endif michael@0: michael@0: extern int debug; michael@0: michael@0: static evutil_socket_t bind_socket_ai(struct evutil_addrinfo *, int reuse); michael@0: static evutil_socket_t bind_socket(const char *, ev_uint16_t, int reuse); michael@0: static void name_from_addr(struct sockaddr *, ev_socklen_t, char **, char **); michael@0: static int evhttp_associate_new_request_with_connection( michael@0: struct evhttp_connection *evcon); michael@0: static void evhttp_connection_start_detectclose( michael@0: struct evhttp_connection *evcon); michael@0: static void evhttp_connection_stop_detectclose( michael@0: struct evhttp_connection *evcon); michael@0: static void evhttp_request_dispatch(struct evhttp_connection* evcon); michael@0: static void evhttp_read_firstline(struct evhttp_connection *evcon, michael@0: struct evhttp_request *req); michael@0: static void evhttp_read_header(struct evhttp_connection *evcon, michael@0: struct evhttp_request *req); michael@0: static int evhttp_add_header_internal(struct evkeyvalq *headers, michael@0: const char *key, const char *value); michael@0: static const char *evhttp_response_phrase_internal(int code); michael@0: static void evhttp_get_request(struct evhttp *, evutil_socket_t, struct sockaddr *, ev_socklen_t); michael@0: static void evhttp_write_buffer(struct evhttp_connection *, michael@0: void (*)(struct evhttp_connection *, void *), void *); michael@0: static void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *); michael@0: michael@0: /* callbacks for bufferevent */ michael@0: static void evhttp_read_cb(struct bufferevent *, void *); michael@0: static void evhttp_write_cb(struct bufferevent *, void *); michael@0: static void evhttp_error_cb(struct bufferevent *bufev, short what, void *arg); michael@0: static int evhttp_decode_uri_internal(const char *uri, size_t length, michael@0: char *ret, int decode_plus); michael@0: static int evhttp_find_vhost(struct evhttp *http, struct evhttp **outhttp, michael@0: const char *hostname); michael@0: michael@0: #ifndef _EVENT_HAVE_STRSEP michael@0: /* strsep replacement for platforms that lack it. Only works if michael@0: * del is one character long. */ michael@0: static char * michael@0: strsep(char **s, const char *del) michael@0: { michael@0: char *d, *tok; michael@0: EVUTIL_ASSERT(strlen(del) == 1); michael@0: if (!s || !*s) michael@0: return NULL; michael@0: tok = *s; michael@0: d = strstr(tok, del); michael@0: if (d) { michael@0: *d = '\0'; michael@0: *s = d + 1; michael@0: } else michael@0: *s = NULL; michael@0: return tok; michael@0: } michael@0: #endif michael@0: michael@0: static size_t michael@0: html_replace(const char ch, const char **escaped) michael@0: { michael@0: switch (ch) { michael@0: case '<': michael@0: *escaped = "<"; michael@0: return 4; michael@0: case '>': michael@0: *escaped = ">"; michael@0: return 4; michael@0: case '"': michael@0: *escaped = """; michael@0: return 6; michael@0: case '\'': michael@0: *escaped = "'"; michael@0: return 6; michael@0: case '&': michael@0: *escaped = "&"; michael@0: return 5; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: return 1; michael@0: } michael@0: michael@0: /* michael@0: * Replaces <, >, ", ' and & with <, >, ", michael@0: * ' and & correspondingly. michael@0: * michael@0: * The returned string needs to be freed by the caller. michael@0: */ michael@0: michael@0: char * michael@0: evhttp_htmlescape(const char *html) michael@0: { michael@0: size_t i; michael@0: size_t new_size = 0, old_size = 0; michael@0: char *escaped_html, *p; michael@0: michael@0: if (html == NULL) michael@0: return (NULL); michael@0: michael@0: old_size = strlen(html); michael@0: for (i = 0; i < old_size; ++i) { michael@0: const char *replaced = NULL; michael@0: const size_t replace_size = html_replace(html[i], &replaced); michael@0: if (replace_size > EV_SIZE_MAX - new_size) { michael@0: event_warn("%s: html_replace overflow", __func__); michael@0: return (NULL); michael@0: } michael@0: new_size += replace_size; michael@0: } michael@0: michael@0: if (new_size == EV_SIZE_MAX) michael@0: return (NULL); michael@0: p = escaped_html = mm_malloc(new_size + 1); michael@0: if (escaped_html == NULL) { michael@0: event_warn("%s: malloc(%lu)", __func__, michael@0: (unsigned long)(new_size + 1)); michael@0: return (NULL); michael@0: } michael@0: for (i = 0; i < old_size; ++i) { michael@0: const char *replaced = &html[i]; michael@0: const size_t len = html_replace(html[i], &replaced); michael@0: memcpy(p, replaced, len); michael@0: p += len; michael@0: } michael@0: michael@0: *p = '\0'; michael@0: michael@0: return (escaped_html); michael@0: } michael@0: michael@0: /** Given an evhttp_cmd_type, returns a constant string containing the michael@0: * equivalent HTTP command, or NULL if the evhttp_command_type is michael@0: * unrecognized. */ michael@0: static const char * michael@0: evhttp_method(enum evhttp_cmd_type type) michael@0: { michael@0: const char *method; michael@0: michael@0: switch (type) { michael@0: case EVHTTP_REQ_GET: michael@0: method = "GET"; michael@0: break; michael@0: case EVHTTP_REQ_POST: michael@0: method = "POST"; michael@0: break; michael@0: case EVHTTP_REQ_HEAD: michael@0: method = "HEAD"; michael@0: break; michael@0: case EVHTTP_REQ_PUT: michael@0: method = "PUT"; michael@0: break; michael@0: case EVHTTP_REQ_DELETE: michael@0: method = "DELETE"; michael@0: break; michael@0: case EVHTTP_REQ_OPTIONS: michael@0: method = "OPTIONS"; michael@0: break; michael@0: case EVHTTP_REQ_TRACE: michael@0: method = "TRACE"; michael@0: break; michael@0: case EVHTTP_REQ_CONNECT: michael@0: method = "CONNECT"; michael@0: break; michael@0: case EVHTTP_REQ_PATCH: michael@0: method = "PATCH"; michael@0: break; michael@0: default: michael@0: method = NULL; michael@0: break; michael@0: } michael@0: michael@0: return (method); michael@0: } michael@0: michael@0: /** michael@0: * Determines if a response should have a body. michael@0: * Follows the rules in RFC 2616 section 4.3. michael@0: * @return 1 if the response MUST have a body; 0 if the response MUST NOT have michael@0: * a body. michael@0: */ michael@0: static int michael@0: evhttp_response_needs_body(struct evhttp_request *req) michael@0: { michael@0: return (req->response_code != HTTP_NOCONTENT && michael@0: req->response_code != HTTP_NOTMODIFIED && michael@0: (req->response_code < 100 || req->response_code >= 200) && michael@0: req->type != EVHTTP_REQ_HEAD); michael@0: } michael@0: michael@0: /** Helper: adds the event 'ev' with the timeout 'timeout', or with michael@0: * default_timeout if timeout is -1. michael@0: */ michael@0: static int michael@0: evhttp_add_event(struct event *ev, int timeout, int default_timeout) michael@0: { michael@0: if (timeout != 0) { michael@0: struct timeval tv; michael@0: michael@0: evutil_timerclear(&tv); michael@0: tv.tv_sec = timeout != -1 ? timeout : default_timeout; michael@0: return event_add(ev, &tv); michael@0: } else { michael@0: return event_add(ev, NULL); michael@0: } michael@0: } michael@0: michael@0: /** Helper: called after we've added some data to an evcon's bufferevent's michael@0: * output buffer. Sets the evconn's writing-is-done callback, and puts michael@0: * the bufferevent into writing mode. michael@0: */ michael@0: static void michael@0: evhttp_write_buffer(struct evhttp_connection *evcon, michael@0: void (*cb)(struct evhttp_connection *, void *), void *arg) michael@0: { michael@0: event_debug(("%s: preparing to write buffer\n", __func__)); michael@0: michael@0: /* Set call back */ michael@0: evcon->cb = cb; michael@0: evcon->cb_arg = arg; michael@0: michael@0: bufferevent_enable(evcon->bufev, EV_WRITE); michael@0: michael@0: /* Disable the read callback: we don't actually care about data; michael@0: * we only care about close detection. (We don't disable reading, michael@0: * since we *do* want to learn about any close events.) */ michael@0: bufferevent_setcb(evcon->bufev, michael@0: NULL, /*read*/ michael@0: evhttp_write_cb, michael@0: evhttp_error_cb, michael@0: evcon); michael@0: } michael@0: michael@0: static void michael@0: evhttp_send_continue_done(struct evhttp_connection *evcon, void *arg) michael@0: { michael@0: bufferevent_disable(evcon->bufev, EV_WRITE); michael@0: } michael@0: michael@0: static void michael@0: evhttp_send_continue(struct evhttp_connection *evcon, michael@0: struct evhttp_request *req) michael@0: { michael@0: bufferevent_enable(evcon->bufev, EV_WRITE); michael@0: evbuffer_add_printf(bufferevent_get_output(evcon->bufev), michael@0: "HTTP/%d.%d 100 Continue\r\n\r\n", michael@0: req->major, req->minor); michael@0: evcon->cb = evhttp_send_continue_done; michael@0: evcon->cb_arg = NULL; michael@0: bufferevent_setcb(evcon->bufev, michael@0: evhttp_read_cb, michael@0: evhttp_write_cb, michael@0: evhttp_error_cb, michael@0: evcon); michael@0: } michael@0: michael@0: /** Helper: returns true iff evconn is in any connected state. */ michael@0: static int michael@0: evhttp_connected(struct evhttp_connection *evcon) michael@0: { michael@0: switch (evcon->state) { michael@0: case EVCON_DISCONNECTED: michael@0: case EVCON_CONNECTING: michael@0: return (0); michael@0: case EVCON_IDLE: michael@0: case EVCON_READING_FIRSTLINE: michael@0: case EVCON_READING_HEADERS: michael@0: case EVCON_READING_BODY: michael@0: case EVCON_READING_TRAILER: michael@0: case EVCON_WRITING: michael@0: default: michael@0: return (1); michael@0: } michael@0: } michael@0: michael@0: /* Create the headers needed for an outgoing HTTP request, adds them to michael@0: * the request's header list, and writes the request line to the michael@0: * connection's output buffer. michael@0: */ michael@0: static void michael@0: evhttp_make_header_request(struct evhttp_connection *evcon, michael@0: struct evhttp_request *req) michael@0: { michael@0: const char *method; michael@0: michael@0: evhttp_remove_header(req->output_headers, "Proxy-Connection"); michael@0: michael@0: /* Generate request line */ michael@0: method = evhttp_method(req->type); michael@0: evbuffer_add_printf(bufferevent_get_output(evcon->bufev), michael@0: "%s %s HTTP/%d.%d\r\n", michael@0: method, req->uri, req->major, req->minor); michael@0: michael@0: /* Add the content length on a post or put request if missing */ michael@0: if ((req->type == EVHTTP_REQ_POST || req->type == EVHTTP_REQ_PUT) && michael@0: evhttp_find_header(req->output_headers, "Content-Length") == NULL){ michael@0: char size[22]; michael@0: evutil_snprintf(size, sizeof(size), EV_SIZE_FMT, michael@0: EV_SIZE_ARG(evbuffer_get_length(req->output_buffer))); michael@0: evhttp_add_header(req->output_headers, "Content-Length", size); michael@0: } michael@0: } michael@0: michael@0: /** Return true if the list of headers in 'headers', intepreted with respect michael@0: * to flags, means that we should send a "connection: close" when the request michael@0: * is done. */ michael@0: static int michael@0: evhttp_is_connection_close(int flags, struct evkeyvalq* headers) michael@0: { michael@0: if (flags & EVHTTP_PROXY_REQUEST) { michael@0: /* proxy connection */ michael@0: const char *connection = evhttp_find_header(headers, "Proxy-Connection"); michael@0: return (connection == NULL || evutil_ascii_strcasecmp(connection, "keep-alive") != 0); michael@0: } else { michael@0: const char *connection = evhttp_find_header(headers, "Connection"); michael@0: return (connection != NULL && evutil_ascii_strcasecmp(connection, "close") == 0); michael@0: } michael@0: } michael@0: michael@0: /* Return true iff 'headers' contains 'Connection: keep-alive' */ michael@0: static int michael@0: evhttp_is_connection_keepalive(struct evkeyvalq* headers) michael@0: { michael@0: const char *connection = evhttp_find_header(headers, "Connection"); michael@0: return (connection != NULL michael@0: && evutil_ascii_strncasecmp(connection, "keep-alive", 10) == 0); michael@0: } michael@0: michael@0: /* Add a correct "Date" header to headers, unless it already has one. */ michael@0: static void michael@0: evhttp_maybe_add_date_header(struct evkeyvalq *headers) michael@0: { michael@0: if (evhttp_find_header(headers, "Date") == NULL) { michael@0: char date[50]; michael@0: #ifndef WIN32 michael@0: struct tm cur; michael@0: #endif michael@0: struct tm *cur_p; michael@0: time_t t = time(NULL); michael@0: #ifdef WIN32 michael@0: cur_p = gmtime(&t); michael@0: #else michael@0: gmtime_r(&t, &cur); michael@0: cur_p = &cur; michael@0: #endif michael@0: if (strftime(date, sizeof(date), michael@0: "%a, %d %b %Y %H:%M:%S GMT", cur_p) != 0) { michael@0: evhttp_add_header(headers, "Date", date); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Add a "Content-Length" header with value 'content_length' to headers, michael@0: * unless it already has a content-length or transfer-encoding header. */ michael@0: static void michael@0: evhttp_maybe_add_content_length_header(struct evkeyvalq *headers, michael@0: size_t content_length) michael@0: { michael@0: if (evhttp_find_header(headers, "Transfer-Encoding") == NULL && michael@0: evhttp_find_header(headers, "Content-Length") == NULL) { michael@0: char len[22]; michael@0: evutil_snprintf(len, sizeof(len), EV_SIZE_FMT, michael@0: EV_SIZE_ARG(content_length)); michael@0: evhttp_add_header(headers, "Content-Length", len); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Create the headers needed for an HTTP reply in req->output_headers, michael@0: * and write the first HTTP response for req line to evcon. michael@0: */ michael@0: static void michael@0: evhttp_make_header_response(struct evhttp_connection *evcon, michael@0: struct evhttp_request *req) michael@0: { michael@0: int is_keepalive = evhttp_is_connection_keepalive(req->input_headers); michael@0: evbuffer_add_printf(bufferevent_get_output(evcon->bufev), michael@0: "HTTP/%d.%d %d %s\r\n", michael@0: req->major, req->minor, req->response_code, michael@0: req->response_code_line); michael@0: michael@0: if (req->major == 1) { michael@0: if (req->minor >= 1) michael@0: evhttp_maybe_add_date_header(req->output_headers); michael@0: michael@0: /* michael@0: * if the protocol is 1.0; and the connection was keep-alive michael@0: * we need to add a keep-alive header, too. michael@0: */ michael@0: if (req->minor == 0 && is_keepalive) michael@0: evhttp_add_header(req->output_headers, michael@0: "Connection", "keep-alive"); michael@0: michael@0: if ((req->minor >= 1 || is_keepalive) && michael@0: evhttp_response_needs_body(req)) { michael@0: /* michael@0: * we need to add the content length if the michael@0: * user did not give it, this is required for michael@0: * persistent connections to work. michael@0: */ michael@0: evhttp_maybe_add_content_length_header( michael@0: req->output_headers, michael@0: evbuffer_get_length(req->output_buffer)); michael@0: } michael@0: } michael@0: michael@0: /* Potentially add headers for unidentified content. */ michael@0: if (evhttp_response_needs_body(req)) { michael@0: if (evhttp_find_header(req->output_headers, michael@0: "Content-Type") == NULL) { michael@0: evhttp_add_header(req->output_headers, michael@0: "Content-Type", "text/html; charset=ISO-8859-1"); michael@0: } michael@0: } michael@0: michael@0: /* if the request asked for a close, we send a close, too */ michael@0: if (evhttp_is_connection_close(req->flags, req->input_headers)) { michael@0: evhttp_remove_header(req->output_headers, "Connection"); michael@0: if (!(req->flags & EVHTTP_PROXY_REQUEST)) michael@0: evhttp_add_header(req->output_headers, "Connection", "close"); michael@0: evhttp_remove_header(req->output_headers, "Proxy-Connection"); michael@0: } michael@0: } michael@0: michael@0: /** Generate all headers appropriate for sending the http request in req (or michael@0: * the response, if we're sending a response), and write them to evcon's michael@0: * bufferevent. Also writes all data from req->output_buffer */ michael@0: static void michael@0: evhttp_make_header(struct evhttp_connection *evcon, struct evhttp_request *req) michael@0: { michael@0: struct evkeyval *header; michael@0: struct evbuffer *output = bufferevent_get_output(evcon->bufev); michael@0: michael@0: /* michael@0: * Depending if this is a HTTP request or response, we might need to michael@0: * add some new headers or remove existing headers. michael@0: */ michael@0: if (req->kind == EVHTTP_REQUEST) { michael@0: evhttp_make_header_request(evcon, req); michael@0: } else { michael@0: evhttp_make_header_response(evcon, req); michael@0: } michael@0: michael@0: TAILQ_FOREACH(header, req->output_headers, next) { michael@0: evbuffer_add_printf(output, "%s: %s\r\n", michael@0: header->key, header->value); michael@0: } michael@0: evbuffer_add(output, "\r\n", 2); michael@0: michael@0: if (evbuffer_get_length(req->output_buffer) > 0) { michael@0: /* michael@0: * For a request, we add the POST data, for a reply, this michael@0: * is the regular data. michael@0: */ michael@0: /* XXX We might want to support waiting (a limited amount of michael@0: time) for a continue status line from the server before michael@0: sending POST/PUT message bodies. */ michael@0: evbuffer_add_buffer(output, req->output_buffer); michael@0: } michael@0: } michael@0: michael@0: void michael@0: evhttp_connection_set_max_headers_size(struct evhttp_connection *evcon, michael@0: ev_ssize_t new_max_headers_size) michael@0: { michael@0: if (new_max_headers_size<0) michael@0: evcon->max_headers_size = EV_SIZE_MAX; michael@0: else michael@0: evcon->max_headers_size = new_max_headers_size; michael@0: } michael@0: void michael@0: evhttp_connection_set_max_body_size(struct evhttp_connection* evcon, michael@0: ev_ssize_t new_max_body_size) michael@0: { michael@0: if (new_max_body_size<0) michael@0: evcon->max_body_size = EV_UINT64_MAX; michael@0: else michael@0: evcon->max_body_size = new_max_body_size; michael@0: } michael@0: michael@0: static int michael@0: evhttp_connection_incoming_fail(struct evhttp_request *req, michael@0: enum evhttp_connection_error error) michael@0: { michael@0: switch (error) { michael@0: case EVCON_HTTP_TIMEOUT: michael@0: case EVCON_HTTP_EOF: michael@0: /* michael@0: * these are cases in which we probably should just michael@0: * close the connection and not send a reply. this michael@0: * case may happen when a browser keeps a persistent michael@0: * connection open and we timeout on the read. when michael@0: * the request is still being used for sending, we michael@0: * need to disassociated it from the connection here. michael@0: */ michael@0: if (!req->userdone) { michael@0: /* remove it so that it will not be freed */ michael@0: TAILQ_REMOVE(&req->evcon->requests, req, next); michael@0: /* indicate that this request no longer has a michael@0: * connection object michael@0: */ michael@0: req->evcon = NULL; michael@0: } michael@0: return (-1); michael@0: case EVCON_HTTP_INVALID_HEADER: michael@0: case EVCON_HTTP_BUFFER_ERROR: michael@0: case EVCON_HTTP_REQUEST_CANCEL: michael@0: default: /* xxx: probably should just error on default */ michael@0: /* the callback looks at the uri to determine errors */ michael@0: if (req->uri) { michael@0: mm_free(req->uri); michael@0: req->uri = NULL; michael@0: } michael@0: if (req->uri_elems) { michael@0: evhttp_uri_free(req->uri_elems); michael@0: req->uri_elems = NULL; michael@0: } michael@0: michael@0: /* michael@0: * the callback needs to send a reply, once the reply has michael@0: * been send, the connection should get freed. michael@0: */ michael@0: (*req->cb)(req, req->cb_arg); michael@0: } michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: /* Called when evcon has experienced a (non-recoverable? -NM) error, as michael@0: * given in error. If it's an outgoing connection, reset the connection, michael@0: * retry any pending requests, and inform the user. If it's incoming, michael@0: * delegates to evhttp_connection_incoming_fail(). */ michael@0: void michael@0: evhttp_connection_fail(struct evhttp_connection *evcon, michael@0: enum evhttp_connection_error error) michael@0: { michael@0: struct evhttp_request* req = TAILQ_FIRST(&evcon->requests); michael@0: void (*cb)(struct evhttp_request *, void *); michael@0: void *cb_arg; michael@0: EVUTIL_ASSERT(req != NULL); michael@0: michael@0: bufferevent_disable(evcon->bufev, EV_READ|EV_WRITE); michael@0: michael@0: if (evcon->flags & EVHTTP_CON_INCOMING) { michael@0: /* michael@0: * for incoming requests, there are two different michael@0: * failure cases. it's either a network level error michael@0: * or an http layer error. for problems on the network michael@0: * layer like timeouts we just drop the connections. michael@0: * For HTTP problems, we might have to send back a michael@0: * reply before the connection can be freed. michael@0: */ michael@0: if (evhttp_connection_incoming_fail(req, error) == -1) michael@0: evhttp_connection_free(evcon); michael@0: return; michael@0: } michael@0: michael@0: /* when the request was canceled, the callback is not executed */ michael@0: if (error != EVCON_HTTP_REQUEST_CANCEL) { michael@0: /* save the callback for later; the cb might free our object */ michael@0: cb = req->cb; michael@0: cb_arg = req->cb_arg; michael@0: } else { michael@0: cb = NULL; michael@0: cb_arg = NULL; michael@0: } michael@0: michael@0: /* do not fail all requests; the next request is going to get michael@0: * send over a new connection. when a user cancels a request, michael@0: * all other pending requests should be processed as normal michael@0: */ michael@0: TAILQ_REMOVE(&evcon->requests, req, next); michael@0: evhttp_request_free(req); michael@0: michael@0: /* reset the connection */ michael@0: evhttp_connection_reset(evcon); michael@0: michael@0: /* We are trying the next request that was queued on us */ michael@0: if (TAILQ_FIRST(&evcon->requests) != NULL) michael@0: evhttp_connection_connect(evcon); michael@0: michael@0: /* inform the user */ michael@0: if (cb != NULL) michael@0: (*cb)(NULL, cb_arg); michael@0: } michael@0: michael@0: /* Bufferevent callback: invoked when any data has been written from an michael@0: * http connection's bufferevent */ michael@0: static void michael@0: evhttp_write_cb(struct bufferevent *bufev, void *arg) michael@0: { michael@0: struct evhttp_connection *evcon = arg; michael@0: michael@0: /* Activate our call back */ michael@0: if (evcon->cb != NULL) michael@0: (*evcon->cb)(evcon, evcon->cb_arg); michael@0: } michael@0: michael@0: /** michael@0: * Advance the connection state. michael@0: * - If this is an outgoing connection, we've just processed the response; michael@0: * idle or close the connection. michael@0: * - If this is an incoming connection, we've just processed the request; michael@0: * respond. michael@0: */ michael@0: static void michael@0: evhttp_connection_done(struct evhttp_connection *evcon) michael@0: { michael@0: struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); michael@0: int con_outgoing = evcon->flags & EVHTTP_CON_OUTGOING; michael@0: michael@0: if (con_outgoing) { michael@0: /* idle or close the connection */ michael@0: int need_close; michael@0: TAILQ_REMOVE(&evcon->requests, req, next); michael@0: req->evcon = NULL; michael@0: michael@0: evcon->state = EVCON_IDLE; michael@0: michael@0: need_close = michael@0: evhttp_is_connection_close(req->flags, req->input_headers)|| michael@0: evhttp_is_connection_close(req->flags, req->output_headers); michael@0: michael@0: /* check if we got asked to close the connection */ michael@0: if (need_close) michael@0: evhttp_connection_reset(evcon); michael@0: michael@0: if (TAILQ_FIRST(&evcon->requests) != NULL) { michael@0: /* michael@0: * We have more requests; reset the connection michael@0: * and deal with the next request. michael@0: */ michael@0: if (!evhttp_connected(evcon)) michael@0: evhttp_connection_connect(evcon); michael@0: else michael@0: evhttp_request_dispatch(evcon); michael@0: } else if (!need_close) { michael@0: /* michael@0: * The connection is going to be persistent, but we michael@0: * need to detect if the other side closes it. michael@0: */ michael@0: evhttp_connection_start_detectclose(evcon); michael@0: } michael@0: } else { michael@0: /* michael@0: * incoming connection - we need to leave the request on the michael@0: * connection so that we can reply to it. michael@0: */ michael@0: evcon->state = EVCON_WRITING; michael@0: } michael@0: michael@0: /* notify the user of the request */ michael@0: (*req->cb)(req, req->cb_arg); michael@0: michael@0: /* if this was an outgoing request, we own and it's done. so free it. michael@0: * unless the callback specifically requested to own the request. michael@0: */ michael@0: if (con_outgoing && ((req->flags & EVHTTP_USER_OWNED) == 0)) { michael@0: evhttp_request_free(req); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Handles reading from a chunked request. michael@0: * return ALL_DATA_READ: michael@0: * all data has been read michael@0: * return MORE_DATA_EXPECTED: michael@0: * more data is expected michael@0: * return DATA_CORRUPTED: michael@0: * data is corrupted michael@0: * return REQUEST_CANCELED: michael@0: * request was canceled by the user calling evhttp_cancel_request michael@0: * return DATA_TOO_LONG: michael@0: * ran over the maximum limit michael@0: */ michael@0: michael@0: static enum message_read_status michael@0: evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf) michael@0: { michael@0: if (req == NULL || buf == NULL) { michael@0: return DATA_CORRUPTED; michael@0: } michael@0: michael@0: while (1) { michael@0: size_t buflen; michael@0: michael@0: if ((buflen = evbuffer_get_length(buf)) == 0) { michael@0: break; michael@0: } michael@0: michael@0: /* evbuffer_get_length returns size_t, but len variable is ssize_t, michael@0: * check for overflow conditions */ michael@0: if (buflen > EV_SSIZE_MAX) { michael@0: return DATA_CORRUPTED; michael@0: } michael@0: michael@0: if (req->ntoread < 0) { michael@0: /* Read chunk size */ michael@0: ev_int64_t ntoread; michael@0: char *p = evbuffer_readln(buf, NULL, EVBUFFER_EOL_CRLF); michael@0: char *endp; michael@0: int error; michael@0: if (p == NULL) michael@0: break; michael@0: /* the last chunk is on a new line? */ michael@0: if (strlen(p) == 0) { michael@0: mm_free(p); michael@0: continue; michael@0: } michael@0: ntoread = evutil_strtoll(p, &endp, 16); michael@0: error = (*p == '\0' || michael@0: (*endp != '\0' && *endp != ' ') || michael@0: ntoread < 0); michael@0: mm_free(p); michael@0: if (error) { michael@0: /* could not get chunk size */ michael@0: return (DATA_CORRUPTED); michael@0: } michael@0: michael@0: /* ntoread is signed int64, body_size is unsigned size_t, check for under/overflow conditions */ michael@0: if ((ev_uint64_t)ntoread > EV_SIZE_MAX - req->body_size) { michael@0: return DATA_CORRUPTED; michael@0: } michael@0: michael@0: if (req->body_size + (size_t)ntoread > req->evcon->max_body_size) { michael@0: /* failed body length test */ michael@0: event_debug(("Request body is too long")); michael@0: return (DATA_TOO_LONG); michael@0: } michael@0: michael@0: req->body_size += (size_t)ntoread; michael@0: req->ntoread = ntoread; michael@0: if (req->ntoread == 0) { michael@0: /* Last chunk */ michael@0: return (ALL_DATA_READ); michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: /* req->ntoread is signed int64, len is ssize_t, based on arch, michael@0: * ssize_t could only be 32b, check for these conditions */ michael@0: if (req->ntoread > EV_SSIZE_MAX) { michael@0: return DATA_CORRUPTED; michael@0: } michael@0: michael@0: /* don't have enough to complete a chunk; wait for more */ michael@0: if (req->ntoread > 0 && buflen < (ev_uint64_t)req->ntoread) michael@0: return (MORE_DATA_EXPECTED); michael@0: michael@0: /* Completed chunk */ michael@0: evbuffer_remove_buffer(buf, req->input_buffer, (size_t)req->ntoread); michael@0: req->ntoread = -1; michael@0: if (req->chunk_cb != NULL) { michael@0: req->flags |= EVHTTP_REQ_DEFER_FREE; michael@0: (*req->chunk_cb)(req, req->cb_arg); michael@0: evbuffer_drain(req->input_buffer, michael@0: evbuffer_get_length(req->input_buffer)); michael@0: req->flags &= ~EVHTTP_REQ_DEFER_FREE; michael@0: if ((req->flags & EVHTTP_REQ_NEEDS_FREE) != 0) { michael@0: return (REQUEST_CANCELED); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return (MORE_DATA_EXPECTED); michael@0: } michael@0: michael@0: static void michael@0: evhttp_read_trailer(struct evhttp_connection *evcon, struct evhttp_request *req) michael@0: { michael@0: struct evbuffer *buf = bufferevent_get_input(evcon->bufev); michael@0: michael@0: switch (evhttp_parse_headers(req, buf)) { michael@0: case DATA_CORRUPTED: michael@0: case DATA_TOO_LONG: michael@0: evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); michael@0: break; michael@0: case ALL_DATA_READ: michael@0: bufferevent_disable(evcon->bufev, EV_READ); michael@0: evhttp_connection_done(evcon); michael@0: break; michael@0: case MORE_DATA_EXPECTED: michael@0: case REQUEST_CANCELED: /* ??? */ michael@0: default: michael@0: bufferevent_enable(evcon->bufev, EV_READ); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req) michael@0: { michael@0: struct evbuffer *buf = bufferevent_get_input(evcon->bufev); michael@0: michael@0: if (req->chunked) { michael@0: switch (evhttp_handle_chunked_read(req, buf)) { michael@0: case ALL_DATA_READ: michael@0: /* finished last chunk */ michael@0: evcon->state = EVCON_READING_TRAILER; michael@0: evhttp_read_trailer(evcon, req); michael@0: return; michael@0: case DATA_CORRUPTED: michael@0: case DATA_TOO_LONG:/*separate error for this? XXX */ michael@0: /* corrupted data */ michael@0: evhttp_connection_fail(evcon, michael@0: EVCON_HTTP_INVALID_HEADER); michael@0: return; michael@0: case REQUEST_CANCELED: michael@0: /* request canceled */ michael@0: evhttp_request_free(req); michael@0: return; michael@0: case MORE_DATA_EXPECTED: michael@0: default: michael@0: break; michael@0: } michael@0: } else if (req->ntoread < 0) { michael@0: /* Read until connection close. */ michael@0: if ((size_t)(req->body_size + evbuffer_get_length(buf)) < req->body_size) { michael@0: evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); michael@0: return; michael@0: } michael@0: michael@0: req->body_size += evbuffer_get_length(buf); michael@0: evbuffer_add_buffer(req->input_buffer, buf); michael@0: } else if (req->chunk_cb != NULL || evbuffer_get_length(buf) >= (size_t)req->ntoread) { michael@0: /* XXX: the above get_length comparison has to be fixed for overflow conditions! */ michael@0: /* We've postponed moving the data until now, but we're michael@0: * about to use it. */ michael@0: size_t n = evbuffer_get_length(buf); michael@0: michael@0: if (n > (size_t) req->ntoread) michael@0: n = (size_t) req->ntoread; michael@0: req->ntoread -= n; michael@0: req->body_size += n; michael@0: evbuffer_remove_buffer(buf, req->input_buffer, n); michael@0: } michael@0: michael@0: if (req->body_size > req->evcon->max_body_size || michael@0: (!req->chunked && req->ntoread >= 0 && michael@0: (size_t)req->ntoread > req->evcon->max_body_size)) { michael@0: /* XXX: The above casted comparison must checked for overflow */ michael@0: /* failed body length test */ michael@0: event_debug(("Request body is too long")); michael@0: evhttp_connection_fail(evcon, michael@0: EVCON_HTTP_INVALID_HEADER); michael@0: return; michael@0: } michael@0: michael@0: if (evbuffer_get_length(req->input_buffer) > 0 && req->chunk_cb != NULL) { michael@0: req->flags |= EVHTTP_REQ_DEFER_FREE; michael@0: (*req->chunk_cb)(req, req->cb_arg); michael@0: req->flags &= ~EVHTTP_REQ_DEFER_FREE; michael@0: evbuffer_drain(req->input_buffer, michael@0: evbuffer_get_length(req->input_buffer)); michael@0: if ((req->flags & EVHTTP_REQ_NEEDS_FREE) != 0) { michael@0: evhttp_request_free(req); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (req->ntoread == 0) { michael@0: bufferevent_disable(evcon->bufev, EV_READ); michael@0: /* Completed content length */ michael@0: evhttp_connection_done(evcon); michael@0: return; michael@0: } michael@0: michael@0: /* Read more! */ michael@0: bufferevent_enable(evcon->bufev, EV_READ); michael@0: } michael@0: michael@0: #define get_deferred_queue(evcon) \ michael@0: (event_base_get_deferred_cb_queue((evcon)->base)) michael@0: michael@0: /* michael@0: * Gets called when more data becomes available michael@0: */ michael@0: michael@0: static void michael@0: evhttp_read_cb(struct bufferevent *bufev, void *arg) michael@0: { michael@0: struct evhttp_connection *evcon = arg; michael@0: struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); michael@0: michael@0: /* Cancel if it's pending. */ michael@0: event_deferred_cb_cancel(get_deferred_queue(evcon), michael@0: &evcon->read_more_deferred_cb); michael@0: michael@0: switch (evcon->state) { michael@0: case EVCON_READING_FIRSTLINE: michael@0: evhttp_read_firstline(evcon, req); michael@0: /* note the request may have been freed in michael@0: * evhttp_read_body */ michael@0: break; michael@0: case EVCON_READING_HEADERS: michael@0: evhttp_read_header(evcon, req); michael@0: /* note the request may have been freed in michael@0: * evhttp_read_body */ michael@0: break; michael@0: case EVCON_READING_BODY: michael@0: evhttp_read_body(evcon, req); michael@0: /* note the request may have been freed in michael@0: * evhttp_read_body */ michael@0: break; michael@0: case EVCON_READING_TRAILER: michael@0: evhttp_read_trailer(evcon, req); michael@0: break; michael@0: case EVCON_IDLE: michael@0: { michael@0: #ifdef USE_DEBUG michael@0: struct evbuffer *input; michael@0: size_t total_len; michael@0: michael@0: input = bufferevent_get_input(evcon->bufev); michael@0: total_len = evbuffer_get_length(input); michael@0: event_debug(("%s: read "EV_SIZE_FMT michael@0: " bytes in EVCON_IDLE state," michael@0: " resetting connection", michael@0: __func__, EV_SIZE_ARG(total_len))); michael@0: #endif michael@0: michael@0: evhttp_connection_reset(evcon); michael@0: } michael@0: break; michael@0: case EVCON_DISCONNECTED: michael@0: case EVCON_CONNECTING: michael@0: case EVCON_WRITING: michael@0: default: michael@0: event_errx(1, "%s: illegal connection state %d", michael@0: __func__, evcon->state); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: evhttp_deferred_read_cb(struct deferred_cb *cb, void *data) michael@0: { michael@0: struct evhttp_connection *evcon = data; michael@0: evhttp_read_cb(evcon->bufev, evcon); michael@0: } michael@0: michael@0: static void michael@0: evhttp_write_connectioncb(struct evhttp_connection *evcon, void *arg) michael@0: { michael@0: /* This is after writing the request to the server */ michael@0: struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); michael@0: EVUTIL_ASSERT(req != NULL); michael@0: michael@0: EVUTIL_ASSERT(evcon->state == EVCON_WRITING); michael@0: michael@0: /* We are done writing our header and are now expecting the response */ michael@0: req->kind = EVHTTP_RESPONSE; michael@0: michael@0: evhttp_start_read(evcon); michael@0: } michael@0: michael@0: /* michael@0: * Clean up a connection object michael@0: */ michael@0: michael@0: void michael@0: evhttp_connection_free(struct evhttp_connection *evcon) michael@0: { michael@0: struct evhttp_request *req; michael@0: michael@0: /* notify interested parties that this connection is going down */ michael@0: if (evcon->fd != -1) { michael@0: if (evhttp_connected(evcon) && evcon->closecb != NULL) michael@0: (*evcon->closecb)(evcon, evcon->closecb_arg); michael@0: } michael@0: michael@0: /* remove all requests that might be queued on this michael@0: * connection. for server connections, this should be empty. michael@0: * because it gets dequeued either in evhttp_connection_done or michael@0: * evhttp_connection_fail. michael@0: */ michael@0: while ((req = TAILQ_FIRST(&evcon->requests)) != NULL) { michael@0: TAILQ_REMOVE(&evcon->requests, req, next); michael@0: evhttp_request_free(req); michael@0: } michael@0: michael@0: if (evcon->http_server != NULL) { michael@0: struct evhttp *http = evcon->http_server; michael@0: TAILQ_REMOVE(&http->connections, evcon, next); michael@0: } michael@0: michael@0: if (event_initialized(&evcon->retry_ev)) { michael@0: event_del(&evcon->retry_ev); michael@0: event_debug_unassign(&evcon->retry_ev); michael@0: } michael@0: michael@0: if (evcon->bufev != NULL) michael@0: bufferevent_free(evcon->bufev); michael@0: michael@0: event_deferred_cb_cancel(get_deferred_queue(evcon), michael@0: &evcon->read_more_deferred_cb); michael@0: michael@0: if (evcon->fd != -1) { michael@0: shutdown(evcon->fd, EVUTIL_SHUT_WR); michael@0: evutil_closesocket(evcon->fd); michael@0: } michael@0: michael@0: if (evcon->bind_address != NULL) michael@0: mm_free(evcon->bind_address); michael@0: michael@0: if (evcon->address != NULL) michael@0: mm_free(evcon->address); michael@0: michael@0: mm_free(evcon); michael@0: } michael@0: michael@0: void michael@0: evhttp_connection_set_local_address(struct evhttp_connection *evcon, michael@0: const char *address) michael@0: { michael@0: EVUTIL_ASSERT(evcon->state == EVCON_DISCONNECTED); michael@0: if (evcon->bind_address) michael@0: mm_free(evcon->bind_address); michael@0: if ((evcon->bind_address = mm_strdup(address)) == NULL) michael@0: event_warn("%s: strdup", __func__); michael@0: } michael@0: michael@0: void michael@0: evhttp_connection_set_local_port(struct evhttp_connection *evcon, michael@0: ev_uint16_t port) michael@0: { michael@0: EVUTIL_ASSERT(evcon->state == EVCON_DISCONNECTED); michael@0: evcon->bind_port = port; michael@0: } michael@0: michael@0: static void michael@0: evhttp_request_dispatch(struct evhttp_connection* evcon) michael@0: { michael@0: struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); michael@0: michael@0: /* this should not usually happy but it's possible */ michael@0: if (req == NULL) michael@0: return; michael@0: michael@0: /* delete possible close detection events */ michael@0: evhttp_connection_stop_detectclose(evcon); michael@0: michael@0: /* we assume that the connection is connected already */ michael@0: EVUTIL_ASSERT(evcon->state == EVCON_IDLE); michael@0: michael@0: evcon->state = EVCON_WRITING; michael@0: michael@0: /* Create the header from the store arguments */ michael@0: evhttp_make_header(evcon, req); michael@0: michael@0: evhttp_write_buffer(evcon, evhttp_write_connectioncb, NULL); michael@0: } michael@0: michael@0: /* Reset our connection state: disables reading/writing, closes our fd (if michael@0: * any), clears out buffers, and puts us in state DISCONNECTED. */ michael@0: void michael@0: evhttp_connection_reset(struct evhttp_connection *evcon) michael@0: { michael@0: struct evbuffer *tmp; michael@0: michael@0: /* XXXX This is not actually an optimal fix. Instead we ought to have michael@0: an API for "stop connecting", or use bufferevent_setfd to turn off michael@0: connecting. But for Libevent 2.0, this seems like a minimal change michael@0: least likely to disrupt the rest of the bufferevent and http code. michael@0: michael@0: Why is this here? If the fd is set in the bufferevent, and the michael@0: bufferevent is connecting, then you can't actually stop the michael@0: bufferevent from trying to connect with bufferevent_disable(). The michael@0: connect will never trigger, since we close the fd, but the timeout michael@0: might. That caused an assertion failure in evhttp_connection_fail. michael@0: */ michael@0: bufferevent_disable_hard(evcon->bufev, EV_READ|EV_WRITE); michael@0: michael@0: if (evcon->fd != -1) { michael@0: /* inform interested parties about connection close */ michael@0: if (evhttp_connected(evcon) && evcon->closecb != NULL) michael@0: (*evcon->closecb)(evcon, evcon->closecb_arg); michael@0: michael@0: shutdown(evcon->fd, EVUTIL_SHUT_WR); michael@0: evutil_closesocket(evcon->fd); michael@0: evcon->fd = -1; michael@0: } michael@0: michael@0: /* we need to clean up any buffered data */ michael@0: tmp = bufferevent_get_output(evcon->bufev); michael@0: evbuffer_drain(tmp, evbuffer_get_length(tmp)); michael@0: tmp = bufferevent_get_input(evcon->bufev); michael@0: evbuffer_drain(tmp, evbuffer_get_length(tmp)); michael@0: michael@0: evcon->state = EVCON_DISCONNECTED; michael@0: } michael@0: michael@0: static void michael@0: evhttp_connection_start_detectclose(struct evhttp_connection *evcon) michael@0: { michael@0: evcon->flags |= EVHTTP_CON_CLOSEDETECT; michael@0: michael@0: bufferevent_enable(evcon->bufev, EV_READ); michael@0: } michael@0: michael@0: static void michael@0: evhttp_connection_stop_detectclose(struct evhttp_connection *evcon) michael@0: { michael@0: evcon->flags &= ~EVHTTP_CON_CLOSEDETECT; michael@0: michael@0: bufferevent_disable(evcon->bufev, EV_READ); michael@0: } michael@0: michael@0: static void michael@0: evhttp_connection_retry(evutil_socket_t fd, short what, void *arg) michael@0: { michael@0: struct evhttp_connection *evcon = arg; michael@0: michael@0: evcon->state = EVCON_DISCONNECTED; michael@0: evhttp_connection_connect(evcon); michael@0: } michael@0: michael@0: static void michael@0: evhttp_connection_cb_cleanup(struct evhttp_connection *evcon) michael@0: { michael@0: struct evcon_requestq requests; michael@0: michael@0: if (evcon->retry_max < 0 || evcon->retry_cnt < evcon->retry_max) { michael@0: evtimer_assign(&evcon->retry_ev, evcon->base, evhttp_connection_retry, evcon); michael@0: /* XXXX handle failure from evhttp_add_event */ michael@0: evhttp_add_event(&evcon->retry_ev, michael@0: MIN(3600, 2 << evcon->retry_cnt), michael@0: HTTP_CONNECT_TIMEOUT); michael@0: evcon->retry_cnt++; michael@0: return; michael@0: } michael@0: evhttp_connection_reset(evcon); michael@0: michael@0: /* michael@0: * User callback can do evhttp_make_request() on the same michael@0: * evcon so new request will be added to evcon->requests. To michael@0: * avoid freeing it prematurely we iterate over the copy of michael@0: * the queue. michael@0: */ michael@0: TAILQ_INIT(&requests); michael@0: while (TAILQ_FIRST(&evcon->requests) != NULL) { michael@0: struct evhttp_request *request = TAILQ_FIRST(&evcon->requests); michael@0: TAILQ_REMOVE(&evcon->requests, request, next); michael@0: TAILQ_INSERT_TAIL(&requests, request, next); michael@0: } michael@0: michael@0: /* for now, we just signal all requests by executing their callbacks */ michael@0: while (TAILQ_FIRST(&requests) != NULL) { michael@0: struct evhttp_request *request = TAILQ_FIRST(&requests); michael@0: TAILQ_REMOVE(&requests, request, next); michael@0: request->evcon = NULL; michael@0: michael@0: /* we might want to set an error here */ michael@0: request->cb(request, request->cb_arg); michael@0: evhttp_request_free(request); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: evhttp_error_cb(struct bufferevent *bufev, short what, void *arg) michael@0: { michael@0: struct evhttp_connection *evcon = arg; michael@0: struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); michael@0: michael@0: switch (evcon->state) { michael@0: case EVCON_CONNECTING: michael@0: if (what & BEV_EVENT_TIMEOUT) { michael@0: event_debug(("%s: connection timeout for \"%s:%d\" on " michael@0: EV_SOCK_FMT, michael@0: __func__, evcon->address, evcon->port, michael@0: EV_SOCK_ARG(evcon->fd))); michael@0: evhttp_connection_cb_cleanup(evcon); michael@0: return; michael@0: } michael@0: break; michael@0: michael@0: case EVCON_READING_BODY: michael@0: if (!req->chunked && req->ntoread < 0 michael@0: && what == (BEV_EVENT_READING|BEV_EVENT_EOF)) { michael@0: /* EOF on read can be benign */ michael@0: evhttp_connection_done(evcon); michael@0: return; michael@0: } michael@0: break; michael@0: michael@0: case EVCON_DISCONNECTED: michael@0: case EVCON_IDLE: michael@0: case EVCON_READING_FIRSTLINE: michael@0: case EVCON_READING_HEADERS: michael@0: case EVCON_READING_TRAILER: michael@0: case EVCON_WRITING: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: /* when we are in close detect mode, a read error means that michael@0: * the other side closed their connection. michael@0: */ michael@0: if (evcon->flags & EVHTTP_CON_CLOSEDETECT) { michael@0: evcon->flags &= ~EVHTTP_CON_CLOSEDETECT; michael@0: EVUTIL_ASSERT(evcon->http_server == NULL); michael@0: /* For connections from the client, we just michael@0: * reset the connection so that it becomes michael@0: * disconnected. michael@0: */ michael@0: EVUTIL_ASSERT(evcon->state == EVCON_IDLE); michael@0: evhttp_connection_reset(evcon); michael@0: return; michael@0: } michael@0: michael@0: if (what & BEV_EVENT_TIMEOUT) { michael@0: evhttp_connection_fail(evcon, EVCON_HTTP_TIMEOUT); michael@0: } else if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { michael@0: evhttp_connection_fail(evcon, EVCON_HTTP_EOF); michael@0: } else { michael@0: evhttp_connection_fail(evcon, EVCON_HTTP_BUFFER_ERROR); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Event callback for asynchronous connection attempt. michael@0: */ michael@0: static void michael@0: evhttp_connection_cb(struct bufferevent *bufev, short what, void *arg) michael@0: { michael@0: struct evhttp_connection *evcon = arg; michael@0: int error; michael@0: ev_socklen_t errsz = sizeof(error); michael@0: michael@0: if (!(what & BEV_EVENT_CONNECTED)) { michael@0: /* some operating systems return ECONNREFUSED immediately michael@0: * when connecting to a local address. the cleanup is going michael@0: * to reschedule this function call. michael@0: */ michael@0: #ifndef WIN32 michael@0: if (errno == ECONNREFUSED) michael@0: goto cleanup; michael@0: #endif michael@0: evhttp_error_cb(bufev, what, arg); michael@0: return; michael@0: } michael@0: michael@0: /* Check if the connection completed */ michael@0: if (getsockopt(evcon->fd, SOL_SOCKET, SO_ERROR, (void*)&error, michael@0: &errsz) == -1) { michael@0: event_debug(("%s: getsockopt for \"%s:%d\" on "EV_SOCK_FMT, michael@0: __func__, evcon->address, evcon->port, michael@0: EV_SOCK_ARG(evcon->fd))); michael@0: goto cleanup; michael@0: } michael@0: michael@0: if (error) { michael@0: event_debug(("%s: connect failed for \"%s:%d\" on " michael@0: EV_SOCK_FMT": %s", michael@0: __func__, evcon->address, evcon->port, michael@0: EV_SOCK_ARG(evcon->fd), michael@0: evutil_socket_error_to_string(error))); michael@0: goto cleanup; michael@0: } michael@0: michael@0: /* We are connected to the server now */ michael@0: event_debug(("%s: connected to \"%s:%d\" on "EV_SOCK_FMT"\n", michael@0: __func__, evcon->address, evcon->port, michael@0: EV_SOCK_ARG(evcon->fd))); michael@0: michael@0: /* Reset the retry count as we were successful in connecting */ michael@0: evcon->retry_cnt = 0; michael@0: evcon->state = EVCON_IDLE; michael@0: michael@0: /* reset the bufferevent cbs */ michael@0: bufferevent_setcb(evcon->bufev, michael@0: evhttp_read_cb, michael@0: evhttp_write_cb, michael@0: evhttp_error_cb, michael@0: evcon); michael@0: michael@0: if (evcon->timeout == -1) michael@0: bufferevent_settimeout(evcon->bufev, michael@0: HTTP_READ_TIMEOUT, HTTP_WRITE_TIMEOUT); michael@0: else { michael@0: struct timeval tv; michael@0: tv.tv_sec = evcon->timeout; michael@0: tv.tv_usec = 0; michael@0: bufferevent_set_timeouts(evcon->bufev, &tv, &tv); michael@0: } michael@0: michael@0: /* try to start requests that have queued up on this connection */ michael@0: evhttp_request_dispatch(evcon); michael@0: return; michael@0: michael@0: cleanup: michael@0: evhttp_connection_cb_cleanup(evcon); michael@0: } michael@0: michael@0: /* michael@0: * Check if we got a valid response code. michael@0: */ michael@0: michael@0: static int michael@0: evhttp_valid_response_code(int code) michael@0: { michael@0: if (code == 0) michael@0: return (0); michael@0: michael@0: return (1); michael@0: } michael@0: michael@0: static int michael@0: evhttp_parse_http_version(const char *version, struct evhttp_request *req) michael@0: { michael@0: int major, minor; michael@0: char ch; michael@0: int n = sscanf(version, "HTTP/%d.%d%c", &major, &minor, &ch); michael@0: if (n != 2 || major > 1) { michael@0: event_debug(("%s: bad version %s on message %p from %s", michael@0: __func__, version, req, req->remote_host)); michael@0: return (-1); michael@0: } michael@0: req->major = major; michael@0: req->minor = minor; michael@0: return (0); michael@0: } michael@0: michael@0: /* Parses the status line of a web server */ michael@0: michael@0: static int michael@0: evhttp_parse_response_line(struct evhttp_request *req, char *line) michael@0: { michael@0: char *protocol; michael@0: char *number; michael@0: const char *readable = ""; michael@0: michael@0: protocol = strsep(&line, " "); michael@0: if (line == NULL) michael@0: return (-1); michael@0: number = strsep(&line, " "); michael@0: if (line != NULL) michael@0: readable = line; michael@0: michael@0: if (evhttp_parse_http_version(protocol, req) < 0) michael@0: return (-1); michael@0: michael@0: req->response_code = atoi(number); michael@0: if (!evhttp_valid_response_code(req->response_code)) { michael@0: event_debug(("%s: bad response code \"%s\"", michael@0: __func__, number)); michael@0: return (-1); michael@0: } michael@0: michael@0: if ((req->response_code_line = mm_strdup(readable)) == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: return (-1); michael@0: } michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: /* Parse the first line of a HTTP request */ michael@0: michael@0: static int michael@0: evhttp_parse_request_line(struct evhttp_request *req, char *line) michael@0: { michael@0: char *method; michael@0: char *uri; michael@0: char *version; michael@0: const char *hostname; michael@0: const char *scheme; michael@0: michael@0: /* Parse the request line */ michael@0: method = strsep(&line, " "); michael@0: if (line == NULL) michael@0: return (-1); michael@0: uri = strsep(&line, " "); michael@0: if (line == NULL) michael@0: return (-1); michael@0: version = strsep(&line, " "); michael@0: if (line != NULL) michael@0: return (-1); michael@0: michael@0: /* First line */ michael@0: if (strcmp(method, "GET") == 0) { michael@0: req->type = EVHTTP_REQ_GET; michael@0: } else if (strcmp(method, "POST") == 0) { michael@0: req->type = EVHTTP_REQ_POST; michael@0: } else if (strcmp(method, "HEAD") == 0) { michael@0: req->type = EVHTTP_REQ_HEAD; michael@0: } else if (strcmp(method, "PUT") == 0) { michael@0: req->type = EVHTTP_REQ_PUT; michael@0: } else if (strcmp(method, "DELETE") == 0) { michael@0: req->type = EVHTTP_REQ_DELETE; michael@0: } else if (strcmp(method, "OPTIONS") == 0) { michael@0: req->type = EVHTTP_REQ_OPTIONS; michael@0: } else if (strcmp(method, "TRACE") == 0) { michael@0: req->type = EVHTTP_REQ_TRACE; michael@0: } else if (strcmp(method, "PATCH") == 0) { michael@0: req->type = EVHTTP_REQ_PATCH; michael@0: } else { michael@0: req->type = _EVHTTP_REQ_UNKNOWN; michael@0: event_debug(("%s: bad method %s on request %p from %s", michael@0: __func__, method, req, req->remote_host)); michael@0: /* No error yet; we'll give a better error later when michael@0: * we see that req->type is unsupported. */ michael@0: } michael@0: michael@0: if (evhttp_parse_http_version(version, req) < 0) michael@0: return (-1); michael@0: michael@0: if ((req->uri = mm_strdup(uri)) == NULL) { michael@0: event_debug(("%s: mm_strdup", __func__)); michael@0: return (-1); michael@0: } michael@0: michael@0: if ((req->uri_elems = evhttp_uri_parse_with_flags(req->uri, michael@0: EVHTTP_URI_NONCONFORMANT)) == NULL) { michael@0: return -1; michael@0: } michael@0: michael@0: /* If we have an absolute-URI, check to see if it is an http request michael@0: for a known vhost or server alias. If we don't know about this michael@0: host, we consider it a proxy request. */ michael@0: scheme = evhttp_uri_get_scheme(req->uri_elems); michael@0: hostname = evhttp_uri_get_host(req->uri_elems); michael@0: if (scheme && (!evutil_ascii_strcasecmp(scheme, "http") || michael@0: !evutil_ascii_strcasecmp(scheme, "https")) && michael@0: hostname && michael@0: !evhttp_find_vhost(req->evcon->http_server, NULL, hostname)) michael@0: req->flags |= EVHTTP_PROXY_REQUEST; michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: const char * michael@0: evhttp_find_header(const struct evkeyvalq *headers, const char *key) michael@0: { michael@0: struct evkeyval *header; michael@0: michael@0: TAILQ_FOREACH(header, headers, next) { michael@0: if (evutil_ascii_strcasecmp(header->key, key) == 0) michael@0: return (header->value); michael@0: } michael@0: michael@0: return (NULL); michael@0: } michael@0: michael@0: void michael@0: evhttp_clear_headers(struct evkeyvalq *headers) michael@0: { michael@0: struct evkeyval *header; michael@0: michael@0: for (header = TAILQ_FIRST(headers); michael@0: header != NULL; michael@0: header = TAILQ_FIRST(headers)) { michael@0: TAILQ_REMOVE(headers, header, next); michael@0: mm_free(header->key); michael@0: mm_free(header->value); michael@0: mm_free(header); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Returns 0, if the header was successfully removed. michael@0: * Returns -1, if the header could not be found. michael@0: */ michael@0: michael@0: int michael@0: evhttp_remove_header(struct evkeyvalq *headers, const char *key) michael@0: { michael@0: struct evkeyval *header; michael@0: michael@0: TAILQ_FOREACH(header, headers, next) { michael@0: if (evutil_ascii_strcasecmp(header->key, key) == 0) michael@0: break; michael@0: } michael@0: michael@0: if (header == NULL) michael@0: return (-1); michael@0: michael@0: /* Free and remove the header that we found */ michael@0: TAILQ_REMOVE(headers, header, next); michael@0: mm_free(header->key); michael@0: mm_free(header->value); michael@0: mm_free(header); michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: static int michael@0: evhttp_header_is_valid_value(const char *value) michael@0: { michael@0: const char *p = value; michael@0: michael@0: while ((p = strpbrk(p, "\r\n")) != NULL) { michael@0: /* we really expect only one new line */ michael@0: p += strspn(p, "\r\n"); michael@0: /* we expect a space or tab for continuation */ michael@0: if (*p != ' ' && *p != '\t') michael@0: return (0); michael@0: } michael@0: return (1); michael@0: } michael@0: michael@0: int michael@0: evhttp_add_header(struct evkeyvalq *headers, michael@0: const char *key, const char *value) michael@0: { michael@0: event_debug(("%s: key: %s val: %s\n", __func__, key, value)); michael@0: michael@0: if (strchr(key, '\r') != NULL || strchr(key, '\n') != NULL) { michael@0: /* drop illegal headers */ michael@0: event_debug(("%s: dropping illegal header key\n", __func__)); michael@0: return (-1); michael@0: } michael@0: michael@0: if (!evhttp_header_is_valid_value(value)) { michael@0: event_debug(("%s: dropping illegal header value\n", __func__)); michael@0: return (-1); michael@0: } michael@0: michael@0: return (evhttp_add_header_internal(headers, key, value)); michael@0: } michael@0: michael@0: static int michael@0: evhttp_add_header_internal(struct evkeyvalq *headers, michael@0: const char *key, const char *value) michael@0: { michael@0: struct evkeyval *header = mm_calloc(1, sizeof(struct evkeyval)); michael@0: if (header == NULL) { michael@0: event_warn("%s: calloc", __func__); michael@0: return (-1); michael@0: } michael@0: if ((header->key = mm_strdup(key)) == NULL) { michael@0: mm_free(header); michael@0: event_warn("%s: strdup", __func__); michael@0: return (-1); michael@0: } michael@0: if ((header->value = mm_strdup(value)) == NULL) { michael@0: mm_free(header->key); michael@0: mm_free(header); michael@0: event_warn("%s: strdup", __func__); michael@0: return (-1); michael@0: } michael@0: michael@0: TAILQ_INSERT_TAIL(headers, header, next); michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: /* michael@0: * Parses header lines from a request or a response into the specified michael@0: * request object given an event buffer. michael@0: * michael@0: * Returns michael@0: * DATA_CORRUPTED on error michael@0: * MORE_DATA_EXPECTED when we need to read more headers michael@0: * ALL_DATA_READ when all headers have been read. michael@0: */ michael@0: michael@0: enum message_read_status michael@0: evhttp_parse_firstline(struct evhttp_request *req, struct evbuffer *buffer) michael@0: { michael@0: char *line; michael@0: enum message_read_status status = ALL_DATA_READ; michael@0: michael@0: size_t line_length; michael@0: /* XXX try */ michael@0: line = evbuffer_readln(buffer, &line_length, EVBUFFER_EOL_CRLF); michael@0: if (line == NULL) { michael@0: if (req->evcon != NULL && michael@0: evbuffer_get_length(buffer) > req->evcon->max_headers_size) michael@0: return (DATA_TOO_LONG); michael@0: else michael@0: return (MORE_DATA_EXPECTED); michael@0: } michael@0: michael@0: if (req->evcon != NULL && michael@0: line_length > req->evcon->max_headers_size) { michael@0: mm_free(line); michael@0: return (DATA_TOO_LONG); michael@0: } michael@0: michael@0: req->headers_size = line_length; michael@0: michael@0: switch (req->kind) { michael@0: case EVHTTP_REQUEST: michael@0: if (evhttp_parse_request_line(req, line) == -1) michael@0: status = DATA_CORRUPTED; michael@0: break; michael@0: case EVHTTP_RESPONSE: michael@0: if (evhttp_parse_response_line(req, line) == -1) michael@0: status = DATA_CORRUPTED; michael@0: break; michael@0: default: michael@0: status = DATA_CORRUPTED; michael@0: } michael@0: michael@0: mm_free(line); michael@0: return (status); michael@0: } michael@0: michael@0: static int michael@0: evhttp_append_to_last_header(struct evkeyvalq *headers, const char *line) michael@0: { michael@0: struct evkeyval *header = TAILQ_LAST(headers, evkeyvalq); michael@0: char *newval; michael@0: size_t old_len, line_len; michael@0: michael@0: if (header == NULL) michael@0: return (-1); michael@0: michael@0: old_len = strlen(header->value); michael@0: line_len = strlen(line); michael@0: michael@0: newval = mm_realloc(header->value, old_len + line_len + 1); michael@0: if (newval == NULL) michael@0: return (-1); michael@0: michael@0: memcpy(newval + old_len, line, line_len + 1); michael@0: header->value = newval; michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: enum message_read_status michael@0: evhttp_parse_headers(struct evhttp_request *req, struct evbuffer* buffer) michael@0: { michael@0: enum message_read_status errcode = DATA_CORRUPTED; michael@0: char *line; michael@0: enum message_read_status status = MORE_DATA_EXPECTED; michael@0: michael@0: struct evkeyvalq* headers = req->input_headers; michael@0: size_t line_length; michael@0: while ((line = evbuffer_readln(buffer, &line_length, EVBUFFER_EOL_CRLF)) michael@0: != NULL) { michael@0: char *skey, *svalue; michael@0: michael@0: req->headers_size += line_length; michael@0: michael@0: if (req->evcon != NULL && michael@0: req->headers_size > req->evcon->max_headers_size) { michael@0: errcode = DATA_TOO_LONG; michael@0: goto error; michael@0: } michael@0: michael@0: if (*line == '\0') { /* Last header - Done */ michael@0: status = ALL_DATA_READ; michael@0: mm_free(line); michael@0: break; michael@0: } michael@0: michael@0: /* Check if this is a continuation line */ michael@0: if (*line == ' ' || *line == '\t') { michael@0: if (evhttp_append_to_last_header(headers, line) == -1) michael@0: goto error; michael@0: mm_free(line); michael@0: continue; michael@0: } michael@0: michael@0: /* Processing of header lines */ michael@0: svalue = line; michael@0: skey = strsep(&svalue, ":"); michael@0: if (svalue == NULL) michael@0: goto error; michael@0: michael@0: svalue += strspn(svalue, " "); michael@0: michael@0: if (evhttp_add_header(headers, skey, svalue) == -1) michael@0: goto error; michael@0: michael@0: mm_free(line); michael@0: } michael@0: michael@0: if (status == MORE_DATA_EXPECTED) { michael@0: if (req->evcon != NULL && michael@0: req->headers_size + evbuffer_get_length(buffer) > req->evcon->max_headers_size) michael@0: return (DATA_TOO_LONG); michael@0: } michael@0: michael@0: return (status); michael@0: michael@0: error: michael@0: mm_free(line); michael@0: return (errcode); michael@0: } michael@0: michael@0: static int michael@0: evhttp_get_body_length(struct evhttp_request *req) michael@0: { michael@0: struct evkeyvalq *headers = req->input_headers; michael@0: const char *content_length; michael@0: const char *connection; michael@0: michael@0: content_length = evhttp_find_header(headers, "Content-Length"); michael@0: connection = evhttp_find_header(headers, "Connection"); michael@0: michael@0: if (content_length == NULL && connection == NULL) michael@0: req->ntoread = -1; michael@0: else if (content_length == NULL && michael@0: evutil_ascii_strcasecmp(connection, "Close") != 0) { michael@0: /* Bad combination, we don't know when it will end */ michael@0: event_warnx("%s: we got no content length, but the " michael@0: "server wants to keep the connection open: %s.", michael@0: __func__, connection); michael@0: return (-1); michael@0: } else if (content_length == NULL) { michael@0: req->ntoread = -1; michael@0: } else { michael@0: char *endp; michael@0: ev_int64_t ntoread = evutil_strtoll(content_length, &endp, 10); michael@0: if (*content_length == '\0' || *endp != '\0' || ntoread < 0) { michael@0: event_debug(("%s: illegal content length: %s", michael@0: __func__, content_length)); michael@0: return (-1); michael@0: } michael@0: req->ntoread = ntoread; michael@0: } michael@0: michael@0: event_debug(("%s: bytes to read: "EV_I64_FMT" (in buffer "EV_SIZE_FMT")\n", michael@0: __func__, EV_I64_ARG(req->ntoread), michael@0: EV_SIZE_ARG(evbuffer_get_length(bufferevent_get_input(req->evcon->bufev))))); michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: static int michael@0: evhttp_method_may_have_body(enum evhttp_cmd_type type) michael@0: { michael@0: switch (type) { michael@0: case EVHTTP_REQ_POST: michael@0: case EVHTTP_REQ_PUT: michael@0: case EVHTTP_REQ_PATCH: michael@0: return 1; michael@0: case EVHTTP_REQ_TRACE: michael@0: return 0; michael@0: /* XXX May any of the below methods have a body? */ michael@0: case EVHTTP_REQ_GET: michael@0: case EVHTTP_REQ_HEAD: michael@0: case EVHTTP_REQ_DELETE: michael@0: case EVHTTP_REQ_OPTIONS: michael@0: case EVHTTP_REQ_CONNECT: michael@0: return 0; michael@0: default: michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req) michael@0: { michael@0: const char *xfer_enc; michael@0: michael@0: /* If this is a request without a body, then we are done */ michael@0: if (req->kind == EVHTTP_REQUEST && michael@0: !evhttp_method_may_have_body(req->type)) { michael@0: evhttp_connection_done(evcon); michael@0: return; michael@0: } michael@0: evcon->state = EVCON_READING_BODY; michael@0: xfer_enc = evhttp_find_header(req->input_headers, "Transfer-Encoding"); michael@0: if (xfer_enc != NULL && evutil_ascii_strcasecmp(xfer_enc, "chunked") == 0) { michael@0: req->chunked = 1; michael@0: req->ntoread = -1; michael@0: } else { michael@0: if (evhttp_get_body_length(req) == -1) { michael@0: evhttp_connection_fail(evcon, michael@0: EVCON_HTTP_INVALID_HEADER); michael@0: return; michael@0: } michael@0: if (req->kind == EVHTTP_REQUEST && req->ntoread < 1) { michael@0: /* An incoming request with no content-length and no michael@0: * transfer-encoding has no body. */ michael@0: evhttp_connection_done(evcon); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: /* Should we send a 100 Continue status line? */ michael@0: if (req->kind == EVHTTP_REQUEST && REQ_VERSION_ATLEAST(req, 1, 1)) { michael@0: const char *expect; michael@0: michael@0: expect = evhttp_find_header(req->input_headers, "Expect"); michael@0: if (expect) { michael@0: if (!evutil_ascii_strcasecmp(expect, "100-continue")) { michael@0: /* XXX It would be nice to do some sanity michael@0: checking here. Does the resource exist? michael@0: Should the resource accept post requests? If michael@0: no, we should respond with an error. For michael@0: now, just optimistically tell the client to michael@0: send their message body. */ michael@0: if (req->ntoread > 0) { michael@0: /* ntoread is ev_int64_t, max_body_size is ev_uint64_t */ michael@0: if ((req->evcon->max_body_size <= EV_INT64_MAX) && (ev_uint64_t)req->ntoread > req->evcon->max_body_size) { michael@0: evhttp_send_error(req, HTTP_ENTITYTOOLARGE, NULL); michael@0: return; michael@0: } michael@0: } michael@0: if (!evbuffer_get_length(bufferevent_get_input(evcon->bufev))) michael@0: evhttp_send_continue(evcon, req); michael@0: } else { michael@0: evhttp_send_error(req, HTTP_EXPECTATIONFAILED, michael@0: NULL); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: evhttp_read_body(evcon, req); michael@0: /* note the request may have been freed in evhttp_read_body */ michael@0: } michael@0: michael@0: static void michael@0: evhttp_read_firstline(struct evhttp_connection *evcon, michael@0: struct evhttp_request *req) michael@0: { michael@0: enum message_read_status res; michael@0: michael@0: res = evhttp_parse_firstline(req, bufferevent_get_input(evcon->bufev)); michael@0: if (res == DATA_CORRUPTED || res == DATA_TOO_LONG) { michael@0: /* Error while reading, terminate */ michael@0: event_debug(("%s: bad header lines on "EV_SOCK_FMT"\n", michael@0: __func__, EV_SOCK_ARG(evcon->fd))); michael@0: evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); michael@0: return; michael@0: } else if (res == MORE_DATA_EXPECTED) { michael@0: /* Need more header lines */ michael@0: return; michael@0: } michael@0: michael@0: evcon->state = EVCON_READING_HEADERS; michael@0: evhttp_read_header(evcon, req); michael@0: } michael@0: michael@0: static void michael@0: evhttp_read_header(struct evhttp_connection *evcon, michael@0: struct evhttp_request *req) michael@0: { michael@0: enum message_read_status res; michael@0: evutil_socket_t fd = evcon->fd; michael@0: michael@0: res = evhttp_parse_headers(req, bufferevent_get_input(evcon->bufev)); michael@0: if (res == DATA_CORRUPTED || res == DATA_TOO_LONG) { michael@0: /* Error while reading, terminate */ michael@0: event_debug(("%s: bad header lines on "EV_SOCK_FMT"\n", michael@0: __func__, EV_SOCK_ARG(fd))); michael@0: evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); michael@0: return; michael@0: } else if (res == MORE_DATA_EXPECTED) { michael@0: /* Need more header lines */ michael@0: return; michael@0: } michael@0: michael@0: /* Disable reading for now */ michael@0: bufferevent_disable(evcon->bufev, EV_READ); michael@0: michael@0: /* Done reading headers, do the real work */ michael@0: switch (req->kind) { michael@0: case EVHTTP_REQUEST: michael@0: event_debug(("%s: checking for post data on "EV_SOCK_FMT"\n", michael@0: __func__, EV_SOCK_ARG(fd))); michael@0: evhttp_get_body(evcon, req); michael@0: /* note the request may have been freed in evhttp_get_body */ michael@0: break; michael@0: michael@0: case EVHTTP_RESPONSE: michael@0: /* Start over if we got a 100 Continue response. */ michael@0: if (req->response_code == 100) { michael@0: evhttp_start_read(evcon); michael@0: return; michael@0: } michael@0: if (!evhttp_response_needs_body(req)) { michael@0: event_debug(("%s: skipping body for code %d\n", michael@0: __func__, req->response_code)); michael@0: evhttp_connection_done(evcon); michael@0: } else { michael@0: event_debug(("%s: start of read body for %s on " michael@0: EV_SOCK_FMT"\n", michael@0: __func__, req->remote_host, EV_SOCK_ARG(fd))); michael@0: evhttp_get_body(evcon, req); michael@0: /* note the request may have been freed in michael@0: * evhttp_get_body */ michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: event_warnx("%s: bad header on "EV_SOCK_FMT, __func__, michael@0: EV_SOCK_ARG(fd)); michael@0: evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); michael@0: break; michael@0: } michael@0: /* request may have been freed above */ michael@0: } michael@0: michael@0: /* michael@0: * Creates a TCP connection to the specified port and executes a callback michael@0: * when finished. Failure or success is indicate by the passed connection michael@0: * object. michael@0: * michael@0: * Although this interface accepts a hostname, it is intended to take michael@0: * only numeric hostnames so that non-blocking DNS resolution can michael@0: * happen elsewhere. michael@0: */ michael@0: michael@0: struct evhttp_connection * michael@0: evhttp_connection_new(const char *address, unsigned short port) michael@0: { michael@0: return (evhttp_connection_base_new(NULL, NULL, address, port)); michael@0: } michael@0: michael@0: struct evhttp_connection * michael@0: evhttp_connection_base_new(struct event_base *base, struct evdns_base *dnsbase, michael@0: const char *address, unsigned short port) michael@0: { michael@0: struct evhttp_connection *evcon = NULL; michael@0: michael@0: event_debug(("Attempting connection to %s:%d\n", address, port)); michael@0: michael@0: if ((evcon = mm_calloc(1, sizeof(struct evhttp_connection))) == NULL) { michael@0: event_warn("%s: calloc failed", __func__); michael@0: goto error; michael@0: } michael@0: michael@0: evcon->fd = -1; michael@0: evcon->port = port; michael@0: michael@0: evcon->max_headers_size = EV_SIZE_MAX; michael@0: evcon->max_body_size = EV_SIZE_MAX; michael@0: michael@0: evcon->timeout = -1; michael@0: evcon->retry_cnt = evcon->retry_max = 0; michael@0: michael@0: if ((evcon->address = mm_strdup(address)) == NULL) { michael@0: event_warn("%s: strdup failed", __func__); michael@0: goto error; michael@0: } michael@0: michael@0: if ((evcon->bufev = bufferevent_new(-1, michael@0: evhttp_read_cb, michael@0: evhttp_write_cb, michael@0: evhttp_error_cb, evcon)) == NULL) { michael@0: event_warn("%s: bufferevent_new failed", __func__); michael@0: goto error; michael@0: } michael@0: michael@0: evcon->state = EVCON_DISCONNECTED; michael@0: TAILQ_INIT(&evcon->requests); michael@0: michael@0: if (base != NULL) { michael@0: evcon->base = base; michael@0: bufferevent_base_set(base, evcon->bufev); michael@0: } michael@0: michael@0: michael@0: event_deferred_cb_init(&evcon->read_more_deferred_cb, michael@0: evhttp_deferred_read_cb, evcon); michael@0: michael@0: evcon->dns_base = dnsbase; michael@0: michael@0: return (evcon); michael@0: michael@0: error: michael@0: if (evcon != NULL) michael@0: evhttp_connection_free(evcon); michael@0: return (NULL); michael@0: } michael@0: michael@0: struct bufferevent * michael@0: evhttp_connection_get_bufferevent(struct evhttp_connection *evcon) michael@0: { michael@0: return evcon->bufev; michael@0: } michael@0: michael@0: void michael@0: evhttp_connection_set_base(struct evhttp_connection *evcon, michael@0: struct event_base *base) michael@0: { michael@0: EVUTIL_ASSERT(evcon->base == NULL); michael@0: EVUTIL_ASSERT(evcon->state == EVCON_DISCONNECTED); michael@0: evcon->base = base; michael@0: bufferevent_base_set(base, evcon->bufev); michael@0: } michael@0: michael@0: void michael@0: evhttp_connection_set_timeout(struct evhttp_connection *evcon, michael@0: int timeout_in_secs) michael@0: { michael@0: evcon->timeout = timeout_in_secs; michael@0: michael@0: if (evcon->timeout == -1) michael@0: bufferevent_settimeout(evcon->bufev, michael@0: HTTP_READ_TIMEOUT, HTTP_WRITE_TIMEOUT); michael@0: else michael@0: bufferevent_settimeout(evcon->bufev, michael@0: evcon->timeout, evcon->timeout); michael@0: } michael@0: michael@0: void michael@0: evhttp_connection_set_retries(struct evhttp_connection *evcon, michael@0: int retry_max) michael@0: { michael@0: evcon->retry_max = retry_max; michael@0: } michael@0: michael@0: void michael@0: evhttp_connection_set_closecb(struct evhttp_connection *evcon, michael@0: void (*cb)(struct evhttp_connection *, void *), void *cbarg) michael@0: { michael@0: evcon->closecb = cb; michael@0: evcon->closecb_arg = cbarg; michael@0: } michael@0: michael@0: void michael@0: evhttp_connection_get_peer(struct evhttp_connection *evcon, michael@0: char **address, ev_uint16_t *port) michael@0: { michael@0: *address = evcon->address; michael@0: *port = evcon->port; michael@0: } michael@0: michael@0: int michael@0: evhttp_connection_connect(struct evhttp_connection *evcon) michael@0: { michael@0: if (evcon->state == EVCON_CONNECTING) michael@0: return (0); michael@0: michael@0: evhttp_connection_reset(evcon); michael@0: michael@0: EVUTIL_ASSERT(!(evcon->flags & EVHTTP_CON_INCOMING)); michael@0: evcon->flags |= EVHTTP_CON_OUTGOING; michael@0: michael@0: evcon->fd = bind_socket( michael@0: evcon->bind_address, evcon->bind_port, 0 /*reuse*/); michael@0: if (evcon->fd == -1) { michael@0: event_debug(("%s: failed to bind to \"%s\"", michael@0: __func__, evcon->bind_address)); michael@0: return (-1); michael@0: } michael@0: michael@0: /* Set up a callback for successful connection setup */ michael@0: bufferevent_setfd(evcon->bufev, evcon->fd); michael@0: bufferevent_setcb(evcon->bufev, michael@0: NULL /* evhttp_read_cb */, michael@0: NULL /* evhttp_write_cb */, michael@0: evhttp_connection_cb, michael@0: evcon); michael@0: bufferevent_settimeout(evcon->bufev, 0, michael@0: evcon->timeout != -1 ? evcon->timeout : HTTP_CONNECT_TIMEOUT); michael@0: /* make sure that we get a write callback */ michael@0: bufferevent_enable(evcon->bufev, EV_WRITE); michael@0: michael@0: if (bufferevent_socket_connect_hostname(evcon->bufev, evcon->dns_base, michael@0: AF_UNSPEC, evcon->address, evcon->port) < 0) { michael@0: event_sock_warn(evcon->fd, "%s: connection to \"%s\" failed", michael@0: __func__, evcon->address); michael@0: /* some operating systems return ECONNREFUSED immediately michael@0: * when connecting to a local address. the cleanup is going michael@0: * to reschedule this function call. michael@0: */ michael@0: evhttp_connection_cb_cleanup(evcon); michael@0: return (0); michael@0: } michael@0: michael@0: evcon->state = EVCON_CONNECTING; michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: /* michael@0: * Starts an HTTP request on the provided evhttp_connection object. michael@0: * If the connection object is not connected to the web server already, michael@0: * this will start the connection. michael@0: */ michael@0: michael@0: int michael@0: evhttp_make_request(struct evhttp_connection *evcon, michael@0: struct evhttp_request *req, michael@0: enum evhttp_cmd_type type, const char *uri) michael@0: { michael@0: /* We are making a request */ michael@0: req->kind = EVHTTP_REQUEST; michael@0: req->type = type; michael@0: if (req->uri != NULL) michael@0: mm_free(req->uri); michael@0: if ((req->uri = mm_strdup(uri)) == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: evhttp_request_free(req); michael@0: return (-1); michael@0: } michael@0: michael@0: /* Set the protocol version if it is not supplied */ michael@0: if (!req->major && !req->minor) { michael@0: req->major = 1; michael@0: req->minor = 1; michael@0: } michael@0: michael@0: EVUTIL_ASSERT(req->evcon == NULL); michael@0: req->evcon = evcon; michael@0: EVUTIL_ASSERT(!(req->flags & EVHTTP_REQ_OWN_CONNECTION)); michael@0: michael@0: TAILQ_INSERT_TAIL(&evcon->requests, req, next); michael@0: michael@0: /* If the connection object is not connected; make it so */ michael@0: if (!evhttp_connected(evcon)) { michael@0: int res = evhttp_connection_connect(evcon); michael@0: /* evhttp_connection_fail(), which is called through michael@0: * evhttp_connection_connect(), assumes that req lies in michael@0: * evcon->requests. Thus, enqueue the request in advance and r michael@0: * it in the error case. */ michael@0: if (res != 0) michael@0: TAILQ_REMOVE(&evcon->requests, req, next); michael@0: michael@0: return res; michael@0: } michael@0: michael@0: /* michael@0: * If it's connected already and we are the first in the queue, michael@0: * then we can dispatch this request immediately. Otherwise, it michael@0: * will be dispatched once the pending requests are completed. michael@0: */ michael@0: if (TAILQ_FIRST(&evcon->requests) == req) michael@0: evhttp_request_dispatch(evcon); michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: void michael@0: evhttp_cancel_request(struct evhttp_request *req) michael@0: { michael@0: struct evhttp_connection *evcon = req->evcon; michael@0: if (evcon != NULL) { michael@0: /* We need to remove it from the connection */ michael@0: if (TAILQ_FIRST(&evcon->requests) == req) { michael@0: /* it's currently being worked on, so reset michael@0: * the connection. michael@0: */ michael@0: evhttp_connection_fail(evcon, michael@0: EVCON_HTTP_REQUEST_CANCEL); michael@0: michael@0: /* connection fail freed the request */ michael@0: return; michael@0: } else { michael@0: /* otherwise, we can just remove it from the michael@0: * queue michael@0: */ michael@0: TAILQ_REMOVE(&evcon->requests, req, next); michael@0: } michael@0: } michael@0: michael@0: evhttp_request_free(req); michael@0: } michael@0: michael@0: /* michael@0: * Reads data from file descriptor into request structure michael@0: * Request structure needs to be set up correctly. michael@0: */ michael@0: michael@0: void michael@0: evhttp_start_read(struct evhttp_connection *evcon) michael@0: { michael@0: /* Set up an event to read the headers */ michael@0: bufferevent_disable(evcon->bufev, EV_WRITE); michael@0: bufferevent_enable(evcon->bufev, EV_READ); michael@0: evcon->state = EVCON_READING_FIRSTLINE; michael@0: /* Reset the bufferevent callbacks */ michael@0: bufferevent_setcb(evcon->bufev, michael@0: evhttp_read_cb, michael@0: evhttp_write_cb, michael@0: evhttp_error_cb, michael@0: evcon); michael@0: michael@0: /* If there's still data pending, process it next time through the michael@0: * loop. Don't do it now; that could get recusive. */ michael@0: if (evbuffer_get_length(bufferevent_get_input(evcon->bufev))) { michael@0: event_deferred_cb_schedule(get_deferred_queue(evcon), michael@0: &evcon->read_more_deferred_cb); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: evhttp_send_done(struct evhttp_connection *evcon, void *arg) michael@0: { michael@0: int need_close; michael@0: struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); michael@0: TAILQ_REMOVE(&evcon->requests, req, next); michael@0: michael@0: need_close = michael@0: (REQ_VERSION_BEFORE(req, 1, 1) && michael@0: !evhttp_is_connection_keepalive(req->input_headers))|| michael@0: evhttp_is_connection_close(req->flags, req->input_headers) || michael@0: evhttp_is_connection_close(req->flags, req->output_headers); michael@0: michael@0: EVUTIL_ASSERT(req->flags & EVHTTP_REQ_OWN_CONNECTION); michael@0: evhttp_request_free(req); michael@0: michael@0: if (need_close) { michael@0: evhttp_connection_free(evcon); michael@0: return; michael@0: } michael@0: michael@0: /* we have a persistent connection; try to accept another request. */ michael@0: if (evhttp_associate_new_request_with_connection(evcon) == -1) { michael@0: evhttp_connection_free(evcon); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Returns an error page. michael@0: */ michael@0: michael@0: void michael@0: evhttp_send_error(struct evhttp_request *req, int error, const char *reason) michael@0: { michael@0: michael@0: #define ERR_FORMAT "\n" \ michael@0: "%d %s\n" \ michael@0: "\n" \ michael@0: "

%s

\n" \ michael@0: "\n" michael@0: michael@0: struct evbuffer *buf = evbuffer_new(); michael@0: if (buf == NULL) { michael@0: /* if we cannot allocate memory; we just drop the connection */ michael@0: evhttp_connection_free(req->evcon); michael@0: return; michael@0: } michael@0: if (reason == NULL) { michael@0: reason = evhttp_response_phrase_internal(error); michael@0: } michael@0: michael@0: evhttp_response_code(req, error, reason); michael@0: michael@0: evbuffer_add_printf(buf, ERR_FORMAT, error, reason, reason); michael@0: michael@0: evhttp_send_page(req, buf); michael@0: michael@0: evbuffer_free(buf); michael@0: #undef ERR_FORMAT michael@0: } michael@0: michael@0: /* Requires that headers and response code are already set up */ michael@0: michael@0: static inline void michael@0: evhttp_send(struct evhttp_request *req, struct evbuffer *databuf) michael@0: { michael@0: struct evhttp_connection *evcon = req->evcon; michael@0: michael@0: if (evcon == NULL) { michael@0: evhttp_request_free(req); michael@0: return; michael@0: } michael@0: michael@0: EVUTIL_ASSERT(TAILQ_FIRST(&evcon->requests) == req); michael@0: michael@0: /* we expect no more calls form the user on this request */ michael@0: req->userdone = 1; michael@0: michael@0: /* xxx: not sure if we really should expose the data buffer this way */ michael@0: if (databuf != NULL) michael@0: evbuffer_add_buffer(req->output_buffer, databuf); michael@0: michael@0: /* Adds headers to the response */ michael@0: evhttp_make_header(evcon, req); michael@0: michael@0: evhttp_write_buffer(evcon, evhttp_send_done, NULL); michael@0: } michael@0: michael@0: void michael@0: evhttp_send_reply(struct evhttp_request *req, int code, const char *reason, michael@0: struct evbuffer *databuf) michael@0: { michael@0: evhttp_response_code(req, code, reason); michael@0: michael@0: evhttp_send(req, databuf); michael@0: } michael@0: michael@0: void michael@0: evhttp_send_reply_start(struct evhttp_request *req, int code, michael@0: const char *reason) michael@0: { michael@0: evhttp_response_code(req, code, reason); michael@0: if (evhttp_find_header(req->output_headers, "Content-Length") == NULL && michael@0: REQ_VERSION_ATLEAST(req, 1, 1) && michael@0: evhttp_response_needs_body(req)) { michael@0: /* michael@0: * prefer HTTP/1.1 chunked encoding to closing the connection; michael@0: * note RFC 2616 section 4.4 forbids it with Content-Length: michael@0: * and it's not necessary then anyway. michael@0: */ michael@0: evhttp_add_header(req->output_headers, "Transfer-Encoding", michael@0: "chunked"); michael@0: req->chunked = 1; michael@0: } else { michael@0: req->chunked = 0; michael@0: } michael@0: evhttp_make_header(req->evcon, req); michael@0: evhttp_write_buffer(req->evcon, NULL, NULL); michael@0: } michael@0: michael@0: void michael@0: evhttp_send_reply_chunk(struct evhttp_request *req, struct evbuffer *databuf) michael@0: { michael@0: struct evhttp_connection *evcon = req->evcon; michael@0: struct evbuffer *output; michael@0: michael@0: if (evcon == NULL) michael@0: return; michael@0: michael@0: output = bufferevent_get_output(evcon->bufev); michael@0: michael@0: if (evbuffer_get_length(databuf) == 0) michael@0: return; michael@0: if (!evhttp_response_needs_body(req)) michael@0: return; michael@0: if (req->chunked) { michael@0: evbuffer_add_printf(output, "%x\r\n", michael@0: (unsigned)evbuffer_get_length(databuf)); michael@0: } michael@0: evbuffer_add_buffer(output, databuf); michael@0: if (req->chunked) { michael@0: evbuffer_add(output, "\r\n", 2); michael@0: } michael@0: evhttp_write_buffer(evcon, NULL, NULL); michael@0: } michael@0: michael@0: void michael@0: evhttp_send_reply_end(struct evhttp_request *req) michael@0: { michael@0: struct evhttp_connection *evcon = req->evcon; michael@0: struct evbuffer *output; michael@0: michael@0: if (evcon == NULL) { michael@0: evhttp_request_free(req); michael@0: return; michael@0: } michael@0: michael@0: output = bufferevent_get_output(evcon->bufev); michael@0: michael@0: /* we expect no more calls form the user on this request */ michael@0: req->userdone = 1; michael@0: michael@0: if (req->chunked) { michael@0: evbuffer_add(output, "0\r\n\r\n", 5); michael@0: evhttp_write_buffer(req->evcon, evhttp_send_done, NULL); michael@0: req->chunked = 0; michael@0: } else if (evbuffer_get_length(output) == 0) { michael@0: /* let the connection know that we are done with the request */ michael@0: evhttp_send_done(evcon, NULL); michael@0: } else { michael@0: /* make the callback execute after all data has been written */ michael@0: evcon->cb = evhttp_send_done; michael@0: evcon->cb_arg = NULL; michael@0: } michael@0: } michael@0: michael@0: static const char *informational_phrases[] = { michael@0: /* 100 */ "Continue", michael@0: /* 101 */ "Switching Protocols" michael@0: }; michael@0: michael@0: static const char *success_phrases[] = { michael@0: /* 200 */ "OK", michael@0: /* 201 */ "Created", michael@0: /* 202 */ "Accepted", michael@0: /* 203 */ "Non-Authoritative Information", michael@0: /* 204 */ "No Content", michael@0: /* 205 */ "Reset Content", michael@0: /* 206 */ "Partial Content" michael@0: }; michael@0: michael@0: static const char *redirection_phrases[] = { michael@0: /* 300 */ "Multiple Choices", michael@0: /* 301 */ "Moved Permanently", michael@0: /* 302 */ "Found", michael@0: /* 303 */ "See Other", michael@0: /* 304 */ "Not Modified", michael@0: /* 305 */ "Use Proxy", michael@0: /* 307 */ "Temporary Redirect" michael@0: }; michael@0: michael@0: static const char *client_error_phrases[] = { michael@0: /* 400 */ "Bad Request", michael@0: /* 401 */ "Unauthorized", michael@0: /* 402 */ "Payment Required", michael@0: /* 403 */ "Forbidden", michael@0: /* 404 */ "Not Found", michael@0: /* 405 */ "Method Not Allowed", michael@0: /* 406 */ "Not Acceptable", michael@0: /* 407 */ "Proxy Authentication Required", michael@0: /* 408 */ "Request Time-out", michael@0: /* 409 */ "Conflict", michael@0: /* 410 */ "Gone", michael@0: /* 411 */ "Length Required", michael@0: /* 412 */ "Precondition Failed", michael@0: /* 413 */ "Request Entity Too Large", michael@0: /* 414 */ "Request-URI Too Large", michael@0: /* 415 */ "Unsupported Media Type", michael@0: /* 416 */ "Requested range not satisfiable", michael@0: /* 417 */ "Expectation Failed" michael@0: }; michael@0: michael@0: static const char *server_error_phrases[] = { michael@0: /* 500 */ "Internal Server Error", michael@0: /* 501 */ "Not Implemented", michael@0: /* 502 */ "Bad Gateway", michael@0: /* 503 */ "Service Unavailable", michael@0: /* 504 */ "Gateway Time-out", michael@0: /* 505 */ "HTTP Version not supported" michael@0: }; michael@0: michael@0: struct response_class { michael@0: const char *name; michael@0: size_t num_responses; michael@0: const char **responses; michael@0: }; michael@0: michael@0: #ifndef MEMBERSOF michael@0: #define MEMBERSOF(x) (sizeof(x)/sizeof(x[0])) michael@0: #endif michael@0: michael@0: static const struct response_class response_classes[] = { michael@0: /* 1xx */ { "Informational", MEMBERSOF(informational_phrases), informational_phrases }, michael@0: /* 2xx */ { "Success", MEMBERSOF(success_phrases), success_phrases }, michael@0: /* 3xx */ { "Redirection", MEMBERSOF(redirection_phrases), redirection_phrases }, michael@0: /* 4xx */ { "Client Error", MEMBERSOF(client_error_phrases), client_error_phrases }, michael@0: /* 5xx */ { "Server Error", MEMBERSOF(server_error_phrases), server_error_phrases } michael@0: }; michael@0: michael@0: static const char * michael@0: evhttp_response_phrase_internal(int code) michael@0: { michael@0: int klass = code / 100 - 1; michael@0: int subcode = code % 100; michael@0: michael@0: /* Unknown class - can't do any better here */ michael@0: if (klass < 0 || klass >= (int) MEMBERSOF(response_classes)) michael@0: return "Unknown Status Class"; michael@0: michael@0: /* Unknown sub-code, return class name at least */ michael@0: if (subcode >= (int) response_classes[klass].num_responses) michael@0: return response_classes[klass].name; michael@0: michael@0: return response_classes[klass].responses[subcode]; michael@0: } michael@0: michael@0: void michael@0: evhttp_response_code(struct evhttp_request *req, int code, const char *reason) michael@0: { michael@0: req->kind = EVHTTP_RESPONSE; michael@0: req->response_code = code; michael@0: if (req->response_code_line != NULL) michael@0: mm_free(req->response_code_line); michael@0: if (reason == NULL) michael@0: reason = evhttp_response_phrase_internal(code); michael@0: req->response_code_line = mm_strdup(reason); michael@0: if (req->response_code_line == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: /* XXX what else can we do? */ michael@0: } michael@0: } michael@0: michael@0: void michael@0: evhttp_send_page(struct evhttp_request *req, struct evbuffer *databuf) michael@0: { michael@0: if (!req->major || !req->minor) { michael@0: req->major = 1; michael@0: req->minor = 1; michael@0: } michael@0: michael@0: if (req->kind != EVHTTP_RESPONSE) michael@0: evhttp_response_code(req, 200, "OK"); michael@0: michael@0: evhttp_clear_headers(req->output_headers); michael@0: evhttp_add_header(req->output_headers, "Content-Type", "text/html"); michael@0: evhttp_add_header(req->output_headers, "Connection", "close"); michael@0: michael@0: evhttp_send(req, databuf); michael@0: } michael@0: michael@0: static const char uri_chars[256] = { michael@0: /* 0 */ michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, michael@0: 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, michael@0: /* 64 */ michael@0: 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, michael@0: 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, michael@0: 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, michael@0: 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, michael@0: /* 128 */ michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: /* 192 */ michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: }; michael@0: michael@0: #define CHAR_IS_UNRESERVED(c) \ michael@0: (uri_chars[(unsigned char)(c)]) michael@0: michael@0: /* michael@0: * Helper functions to encode/decode a string for inclusion in a URI. michael@0: * The returned string must be freed by the caller. michael@0: */ michael@0: char * michael@0: evhttp_uriencode(const char *uri, ev_ssize_t len, int space_as_plus) michael@0: { michael@0: struct evbuffer *buf = evbuffer_new(); michael@0: const char *p, *end; michael@0: char *result; michael@0: michael@0: if (buf == NULL) michael@0: return (NULL); michael@0: michael@0: if (len >= 0) michael@0: end = uri+len; michael@0: else michael@0: end = uri+strlen(uri); michael@0: michael@0: for (p = uri; p < end; p++) { michael@0: if (CHAR_IS_UNRESERVED(*p)) { michael@0: evbuffer_add(buf, p, 1); michael@0: } else if (*p == ' ' && space_as_plus) { michael@0: evbuffer_add(buf, "+", 1); michael@0: } else { michael@0: evbuffer_add_printf(buf, "%%%02X", (unsigned char)(*p)); michael@0: } michael@0: } michael@0: evbuffer_add(buf, "", 1); /* NUL-terminator. */ michael@0: result = mm_malloc(evbuffer_get_length(buf)); michael@0: if (result) michael@0: evbuffer_remove(buf, result, evbuffer_get_length(buf)); michael@0: evbuffer_free(buf); michael@0: michael@0: return (result); michael@0: } michael@0: michael@0: char * michael@0: evhttp_encode_uri(const char *str) michael@0: { michael@0: return evhttp_uriencode(str, -1, 0); michael@0: } michael@0: michael@0: /* michael@0: * @param decode_plus_ctl: if 1, we decode plus into space. If 0, we don't. michael@0: * If -1, when true we transform plus to space only after we've seen michael@0: * a ?. -1 is deprecated. michael@0: * @return the number of bytes written to 'ret'. michael@0: */ michael@0: static int michael@0: evhttp_decode_uri_internal( michael@0: const char *uri, size_t length, char *ret, int decode_plus_ctl) michael@0: { michael@0: char c; michael@0: int j; michael@0: int decode_plus = (decode_plus_ctl == 1) ? 1: 0; michael@0: unsigned i; michael@0: michael@0: for (i = j = 0; i < length; i++) { michael@0: c = uri[i]; michael@0: if (c == '?') { michael@0: if (decode_plus_ctl < 0) michael@0: decode_plus = 1; michael@0: } else if (c == '+' && decode_plus) { michael@0: c = ' '; michael@0: } else if (c == '%' && EVUTIL_ISXDIGIT(uri[i+1]) && michael@0: EVUTIL_ISXDIGIT(uri[i+2])) { michael@0: char tmp[3]; michael@0: tmp[0] = uri[i+1]; michael@0: tmp[1] = uri[i+2]; michael@0: tmp[2] = '\0'; michael@0: c = (char)strtol(tmp, NULL, 16); michael@0: i += 2; michael@0: } michael@0: ret[j++] = c; michael@0: } michael@0: ret[j] = '\0'; michael@0: michael@0: return (j); michael@0: } michael@0: michael@0: /* deprecated */ michael@0: char * michael@0: evhttp_decode_uri(const char *uri) michael@0: { michael@0: char *ret; michael@0: michael@0: if ((ret = mm_malloc(strlen(uri) + 1)) == NULL) { michael@0: event_warn("%s: malloc(%lu)", __func__, michael@0: (unsigned long)(strlen(uri) + 1)); michael@0: return (NULL); michael@0: } michael@0: michael@0: evhttp_decode_uri_internal(uri, strlen(uri), michael@0: ret, -1 /*always_decode_plus*/); michael@0: michael@0: return (ret); michael@0: } michael@0: michael@0: char * michael@0: evhttp_uridecode(const char *uri, int decode_plus, size_t *size_out) michael@0: { michael@0: char *ret; michael@0: int n; michael@0: michael@0: if ((ret = mm_malloc(strlen(uri) + 1)) == NULL) { michael@0: event_warn("%s: malloc(%lu)", __func__, michael@0: (unsigned long)(strlen(uri) + 1)); michael@0: return (NULL); michael@0: } michael@0: michael@0: n = evhttp_decode_uri_internal(uri, strlen(uri), michael@0: ret, !!decode_plus/*always_decode_plus*/); michael@0: michael@0: if (size_out) { michael@0: EVUTIL_ASSERT(n >= 0); michael@0: *size_out = (size_t)n; michael@0: } michael@0: michael@0: return (ret); michael@0: } michael@0: michael@0: /* michael@0: * Helper function to parse out arguments in a query. michael@0: * The arguments are separated by key and value. michael@0: */ michael@0: michael@0: static int michael@0: evhttp_parse_query_impl(const char *str, struct evkeyvalq *headers, michael@0: int is_whole_uri) michael@0: { michael@0: char *line=NULL; michael@0: char *argument; michael@0: char *p; michael@0: const char *query_part; michael@0: int result = -1; michael@0: struct evhttp_uri *uri=NULL; michael@0: michael@0: TAILQ_INIT(headers); michael@0: michael@0: if (is_whole_uri) { michael@0: uri = evhttp_uri_parse(str); michael@0: if (!uri) michael@0: goto error; michael@0: query_part = evhttp_uri_get_query(uri); michael@0: } else { michael@0: query_part = str; michael@0: } michael@0: michael@0: /* No arguments - we are done */ michael@0: if (!query_part || !strlen(query_part)) { michael@0: result = 0; michael@0: goto done; michael@0: } michael@0: michael@0: if ((line = mm_strdup(query_part)) == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: goto error; michael@0: } michael@0: michael@0: p = argument = line; michael@0: while (p != NULL && *p != '\0') { michael@0: char *key, *value, *decoded_value; michael@0: argument = strsep(&p, "&"); michael@0: michael@0: value = argument; michael@0: key = strsep(&value, "="); michael@0: if (value == NULL || *key == '\0') { michael@0: goto error; michael@0: } michael@0: michael@0: if ((decoded_value = mm_malloc(strlen(value) + 1)) == NULL) { michael@0: event_warn("%s: mm_malloc", __func__); michael@0: goto error; michael@0: } michael@0: evhttp_decode_uri_internal(value, strlen(value), michael@0: decoded_value, 1 /*always_decode_plus*/); michael@0: event_debug(("Query Param: %s -> %s\n", key, decoded_value)); michael@0: evhttp_add_header_internal(headers, key, decoded_value); michael@0: mm_free(decoded_value); michael@0: } michael@0: michael@0: result = 0; michael@0: goto done; michael@0: error: michael@0: evhttp_clear_headers(headers); michael@0: done: michael@0: if (line) michael@0: mm_free(line); michael@0: if (uri) michael@0: evhttp_uri_free(uri); michael@0: return result; michael@0: } michael@0: michael@0: int michael@0: evhttp_parse_query(const char *uri, struct evkeyvalq *headers) michael@0: { michael@0: return evhttp_parse_query_impl(uri, headers, 1); michael@0: } michael@0: int michael@0: evhttp_parse_query_str(const char *uri, struct evkeyvalq *headers) michael@0: { michael@0: return evhttp_parse_query_impl(uri, headers, 0); michael@0: } michael@0: michael@0: static struct evhttp_cb * michael@0: evhttp_dispatch_callback(struct httpcbq *callbacks, struct evhttp_request *req) michael@0: { michael@0: struct evhttp_cb *cb; michael@0: size_t offset = 0; michael@0: char *translated; michael@0: const char *path; michael@0: michael@0: /* Test for different URLs */ michael@0: path = evhttp_uri_get_path(req->uri_elems); michael@0: offset = strlen(path); michael@0: if ((translated = mm_malloc(offset + 1)) == NULL) michael@0: return (NULL); michael@0: evhttp_decode_uri_internal(path, offset, translated, michael@0: 0 /* decode_plus */); michael@0: michael@0: TAILQ_FOREACH(cb, callbacks, next) { michael@0: if (!strcmp(cb->what, translated)) { michael@0: mm_free(translated); michael@0: return (cb); michael@0: } michael@0: } michael@0: michael@0: mm_free(translated); michael@0: return (NULL); michael@0: } michael@0: michael@0: michael@0: static int michael@0: prefix_suffix_match(const char *pattern, const char *name, int ignorecase) michael@0: { michael@0: char c; michael@0: michael@0: while (1) { michael@0: switch (c = *pattern++) { michael@0: case '\0': michael@0: return *name == '\0'; michael@0: michael@0: case '*': michael@0: while (*name != '\0') { michael@0: if (prefix_suffix_match(pattern, name, michael@0: ignorecase)) michael@0: return (1); michael@0: ++name; michael@0: } michael@0: return (0); michael@0: default: michael@0: if (c != *name) { michael@0: if (!ignorecase || michael@0: EVUTIL_TOLOWER(c) != EVUTIL_TOLOWER(*name)) michael@0: return (0); michael@0: } michael@0: ++name; michael@0: } michael@0: } michael@0: /* NOTREACHED */ michael@0: } michael@0: michael@0: /* michael@0: Search the vhost hierarchy beginning with http for a server alias michael@0: matching hostname. If a match is found, and outhttp is non-null, michael@0: outhttp is set to the matching http object and 1 is returned. michael@0: */ michael@0: michael@0: static int michael@0: evhttp_find_alias(struct evhttp *http, struct evhttp **outhttp, michael@0: const char *hostname) michael@0: { michael@0: struct evhttp_server_alias *alias; michael@0: struct evhttp *vhost; michael@0: michael@0: TAILQ_FOREACH(alias, &http->aliases, next) { michael@0: /* XXX Do we need to handle IP addresses? */ michael@0: if (!evutil_ascii_strcasecmp(alias->alias, hostname)) { michael@0: if (outhttp) michael@0: *outhttp = http; michael@0: return 1; michael@0: } michael@0: } michael@0: michael@0: /* XXX It might be good to avoid recursion here, but I don't michael@0: see a way to do that w/o a list. */ michael@0: TAILQ_FOREACH(vhost, &http->virtualhosts, next_vhost) { michael@0: if (evhttp_find_alias(vhost, outhttp, hostname)) michael@0: return 1; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: /* michael@0: Attempts to find the best http object to handle a request for a hostname. michael@0: All aliases for the root http object and vhosts are searched for an exact michael@0: match. Then, the vhost hierarchy is traversed again for a matching michael@0: pattern. michael@0: michael@0: If an alias or vhost is matched, 1 is returned, and outhttp, if non-null, michael@0: is set with the best matching http object. If there are no matches, the michael@0: root http object is stored in outhttp and 0 is returned. michael@0: */ michael@0: michael@0: static int michael@0: evhttp_find_vhost(struct evhttp *http, struct evhttp **outhttp, michael@0: const char *hostname) michael@0: { michael@0: struct evhttp *vhost; michael@0: struct evhttp *oldhttp; michael@0: int match_found = 0; michael@0: michael@0: if (evhttp_find_alias(http, outhttp, hostname)) michael@0: return 1; michael@0: michael@0: do { michael@0: oldhttp = http; michael@0: TAILQ_FOREACH(vhost, &http->virtualhosts, next_vhost) { michael@0: if (prefix_suffix_match(vhost->vhost_pattern, michael@0: hostname, 1 /* ignorecase */)) { michael@0: http = vhost; michael@0: match_found = 1; michael@0: break; michael@0: } michael@0: } michael@0: } while (oldhttp != http); michael@0: michael@0: if (outhttp) michael@0: *outhttp = http; michael@0: michael@0: return match_found; michael@0: } michael@0: michael@0: static void michael@0: evhttp_handle_request(struct evhttp_request *req, void *arg) michael@0: { michael@0: struct evhttp *http = arg; michael@0: struct evhttp_cb *cb = NULL; michael@0: const char *hostname; michael@0: michael@0: /* we have a new request on which the user needs to take action */ michael@0: req->userdone = 0; michael@0: michael@0: if (req->type == 0 || req->uri == NULL) { michael@0: evhttp_send_error(req, HTTP_BADREQUEST, NULL); michael@0: return; michael@0: } michael@0: michael@0: if ((http->allowed_methods & req->type) == 0) { michael@0: event_debug(("Rejecting disallowed method %x (allowed: %x)\n", michael@0: (unsigned)req->type, (unsigned)http->allowed_methods)); michael@0: evhttp_send_error(req, HTTP_NOTIMPLEMENTED, NULL); michael@0: return; michael@0: } michael@0: michael@0: /* handle potential virtual hosts */ michael@0: hostname = evhttp_request_get_host(req); michael@0: if (hostname != NULL) { michael@0: evhttp_find_vhost(http, &http, hostname); michael@0: } michael@0: michael@0: if ((cb = evhttp_dispatch_callback(&http->callbacks, req)) != NULL) { michael@0: (*cb->cb)(req, cb->cbarg); michael@0: return; michael@0: } michael@0: michael@0: /* Generic call back */ michael@0: if (http->gencb) { michael@0: (*http->gencb)(req, http->gencbarg); michael@0: return; michael@0: } else { michael@0: /* We need to send a 404 here */ michael@0: #define ERR_FORMAT "" \ michael@0: "404 Not Found" \ michael@0: "" \ michael@0: "

Not Found

" \ michael@0: "

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

"\ michael@0: "\n" michael@0: michael@0: char *escaped_html; michael@0: struct evbuffer *buf; michael@0: michael@0: if ((escaped_html = evhttp_htmlescape(req->uri)) == NULL) { michael@0: evhttp_connection_free(req->evcon); michael@0: return; michael@0: } michael@0: michael@0: if ((buf = evbuffer_new()) == NULL) { michael@0: mm_free(escaped_html); michael@0: evhttp_connection_free(req->evcon); michael@0: return; michael@0: } michael@0: michael@0: evhttp_response_code(req, HTTP_NOTFOUND, "Not Found"); michael@0: michael@0: evbuffer_add_printf(buf, ERR_FORMAT, escaped_html); michael@0: michael@0: mm_free(escaped_html); michael@0: michael@0: evhttp_send_page(req, buf); michael@0: michael@0: evbuffer_free(buf); michael@0: #undef ERR_FORMAT michael@0: } michael@0: } michael@0: michael@0: /* Listener callback when a connection arrives at a server. */ michael@0: static void michael@0: accept_socket_cb(struct evconnlistener *listener, evutil_socket_t nfd, struct sockaddr *peer_sa, int peer_socklen, void *arg) michael@0: { michael@0: struct evhttp *http = arg; michael@0: michael@0: evhttp_get_request(http, nfd, peer_sa, peer_socklen); michael@0: } michael@0: michael@0: int michael@0: evhttp_bind_socket(struct evhttp *http, const char *address, ev_uint16_t port) michael@0: { michael@0: struct evhttp_bound_socket *bound = michael@0: evhttp_bind_socket_with_handle(http, address, port); michael@0: if (bound == NULL) michael@0: return (-1); michael@0: return (0); michael@0: } michael@0: michael@0: struct evhttp_bound_socket * michael@0: evhttp_bind_socket_with_handle(struct evhttp *http, const char *address, ev_uint16_t port) michael@0: { michael@0: evutil_socket_t fd; michael@0: struct evhttp_bound_socket *bound; michael@0: michael@0: if ((fd = bind_socket(address, port, 1 /*reuse*/)) == -1) michael@0: return (NULL); michael@0: michael@0: if (listen(fd, 128) == -1) { michael@0: event_sock_warn(fd, "%s: listen", __func__); michael@0: evutil_closesocket(fd); michael@0: return (NULL); michael@0: } michael@0: michael@0: bound = evhttp_accept_socket_with_handle(http, fd); michael@0: michael@0: if (bound != NULL) { michael@0: event_debug(("Bound to port %d - Awaiting connections ... ", michael@0: port)); michael@0: return (bound); michael@0: } michael@0: michael@0: return (NULL); michael@0: } michael@0: michael@0: int michael@0: evhttp_accept_socket(struct evhttp *http, evutil_socket_t fd) michael@0: { michael@0: struct evhttp_bound_socket *bound = michael@0: evhttp_accept_socket_with_handle(http, fd); michael@0: if (bound == NULL) michael@0: return (-1); michael@0: return (0); michael@0: } michael@0: michael@0: michael@0: struct evhttp_bound_socket * michael@0: evhttp_accept_socket_with_handle(struct evhttp *http, evutil_socket_t fd) michael@0: { michael@0: struct evhttp_bound_socket *bound; michael@0: struct evconnlistener *listener; michael@0: const int flags = michael@0: LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_EXEC|LEV_OPT_CLOSE_ON_FREE; michael@0: michael@0: listener = evconnlistener_new(http->base, NULL, NULL, michael@0: flags, michael@0: 0, /* Backlog is '0' because we already said 'listen' */ michael@0: fd); michael@0: if (!listener) michael@0: return (NULL); michael@0: michael@0: bound = evhttp_bind_listener(http, listener); michael@0: if (!bound) { michael@0: evconnlistener_free(listener); michael@0: return (NULL); michael@0: } michael@0: return (bound); michael@0: } michael@0: michael@0: struct evhttp_bound_socket * michael@0: evhttp_bind_listener(struct evhttp *http, struct evconnlistener *listener) michael@0: { michael@0: struct evhttp_bound_socket *bound; michael@0: michael@0: bound = mm_malloc(sizeof(struct evhttp_bound_socket)); michael@0: if (bound == NULL) michael@0: return (NULL); michael@0: michael@0: bound->listener = listener; michael@0: TAILQ_INSERT_TAIL(&http->sockets, bound, next); michael@0: michael@0: evconnlistener_set_cb(listener, accept_socket_cb, http); michael@0: return bound; michael@0: } michael@0: michael@0: evutil_socket_t michael@0: evhttp_bound_socket_get_fd(struct evhttp_bound_socket *bound) michael@0: { michael@0: return evconnlistener_get_fd(bound->listener); michael@0: } michael@0: michael@0: struct evconnlistener * michael@0: evhttp_bound_socket_get_listener(struct evhttp_bound_socket *bound) michael@0: { michael@0: return bound->listener; michael@0: } michael@0: michael@0: void michael@0: evhttp_del_accept_socket(struct evhttp *http, struct evhttp_bound_socket *bound) michael@0: { michael@0: TAILQ_REMOVE(&http->sockets, bound, next); michael@0: evconnlistener_free(bound->listener); michael@0: mm_free(bound); michael@0: } michael@0: michael@0: static struct evhttp* michael@0: evhttp_new_object(void) michael@0: { michael@0: struct evhttp *http = NULL; michael@0: michael@0: if ((http = mm_calloc(1, sizeof(struct evhttp))) == NULL) { michael@0: event_warn("%s: calloc", __func__); michael@0: return (NULL); michael@0: } michael@0: michael@0: http->timeout = -1; michael@0: evhttp_set_max_headers_size(http, EV_SIZE_MAX); michael@0: evhttp_set_max_body_size(http, EV_SIZE_MAX); michael@0: evhttp_set_allowed_methods(http, michael@0: EVHTTP_REQ_GET | michael@0: EVHTTP_REQ_POST | michael@0: EVHTTP_REQ_HEAD | michael@0: EVHTTP_REQ_PUT | michael@0: EVHTTP_REQ_DELETE); michael@0: michael@0: TAILQ_INIT(&http->sockets); michael@0: TAILQ_INIT(&http->callbacks); michael@0: TAILQ_INIT(&http->connections); michael@0: TAILQ_INIT(&http->virtualhosts); michael@0: TAILQ_INIT(&http->aliases); michael@0: michael@0: return (http); michael@0: } michael@0: michael@0: struct evhttp * michael@0: evhttp_new(struct event_base *base) michael@0: { michael@0: struct evhttp *http = NULL; michael@0: michael@0: http = evhttp_new_object(); michael@0: if (http == NULL) michael@0: return (NULL); michael@0: http->base = base; michael@0: michael@0: return (http); michael@0: } michael@0: michael@0: /* michael@0: * Start a web server on the specified address and port. michael@0: */ michael@0: michael@0: struct evhttp * michael@0: evhttp_start(const char *address, unsigned short port) michael@0: { michael@0: struct evhttp *http = NULL; michael@0: michael@0: http = evhttp_new_object(); michael@0: if (http == NULL) michael@0: return (NULL); michael@0: if (evhttp_bind_socket(http, address, port) == -1) { michael@0: mm_free(http); michael@0: return (NULL); michael@0: } michael@0: michael@0: return (http); michael@0: } michael@0: michael@0: void michael@0: evhttp_free(struct evhttp* http) michael@0: { michael@0: struct evhttp_cb *http_cb; michael@0: struct evhttp_connection *evcon; michael@0: struct evhttp_bound_socket *bound; michael@0: struct evhttp* vhost; michael@0: struct evhttp_server_alias *alias; michael@0: michael@0: /* Remove the accepting part */ michael@0: while ((bound = TAILQ_FIRST(&http->sockets)) != NULL) { michael@0: TAILQ_REMOVE(&http->sockets, bound, next); michael@0: michael@0: evconnlistener_free(bound->listener); michael@0: michael@0: mm_free(bound); michael@0: } michael@0: michael@0: while ((evcon = TAILQ_FIRST(&http->connections)) != NULL) { michael@0: /* evhttp_connection_free removes the connection */ michael@0: evhttp_connection_free(evcon); michael@0: } michael@0: michael@0: while ((http_cb = TAILQ_FIRST(&http->callbacks)) != NULL) { michael@0: TAILQ_REMOVE(&http->callbacks, http_cb, next); michael@0: mm_free(http_cb->what); michael@0: mm_free(http_cb); michael@0: } michael@0: michael@0: while ((vhost = TAILQ_FIRST(&http->virtualhosts)) != NULL) { michael@0: TAILQ_REMOVE(&http->virtualhosts, vhost, next_vhost); michael@0: michael@0: evhttp_free(vhost); michael@0: } michael@0: michael@0: if (http->vhost_pattern != NULL) michael@0: mm_free(http->vhost_pattern); michael@0: michael@0: while ((alias = TAILQ_FIRST(&http->aliases)) != NULL) { michael@0: TAILQ_REMOVE(&http->aliases, alias, next); michael@0: mm_free(alias->alias); michael@0: mm_free(alias); michael@0: } michael@0: michael@0: mm_free(http); michael@0: } michael@0: michael@0: int michael@0: evhttp_add_virtual_host(struct evhttp* http, const char *pattern, michael@0: struct evhttp* vhost) michael@0: { michael@0: /* a vhost can only be a vhost once and should not have bound sockets */ michael@0: if (vhost->vhost_pattern != NULL || michael@0: TAILQ_FIRST(&vhost->sockets) != NULL) michael@0: return (-1); michael@0: michael@0: vhost->vhost_pattern = mm_strdup(pattern); michael@0: if (vhost->vhost_pattern == NULL) michael@0: return (-1); michael@0: michael@0: TAILQ_INSERT_TAIL(&http->virtualhosts, vhost, next_vhost); michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: int michael@0: evhttp_remove_virtual_host(struct evhttp* http, struct evhttp* vhost) michael@0: { michael@0: if (vhost->vhost_pattern == NULL) michael@0: return (-1); michael@0: michael@0: TAILQ_REMOVE(&http->virtualhosts, vhost, next_vhost); michael@0: michael@0: mm_free(vhost->vhost_pattern); michael@0: vhost->vhost_pattern = NULL; michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: int michael@0: evhttp_add_server_alias(struct evhttp *http, const char *alias) michael@0: { michael@0: struct evhttp_server_alias *evalias; michael@0: michael@0: evalias = mm_calloc(1, sizeof(*evalias)); michael@0: if (!evalias) michael@0: return -1; michael@0: michael@0: evalias->alias = mm_strdup(alias); michael@0: if (!evalias->alias) { michael@0: mm_free(evalias); michael@0: return -1; michael@0: } michael@0: michael@0: TAILQ_INSERT_TAIL(&http->aliases, evalias, next); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: evhttp_remove_server_alias(struct evhttp *http, const char *alias) michael@0: { michael@0: struct evhttp_server_alias *evalias; michael@0: michael@0: TAILQ_FOREACH(evalias, &http->aliases, next) { michael@0: if (evutil_ascii_strcasecmp(evalias->alias, alias) == 0) { michael@0: TAILQ_REMOVE(&http->aliases, evalias, next); michael@0: mm_free(evalias->alias); michael@0: mm_free(evalias); michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: void michael@0: evhttp_set_timeout(struct evhttp* http, int timeout_in_secs) michael@0: { michael@0: http->timeout = timeout_in_secs; michael@0: } michael@0: michael@0: void michael@0: evhttp_set_max_headers_size(struct evhttp* http, ev_ssize_t max_headers_size) michael@0: { michael@0: if (max_headers_size < 0) michael@0: http->default_max_headers_size = EV_SIZE_MAX; michael@0: else michael@0: http->default_max_headers_size = max_headers_size; michael@0: } michael@0: michael@0: void michael@0: evhttp_set_max_body_size(struct evhttp* http, ev_ssize_t max_body_size) michael@0: { michael@0: if (max_body_size < 0) michael@0: http->default_max_body_size = EV_UINT64_MAX; michael@0: else michael@0: http->default_max_body_size = max_body_size; michael@0: } michael@0: michael@0: void michael@0: evhttp_set_allowed_methods(struct evhttp* http, ev_uint16_t methods) michael@0: { michael@0: http->allowed_methods = methods; michael@0: } michael@0: michael@0: int michael@0: evhttp_set_cb(struct evhttp *http, const char *uri, michael@0: void (*cb)(struct evhttp_request *, void *), void *cbarg) michael@0: { michael@0: struct evhttp_cb *http_cb; michael@0: michael@0: TAILQ_FOREACH(http_cb, &http->callbacks, next) { michael@0: if (strcmp(http_cb->what, uri) == 0) michael@0: return (-1); michael@0: } michael@0: michael@0: if ((http_cb = mm_calloc(1, sizeof(struct evhttp_cb))) == NULL) { michael@0: event_warn("%s: calloc", __func__); michael@0: return (-2); michael@0: } michael@0: michael@0: http_cb->what = mm_strdup(uri); michael@0: if (http_cb->what == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: mm_free(http_cb); michael@0: return (-3); michael@0: } michael@0: http_cb->cb = cb; michael@0: http_cb->cbarg = cbarg; michael@0: michael@0: TAILQ_INSERT_TAIL(&http->callbacks, http_cb, next); michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: int michael@0: evhttp_del_cb(struct evhttp *http, const char *uri) michael@0: { michael@0: struct evhttp_cb *http_cb; michael@0: michael@0: TAILQ_FOREACH(http_cb, &http->callbacks, next) { michael@0: if (strcmp(http_cb->what, uri) == 0) michael@0: break; michael@0: } michael@0: if (http_cb == NULL) michael@0: return (-1); michael@0: michael@0: TAILQ_REMOVE(&http->callbacks, http_cb, next); michael@0: mm_free(http_cb->what); michael@0: mm_free(http_cb); michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: void michael@0: evhttp_set_gencb(struct evhttp *http, michael@0: void (*cb)(struct evhttp_request *, void *), void *cbarg) michael@0: { michael@0: http->gencb = cb; michael@0: http->gencbarg = cbarg; michael@0: } michael@0: michael@0: /* michael@0: * Request related functions michael@0: */ michael@0: michael@0: struct evhttp_request * michael@0: evhttp_request_new(void (*cb)(struct evhttp_request *, void *), void *arg) michael@0: { michael@0: struct evhttp_request *req = NULL; michael@0: michael@0: /* Allocate request structure */ michael@0: if ((req = mm_calloc(1, sizeof(struct evhttp_request))) == NULL) { michael@0: event_warn("%s: calloc", __func__); michael@0: goto error; michael@0: } michael@0: michael@0: req->headers_size = 0; michael@0: req->body_size = 0; michael@0: michael@0: req->kind = EVHTTP_RESPONSE; michael@0: req->input_headers = mm_calloc(1, sizeof(struct evkeyvalq)); michael@0: if (req->input_headers == NULL) { michael@0: event_warn("%s: calloc", __func__); michael@0: goto error; michael@0: } michael@0: TAILQ_INIT(req->input_headers); michael@0: michael@0: req->output_headers = mm_calloc(1, sizeof(struct evkeyvalq)); michael@0: if (req->output_headers == NULL) { michael@0: event_warn("%s: calloc", __func__); michael@0: goto error; michael@0: } michael@0: TAILQ_INIT(req->output_headers); michael@0: michael@0: if ((req->input_buffer = evbuffer_new()) == NULL) { michael@0: event_warn("%s: evbuffer_new", __func__); michael@0: goto error; michael@0: } michael@0: michael@0: if ((req->output_buffer = evbuffer_new()) == NULL) { michael@0: event_warn("%s: evbuffer_new", __func__); michael@0: goto error; michael@0: } michael@0: michael@0: req->cb = cb; michael@0: req->cb_arg = arg; michael@0: michael@0: return (req); michael@0: michael@0: error: michael@0: if (req != NULL) michael@0: evhttp_request_free(req); michael@0: return (NULL); michael@0: } michael@0: michael@0: void michael@0: evhttp_request_free(struct evhttp_request *req) michael@0: { michael@0: if ((req->flags & EVHTTP_REQ_DEFER_FREE) != 0) { michael@0: req->flags |= EVHTTP_REQ_NEEDS_FREE; michael@0: return; michael@0: } michael@0: michael@0: if (req->remote_host != NULL) michael@0: mm_free(req->remote_host); michael@0: if (req->uri != NULL) michael@0: mm_free(req->uri); michael@0: if (req->uri_elems != NULL) michael@0: evhttp_uri_free(req->uri_elems); michael@0: if (req->response_code_line != NULL) michael@0: mm_free(req->response_code_line); michael@0: if (req->host_cache != NULL) michael@0: mm_free(req->host_cache); michael@0: michael@0: evhttp_clear_headers(req->input_headers); michael@0: mm_free(req->input_headers); michael@0: michael@0: evhttp_clear_headers(req->output_headers); michael@0: mm_free(req->output_headers); michael@0: michael@0: if (req->input_buffer != NULL) michael@0: evbuffer_free(req->input_buffer); michael@0: michael@0: if (req->output_buffer != NULL) michael@0: evbuffer_free(req->output_buffer); michael@0: michael@0: mm_free(req); michael@0: } michael@0: michael@0: void michael@0: evhttp_request_own(struct evhttp_request *req) michael@0: { michael@0: req->flags |= EVHTTP_USER_OWNED; michael@0: } michael@0: michael@0: int michael@0: evhttp_request_is_owned(struct evhttp_request *req) michael@0: { michael@0: return (req->flags & EVHTTP_USER_OWNED) != 0; michael@0: } michael@0: michael@0: struct evhttp_connection * michael@0: evhttp_request_get_connection(struct evhttp_request *req) michael@0: { michael@0: return req->evcon; michael@0: } michael@0: michael@0: struct event_base * michael@0: evhttp_connection_get_base(struct evhttp_connection *conn) michael@0: { michael@0: return conn->base; michael@0: } michael@0: michael@0: void michael@0: evhttp_request_set_chunked_cb(struct evhttp_request *req, michael@0: void (*cb)(struct evhttp_request *, void *)) michael@0: { michael@0: req->chunk_cb = cb; michael@0: } michael@0: michael@0: /* michael@0: * Allows for inspection of the request URI michael@0: */ michael@0: michael@0: const char * michael@0: evhttp_request_get_uri(const struct evhttp_request *req) { michael@0: if (req->uri == NULL) michael@0: event_debug(("%s: request %p has no uri\n", __func__, req)); michael@0: return (req->uri); michael@0: } michael@0: michael@0: const struct evhttp_uri * michael@0: evhttp_request_get_evhttp_uri(const struct evhttp_request *req) { michael@0: if (req->uri_elems == NULL) michael@0: event_debug(("%s: request %p has no uri elems\n", michael@0: __func__, req)); michael@0: return (req->uri_elems); michael@0: } michael@0: michael@0: const char * michael@0: evhttp_request_get_host(struct evhttp_request *req) michael@0: { michael@0: const char *host = NULL; michael@0: michael@0: if (req->host_cache) michael@0: return req->host_cache; michael@0: michael@0: if (req->uri_elems) michael@0: host = evhttp_uri_get_host(req->uri_elems); michael@0: if (!host && req->input_headers) { michael@0: const char *p; michael@0: size_t len; michael@0: michael@0: host = evhttp_find_header(req->input_headers, "Host"); michael@0: /* The Host: header may include a port. Remove it here michael@0: to be consistent with uri_elems case above. */ michael@0: if (host) { michael@0: p = host + strlen(host) - 1; michael@0: while (p > host && EVUTIL_ISDIGIT(*p)) michael@0: --p; michael@0: if (p > host && *p == ':') { michael@0: len = p - host; michael@0: req->host_cache = mm_malloc(len + 1); michael@0: if (!req->host_cache) { michael@0: event_warn("%s: malloc", __func__); michael@0: return NULL; michael@0: } michael@0: memcpy(req->host_cache, host, len); michael@0: req->host_cache[len] = '\0'; michael@0: host = req->host_cache; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return host; michael@0: } michael@0: michael@0: enum evhttp_cmd_type michael@0: evhttp_request_get_command(const struct evhttp_request *req) { michael@0: return (req->type); michael@0: } michael@0: michael@0: int michael@0: evhttp_request_get_response_code(const struct evhttp_request *req) michael@0: { michael@0: return req->response_code; michael@0: } michael@0: michael@0: /** Returns the input headers */ michael@0: struct evkeyvalq *evhttp_request_get_input_headers(struct evhttp_request *req) michael@0: { michael@0: return (req->input_headers); michael@0: } michael@0: michael@0: /** Returns the output headers */ michael@0: struct evkeyvalq *evhttp_request_get_output_headers(struct evhttp_request *req) michael@0: { michael@0: return (req->output_headers); michael@0: } michael@0: michael@0: /** Returns the input buffer */ michael@0: struct evbuffer *evhttp_request_get_input_buffer(struct evhttp_request *req) michael@0: { michael@0: return (req->input_buffer); michael@0: } michael@0: michael@0: /** Returns the output buffer */ michael@0: struct evbuffer *evhttp_request_get_output_buffer(struct evhttp_request *req) michael@0: { michael@0: return (req->output_buffer); michael@0: } michael@0: michael@0: michael@0: /* michael@0: * Takes a file descriptor to read a request from. michael@0: * The callback is executed once the whole request has been read. michael@0: */ michael@0: michael@0: static struct evhttp_connection* michael@0: evhttp_get_request_connection( michael@0: struct evhttp* http, michael@0: evutil_socket_t fd, struct sockaddr *sa, ev_socklen_t salen) michael@0: { michael@0: struct evhttp_connection *evcon; michael@0: char *hostname = NULL, *portname = NULL; michael@0: michael@0: name_from_addr(sa, salen, &hostname, &portname); michael@0: if (hostname == NULL || portname == NULL) { michael@0: if (hostname) mm_free(hostname); michael@0: if (portname) mm_free(portname); michael@0: return (NULL); michael@0: } michael@0: michael@0: event_debug(("%s: new request from %s:%s on "EV_SOCK_FMT"\n", michael@0: __func__, hostname, portname, EV_SOCK_ARG(fd))); michael@0: michael@0: /* we need a connection object to put the http request on */ michael@0: evcon = evhttp_connection_base_new( michael@0: http->base, NULL, hostname, atoi(portname)); michael@0: mm_free(hostname); michael@0: mm_free(portname); michael@0: if (evcon == NULL) michael@0: return (NULL); michael@0: michael@0: evcon->max_headers_size = http->default_max_headers_size; michael@0: evcon->max_body_size = http->default_max_body_size; michael@0: michael@0: evcon->flags |= EVHTTP_CON_INCOMING; michael@0: evcon->state = EVCON_READING_FIRSTLINE; michael@0: michael@0: evcon->fd = fd; michael@0: michael@0: bufferevent_setfd(evcon->bufev, fd); michael@0: michael@0: return (evcon); michael@0: } michael@0: michael@0: static int michael@0: evhttp_associate_new_request_with_connection(struct evhttp_connection *evcon) michael@0: { michael@0: struct evhttp *http = evcon->http_server; michael@0: struct evhttp_request *req; michael@0: if ((req = evhttp_request_new(evhttp_handle_request, http)) == NULL) michael@0: return (-1); michael@0: michael@0: if ((req->remote_host = mm_strdup(evcon->address)) == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: evhttp_request_free(req); michael@0: return (-1); michael@0: } michael@0: req->remote_port = evcon->port; michael@0: michael@0: req->evcon = evcon; /* the request ends up owning the connection */ michael@0: req->flags |= EVHTTP_REQ_OWN_CONNECTION; michael@0: michael@0: /* We did not present the request to the user user yet, so treat it as michael@0: * if the user was done with the request. This allows us to free the michael@0: * request on a persistent connection if the client drops it without michael@0: * sending a request. michael@0: */ michael@0: req->userdone = 1; michael@0: michael@0: TAILQ_INSERT_TAIL(&evcon->requests, req, next); michael@0: michael@0: req->kind = EVHTTP_REQUEST; michael@0: michael@0: michael@0: evhttp_start_read(evcon); michael@0: michael@0: return (0); michael@0: } michael@0: michael@0: static void michael@0: evhttp_get_request(struct evhttp *http, evutil_socket_t fd, michael@0: struct sockaddr *sa, ev_socklen_t salen) michael@0: { michael@0: struct evhttp_connection *evcon; michael@0: michael@0: evcon = evhttp_get_request_connection(http, fd, sa, salen); michael@0: if (evcon == NULL) { michael@0: event_sock_warn(fd, "%s: cannot get connection on "EV_SOCK_FMT, michael@0: __func__, EV_SOCK_ARG(fd)); michael@0: evutil_closesocket(fd); michael@0: return; michael@0: } michael@0: michael@0: /* the timeout can be used by the server to close idle connections */ michael@0: if (http->timeout != -1) michael@0: evhttp_connection_set_timeout(evcon, http->timeout); michael@0: michael@0: /* michael@0: * if we want to accept more than one request on a connection, michael@0: * we need to know which http server it belongs to. michael@0: */ michael@0: evcon->http_server = http; michael@0: TAILQ_INSERT_TAIL(&http->connections, evcon, next); michael@0: michael@0: if (evhttp_associate_new_request_with_connection(evcon) == -1) michael@0: evhttp_connection_free(evcon); michael@0: } michael@0: michael@0: michael@0: /* michael@0: * Network helper functions that we do not want to export to the rest of michael@0: * the world. michael@0: */ michael@0: michael@0: static void michael@0: name_from_addr(struct sockaddr *sa, ev_socklen_t salen, michael@0: char **phost, char **pport) michael@0: { michael@0: char ntop[NI_MAXHOST]; michael@0: char strport[NI_MAXSERV]; michael@0: int ni_result; michael@0: michael@0: #ifdef _EVENT_HAVE_GETNAMEINFO michael@0: ni_result = getnameinfo(sa, salen, michael@0: ntop, sizeof(ntop), strport, sizeof(strport), michael@0: NI_NUMERICHOST|NI_NUMERICSERV); michael@0: michael@0: if (ni_result != 0) { michael@0: #ifdef EAI_SYSTEM michael@0: /* Windows doesn't have an EAI_SYSTEM. */ michael@0: if (ni_result == EAI_SYSTEM) michael@0: event_err(1, "getnameinfo failed"); michael@0: else michael@0: #endif michael@0: event_errx(1, "getnameinfo failed: %s", gai_strerror(ni_result)); michael@0: return; michael@0: } michael@0: #else michael@0: ni_result = fake_getnameinfo(sa, salen, michael@0: ntop, sizeof(ntop), strport, sizeof(strport), michael@0: NI_NUMERICHOST|NI_NUMERICSERV); michael@0: if (ni_result != 0) michael@0: return; michael@0: #endif michael@0: michael@0: *phost = mm_strdup(ntop); michael@0: *pport = mm_strdup(strport); michael@0: } michael@0: michael@0: /* Create a non-blocking socket and bind it */ michael@0: /* todo: rename this function */ michael@0: static evutil_socket_t michael@0: bind_socket_ai(struct evutil_addrinfo *ai, int reuse) michael@0: { michael@0: evutil_socket_t fd; michael@0: michael@0: int on = 1, r; michael@0: int serrno; michael@0: michael@0: /* Create listen socket */ michael@0: fd = socket(ai ? ai->ai_family : AF_INET, SOCK_STREAM, 0); michael@0: if (fd == -1) { michael@0: event_sock_warn(-1, "socket"); michael@0: return (-1); michael@0: } michael@0: michael@0: if (evutil_make_socket_nonblocking(fd) < 0) michael@0: goto out; michael@0: if (evutil_make_socket_closeonexec(fd) < 0) michael@0: goto out; michael@0: michael@0: if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on))<0) michael@0: goto out; michael@0: if (reuse) { michael@0: if (evutil_make_listen_socket_reuseable(fd) < 0) michael@0: goto out; michael@0: } michael@0: michael@0: if (ai != NULL) { michael@0: r = bind(fd, ai->ai_addr, (ev_socklen_t)ai->ai_addrlen); michael@0: if (r == -1) michael@0: goto out; michael@0: } michael@0: michael@0: return (fd); michael@0: michael@0: out: michael@0: serrno = EVUTIL_SOCKET_ERROR(); michael@0: evutil_closesocket(fd); michael@0: EVUTIL_SET_SOCKET_ERROR(serrno); michael@0: return (-1); michael@0: } michael@0: michael@0: static struct evutil_addrinfo * michael@0: make_addrinfo(const char *address, ev_uint16_t port) michael@0: { michael@0: struct evutil_addrinfo *ai = NULL; michael@0: michael@0: struct evutil_addrinfo hints; michael@0: char strport[NI_MAXSERV]; michael@0: int ai_result; michael@0: michael@0: memset(&hints, 0, sizeof(hints)); michael@0: hints.ai_family = AF_UNSPEC; michael@0: hints.ai_socktype = SOCK_STREAM; michael@0: /* turn NULL hostname into INADDR_ANY, and skip looking up any address michael@0: * types we don't have an interface to connect to. */ michael@0: hints.ai_flags = EVUTIL_AI_PASSIVE|EVUTIL_AI_ADDRCONFIG; michael@0: evutil_snprintf(strport, sizeof(strport), "%d", port); michael@0: if ((ai_result = evutil_getaddrinfo(address, strport, &hints, &ai)) michael@0: != 0) { michael@0: if (ai_result == EVUTIL_EAI_SYSTEM) michael@0: event_warn("getaddrinfo"); michael@0: else michael@0: event_warnx("getaddrinfo: %s", michael@0: evutil_gai_strerror(ai_result)); michael@0: return (NULL); michael@0: } michael@0: michael@0: return (ai); michael@0: } michael@0: michael@0: static evutil_socket_t michael@0: bind_socket(const char *address, ev_uint16_t port, int reuse) michael@0: { michael@0: evutil_socket_t fd; michael@0: struct evutil_addrinfo *aitop = NULL; michael@0: michael@0: /* just create an unbound socket */ michael@0: if (address == NULL && port == 0) michael@0: return bind_socket_ai(NULL, 0); michael@0: michael@0: aitop = make_addrinfo(address, port); michael@0: michael@0: if (aitop == NULL) michael@0: return (-1); michael@0: michael@0: fd = bind_socket_ai(aitop, reuse); michael@0: michael@0: evutil_freeaddrinfo(aitop); michael@0: michael@0: return (fd); michael@0: } michael@0: michael@0: struct evhttp_uri { michael@0: unsigned flags; michael@0: char *scheme; /* scheme; e.g http, ftp etc */ michael@0: char *userinfo; /* userinfo (typically username:pass), or NULL */ michael@0: char *host; /* hostname, IP address, or NULL */ michael@0: int port; /* port, or zero */ michael@0: char *path; /* path, or "". */ michael@0: char *query; /* query, or NULL */ michael@0: char *fragment; /* fragment or NULL */ michael@0: }; michael@0: michael@0: struct evhttp_uri * michael@0: evhttp_uri_new(void) michael@0: { michael@0: struct evhttp_uri *uri = mm_calloc(sizeof(struct evhttp_uri), 1); michael@0: if (uri) michael@0: uri->port = -1; michael@0: return uri; michael@0: } michael@0: michael@0: void michael@0: evhttp_uri_set_flags(struct evhttp_uri *uri, unsigned flags) michael@0: { michael@0: uri->flags = flags; michael@0: } michael@0: michael@0: /* Return true if the string starting at s and ending immediately before eos michael@0: * is a valid URI scheme according to RFC3986 michael@0: */ michael@0: static int michael@0: scheme_ok(const char *s, const char *eos) michael@0: { michael@0: /* scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */ michael@0: EVUTIL_ASSERT(eos >= s); michael@0: if (s == eos) michael@0: return 0; michael@0: if (!EVUTIL_ISALPHA(*s)) michael@0: return 0; michael@0: while (++s < eos) { michael@0: if (! EVUTIL_ISALNUM(*s) && michael@0: *s != '+' && *s != '-' && *s != '.') michael@0: return 0; michael@0: } michael@0: return 1; michael@0: } michael@0: michael@0: #define SUBDELIMS "!$&'()*+,;=" michael@0: michael@0: /* Return true iff [s..eos) is a valid userinfo */ michael@0: static int michael@0: userinfo_ok(const char *s, const char *eos) michael@0: { michael@0: while (s < eos) { michael@0: if (CHAR_IS_UNRESERVED(*s) || michael@0: strchr(SUBDELIMS, *s) || michael@0: *s == ':') michael@0: ++s; michael@0: else if (*s == '%' && s+2 < eos && michael@0: EVUTIL_ISXDIGIT(s[1]) && michael@0: EVUTIL_ISXDIGIT(s[2])) michael@0: s += 3; michael@0: else michael@0: return 0; michael@0: } michael@0: return 1; michael@0: } michael@0: michael@0: static int michael@0: regname_ok(const char *s, const char *eos) michael@0: { michael@0: while (s && s eos || *s != '[' || *(eos-1) != ']') michael@0: return 0; michael@0: if (s[1] == 'v') { michael@0: /* IPvFuture, or junk. michael@0: "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) michael@0: */ michael@0: s += 2; /* skip [v */ michael@0: --eos; michael@0: if (!EVUTIL_ISXDIGIT(*s)) /*require at least one*/ michael@0: return 0; michael@0: while (s < eos && *s != '.') { michael@0: if (EVUTIL_ISXDIGIT(*s)) michael@0: ++s; michael@0: else michael@0: return 0; michael@0: } michael@0: if (*s != '.') michael@0: return 0; michael@0: ++s; michael@0: while (s < eos) { michael@0: if (CHAR_IS_UNRESERVED(*s) || michael@0: strchr(SUBDELIMS, *s) || michael@0: *s == ':') michael@0: ++s; michael@0: else michael@0: return 0; michael@0: } michael@0: return 2; michael@0: } else { michael@0: /* IPv6, or junk */ michael@0: char buf[64]; michael@0: ev_ssize_t n_chars = eos-s-2; michael@0: struct in6_addr in6; michael@0: if (n_chars >= 64) /* way too long */ michael@0: return 0; michael@0: memcpy(buf, s+1, n_chars); michael@0: buf[n_chars]='\0'; michael@0: return (evutil_inet_pton(AF_INET6,buf,&in6)==1) ? 1 : 0; michael@0: } michael@0: } michael@0: michael@0: static int michael@0: parse_authority(struct evhttp_uri *uri, char *s, char *eos) michael@0: { michael@0: char *cp, *port; michael@0: EVUTIL_ASSERT(eos); michael@0: if (eos == s) { michael@0: uri->host = mm_strdup(""); michael@0: if (uri->host == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: return -1; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: /* Optionally, we start with "userinfo@" */ michael@0: michael@0: cp = strchr(s, '@'); michael@0: if (cp && cp < eos) { michael@0: if (! userinfo_ok(s,cp)) michael@0: return -1; michael@0: *cp++ = '\0'; michael@0: uri->userinfo = mm_strdup(s); michael@0: if (uri->userinfo == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: return -1; michael@0: } michael@0: } else { michael@0: cp = s; michael@0: } michael@0: /* Optionally, we end with ":port" */ michael@0: for (port=eos-1; port >= cp && EVUTIL_ISDIGIT(*port); --port) michael@0: ; michael@0: if (port >= cp && *port == ':') { michael@0: if (port+1 == eos) /* Leave port unspecified; the RFC allows a michael@0: * nil port */ michael@0: uri->port = -1; michael@0: else if ((uri->port = parse_port(port+1, eos))<0) michael@0: return -1; michael@0: eos = port; michael@0: } michael@0: /* Now, cp..eos holds the "host" port, which can be an IPv4Address, michael@0: * an IP-Literal, or a reg-name */ michael@0: EVUTIL_ASSERT(eos >= cp); michael@0: if (*cp == '[' && eos >= cp+2 && *(eos-1) == ']') { michael@0: /* IPv6address, IP-Literal, or junk. */ michael@0: if (! bracket_addr_ok(cp, eos)) michael@0: return -1; michael@0: } else { michael@0: /* Make sure the host part is ok. */ michael@0: if (! regname_ok(cp,eos)) /* Match IPv4Address or reg-name */ michael@0: return -1; michael@0: } michael@0: uri->host = mm_malloc(eos-cp+1); michael@0: if (uri->host == NULL) { michael@0: event_warn("%s: malloc", __func__); michael@0: return -1; michael@0: } michael@0: memcpy(uri->host, cp, eos-cp); michael@0: uri->host[eos-cp] = '\0'; michael@0: return 0; michael@0: michael@0: } michael@0: michael@0: static char * michael@0: end_of_authority(char *cp) michael@0: { michael@0: while (*cp) { michael@0: if (*cp == '?' || *cp == '#' || *cp == '/') michael@0: return cp; michael@0: ++cp; michael@0: } michael@0: return cp; michael@0: } michael@0: michael@0: enum uri_part { michael@0: PART_PATH, michael@0: PART_QUERY, michael@0: PART_FRAGMENT michael@0: }; michael@0: michael@0: /* Return the character after the longest prefix of 'cp' that matches... michael@0: * *pchar / "/" if allow_qchars is false, or michael@0: * *(pchar / "/" / "?") if allow_qchars is true. michael@0: */ michael@0: static char * michael@0: end_of_path(char *cp, enum uri_part part, unsigned flags) michael@0: { michael@0: if (flags & EVHTTP_URI_NONCONFORMANT) { michael@0: /* If NONCONFORMANT: michael@0: * Path is everything up to a # or ? or nul. michael@0: * Query is everything up a # or nul michael@0: * Fragment is everything up to a nul. michael@0: */ michael@0: switch (part) { michael@0: case PART_PATH: michael@0: while (*cp && *cp != '#' && *cp != '?') michael@0: ++cp; michael@0: break; michael@0: case PART_QUERY: michael@0: while (*cp && *cp != '#') michael@0: ++cp; michael@0: break; michael@0: case PART_FRAGMENT: michael@0: cp += strlen(cp); michael@0: break; michael@0: }; michael@0: return cp; michael@0: } michael@0: michael@0: while (*cp) { michael@0: if (CHAR_IS_UNRESERVED(*cp) || michael@0: strchr(SUBDELIMS, *cp) || michael@0: *cp == ':' || *cp == '@' || *cp == '/') michael@0: ++cp; michael@0: else if (*cp == '%' && EVUTIL_ISXDIGIT(cp[1]) && michael@0: EVUTIL_ISXDIGIT(cp[2])) michael@0: cp += 3; michael@0: else if (*cp == '?' && part != PART_PATH) michael@0: ++cp; michael@0: else michael@0: return cp; michael@0: } michael@0: return cp; michael@0: } michael@0: michael@0: static int michael@0: path_matches_noscheme(const char *cp) michael@0: { michael@0: while (*cp) { michael@0: if (*cp == ':') michael@0: return 0; michael@0: else if (*cp == '/') michael@0: return 1; michael@0: ++cp; michael@0: } michael@0: return 1; michael@0: } michael@0: michael@0: struct evhttp_uri * michael@0: evhttp_uri_parse(const char *source_uri) michael@0: { michael@0: return evhttp_uri_parse_with_flags(source_uri, 0); michael@0: } michael@0: michael@0: struct evhttp_uri * michael@0: evhttp_uri_parse_with_flags(const char *source_uri, unsigned flags) michael@0: { michael@0: char *readbuf = NULL, *readp = NULL, *token = NULL, *query = NULL; michael@0: char *path = NULL, *fragment = NULL; michael@0: int got_authority = 0; michael@0: michael@0: struct evhttp_uri *uri = mm_calloc(1, sizeof(struct evhttp_uri)); michael@0: if (uri == NULL) { michael@0: event_warn("%s: calloc", __func__); michael@0: goto err; michael@0: } michael@0: uri->port = -1; michael@0: uri->flags = flags; michael@0: michael@0: readbuf = mm_strdup(source_uri); michael@0: if (readbuf == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: goto err; michael@0: } michael@0: michael@0: readp = readbuf; michael@0: token = NULL; michael@0: michael@0: /* We try to follow RFC3986 here as much as we can, and match michael@0: the productions michael@0: michael@0: URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] michael@0: michael@0: relative-ref = relative-part [ "?" query ] [ "#" fragment ] michael@0: */ michael@0: michael@0: /* 1. scheme: */ michael@0: token = strchr(readp, ':'); michael@0: if (token && scheme_ok(readp,token)) { michael@0: *token = '\0'; michael@0: uri->scheme = mm_strdup(readp); michael@0: if (uri->scheme == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: goto err; michael@0: } michael@0: readp = token+1; /* eat : */ michael@0: } michael@0: michael@0: /* 2. Optionally, "//" then an 'authority' part. */ michael@0: if (readp[0]=='/' && readp[1] == '/') { michael@0: char *authority; michael@0: readp += 2; michael@0: authority = readp; michael@0: path = end_of_authority(readp); michael@0: if (parse_authority(uri, authority, path) < 0) michael@0: goto err; michael@0: readp = path; michael@0: got_authority = 1; michael@0: } michael@0: michael@0: /* 3. Query: path-abempty, path-absolute, path-rootless, or path-empty michael@0: */ michael@0: path = readp; michael@0: readp = end_of_path(path, PART_PATH, flags); michael@0: michael@0: /* Query */ michael@0: if (*readp == '?') { michael@0: *readp = '\0'; michael@0: ++readp; michael@0: query = readp; michael@0: readp = end_of_path(readp, PART_QUERY, flags); michael@0: } michael@0: /* fragment */ michael@0: if (*readp == '#') { michael@0: *readp = '\0'; michael@0: ++readp; michael@0: fragment = readp; michael@0: readp = end_of_path(readp, PART_FRAGMENT, flags); michael@0: } michael@0: if (*readp != '\0') { michael@0: goto err; michael@0: } michael@0: michael@0: /* These next two cases may be unreachable; I'm leaving them michael@0: * in to be defensive. */ michael@0: /* If you didn't get an authority, the path can't begin with "//" */ michael@0: if (!got_authority && path[0]=='/' && path[1]=='/') michael@0: goto err; michael@0: /* If you did get an authority, the path must begin with "/" or be michael@0: * empty. */ michael@0: if (got_authority && path[0] != '/' && path[0] != '\0') michael@0: goto err; michael@0: /* (End of maybe-unreachable cases) */ michael@0: michael@0: /* If there was no scheme, the first part of the path (if any) must michael@0: * have no colon in it. */ michael@0: if (! uri->scheme && !path_matches_noscheme(path)) michael@0: goto err; michael@0: michael@0: EVUTIL_ASSERT(path); michael@0: uri->path = mm_strdup(path); michael@0: if (uri->path == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: goto err; michael@0: } michael@0: michael@0: if (query) { michael@0: uri->query = mm_strdup(query); michael@0: if (uri->query == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: goto err; michael@0: } michael@0: } michael@0: if (fragment) { michael@0: uri->fragment = mm_strdup(fragment); michael@0: if (uri->fragment == NULL) { michael@0: event_warn("%s: strdup", __func__); michael@0: goto err; michael@0: } michael@0: } michael@0: michael@0: mm_free(readbuf); michael@0: michael@0: return uri; michael@0: err: michael@0: if (uri) michael@0: evhttp_uri_free(uri); michael@0: if (readbuf) michael@0: mm_free(readbuf); michael@0: return NULL; michael@0: } michael@0: michael@0: void michael@0: evhttp_uri_free(struct evhttp_uri *uri) michael@0: { michael@0: #define _URI_FREE_STR(f) \ michael@0: if (uri->f) { \ michael@0: mm_free(uri->f); \ michael@0: } michael@0: michael@0: _URI_FREE_STR(scheme); michael@0: _URI_FREE_STR(userinfo); michael@0: _URI_FREE_STR(host); michael@0: _URI_FREE_STR(path); michael@0: _URI_FREE_STR(query); michael@0: _URI_FREE_STR(fragment); michael@0: michael@0: mm_free(uri); michael@0: #undef _URI_FREE_STR michael@0: } michael@0: michael@0: char * michael@0: evhttp_uri_join(struct evhttp_uri *uri, char *buf, size_t limit) michael@0: { michael@0: struct evbuffer *tmp = 0; michael@0: size_t joined_size = 0; michael@0: char *output = NULL; michael@0: michael@0: #define _URI_ADD(f) evbuffer_add(tmp, uri->f, strlen(uri->f)) michael@0: michael@0: if (!uri || !buf || !limit) michael@0: return NULL; michael@0: michael@0: tmp = evbuffer_new(); michael@0: if (!tmp) michael@0: return NULL; michael@0: michael@0: if (uri->scheme) { michael@0: _URI_ADD(scheme); michael@0: evbuffer_add(tmp, ":", 1); michael@0: } michael@0: if (uri->host) { michael@0: evbuffer_add(tmp, "//", 2); michael@0: if (uri->userinfo) michael@0: evbuffer_add_printf(tmp,"%s@", uri->userinfo); michael@0: _URI_ADD(host); michael@0: if (uri->port >= 0) michael@0: evbuffer_add_printf(tmp,":%d", uri->port); michael@0: michael@0: if (uri->path && uri->path[0] != '/' && uri->path[0] != '\0') michael@0: goto err; michael@0: } michael@0: michael@0: if (uri->path) michael@0: _URI_ADD(path); michael@0: michael@0: if (uri->query) { michael@0: evbuffer_add(tmp, "?", 1); michael@0: _URI_ADD(query); michael@0: } michael@0: michael@0: if (uri->fragment) { michael@0: evbuffer_add(tmp, "#", 1); michael@0: _URI_ADD(fragment); michael@0: } michael@0: michael@0: evbuffer_add(tmp, "\0", 1); /* NUL */ michael@0: michael@0: joined_size = evbuffer_get_length(tmp); michael@0: michael@0: if (joined_size > limit) { michael@0: /* It doesn't fit. */ michael@0: evbuffer_free(tmp); michael@0: return NULL; michael@0: } michael@0: evbuffer_remove(tmp, buf, joined_size); michael@0: michael@0: output = buf; michael@0: err: michael@0: evbuffer_free(tmp); michael@0: michael@0: return output; michael@0: #undef _URI_ADD michael@0: } michael@0: michael@0: const char * michael@0: evhttp_uri_get_scheme(const struct evhttp_uri *uri) michael@0: { michael@0: return uri->scheme; michael@0: } michael@0: const char * michael@0: evhttp_uri_get_userinfo(const struct evhttp_uri *uri) michael@0: { michael@0: return uri->userinfo; michael@0: } michael@0: const char * michael@0: evhttp_uri_get_host(const struct evhttp_uri *uri) michael@0: { michael@0: return uri->host; michael@0: } michael@0: int michael@0: evhttp_uri_get_port(const struct evhttp_uri *uri) michael@0: { michael@0: return uri->port; michael@0: } michael@0: const char * michael@0: evhttp_uri_get_path(const struct evhttp_uri *uri) michael@0: { michael@0: return uri->path; michael@0: } michael@0: const char * michael@0: evhttp_uri_get_query(const struct evhttp_uri *uri) michael@0: { michael@0: return uri->query; michael@0: } michael@0: const char * michael@0: evhttp_uri_get_fragment(const struct evhttp_uri *uri) michael@0: { michael@0: return uri->fragment; michael@0: } michael@0: michael@0: #define _URI_SET_STR(f) do { \ michael@0: if (uri->f) \ michael@0: mm_free(uri->f); \ michael@0: if (f) { \ michael@0: if ((uri->f = mm_strdup(f)) == NULL) { \ michael@0: event_warn("%s: strdup()", __func__); \ michael@0: return -1; \ michael@0: } \ michael@0: } else { \ michael@0: uri->f = NULL; \ michael@0: } \ michael@0: } while(0) michael@0: michael@0: int michael@0: evhttp_uri_set_scheme(struct evhttp_uri *uri, const char *scheme) michael@0: { michael@0: if (scheme && !scheme_ok(scheme, scheme+strlen(scheme))) michael@0: return -1; michael@0: michael@0: _URI_SET_STR(scheme); michael@0: return 0; michael@0: } michael@0: int michael@0: evhttp_uri_set_userinfo(struct evhttp_uri *uri, const char *userinfo) michael@0: { michael@0: if (userinfo && !userinfo_ok(userinfo, userinfo+strlen(userinfo))) michael@0: return -1; michael@0: _URI_SET_STR(userinfo); michael@0: return 0; michael@0: } michael@0: int michael@0: evhttp_uri_set_host(struct evhttp_uri *uri, const char *host) michael@0: { michael@0: if (host) { michael@0: if (host[0] == '[') { michael@0: if (! bracket_addr_ok(host, host+strlen(host))) michael@0: return -1; michael@0: } else { michael@0: if (! regname_ok(host, host+strlen(host))) michael@0: return -1; michael@0: } michael@0: } michael@0: michael@0: _URI_SET_STR(host); michael@0: return 0; michael@0: } michael@0: int michael@0: evhttp_uri_set_port(struct evhttp_uri *uri, int port) michael@0: { michael@0: if (port < -1) michael@0: return -1; michael@0: uri->port = port; michael@0: return 0; michael@0: } michael@0: #define end_of_cpath(cp,p,f) \ michael@0: ((const char*)(end_of_path(((char*)(cp)), (p), (f)))) michael@0: michael@0: int michael@0: evhttp_uri_set_path(struct evhttp_uri *uri, const char *path) michael@0: { michael@0: if (path && end_of_cpath(path, PART_PATH, uri->flags) != path+strlen(path)) michael@0: return -1; michael@0: michael@0: _URI_SET_STR(path); michael@0: return 0; michael@0: } michael@0: int michael@0: evhttp_uri_set_query(struct evhttp_uri *uri, const char *query) michael@0: { michael@0: if (query && end_of_cpath(query, PART_QUERY, uri->flags) != query+strlen(query)) michael@0: return -1; michael@0: _URI_SET_STR(query); michael@0: return 0; michael@0: } michael@0: int michael@0: evhttp_uri_set_fragment(struct evhttp_uri *uri, const char *fragment) michael@0: { michael@0: if (fragment && end_of_cpath(fragment, PART_FRAGMENT, uri->flags) != fragment+strlen(fragment)) michael@0: return -1; michael@0: _URI_SET_STR(fragment); michael@0: return 0; michael@0: }