| #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); |
| |
| val_buf = malloc(len + 1); |
| evhtp_alloc_assert(val_buf); |
| #endif |
| |
| for (i = 0; i < len; i++) { |
| ch = query[i]; |
| |
| if (key_idx >= len || val_idx >= len) { |
| goto error; |
| } |
| |
| switch (state) { |
| case s_query_start: |
| key_idx = 0; |
| val_idx = 0; |
| |
| key_buf[0] = '\0'; |
| val_buf[0] = '\0'; |
| |
| state = s_query_key; |
| /* Fall through. */ |
| case s_query_key: |
| switch (ch) { |
| case '=': |
| state = s_query_val; |
| break; |
| case '%': |
| key_buf[key_idx++] = ch; |
| key_buf[key_idx] = '\0'; |
| |
| if (!(flags & EVHTP_PARSE_QUERY_FLAG_IGNORE_HEX)) { |
| state = s_query_key_hex_1; |
| } |
| |
| break; |
| case ';': |
| if (!(flags & EVHTP_PARSE_QUERY_FLAG_TREAT_SEMICOLON_AS_SEP)) { |
| key_buf[key_idx++] = ch; |
| key_buf[key_idx] = '\0'; |
| break; |
| } |
| |
| /* otherwise we fallthrough */ |
| case '&': |
| /* in this state, we have a NULL value */ |
| if (!(flags & EVHTP_PARSE_QUERY_FLAG_ALLOW_NULL_VALS)) { |
| goto error; |
| } |
| |
| /* insert the key with value of NULL and set the |
| * state back to parsing s_query_key. |
| */ |
| evhtp_kvs_add_kv(query_args, evhtp_kv_new(key_buf, NULL, 1, 1)); |
| |
| key_idx = 0; |
| val_idx = 0; |
| |
| key_buf[0] = '\0'; |
| val_buf[0] = '\0'; |
| |
| state = s_query_key; |
| break; |
| default: |
| key_buf[key_idx++] = ch; |
| key_buf[key_idx] = '\0'; |
| break; |
| } /* switch */ |
| break; |
| case s_query_key_hex_1: |
| if (!evhtp_is_hex_query_char(ch)) { |
| /* not hex, so we treat as a normal key */ |
| if ((key_idx + 2) >= len) { |
| /* we need to insert \%<ch>, but not enough space */ |
| goto error; |
| } |
| |
| key_buf[key_idx - 1] = '%'; |
| key_buf[key_idx++] = ch; |
| key_buf[key_idx] = '\0'; |
| |
| state = s_query_key; |
| break; |
| } |
| |
| key_buf[key_idx++] = ch; |
| key_buf[key_idx] = '\0'; |
| |
| state = s_query_key_hex_2; |
| break; |
| case s_query_key_hex_2: |
| if (!evhtp_is_hex_query_char(ch)) { |
| goto error; |
| } |
| |
| key_buf[key_idx++] = ch; |
| key_buf[key_idx] = '\0'; |
| |
| state = s_query_key; |
| break; |
| case s_query_val: |
| switch (ch) { |
| case ';': |
| if (!(flags & EVHTP_PARSE_QUERY_FLAG_TREAT_SEMICOLON_AS_SEP)) { |
| val_buf[val_idx++] = ch; |
| val_buf[val_idx] = '\0'; |
| break; |
| } |
| case '&': |
| evhtp_kvs_add_kv(query_args, evhtp_kv_new(key_buf, val_buf, 1, 1)); |
| |
| key_idx = 0; |
| val_idx = 0; |
| |
| key_buf[0] = '\0'; |
| val_buf[0] = '\0'; |
| state = s_query_key; |
| |
| break; |
| case '%': |
| val_buf[val_idx++] = ch; |
| val_buf[val_idx] = '\0'; |
| |
| if (!(flags & EVHTP_PARSE_QUERY_FLAG_IGNORE_HEX)) { |
| state = s_query_val_hex_1; |
| } |
| |
| break; |
| default: |
| val_buf[val_idx++] = ch; |
| val_buf[val_idx] = '\0'; |
| |
| break; |
| } /* switch */ |
| break; |
| case s_query_val_hex_1: |
| if (!evhtp_is_hex_query_char(ch)) { |
| /* not really a hex val */ |
| if ((val_idx + 2) >= len) { |
| /* we need to insert \%<ch>, but not enough space */ |
| goto error; |
| } |
| |
| if (val_idx == 0) { |
| goto error; |
| } |
| |
| val_buf[val_idx - 1] = '%'; |
| val_buf[val_idx++] = ch; |
| val_buf[val_idx] = '\0'; |
| |
| state = s_query_val; |
| break; |
| } |
| |
| val_buf[val_idx++] = ch; |
| val_buf[val_idx] = '\0'; |
| |
| state = s_query_val_hex_2; |
| break; |
| case s_query_val_hex_2: |
| if (!evhtp_is_hex_query_char(ch)) { |
| goto error; |
| } |
| |
| val_buf[val_idx++] = ch; |
| val_buf[val_idx] = '\0'; |
| |
| state = s_query_val; |
| break; |
| default: |
| /* bad state */ |
| goto error; |
| } /* switch */ |
| } |
| |
| if (key_idx) { |
| do { |
| if (val_idx) { |
| evhtp_kvs_add_kv(query_args, evhtp_kv_new(key_buf, val_buf, 1, 1)); |
| break; |
| } |
| |
| if (state >= s_query_val) { |
| if (!(flags & EVHTP_PARSE_QUERY_FLAG_ALLOW_EMPTY_VALS)) { |
| goto error; |
| } |
| |
| evhtp_kvs_add_kv(query_args, evhtp_kv_new(key_buf, "", 1, 1)); |
| break; |
| } |
| |
| if (!(flags & EVHTP_PARSE_QUERY_FLAG_ALLOW_NULL_VALS)) { |
| goto error; |
| } |
| |
| evhtp_kvs_add_kv(query_args, evhtp_kv_new(key_buf, NULL, 1, 0)); |
| } while (0); |
| } |
| |
| #ifndef EVHTP_HAS_C99 |
| evhtp_safe_free(key_buf, free); |
| evhtp_safe_free(val_buf, free); |
| #endif |
| |
| return query_args; |
| error: |
| #ifndef EVHTP_HAS_C99 |
| evhtp_safe_free(key_buf, free); |
| evhtp_safe_free(val_buf, free); |
| #endif |
| |
| return NULL; |
| } /* evhtp_parse_query */ |
| |
| evhtp_query_t * |
| evhtp_parse_query(const char * query, size_t len) { |
| return evhtp_parse_query_wflags(query, len, |
| EVHTP_PARSE_QUERY_FLAG_STRICT); |
| } |
| |
| void |
| evhtp_send_reply_start(evhtp_request_t * request, evhtp_res code) { |
| evhtp_connection_t * c; |
| evbuf_t * reply_buf; |
| |
| c = evhtp_request_get_connection(request); |
| |
| if (!(reply_buf = _evhtp_create_reply(request, code))) { |
| evhtp_connection_free(c); |
| |
| return; |
| } |
| |
| bufferevent_write_buffer(c->bev, reply_buf); |
| evbuffer_drain(reply_buf, -1); |
| /* evbuffer_free(reply_buf); */ |
| } |
| |
| void |
| evhtp_send_reply_body(evhtp_request_t * request, evbuf_t * buf) { |
| evhtp_connection_t * c; |
| |
| c = request->conn; |
| |
| bufferevent_write_buffer(c->bev, buf); |
| } |
| |
| void |
| evhtp_send_reply_end(evhtp_request_t * request) { |
| request->finished = 1; |
| } |
| |
| void |
| evhtp_send_reply(evhtp_request_t * request, evhtp_res code) { |
| evhtp_connection_t * c; |
| evbuf_t * reply_buf; |
| |
| c = evhtp_request_get_connection(request); |
| request->finished = 1; |
| |
| if (!(reply_buf = _evhtp_create_reply(request, code))) { |
| evhtp_connection_free(request->conn); |
| |
| return; |
| } |
| |
| bufferevent_write_buffer(evhtp_connection_get_bev(c), reply_buf); |
| evbuffer_drain(reply_buf, -1); |
| /* evbuffer_free(reply_buf); */ |
| } |
| |
| int |
| evhtp_response_needs_body(const evhtp_res code, const htp_method method) { |
| return code != EVHTP_RES_NOCONTENT && |
| code != EVHTP_RES_NOTMOD && |
| (code < 100 || code >= 200) && |
| method != htp_method_HEAD; |
| } |
| |
| void |
| evhtp_send_reply_chunk_start(evhtp_request_t * request, evhtp_res code) { |
| evhtp_header_t * content_len; |
| |
| if (evhtp_response_needs_body(code, request->method)) { |
| content_len = evhtp_headers_find_header(request->headers_out, "Content-Length"); |
| |
| switch (request->proto) { |
| case EVHTP_PROTO_11: |
| |
| /* |
| * prefer HTTP/1.1 chunked encoding to closing the connection; |
| * note RFC 2616 section 4.4 forbids it with Content-Length: |
| * and it's not necessary then anyway. |
| */ |
| |
| evhtp_kv_rm_and_free(request->headers_out, content_len); |
| request->chunked = 1; |
| break; |
| case EVHTP_PROTO_10: |
| /* |
| * HTTP/1.0 can be chunked as long as the Content-Length header |
| * is set to 0 |
| */ |
| evhtp_kv_rm_and_free(request->headers_out, content_len); |
| |
| #if 0 |
| evhtp_headers_add_header(request->headers_out, |
| evhtp_header_new("Content-Length", "0", 0, 0)); |
| #endif |
| |
| request->chunked = 1; |
| break; |
| default: |
| request->chunked = 0; |
| break; |
| } /* switch */ |
| } else { |
| request->chunked = 0; |
| } |
| |
| if (request->chunked == 1) { |
| evhtp_headers_add_header(request->headers_out, |
| evhtp_header_new("Transfer-Encoding", "chunked", 0, 0)); |
| |
| /* |
| * if data already exists on the output buffer, we automagically convert |
| * it to the first chunk. |
| */ |
| if (evbuffer_get_length(request->buffer_out) > 0) { |
| char lstr[128]; |
| int sres; |
| |
| sres = snprintf(lstr, sizeof(lstr), "%x\r\n", |
| (unsigned)evbuffer_get_length(request->buffer_out)); |
| |
| if (sres >= sizeof(lstr) || sres < 0) { |
| /* overflow condition, shouldn't ever get here, but lets |
| * terminate the connection asap */ |
| goto end; |
| } |
| |
| evbuffer_prepend(request->buffer_out, lstr, strlen(lstr)); |
| evbuffer_add(request->buffer_out, "\r\n", 2); |
| } |
| } |
| |
| end: |
| evhtp_send_reply_start(request, code); |
| } /* evhtp_send_reply_chunk_start */ |
| |
| void |
| evhtp_send_reply_chunk(evhtp_request_t * request, evbuf_t * buf) { |
| evbuf_t * output; |
| |
| if (evbuffer_get_length(buf) == 0) { |
| return; |
| } |
| |
| output = bufferevent_get_output(request->conn->bev); |
| |
| if (request->chunked == 1) { |
| evbuffer_add_printf(output, "%x\r\n", |
| (unsigned)evbuffer_get_length(buf)); |
| } |
| |
| evhtp_send_reply_body(request, buf); |
| |
| if (request->chunked) { |
| evbuffer_add(output, "\r\n", 2); |
| } |
| |
| bufferevent_flush(request->conn->bev, EV_WRITE, BEV_FLUSH); |
| } |
| |
| void |
| evhtp_send_reply_chunk_end(evhtp_request_t * request) { |
| if (request->chunked == 1) { |
| evbuffer_add(bufferevent_get_output(evhtp_request_get_bev(request)), |
| "0\r\n\r\n", 5); |
| } |
| |
| evhtp_send_reply_end(request); |
| } |
| |
| void |
| evhtp_unbind_socket(evhtp_t * htp) { |
| evhtp_safe_free(htp->server, evconnlistener_free); |
| } |
| |
| int |
| evhtp_bind_sockaddr(evhtp_t * htp, struct sockaddr * sa, size_t sin_len, int backlog) { |
| #ifndef WIN32 |
| signal(SIGPIPE, SIG_IGN); |
| #endif |
| evutil_socket_t fd; |
| int on = 1; |
| |
| fd = socket(sa->sa_family, SOCK_STREAM, 0); |
| evhtp_errno_assert(fd != -1); |
| |
| evutil_make_socket_closeonexec(fd); |
| evutil_make_socket_nonblocking(fd); |
| |
| setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)); |
| setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); |
| |
| if (sa->sa_family == AF_INET6) { |
| int rc; |
| |
| rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); |
| evhtp_errno_assert(rc != -1); |
| } |
| |
| #if defined SO_REUSEPORT |
| if (htp->enable_reuseport) { |
| setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on)); |
| } |
| #endif |
| |
| #if defined TCP_NODELAY |
| if (htp->enable_nodelay == 1) { |
| setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)); |
| } |
| #endif |
| |
| #if defined TCP_DEFER_ACCEPT |
| if (htp->enable_defer_accept == 1) { |
| setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, (void *)&on, sizeof(on)); |
| } |
| #endif |
| |
| evhtp_errno_assert(bind(fd, sa, sin_len) != -1); |
| |
| htp->server = evconnlistener_new(htp->evbase, _evhtp_accept_cb, htp, |
| LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, |
| backlog, fd); |
| evhtp_errno_assert(htp->server != NULL); |
| |
| #ifndef EVHTP_DISABLE_SSL |
| if (htp->ssl_ctx != NULL) { |
| /* if ssl is enabled and we have virtual hosts, set our servername |
| * callback. We do this here because we want to make sure that this gets |
| * set after all potential virtualhosts have been set, not just after |
| * ssl_init. |
| */ |
| if (TAILQ_FIRST(&htp->vhosts) != NULL) { |
| SSL_CTX_set_tlsext_servername_callback(htp->ssl_ctx, |
| _evhtp_ssl_servername); |
| } |
| } |
| #endif |
| |
| return 0; |
| } /* evhtp_bind_sockaddr */ |
| |
| int |
| evhtp_bind_socket(evhtp_t * htp, const char * baddr, uint16_t port, int backlog) { |
| #ifndef NO_SYS_UN |
| struct sockaddr_un sun; |
| #endif |
| struct sockaddr_in6 sin6; |
| struct sockaddr_in sin; |
| struct sockaddr * sa; |
| size_t sin_len; |
| |
| memset(&sin, 0, sizeof(sin)); |
| |
| if (!strncmp(baddr, "ipv6:", 5)) { |
| memset(&sin6, 0, sizeof(sin6)); |
| |
| baddr += 5; |
| sin_len = sizeof(struct sockaddr_in6); |
| sin6.sin6_port = htons(port); |
| sin6.sin6_family = AF_INET6; |
| |
| evutil_inet_pton(AF_INET6, baddr, &sin6.sin6_addr); |
| sa = (struct sockaddr *)&sin6; |
| } else if (!strncmp(baddr, "unix:", 5)) { |
| #ifndef NO_SYS_UN |
| baddr += 5; |
| |
| if (strlen(baddr) >= sizeof(sun.sun_path)) { |
| return -1; |
| } |
| |
| memset(&sun, 0, sizeof(sun)); |
| |
| sin_len = sizeof(struct sockaddr_un); |
| sun.sun_family = AF_UNIX; |
| |
| strncpy(sun.sun_path, baddr, strlen(baddr)); |
| |
| sa = (struct sockaddr *)&sun; |
| #else |
| |
| return -1; |
| #endif |
| } else { |
| if (!strncmp(baddr, "ipv4:", 5)) { |
| baddr += 5; |
| } |
| |
| sin_len = sizeof(struct sockaddr_in); |
| |
| sin.sin_family = AF_INET; |
| sin.sin_port = htons(port); |
| sin.sin_addr.s_addr = inet_addr(baddr); |
| |
| sa = (struct sockaddr *)&sin; |
| } |
| |
| return evhtp_bind_sockaddr(htp, sa, sin_len, backlog); |
| } /* evhtp_bind_socket */ |
| |
| void |
| evhtp_callbacks_free(evhtp_callbacks_t * callbacks) { |
| evhtp_callback_t * callback; |
| evhtp_callback_t * tmp; |
| |
| if (callbacks == NULL) { |
| return; |
| } |
| |
| TAILQ_FOREACH_SAFE(callback, callbacks, next, tmp) { |
| TAILQ_REMOVE(callbacks, callback, next); |
| |
| evhtp_safe_free(callback, evhtp_callback_free); |
| } |
| |
| evhtp_safe_free(callbacks, free); |
| } |
| |
| evhtp_callback_t * |
| evhtp_callback_new(const char * path, evhtp_callback_type type, evhtp_callback_cb cb, void * arg) { |
| evhtp_callback_t * hcb; |
| |
| hcb = calloc(sizeof(evhtp_callback_t), 1); |
| evhtp_alloc_assert(hcb); |
| |
| hcb->type = type; |
| hcb->cb = cb; |
| hcb->cbarg = arg; |
| |
| switch (type) { |
| case evhtp_callback_type_hash: |
| hcb->hash = _evhtp_quick_hash(path); |
| hcb->val.path = strdup(path); |
| break; |
| #ifndef EVHTP_DISABLE_REGEX |
| case evhtp_callback_type_regex: |
| hcb->val.regex = malloc(sizeof(regex_t)); |
| |
| if (regcomp(hcb->val.regex, (char *)path, REG_EXTENDED) != 0) { |
| evhtp_safe_free(hcb->val.regex, free); |
| evhtp_safe_free(hcb, free); |
| |
| return NULL; |
| } |
| break; |
| #endif |
| case evhtp_callback_type_glob: |
| hcb->val.glob = strdup(path); |
| break; |
| default: |
| evhtp_safe_free(hcb, free); |
| |
| return NULL; |
| } /* switch */ |
| |
| return hcb; |
| } |
| |
| void |
| evhtp_callback_free(evhtp_callback_t * callback) { |
| if (callback == NULL) { |
| return; |
| } |
| |
| switch (callback->type) { |
| case evhtp_callback_type_hash: |
| evhtp_safe_free(callback->val.path, free); |
| break; |
| case evhtp_callback_type_glob: |
| evhtp_safe_free(callback->val.glob, free); |
| break; |
| #ifndef EVHTP_DISABLE_REGEX |
| case evhtp_callback_type_regex: |
| regfree(callback->val.regex); |
| evhtp_safe_free(callback->val.regex, free); |
| break; |
| #endif |
| } |
| |
| if (callback->hooks) { |
| evhtp_safe_free(callback->hooks, free); |
| } |
| |
| evhtp_safe_free(callback, free); |
| |
| return; |
| } |
| |
| int |
| evhtp_callbacks_add_callback(evhtp_callbacks_t * cbs, evhtp_callback_t * cb) { |
| TAILQ_INSERT_TAIL(cbs, cb, next); |
| |
| return 0; |
| } |
| |
| int |
| evhtp_set_hook(evhtp_hooks_t ** hooks, evhtp_hook_type type, evhtp_hook cb, void * arg) { |
| if (*hooks == NULL) { |
| if (!(*hooks = calloc(sizeof(evhtp_hooks_t), 1))) { |
| return -1; |
| } |
| } |
| |
| switch (type) { |
| case evhtp_hook_on_headers_start: |
| (*hooks)->on_headers_start = (evhtp_hook_headers_start_cb)cb; |
| (*hooks)->on_headers_start_arg = arg; |
| break; |
| case evhtp_hook_on_header: |
| (*hooks)->on_header = (evhtp_hook_header_cb)cb; |
| (*hooks)->on_header_arg = arg; |
| break; |
| case evhtp_hook_on_headers: |
| (*hooks)->on_headers = (evhtp_hook_headers_cb)cb; |
| (*hooks)->on_headers_arg = arg; |
| break; |
| case evhtp_hook_on_path: |
| (*hooks)->on_path = (evhtp_hook_path_cb)cb; |
| (*hooks)->on_path_arg = arg; |
| break; |
| case evhtp_hook_on_read: |
| (*hooks)->on_read = (evhtp_hook_read_cb)cb; |
| (*hooks)->on_read_arg = arg; |
| break; |
| case evhtp_hook_on_request_fini: |
| (*hooks)->on_request_fini = (evhtp_hook_request_fini_cb)cb; |
| (*hooks)->on_request_fini_arg = arg; |
| break; |
| case evhtp_hook_on_connection_fini: |
| (*hooks)->on_connection_fini = (evhtp_hook_connection_fini_cb)cb; |
| (*hooks)->on_connection_fini_arg = arg; |
| break; |
| case evhtp_hook_on_conn_error: |
| (*hooks)->on_connection_error = (evhtp_hook_conn_err_cb)cb; |
| (*hooks)->on_connection_error_arg = arg; |
| break; |
| case evhtp_hook_on_error: |
| (*hooks)->on_error = (evhtp_hook_err_cb)cb; |
| (*hooks)->on_error_arg = arg; |
| break; |
| case evhtp_hook_on_new_chunk: |
| (*hooks)->on_new_chunk = (evhtp_hook_chunk_new_cb)cb; |
| (*hooks)->on_new_chunk_arg = arg; |
| break; |
| case evhtp_hook_on_chunk_complete: |
| (*hooks)->on_chunk_fini = (evhtp_hook_chunk_fini_cb)cb; |
| (*hooks)->on_chunk_fini_arg = arg; |
| break; |
| case evhtp_hook_on_chunks_complete: |
| (*hooks)->on_chunks_fini = (evhtp_hook_chunks_fini_cb)cb; |
| (*hooks)->on_chunks_fini_arg = arg; |
| break; |
| case evhtp_hook_on_hostname: |
| (*hooks)->on_hostname = (evhtp_hook_hostname_cb)cb; |
| (*hooks)->on_hostname_arg = arg; |
| break; |
| case evhtp_hook_on_write: |
| (*hooks)->on_write = (evhtp_hook_write_cb)cb; |
| (*hooks)->on_write_arg = arg; |
| break; |
| case evhtp_hook_on_event: |
| (*hooks)->on_event = (evhtp_hook_event_cb)cb; |
| (*hooks)->on_event_arg = arg; |
| break; |
| default: |
| return -1; |
| } /* switch */ |
| |
| return 0; |
| } /* evhtp_set_hook */ |
| |
| int |
| evhtp_unset_hook(evhtp_hooks_t ** hooks, evhtp_hook_type type) { |
| return evhtp_set_hook(hooks, type, NULL, NULL); |
| } |
| |
| int |
| evhtp_unset_all_hooks(evhtp_hooks_t ** hooks) { |
| int res = 0; |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_headers_start)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_header)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_headers)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_path)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_read)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_request_fini)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_connection_fini)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_conn_error)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_error)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_new_chunk)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_chunk_complete)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_chunks_complete)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_hostname)) { |
| res -= 1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_write)) { |
| return -1; |
| } |
| |
| if (evhtp_unset_hook(hooks, evhtp_hook_on_event)) { |
| return -1; |
| } |
| |
| return res; |
| } /* evhtp_unset_all_hooks */ |
| |
| evhtp_callback_t * |
| evhtp_set_cb(evhtp_t * htp, const char * path, evhtp_callback_cb cb, void * arg) { |
| evhtp_callback_t * hcb; |
| |
| _evhtp_lock(htp); |
| |
| if (htp->callbacks == NULL) { |
| if (!(htp->callbacks = calloc(sizeof(evhtp_callbacks_t), 1))) { |
| _evhtp_unlock(htp); |
| |
| return NULL; |
| } |
| |
| TAILQ_INIT(htp->callbacks); |
| } |
| |
| if (!(hcb = evhtp_callback_new(path, evhtp_callback_type_hash, cb, arg))) { |
| _evhtp_unlock(htp); |
| |
| return NULL; |
| } |
| |
| if (evhtp_callbacks_add_callback(htp->callbacks, hcb)) { |
| evhtp_safe_free(hcb, evhtp_callback_free); |
| _evhtp_unlock(htp); |
| |
| return NULL; |
| } |
| |
| _evhtp_unlock(htp); |
| |
| return hcb; |
| } |
| |
| evhtp_callback_t * |
| evhtp_get_cb(evhtp_t * htp, const char * path) { |
| evhtp_callback_t * callback; |
| |
| TAILQ_FOREACH(callback, htp->callbacks, next) { |
| if (strcmp(callback->val.path, path) == 0) { |
| return callback; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| #ifndef EVHTP_DISABLE_EVTHR |
| static void |
| _evhtp_thread_init(evthr_t * thr, void * arg) { |
| evhtp_t * htp = (evhtp_t *)arg; |
| |
| if (htp->thread_init_cb) { |
| htp->thread_init_cb(htp, thr, htp->thread_init_cbarg); |
| } |
| } |
| |
| int |
| evhtp_use_threads(evhtp_t * htp, evhtp_thread_init_cb init_cb, int nthreads, void * arg) { |
| htp->thread_init_cb = init_cb; |
| htp->thread_init_cbarg = arg; |
| |
| #ifndef EVHTP_DISABLE_SSL |
| evhtp_ssl_use_threads(); |
| #endif |
| |
| if (!(htp->thr_pool = evthr_pool_new(nthreads, _evhtp_thread_init, htp))) { |
| return -1; |
| } |
| |
| evthr_pool_start(htp->thr_pool); |
| |
| return 0; |
| } |
| |
| #endif |
| |
| #ifndef EVHTP_DISABLE_EVTHR |
| int |
| evhtp_use_callback_locks(evhtp_t * htp) { |
| if (htp == NULL) { |
| return -1; |
| } |
| |
| if (!(htp->lock = malloc(sizeof(pthread_mutex_t)))) { |
| return -1; |
| } |
| |
| return pthread_mutex_init(htp->lock, NULL); |
| } |
| |
| #endif |
| |
| #ifndef EVHTP_DISABLE_REGEX |
| evhtp_callback_t * |
| evhtp_set_regex_cb(evhtp_t * htp, const char * pattern, evhtp_callback_cb cb, void * arg) { |
| evhtp_callback_t * hcb; |
| |
| _evhtp_lock(htp); |
| |
| if (htp->callbacks == NULL) { |
| if (!(htp->callbacks = calloc(sizeof(evhtp_callbacks_t), 1))) { |
| _evhtp_unlock(htp); |
| |
| return NULL; |
| } |
| |
| TAILQ_INIT(htp->callbacks); |
| } |
| |
| if (!(hcb = evhtp_callback_new(pattern, evhtp_callback_type_regex, cb, arg))) { |
| _evhtp_unlock(htp); |
| |
| return NULL; |
| } |
| |
| if (evhtp_callbacks_add_callback(htp->callbacks, hcb)) { |
| evhtp_safe_free(hcb, evhtp_callback_free); |
| _evhtp_unlock(htp); |
| |
| return NULL; |
| } |
| |
| _evhtp_unlock(htp); |
| |
| return hcb; |
| } |
| |
| #endif |
| |
| evhtp_callback_t * |
| evhtp_set_glob_cb(evhtp_t * htp, const char * pattern, evhtp_callback_cb cb, void * arg) { |
| evhtp_callback_t * hcb; |
| |
| _evhtp_lock(htp); |
| |
| if (htp->callbacks == NULL) { |
| if (!(htp->callbacks = calloc(sizeof(evhtp_callbacks_t), 1))) { |
| _evhtp_unlock(htp); |
| |
| return NULL; |
| } |
| |
| TAILQ_INIT(htp->callbacks); |
| } |
| |
| if (!(hcb = evhtp_callback_new(pattern, evhtp_callback_type_glob, cb, arg))) { |
| _evhtp_unlock(htp); |
| |
| return NULL; |
| } |
| |
| if (evhtp_callbacks_add_callback(htp->callbacks, hcb)) { |
| evhtp_safe_free(hcb, evhtp_callback_free); |
| _evhtp_unlock(htp); |
| |
| return NULL; |
| } |
| |
| _evhtp_unlock(htp); |
| |
| return hcb; |
| } |
| |
| void |
| evhtp_set_gencb(evhtp_t * htp, evhtp_callback_cb cb, void * arg) { |
| htp->defaults.cb = cb; |
| htp->defaults.cbarg = arg; |
| } |
| |
| void |
| evhtp_set_pre_accept_cb(evhtp_t * htp, evhtp_pre_accept_cb cb, void * arg) { |
| htp->defaults.pre_accept = cb; |
| htp->defaults.pre_accept_cbarg = arg; |
| } |
| |
| void |
| evhtp_set_post_accept_cb(evhtp_t * htp, evhtp_post_accept_cb cb, void * arg) { |
| htp->defaults.post_accept = cb; |
| htp->defaults.post_accept_cbarg = arg; |
| } |
| |
| #ifndef EVHTP_DISABLE_SSL |
| #ifndef EVHTP_DISABLE_EVTHR |
| int |
| evhtp_ssl_use_threads(void) { |
| int i; |
| |
| if (ssl_locks_initialized == 1) { |
| return 0; |
| } |
| |
| ssl_locks_initialized = 1; |
| |
| ssl_num_locks = CRYPTO_num_locks(); |
| ssl_locks = malloc(ssl_num_locks * sizeof(evhtp_mutex_t)); |
| |
| for (i = 0; i < ssl_num_locks; i++) { |
| pthread_mutex_init(&(ssl_locks[i]), NULL); |
| } |
| |
| CRYPTO_set_id_callback(_evhtp_ssl_get_thread_id); |
| CRYPTO_set_locking_callback(_evhtp_ssl_thread_lock); |
| |
| return 0; |
| } |
| |
| #endif |
| |
| int |
| evhtp_ssl_init(evhtp_t * htp, evhtp_ssl_cfg_t * cfg) { |
| #ifdef EVHTP_ENABLE_FUTURE_STUFF |
| evhtp_ssl_scache_init init_cb = NULL; |
| evhtp_ssl_scache_add add_cb = NULL; |
| evhtp_ssl_scache_get get_cb = NULL; |
| evhtp_ssl_scache_del del_cb = NULL; |
| #endif |
| long cache_mode; |
| |
| if (cfg == NULL || htp == NULL || cfg->pemfile == NULL) { |
| return -1; |
| } |
| |
| SSL_library_init(); |
| SSL_load_error_strings(); |
| RAND_poll(); |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10000000L |
| STACK_OF(SSL_COMP) * comp_methods = SSL_COMP_get_compression_methods(); |
| sk_SSL_COMP_zero(comp_methods); |
| #endif |
| |
| htp->ssl_cfg = cfg; |
| htp->ssl_ctx = SSL_CTX_new(SSLv23_server_method()); |
| |
| evhtp_alloc_assert(htp->ssl_ctx); |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x10000000L |
| SSL_CTX_set_options(htp->ssl_ctx, SSL_MODE_RELEASE_BUFFERS | SSL_OP_NO_COMPRESSION); |
| SSL_CTX_set_timeout(htp->ssl_ctx, cfg->ssl_ctx_timeout); |
| #endif |
| |
| SSL_CTX_set_options(htp->ssl_ctx, cfg->ssl_opts); |
| |
| #ifndef OPENSSL_NO_ECDH |
| if (cfg->named_curve != NULL) { |
| EC_KEY * ecdh = NULL; |
| int nid = 0; |
| |
| nid = OBJ_sn2nid(cfg->named_curve); |
| if (nid == 0) { |
| fprintf(stderr, "ECDH initialization failed: unknown curve %s\n", cfg->named_curve); |
| } |
| ecdh = EC_KEY_new_by_curve_name(nid); |
| if (ecdh == NULL) { |
| fprintf(stderr, "ECDH initialization failed for curve %s\n", cfg->named_curve); |
| } |
| SSL_CTX_set_tmp_ecdh(htp->ssl_ctx, ecdh); |
| EC_KEY_free(ecdh); |
| } |
| #endif /* OPENSSL_NO_ECDH */ |
| #ifndef OPENSSL_NO_DH |
| if (cfg->dhparams != NULL) { |
| FILE * fh; |
| DH * dh; |
| |
| fh = fopen(cfg->dhparams, "r"); |
| if (fh != NULL) { |
| dh = PEM_read_DHparams(fh, NULL, NULL, NULL); |
| if (dh != NULL) { |
| SSL_CTX_set_tmp_dh(htp->ssl_ctx, dh); |
| DH_free(dh); |
| } else { |
| fprintf(stderr, "DH initialization failed: unable to parse file %s\n", cfg->dhparams); |
| } |
| fclose(fh); |
| } else { |
| fprintf(stderr, "DH initialization failed: unable to open file %s\n", cfg->dhparams); |
| } |
| } |
| #endif /* OPENSSL_NO_DH */ |
| |
| if (cfg->ciphers != NULL) { |
| SSL_CTX_set_cipher_list(htp->ssl_ctx, cfg->ciphers); |
| } |
| |
| SSL_CTX_load_verify_locations(htp->ssl_ctx, cfg->cafile, cfg->capath); |
| X509_STORE_set_flags(SSL_CTX_get_cert_store(htp->ssl_ctx), cfg->store_flags); |
| SSL_CTX_set_verify(htp->ssl_ctx, cfg->verify_peer, cfg->x509_verify_cb); |
| |
| if (cfg->x509_chk_issued_cb != NULL) { |
| htp->ssl_ctx->cert_store->check_issued = cfg->x509_chk_issued_cb; |
| } |
| |
| if (cfg->verify_depth) { |
| SSL_CTX_set_verify_depth(htp->ssl_ctx, cfg->verify_depth); |
| } |
| |
| switch (cfg->scache_type) { |
| case evhtp_ssl_scache_type_disabled: |
| cache_mode = SSL_SESS_CACHE_OFF; |
| break; |
| #ifdef EVHTP_ENABLE_FUTURE_STUFF |
| case evhtp_ssl_scache_type_user: |
| cache_mode = SSL_SESS_CACHE_SERVER | |
| SSL_SESS_CACHE_NO_INTERNAL | |
| SSL_SESS_CACHE_NO_INTERNAL_LOOKUP; |
| |
| init_cb = cfg->scache_init; |
| add_cb = cfg->scache_add; |
| get_cb = cfg->scache_get; |
| del_cb = cfg->scache_del; |
| break; |
| case evhtp_ssl_scache_type_builtin: |
| cache_mode = SSL_SESS_CACHE_SERVER | |
| SSL_SESS_CACHE_NO_INTERNAL | |
| SSL_SESS_CACHE_NO_INTERNAL_LOOKUP; |
| |
| init_cb = _evhtp_ssl_builtin_init; |
| add_cb = _evhtp_ssl_builtin_add; |
| get_cb = _evhtp_ssl_builtin_get; |
| del_cb = _evhtp_ssl_builtin_del; |
| break; |
| #endif |
| case evhtp_ssl_scache_type_internal: |
| default: |
| cache_mode = SSL_SESS_CACHE_SERVER; |
| break; |
| } /* switch */ |
| |
| SSL_CTX_use_certificate_file(htp->ssl_ctx, cfg->pemfile, SSL_FILETYPE_PEM); |
| SSL_CTX_use_PrivateKey_file(htp->ssl_ctx, |
| cfg->privfile ? cfg->privfile : cfg->pemfile, SSL_FILETYPE_PEM); |
| |
| SSL_CTX_set_session_id_context(htp->ssl_ctx, |
| (void *)&session_id_context, |
| sizeof(session_id_context)); |
| |
| SSL_CTX_set_app_data(htp->ssl_ctx, htp); |
| SSL_CTX_set_session_cache_mode(htp->ssl_ctx, cache_mode); |
| |
| if (cache_mode != SSL_SESS_CACHE_OFF) { |
| SSL_CTX_sess_set_cache_size(htp->ssl_ctx, |
| cfg->scache_size ? cfg->scache_size : 1024); |
| |
| if (cfg->scache_type == evhtp_ssl_scache_type_builtin || |
| cfg->scache_type == evhtp_ssl_scache_type_user) { |
| SSL_CTX_sess_set_new_cb(htp->ssl_ctx, _evhtp_ssl_add_scache_ent); |
| SSL_CTX_sess_set_get_cb(htp->ssl_ctx, _evhtp_ssl_get_scache_ent); |
| SSL_CTX_sess_set_remove_cb(htp->ssl_ctx, _evhtp_ssl_delete_scache_ent); |
| |
| if (cfg->scache_init) { |
| cfg->args = (cfg->scache_init)(htp); |
| } |
| } |
| } |
| |
| return 0; |
| } /* evhtp_use_ssl */ |
| |
| #endif |
| |
| evbev_t * |
| evhtp_connection_get_bev(evhtp_connection_t * connection) { |
| return connection->bev; |
| } |
| |
| evbev_t * |
| evhtp_connection_take_ownership(evhtp_connection_t * connection) { |
| evbev_t * bev = evhtp_connection_get_bev(connection); |
| |
| if (connection->hooks) { |
| evhtp_unset_all_hooks(&connection->hooks); |
| } |
| |
| if (connection->request && connection->request->hooks) { |
| evhtp_unset_all_hooks(&connection->request->hooks); |
| } |
| |
| evhtp_connection_set_bev(connection, NULL); |
| |
| connection->owner = 0; |
| |
| bufferevent_disable(bev, EV_READ); |
| bufferevent_setcb(bev, NULL, NULL, NULL, NULL); |
| |
| return bev; |
| } |
| |
| evbev_t * |
| evhtp_request_get_bev(evhtp_request_t * request) { |
| return evhtp_connection_get_bev(request->conn); |
| } |
| |
| evbev_t * |
| evhtp_request_take_ownership(evhtp_request_t * request) { |
| return evhtp_connection_take_ownership(evhtp_request_get_connection(request)); |
| } |
| |
| void |
| evhtp_connection_set_bev(evhtp_connection_t * conn, evbev_t * bev) { |
| conn->bev = bev; |
| } |
| |
| void |
| evhtp_request_set_bev(evhtp_request_t * request, evbev_t * bev) { |
| evhtp_connection_set_bev(request->conn, bev); |
| } |
| |
| void |
| evhtp_request_set_keepalive(evhtp_request_t * request, int val) { |
| request->keepalive = (val > 0) ? 1 : 0; |
| } |
| |
| evhtp_connection_t * |
| evhtp_request_get_connection(evhtp_request_t * request) { |
| return request->conn; |
| } |
| |
| evhtp_proto |
| evhtp_request_get_proto(evhtp_request_t * request) { |
| return request->proto; |
| } |
| |
| inline void |
| evhtp_connection_set_timeouts(evhtp_connection_t * c, |
| const struct timeval * rtimeo, |
| const struct timeval * wtimeo) { |
| if (evhtp_unlikely(c == NULL)) { |
| return; |
| } |
| |
| |
| bufferevent_set_timeouts(c->bev, rtimeo, wtimeo); |
| } |
| |
| void |
| evhtp_connection_set_max_body_size(evhtp_connection_t * c, uint64_t len) { |
| if (len == 0) { |
| c->max_body_size = c->htp->max_body_size; |
| } else { |
| c->max_body_size = len; |
| } |
| } |
| |
| void |
| evhtp_request_set_max_body_size(evhtp_request_t * req, uint64_t len) { |
| evhtp_connection_set_max_body_size(req->conn, len); |
| } |
| |
| void |
| evhtp_connection_free(evhtp_connection_t * connection) { |
| if (evhtp_unlikely(connection == NULL)) { |
| return; |
| } |
| |
| _evhtp_connection_fini_hook(connection); |
| |
| evhtp_safe_free(connection->request, _evhtp_request_free); |
| evhtp_safe_free(connection->parser, free); |
| evhtp_safe_free(connection->hooks, free); |
| evhtp_safe_free(connection->saddr, free); |
| evhtp_safe_free(connection->scratch_buf, evbuffer_free); |
| |
| if (connection->resume_ev) { |
| evhtp_safe_free(connection->resume_ev, event_free); |
| } |
| |
| if (connection->bev) { |
| #ifdef LIBEVENT_HAS_SHUTDOWN |
| bufferevent_shutdown(connection->bev, _evhtp_shutdown_eventcb); |
| #else |
| #ifndef EVHTP_DISABLE_SSL |
| if (connection->ssl != NULL) { |
| SSL_set_shutdown(connection->ssl, SSL_RECEIVED_SHUTDOWN); |
| SSL_shutdown(connection->ssl); |
| } |
| #endif |
| bufferevent_free(connection->bev); |
| #endif |
| } |
| |
| evhtp_safe_free(connection, free); |
| } /* evhtp_connection_free */ |
| |
| void |
| evhtp_request_free(evhtp_request_t * request) { |
| _evhtp_request_free(request); |
| } |
| |
| void |
| evhtp_set_timeouts(evhtp_t * htp, const struct timeval * r_timeo, const struct timeval * w_timeo) { |
| if (r_timeo != NULL) { |
| htp->recv_timeo = *r_timeo; |
| } |
| |
| if (w_timeo != NULL) { |
| htp->send_timeo = *w_timeo; |
| } |
| } |
| |
| void |
| evhtp_set_max_keepalive_requests(evhtp_t * htp, uint64_t num) { |
| htp->max_keepalive_requests = num; |
| } |
| |
| /** |
| * @brief set bufferevent flags, defaults to BEV_OPT_CLOSE_ON_FREE |
| * |
| * @param htp |
| * @param flags |
| */ |
| void |
| evhtp_set_bev_flags(evhtp_t * htp, int flags) { |
| htp->bev_flags = flags; |
| } |
| |
| void |
| evhtp_set_max_body_size(evhtp_t * htp, uint64_t len) { |
| htp->max_body_size = len; |
| } |
| |
| void |
| evhtp_disable_100_continue(evhtp_t * htp) { |
| htp->disable_100_cont = 1; |
| } |
| |
| void |
| evhtp_set_parser_flags(evhtp_t * htp, int flags) { |
| htp->parser_flags = flags; |
| } |
| |
| int |
| evhtp_add_alias(evhtp_t * evhtp, const char * name) { |
| evhtp_alias_t * alias; |
| |
| if (evhtp_unlikely(evhtp == NULL || name == NULL)) { |
| return -1; |
| } |
| |
| if (!(alias = calloc(sizeof(evhtp_alias_t), 1))) { |
| return -1; |
| } |
| |
| alias->alias = strdup(name); |
| evhtp_alloc_assert(alias->alias); |
| |
| TAILQ_INSERT_TAIL(&evhtp->aliases, alias, next); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief add a virtual host. |
| * |
| * NOTE: If SSL is being used and the vhost was found via SNI, the Host: header |
| * will *NOT* be used to find a matching vhost. |
| * |
| * Also, any hooks which are set prior to finding a vhost that are hooks |
| * which are after the host hook, they are overwritten by the callbacks |
| * and hooks set for the vhost specific evhtp_t structure. |
| * |
| * @param evhtp |
| * @param name |
| * @param vhost |
| * |
| * @return |
| */ |
| int |
| evhtp_add_vhost(evhtp_t * evhtp, const char * name, evhtp_t * vhost) { |
| if (evhtp == NULL || name == NULL || vhost == NULL) { |
| return -1; |
| } |
| |
| if (TAILQ_FIRST(&vhost->vhosts) != NULL) { |
| /* vhosts cannot have secondary vhosts defined */ |
| return -1; |
| } |
| |
| if (!(vhost->server_name = strdup(name))) { |
| return -1; |
| } |
| |
| /* set the parent of this vhost so when the request has been completely |
| * serviced, the vhost can be reset to the original evhtp structure. |
| * |
| * This allows for a keep-alive connection to make multiple requests with |
| * different Host: values. |
| */ |
| vhost->parent = evhtp; |
| |
| /* inherit various flags from the parent evhtp structure */ |
| vhost->bev_flags = evhtp->bev_flags; |
| vhost->max_body_size = evhtp->max_body_size; |
| vhost->max_keepalive_requests = evhtp->max_keepalive_requests; |
| vhost->recv_timeo = evhtp->recv_timeo; |
| vhost->send_timeo = evhtp->send_timeo; |
| |
| TAILQ_INSERT_TAIL(&evhtp->vhosts, vhost, next_vhost); |
| |
| return 0; |
| } |
| |
| evhtp_t * |
| evhtp_new(evbase_t * evbase, void * arg) { |
| evhtp_t * htp; |
| |
| evhtp_assert(evbase != NULL); |
| |
| htp = calloc(sizeof(evhtp_t), 1); |
| evhtp_alloc_assert(htp); |
| |
| htp->arg = arg; |
| htp->evbase = evbase; |
| htp->bev_flags = BEV_OPT_CLOSE_ON_FREE; |
| |
| /* default to lenient argument parsing */ |
| htp->parser_flags = EVHTP_PARSE_QUERY_FLAG_LENIENT; |
| |
| |
| TAILQ_INIT(&htp->vhosts); |
| TAILQ_INIT(&htp->aliases); |
| |
| evhtp_set_gencb(htp, _evhtp_default_request_cb, (void *)htp); |
| |
| return htp; |
| } |
| |
| void |
| evhtp_free(evhtp_t * evhtp) { |
| evhtp_alias_t * evhtp_alias, * tmp; |
| |
| if (evhtp == NULL) { |
| return; |
| } |
| |
| #ifndef EVHTP_DISABLE_EVTHR |
| if (evhtp->thr_pool) { |
| evthr_pool_stop(evhtp->thr_pool); |
| evthr_pool_free(evhtp->thr_pool); |
| } |
| #endif |
| |
| #ifndef EVHTP_DISABLE_SSL |
| if (evhtp->ssl_ctx) { |
| SSL_CTX_free(evhtp->ssl_ctx); |
| } |
| #endif |
| |
| if (evhtp->server_name) { |
| evhtp_safe_free(evhtp->server_name, free); |
| } |
| |
| if (evhtp->callbacks) { |
| evhtp_safe_free(evhtp->callbacks, evhtp_callbacks_free); |
| } |
| |
| TAILQ_FOREACH_SAFE(evhtp_alias, &evhtp->aliases, next, tmp) { |
| if (evhtp_alias->alias != NULL) { |
| evhtp_safe_free(evhtp_alias->alias, free); |
| } |
| |
| TAILQ_REMOVE(&evhtp->aliases, evhtp_alias, next); |
| evhtp_safe_free(evhtp_alias, free); |
| } |
| |
| #ifndef EVHTP_DISABLE_SSL |
| if (evhtp->ssl_ctx) { |
| SSL_CTX_free(evhtp->ssl_ctx); |
| } |
| #endif |
| |
| evhtp_safe_free(evhtp, free); |
| } /* evhtp_free */ |
| |
| /***************************************************************** |
| * client request functions * |
| *****************************************************************/ |
| |
| evhtp_connection_t * |
| evhtp_connection_new(evbase_t * evbase, const char * addr, uint16_t port) { |
| return evhtp_connection_new_dns(evbase, NULL, addr, port); |
| } |
| |
| evhtp_connection_t * |
| evhtp_connection_new_dns(evbase_t * evbase, struct evdns_base * dns_base, |
| const char * addr, uint16_t port) { |
| evhtp_connection_t * conn; |
| int err; |
| |
| evhtp_assert(evbase != NULL); |
| |
| if (!(conn = _evhtp_connection_new(NULL, -1, evhtp_type_client))) { |
| return NULL; |
| } |
| |
| conn->evbase = evbase; |
| conn->bev = bufferevent_socket_new(evbase, -1, BEV_OPT_CLOSE_ON_FREE); |
| |
| if (conn->bev == NULL) { |
| evhtp_connection_free(conn); |
| |
| return NULL; |
| } |
| |
| bufferevent_enable(conn->bev, EV_READ); |
| bufferevent_setcb(conn->bev, NULL, NULL, |
| _evhtp_connection_eventcb, conn); |
| |
| if (dns_base != NULL) { |
| err = bufferevent_socket_connect_hostname(conn->bev, dns_base, |
| AF_UNSPEC, addr, port); |
| } else { |
| struct sockaddr_in sin4; |
| struct sockaddr_in6 sin6; |
| struct sockaddr * sin; |
| int salen; |
| |
| if (inet_pton(AF_INET, addr, &sin4.sin_addr)) { |
| sin4.sin_family = AF_INET; |
| sin4.sin_port = htons(port); |
| sin = (struct sockaddr *)&sin4; |
| salen = sizeof(sin4); |
| } else if (inet_pton(AF_INET6, addr, &sin6.sin6_addr)) { |
| sin6.sin6_family = AF_INET6; |
| sin6.sin6_port = htons(port); |
| sin = (struct sockaddr *)&sin6; |
| salen = sizeof(sin6); |
| } else { |
| /* Not a valid IP. */ |
| evhtp_connection_free(conn); |
| |
| return NULL; |
| } |
| |
| err = bufferevent_socket_connect(conn->bev, sin, salen); |
| } |
| |
| /* not needed since any of the bufferevent errors will go straight to |
| * the eventcb |
| */ |
| if (err) { |
| return NULL; |
| } |
| |
| return conn; |
| } /* evhtp_connection_new_dns */ |
| |
| #ifndef EVHTP_DISABLE_SSL |
| evhtp_connection_t * |
| evhtp_connection_ssl_new(evbase_t * evbase, const char * addr, uint16_t port, evhtp_ssl_ctx_t * ctx) { |
| evhtp_connection_t * conn; |
| struct sockaddr_in sin; |
| |
| evhtp_assert(evbase != NULL); |
| |
| if (!(conn = _evhtp_connection_new(NULL, -1, evhtp_type_client))) { |
| return NULL; |
| } |
| |
| sin.sin_family = AF_INET; |
| sin.sin_addr.s_addr = inet_addr(addr); |
| sin.sin_port = htons(port); |
| |
| conn->ssl = SSL_new(ctx); |
| conn->evbase = evbase; |
| conn->bev = bufferevent_openssl_socket_new(evbase, -1, conn->ssl, |
| BUFFEREVENT_SSL_CONNECTING, BEV_OPT_CLOSE_ON_FREE); |
| |
| bufferevent_enable(conn->bev, EV_READ); |
| bufferevent_setcb(conn->bev, NULL, NULL, |
| _evhtp_connection_eventcb, conn); |
| |
| bufferevent_socket_connect(conn->bev, |
| (struct sockaddr *)&sin, sizeof(sin)); |
| |
| |
| return conn; |
| } |
| |
| #endif |
| |
| |
| evhtp_request_t * |
| evhtp_request_new(evhtp_callback_cb cb, void * arg) { |
| evhtp_request_t * r; |
| |
| r = _evhtp_request_new(NULL); |
| evhtp_alloc_assert(r); |
| |
| r->cb = cb; |
| r->cbarg = arg; |
| r->proto = EVHTP_PROTO_11; |
| |
| return r; |
| } |
| |
| int |
| evhtp_make_request(evhtp_connection_t * c, evhtp_request_t * r, |
| htp_method meth, const char * uri) { |
| evbuf_t * obuf; |
| char * proto; |
| |
| obuf = bufferevent_get_output(c->bev); |
| r->conn = c; |
| c->request = r; |
| |
| switch (r->proto) { |
| case EVHTP_PROTO_10: |
| proto = "1.0"; |
| break; |
| case EVHTP_PROTO_11: |
| default: |
| proto = "1.1"; |
| break; |
| } |
| |
| evbuffer_add_printf(obuf, "%s %s HTTP/%s\r\n", |
| htparser_get_methodstr_m(meth), uri, proto); |
| |
| evhtp_headers_for_each(r->headers_out, _evhtp_create_headers, obuf); |
| evbuffer_add_reference(obuf, "\r\n", 2, NULL, NULL); |
| if (evbuffer_get_length(r->buffer_out)) { |
| evbuffer_add_buffer(obuf, r->buffer_out); |
| } |
| |
| return 0; |
| } |
| |
| unsigned int |
| evhtp_request_status(evhtp_request_t * r) { |
| return htparser_get_status(r->conn->parser); |
| } |