blob: 6ff6b9058c347703ea8b4a3a44cc3530df6cfa7c [file] [log] [blame]
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <signal.h>
#include <strings.h>
#include <inttypes.h>
#ifndef WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#else
#define WINVER 0x0501
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#ifndef NO_SYS_UN
#include <sys/un.h>
#endif
#include <limits.h>
#include <event2/dns.h>
#include "evhtp-internal.h"
#include "evhtp_numtoa.h"
#include "evhtp.h"
#ifdef EVHTP_DEBUG
static void
htp_log_connection(evhtp_connection_t * c) {
htp_log_debug("connection = %p\n", c);
htp_log_debug("request = %p\n", c->request);
}
#endif
static int _evhtp_request_parser_start(htparser * p);
static int _evhtp_request_parser_host(htparser * p, const char * data, size_t len);
static int _evhtp_request_parser_port(htparser * p, const char * data, size_t len);
static int _evhtp_request_parser_path(htparser * p, const char * data, size_t len);
static int _evhtp_request_parser_args(htparser * p, const char * data, size_t len);
static int _evhtp_request_parser_header_key(htparser * p, const char * data, size_t len);
static int _evhtp_request_parser_header_val(htparser * p, const char * data, size_t len);
static int _evhtp_request_parser_hostname(htparser * p, const char * data, size_t len);
static int _evhtp_request_parser_headers(htparser * p);
static int _evhtp_request_parser_body(htparser * p, const char * data, size_t len);
static int _evhtp_request_parser_fini(htparser * p);
static int _evhtp_request_parser_chunk_new(htparser * p);
static int _evhtp_request_parser_chunk_fini(htparser * p);
static int _evhtp_request_parser_chunks_fini(htparser * p);
static int _evhtp_request_parser_headers_start(htparser * p);
static void _evhtp_connection_readcb(evbev_t * bev, void * arg);
static evhtp_connection_t * _evhtp_connection_new(evhtp_t * htp, evutil_socket_t sock, evhtp_type type);
static evhtp_uri_t * _evhtp_uri_new(void);
static void _evhtp_uri_free(evhtp_uri_t * uri);
static evhtp_authority_t * _evhtp_authority_new(void);
static void _evhtp_authority_free(evhtp_authority_t * authority);
static evhtp_path_t * _evhtp_path_new(const char * data, size_t len);
static void _evhtp_path_free(evhtp_path_t * path);
static void _evhtp_request_free(evhtp_request_t *);
#define HOOK_AVAIL(var, hook_name) (var->hooks && var->hooks->hook_name)
#define HOOK_FUNC(var, hook_name) (var->hooks->hook_name)
#define HOOK_ARGS(var, hook_name) var->hooks->hook_name ## _arg
#define HOOK_REQUEST_RUN(request, hook_name, ...) do { \
if (HOOK_AVAIL(request, hook_name)) { \
return HOOK_FUNC(request, hook_name) (request, __VA_ARGS__, \
HOOK_ARGS(request, hook_name)); \
} \
\
if (HOOK_AVAIL(evhtp_request_get_connection(request), hook_name)) { \
return HOOK_FUNC(request->conn, hook_name) (request, __VA_ARGS__, \
HOOK_ARGS(request->conn, hook_name)); \
} \
} while (0)
#define HOOK_REQUEST_RUN_NARGS(request, hook_name) do { \
if (HOOK_AVAIL(request, hook_name)) { \
return HOOK_FUNC(request, hook_name) (request, \
HOOK_ARGS(request, hook_name)); \
} \
\
if (HOOK_AVAIL(request->conn, hook_name)) { \
return HOOK_FUNC(request->conn, hook_name) (request, \
HOOK_ARGS(request->conn, hook_name)); \
} \
} while (0);
#define HOOK_CONN_RUN(conn, hook_name, ...) do { \
if (conn->request) { \
evhtp_request_t * request = conn->request; \
if (HOOK_AVAIL(request, hook_name)) { \
return HOOK_FUNC(conn, hook_name) (conn, __VA_ARGS__, \
HOOK_ARGS(conn, hook_name)); \
} \
} \
\
if (HOOK_AVAIL(conn, hook_name)) { \
return HOOK_FUNC(conn, hook_name) (conn, __VA_ARGS__, \
HOOK_ARGS(conn, hook_name)); \
} \
} while (0);
#ifndef EVHTP_DISABLE_EVTHR
#define _evhtp_lock(h) do { \
if (h->lock) { \
pthread_mutex_lock(h->lock); \
} \
} while (0)
#define _evhtp_unlock(h) do { \
if (h->lock) { \
pthread_mutex_unlock(h->lock); \
} \
} while (0)
#else
#define _evhtp_lock(h) do {} while (0)
#define _evhtp_unlock(h) do {} while (0)
#endif
#ifndef TAILQ_FOREACH_SAFE
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = TAILQ_FIRST((head)); \
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#endif
static const char *
status_code_to_str(evhtp_res code) {
switch (code) {
case EVHTP_RES_200:
return "OK";
case EVHTP_RES_300:
return "Redirect";
case EVHTP_RES_400:
return "Bad Request";
case EVHTP_RES_NOTFOUND:
return "Not Found";
case EVHTP_RES_SERVERR:
return "Internal Server Error";
case EVHTP_RES_CONTINUE:
return "Continue";
case EVHTP_RES_FORBIDDEN:
return "Forbidden";
case EVHTP_RES_SWITCH_PROTO:
return "Switching Protocols";
case EVHTP_RES_MOVEDPERM:
return "Moved Permanently";
case EVHTP_RES_PROCESSING:
return "Processing";
case EVHTP_RES_URI_TOOLONG:
return "URI Too Long";
case EVHTP_RES_CREATED:
return "Created";
case EVHTP_RES_ACCEPTED:
return "Accepted";
case EVHTP_RES_NAUTHINFO:
return "No Auth Info";
case EVHTP_RES_NOCONTENT:
return "No Content";
case EVHTP_RES_RSTCONTENT:
return "Reset Content";
case EVHTP_RES_PARTIAL:
return "Partial Content";
case EVHTP_RES_MSTATUS:
return "Multi-Status";
case EVHTP_RES_IMUSED:
return "IM Used";
case EVHTP_RES_FOUND:
return "Found";
case EVHTP_RES_SEEOTHER:
return "See Other";
case EVHTP_RES_NOTMOD:
return "Not Modified";
case EVHTP_RES_USEPROXY:
return "Use Proxy";
case EVHTP_RES_SWITCHPROXY:
return "Switch Proxy";
case EVHTP_RES_TMPREDIR:
return "Temporary Redirect";
case EVHTP_RES_UNAUTH:
return "Unauthorized";
case EVHTP_RES_PAYREQ:
return "Payment Required";
case EVHTP_RES_METHNALLOWED:
return "Not Allowed";
case EVHTP_RES_NACCEPTABLE:
return "Not Acceptable";
case EVHTP_RES_PROXYAUTHREQ:
return "Proxy Authentication Required";
case EVHTP_RES_TIMEOUT:
return "Request Timeout";
case EVHTP_RES_CONFLICT:
return "Conflict";
case EVHTP_RES_GONE:
return "Gone";
case EVHTP_RES_LENREQ:
return "Length Required";
case EVHTP_RES_PRECONDFAIL:
return "Precondition Failed";
case EVHTP_RES_ENTOOLARGE:
return "Entity Too Large";
case EVHTP_RES_URITOOLARGE:
return "Request-URI Too Long";
case EVHTP_RES_UNSUPPORTED:
return "Unsupported Media Type";
case EVHTP_RES_RANGENOTSC:
return "Requested Range Not Satisfiable";
case EVHTP_RES_EXPECTFAIL:
return "Expectation Failed";
case EVHTP_RES_IAMATEAPOT:
return "I'm a teapot";
case EVHTP_RES_NOTIMPL:
return "Not Implemented";
case EVHTP_RES_BADGATEWAY:
return "Bad Gateway";
case EVHTP_RES_SERVUNAVAIL:
return "Service Unavailable";
case EVHTP_RES_GWTIMEOUT:
return "Gateway Timeout";
case EVHTP_RES_VERNSUPPORT:
return "HTTP Version Not Supported";
case EVHTP_RES_BWEXEED:
return "Bandwidth Limit Exceeded";
} /* switch */
return "UNKNOWN";
} /* status_code_to_str */
/**
* @brief callback definitions for request processing from libhtparse
*/
static htparse_hooks request_psets = {
.on_msg_begin = _evhtp_request_parser_start,
.method = NULL,
.scheme = NULL,
.host = _evhtp_request_parser_host,
.port = _evhtp_request_parser_port,
.path = _evhtp_request_parser_path,
.args = _evhtp_request_parser_args,
.uri = NULL,
.on_hdrs_begin = _evhtp_request_parser_headers_start,
.hdr_key = _evhtp_request_parser_header_key,
.hdr_val = _evhtp_request_parser_header_val,
.hostname = _evhtp_request_parser_hostname,
.on_hdrs_complete = _evhtp_request_parser_headers,
.on_new_chunk = _evhtp_request_parser_chunk_new,
.on_chunk_complete = _evhtp_request_parser_chunk_fini,
.on_chunks_complete = _evhtp_request_parser_chunks_fini,
.body = _evhtp_request_parser_body,
.on_msg_complete = _evhtp_request_parser_fini
};
#ifndef EVHTP_DISABLE_SSL
static int session_id_context = 1;
#ifndef EVHTP_DISABLE_EVTHR
static int ssl_num_locks;
static evhtp_mutex_t * ssl_locks;
static int ssl_locks_initialized = 0;
#endif
#endif
/*
* COMPAT FUNCTIONS
*/
#ifdef NO_STRNLEN
static size_t
strnlen(const char * s, size_t maxlen) {
const char * e;
size_t n;
for (e = s, n = 0; *e && n < maxlen; e++, n++) {
;
}
return n;
}
#endif
#ifdef NO_STRNDUP
static char *
strndup(const char * s, size_t n) {
size_t len = strnlen(s, n);
char * ret;
if (len < n) {
return strdup(s);
}
ret = malloc(n + 1);
ret[n] = '\0';
memcpy(ret, s, n);
return ret;
}
#endif
/*
* PRIVATE FUNCTIONS
*/
/**
* @brief a weak hash function
*
* @param str a null terminated string
*
* @return an unsigned integer hash of str
*/
static inline unsigned int
_evhtp_quick_hash(const char * str) {
unsigned int h = 0;
for (; *str; str++) {
h = 31 * h + *str;
}
return h;
}
/**
*
* @brief helper macro to determine if http version is HTTP/1.0
*
* @param major the major version number
* @param minor the minor version number
*
* @return 1 if HTTP/1.0, else 0
*/
#define _evhtp_is_http_11(_major, _minor) \
(_major >= 1 && _minor >= 1)
/**
* @brief helper function to determine if http version is HTTP/1.1
*
* @param major the major version number
* @param minor the minor version number
*
* @return 1 if HTTP/1.1, else 0
*/
#define _evhtp_is_http_10(_major, _minor) \
(_major >= 1 && _minor <= 0)
/**
* @brief returns the HTTP protocol version
*
* @param major the major version number
* @param minor the minor version number
*
* @return EVHTP_PROTO_10 if HTTP/1.0, EVHTP_PROTO_11 if HTTP/1.1, otherwise
* EVHTP_PROTO_INVALID
*/
static inline evhtp_proto
_evhtp_protocol(const char major, const char minor) {
if (_evhtp_is_http_10(major, minor)) {
return EVHTP_PROTO_10;
}
if (_evhtp_is_http_11(major, minor)) {
return EVHTP_PROTO_11;
}
return EVHTP_PROTO_INVALID;
}
/**
* @brief runs the user-defined on_path hook for a request
*
* @param request the request structure
* @param path the path structure
*
* @return EVHTP_RES_OK on success, otherwise something else.
*/
static inline evhtp_res
_evhtp_path_hook(evhtp_request_t * request, evhtp_path_t * path) {
HOOK_REQUEST_RUN(request, on_path, path);
return EVHTP_RES_OK;
}
/**
* @brief runs the user-defined on_header hook for a request
*
* once a full key: value header has been parsed, this will call the hook
*
* @param request the request strucutre
* @param header the header structure
*
* @return EVHTP_RES_OK on success, otherwise something else.
*/
static inline evhtp_res
_evhtp_header_hook(evhtp_request_t * request, evhtp_header_t * header) {
HOOK_REQUEST_RUN(request, on_header, header);
return EVHTP_RES_OK;
}
/**
* @brief runs the user-defined on_Headers hook for a request after all headers
* have been parsed.
*
* @param request the request structure
* @param headers the headers tailq structure
*
* @return EVHTP_RES_OK on success, otherwise something else.
*/
static inline evhtp_res
_evhtp_headers_hook(evhtp_request_t * request, evhtp_headers_t * headers) {
HOOK_REQUEST_RUN(request, on_headers, headers);
return EVHTP_RES_OK;
}
/**
* @brief runs the user-defined on_body hook for requests containing a body.
* the data is stored in the request->buffer_in so the user may either
* leave it, or drain upon being called.
*
* @param request the request strucutre
* @param buf a evbuffer containing body data
*
* @return EVHTP_RES_OK on success, otherwise something else.
*/
static inline evhtp_res
_evhtp_body_hook(evhtp_request_t * request, evbuf_t * buf) {
HOOK_REQUEST_RUN(request, on_read, buf);
return EVHTP_RES_OK;
}
/**
* @brief runs the user-defined hook called just prior to a request been
* free()'d
*
* @param request therequest structure
*
* @return EVHTP_RES_OK on success, otherwise treated as an error
*/
static inline evhtp_res
_evhtp_request_fini_hook(evhtp_request_t * request) {
HOOK_REQUEST_RUN_NARGS(request, on_request_fini);
return EVHTP_RES_OK;
}
static inline evhtp_res
_evhtp_chunk_new_hook(evhtp_request_t * request, uint64_t len) {
HOOK_REQUEST_RUN(request, on_new_chunk, len);
return EVHTP_RES_OK;
}
static inline evhtp_res
_evhtp_chunk_fini_hook(evhtp_request_t * request) {
HOOK_REQUEST_RUN_NARGS(request, on_chunk_fini);
return EVHTP_RES_OK;
}
static inline evhtp_res
_evhtp_chunks_fini_hook(evhtp_request_t * request) {
HOOK_REQUEST_RUN_NARGS(request, on_chunks_fini);
return EVHTP_RES_OK;
}
static inline evhtp_res
_evhtp_headers_start_hook(evhtp_request_t * request) {
HOOK_REQUEST_RUN_NARGS(request, on_headers_start);
return EVHTP_RES_OK;
}
/**
* @brief runs the user-definedhook called just prior to a connection being
* closed
*
* @param connection the connection structure
*
* @return EVHTP_RES_OK on success, but pretty much ignored in any case.
*/
static inline evhtp_res
_evhtp_connection_fini_hook(evhtp_connection_t * connection) {
if (connection->hooks && connection->hooks->on_connection_fini) {
return (connection->hooks->on_connection_fini)(connection,
connection->hooks->on_connection_fini_arg);
}
return EVHTP_RES_OK;
}
/**
* @brief runs the user-defined hook when a connection error occurs
*
* @param request the request structure
* @param errtype the error that ocurred
*/
static inline void
_evhtp_error_hook(evhtp_request_t * request, evhtp_error_flags errtype) {
if (request && request->hooks && request->hooks->on_error) {
(*request->hooks->on_error)(request, errtype,
request->hooks->on_error_arg);
}
}
/**
* @brief runs the user-defined hook when a connection error occurs
*
* @param connection the connection structure
* @param errtype the error that ocurred
*/
static inline evhtp_res
_evhtp_connection_error_hook(evhtp_connection_t * connection, evhtp_error_flags errtype) {
if (connection->request) {
_evhtp_error_hook(connection->request, errtype);
}
HOOK_CONN_RUN(connection, on_connection_error, errtype);
return EVHTP_RES_OK;
}
static inline evhtp_res
_evhtp_hostname_hook(evhtp_request_t * r, const char * hostname) {
HOOK_REQUEST_RUN(r, on_hostname, hostname);
return EVHTP_RES_OK;
}
static inline evhtp_res
_evhtp_connection_write_hook(evhtp_connection_t * connection) {
if (connection->hooks && connection->hooks->on_write) {
return (connection->hooks->on_write)(connection,
connection->hooks->on_write_arg);
}
return EVHTP_RES_OK;
}
static int
_evhtp_glob_match2(const char * pattern, size_t plen,
const char * string, size_t str_len) {
while (plen) {
switch (pattern[0]) {
case '*':
while (pattern[1] == '*') {
pattern++;
plen--;
}
if (plen == 1) {
return 1; /* match */
}
while (str_len) {
if (_evhtp_glob_match2(pattern + 1, plen - 1,
string, str_len)) {
return 1; /* match */
}
string++;
str_len--;
}
return 0; /* no match */
default:
if (pattern[0] != string[0]) {
return 0; /* no match */
}
string++;
str_len--;
break;
} /* switch */
pattern++;
plen--;
if (str_len == 0) {
while (*pattern == '*') {
pattern++;
plen--;
}
break;
}
}
if (plen == 0 && str_len == 0) {
return 1;
}
return 0;
} /* _evhtp_glob_match2 */
/**
* @brief glob/wildcard type pattern matching.
*
* Note: This code was derived from redis's (v2.6) stringmatchlen() function.
*
* @param pattern
* @param string
*
* @return
*/
static inline int
_evhtp_glob_match(const char * pattern, size_t pat_len, const char * string, size_t str_len) {
if (evhtp_unlikely(!pattern || !string)) {
return 0;
}
if (pat_len == 0) {
pat_len = strlen(pattern);
}
if (str_len == 0) {
str_len = strlen(string);
}
return _evhtp_glob_match2(pattern, pat_len, string, str_len);
} /* _evhtp_glob_match */
static evhtp_callback_t *
_evhtp_callback_find(evhtp_callbacks_t * cbs,
const char * path,
unsigned int * start_offset,
unsigned int * end_offset) {
#ifndef EVHTP_DISABLE_REGEX
regmatch_t pmatch[28];
#endif
evhtp_callback_t * callback;
if (evhtp_unlikely(cbs == NULL)) {
return NULL;
}
TAILQ_FOREACH(callback, cbs, next) {
switch (callback->type) {
case evhtp_callback_type_hash:
if (strcmp(callback->val.path, path) == 0) {
*start_offset = 0;
*end_offset = (unsigned int)strlen(path);
return callback;
}
break;
#ifndef EVHTP_DISABLE_REGEX
case evhtp_callback_type_regex:
if (regexec(callback->val.regex, path, callback->val.regex->re_nsub + 1, pmatch, 0) == 0) {
*start_offset = pmatch[callback->val.regex->re_nsub].rm_so;
*end_offset = pmatch[callback->val.regex->re_nsub].rm_eo;
return callback;
}
break;
#endif
case evhtp_callback_type_glob:
{
size_t path_len = strlen(path);
size_t glob_len = strlen(callback->val.glob);
if (_evhtp_glob_match(callback->val.glob, glob_len,
path, path_len) == 1) {
*start_offset = 0;
*end_offset = (unsigned int)path_len;
return callback;
}
}
default:
break;
} /* switch */
}
return NULL;
} /* _evhtp_callback_find */
/**
* @brief Creates a new evhtp_request_t
*
* @param c
*
* @return evhtp_request_t structure on success, otherwise NULL
*/
static evhtp_request_t *
_evhtp_request_new(evhtp_connection_t * c) {
evhtp_request_t * req;
uint8_t error;
if (evhtp_unlikely(!(req = calloc(sizeof(evhtp_request_t), 1)))) {
return NULL;
}
error = 1;
req->conn = c;
req->htp = c ? c->htp : NULL;
req->status = EVHTP_RES_OK;
do {
if (evhtp_unlikely(!(req->buffer_in = evbuffer_new()))) {
break;
}
if (evhtp_unlikely(!(req->buffer_out = evbuffer_new()))) {
break;
}
if (evhtp_unlikely(!(req->headers_in = malloc(sizeof(evhtp_headers_t))))) {
break;
}
if (evhtp_unlikely(!(req->headers_out = malloc(sizeof(evhtp_headers_t))))) {
break;
}
TAILQ_INIT(req->headers_in);
TAILQ_INIT(req->headers_out);
error = 0;
} while (0);
if (error == 0) {
return req;
}
_evhtp_request_free(req);
return NULL;
} /* _evhtp_request_new */
/**
* @brief frees all data in an evhtp_request_t along with calling finished hooks
*
* @param request the request structure
*/
static void
_evhtp_request_free(evhtp_request_t * request) {
if (evhtp_unlikely(request == NULL)) {
return;
}
_evhtp_request_fini_hook(request);
_evhtp_uri_free(request->uri);
evhtp_headers_free(request->headers_in);
evhtp_headers_free(request->headers_out);
if (request->conn && request->conn->request == request) {
request->conn->request = NULL;
}
if (request->buffer_in) {
evbuffer_free(request->buffer_in);
}
if (request->buffer_out) {
evbuffer_free(request->buffer_out);
}
free(request->hooks);
free(request);
}
/**
* @brief create an overlay URI structure
*
* @return evhtp_uri_t
*/
static evhtp_uri_t *
_evhtp_uri_new(void) {
evhtp_uri_t * uri;
if (!(uri = calloc(sizeof(evhtp_uri_t), 1))) {
return NULL;
}
uri->authority = _evhtp_authority_new();
if (!uri->authority) {
_evhtp_uri_free(uri);
return NULL;
}
return uri;
}
/**
* @brief frees an authority structure
*
* @param authority evhtp_authority_t
*/
static void
_evhtp_authority_free(evhtp_authority_t * authority) {
if (authority == NULL) {
return;
}
evhtp_safe_free(authority->username, free);
evhtp_safe_free(authority->password, free);
evhtp_safe_free(authority->hostname, free);
evhtp_safe_free(authority, free);
}
/**
* @brief create an authority structure
*
* @return evhtp_authority_t
*/
static evhtp_authority_t *
_evhtp_authority_new(void) {
evhtp_authority_t * authority;
if (!(authority = calloc(1, sizeof(*authority)))) {
return NULL;
}
return authority;
}
/**
* @brief frees an overlay URI structure
*
* @param uri evhtp_uri_t
*/
static void
_evhtp_uri_free(evhtp_uri_t * uri) {
if (evhtp_unlikely(uri == NULL)) {
return;
}
evhtp_safe_free(uri->query, evhtp_query_free);
evhtp_safe_free(uri->path, _evhtp_path_free);
evhtp_safe_free(uri->authority, _evhtp_authority_free);
evhtp_safe_free(uri->fragment, free);
evhtp_safe_free(uri->query_raw, free);
evhtp_safe_free(uri, free);
}
/**
* @brief parses the path and file from an input buffer
*
* @details in order to properly create a structure that can match
* both a path and a file, this will parse a string into
* what it considers a path, and a file.
*
* @details if for example the input was "/a/b/c", the parser will
* consider "/a/b/" as the path, and "c" as the file.
*
* @param data raw input data (assumes a /path/[file] structure)
* @param len length of the input data
*
* @return evhtp_request_t * on success, NULL on error.
*/
static evhtp_path_t *
_evhtp_path_new(const char * data, size_t len) {
evhtp_path_t * req_path;
const char * data_end = (const char *)(data + len);
char * path = NULL;
char * file = NULL;
req_path = calloc(sizeof(evhtp_path_t), 1);
evhtp_alloc_assert(req_path);
if (evhtp_unlikely(len == 0)) {
/*
* odd situation here, no preceding "/", so just assume the path is "/"
*/
path = strdup("/");
evhtp_alloc_assert(path);
} else if (*data != '/') {
/* request like GET stupid HTTP/1.0, treat stupid as the file, and
* assume the path is "/"
*/
path = strdup("/");
file = strndup(data, len);
evhtp_alloc_assert(path);
evhtp_alloc_assert(file);
} else {
if (data[len - 1] != '/') {
/*
* the last character in data is assumed to be a file, not the end of path
* loop through the input data backwards until we find a "/"
*/
size_t i;
for (i = (len - 1); i != 0; i--) {
if (data[i] == '/') {
/*
* we have found a "/" representing the start of the file,
* and the end of the path
*/
size_t path_len;
size_t file_len;
path_len = (size_t)(&data[i] - data) + 1;
file_len = (size_t)(data_end - &data[i + 1]);
/* check for overflow */
if ((const char *)(data + path_len) > data_end) {
evhtp_safe_free(req_path, free);
return NULL;
}
/* check for overflow */
if ((const char *)(&data[i + 1] + file_len) > data_end) {
evhtp_safe_free(req_path, free);
return NULL;
}
path = strndup(data, path_len);
file = strndup(&data[i + 1], file_len);
evhtp_alloc_assert(path);
evhtp_alloc_assert(file);
break;
}
}
if (i == 0 && data[i] == '/' && !file && !path) {
/* drops here if the request is something like GET /foo */
path = strdup("/");
evhtp_alloc_assert(path);
if (len > 1) {
file = strndup((const char *)(data + 1), len);
evhtp_alloc_assert(file);
}
}
} else {
/* the last character is a "/", thus the request is just a path */
path = strndup(data, len);
evhtp_alloc_assert(path);
}
}
if (len != 0) {
req_path->full = strndup(data, len);
} else {
req_path->full = strdup("/");
}
evhtp_alloc_assert(req_path->full);
req_path->path = path;
req_path->file = file;
return req_path;
} /* _evhtp_path_new */
static void
_evhtp_path_free(evhtp_path_t * path) {
if (evhtp_unlikely(path == NULL)) {
return;
}
evhtp_safe_free(path->full, free);
evhtp_safe_free(path->path, free);
evhtp_safe_free(path->file, free);
evhtp_safe_free(path->match_start, free);
evhtp_safe_free(path->match_end, free);
evhtp_safe_free(path, free);
}
static int
_evhtp_request_parser_start(htparser * p) {
evhtp_connection_t * c = htparser_get_userdata(p);
if (evhtp_unlikely(c->type == evhtp_type_client)) {
return 0;
}
if (evhtp_unlikely(c->paused == 1)) {
return -1;
}
if (c->request) {
if (c->request->finished == 1) {
_evhtp_request_free(c->request);
} else {
return -1;
}
}
if (evhtp_unlikely(!(c->request = _evhtp_request_new(c)))) {
return -1;
}
return 0;
}
static int
_evhtp_request_parser_args(htparser * p, const char * data, size_t len) {
evhtp_connection_t * c = htparser_get_userdata(p);
evhtp_uri_t * uri = c->request->uri;
const char * fragment;
int ignore_fragment;
if (c->type == evhtp_type_client) {
/* as a client, technically we should never get here, but just in case
* we return a 0 to the parser to continue.
*/
return 0;
}
/* if the parser flags has the IGNORE_FRAGMENTS bit set, skip
* the fragment parsing
*/
ignore_fragment = (c->htp->parser_flags &
EVHTP_PARSE_QUERY_FLAG_IGNORE_FRAGMENTS);
if (!ignore_fragment && (fragment = memchr(data, '#', len))) {
/* Separate fragment from query according to RFC 3986.
*
* XXX: not happy about using strchr stuff, maybe this functionality
* is more apt as part of evhtp_parse_query()
*/
ptrdiff_t frag_offset;
frag_offset = fragment - data;
if (frag_offset < len) {
size_t fraglen;
/* Skip '#'. */
fragment += 1;
frag_offset += 1;
fraglen = len - frag_offset;
uri->fragment = malloc(fraglen + 1);
evhtp_alloc_assert(uri->fragment);
memcpy(uri->fragment, fragment, fraglen);
uri->fragment[fraglen] = '\0';
len -= fraglen + 1; /* Skip '#' + fragment string. */
}
}
uri->query = evhtp_parse_query_wflags(data, len, c->htp->parser_flags);
if (evhtp_unlikely(!uri->query)) {
c->request->status = EVHTP_RES_ERROR;
return -1;
}
uri->query_raw = malloc(len + 1);
evhtp_alloc_assert(uri->query_raw);
memcpy(uri->query_raw, data, len);
uri->query_raw[len] = '\0';
return 0;
} /* _evhtp_request_parser_args */
static int
_evhtp_request_parser_headers_start(htparser * p) {
evhtp_connection_t * c = htparser_get_userdata(p);
if ((c->request->status = _evhtp_headers_start_hook(c->request)) != EVHTP_RES_OK) {
return -1;
}
return 0;
}
static int
_evhtp_request_parser_header_key(htparser * p, const char * data, size_t len) {
evhtp_connection_t * c = htparser_get_userdata(p);
char * key_s;
evhtp_header_t * hdr;
key_s = malloc(len + 1);
evhtp_alloc_assert(key_s);
key_s[len] = '\0';
memcpy(key_s, data, len);
if ((hdr = evhtp_header_key_add(c->request->headers_in, key_s, 0)) == NULL) {
c->request->status = EVHTP_RES_FATAL;
return -1;
}
hdr->k_heaped = 1;
return 0;
}
static int
_evhtp_request_parser_header_val(htparser * p, const char * data, size_t len) {
evhtp_connection_t * c = htparser_get_userdata(p);
char * val_s;
evhtp_header_t * header;
val_s = malloc(len + 1);
evhtp_alloc_assert(val_s);
val_s[len] = '\0';
memcpy(val_s, data, len);
if ((header = evhtp_header_val_add(c->request->headers_in, val_s, 0)) == NULL) {
evhtp_safe_free(val_s, free);
c->request->status = EVHTP_RES_FATAL;
return -1;
}
header->v_heaped = 1;
if ((c->request->status = _evhtp_header_hook(c->request, header)) != EVHTP_RES_OK) {
return -1;
}
return 0;
}
static inline evhtp_t *
_evhtp_request_find_vhost(evhtp_t * evhtp, const char * name) {
evhtp_t * evhtp_vhost;
evhtp_alias_t * evhtp_alias;
TAILQ_FOREACH(evhtp_vhost, &evhtp->vhosts, next_vhost) {
if (evhtp_unlikely(evhtp_vhost->server_name == NULL)) {
continue;
}
if (_evhtp_glob_match(evhtp_vhost->server_name, 0, name, 0) == 1) {
return evhtp_vhost;
}
TAILQ_FOREACH(evhtp_alias, &evhtp_vhost->aliases, next) {
if (evhtp_alias->alias == NULL) {
continue;
}
if (_evhtp_glob_match(evhtp_alias->alias, 0, name, 0) == 1) {
return evhtp_vhost;
}
}
}
return NULL;
}
static inline int
_evhtp_request_set_callbacks(evhtp_request_t * request) {
evhtp_t * evhtp;
evhtp_connection_t * conn;
evhtp_uri_t * uri;
evhtp_path_t * path;
evhtp_hooks_t * hooks;
evhtp_callback_t * callback;
evhtp_callback_cb cb;
void * cbarg;
if (request == NULL) {
return -1;
}
if (evhtp_unlikely((evhtp = request->htp) == NULL)) {
return -1;
}
if (evhtp_unlikely((conn = request->conn) == NULL)) {
return -1;
}
if ((uri = request->uri) == NULL) {
return -1;
}
if ((path = uri->path) == NULL) {
return -1;
}
hooks = NULL;
callback = NULL;
cb = NULL;
cbarg = NULL;
if ((callback = _evhtp_callback_find(evhtp->callbacks, path->full,
&path->matched_soff, &path->matched_eoff))) {
/* matched a callback using both path and file (/a/b/c/d) */
cb = callback->cb;
cbarg = callback->cbarg;
hooks = callback->hooks;
} else if ((callback = _evhtp_callback_find(evhtp->callbacks, path->path,
&path->matched_soff, &path->matched_eoff))) {
/* matched a callback using *just* the path (/a/b/c/) */
cb = callback->cb;
cbarg = callback->cbarg;
hooks = callback->hooks;
} else {
/* no callbacks found for either case, use defaults */
cb = evhtp->defaults.cb;
cbarg = evhtp->defaults.cbarg;
path->matched_soff = 0;
path->matched_eoff = (unsigned int)strlen(path->full);
}
if (path->match_start == NULL) {
path->match_start = calloc(strlen(path->full) + 1, 1);
evhtp_alloc_assert(path->match_start);
}
if (path->match_end == NULL) {
path->match_end = calloc(strlen(path->full) + 1, 1);
evhtp_alloc_assert(path->match_end);
}
if (path->matched_soff != UINT_MAX /*ONIG_REGION_NOTPOS*/) {
if (path->matched_eoff - path->matched_soff) {
memcpy(path->match_start, (void *)(path->full + path->matched_soff),
path->matched_eoff - path->matched_soff);
} else {
memcpy(path->match_start, (void *)(path->full + path->matched_soff),
strlen((const char *)(path->full + path->matched_soff)));
}
memcpy(path->match_end,
(void *)(path->full + path->matched_eoff),
strlen(path->full) - path->matched_eoff);
}
if (hooks != NULL) {
if (request->hooks == NULL) {
request->hooks = malloc(sizeof(evhtp_hooks_t));
evhtp_alloc_assert(request->hooks);
}
memcpy(request->hooks, hooks, sizeof(evhtp_hooks_t));
}
request->cb = cb;
request->cbarg = cbarg;
return 0;
} /* _evhtp_request_set_callbacks */
static int
_evhtp_request_parser_hostname(htparser * p, const char * data, size_t len) {
evhtp_connection_t * c = htparser_get_userdata(p);
evhtp_t * evhtp;
evhtp_t * evhtp_vhost;
#ifndef EVHTP_DISABLE_SSL
if (c->vhost_via_sni == 1 && c->ssl != NULL) {
/* use the SNI set hostname instead of the header hostname */
const char * host;
host = SSL_get_servername(c->ssl, TLSEXT_NAMETYPE_host_name);
if ((c->request->status = _evhtp_hostname_hook(c->request, host)) != EVHTP_RES_OK) {
return -1;
}
return 0;
}
#endif
evhtp = c->htp;
/* since this is called after _evhtp_request_parser_path(), which already
* setup callbacks for the URI, we must now attempt to find callbacks which
* are specific to this host.
*/
_evhtp_lock(evhtp);
{
if ((evhtp_vhost = _evhtp_request_find_vhost(evhtp, data))) {
_evhtp_lock(evhtp_vhost);
{
/* if we found a match for the host, we must set the htp
* variables for both the connection and the request.
*/
c->htp = evhtp_vhost;
c->request->htp = evhtp_vhost;
_evhtp_request_set_callbacks(c->request);
}
_evhtp_unlock(evhtp_vhost);
}
}
_evhtp_unlock(evhtp);
if ((c->request->status = _evhtp_hostname_hook(c->request, data)) != EVHTP_RES_OK) {
return -1;
}
return 0;
} /* _evhtp_request_parser_hostname */
static int
_evhtp_require_uri(evhtp_connection_t * c) {
if (c && c->request && !c->request->uri) {
c->request->uri = _evhtp_uri_new();
evhtp_alloc_assert(c->request->uri);
}
return 0;
}
static int
_evhtp_request_parser_host(htparser * p, const char * data, size_t len) {
evhtp_connection_t * c = htparser_get_userdata(p);
evhtp_authority_t * authority;
if (_evhtp_require_uri(c) != 0) {
return -1;
}
authority = c->request->uri->authority;
authority->hostname = malloc(len + 1);
if (!authority->hostname) {
c->request->status = EVHTP_RES_FATAL;
return -1;
}
memcpy(authority->hostname, data, len);
authority->hostname[len] = '\0';
return 0;
}
static int
_evhtp_request_parser_port(htparser * p, const char * data, size_t len) {
evhtp_connection_t * c = htparser_get_userdata(p);
evhtp_authority_t * authority;
char * endptr;
unsigned long port;
if (_evhtp_require_uri(c) != 0) {
return -1;
}
authority = c->request->uri->authority;
port = strtoul(data, &endptr, 10);
if (endptr - data != len || port > 65535) {
c->request->status = EVHTP_RES_FATAL;
return -1;
}
authority->port = port;
return 0;
}
static int
_evhtp_request_parser_path(htparser * p, const char * data, size_t len) {
evhtp_connection_t * c = htparser_get_userdata(p);
evhtp_path_t * path;
if (_evhtp_require_uri(c) != 0) {
return -1;
}
if (evhtp_unlikely(!(path = _evhtp_path_new(data, len)))) {
c->request->status = EVHTP_RES_FATAL;
return -1;
}
c->request->uri->path = path;
c->request->uri->scheme = htparser_get_scheme(p);
c->request->method = htparser_get_method(p);
_evhtp_lock(c->htp);
{
_evhtp_request_set_callbacks(c->request);
}
_evhtp_unlock(c->htp);
if ((c->request->status = _evhtp_path_hook(c->request, path)) != EVHTP_RES_OK) {
return -1;
}
return 0;
} /* _evhtp_request_parser_path */
static int
_evhtp_request_parser_headers(htparser * p) {
evhtp_connection_t * c;
c = htparser_get_userdata(p);
evhtp_assert(c != NULL);
/* XXX proto should be set with htparsers on_hdrs_begin hook */
c->request->keepalive = htparser_should_keep_alive(p);
c->request->proto = _evhtp_protocol(htparser_get_major(p), htparser_get_minor(p));
c->request->status = _evhtp_headers_hook(c->request, c->request->headers_in);
if (c->request->status != EVHTP_RES_OK) {
return -1;
}
if (c->type == evhtp_type_server && c->htp->disable_100_cont == 0) {
/* only send a 100 continue response if it hasn't been disabled via
* evhtp_disable_100_continue.
*/
if (!evhtp_header_find(c->request->headers_in, "Expect")) {
return 0;
}
evbuffer_add_printf(bufferevent_get_output(c->bev),
"HTTP/%c.%c 100 Continue\r\n\r\n",
evhtp_modp_uchartoa(htparser_get_major(p)),
evhtp_modp_uchartoa(htparser_get_minor(p)));
}
return 0;
}
static int
_evhtp_request_parser_body(htparser * p, const char * data, size_t len) {
evhtp_connection_t * c = htparser_get_userdata(p);
evbuf_t * buf;
int res = 0;
if (c->max_body_size > 0 && c->body_bytes_read + len >= c->max_body_size) {
c->error = 1;
c->request->status = EVHTP_RES_DATA_TOO_LONG;
return -1;
}
buf = c->scratch_buf;
evhtp_assert(buf != NULL);
evbuffer_add(buf, data, len);
if ((c->request->status = _evhtp_body_hook(c->request, buf)) != EVHTP_RES_OK) {
res = -1;
}
if (evbuffer_get_length(buf)) {
evbuffer_add_buffer(c->request->buffer_in, buf);
}
evbuffer_drain(buf, -1);
c->body_bytes_read += len;
return res;
}
static int
_evhtp_request_parser_chunk_new(htparser * p) {
evhtp_connection_t * c = htparser_get_userdata(p);
if ((c->request->status = _evhtp_chunk_new_hook(c->request,
htparser_get_content_length(p))) != EVHTP_RES_OK) {
return -1;
}
return 0;
}
static int
_evhtp_request_parser_chunk_fini(htparser * p) {
evhtp_connection_t * c = htparser_get_userdata(p);
if ((c->request->status = _evhtp_chunk_fini_hook(c->request)) != EVHTP_RES_OK) {
return -1;
}
return 0;
}
static int
_evhtp_request_parser_chunks_fini(htparser * p) {
evhtp_connection_t * c = htparser_get_userdata(p);
if ((c->request->status = _evhtp_chunks_fini_hook(c->request)) != EVHTP_RES_OK) {
return -1;
}
return 0;
}
/**
* @brief determines if the request body contains the query arguments.
* if the query is NULL and the contenet length of the body has never
* been drained, and the content-type is x-www-form-urlencoded, the
* function returns 1
*
* @param req
*
* @return 1 if evhtp can use the body as the query arguments, 0 otherwise.
*/
static int
_evhtp_should_parse_query_body(evhtp_request_t * req) {
const char * content_type;
if (req == NULL) {
return 0;
}
if (req->uri == NULL || req->uri->query != NULL) {
return 0;
}
if (evhtp_request_content_len(req) == 0) {
return 0;
}
if (evhtp_request_content_len(req) !=
evbuffer_get_length(req->buffer_in)) {
return 0;
}
content_type = evhtp_kv_find(req->headers_in, "content-type");
if (content_type == NULL) {
return 0;
}
if (strncasecmp(content_type, "application/x-www-form-urlencoded", 33)) {
return 0;
}
return 1;
}
static int
_evhtp_request_parser_fini(htparser * p) {
evhtp_connection_t * c = htparser_get_userdata(p);
if (c->paused == 1) {
return -1;
}
/* check to see if we should use the body of the request as the query
* arguments.
*/
if (_evhtp_should_parse_query_body(c->request) == 1) {
const char * body;
size_t body_len;
evhtp_uri_t * uri;
evbuf_t * buf_in;
uri = c->request->uri;
buf_in = c->request->buffer_in;
body_len = evbuffer_get_length(buf_in);
body = (const char *)evbuffer_pullup(buf_in, body_len);
uri->query_raw = calloc(body_len + 1, 1);
evhtp_alloc_assert(uri->query_raw);
memcpy(uri->query_raw, body, body_len);
uri->query = evhtp_parse_query(body, body_len);
}
/*
* XXX c->request should never be NULL, but we have found some path of
* execution where this actually happens. We will check for now, but the bug
* path needs to be tracked down.
*
*/
if (c->request && c->request->cb) {
(c->request->cb)(c->request, c->request->cbarg);
}
if (c->paused == 1) {
return -1;
}
return 0;
} /* _evhtp_request_parser_fini */
static int
_evhtp_create_headers(evhtp_header_t * header, void * arg) {
evbuf_t * buf = arg;
evbuffer_expand(buf, header->klen + 2 + header->vlen + 2);
evbuffer_add(buf, header->key, header->klen);
evbuffer_add(buf, ": ", 2);
evbuffer_add(buf, header->val, header->vlen);
evbuffer_add(buf, "\r\n", 2);
return 0;
}
static evbuf_t *
_evhtp_create_reply(evhtp_request_t * request, evhtp_res code) {
evbuf_t * buf;
const char * content_type;
char res_buf[2048];
int sres;
size_t out_len;
unsigned char major;
unsigned char minor;
char out_buf[64];
content_type = evhtp_header_find(request->headers_out, "Content-Type");
out_len = evbuffer_get_length(request->buffer_out);
buf = request->conn->scratch_buf;
evhtp_assert(buf != NULL);
evbuffer_drain(buf, -1);
/*
* buf = evbuffer_new();
* evhtp_alloc_assert(buf);
*/
if (htparser_get_multipart(request->conn->parser) == 1) {
goto check_proto;
}
if (out_len && request->chunked == 0) {
/* add extra headers (like content-length/type) if not already present */
if (!evhtp_header_find(request->headers_out, "Content-Length")) {
/* convert the buffer_out length to a string and set
* and add the new Content-Length header.
*/
evhtp_modp_sizetoa(out_len, out_buf);
evhtp_headers_add_header(request->headers_out,
evhtp_header_new("Content-Length", out_buf, 0, 1));
}
}
check_proto:
/* add the proper keep-alive type headers based on http version */
switch (request->proto) {
case EVHTP_PROTO_11:
if (request->keepalive == 0) {
/* protocol is HTTP/1.1 but client wanted to close */
evhtp_headers_add_header(request->headers_out,
evhtp_header_new("Connection", "close", 0, 0));
}
#if 0
if (!out_len && !evhtp_header_find(request->headers_out, "Content-Length")) {
evhtp_headers_add_header(request->headers_out,
evhtp_header_new("Content-Length", "0", 0, 0));
}
#endif
break;
case EVHTP_PROTO_10:
if (request->keepalive == 1) {
/* protocol is HTTP/1.0 and clients wants to keep established */
evhtp_headers_add_header(request->headers_out,
evhtp_header_new("Connection", "keep-alive", 0, 0));
}
break;
default:
/* this sometimes happens when a response is made but paused before
* the method has been parsed */
htparser_set_major(request->conn->parser, 1);
htparser_set_minor(request->conn->parser, 0);
break;
} /* switch */
if (!content_type) {
evhtp_headers_add_header(request->headers_out,
evhtp_header_new("Content-Type", "text/plain", 0, 0));
}
/* attempt to add the status line into a temporary buffer and then use
* evbuffer_add(). Using plain old snprintf() will be faster than
* evbuffer_add_printf(). If the snprintf() fails, which it rarely should,
* we fallback to using evbuffer_add_printf().
*/
major = evhtp_modp_uchartoa(htparser_get_major(request->conn->parser));
minor = evhtp_modp_uchartoa(htparser_get_minor(request->conn->parser));
evhtp_modp_u32toa((uint32_t)code, out_buf);
sres = snprintf(res_buf, sizeof(res_buf), "HTTP/%c.%c %s %s\r\n",
major, minor, out_buf, status_code_to_str(code));
if (sres >= sizeof(res_buf) || sres < 0) {
/* failed to fit the whole thing in the res_buf, so just fallback to
* using evbuffer_add_printf().
*/
evbuffer_add_printf(buf, "HTTP/%c.%c %d %s\r\n",
major, minor,
code, status_code_to_str(code));
} else {
/* copy the res_buf using evbuffer_add() instead of add_printf() */
evbuffer_add(buf, res_buf, sres);
}
evhtp_headers_for_each(request->headers_out, _evhtp_create_headers, buf);
evbuffer_add(buf, "\r\n", 2);
if (evbuffer_get_length(request->buffer_out)) {
evbuffer_add_buffer(buf, request->buffer_out);
}
return buf;
} /* _evhtp_create_reply */
static void
_evhtp_connection_resumecb(int fd, short events, void * arg) {
evhtp_connection_t * c = arg;
c->paused = 0;
if (c->request) {
c->request->status = EVHTP_RES_OK;
}
if (c->free_connection == 1) {
evhtp_connection_free(c);
return;
}
/* XXX this is a hack to show a potential fix for issues/86, the main indea
* is that you call resume AFTER you have sent the reply (not BEFORE).
*
* When it has been decided this is a proper fix, the pause bit should be
* changed to a state-type flag.
*/
if (evbuffer_get_length(bufferevent_get_output(c->bev))) {
bufferevent_enable(c->bev, EV_WRITE);
c->waiting = 1;
} else {
bufferevent_enable(c->bev, EV_READ | EV_WRITE);
_evhtp_connection_readcb(c->bev, c);
}
}
static void
_evhtp_connection_readcb(evbev_t * bev, void * arg) {
evhtp_connection_t * c = arg;
void * buf;
size_t nread;
size_t avail;
htp_log_debug("enter sock = %d", c->sock);
avail = evbuffer_get_length(bufferevent_get_input(bev));
htp_log_debug("available bytes %zu", avail);
if (evhtp_unlikely(avail == 0)) {
return;
}
if (c->request) {
c->request->status = EVHTP_RES_OK;
}
if (c->paused == 1) {
return;
}
buf = evbuffer_pullup(bufferevent_get_input(bev), avail);
htp_log_debug("buffer is\n----\n%.*s\n-----", (int)avail, (const char *)buf);
nread = htparser_run(c->parser, &request_psets, (const char *)buf, avail);
htp_log_debug("nread = %zu", nread);
if (evhtp_unlikely(c->owner != 1)) {
/*
* someone has taken the ownership of this connection, we still need to
* drain the input buffer that had been read up to this point.
*/
evbuffer_drain(bufferevent_get_input(bev), nread);
evhtp_connection_free(c);
return;
}
if (c->request) {
switch (c->request->status) {
case EVHTP_RES_DATA_TOO_LONG:
_evhtp_connection_error_hook(c, -1);
evhtp_connection_free(c);
return;
default:
break;
}
}
evbuffer_drain(bufferevent_get_input(bev), nread);
if (c->request && c->request->status == EVHTP_RES_PAUSE) {
evhtp_request_pause(c->request);
} else if (htparser_get_error(c->parser) != htparse_error_none) {
evhtp_connection_free(c);
} else if (nread < avail) {
/* we still have more data to read (piped request probably) */
evhtp_connection_resume(c);
}
} /* _evhtp_connection_readcb */
static void
_evhtp_connection_writecb(evbev_t * bev, void * arg) {
evhtp_connection_t * c = arg;
htp_log_debug("c->request = %p", c->request);
if (evhtp_unlikely(c->request == NULL)) {
return;
}
_evhtp_connection_write_hook(c);
if (evhtp_unlikely(c->paused == 1)) {
return;
}
if (evhtp_unlikely(c->waiting == 1)) {
c->waiting = 0;
bufferevent_enable(bev, EV_READ);
if (evbuffer_get_length(bufferevent_get_input(bev))) {
_evhtp_connection_readcb(bev, arg);
}
return;
}
if (c->request->finished == 0 || evbuffer_get_length(bufferevent_get_output(bev))) {
return;
}
/*
* if there is a set maximum number of keepalive requests configured, check
* to make sure we are not over it. If we have gone over the max we set the
* keepalive bit to 0, thus closing the connection.
*/
if (c->htp->max_keepalive_requests) {
if (++c->num_requests >= c->htp->max_keepalive_requests) {
c->request->keepalive = 0;
}
}
if (c->request->keepalive == 1) {
_evhtp_request_free(c->request);
c->keepalive = 1;
c->request = NULL;
c->body_bytes_read = 0;
if (c->htp->parent && c->vhost_via_sni == 0) {
/* this request was servied by a virtual host evhtp_t structure
* which was *NOT* found via SSL SNI lookup. In this case we want to
* reset our connections evhtp_t structure back to the original so
* that subsequent requests can have a different Host: header.
*/
evhtp_t * orig_htp = c->htp->parent;
c->htp = orig_htp;
}
htparser_init(c->parser, htp_type_request);
htparser_set_userdata(c->parser, c);
return;
} else {
evhtp_connection_free(c);
return;
}
return;
} /* _evhtp_connection_writecb */
static void
_evhtp_connection_eventcb(evbev_t * bev, short events, void * arg) {
evhtp_connection_t * c = arg;
if (c->hooks && c->hooks->on_event) {
(c->hooks->on_event)(c, events, c->hooks->on_event_arg);
}
if ((events & BEV_EVENT_CONNECTED)) {
if (evhtp_likely(c->type == evhtp_type_client)) {
c->connected = 1;
bufferevent_setcb(bev,
_evhtp_connection_readcb,
_evhtp_connection_writecb,
_evhtp_connection_eventcb, c);
}
return;
}
#ifndef EVHTP_DISABLE_SSL
if (c->ssl && !(events & BEV_EVENT_EOF)) {
/* XXX need to do better error handling for SSL specific errors */
c->error = 1;
if (c->request) {
c->request->error = 1;
}
}
#endif
if (events == (BEV_EVENT_EOF | BEV_EVENT_READING)) {
if (errno == EAGAIN) {
/* libevent will sometimes recv again when it's not actually ready,
* this results in a 0 return value, and errno will be set to EAGAIN
* (try again). This does not mean there is a hard socket error, but
* simply needs to be read again.
*
* but libevent will disable the read side of the bufferevent
* anyway, so we must re-enable it.
*/
bufferevent_enable(bev, EV_READ);
errno = 0;
return;
}
}
c->error = 1;
c->connected = 0;
_evhtp_connection_error_hook(c, events);
if (c->paused == 1) {
/* we are currently paused, so we don't want to free just yet, let's
* wait till the next loop.
*/
c->free_connection = 1;
} else {
evhtp_connection_free((evhtp_connection_t *)arg);
}
} /* _evhtp_connection_eventcb */
static int
_evhtp_run_pre_accept(evhtp_t * htp, evhtp_connection_t * conn) {
void * args;
evhtp_res res;
if (evhtp_likely(htp->defaults.pre_accept == NULL)) {
return 0;
}
args = htp->defaults.pre_accept_cbarg;
res = htp->defaults.pre_accept(conn, args);
if (res != EVHTP_RES_OK) {
return -1;
}
return 0;
}
static int
_evhtp_connection_accept(evbase_t * evbase, evhtp_connection_t * connection) {
struct timeval * c_recv_timeo;
struct timeval * c_send_timeo;
if (_evhtp_run_pre_accept(connection->htp, connection) < 0) {
evutil_closesocket(connection->sock);
return -1;
}
#ifndef EVHTP_DISABLE_SSL
if (connection->htp->ssl_ctx != NULL) {
connection->ssl = SSL_new(connection->htp->ssl_ctx);
connection->bev = bufferevent_openssl_socket_new(evbase,
connection->sock,
connection->ssl,
BUFFEREVENT_SSL_ACCEPTING,
connection->htp->bev_flags);
SSL_set_app_data(connection->ssl, connection);
goto end;
}
#endif
connection->bev = bufferevent_socket_new(evbase,
connection->sock,
connection->htp->bev_flags);
htp_log_debug("enter sock=%d\n", connection->sock);
#ifndef EVHTP_DISABLE_SSL
end:
#endif
if (connection->recv_timeo.tv_sec || connection->recv_timeo.tv_usec) {
c_recv_timeo = &connection->recv_timeo;
} else if (connection->htp->recv_timeo.tv_sec ||
connection->htp->recv_timeo.tv_usec) {
c_recv_timeo = &connection->htp->recv_timeo;
} else {
c_recv_timeo = NULL;
}
if (connection->send_timeo.tv_sec || connection->send_timeo.tv_usec) {
c_send_timeo = &connection->send_timeo;
} else if (connection->htp->send_timeo.tv_sec ||
connection->htp->send_timeo.tv_usec) {
c_send_timeo = &connection->htp->send_timeo;
} else {
c_send_timeo = NULL;
}
evhtp_connection_set_timeouts(connection, c_recv_timeo, c_send_timeo);
connection->resume_ev = event_new(evbase, -1, EV_READ | EV_PERSIST,
_evhtp_connection_resumecb, connection);
event_add(connection->resume_ev, NULL);
bufferevent_enable(connection->bev, EV_READ);
bufferevent_setcb(connection->bev,
_evhtp_connection_readcb,
_evhtp_connection_writecb,
_evhtp_connection_eventcb, connection);
return 0;
} /* _evhtp_connection_accept */
static void
_evhtp_default_request_cb(evhtp_request_t * request, void * arg) {
evhtp_headers_add_header(request->headers_out,
evhtp_header_new("Content-Length", "0", 0, 0));
evhtp_send_reply(request, EVHTP_RES_NOTFOUND);
}
static evhtp_connection_t *
_evhtp_connection_new(evhtp_t * htp, evutil_socket_t sock, evhtp_type type) {
evhtp_connection_t * connection;
htp_type ptype;
switch (type) {
case evhtp_type_client:
ptype = htp_type_response;
break;
case evhtp_type_server:
ptype = htp_type_request;
break;
default:
return NULL;
}
connection = calloc(sizeof(evhtp_connection_t), 1);
evhtp_alloc_assert(connection);
connection->scratch_buf = evbuffer_new();
evhtp_alloc_assert(connection->scratch_buf);
connection->error = 0;
connection->owner = 1;
connection->paused = 0;
connection->connected = 0;
connection->sock = sock;
connection->htp = htp;
connection->type = type;
connection->parser = htparser_new();
evhtp_alloc_assert(connection->parser);
htparser_init(connection->parser, ptype);
htparser_set_userdata(connection->parser, connection);
#ifdef EVHTP_FUTURE_USE
TAILQ_INIT(&connection->pending);
#endif
return connection;
} /* _evhtp_connection_new */
#ifdef LIBEVENT_HAS_SHUTDOWN
#ifndef EVHTP_DISABLE_SSL
static void
_evhtp_shutdown_eventcb(evbev_t * bev, short events, void * arg) {
}
#endif
#endif
static int
_evhtp_run_post_accept(evhtp_t * htp, evhtp_connection_t * connection) {
void * args;
evhtp_res res;
if (evhtp_likely(htp->defaults.post_accept == NULL)) {
return 0;
}
args = htp->defaults.post_accept_cbarg;
res = htp->defaults.post_accept(connection, args);
if (res != EVHTP_RES_OK) {
return -1;
}
return 0;
}
#ifndef EVHTP_DISABLE_EVTHR
static void
_evhtp_run_in_thread(evthr_t * thr, void * arg, void * shared) {
evhtp_t * htp = shared;
evhtp_connection_t * connection = arg;
connection->evbase = evthr_get_base(thr);
connection->thread = thr;
if (_evhtp_connection_accept(connection->evbase, connection) < 0) {
evhtp_connection_free(connection);
return;
}
if (_evhtp_run_post_accept(htp, connection) < 0) {
evhtp_connection_free(connection);
return;
}
}
#endif
static void
_evhtp_accept_cb(evserv_t * serv, int fd, struct sockaddr * s, int sl, void * arg) {
evhtp_t * htp = arg;
evhtp_connection_t * connection;
if (evhtp_unlikely(!(connection = _evhtp_connection_new(htp, fd, evhtp_type_server)))) {
return;
}
htp_log_debug("fd = %d, conn = %p", fd, connection);
connection->saddr = malloc(sl);
evhtp_alloc_assert(connection->saddr);
memcpy(connection->saddr, s, sl);
#ifndef EVHTP_DISABLE_EVTHR
if (htp->thr_pool != NULL) {
if (evthr_pool_defer(htp->thr_pool,
_evhtp_run_in_thread, connection) != EVTHR_RES_OK) {
evutil_closesocket(connection->sock);
evhtp_connection_free(connection);
return;
}
return;
}
#endif
connection->evbase = htp->evbase;
if (_evhtp_connection_accept(htp->evbase, connection) < 0) {
evhtp_connection_free(connection);
return;
}
if (_evhtp_run_post_accept(htp, connection) < 0) {
evhtp_connection_free(connection);
return;
}
} /* _evhtp_accept_cb */
#ifndef EVHTP_DISABLE_SSL
#ifndef EVHTP_DISABLE_EVTHR
static unsigned long
_evhtp_ssl_get_thread_id(void) {
#ifndef WIN32
return (unsigned long)pthread_self();
#else
return (unsigned long)(pthread_self().p);
#endif
}
static void
_evhtp_ssl_thread_lock(int mode, int type, const char * file, int line) {
if (type < ssl_num_locks) {
if (mode & CRYPTO_LOCK) {
pthread_mutex_lock(&(ssl_locks[type]));
} else {
pthread_mutex_unlock(&(ssl_locks[type]));
}
}
}
#endif
static void
_evhtp_ssl_delete_scache_ent(evhtp_ssl_ctx_t * ctx, evhtp_ssl_sess_t * sess) {
evhtp_t * htp;
evhtp_ssl_cfg_t * cfg;
unsigned char * sid;
unsigned int slen;
htp = (evhtp_t *)SSL_CTX_get_app_data(ctx);
cfg = htp->ssl_cfg;
sid = sess->session_id;
slen = sess->session_id_length;
if (cfg->scache_del) {
(cfg->scache_del)(htp, sid, slen);
}
}
static int
_evhtp_ssl_add_scache_ent(evhtp_ssl_t * ssl, evhtp_ssl_sess_t * sess) {
evhtp_connection_t * connection;
evhtp_ssl_cfg_t * cfg;
unsigned char * sid;
int slen;
connection = (evhtp_connection_t *)SSL_get_app_data(ssl);
cfg = connection->htp->ssl_cfg;
sid = sess->session_id;
slen = sess->session_id_length;
SSL_set_timeout(sess, cfg->scache_timeout);
if (cfg->scache_add) {
return (cfg->scache_add)(connection, sid, slen, sess);
}
return 0;
}
static evhtp_ssl_sess_t *
_evhtp_ssl_get_scache_ent(evhtp_ssl_t * ssl, unsigned char * sid, int sid_len, int * copy) {
evhtp_connection_t * connection;
evhtp_ssl_cfg_t * cfg;
evhtp_ssl_sess_t * sess;
connection = (evhtp_connection_t * )SSL_get_app_data(ssl);
cfg = connection->htp->ssl_cfg;
sess = NULL;
if (cfg->scache_get) {
sess = (cfg->scache_get)(connection, sid, sid_len);
}
*copy = 0;
return sess;
}
static int
_evhtp_ssl_servername(evhtp_ssl_t * ssl, int * unused, void * arg) {
const char * sname;
evhtp_connection_t * connection;
evhtp_t * evhtp;
evhtp_t * evhtp_vhost;
if (!(sname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) {
return SSL_TLSEXT_ERR_NOACK;
}
if (!(connection = SSL_get_app_data(ssl))) {
return SSL_TLSEXT_ERR_NOACK;
}
if (!(evhtp = connection->htp)) {
return SSL_TLSEXT_ERR_NOACK;
}
if ((evhtp_vhost = _evhtp_request_find_vhost(evhtp, sname))) {
connection->htp = evhtp_vhost;
connection->vhost_via_sni = 1;
SSL_set_SSL_CTX(ssl, evhtp_vhost->ssl_ctx);
SSL_set_options(ssl, SSL_CTX_get_options(ssl->ctx));
if ((SSL_get_verify_mode(ssl) == SSL_VERIFY_NONE) ||
(SSL_num_renegotiations(ssl) == 0)) {
SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ssl->ctx),
SSL_CTX_get_verify_callback(ssl->ctx));
}
return SSL_TLSEXT_ERR_OK;
}
return SSL_TLSEXT_ERR_NOACK;
} /* _evhtp_ssl_servername */
#endif
/*
* PUBLIC FUNCTIONS
*/
htp_method
evhtp_request_get_method(evhtp_request_t * r) {
evhtp_assert(r != NULL);
evhtp_assert(r->conn != NULL);
evhtp_assert(r->conn->parser != NULL);
return htparser_get_method(r->conn->parser);
}
/**
* @brief pauses a connection (disables reading)
*
* @param c a evhtp_connection_t * structure
*/
void
evhtp_connection_pause(evhtp_connection_t * c) {
evhtp_assert(c != NULL);
c->paused = 1;
bufferevent_disable(c->bev, EV_READ | EV_WRITE);
return;
}
/**
* @brief resumes a connection (enables reading) and activates resume event.
*
* @param c
*/
void
evhtp_connection_resume(evhtp_connection_t * c) {
evhtp_assert(c != NULL);
c->paused = 0;
event_active(c->resume_ev, EV_WRITE, 1);
return;
}
/**
* @brief Wrapper around evhtp_connection_pause
*
* @see evhtp_connection_pause
*
* @param request
*/
void
evhtp_request_pause(evhtp_request_t * request) {
evhtp_assert(request != NULL);
request->status = EVHTP_RES_PAUSE;
evhtp_connection_pause(request->conn);
}
/**
* @brief Wrapper around evhtp_connection_resume
*
* @see evhtp_connection_resume
*
* @param request
*/
void
evhtp_request_resume(evhtp_request_t * request) {
evhtp_assert(request != NULL);
evhtp_connection_resume(request->conn);
}
evhtp_header_t *
evhtp_header_key_add(evhtp_headers_t * headers, const char * key, char kalloc) {
evhtp_header_t * header;
if (!(header = evhtp_header_new(key, NULL, kalloc, 0))) {
return NULL;
}
evhtp_headers_add_header(headers, header);
return header;
}
evhtp_header_t *
evhtp_header_val_add(evhtp_headers_t * headers, const char * val, char valloc) {
evhtp_header_t * header;
if (!headers || !val) {
return NULL;
}
if (!(header = TAILQ_LAST(headers, evhtp_headers_s))) {
return NULL;
}
if (header->val != NULL) {
return NULL;
}
header->vlen = strlen(val);
if (valloc == 1) {
header->val = malloc(header->vlen + 1);
header->val[header->vlen] = '\0';
memcpy(header->val, val, header->vlen);
} else {
header->val = (char *)val;
}
header->v_heaped = valloc;
return header;
}
evhtp_kvs_t *
evhtp_kvs_new(void) {
evhtp_kvs_t * kvs;
kvs = malloc(sizeof(evhtp_kvs_t));
evhtp_alloc_assert(kvs);
TAILQ_INIT(kvs);
return kvs;
}
evhtp_kv_t *
evhtp_kv_new(const char * key, const char * val, char kalloc, char valloc) {
evhtp_kv_t * kv;
kv = malloc(sizeof(evhtp_kv_t));
evhtp_alloc_assert(kv);
kv->k_heaped = kalloc;
kv->v_heaped = valloc;
kv->klen = 0;
kv->vlen = 0;
kv->key = NULL;
kv->val = NULL;
if (key != NULL) {
kv->klen = strlen(key);
if (kalloc == 1) {
char * s;
if (!(s = malloc(kv->klen + 1))) {
evhtp_safe_free(kv, free);
return NULL;
}
memcpy(s, key, kv->klen);
s[kv->klen] = '\0';
kv->key = s;
} else {
kv->key = (char *)key;
}
}
if (val != NULL) {
kv->vlen = strlen(val);
if (valloc == 1) {
char * s = malloc(kv->vlen + 1);
s[kv->vlen] = '\0';
memcpy(s, val, kv->vlen);
kv->val = s;
} else {
kv->val = (char *)val;
}
}
return kv;
} /* evhtp_kv_new */
void
evhtp_kv_free(evhtp_kv_t * kv) {
if (evhtp_unlikely(kv == NULL)) {
return;
}
if (kv->k_heaped) {
evhtp_safe_free(kv->key, free);
}
if (kv->v_heaped) {
evhtp_safe_free(kv->val, free);
}
evhtp_safe_free(kv, free);
}
void
evhtp_kv_rm_and_free(evhtp_kvs_t * kvs, evhtp_kv_t * kv) {
if (evhtp_unlikely(kvs == NULL || kv == NULL)) {
return;
}
TAILQ_REMOVE(kvs, kv, next);
evhtp_kv_free(kv);
}
void
evhtp_kvs_free(evhtp_kvs_t * kvs) {
evhtp_kv_t * kv;
evhtp_kv_t * save;
if (evhtp_unlikely(kvs == NULL)) {
return;
}
for (kv = TAILQ_FIRST(kvs); kv != NULL; kv = save) {
save = TAILQ_NEXT(kv, next);
TAILQ_REMOVE(kvs, kv, next);
evhtp_safe_free(kv, evhtp_kv_free);
}
evhtp_safe_free(kvs, free);
}
int
evhtp_kvs_for_each(evhtp_kvs_t * kvs, evhtp_kvs_iterator cb, void * arg) {
evhtp_kv_t * kv;
if (kvs == NULL || cb == NULL) {
return -1;
}
TAILQ_FOREACH(kv, kvs, next) {
int res;
if ((res = cb(kv, arg))) {
return res;
}
}
return 0;
}
const char *
evhtp_kv_find(evhtp_kvs_t * kvs, const char * key) {
evhtp_kv_t * kv;
if (evhtp_unlikely(kvs == NULL || key == NULL)) {
return NULL;
}
TAILQ_FOREACH(kv, kvs, next) {
if (strcasecmp(kv->key, key) == 0) {
return kv->val;
}
}
return NULL;
}
evhtp_kv_t *
evhtp_kvs_find_kv(evhtp_kvs_t * kvs, const char * key) {
evhtp_kv_t * kv;
if (evhtp_unlikely(kvs == NULL || key == NULL)) {
return NULL;
}
TAILQ_FOREACH(kv, kvs, next) {
if (strcasecmp(kv->key, key) == 0) {
return kv;
}
}
return NULL;
}
void
evhtp_kvs_add_kv(evhtp_kvs_t * kvs, evhtp_kv_t * kv) {
if (evhtp_unlikely(kvs == NULL || kv == NULL)) {
return;
}
TAILQ_INSERT_TAIL(kvs, kv, next);
}
void
evhtp_kvs_add_kvs(evhtp_kvs_t * dst, evhtp_kvs_t * src) {
if (dst == NULL || src == NULL) {
return;
}
evhtp_kv_t * kv;
TAILQ_FOREACH(kv, src, next) {
evhtp_kvs_add_kv(dst, evhtp_kv_new(kv->key,
kv->val,
kv->k_heaped,
kv->v_heaped));
}
}
typedef enum {
s_query_start = 0,
s_query_separator,
s_query_key,
s_query_val,
s_query_key_hex_1,
s_query_key_hex_2,
s_query_val_hex_1,
s_query_val_hex_2,
s_query_done
} query_parser_state;
static inline int
evhtp_is_hex_query_char(unsigned char ch) {
switch (ch) {
case 'a': case 'A':
case 'b': case 'B':
case 'c': case 'C':
case 'd': case 'D':
case 'e': case 'E':
case 'f': case 'F':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return 1;
default:
return 0;
} /* switch */
}
enum unscape_state {
unscape_state_start = 0,
unscape_state_hex1,
unscape_state_hex2
};
int
evhtp_unescape_string(unsigned char ** out, unsigned char * str, size_t str_len) {
unsigned char * optr;
unsigned char * sptr;
unsigned char d;
unsigned char ch;
unsigned char c;
size_t i;
enum unscape_state state;
if (out == NULL || *out == NULL) {
return -1;
}
state = unscape_state_start;
optr = *out;
sptr = str;
d = 0;
for (i = 0; i < str_len; i++) {
ch = *sptr++;
switch (state) {
case unscape_state_start:
if (ch == '%') {
state = unscape_state_hex1;
break;
}
*optr++ = ch;
break;
case unscape_state_hex1:
if (ch >= '0' && ch <= '9') {
d = (unsigned char)(ch - '0');
state = unscape_state_hex2;
break;
}
c = (unsigned char)(ch | 0x20);
if (c >= 'a' && c <= 'f') {
d = (unsigned char)(c - 'a' + 10);
state = unscape_state_hex2;
break;
}
state = unscape_state_start;
*optr++ = ch;
break;
case unscape_state_hex2:
state = unscape_state_start;
if (ch >= '0' && ch <= '9') {
ch = (unsigned char)((d << 4) + ch - '0');
*optr++ = ch;
break;
}
c = (unsigned char)(ch | 0x20);
if (c >= 'a' && c <= 'f') {
ch = (unsigned char)((d << 4) + c - 'a' + 10);
*optr++ = ch;
break;
}
break;
} /* switch */
}
return 0;
} /* evhtp_unescape_string */
evhtp_query_t *
evhtp_parse_query_wflags(const char * query, size_t len, int flags) {
evhtp_query_t * query_args;
query_parser_state state;
size_t key_idx;
size_t val_idx;
unsigned char ch;
size_t i;
if (len > (SIZE_MAX - (len + 2))) {
return NULL;
}
query_args = evhtp_query_new();
state = s_query_start;
key_idx = 0;
val_idx = 0;
#ifdef EVHTP_HAS_C99
char key_buf[len + 1];
char val_buf[len + 1];
#else
char * key_buf;
char * val_buf;
key_buf = malloc(len + 1);
evhtp_alloc_assert(key_buf);