From d46aa5114f68566568c9891dbafed34e99a2a07e Mon Sep 17 00:00:00 2001 From: Torsten Dreyer Date: Thu, 6 Nov 2014 22:21:22 +0100 Subject: [PATCH] httpd: upgrade to latest mongoose 5.5 --- 3rdparty/mongoose/LICENSE | 4 +- 3rdparty/mongoose/mongoose.c | 2397 ++++++++++++++++++++-------------- 3rdparty/mongoose/mongoose.h | 282 ++-- src/Network/http/httpd.cxx | 2 - 4 files changed, 1566 insertions(+), 1119 deletions(-) diff --git a/3rdparty/mongoose/LICENSE b/3rdparty/mongoose/LICENSE index 2473d6dd7..08bf9ddc1 100644 --- a/3rdparty/mongoose/LICENSE +++ b/3rdparty/mongoose/LICENSE @@ -5,7 +5,7 @@ All rights reserved This code is dual-licensed: you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. For the terms of this -license, see http://www.gnu.org/licenses. +license, see . You are free to use this code under the terms of the GNU General Public License, but WITHOUT ANY WARRANTY; without even the implied @@ -13,4 +13,4 @@ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Alternatively, you can license this code under a commercial -license, as set out in http://cesanta.com/products.html. +license, as set out in . diff --git a/3rdparty/mongoose/mongoose.c b/3rdparty/mongoose/mongoose.c index 9fdfa305d..84c2d6094 100644 --- a/3rdparty/mongoose/mongoose.c +++ b/3rdparty/mongoose/mongoose.c @@ -14,32 +14,35 @@ // // Alternatively, you can license this library under a commercial // license, as set out in . +// +// $Date: 2014-09-16 06:47:40 UTC $ #ifdef NOEMBED_NET_SKELETON #include "net_skeleton.h" #else // net_skeleton start - // Copyright (c) 2014 Cesanta Software Limited // All rights reserved // -// This library is dual-licensed: you can redistribute it and/or modify +// This software is dual-licensed: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. For the terms of this // license, see . // -// You are free to use this library under the terms of the GNU General +// You are free to use this software under the terms of the GNU General // Public License, but WITHOUT ANY WARRANTY; without even the implied // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // -// Alternatively, you can license this library under a commercial +// Alternatively, you can license this software under a commercial // license, as set out in . +// +// $Date: 2014-09-28 05:04:41 UTC $ #ifndef NS_SKELETON_HEADER_INCLUDED #define NS_SKELETON_HEADER_INCLUDED -#define NS_SKELETON_VERSION "1.0" +#define NS_SKELETON_VERSION "2.1.0" #undef UNICODE // Use ANSI WinAPI functions #undef _UNICODE // Use multibyte encoding on Windows @@ -50,7 +53,9 @@ #define _XOPEN_SOURCE 600 // For flockfile() on Linux #define __STDC_FORMAT_MACROS // wants this for C++ #define __STDC_LIMIT_MACROS // C++ wants that for INT64_MAX +#ifndef _LARGEFILE_SOURCE #define _LARGEFILE_SOURCE // Enable fseeko() and ftello() functions +#endif #define _FILE_OFFSET_BITS 64 // Enable 64-bit file offsets #ifdef _MSC_VER @@ -61,6 +66,7 @@ #include #include #include +#include #include #include #include @@ -72,7 +78,9 @@ #include #ifdef _WIN32 +#ifdef _MSC_VER #pragma comment(lib, "ws2_32.lib") // Linking with winsock library +#endif #include #include #ifndef EINPROGRESS @@ -91,6 +99,7 @@ #endif // MINGW #defines va_copy #define snprintf _snprintf #define vsnprintf _vsnprintf +#define sleep(x) Sleep((x) * 1000) #define to64(x) _atoi64(x) typedef int socklen_t; typedef unsigned char uint8_t; @@ -99,6 +108,10 @@ typedef unsigned short uint16_t; typedef unsigned __int64 uint64_t; typedef __int64 int64_t; typedef SOCKET sock_t; +typedef struct _stati64 ns_stat_t; +#ifndef S_ISDIR +#define S_ISDIR(x) ((x) & _S_IFDIR) +#endif #else #include #include @@ -115,6 +128,7 @@ typedef SOCKET sock_t; #define INVALID_SOCKET (-1) #define to64(x) strtoll(x, NULL, 10) typedef int sock_t; +typedef struct stat ns_stat_t; #endif #ifdef NS_ENABLE_DEBUG @@ -124,7 +138,9 @@ typedef int sock_t; #define DBG(x) #endif +#ifndef ARRAY_SIZE #define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) +#endif #ifdef NS_ENABLE_SSL #ifdef __APPLE__ @@ -145,68 +161,99 @@ union socket_address { struct sockaddr_in sin; #ifdef NS_ENABLE_IPV6 struct sockaddr_in6 sin6; +#else + struct sockaddr sin6; #endif }; +// Describes chunk of memory +struct ns_str { + const char *p; + size_t len; +}; + +// IO buffers interface struct iobuf { char *buf; - int len; - int size; + size_t len; + size_t size; }; -void iobuf_init(struct iobuf *, int initial_size); +void iobuf_init(struct iobuf *, size_t initial_size); void iobuf_free(struct iobuf *); -int iobuf_append(struct iobuf *, const void *data, int data_size); -void iobuf_remove(struct iobuf *, int data_size); +size_t iobuf_append(struct iobuf *, const void *data, size_t data_size); +void iobuf_remove(struct iobuf *, size_t data_size); +void iobuf_resize(struct iobuf *, size_t new_size); +// Callback function (event handler) prototype, must be defined by user. +// Net skeleton will call event handler, passing events defined above. struct ns_connection; -enum ns_event { NS_POLL, NS_ACCEPT, NS_CONNECT, NS_RECV, NS_SEND, NS_CLOSE }; -typedef void (*ns_callback_t)(struct ns_connection *, enum ns_event, void *); +typedef void (*ns_callback_t)(struct ns_connection *, int event_num, void *evp); + +// Events. Meaning of event parameter (evp) is given in the comment. +#define NS_POLL 0 // Sent to each connection on each call to ns_mgr_poll() +#define NS_ACCEPT 1 // New connection accept()-ed. union socket_address *addr +#define NS_CONNECT 2 // connect() succeeded or failed. int *success_status +#define NS_RECV 3 // Data has benn received. int *num_bytes +#define NS_SEND 4 // Data has been written to a socket. int *num_bytes +#define NS_CLOSE 5 // Connection is closed. NULL -struct ns_server { - void *server_data; - union socket_address listening_sa; - sock_t listening_sock; + +struct ns_mgr { struct ns_connection *active_connections; - ns_callback_t callback; - SSL_CTX *ssl_ctx; - SSL_CTX *client_ssl_ctx; - sock_t ctl[2]; + const char *hexdump_file; // Debug hexdump file path + sock_t ctl[2]; // Socketpair for mg_wakeup() + void *user_data; // User data }; + struct ns_connection { - struct ns_connection *prev, *next; - struct ns_server *server; - void *connection_data; - time_t last_io_time; - sock_t sock; - struct iobuf recv_iobuf; - struct iobuf send_iobuf; + struct ns_connection *next, *prev; // ns_mgr::active_connections linkage + struct ns_connection *listener; // Set only for accept()-ed connections + struct ns_mgr *mgr; + + sock_t sock; // Socket + union socket_address sa; // Peer address + struct iobuf recv_iobuf; // Received data + struct iobuf send_iobuf; // Data scheduled for sending SSL *ssl; + SSL_CTX *ssl_ctx; + void *user_data; // User-specific data + void *proto_data; // Application protocol-specific data + time_t last_io_time; // Timestamp of the last socket IO + ns_callback_t callback; // Event handler function + unsigned int flags; #define NSF_FINISHED_SENDING_DATA (1 << 0) #define NSF_BUFFER_BUT_DONT_SEND (1 << 1) #define NSF_SSL_HANDSHAKE_DONE (1 << 2) #define NSF_CONNECTING (1 << 3) #define NSF_CLOSE_IMMEDIATELY (1 << 4) -#define NSF_ACCEPTED (1 << 5) -#define NSF_USER_1 (1 << 6) -#define NSF_USER_2 (1 << 7) -#define NSF_USER_3 (1 << 8) -#define NSF_USER_4 (1 << 9) +#define NSF_WANT_READ (1 << 5) +#define NSF_WANT_WRITE (1 << 6) +#define NSF_LISTENING (1 << 7) +#define NSF_UDP (1 << 8) + +#define NSF_USER_1 (1 << 20) +#define NSF_USER_2 (1 << 21) +#define NSF_USER_3 (1 << 22) +#define NSF_USER_4 (1 << 23) +#define NSF_USER_5 (1 << 24) +#define NSF_USER_6 (1 << 25) }; -void ns_server_init(struct ns_server *, void *server_data, ns_callback_t); -void ns_server_free(struct ns_server *); -int ns_server_poll(struct ns_server *, int milli); -void ns_server_wakeup(struct ns_server *); -void ns_iterate(struct ns_server *, ns_callback_t cb, void *param); -struct ns_connection *ns_add_sock(struct ns_server *, sock_t sock, void *p); +void ns_mgr_init(struct ns_mgr *, void *user_data); +void ns_mgr_free(struct ns_mgr *); +time_t ns_mgr_poll(struct ns_mgr *, int milli); +void ns_broadcast(struct ns_mgr *, ns_callback_t, void *, size_t); -int ns_bind(struct ns_server *, const char *addr); -int ns_set_ssl_cert(struct ns_server *, const char *ssl_cert); -struct ns_connection *ns_connect(struct ns_server *, const char *host, - int port, int ssl, void *connection_param); +struct ns_connection *ns_next(struct ns_mgr *, struct ns_connection *); +struct ns_connection *ns_add_sock(struct ns_mgr *, sock_t, + ns_callback_t, void *); +struct ns_connection *ns_bind(struct ns_mgr *, const char *, + ns_callback_t, void *); +struct ns_connection *ns_connect(struct ns_mgr *, const char *, + ns_callback_t, void *); int ns_send(struct ns_connection *, const void *buf, int len); int ns_printf(struct ns_connection *, const char *fmt, ...); @@ -215,7 +262,12 @@ int ns_vprintf(struct ns_connection *, const char *fmt, va_list ap); // Utility functions void *ns_start_thread(void *(*f)(void *), void *p); int ns_socketpair(sock_t [2]); +int ns_socketpair2(sock_t [2], int sock_type); // SOCK_STREAM or SOCK_DGRAM void ns_set_close_on_exec(sock_t); +void ns_sock_to_str(sock_t sock, char *buf, size_t len, int flags); +int ns_hexdump(const void *buf, int len, char *dst, int dst_len); +int ns_avprintf(char **buf, size_t size, const char *fmt, va_list ap); +int ns_resolve(const char *domain_name, char *ip_addr_buf, size_t buf_len); #ifdef __cplusplus } @@ -225,18 +277,20 @@ void ns_set_close_on_exec(sock_t); // Copyright (c) 2014 Cesanta Software Limited // All rights reserved // -// This library is dual-licensed: you can redistribute it and/or modify +// This software is dual-licensed: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. For the terms of this // license, see . // -// You are free to use this library under the terms of the GNU General +// You are free to use this software under the terms of the GNU General // Public License, but WITHOUT ANY WARRANTY; without even the implied // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // -// Alternatively, you can license this library under a commercial +// Alternatively, you can license this software under a commercial // license, as set out in . +// +// $Date: 2014-09-28 05:04:41 UTC $ #ifndef NS_MALLOC @@ -251,19 +305,29 @@ void ns_set_close_on_exec(sock_t); #define NS_FREE free #endif -#ifndef IOBUF_RESIZE_MULTIPLIER -#define IOBUF_RESIZE_MULTIPLIER 2.0 -#endif +#define NS_UDP_RECEIVE_BUFFER_SIZE 2000 +#define NS_VPRINTF_BUFFER_SIZE 500 -void iobuf_init(struct iobuf *iobuf, int size) { - iobuf->len = iobuf->size = 0; - iobuf->buf = NULL; +struct ctl_msg { + ns_callback_t callback; + char message[1024 * 8]; +}; - if (size > 0 && (iobuf->buf = (char *) NS_MALLOC(size)) != NULL) { - iobuf->size = size; +void iobuf_resize(struct iobuf *io, size_t new_size) { + char *p; + if ((new_size > io->size || (new_size < io->size && new_size >= io->len)) && + (p = (char *) NS_REALLOC(io->buf, new_size)) != NULL) { + io->size = new_size; + io->buf = p; } } +void iobuf_init(struct iobuf *iobuf, size_t initial_size) { + iobuf->len = iobuf->size = 0; + iobuf->buf = NULL; + iobuf_resize(iobuf, initial_size); +} + void iobuf_free(struct iobuf *iobuf) { if (iobuf != NULL) { if (iobuf->buf != NULL) NS_FREE(iobuf->buf); @@ -271,24 +335,21 @@ void iobuf_free(struct iobuf *iobuf) { } } -int iobuf_append(struct iobuf *io, const void *buf, int len) { - static const double mult = IOBUF_RESIZE_MULTIPLIER; +size_t iobuf_append(struct iobuf *io, const void *buf, size_t len) { char *p = NULL; - int new_len = 0; - assert(io->len >= 0); + assert(io != NULL); assert(io->len <= io->size); if (len <= 0) { - } else if ((new_len = io->len + len) < io->size) { + } else if (io->len + len <= io->size) { memcpy(io->buf + io->len, buf, len); - io->len = new_len; - } else if ((p = (char *) - NS_REALLOC(io->buf, (int) (new_len * mult))) != NULL) { + io->len += len; + } else if ((p = (char *) NS_REALLOC(io->buf, io->len + len)) != NULL) { io->buf = p; memcpy(io->buf + io->len, buf, len); - io->len = new_len; - io->size = (int) (new_len * mult); + io->len += len; + io->size = io->len; } else { len = 0; } @@ -296,13 +357,23 @@ int iobuf_append(struct iobuf *io, const void *buf, int len) { return len; } -void iobuf_remove(struct iobuf *io, int n) { - if (n >= 0 && n <= io->len) { +void iobuf_remove(struct iobuf *io, size_t n) { + if (n > 0 && n <= io->len) { memmove(io->buf, io->buf + n, io->len - n); io->len -= n; } } +static size_t ns_out(struct ns_connection *nc, const void *buf, size_t len) { + if (nc->flags & NSF_UDP) { + long n = sendto(nc->sock, buf, len, 0, &nc->sa.sa, sizeof(nc->sa.sin)); + DBG(("%p %d send %ld (%d %s)", nc, nc->sock, n, errno, strerror(errno))); + return n < 0 ? 0 : n; + } else { + return iobuf_append(&nc->send_iobuf, buf, len); + } +} + #ifndef NS_DISABLE_THREADS void *ns_start_thread(void *(*f)(void *), void *p) { #ifdef _WIN32 @@ -314,7 +385,7 @@ void *ns_start_thread(void *(*f)(void *), void *p) { (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); -#if NS_STACK_SIZE > 1 +#if defined(NS_STACK_SIZE) && NS_STACK_SIZE > 1 (void) pthread_attr_setstacksize(&attr, NS_STACK_SIZE); #endif @@ -326,15 +397,15 @@ void *ns_start_thread(void *(*f)(void *), void *p) { } #endif // NS_DISABLE_THREADS -static void ns_add_conn(struct ns_server *server, struct ns_connection *c) { - c->next = server->active_connections; - server->active_connections = c; +static void ns_add_conn(struct ns_mgr *mgr, struct ns_connection *c) { + c->next = mgr->active_connections; + mgr->active_connections = c; c->prev = NULL; if (c->next != NULL) c->next->prev = c; } static void ns_remove_conn(struct ns_connection *conn) { - if (conn->prev == NULL) conn->server->active_connections = conn->next; + if (conn->prev == NULL) conn->mgr->active_connections = conn->next; if (conn->prev) conn->prev->next = conn->next; if (conn->next) conn->next->prev = conn->prev; } @@ -342,7 +413,7 @@ static void ns_remove_conn(struct ns_connection *conn) { // Print message to buffer. If buffer is large enough to hold the message, // return buffer. If buffer is to small, allocate large enough buffer on heap, // and return allocated buffer. -static int ns_avprintf(char **buf, size_t size, const char *fmt, va_list ap) { +int ns_avprintf(char **buf, size_t size, const char *fmt, va_list ap) { va_list ap_copy; int len; @@ -377,12 +448,12 @@ static int ns_avprintf(char **buf, size_t size, const char *fmt, va_list ap) { return len; } -int ns_vprintf(struct ns_connection *conn, const char *fmt, va_list ap) { - char mem[2000], *buf = mem; +int ns_vprintf(struct ns_connection *nc, const char *fmt, va_list ap) { + char mem[NS_VPRINTF_BUFFER_SIZE], *buf = mem; int len; if ((len = ns_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) { - iobuf_append(&conn->send_iobuf, buf, len); + ns_out(nc, buf, len); } if (buf != mem && buf != NULL) { free(buf); @@ -400,20 +471,62 @@ int ns_printf(struct ns_connection *conn, const char *fmt, ...) { return len; } -static void ns_call(struct ns_connection *conn, enum ns_event ev, void *p) { - if (conn->server->callback) conn->server->callback(conn, ev, p); +static void hexdump(struct ns_connection *nc, const char *path, + int num_bytes, int ev) { + const struct iobuf *io = ev == NS_SEND ? &nc->send_iobuf : &nc->recv_iobuf; + FILE *fp; + char *buf, src[60], dst[60]; + int buf_size = num_bytes * 5 + 100; + + if ((fp = fopen(path, "a")) != NULL) { + ns_sock_to_str(nc->sock, src, sizeof(src), 3); + ns_sock_to_str(nc->sock, dst, sizeof(dst), 7); + fprintf(fp, "%lu %p %s %s %s %d\n", (unsigned long) time(NULL), + nc->user_data, src, + ev == NS_RECV ? "<-" : ev == NS_SEND ? "->" : + ev == NS_ACCEPT ? "" : "XX", + dst, num_bytes); + if (num_bytes > 0 && (buf = (char *) NS_MALLOC(buf_size)) != NULL) { + ns_hexdump(io->buf + (ev == NS_SEND ? 0 : io->len) - + (ev == NS_SEND ? 0 : num_bytes), num_bytes, buf, buf_size); + fprintf(fp, "%s", buf); + free(buf); + } + fclose(fp); + } } -static void ns_close_conn(struct ns_connection *conn) { - DBG(("%p %d", conn, conn->flags)); - ns_call(conn, NS_CLOSE, NULL); - ns_remove_conn(conn); +static void ns_call(struct ns_connection *nc, int ev, void *p) { + if (nc->mgr->hexdump_file != NULL && ev != NS_POLL) { + int len = (ev == NS_RECV || ev == NS_SEND) ? * (int *) p : 0; + hexdump(nc, nc->mgr->hexdump_file, len, ev); + } + + nc->callback(nc, ev, p); +} + +static void ns_destroy_conn(struct ns_connection *conn) { closesocket(conn->sock); iobuf_free(&conn->recv_iobuf); iobuf_free(&conn->send_iobuf); +#ifdef NS_ENABLE_SSL + if (conn->ssl != NULL) { + SSL_free(conn->ssl); + } + if (conn->ssl_ctx != NULL) { + SSL_CTX_free(conn->ssl_ctx); + } +#endif NS_FREE(conn); } +static void ns_close_conn(struct ns_connection *conn) { + DBG(("%p %d", conn, conn->flags)); + ns_call(conn, NS_CLOSE, NULL); + ns_remove_conn(conn); + ns_destroy_conn(conn); +} + void ns_set_close_on_exec(sock_t sock) { #ifdef _WIN32 (void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0); @@ -433,26 +546,29 @@ static void ns_set_non_blocking_mode(sock_t sock) { } #ifndef NS_DISABLE_SOCKETPAIR -int ns_socketpair(sock_t sp[2]) { - struct sockaddr_in sa; +int ns_socketpair2(sock_t sp[2], int sock_type) { + union socket_address sa; sock_t sock; - socklen_t len = sizeof(sa); + socklen_t len = sizeof(sa.sin); int ret = 0; sp[0] = sp[1] = INVALID_SOCKET; (void) memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(0); - sa.sin_addr.s_addr = htonl(0x7f000001); - - if ((sock = socket(AF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET && - !bind(sock, (struct sockaddr *) &sa, len) && - !listen(sock, 1) && - !getsockname(sock, (struct sockaddr *) &sa, &len) && - (sp[0] = socket(AF_INET, SOCK_STREAM, 6)) != -1 && - !connect(sp[0], (struct sockaddr *) &sa, len) && - (sp[1] = accept(sock,(struct sockaddr *) &sa, &len)) != INVALID_SOCKET) { + sa.sin.sin_family = AF_INET; + sa.sin.sin_port = htons(0); + sa.sin.sin_addr.s_addr = htonl(0x7f000001); + + if ((sock = socket(AF_INET, sock_type, 0)) != INVALID_SOCKET && + !bind(sock, &sa.sa, len) && + (sock_type == SOCK_DGRAM || !listen(sock, 1)) && + !getsockname(sock, &sa.sa, &len) && + (sp[0] = socket(AF_INET, sock_type, 0)) != INVALID_SOCKET && + !connect(sp[0], &sa.sa, len) && + (sock_type == SOCK_STREAM || + (!getsockname(sp[0], &sa.sa, &len) && !connect(sock, &sa.sa, len))) && + (sp[1] = (sock_type == SOCK_DGRAM ? sock : + accept(sock, &sa.sa, &len))) != INVALID_SOCKET) { ns_set_close_on_exec(sp[0]); ns_set_close_on_exec(sp[1]); ret = 1; @@ -461,16 +577,41 @@ int ns_socketpair(sock_t sp[2]) { if (sp[1] != INVALID_SOCKET) closesocket(sp[1]); sp[0] = sp[1] = INVALID_SOCKET; } - closesocket(sock); + if (sock_type != SOCK_DGRAM) closesocket(sock); return ret; } + +int ns_socketpair(sock_t sp[2]) { + return ns_socketpair2(sp, SOCK_STREAM); +} #endif // NS_DISABLE_SOCKETPAIR -// Valid listening port spec is: [ip_address:]port, e.g. "80", "127.0.0.1:3128" -static int ns_parse_port_string(const char *str, union socket_address *sa) { +// TODO(lsm): use non-blocking resolver +static int ns_resolve2(const char *host, struct in_addr *ina) { + struct hostent *he; + if ((he = gethostbyname(host)) == NULL) { + DBG(("gethostbyname(%s) failed: %s", host, strerror(errno))); + } else { + memcpy(ina, he->h_addr_list[0], sizeof(*ina)); + return 1; + } + return 0; +} + +// Resolve FDQN "host", store IP address in the "ip". +// Return > 0 (IP address length) on success. +int ns_resolve(const char *host, char *buf, size_t n) { + struct in_addr ad; + return ns_resolve2(host, &ad) ? snprintf(buf, n, "%s", inet_ntoa(ad)) : 0; +} + +// Address format: [PROTO://][IP_ADDRESS:]PORT[:CERT][:CA_CERT] +static int ns_parse_address(const char *str, union socket_address *sa, + int *proto, int *use_ssl, char *cert, char *ca) { unsigned int a, b, c, d, port; - int len = 0; + int n = 0, len = 0; + char host[200]; #ifdef NS_ENABLE_IPV6 char buf[100]; #endif @@ -481,40 +622,71 @@ static int ns_parse_port_string(const char *str, union socket_address *sa) { memset(sa, 0, sizeof(*sa)); sa->sin.sin_family = AF_INET; + *proto = SOCK_STREAM; + *use_ssl = 0; + cert[0] = ca[0] = '\0'; + + if (memcmp(str, "ssl://", 6) == 0) { + str += 6; + *use_ssl = 1; + } else if (memcmp(str, "udp://", 6) == 0) { + str += 6; + *proto = SOCK_DGRAM; + } else if (memcmp(str, "tcp://", 6) == 0) { + str += 6; + } + if (sscanf(str, "%u.%u.%u.%u:%u%n", &a, &b, &c, &d, &port, &len) == 5) { // Bind to a specific IPv4 address, e.g. 192.168.1.5:8080 sa->sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d); sa->sin.sin_port = htons((uint16_t) port); #ifdef NS_ENABLE_IPV6 - } else if (sscanf(str, "[%49[^]]]:%u%n", buf, &port, &len) == 2 && + } else if (sscanf(str, "[%99[^]]]:%u%n", buf, &port, &len) == 2 && inet_pton(AF_INET6, buf, &sa->sin6.sin6_addr)) { // IPv6 address, e.g. [3ffe:2a00:100:7031::1]:8080 sa->sin6.sin6_family = AF_INET6; sa->sin6.sin6_port = htons((uint16_t) port); #endif + } else if (sscanf(str, "%199[^ :]:%u%n", host, &port, &len) == 2) { + sa->sin.sin_port = htons((uint16_t) port); + ns_resolve2(host, &sa->sin.sin_addr); } else if (sscanf(str, "%u%n", &port, &len) == 1) { // If only port is specified, bind to IPv4, INADDR_ANY sa->sin.sin_port = htons((uint16_t) port); - } else { - port = 0; // Parsing failure. Make port invalid. } - return port <= 0xffff && str[len] == '\0'; + if (*use_ssl && (sscanf(str + len, ":%99[^:]:%99[^:]%n", cert, ca, &n) == 2 || + sscanf(str + len, ":%99[^:]%n", cert, &n) == 1)) { + len += n; + } + + return port < 0xffff && str[len] == '\0' ? len : 0; } // 'sa' must be an initialized address to bind to -static sock_t ns_open_listening_socket(union socket_address *sa) { - socklen_t len = sizeof(*sa); - sock_t on = 1, sock = INVALID_SOCKET; +static sock_t ns_open_listening_socket(union socket_address *sa, int proto) { + socklen_t sa_len = (sa->sa.sa_family == AF_INET) ? + sizeof(sa->sin) : sizeof(sa->sin6); + sock_t sock = INVALID_SOCKET; +#ifndef _WIN32 + int on = 1; +#endif - if ((sock = socket(sa->sa.sa_family, SOCK_STREAM, 6)) != INVALID_SOCKET && + if ((sock = socket(sa->sa.sa_family, proto, 0)) != INVALID_SOCKET && +#ifndef _WIN32 + // SO_RESUSEADDR is not enabled on Windows because the semantics of + // SO_REUSEADDR on UNIX and Windows is different. On Windows, + // SO_REUSEADDR allows to bind a socket to a port without error even if + // the port is already open by another program. This is not the behavior + // SO_REUSEADDR was designed for, and leads to hard-to-track failure + // scenarios. Therefore, SO_REUSEADDR was disabled on Windows. !setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on)) && - !bind(sock, &sa->sa, sa->sa.sa_family == AF_INET ? - sizeof(sa->sin) : sizeof(sa->sa)) && - !listen(sock, SOMAXCONN)) { +#endif + !bind(sock, &sa->sa, sa_len) && + (proto == SOCK_DGRAM || listen(sock, SOMAXCONN) == 0)) { ns_set_non_blocking_mode(sock); // In case port was set to 0, get the real port number - (void) getsockname(sock, &sa->sa, &len); + (void) getsockname(sock, &sa->sa, &sa_len); } else if (sock != INVALID_SOCKET) { closesocket(sock); sock = INVALID_SOCKET; @@ -523,66 +695,101 @@ static sock_t ns_open_listening_socket(union socket_address *sa) { return sock; } - -int ns_set_ssl_cert(struct ns_server *server, const char *cert) { #ifdef NS_ENABLE_SSL - if (cert != NULL && - (server->ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) { +// Certificate generation script is at +// https://github.com/cesanta/net_skeleton/blob/master/scripts/gen_certs.sh + +static int ns_use_ca_cert(SSL_CTX *ctx, const char *cert) { + if (ctx == NULL) { + return -1; + } else if (cert == NULL || cert[0] == '\0') { + return 0; + } + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0); + return SSL_CTX_load_verify_locations(ctx, cert, NULL) == 1 ? 0 : -2; +} + +static int ns_use_cert(SSL_CTX *ctx, const char *pem_file) { + if (ctx == NULL) { return -1; - } else if (SSL_CTX_use_certificate_file(server->ssl_ctx, cert, 1) == 0 || - SSL_CTX_use_PrivateKey_file(server->ssl_ctx, cert, 1) == 0) { + } else if (pem_file == NULL || pem_file[0] == '\0') { + return 0; + } else if (SSL_CTX_use_certificate_file(ctx, pem_file, 1) == 0 || + SSL_CTX_use_PrivateKey_file(ctx, pem_file, 1) == 0) { return -2; } else { - SSL_CTX_use_certificate_chain_file(server->ssl_ctx, cert); + SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_use_certificate_chain_file(ctx, pem_file); + return 0; } - return 0; -#else - return server != NULL && cert == NULL ? 0 : -3; -#endif } +#endif // NS_ENABLE_SSL -int ns_bind(struct ns_server *server, const char *str) { - ns_parse_port_string(str, &server->listening_sa); - if (server->listening_sock != INVALID_SOCKET) { - closesocket(server->listening_sock); +struct ns_connection *ns_bind(struct ns_mgr *srv, const char *str, + ns_callback_t callback, void *user_data) { + union socket_address sa; + struct ns_connection *nc = NULL; + int use_ssl, proto; + char cert[100], ca_cert[100]; + sock_t sock; + + ns_parse_address(str, &sa, &proto, &use_ssl, cert, ca_cert); + if (use_ssl && cert[0] == '\0') return NULL; + + if ((sock = ns_open_listening_socket(&sa, proto)) == INVALID_SOCKET) { + } else if ((nc = ns_add_sock(srv, sock, callback, NULL)) == NULL) { + closesocket(sock); + } else { + nc->sa = sa; + nc->flags |= NSF_LISTENING; + nc->user_data = user_data; + nc->callback = callback; + + if (proto == SOCK_DGRAM) { + nc->flags |= NSF_UDP; + } + +#ifdef NS_ENABLE_SSL + if (use_ssl) { + nc->ssl_ctx = SSL_CTX_new(SSLv23_server_method()); + if (ns_use_cert(nc->ssl_ctx, cert) != 0 || + ns_use_ca_cert(nc->ssl_ctx, ca_cert) != 0) { + ns_close_conn(nc); + nc = NULL; + } + } +#endif + + DBG(("%p sock %d/%d ssl %p %p", nc, sock, proto, nc->ssl_ctx, nc->ssl)); } - server->listening_sock = ns_open_listening_socket(&server->listening_sa); - return server->listening_sock == INVALID_SOCKET ? -1 : - (int) ntohs(server->listening_sa.sin.sin_port); -} + return nc; +} -static struct ns_connection *accept_conn(struct ns_server *server) { +static struct ns_connection *accept_conn(struct ns_connection *ls) { struct ns_connection *c = NULL; union socket_address sa; socklen_t len = sizeof(sa); sock_t sock = INVALID_SOCKET; // NOTE(lsm): on Windows, sock is always > FD_SETSIZE - if ((sock = accept(server->listening_sock, &sa.sa, &len)) == INVALID_SOCKET) { - closesocket(sock); - } else if ((c = (struct ns_connection *) NS_MALLOC(sizeof(*c))) == NULL || - memset(c, 0, sizeof(*c)) == NULL) { + if ((sock = accept(ls->sock, &sa.sa, &len)) == INVALID_SOCKET) { + } else if ((c = ns_add_sock(ls->mgr, sock, ls->callback, + ls->user_data)) == NULL) { closesocket(sock); #ifdef NS_ENABLE_SSL - } else if (server->ssl_ctx != NULL && - ((c->ssl = SSL_new(server->ssl_ctx)) == NULL || + } else if (ls->ssl_ctx != NULL && + ((c->ssl = SSL_new(ls->ssl_ctx)) == NULL || SSL_set_fd(c->ssl, sock) != 1)) { DBG(("SSL error")); - closesocket(sock); - free(c); + ns_close_conn(c); c = NULL; #endif } else { - ns_set_close_on_exec(sock); - ns_set_non_blocking_mode(sock); - c->server = server; - c->sock = sock; - c->flags |= NSF_ACCEPTED; - - ns_add_conn(server, c); + c->listener = ls; + c->proto_data = ls->proto_data; ns_call(c, NS_ACCEPT, &sa); - DBG(("%p %d %p %p", c, c->sock, c->ssl, server->ssl_ctx)); + DBG(("%p %d %p %p", c, c->sock, c->ssl_ctx, c->ssl)); } return c; @@ -598,47 +805,65 @@ static int ns_is_error(int n) { ); } -#ifdef NS_ENABLE_HEXDUMP -static void ns_hexdump(const struct ns_connection *conn, const void *buf, - int len, const char *marker) { - const unsigned char *p = (const unsigned char *) buf; - char path[500], date[100], ascii[17]; - FILE *fp; - -#if 0 - if (!match_prefix(NS_ENABLE_HEXDUMP, strlen(NS_ENABLE_HEXDUMP), - conn->remote_ip)) { - return; - } +void ns_sock_to_str(sock_t sock, char *buf, size_t len, int flags) { + union socket_address sa; + socklen_t slen = sizeof(sa); - snprintf(path, sizeof(path), "%s.%hu.txt", - conn->mg_conn.remote_ip, conn->mg_conn.remote_port); + if (buf != NULL && len > 0) { + buf[0] = '\0'; + memset(&sa, 0, sizeof(sa)); + if (flags & 4) { + getpeername(sock, &sa.sa, &slen); + } else { + getsockname(sock, &sa.sa, &slen); + } + if (flags & 1) { +#if defined(NS_ENABLE_IPV6) + inet_ntop(sa.sa.sa_family, sa.sa.sa_family == AF_INET ? + (void *) &sa.sin.sin_addr : + (void *) &sa.sin6.sin6_addr, buf, len); +#elif defined(_WIN32) + // Only Windoze Vista (and newer) have inet_ntop() + strncpy(buf, inet_ntoa(sa.sin.sin_addr), len); +#else + inet_ntop(sa.sa.sa_family, (void *) &sa.sin.sin_addr, buf,(socklen_t)len); #endif - snprintf(path, sizeof(path), "%p.txt", conn); - - if ((fp = fopen(path, "a")) != NULL) { - time_t cur_time = time(NULL); - int i, idx; + } + if (flags & 2) { + snprintf(buf + strlen(buf), len - (strlen(buf) + 1), "%s%d", + flags & 1 ? ":" : "", (int) ntohs(sa.sin.sin_port)); + } + } +} - strftime(date, sizeof(date), "%d/%b/%Y %H:%M:%S", localtime(&cur_time)); - fprintf(fp, "%s %s %d bytes\n", marker, date, len); +int ns_hexdump(const void *buf, int len, char *dst, int dst_len) { + const unsigned char *p = (const unsigned char *) buf; + char ascii[17] = ""; + int i, idx, n = 0; - for (i = 0; i < len; i++) { - idx = i % 16; - if (idx == 0) { - if (i > 0) fprintf(fp, " %s\n", ascii); - fprintf(fp, "%04x ", i); - } - fprintf(fp, " %02x", p[i]); - ascii[idx] = p[i] < 0x20 || p[i] > 0x7e ? '.' : p[i]; - ascii[idx + 1] = '\0'; + for (i = 0; i < len; i++) { + idx = i % 16; + if (idx == 0) { + if (i > 0) n += snprintf(dst + n, dst_len - n, " %s\n", ascii); + n += snprintf(dst + n, dst_len - n, "%04x ", i); } + n += snprintf(dst + n, dst_len - n, " %02x", p[i]); + ascii[idx] = p[i] < 0x20 || p[i] > 0x7e ? '.' : p[i]; + ascii[idx + 1] = '\0'; + } - while (i++ % 16) fprintf(fp, "%s", " "); - fprintf(fp, " %s\n\n", ascii); + while (i++ % 16) n += snprintf(dst + n, dst_len - n, "%s", " "); + n += snprintf(dst + n, dst_len - n, " %s\n\n", ascii); - fclose(fp); - } + return n; +} + +#ifdef NS_ENABLE_SSL +static int ns_ssl_err(struct ns_connection *conn, int res) { + int ssl_err = SSL_get_error(conn->ssl, res); + if (ssl_err == SSL_ERROR_WANT_READ) conn->flags |= NSF_WANT_READ; + if (ssl_err == SSL_ERROR_WANT_WRITE) conn->flags |= NSF_WANT_WRITE; + return ssl_err; } #endif @@ -655,11 +880,11 @@ static void ns_read_from_socket(struct ns_connection *conn) { #ifdef NS_ENABLE_SSL if (ret == 0 && ok == 0 && conn->ssl != NULL) { int res = SSL_connect(conn->ssl); - int ssl_err = SSL_get_error(conn->ssl, res); - DBG(("%p SSL_connect %d %d", conn, res, ssl_err)); + int ssl_err = ns_ssl_err(conn, res); if (res == 1) { - conn->flags = NSF_SSL_HANDSHAKE_DONE; - } else if (ssl_err == 2 || ssl_err == 3) { + conn->flags |= NSF_SSL_HANDSHAKE_DONE; + } else if (ssl_err == SSL_ERROR_WANT_READ || + ssl_err == SSL_ERROR_WANT_WRITE) { return; // Call us again } else { ok = 1; @@ -678,14 +903,22 @@ static void ns_read_from_socket(struct ns_connection *conn) { #ifdef NS_ENABLE_SSL if (conn->ssl != NULL) { if (conn->flags & NSF_SSL_HANDSHAKE_DONE) { - n = SSL_read(conn->ssl, buf, sizeof(buf)); + // SSL library may have more bytes ready to read then we ask to read. + // Therefore, read in a loop until we read everything. Without the loop, + // we skip to the next select() cycle which can just timeout. + while ((n = SSL_read(conn->ssl, buf, sizeof(buf))) > 0) { + DBG(("%p %d <- %d bytes (SSL)", conn, conn->flags, n)); + iobuf_append(&conn->recv_iobuf, buf, n); + ns_call(conn, NS_RECV, &n); + } + ns_ssl_err(conn, n); } else { int res = SSL_accept(conn->ssl); - int ssl_err = SSL_get_error(conn->ssl, res); - DBG(("%p SSL_accept %d %d", conn, res, ssl_err)); + int ssl_err = ns_ssl_err(conn, res); if (res == 1) { conn->flags |= NSF_SSL_HANDSHAKE_DONE; - } else if (ssl_err == 2 || ssl_err == 3) { + } else if (ssl_err == SSL_ERROR_WANT_READ || + ssl_err == SSL_ERROR_WANT_WRITE) { return; // Call us again } else { conn->flags |= NSF_CLOSE_IMMEDIATELY; @@ -695,21 +928,15 @@ static void ns_read_from_socket(struct ns_connection *conn) { } else #endif { - n = recv(conn->sock, buf, sizeof(buf), 0); + while ((n = (int) recv(conn->sock, buf, sizeof(buf), 0)) > 0) { + DBG(("%p %d <- %d bytes (PLAIN)", conn, conn->flags, n)); + iobuf_append(&conn->recv_iobuf, buf, n); + ns_call(conn, NS_RECV, &n); + } } -#ifdef NS_ENABLE_HEXDUMP - ns_hexdump(conn, buf, n, "<-"); -#endif - - DBG(("%p <- %d bytes [%.*s%s]", - conn, n, n < 40 ? n : 40, buf, n < 40 ? "" : "...")); - if (ns_is_error(n)) { conn->flags |= NSF_CLOSE_IMMEDIATELY; - } else if (n > 0) { - iobuf_append(&conn->recv_iobuf, buf, n); - ns_call(conn, NS_RECV, &n); } } @@ -720,35 +947,56 @@ static void ns_write_to_socket(struct ns_connection *conn) { #ifdef NS_ENABLE_SSL if (conn->ssl != NULL) { n = SSL_write(conn->ssl, io->buf, io->len); + if (n <= 0) { + int ssl_err = ns_ssl_err(conn, n); + if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) { + return; // Call us again + } else { + conn->flags |= NSF_CLOSE_IMMEDIATELY; + } + } } else #endif - { n = send(conn->sock, io->buf, io->len, 0); } - - -#ifdef NS_ENABLE_HEXDUMP - ns_hexdump(conn, io->buf, n, "->"); -#endif + { n = (int) send(conn->sock, io->buf, io->len, 0); } - DBG(("%p -> %d bytes %d [%.*s%s]", conn, n, conn->flags, - io->len < 40 ? io->len : 40, - io->buf, io->len < 40 ? "" : "...")); + DBG(("%p %d -> %d bytes", conn, conn->flags, n)); + ns_call(conn, NS_SEND, &n); if (ns_is_error(n)) { conn->flags |= NSF_CLOSE_IMMEDIATELY; } else if (n > 0) { iobuf_remove(io, n); - //conn->num_bytes_sent += n; - } - - if (io->len == 0 && conn->flags & NSF_FINISHED_SENDING_DATA) { - conn->flags |= NSF_CLOSE_IMMEDIATELY; } - - ns_call(conn, NS_SEND, NULL); } int ns_send(struct ns_connection *conn, const void *buf, int len) { - return iobuf_append(&conn->send_iobuf, buf, len); + return (int) ns_out(conn, buf, len); +} + +static void ns_handle_udp(struct ns_connection *ls) { + struct ns_connection nc; + char buf[NS_UDP_RECEIVE_BUFFER_SIZE]; + int n; + socklen_t s_len = sizeof(nc.sa); + + memset(&nc, 0, sizeof(nc)); + n = recvfrom(ls->sock, buf, sizeof(buf), 0, &nc.sa.sa, &s_len); + if (n <= 0) { + DBG(("%p recvfrom: %s", ls, strerror(errno))); + } else { + nc.mgr = ls->mgr; + nc.recv_iobuf.buf = buf; + nc.recv_iobuf.len = nc.recv_iobuf.size = n; + nc.sock = ls->sock; + nc.callback = ls->callback; + nc.user_data = ls->user_data; + nc.proto_data = ls->proto_data; + nc.mgr = ls->mgr; + nc.listener = ls; + nc.flags = NSF_UDP; + DBG(("%p %d bytes received", ls, n)); + ns_call(&nc, NS_RECV, &n); + } } static void ns_add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) { @@ -760,32 +1008,33 @@ static void ns_add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) { } } -int ns_server_poll(struct ns_server *server, int milli) { +time_t ns_mgr_poll(struct ns_mgr *mgr, int milli) { struct ns_connection *conn, *tmp_conn; struct timeval tv; fd_set read_set, write_set; - int num_active_connections = 0; sock_t max_fd = INVALID_SOCKET; time_t current_time = time(NULL); - if (server->listening_sock == INVALID_SOCKET && - server->active_connections == NULL) return 0; - FD_ZERO(&read_set); FD_ZERO(&write_set); - ns_add_to_set(server->listening_sock, &read_set, &max_fd); - ns_add_to_set(server->ctl[1], &read_set, &max_fd); + ns_add_to_set(mgr->ctl[1], &read_set, &max_fd); - for (conn = server->active_connections; conn != NULL; conn = tmp_conn) { + for (conn = mgr->active_connections; conn != NULL; conn = tmp_conn) { tmp_conn = conn->next; - ns_call(conn, NS_POLL, ¤t_time); - ns_add_to_set(conn->sock, &read_set, &max_fd); - if (conn->flags & NSF_CONNECTING) { - ns_add_to_set(conn->sock, &write_set, &max_fd); + if (!(conn->flags & (NSF_LISTENING | NSF_CONNECTING))) { + ns_call(conn, NS_POLL, ¤t_time); } - if (conn->send_iobuf.len > 0 && !(conn->flags & NSF_BUFFER_BUT_DONT_SEND)) { + if (!(conn->flags & NSF_WANT_WRITE)) { + //DBG(("%p read_set", conn)); + ns_add_to_set(conn->sock, &read_set, &max_fd); + } + if (((conn->flags & NSF_CONNECTING) && !(conn->flags & NSF_WANT_READ)) || + (conn->send_iobuf.len > 0 && !(conn->flags & NSF_CONNECTING) && + !(conn->flags & NSF_BUFFER_BUT_DONT_SEND))) { + //DBG(("%p write_set", conn)); ns_add_to_set(conn->sock, &write_set, &max_fd); - } else if (conn->flags & NSF_CLOSE_IMMEDIATELY) { + } + if (conn->flags & NSF_CLOSE_IMMEDIATELY) { ns_close_conn(conn); } } @@ -794,31 +1043,42 @@ int ns_server_poll(struct ns_server *server, int milli) { tv.tv_usec = (milli % 1000) * 1000; if (select((int) max_fd + 1, &read_set, &write_set, NULL, &tv) > 0) { - // Accept new connections - if (server->listening_sock != INVALID_SOCKET && - FD_ISSET(server->listening_sock, &read_set)) { - // We're not looping here, and accepting just one connection at - // a time. The reason is that eCos does not respect non-blocking - // flag on a listening socket and hangs in a loop. - if ((conn = accept_conn(server)) != NULL) { - conn->last_io_time = current_time; + // select() might have been waiting for a long time, reset current_time + // now to prevent last_io_time being set to the past. + current_time = time(NULL); + + // Read wakeup messages + if (mgr->ctl[1] != INVALID_SOCKET && + FD_ISSET(mgr->ctl[1], &read_set)) { + struct ctl_msg ctl_msg; + int len = (int) recv(mgr->ctl[1], (char *) &ctl_msg, sizeof(ctl_msg), 0); + send(mgr->ctl[1], ctl_msg.message, 1, 0); + if (len >= (int) sizeof(ctl_msg.callback) && ctl_msg.callback != NULL) { + struct ns_connection *c; + for (c = ns_next(mgr, NULL); c != NULL; c = ns_next(mgr, c)) { + ctl_msg.callback(c, NS_POLL, ctl_msg.message); + } } } - // Read possible wakeup calls - if (server->ctl[1] != INVALID_SOCKET && - FD_ISSET(server->ctl[1], &read_set)) { - unsigned char ch; - recv(server->ctl[1], &ch, 1, 0); - send(server->ctl[1], &ch, 1, 0); - } - - for (conn = server->active_connections; conn != NULL; conn = tmp_conn) { + for (conn = mgr->active_connections; conn != NULL; conn = tmp_conn) { tmp_conn = conn->next; if (FD_ISSET(conn->sock, &read_set)) { - conn->last_io_time = current_time; - ns_read_from_socket(conn); + if (conn->flags & NSF_LISTENING) { + if (conn->flags & NSF_UDP) { + ns_handle_udp(conn); + } else { + // We're not looping here, and accepting just one connection at + // a time. The reason is that eCos does not respect non-blocking + // flag on a listening socket and hangs in a loop. + accept_conn(conn); + } + } else { + conn->last_io_time = current_time; + ns_read_from_socket(conn); + } } + if (FD_ISSET(conn->sock, &write_set)) { if (conn->flags & NSF_CONNECTING) { ns_read_from_socket(conn); @@ -830,76 +1090,72 @@ int ns_server_poll(struct ns_server *server, int milli) { } } - for (conn = server->active_connections; conn != NULL; conn = tmp_conn) { + for (conn = mgr->active_connections; conn != NULL; conn = tmp_conn) { tmp_conn = conn->next; - num_active_connections++; - if (conn->flags & NSF_CLOSE_IMMEDIATELY) { + if ((conn->flags & NSF_CLOSE_IMMEDIATELY) || + (conn->send_iobuf.len == 0 && + (conn->flags & NSF_FINISHED_SENDING_DATA))) { ns_close_conn(conn); } } - //DBG(("%d active connections", num_active_connections)); - return num_active_connections; + return current_time; } -struct ns_connection *ns_connect(struct ns_server *server, const char *host, - int port, int use_ssl, void *param) { +struct ns_connection *ns_connect(struct ns_mgr *mgr, const char *address, + ns_callback_t callback, void *user_data) { sock_t sock = INVALID_SOCKET; - struct sockaddr_in sin; - struct hostent *he = NULL; - struct ns_connection *conn = NULL; - int connect_ret_val; + struct ns_connection *nc = NULL; + union socket_address sa; + char cert[100], ca_cert[100]; + int rc, use_ssl, proto; - (void) use_ssl; - if (host == NULL || (he = gethostbyname(host)) == NULL || - (sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { - DBG(("gethostbyname(%s) failed: %s", host, strerror(errno))); + ns_parse_address(address, &sa, &proto, &use_ssl, cert, ca_cert); + if ((sock = socket(AF_INET, proto, 0)) == INVALID_SOCKET) { return NULL; } - - sin.sin_family = AF_INET; - sin.sin_port = htons((uint16_t) port); - sin.sin_addr = * (struct in_addr *) he->h_addr_list[0]; ns_set_non_blocking_mode(sock); + rc = (proto == SOCK_DGRAM) ? 0 : connect(sock, &sa.sa, sizeof(sa.sin)); - connect_ret_val = connect(sock, (struct sockaddr *) &sin, sizeof(sin)); - if (ns_is_error(connect_ret_val)) { + if (rc != 0 && ns_is_error(rc)) { closesocket(sock); return NULL; - } else if ((conn = (struct ns_connection *) - NS_MALLOC(sizeof(*conn))) == NULL) { + } else if ((nc = ns_add_sock(mgr, sock, callback, user_data)) == NULL) { closesocket(sock); return NULL; } - memset(conn, 0, sizeof(*conn)); - conn->server = server; - conn->sock = sock; - conn->connection_data = param; - conn->flags = NSF_CONNECTING; - conn->last_io_time = time(NULL); + nc->sa = sa; // Important, cause UDP conns will use sendto() + nc->flags = (proto == SOCK_DGRAM) ? NSF_UDP : NSF_CONNECTING; #ifdef NS_ENABLE_SSL - if (use_ssl && - (conn->ssl = SSL_new(server->client_ssl_ctx)) != NULL) { - SSL_set_fd(conn->ssl, sock); + if (use_ssl) { + if ((nc->ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL || + ns_use_cert(nc->ssl_ctx, cert) != 0 || + ns_use_ca_cert(nc->ssl_ctx, ca_cert) != 0 || + (nc->ssl = SSL_new(nc->ssl_ctx)) == NULL) { + ns_close_conn(nc); + return NULL; + } else { + SSL_set_fd(nc->ssl, sock); + } } #endif - ns_add_conn(server, conn); - DBG(("%p %s:%d %d %p", conn, host, port, conn->sock, conn->ssl)); - - return conn; + return nc; } -struct ns_connection *ns_add_sock(struct ns_server *s, sock_t sock, void *p) { +struct ns_connection *ns_add_sock(struct ns_mgr *s, sock_t sock, + ns_callback_t callback, void *user_data) { struct ns_connection *conn; if ((conn = (struct ns_connection *) NS_MALLOC(sizeof(*conn))) != NULL) { memset(conn, 0, sizeof(*conn)); ns_set_non_blocking_mode(sock); + ns_set_close_on_exec(sock); conn->sock = sock; - conn->connection_data = p; - conn->server = s; + conn->user_data = user_data; + conn->callback = callback; + conn->mgr = s; conn->last_io_time = time(NULL); ns_add_conn(s, conn); DBG(("%p %d", conn, sock)); @@ -907,28 +1163,26 @@ struct ns_connection *ns_add_sock(struct ns_server *s, sock_t sock, void *p) { return conn; } -void ns_iterate(struct ns_server *server, ns_callback_t cb, void *param) { - struct ns_connection *conn, *tmp_conn; - - for (conn = server->active_connections; conn != NULL; conn = tmp_conn) { - tmp_conn = conn->next; - cb(conn, NS_POLL, param); - } +struct ns_connection *ns_next(struct ns_mgr *s, struct ns_connection *conn) { + return conn == NULL ? s->active_connections : conn->next; } -void ns_server_wakeup(struct ns_server *server) { - unsigned char ch = 0; - if (server->ctl[0] != INVALID_SOCKET) { - send(server->ctl[0], &ch, 1, 0); - recv(server->ctl[0], &ch, 1, 0); +void ns_broadcast(struct ns_mgr *mgr, ns_callback_t cb,void *data, size_t len) { + struct ctl_msg ctl_msg; + if (mgr->ctl[0] != INVALID_SOCKET && data != NULL && + len < sizeof(ctl_msg.message)) { + ctl_msg.callback = cb; + memcpy(ctl_msg.message, data, len); + send(mgr->ctl[0], (char *) &ctl_msg, + offsetof(struct ctl_msg, message) + len, 0); + recv(mgr->ctl[0], (char *) &len, 1, 0); } } -void ns_server_init(struct ns_server *s, void *server_data, ns_callback_t cb) { +void ns_mgr_init(struct ns_mgr *s, void *user_data) { memset(s, 0, sizeof(*s)); - s->listening_sock = s->ctl[0] = s->ctl[1] = INVALID_SOCKET; - s->server_data = server_data; - s->callback = cb; + s->ctl[0] = s->ctl[1] = INVALID_SOCKET; + s->user_data = user_data; #ifdef _WIN32 { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); } @@ -940,40 +1194,32 @@ void ns_server_init(struct ns_server *s, void *server_data, ns_callback_t cb) { #ifndef NS_DISABLE_SOCKETPAIR do { - ns_socketpair(s->ctl); + ns_socketpair2(s->ctl, SOCK_DGRAM); } while (s->ctl[0] == INVALID_SOCKET); #endif #ifdef NS_ENABLE_SSL - SSL_library_init(); - s->client_ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + {static int init_done; if (!init_done) { SSL_library_init(); init_done++; }} #endif } -void ns_server_free(struct ns_server *s) { +void ns_mgr_free(struct ns_mgr *s) { struct ns_connection *conn, *tmp_conn; DBG(("%p", s)); if (s == NULL) return; // Do one last poll, see https://github.com/cesanta/mongoose/issues/286 - ns_server_poll(s, 0); + ns_mgr_poll(s, 0); - if (s->listening_sock != INVALID_SOCKET) closesocket(s->listening_sock); if (s->ctl[0] != INVALID_SOCKET) closesocket(s->ctl[0]); if (s->ctl[1] != INVALID_SOCKET) closesocket(s->ctl[1]); - s->listening_sock = s->ctl[0] = s->ctl[1] = INVALID_SOCKET; + s->ctl[0] = s->ctl[1] = INVALID_SOCKET; for (conn = s->active_connections; conn != NULL; conn = tmp_conn) { tmp_conn = conn->next; ns_close_conn(conn); } - -#ifdef NS_ENABLE_SSL - if (s->ssl_ctx != NULL) SSL_CTX_free(s->ssl_ctx); - if (s->client_ssl_ctx != NULL) SSL_CTX_free(s->client_ssl_ctx); -#endif } - // net_skeleton end #endif // NOEMBED_NET_SKELETON @@ -985,35 +1231,52 @@ void ns_server_free(struct ns_server *s) { #ifndef S_ISDIR #define S_ISDIR(x) ((x) & _S_IFDIR) #endif -#define sleep(x) Sleep((x) * 1000) +#ifdef stat +#undef stat +#endif +#ifdef lseek +#undef lseek +#endif +#ifdef popen +#undef popen +#endif +#ifdef pclose +#undef pclose +#endif #define stat(x, y) mg_stat((x), (y)) #define fopen(x, y) mg_fopen((x), (y)) -#define open(x, y) mg_open((x), (y)) +#define open(x, y, z) mg_open((x), (y), (z)) +#define close(x) _close(x) #define lseek(x, y, z) _lseeki64((x), (y), (z)) #define popen(x, y) _popen((x), (y)) #define pclose(x) _pclose(x) #define mkdir(x, y) _mkdir(x) -#define to64(x) _atoi64(x) #ifndef __func__ #define STRX(x) #x #define STR(x) STRX(x) #define __func__ __FILE__ ":" STR(__LINE__) #endif -#define INT64_FMT "I64d" -#define stat(x, y) mg_stat((x), (y)) -#define fopen(x, y) mg_fopen((x), (y)) -#define open(x, y) mg_open((x), (y)) +/* MINGW has adopted the MSVC formatting for 64-bit ints as of gcc 4.4 till 4.8*/ +#if (defined(__MINGW32__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4 && __GNUC_MINOR__ < 8))) || defined(_MSC_VER) +#define INT64_FMT "I64d" +#else +#define INT64_FMT "lld" +#endif #define flockfile(x) ((void) (x)) #define funlockfile(x) ((void) (x)) typedef struct _stati64 file_stat_t; -typedef HANDLE pid_t; +typedef HANDLE process_id_t; + #else ////////////// UNIX specific defines and includes + #include +#include #include #include #define O_BINARY 0 #define INT64_FMT PRId64 typedef struct stat file_stat_t; +typedef pid_t process_id_t; #endif //////// End of platform-specific defines and includes #include "mongoose.h" @@ -1021,7 +1284,6 @@ typedef struct stat file_stat_t; #define MAX_REQUEST_SIZE 16384 #define IOBUF_SIZE 8192 #define MAX_PATH_SIZE 8192 -#define LUA_SCRIPT_PATTERN "**.lp$" #define DEFAULT_CGI_PATTERN "**.cgi$|**.pl$|**.php$" #define CGI_ENVIRONMENT_SIZE 8192 #define MAX_CGI_ENVIR_VARS 64 @@ -1042,10 +1304,10 @@ typedef struct stat file_stat_t; #endif #ifndef MONGOOSE_IDLE_TIMEOUT_SECONDS -#define MONGOOSE_IDLE_TIMEOUT_SECONDS 30 +#define MONGOOSE_IDLE_TIMEOUT_SECONDS 300 #endif -#ifdef MONGOOSE_NO_SOCKETPAIR +#ifdef NS_DISABLE_SOCKETPAIR #define MONGOOSE_NO_CGI #endif @@ -1056,6 +1318,7 @@ typedef struct stat file_stat_t; #define MONGOOSE_NO_DIRECTORY_LISTING #define MONGOOSE_NO_LOGGING #define MONGOOSE_NO_SSI +#define MONGOOSE_NO_DL #endif struct vec { @@ -1082,20 +1345,20 @@ enum { CGI_INTERPRETER, CGI_PATTERN, #endif -#ifndef MONGOOSE_NO_DAV DAV_AUTH_FILE, -#endif DOCUMENT_ROOT, #ifndef MONGOOSE_NO_DIRECTORY_LISTING ENABLE_DIRECTORY_LISTING, #endif #endif + ENABLE_PROXY, EXTRA_MIME_TYPES, #if !defined(MONGOOSE_NO_FILESYSTEM) && !defined(MONGOOSE_NO_AUTH) GLOBAL_AUTH_FILE, #endif - HIDE_FILES_PATTERN, #ifndef MONGOOSE_NO_FILESYSTEM + HIDE_FILES_PATTERN, + HEXDUMP_FILE, INDEX_FILES, #endif LISTENING_PORT, @@ -1104,9 +1367,6 @@ enum { #endif #ifndef MONGOOSE_NO_SSI SSI_PATTERN, -#endif -#ifdef NS_ENABLE_SSL - SSL_CERTIFICATE, #endif URL_REWRITES, NUM_OPTIONS @@ -1123,21 +1383,21 @@ static const char *static_config_options[] = { "cgi_interpreter", NULL, "cgi_pattern", DEFAULT_CGI_PATTERN, #endif -#ifndef MONGOOSE_NO_DAV "dav_auth_file", NULL, -#endif "document_root", NULL, #ifndef MONGOOSE_NO_DIRECTORY_LISTING "enable_directory_listing", "yes", #endif #endif + "enable_proxy", NULL, "extra_mime_types", NULL, #if !defined(MONGOOSE_NO_FILESYSTEM) && !defined(MONGOOSE_NO_AUTH) "global_auth_file", NULL, #endif - "hide_files_patterns", NULL, #ifndef MONGOOSE_NO_FILESYSTEM - "index_files","index.html,index.htm,index.shtml,index.cgi,index.php,index.lp", + "hide_files_patterns", NULL, + "hexdump_file", NULL, + "index_files","index.html,index.htm,index.shtml,index.cgi,index.php", #endif "listening_port", NULL, #ifndef _WIN32 @@ -1145,47 +1405,45 @@ static const char *static_config_options[] = { #endif #ifndef MONGOOSE_NO_SSI "ssi_pattern", "**.shtml$|**.shtm$", -#endif -#ifdef NS_ENABLE_SSL - "ssl_certificate", NULL, #endif "url_rewrites", NULL, NULL }; struct mg_server { - struct ns_server ns_server; + struct ns_mgr ns_mgr; union socket_address lsa; // Listening socket address mg_handler_t event_handler; char *config_options[NUM_OPTIONS]; - char local_ip[48]; }; // Local endpoint representation union endpoint { - int fd; // Opened regular local file - struct ns_connection *cgi_conn; // CGI socket + int fd; // Opened regular local file + struct ns_connection *nc; // CGI or proxy->target connection }; -enum endpoint_type { EP_NONE, EP_FILE, EP_CGI, EP_USER, EP_PUT, EP_CLIENT }; +enum endpoint_type { + EP_NONE, EP_FILE, EP_CGI, EP_USER, EP_PUT, EP_CLIENT, EP_PROXY +}; #define MG_HEADERS_SENT NSF_USER_1 #define MG_LONG_RUNNING NSF_USER_2 #define MG_CGI_CONN NSF_USER_3 +#define MG_PROXY_CONN NSF_USER_4 +#define MG_PROXY_DONT_PARSE NSF_USER_5 struct connection { - struct ns_connection *ns_conn; + struct ns_connection *ns_conn; // NOTE(lsm): main.c depends on this order struct mg_connection mg_conn; struct mg_server *server; union endpoint endpoint; enum endpoint_type endpoint_type; char *path_info; char *request; - int64_t num_bytes_sent; // Total number of bytes sent + int64_t num_bytes_recv; // Total number of bytes received int64_t cl; // Reply content length, for Range support int request_len; // Request length, including last \r\n after last header - //int flags; // CONN_* flags: CONN_CLOSE, CONN_SPOOL_DONE, etc - //mg_handler_t handler; // Callback for HTTP client }; #define MG_CONN_2_CONN(c) ((struct connection *) ((char *) (c) - \ @@ -1193,6 +1451,7 @@ struct connection { static void open_local_endpoint(struct connection *conn, int skip_user); static void close_local_endpoint(struct connection *conn); +static void mg_ev_handler(struct ns_connection *nc, int ev, void *p); static const struct { const char *extension; @@ -1220,7 +1479,7 @@ static const struct { {".ogg", 4, "application/ogg"}, {".ram", 4, "audio/x-pn-realaudio"}, {".xml", 4, "text/xml"}, - {".json", 5, "text/json"}, + {".json", 5, "application/json"}, {".xslt", 5, "application/xml"}, {".xsl", 4, "application/xml"}, {".ra", 3, "audio/x-pn-realaudio"}, @@ -1232,7 +1491,7 @@ static const struct { {".tar", 4, "application/x-tar"}, {".gz", 3, "application/x-gunzip"}, {".arj", 4, "application/x-arj-compressed"}, - {".rar", 4, "application/x-arj-compressed"}, + {".rar", 4, "application/x-rar-compressed"}, {".rtf", 4, "application/rtf"}, {".pdf", 4, "application/pdf"}, {".swf", 4, "application/x-shockwave-flash"}, @@ -1255,17 +1514,44 @@ void *mg_start_thread(void *(*f)(void *), void *p) { } #endif // MONGOOSE_NO_THREADS +#ifndef MONGOOSE_NO_MMAP #ifdef _WIN32 -#ifndef MONGOOSE_NO_FILESYSTEM -// Encode 'path' which is assumed UTF-8 string, into UNICODE string. -// wbuf and wbuf_len is a target buffer and its length. -static void to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { - char buf[MAX_PATH_SIZE * 2], buf2[MAX_PATH_SIZE * 2], *p; - - strncpy(buf, path, sizeof(buf)); - buf[sizeof(buf) - 1] = '\0'; - - // Trim trailing slashes. Leave backslash for paths like "X:\" +static void *mmap(void *addr, int64_t len, int prot, int flags, int fd, + int offset) { + HANDLE fh = (HANDLE) _get_osfhandle(fd); + HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0); + void *p = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, (size_t) len); + CloseHandle(mh); + return p; +} +#define munmap(x, y) UnmapViewOfFile(x) +#define MAP_FAILED NULL +#define MAP_PRIVATE 0 +#define PROT_READ 0 +#else +#include +#endif + +void *mg_mmap(FILE *fp, size_t size) { + void *p = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fileno(fp), 0); + return p == MAP_FAILED ? NULL : p; +} + +void mg_munmap(void *p, size_t size) { + munmap(p, size); +} +#endif // MONGOOSE_NO_MMAP + +#if defined(_WIN32) && !defined(MONGOOSE_NO_FILESYSTEM) +// Encode 'path' which is assumed UTF-8 string, into UNICODE string. +// wbuf and wbuf_len is a target buffer and its length. +static void to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { + char buf[MAX_PATH_SIZE * 2], buf2[MAX_PATH_SIZE * 2], *p; + + strncpy(buf, path, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + + // Trim trailing slashes. Leave backslash for paths like "X:\" p = buf + strlen(buf) - 1; while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; @@ -1294,13 +1580,12 @@ static FILE *mg_fopen(const char *path, const char *mode) { return _wfopen(wpath, wmode); } -static int mg_open(const char *path, int flag) { +static int mg_open(const char *path, int flag, int mode) { wchar_t wpath[MAX_PATH_SIZE]; to_wchar(path, wpath, ARRAY_SIZE(wpath)); - return _wopen(wpath, flag); + return _wopen(wpath, flag, mode); } -#endif -#endif // MONGOOSE_NO_FILESYSTEM +#endif // _WIN32 && !MONGOOSE_NO_FILESYSTEM // A helper function for traversing a comma separated list of values. // It returns a list pointer shifted to the next value, or NULL if the end @@ -1424,23 +1709,72 @@ static void parse_http_headers(char **buf, struct mg_connection *ri) { static const char *status_code_to_str(int status_code) { switch (status_code) { + + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 102: return "Processing"; + case 200: return "OK"; case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + + case 300: return "Multiple Choices"; case 301: return "Moved Permanently"; case 302: return "Found"; + case 303: return "See Other"; case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "Switch Proxy"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; case 403: return "Forbidden"; case 404: return "Not Found"; case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; case 409: return "Conflict"; + case 410: return "Gone"; case 411: return "Length Required"; - case 413: return "Request Entity Too Large"; + case 412: return "Precondition Failed"; + case 413: return "Payload Too Large"; + case 414: return "URI Too Long"; case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I\'m a teapot"; + case 422: return "Unprocessable Entity"; case 423: return "Locked"; - case 500: return "Server Error"; + case 424: return "Failed Dependency"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 451: return "Unavailable For Legal Reasons"; + + case 500: return "Internal Server Error"; case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + default: return "Server Error"; } } @@ -1488,7 +1822,7 @@ static void send_http_error(struct connection *conn, int code, body_len += mg_vsnprintf(body + body_len, sizeof(body) - body_len, fmt, ap); va_end(ap); } - if (code >= 300 && code <= 399) { + if ((code >= 300 && code <= 399) || code == 204) { // 3xx errors do not have body body_len = 0; } @@ -1509,16 +1843,21 @@ static void write_chunk(struct connection *conn, const char *buf, int len) { ns_send(conn->ns_conn, "\r\n", 2); } -int mg_printf(struct mg_connection *conn, const char *fmt, ...) { +size_t mg_printf(struct mg_connection *conn, const char *fmt, ...) { struct connection *c = MG_CONN_2_CONN(conn); - int len; va_list ap; va_start(ap, fmt); - len = ns_vprintf(c->ns_conn, fmt, ap); + ns_vprintf(c->ns_conn, fmt, ap); va_end(ap); - return len; + return c->ns_conn->send_iobuf.len; +} + +static void ns_forward(struct ns_connection *from, struct ns_connection *to) { + DBG(("%p -> %p %lu bytes", from, to, (unsigned long)from->recv_iobuf.len)); + ns_send(to, from->recv_iobuf.buf, from->recv_iobuf.len); + iobuf_remove(&from->recv_iobuf, from->recv_iobuf.len); } #ifndef MONGOOSE_NO_CGI @@ -1537,7 +1876,7 @@ static int wait_until_ready(sock_t sock, int for_read) { } static void *push_to_stdin(void *arg) { - struct threadparam *tp = arg; + struct threadparam *tp = (struct threadparam *)arg; int n, sent, stop = 0; DWORD k; char buf[IOBUF_SIZE]; @@ -1557,8 +1896,8 @@ static void *push_to_stdin(void *arg) { } static void *pull_from_stdout(void *arg) { - struct threadparam *tp = arg; - int k, stop = 0; + struct threadparam *tp = (struct threadparam *)arg; + int k = 0, stop = 0; DWORD n, sent; char buf[IOBUF_SIZE]; @@ -1579,7 +1918,7 @@ static void *pull_from_stdout(void *arg) { static void spawn_stdio_thread(sock_t sock, HANDLE hPipe, void *(*func)(void *)) { - struct threadparam *tp = malloc(sizeof(*tp)); + struct threadparam *tp = (struct threadparam *)malloc(sizeof(*tp)); if (tp != NULL) { tp->s = sock; tp->hPipe = hPipe; @@ -1594,10 +1933,11 @@ static void abs_path(const char *utf8_path, char *abs_path, size_t len) { WideCharToMultiByte(CP_UTF8, 0, buf2, wcslen(buf2) + 1, abs_path, len, 0, 0); } -static pid_t start_process(char *interp, const char *cmd, const char *env, - const char *envp[], const char *dir, sock_t sock) { - STARTUPINFOW si = {0}; - PROCESS_INFORMATION pi = {0}; +static process_id_t start_process(char *interp, const char *cmd, + const char *env, const char *envp[], + const char *dir, sock_t sock) { + STARTUPINFOW si; + PROCESS_INFORMATION pi; HANDLE a[2], b[2], me = GetCurrentProcess(); wchar_t wcmd[MAX_PATH_SIZE], full_dir[MAX_PATH_SIZE]; char buf[MAX_PATH_SIZE], buf4[MAX_PATH_SIZE], buf5[MAX_PATH_SIZE], @@ -1605,6 +1945,9 @@ static pid_t start_process(char *interp, const char *cmd, const char *env, DWORD flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS; FILE *fp; + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); + si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; @@ -1621,7 +1964,7 @@ static pid_t start_process(char *interp, const char *cmd, const char *env, buf[sizeof(buf) - 1] = '\0'; if (buf[0] == '#' && buf[1] == '!') { interp = buf + 2; - for (p = interp + strlen(interp); + for (p = interp + strlen(interp) - 1; isspace(* (uint8_t *) p) && p > interp; p--) *p = '\0'; } fclose(fp); @@ -1648,20 +1991,20 @@ static pid_t start_process(char *interp, const char *cmd, const char *env, } DBG(("CGI command: [%ls] -> %p", wcmd, pi.hProcess)); + // Not closing a[0] and b[1] because we've used DUPLICATE_CLOSE_SOURCE CloseHandle(si.hStdOutput); CloseHandle(si.hStdInput); - CloseHandle(a[0]); - CloseHandle(b[1]); - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); + //CloseHandle(pi.hThread); + //CloseHandle(pi.hProcess); - return (pid_t) pi.hProcess; + return pi.hProcess; } #else -static pid_t start_process(const char *interp, const char *cmd, const char *env, - const char *envp[], const char *dir, sock_t sock) { +static process_id_t start_process(const char *interp, const char *cmd, + const char *env, const char *envp[], + const char *dir, sock_t sock) { char buf[500]; - pid_t pid = fork(); + process_id_t pid = fork(); (void) env; if (pid == 0) { @@ -1677,9 +2020,9 @@ static pid_t start_process(const char *interp, const char *cmd, const char *env, signal(SIGCHLD, SIG_DFL); if (interp == NULL) { - execle(cmd, cmd, NULL, envp); + execle(cmd, cmd, (char *) 0, envp); // Using (char *) 0 to avoid warning } else { - execle(interp, interp, cmd, NULL, envp); + execle(interp, interp, cmd, (char *) 0, envp); } snprintf(buf, sizeof(buf), "Status: 500\r\n\r\n" "500 Server Error: %s%s%s: %s", interp == NULL ? "" : interp, @@ -1758,7 +2101,7 @@ static void prepare_cgi_environment(struct connection *conn, if ((s = getenv("SERVER_NAME")) != NULL) { addenv(blk, "SERVER_NAME=%s", s); } else { - addenv(blk, "SERVER_NAME=%s", conn->server->local_ip); + addenv(blk, "SERVER_NAME=%s", ri->local_ip); } addenv(blk, "SERVER_ROOT=%s", opts[DOCUMENT_ROOT]); addenv(blk, "DOCUMENT_ROOT=%s", opts[DOCUMENT_ROOT]); @@ -1870,14 +2213,17 @@ static void open_cgi_endpoint(struct connection *conn, const char *prog) { } while (fds[0] == INVALID_SOCKET); if (start_process(conn->server->config_options[CGI_INTERPRETER], - prog, blk.buf, blk.vars, dir, fds[1]) > 0) { + prog, blk.buf, blk.vars, dir, fds[1]) != 0) { conn->endpoint_type = EP_CGI; - conn->endpoint.cgi_conn = ns_add_sock(&conn->server->ns_server, - fds[0], conn); - conn->endpoint.cgi_conn->flags |= MG_CGI_CONN; + conn->endpoint.nc = ns_add_sock(&conn->server->ns_mgr, fds[0], + mg_ev_handler, conn); + conn->endpoint.nc->flags |= MG_CGI_CONN; ns_send(conn->ns_conn, cgi_status, sizeof(cgi_status) - 1); conn->mg_conn.status_code = 200; conn->ns_conn->flags |= NSF_BUFFER_BUT_DONT_SEND; + // Pass POST data to the CGI process + conn->endpoint.nc->send_iobuf = conn->ns_conn->recv_iobuf; + iobuf_init(&conn->ns_conn->recv_iobuf, 0); } else { closesocket(fds[0]); send_http_error(conn, 500, "start_process(%s) failed", prog); @@ -1889,15 +2235,14 @@ static void open_cgi_endpoint(struct connection *conn, const char *prog) { } static void on_cgi_data(struct ns_connection *nc) { - struct connection *conn = (struct connection *) nc->connection_data; + struct connection *conn = (struct connection *) nc->user_data; const char *status = "500"; struct mg_connection c; if (!conn) return; // Copy CGI data from CGI socket to the client send buffer - ns_send(conn->ns_conn, nc->recv_iobuf.buf, nc->recv_iobuf.len); - iobuf_remove(&nc->recv_iobuf, nc->recv_iobuf.len); + ns_forward(nc, conn->ns_conn); // If reply has not been parsed yet, parse it if (conn->ns_conn->flags & NSF_BUFFER_BUT_DONT_SEND) { @@ -1909,8 +2254,10 @@ static void on_cgi_data(struct ns_connection *nc) { if (len == 0) return; if (len < 0 || len > (int) sizeof(buf)) { + len = io->len; iobuf_remove(io, io->len); - send_http_error(conn, 500, "%s", "CGI program sent malformed headers"); + send_http_error(conn, 500, "CGI program sent malformed headers: [%.*s]", + len, io->buf); } else { memset(&c, 0, sizeof(c)); memcpy(buf, io->buf + s_len, len); @@ -1927,14 +2274,6 @@ static void on_cgi_data(struct ns_connection *nc) { conn->ns_conn->flags &= ~NSF_BUFFER_BUT_DONT_SEND; } } - -static void forward_post_data(struct connection *conn) { - struct iobuf *io = &conn->ns_conn->recv_iobuf; - if (conn->endpoint.cgi_conn != NULL) { - ns_send(conn->endpoint.cgi_conn, io->buf, io->len); - iobuf_remove(io, io->len); - } -} #endif // !MONGOOSE_NO_CGI static char *mg_strdup(const char *str) { @@ -1989,21 +2328,6 @@ static int check_acl(const char *acl, uint32_t remote_ip) { return allowed == '+'; } -static void sockaddr_to_string(char *buf, size_t len, - const union socket_address *usa) { - buf[0] = '\0'; -#if defined(NS_ENABLE_IPV6) - inet_ntop(usa->sa.sa_family, usa->sa.sa_family == AF_INET ? - (void *) &usa->sin.sin_addr : - (void *) &usa->sin6.sin6_addr, buf, len); -#elif defined(_WIN32) - // Only Windoze Vista (and newer) have inet_ntop() - strncpy(buf, inet_ntoa(usa->sin.sin_addr), len); -#else - inet_ntop(usa->sa.sa_family, (void *) &usa->sin.sin_addr, buf, len); -#endif -} - // Protect against directory disclosure attack by removing '..', // excessive '/' and '\' characters static void remove_double_dots_and_double_slashes(char *s) { @@ -2048,12 +2372,10 @@ int mg_url_decode(const char *src, int src_len, char *dst, return i >= src_len ? j : -1; } -static int is_valid_http_method(const char *method) { - return !strcmp(method, "GET") || !strcmp(method, "POST") || - !strcmp(method, "HEAD") || !strcmp(method, "CONNECT") || - !strcmp(method, "PUT") || !strcmp(method, "DELETE") || - !strcmp(method, "OPTIONS") || !strcmp(method, "PROPFIND") - || !strcmp(method, "MKCOL"); +static int is_valid_http_method(const char *s) { + return !strcmp(s, "GET") || !strcmp(s, "POST") || !strcmp(s, "HEAD") || + !strcmp(s, "CONNECT") || !strcmp(s, "PUT") || !strcmp(s, "DELETE") || + !strcmp(s, "OPTIONS") || !strcmp(s, "PROPFIND") || !strcmp(s, "MKCOL"); } // Parse HTTP request, fill in mg_request structure. @@ -2087,6 +2409,8 @@ static int parse_http_message(char *buf, int len, struct mg_connection *ri) { } else { if (is_request) { ri->http_version += 5; + } else { + ri->status_code = atoi(ri->uri); } parse_http_headers(&buf, ri); @@ -2095,7 +2419,9 @@ static int parse_http_message(char *buf, int len, struct mg_connection *ri) { } n = (int) strlen(ri->uri); mg_url_decode(ri->uri, n, (char *) ri->uri, n + 1, 0); - remove_double_dots_and_double_slashes((char *) ri->uri); + if (*ri->uri == '/' || *ri->uri == '.') { + remove_double_dots_and_double_slashes((char *) ri->uri); + } } return len; @@ -2137,16 +2463,15 @@ const char *mg_get_header(const struct mg_connection *ri, const char *s) { return NULL; } -#ifndef MONGOOSE_NO_FILESYSTEM // Perform case-insensitive match of string against pattern -static int match_prefix(const char *pattern, int pattern_len, const char *str) { +int mg_match_prefix(const char *pattern, int pattern_len, const char *str) { const char *or_str; int len, res, i = 0, j = 0; if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) { - res = match_prefix(pattern, or_str - pattern, str); - return res > 0 ? res : - match_prefix(or_str + 1, (pattern + pattern_len) - (or_str + 1), str); + res = mg_match_prefix(pattern, or_str - pattern, str); + return res > 0 ? res : mg_match_prefix(or_str + 1, + (pattern + pattern_len) - (or_str + 1), str); } for (; i < pattern_len; i++, j++) { @@ -2166,7 +2491,7 @@ static int match_prefix(const char *pattern, int pattern_len, const char *str) { return j + len; } do { - res = match_prefix(pattern + i, pattern_len - i, str + j + len); + res = mg_match_prefix(pattern + i, pattern_len - i, str + j + len); } while (res == -1 && len-- > 0); return res == -1 ? -1 : j + res + len; } else if (lowercase(&pattern[i]) != lowercase(&str[j])) { @@ -2176,11 +2501,46 @@ static int match_prefix(const char *pattern, int pattern_len, const char *str) { return j; } +// This function prints HTML pages, and expands "{{something}}" blocks +// inside HTML by calling appropriate callback functions. +// Note that {{@path/to/file}} construct outputs embedded file's contents, +// which provides SSI-like functionality. +void mg_template(struct mg_connection *conn, const char *s, + struct mg_expansion *expansions) { + int i, j, pos = 0, inside_marker = 0; + + for (i = 0; s[i] != '\0'; i++) { + if (inside_marker == 0 && !memcmp(&s[i], "{{", 2)) { + if (i > pos) { + mg_send_data(conn, &s[pos], i - pos); + } + pos = i; + inside_marker = 1; + } + if (inside_marker == 1 && !memcmp(&s[i], "}}", 2)) { + for (j = 0; expansions[j].keyword != NULL; j++) { + const char *kw = expansions[j].keyword; + if ((int) strlen(kw) == i - (pos + 2) && + memcmp(kw, &s[pos + 2], i - (pos + 2)) == 0) { + expansions[j].handler(conn); + pos = i + 2; + break; + } + } + inside_marker = 0; + } + } + if (i > pos) { + mg_send_data(conn, &s[pos], i - pos); + } +} + +#ifndef MONGOOSE_NO_FILESYSTEM static int must_hide_file(struct connection *conn, const char *path) { const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$"; const char *pattern = conn->server->config_options[HIDE_FILES_PATTERN]; - return match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 || - (pattern != NULL && match_prefix(pattern, strlen(pattern), path) > 0); + return mg_match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 || + (pattern != NULL && mg_match_prefix(pattern, strlen(pattern), path) > 0); } // Return 1 if real file has been found, 0 otherwise @@ -2219,7 +2579,7 @@ static int convert_uri_to_file_name(struct connection *conn, char *buf, mg_snprintf(buf, buf_len, "%.*s%s", root_len, root, uri); rewrites = conn->server->config_options[URL_REWRITES]; // Re-initialize! while ((rewrites = next_option(rewrites, &a, &b)) != NULL) { - if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) { + if ((match_len = mg_match_prefix(a.ptr, a.len, uri)) > 0) { mg_snprintf(buf, buf_len, "%.*s%s", (int) b.len, b.ptr, uri + match_len); break; } @@ -2232,7 +2592,8 @@ static int convert_uri_to_file_name(struct connection *conn, char *buf, for (p = buf + strlen(root) + 2; *p != '\0'; p++) { if (*p == '/') { *p = '\0'; - if (match_prefix(cgi_pat, strlen(cgi_pat), buf) > 0 && !stat(buf, st)) { + if (mg_match_prefix(cgi_pat, strlen(cgi_pat), buf) > 0 && + !stat(buf, st)) { DBG(("!!!! [%s]", buf)); *p = '/'; conn->path_info = mg_strdup(p); @@ -2259,9 +2620,10 @@ static int should_keep_alive(const struct mg_connection *conn) { (header == NULL && http_version && !strcmp(http_version, "1.1"))); } -int mg_write(struct mg_connection *c, const void *buf, int len) { +size_t mg_write(struct mg_connection *c, const void *buf, int len) { struct connection *conn = MG_CONN_2_CONN(c); - return ns_send(conn->ns_conn, buf, len); + ns_send(conn->ns_conn, buf, len); + return conn->ns_conn->send_iobuf.len; } void mg_send_status(struct mg_connection *c, int status) { @@ -2288,12 +2650,14 @@ static void terminate_headers(struct mg_connection *c) { } } -void mg_send_data(struct mg_connection *c, const void *data, int data_len) { +size_t mg_send_data(struct mg_connection *c, const void *data, int data_len) { + struct connection *conn = MG_CONN_2_CONN(c); terminate_headers(c); write_chunk(MG_CONN_2_CONN(c), (const char *) data, data_len); + return conn->ns_conn->send_iobuf.len; } -void mg_printf_data(struct mg_connection *c, const char *fmt, ...) { +size_t mg_printf_data(struct mg_connection *c, const char *fmt, ...) { struct connection *conn = MG_CONN_2_CONN(c); va_list ap; int len; @@ -2305,12 +2669,13 @@ void mg_printf_data(struct mg_connection *c, const char *fmt, ...) { len = ns_avprintf(&buf, sizeof(mem), fmt, ap); va_end(ap); - if (len > 0) { + if (len >= 0) { write_chunk((struct connection *) conn, buf, len); } if (buf != mem && buf != NULL) { free(buf); } + return conn->ns_conn->send_iobuf.len; } #if !defined(MONGOOSE_NO_WEBSOCKET) || !defined(MONGOOSE_NO_AUTH) @@ -2341,6 +2706,14 @@ static uint32_t blk0(union char64long16 *block, int i) { return block->l[i]; } +/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */ +#undef blk +#undef R0 +#undef R1 +#undef R2 +#undef R3 +#undef R4 + #define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ ^block->l[(i+2)&15]^block->l[i&15],1)) #define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(block, i)+0x5A827999+rol(v,5);w=rol(w,30); @@ -2397,7 +2770,7 @@ static void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) { (void) a; (void) b; (void) c; (void) d; (void) e; } -static void SHA1Init(SHA1_CTX* context) { +static void SHA1Init(SHA1_CTX *context) { context->state[0] = 0x67452301; context->state[1] = 0xEFCDAB89; context->state[2] = 0x98BADCFE; @@ -2406,7 +2779,7 @@ static void SHA1Init(SHA1_CTX* context) { context->count[0] = context->count[1] = 0; } -static void SHA1Update(SHA1_CTX* context, const unsigned char* data, +static void SHA1Update(SHA1_CTX *context, const unsigned char *data, uint32_t len) { uint32_t i, j; @@ -2427,7 +2800,7 @@ static void SHA1Update(SHA1_CTX* context, const unsigned char* data, memcpy(&context->buffer[j], &data[i], len - i); } -static void SHA1Final(unsigned char digest[20], SHA1_CTX* context) { +static void SHA1Final(unsigned char digest[20], SHA1_CTX *context) { unsigned i; unsigned char finalcount[8], c; @@ -2543,14 +2916,14 @@ static int deliver_websocket_frame(struct connection *conn) { return buffered; } -int mg_websocket_write(struct mg_connection* conn, int opcode, +size_t mg_websocket_write(struct mg_connection *conn, int opcode, const char *data, size_t data_len) { - unsigned char *copy; + unsigned char mem[4192], *copy = mem; size_t copy_len = 0; - int retval = -1; - if ((copy = (unsigned char *) malloc(data_len + 10)) == NULL) { - return -1; + if (data_len + 10 > sizeof(mem) && + (copy = (unsigned char *) malloc(data_len + 10)) == NULL) { + return 0; } copy[0] = 0x80 + (opcode & 0x0f); @@ -2578,11 +2951,38 @@ int mg_websocket_write(struct mg_connection* conn, int opcode, } if (copy_len > 0) { - retval = mg_write(conn, copy, copy_len); + mg_write(conn, copy, copy_len); + } + if (copy != mem) { + free(copy); } - free(copy); - return retval; + // If we send closing frame, schedule a connection to be closed after + // data is drained to the client. + if (opcode == WEBSOCKET_OPCODE_CONNECTION_CLOSE) { + MG_CONN_2_CONN(conn)->ns_conn->flags |= NSF_FINISHED_SENDING_DATA; + } + + return MG_CONN_2_CONN(conn)->ns_conn->send_iobuf.len; +} + +size_t mg_websocket_printf(struct mg_connection *conn, int opcode, + const char *fmt, ...) { + char mem[4192], *buf = mem; + va_list ap; + int len; + + va_start(ap, fmt); + if ((len = ns_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) { + mg_websocket_write(conn, opcode, buf, len); + } + va_end(ap); + + if (buf != mem && buf != NULL) { + free(buf); + } + + return MG_CONN_2_CONN(conn)->ns_conn->send_iobuf.len; } static void send_websocket_handshake_if_requested(struct mg_connection *conn) { @@ -2590,13 +2990,16 @@ static void send_websocket_handshake_if_requested(struct mg_connection *conn) { *key = mg_get_header(conn, "Sec-WebSocket-Key"); if (ver != NULL && key != NULL) { conn->is_websocket = 1; - send_websocket_handshake(conn, key); + if (call_user(MG_CONN_2_CONN(conn), MG_WS_HANDSHAKE) == MG_FALSE) { + send_websocket_handshake(conn, key); + } + call_user(MG_CONN_2_CONN(conn), MG_WS_CONNECT); } } static void ping_idle_websocket_connection(struct connection *conn, time_t t) { if (t - conn->ns_conn->last_io_time > MONGOOSE_USE_WEBSOCKET_PING_INTERVAL) { - mg_websocket_write(&conn->mg_conn, 0x9, "", 0); + mg_websocket_write(&conn->mg_conn, WEBSOCKET_OPCODE_PING, "", 0); } } #else @@ -2788,8 +3191,8 @@ static void gmt_time_string(char *buf, size_t buf_len, time_t *t) { } static void open_file_endpoint(struct connection *conn, const char *path, - file_stat_t *st) { - char date[64], lm[64], etag[64], range[64], headers[500]; + file_stat_t *st, const char *extra_headers) { + char date[64], lm[64], etag[64], range[64], headers[1000]; const char *msg = "OK", *hdr; time_t curtime = time(NULL); int64_t r1, r2; @@ -2833,11 +3236,12 @@ static void open_file_endpoint(struct connection *conn, const char *path, "Content-Length: %" INT64_FMT "\r\n" "Connection: %s\r\n" "Accept-Ranges: bytes\r\n" - "%s%s\r\n", + "%s%s%s\r\n", conn->mg_conn.status_code, msg, date, lm, etag, (int) mime_vec.len, mime_vec.ptr, conn->cl, suggest_connection_header(&conn->mg_conn), - range, MONGOOSE_USE_EXTRA_HTTP_HEADERS); + range, extra_headers == NULL ? "" : extra_headers, + MONGOOSE_USE_EXTRA_HTTP_HEADERS); ns_send(conn->ns_conn, headers, n); if (!strcmp(conn->mg_conn.request_method, "HEAD")) { @@ -2846,18 +3250,22 @@ static void open_file_endpoint(struct connection *conn, const char *path, conn->endpoint_type = EP_NONE; } } + +void mg_send_file_data(struct mg_connection *c, int fd) { + struct connection *conn = MG_CONN_2_CONN(c); + conn->endpoint_type = EP_FILE; + conn->endpoint.fd = fd; + ns_set_close_on_exec(conn->endpoint.fd); +} #endif // MONGOOSE_NO_FILESYSTEM static void call_request_handler_if_data_is_buffered(struct connection *conn) { - struct iobuf *loc = &conn->ns_conn->recv_iobuf; - struct mg_connection *c = &conn->mg_conn; - #ifndef MONGOOSE_NO_WEBSOCKET if (conn->mg_conn.is_websocket) { do { } while (deliver_websocket_frame(conn)); } else #endif - if ((size_t) loc->len >= c->content_len && + if (conn->num_bytes_recv >= (conn->cl + conn->request_len) && call_request_handler(conn) == MG_FALSE) { open_local_endpoint(conn, 1); } @@ -2991,24 +3399,25 @@ static int scan_directory(struct connection *conn, const char *dir, return arr_ind; } -static void mg_url_encode(const char *src, char *dst, size_t dst_len) { +int mg_url_encode(const char *src, size_t s_len, char *dst, size_t dst_len) { static const char *dont_escape = "._-$,;~()"; static const char *hex = "0123456789abcdef"; - const char *end = dst + dst_len - 1; - - for (; *src != '\0' && dst < end; src++, dst++) { - if (isalnum(*(const unsigned char *) src) || - strchr(dont_escape, * (const unsigned char *) src) != NULL) { - *dst = *src; - } else if (dst + 2 < end) { - dst[0] = '%'; - dst[1] = hex[(* (const unsigned char *) src) >> 4]; - dst[2] = hex[(* (const unsigned char *) src) & 0xf]; - dst += 2; + size_t i = 0, j = 0; + + for (i = j = 0; dst_len > 0 && i < s_len && j + 2 < dst_len - 1; i++, j++) { + if (isalnum(* (const unsigned char *) (src + i)) || + strchr(dont_escape, * (const unsigned char *) (src + i)) != NULL) { + dst[j] = src[i]; + } else if (j + 3 < dst_len) { + dst[j] = '%'; + dst[j + 1] = hex[(* (const unsigned char *) (src + i)) >> 4]; + dst[j + 2] = hex[(* (const unsigned char *) (src + i)) & 0xf]; + j += 2; } } - *dst = '\0'; + dst[j] = '\0'; + return j; } #endif // !NO_DIRECTORY_LISTING || !MONGOOSE_NO_DAV @@ -3036,12 +3445,11 @@ static void print_dir_entry(const struct dir_entry *de) { } } strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.st_mtime)); - mg_url_encode(de->file_name, href, sizeof(href)); + mg_url_encode(de->file_name, strlen(de->file_name), href, sizeof(href)); mg_printf_data(&de->conn->mg_conn, - "%s%s" + "%s%s" " %s  %s\n", - de->conn->mg_conn.uri, href, slash, de->file_name, slash, - mod, size); + href, slash, de->file_name, slash, mod, size); } // Sort directory entries by size, or name, or modification time. @@ -3126,40 +3534,51 @@ static void print_props(struct connection *conn, const char *uri, } static void handle_propfind(struct connection *conn, const char *path, - file_stat_t *stp) { + file_stat_t *stp, int exists) { static const char header[] = "HTTP/1.1 207 Multi-Status\r\n" "Connection: close\r\n" "Content-Type: text/xml; charset=utf-8\r\n\r\n" "" "\n"; static const char footer[] = ""; - const char *depth = mg_get_header(&conn->mg_conn, "Depth"), - *list_dir = conn->server->config_options[ENABLE_DIRECTORY_LISTING]; + const char *depth = mg_get_header(&conn->mg_conn, "Depth"); +#ifdef MONGOOSE_NO_DIRECTORY_LISTING + const char *list_dir = "no"; +#else + const char *list_dir = conn->server->config_options[ENABLE_DIRECTORY_LISTING]; +#endif conn->mg_conn.status_code = 207; - ns_send(conn->ns_conn, header, sizeof(header) - 1); // Print properties for the requested resource itself - print_props(conn, conn->mg_conn.uri, stp); - - // If it is a directory, print directory entries too if Depth is not 0 - if (S_ISDIR(stp->st_mode) && !mg_strcasecmp(list_dir, "yes") && - (depth == NULL || strcmp(depth, "0") != 0)) { - struct dir_entry *arr = NULL; - int i, num_entries = scan_directory(conn, path, &arr); - - for (i = 0; i < num_entries; i++) { - char buf[MAX_PATH_SIZE], buf2[sizeof(buf) * 3]; - struct dir_entry *de = &arr[i]; - - mg_snprintf(buf, sizeof(buf), "%s%s", de->conn->mg_conn.uri, - de->file_name); - mg_url_encode(buf, buf2, sizeof(buf2) - 1); - print_props(conn, buf, &de->st); + if (!exists) { + conn->mg_conn.status_code = 404; + mg_printf(&conn->mg_conn, "%s", "HTTP/1.1 404 Not Found\r\n\r\n"); + } else if (S_ISDIR(stp->st_mode) && mg_strcasecmp(list_dir, "yes") != 0) { + conn->mg_conn.status_code = 403; + mg_printf(&conn->mg_conn, "%s", + "HTTP/1.1 403 Directory Listing Denied\r\n\r\n"); + } else { + ns_send(conn->ns_conn, header, sizeof(header) - 1); + print_props(conn, conn->mg_conn.uri, stp); + + if (S_ISDIR(stp->st_mode) && + (depth == NULL || strcmp(depth, "0") != 0)) { + struct dir_entry *arr = NULL; + int i, num_entries = scan_directory(conn, path, &arr); + + for (i = 0; i < num_entries; i++) { + char buf[MAX_PATH_SIZE * 3]; + struct dir_entry *de = &arr[i]; + mg_url_encode(de->file_name, strlen(de->file_name), buf, sizeof(buf)); + print_props(conn, buf, &de->st); + free(de->file_name); + } + free(arr); } + ns_send(conn->ns_conn, footer, sizeof(footer) - 1); } - ns_send(conn->ns_conn, footer, sizeof(footer) - 1); close_local_endpoint(conn); } @@ -3212,7 +3631,7 @@ static void handle_delete(struct connection *conn, const char *path) { } else if (S_ISDIR(st.st_mode)) { remove_directory(path); send_http_error(conn, 204, NULL); - } else if (!remove(path) == 0) { + } else if (remove(path) == 0) { send_http_error(conn, 204, NULL); } else { send_http_error(conn, 423, NULL); @@ -3254,17 +3673,11 @@ static void handle_put(struct connection *conn, const char *path) { send_http_error(conn, 500, "put_dir: %s", strerror(errno)); } else if (cl_hdr == NULL) { send_http_error(conn, 411, NULL); -#ifdef _WIN32 - //On Windows, open() is a macro with 2 params - } else if ((conn->endpoint.fd = - open(path, O_RDWR | O_CREAT | O_TRUNC)) < 0) { -#else } else if ((conn->endpoint.fd = - open(path, O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) { -#endif + open(path, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0644)) < 0) { send_http_error(conn, 500, "open(%s): %s", path, strerror(errno)); } else { - DBG(("PUT [%s] %d", path, conn->ns_conn->recv_iobuf.len)); + DBG(("PUT [%s] %lu", path, (unsigned long) conn->ns_conn->recv_iobuf.len)); conn->endpoint_type = EP_PUT; ns_set_close_on_exec(conn->endpoint.fd); range = mg_get_header(&conn->mg_conn, "Content-Range"); @@ -3282,22 +3695,24 @@ static void handle_put(struct connection *conn, const char *path) { static void forward_put_data(struct connection *conn) { struct iobuf *io = &conn->ns_conn->recv_iobuf; - int n = write(conn->endpoint.fd, io->buf, io->len); + size_t k = conn->cl < (int64_t) io->len ? conn->cl : (int64_t) io->len; // To write + int n = write(conn->endpoint.fd, io->buf, k); // Write them! if (n > 0) { iobuf_remove(io, n); conn->cl -= n; - if (conn->cl <= 0) { - close_local_endpoint(conn); - } + } + if (conn->cl <= 0) { + close_local_endpoint(conn); } } #endif // MONGOOSE_NO_DAV static void send_options(struct connection *conn) { - static const char reply[] = "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, " - "CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL\r\nDAV: 1\r\n\r\n"; - ns_send(conn->ns_conn, reply, sizeof(reply) - 1); - conn->ns_conn->flags |= NSF_FINISHED_SENDING_DATA; + conn->mg_conn.status_code = 200; + mg_printf(&conn->mg_conn, "%s", + "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, CONNECT, PUT, " + "DELETE, OPTIONS, PROPFIND, MKCOL\r\nDAV: 1\r\n\r\n"); + close_local_endpoint(conn); } #ifndef MONGOOSE_NO_AUTH @@ -3315,16 +3730,16 @@ void mg_send_digest_auth_request(struct mg_connection *c) { // Use the global passwords file, if specified by auth_gpass option, // or search for .htpasswd in the requested directory. -static FILE *open_auth_file(struct connection *conn, const char *path) { +static FILE *open_auth_file(struct connection *conn, const char *path, + int is_directory) { char name[MAX_PATH_SIZE]; const char *p, *gpass = conn->server->config_options[GLOBAL_AUTH_FILE]; - file_stat_t st; FILE *fp = NULL; if (gpass != NULL) { // Use global passwords file fp = fopen(gpass, "r"); - } else if (!stat(path, &st) && S_ISDIR(st.st_mode)) { + } else if (is_directory) { mg_snprintf(name, sizeof(name), "%s%c%s", path, '/', PASSWORDS_FILE_NAME); fp = fopen(name, "r"); } else { @@ -3614,11 +4029,12 @@ int mg_authorize_digest(struct mg_connection *c, FILE *fp) { // Return 1 if request is authorised, 0 otherwise. -static int is_authorized(struct connection *conn, const char *path) { +static int is_authorized(struct connection *conn, const char *path, + int is_directory) { FILE *fp; int authorized = MG_TRUE; - if ((fp = open_auth_file(conn, path)) != NULL) { + if ((fp = open_auth_file(conn, path, is_directory)) != NULL) { authorized = mg_authorize_digest(&conn->mg_conn, fp); fclose(fp); } @@ -3628,10 +4044,14 @@ static int is_authorized(struct connection *conn, const char *path) { static int is_authorized_for_dav(struct connection *conn) { const char *auth_file = conn->server->config_options[DAV_AUTH_FILE]; + const char *method = conn->mg_conn.request_method; FILE *fp; int authorized = MG_FALSE; - if (auth_file != NULL && (fp = fopen(auth_file, "r")) != NULL) { + // If dav_auth_file is not set, allow non-authorized PROPFIND + if (method != NULL && !strcmp(method, "PROPFIND") && auth_file == NULL) { + authorized = MG_TRUE; + } else if (auth_file != NULL && (fp = fopen(auth_file, "r")) != NULL) { authorized = mg_authorize_digest(&conn->mg_conn, fp); fclose(fp); } @@ -3639,10 +4059,10 @@ static int is_authorized_for_dav(struct connection *conn) { return authorized; } -static int is_dav_mutation(const struct connection *conn) { +static int is_dav_request(const struct connection *conn) { const char *s = conn->mg_conn.request_method; - return s && (!strcmp(s, "PUT") || !strcmp(s, "DELETE") || - !strcmp(s, "MKCOL")); + return !strcmp(s, "PUT") || !strcmp(s, "DELETE") || + !strcmp(s, "MKCOL") || !strcmp(s, "PROPFIND"); } #endif // MONGOOSE_NO_AUTH @@ -3684,274 +4104,6 @@ int mg_parse_header(const char *s, const char *var_name, char *buf, return parse_header(s, s == NULL ? 0 : strlen(s), var_name, buf, buf_size); } -#ifdef MONGOOSE_USE_LUA -#ifdef _WIN32 -static void *mmap(void *addr, int64_t len, int prot, int flags, int fd, - int offset) { - HANDLE fh = (HANDLE) _get_osfhandle(fd); - HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0); - void *p = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, (size_t) len); - CloseHandle(mh); - return p; -} -#define munmap(x, y) UnmapViewOfFile(x) -#define MAP_FAILED NULL -#define MAP_PRIVATE 0 -#define PROT_READ 0 -#else -#include -#endif - -void reg_string(struct lua_State *L, const char *name, const char *val) { - lua_pushstring(L, name); - lua_pushstring(L, val); - lua_rawset(L, -3); -} - -void reg_int(struct lua_State *L, const char *name, int val) { - lua_pushstring(L, name); - lua_pushinteger(L, val); - lua_rawset(L, -3); -} - -void reg_function(struct lua_State *L, const char *name, - lua_CFunction func, struct mg_connection *conn) { - lua_pushstring(L, name); - lua_pushlightuserdata(L, conn); - lua_pushcclosure(L, func, 1); - lua_rawset(L, -3); -} - -static int lua_write(lua_State *L) { - int i, num_args; - const char *str; - size_t size; - struct mg_connection *conn = (struct mg_connection *) - lua_touserdata(L, lua_upvalueindex(1)); - - num_args = lua_gettop(L); - for (i = 1; i <= num_args; i++) { - if (lua_isstring(L, i)) { - str = lua_tolstring(L, i, &size); - mg_write(conn, str, size); - } - } - - return 0; -} - -static int lsp_sock_close(lua_State *L) { - if (lua_gettop(L) > 0 && lua_istable(L, -1)) { - lua_getfield(L, -1, "sock"); - closesocket((sock_t) lua_tonumber(L, -1)); - } else { - return luaL_error(L, "invalid :close() call"); - } - return 1; -} - -static int lsp_sock_recv(lua_State *L) { - char buf[2000]; - int n; - - if (lua_gettop(L) > 0 && lua_istable(L, -1)) { - lua_getfield(L, -1, "sock"); - n = recv((sock_t) lua_tonumber(L, -1), buf, sizeof(buf), 0); - if (n <= 0) { - lua_pushnil(L); - } else { - lua_pushlstring(L, buf, n); - } - } else { - return luaL_error(L, "invalid :close() call"); - } - return 1; -} - -static int lsp_sock_send(lua_State *L) { - const char *buf; - size_t len, sent = 0; - int n, sock; - - if (lua_gettop(L) > 1 && lua_istable(L, -2) && lua_isstring(L, -1)) { - buf = lua_tolstring(L, -1, &len); - lua_getfield(L, -2, "sock"); - sock = (int) lua_tonumber(L, -1); - while (sent < len) { - if ((n = send(sock, buf + sent, len - sent, 0)) <= 0) break; - sent += n; - } - lua_pushnumber(L, sent); - } else { - return luaL_error(L, "invalid :close() call"); - } - return 1; -} - -static const struct luaL_Reg luasocket_methods[] = { - {"close", lsp_sock_close}, - {"send", lsp_sock_send}, - {"recv", lsp_sock_recv}, - {NULL, NULL} -}; - -static sock_t conn2(const char *host, int port) { - struct sockaddr_in sin; - struct hostent *he = NULL; - sock_t sock = INVALID_SOCKET; - - if (host != NULL && - (he = gethostbyname(host)) != NULL && - (sock = socket(AF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET) { - ns_set_close_on_exec(sock); - sin.sin_family = AF_INET; - sin.sin_port = htons((uint16_t) port); - sin.sin_addr = * (struct in_addr *) he->h_addr_list[0]; - if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) { - closesocket(sock); - sock = INVALID_SOCKET; - } - } - return sock; -} - -static int lsp_connect(lua_State *L) { - sock_t sock; - - if (lua_isstring(L, -2) && lua_isnumber(L, -1)) { - sock = conn2(lua_tostring(L, -2), (int) lua_tonumber(L, -1)); - if (sock == INVALID_SOCKET) { - lua_pushnil(L); - } else { - lua_newtable(L); - reg_int(L, "sock", sock); - reg_string(L, "host", lua_tostring(L, -4)); - luaL_getmetatable(L, "luasocket"); - lua_setmetatable(L, -2); - } - } else { - return luaL_error(L, "connect(host,port): invalid parameter given."); - } - return 1; -} - -static void prepare_lua_environment(struct mg_connection *ri, lua_State *L) { - extern void luaL_openlibs(lua_State *); - int i; - - luaL_openlibs(L); - - luaL_newmetatable(L, "luasocket"); - lua_newtable(L); - luaL_newlib(L, luasocket_methods); - lua_rawset(L, -3); - lua_pop(L, 1); - lua_register(L, "connect", lsp_connect); - - if (ri == NULL) return; - - // Register mg module - lua_newtable(L); - reg_function(L, "write", lua_write, ri); - - // Export request_info - lua_pushstring(L, "request_info"); - lua_newtable(L); - reg_string(L, "request_method", ri->request_method); - reg_string(L, "uri", ri->uri); - reg_string(L, "http_version", ri->http_version); - reg_string(L, "query_string", ri->query_string); - reg_string(L, "remote_ip", ri->remote_ip); - reg_int(L, "remote_port", ri->remote_port); - reg_string(L, "local_ip", ri->local_ip); - reg_int(L, "local_port", ri->local_port); - lua_pushstring(L, "content"); - lua_pushlstring(L, ri->content == NULL ? "" : ri->content, ri->content_len); - lua_rawset(L, -3); - reg_int(L, "num_headers", ri->num_headers); - lua_pushstring(L, "http_headers"); - lua_newtable(L); - for (i = 0; i < ri->num_headers; i++) { - reg_string(L, ri->http_headers[i].name, ri->http_headers[i].value); - } - lua_rawset(L, -3); - lua_rawset(L, -3); - - lua_setglobal(L, "mg"); - - // Register default mg.onerror function - (void) luaL_dostring(L, "mg.onerror = function(e) mg.write('\\nLua " - "error:\\n', debug.traceback(e, 1)) end"); -} - -static int lua_error_handler(lua_State *L) { - const char *error_msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "?\n"; - - lua_getglobal(L, "mg"); - if (!lua_isnil(L, -1)) { - lua_getfield(L, -1, "write"); // call mg.write() - lua_pushstring(L, error_msg); - lua_pushliteral(L, "\n"); - lua_call(L, 2, 0); - (void) luaL_dostring(L, "mg.write(debug.traceback(), '\\n')"); - } else { - printf("Lua error: [%s]\n", error_msg); - (void) luaL_dostring(L, "print(debug.traceback(), '\\n')"); - } - // TODO(lsm): leave the stack balanced - - return 0; -} - -static void lsp(struct connection *conn, const char *p, int len, lua_State *L) { - int i, j, pos = 0; - - for (i = 0; i < len; i++) { - if (p[i] == '<' && p[i + 1] == '?') { - for (j = i + 1; j < len ; j++) { - if (p[j] == '?' && p[j + 1] == '>') { - mg_write(&conn->mg_conn, p + pos, i - pos); - if (luaL_loadbuffer(L, p + (i + 2), j - (i + 2), "") == 0) { - lua_pcall(L, 0, LUA_MULTRET, 0); - } - pos = j + 2; - i = pos - 1; - break; - } - } - } - } - if (i > pos) mg_write(&conn->mg_conn, p + pos, i - pos); -} - -static void handle_lsp_request(struct connection *conn, const char *path, - file_stat_t *st) { - void *p = MAP_FAILED; - lua_State *L = NULL; - FILE *fp = NULL; - - if ((fp = fopen(path, "r")) == NULL || - (p = mmap(NULL, st->st_size, PROT_READ, MAP_PRIVATE, - fileno(fp), 0)) == MAP_FAILED || - (L = luaL_newstate()) == NULL) { - send_http_error(conn, 500, "mmap(%s): %s", path, strerror(errno)); - } else { - // We're not sending HTTP headers here, Lua page must do it. - prepare_lua_environment(&conn->mg_conn, L); - conn->mg_conn.connection_param = L; - call_user(conn, MG_LUA); - lua_pushcclosure(L, &lua_error_handler, 0); - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); - lsp(conn, p, (int) st->st_size, L); - close_local_endpoint(conn); - } - - if (L != NULL) lua_close(L); - if (p != MAP_FAILED) munmap(p, st->st_size); - if (fp != NULL) fclose(fp); -} -#endif // MONGOOSE_USE_LUA - #ifndef MONGOOSE_NO_SSI static void send_ssi_file(struct mg_connection *, const char *, FILE *, int); @@ -3998,7 +4150,8 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi, tag, path, strerror(errno)); } else { ns_set_close_on_exec(fileno(fp)); - if (match_prefix(opts[SSI_PATTERN], strlen(opts[SSI_PATTERN]), path) > 0) { + if (mg_match_prefix(opts[SSI_PATTERN], strlen(opts[SSI_PATTERN]), + path) > 0) { send_ssi_file(conn, path, fp, include_level + 1); } else { send_file_data(conn, fp); @@ -4108,12 +4261,118 @@ static void handle_ssi_request(struct connection *conn, const char *path) { } #endif -static void open_local_endpoint(struct connection *conn, int skip_user) { +static void proxy_request(struct ns_connection *pc, struct mg_connection *c) { + int i, sent_close_header = 0; + + ns_printf(pc, "%s %s%s%s HTTP/%s\r\n", c->request_method, c->uri, + c->query_string ? "?" : "", + c->query_string ? c->query_string : "", + c->http_version); + for (i = 0; i < c->num_headers; i++) { + if (mg_strcasecmp(c->http_headers[i].name, "Connection") == 0) { + // Force connection close, cause we don't parse proxy replies + // therefore we don't know message boundaries + ns_printf(pc, "%s: %s\r\n", "Connection", "close"); + sent_close_header = 1; + } else { + ns_printf(pc, "%s: %s\r\n", c->http_headers[i].name, + c->http_headers[i].value); + } + } + if (!sent_close_header) { + ns_printf(pc, "%s: %s\r\n", "Connection", "close"); + } + ns_printf(pc, "%s", "\r\n"); + ns_send(pc, c->content, c->content_len); + +} + +#ifdef NS_ENABLE_SSL +int mg_terminate_ssl(struct mg_connection *c, const char *cert) { + static const char ok[] = "HTTP/1.0 200 OK\r\n\r\n"; + struct connection *conn = MG_CONN_2_CONN(c); + SSL_CTX *ctx; + + DBG(("%p MITM", conn)); + if ((ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) return 0; + + SSL_CTX_use_certificate_file(ctx, cert, 1); + SSL_CTX_use_PrivateKey_file(ctx, cert, 1); + SSL_CTX_use_certificate_chain_file(ctx, cert); + + // When clear-text reply is pushed to client, switch to SSL mode. + // TODO(lsm): check for send() failure + send(conn->ns_conn->sock, ok, sizeof(ok) - 1, 0); + //DBG(("%p %lu %d SEND", c, (unsigned long) sizeof(ok) - 1, n)); + conn->ns_conn->send_iobuf.len = 0; + conn->endpoint_type = EP_USER; // To keep-alive in close_local_endpoint() + close_local_endpoint(conn); // Clean up current CONNECT request + if ((conn->ns_conn->ssl = SSL_new(ctx)) != NULL) { + SSL_set_fd(conn->ns_conn->ssl, conn->ns_conn->sock); + } + SSL_CTX_free(ctx); + return 1; +} +#endif + +int mg_forward(struct mg_connection *c, const char *addr) { + static const char ok[] = "HTTP/1.1 200 OK\r\n\r\n"; + struct connection *conn = MG_CONN_2_CONN(c); + struct ns_connection *pc; + + if ((pc = ns_connect(&conn->server->ns_mgr, addr, + mg_ev_handler, conn)) == NULL) { + conn->ns_conn->flags |= NSF_CLOSE_IMMEDIATELY; + return 0; + } + + // Interlink two connections + pc->flags |= MG_PROXY_CONN; + conn->endpoint_type = EP_PROXY; + conn->endpoint.nc = pc; + DBG(("%p [%s] [%s] -> %p %p", conn, c->uri, addr, pc, conn->ns_conn->ssl)); + + if (strcmp(c->request_method, "CONNECT") == 0) { + // For CONNECT request, reply with 200 OK. Tunnel is established. + // TODO(lsm): check for send() failure + (void) send(conn->ns_conn->sock, ok, sizeof(ok) - 1, 0); + } else { + // Strip "http://host:port" part from the URI + if (memcmp(c->uri, "http://", 7) == 0) c->uri += 7; + while (*c->uri != '\0' && *c->uri != '/') c->uri++; + proxy_request(pc, c); + } + return 1; +} + +static void proxify_connection(struct connection *conn) { + char proto[10], host[500], cert[500], addr[1000]; + unsigned short port = 80; + struct mg_connection *c = &conn->mg_conn; + int n = 0; + const char *url = c->uri; + + proto[0] = host[0] = cert[0] = '\0'; + if (sscanf(url, "%499[^: ]:%hu%n", host, &port, &n) != 2 && + sscanf(url, "%9[a-z]://%499[^: ]:%hu%n", proto, host, &port, &n) != 3 && + sscanf(url, "%9[a-z]://%499[^/ ]%n", proto, host, &n) != 2) { + n = 0; + } + + snprintf(addr, sizeof(addr), "%s://%s:%hu", + conn->ns_conn->ssl != NULL ? "ssl" : "tcp", host, port); + if (n <= 0 || !mg_forward(c, addr)) { + conn->ns_conn->flags |= NSF_CLOSE_IMMEDIATELY; + } +} + #ifndef MONGOOSE_NO_FILESYSTEM - static const char lua_pat[] = LUA_SCRIPT_PATTERN; - file_stat_t st; +void mg_send_file_internal(struct mg_connection *c, const char *file_name, + file_stat_t *st, int exists, + const char *extra_headers) { + struct connection *conn = MG_CONN_2_CONN(c); char path[MAX_PATH_SIZE]; - int exists = 0, is_directory = 0; + const int is_directory = S_ISDIR(st->st_mode); #ifndef MONGOOSE_NO_CGI const char *cgi_pat = conn->server->config_options[CGI_PATTERN]; #else @@ -4124,6 +4383,62 @@ static void open_local_endpoint(struct connection *conn, int skip_user) { #else const char *dir_lst = "yes"; #endif + + mg_snprintf(path, sizeof(path), "%s", file_name); + + if (!exists || must_hide_file(conn, path)) { + send_http_error(conn, 404, NULL); + } else if (is_directory && + conn->mg_conn.uri[strlen(conn->mg_conn.uri) - 1] != '/') { + conn->mg_conn.status_code = 301; + mg_printf(&conn->mg_conn, "HTTP/1.1 301 Moved Permanently\r\n" + "Location: %s/\r\n\r\n", conn->mg_conn.uri); + close_local_endpoint(conn); + } else if (is_directory && !find_index_file(conn, path, sizeof(path), st)) { + if (!mg_strcasecmp(dir_lst, "yes")) { +#ifndef MONGOOSE_NO_DIRECTORY_LISTING + send_directory_listing(conn, path); +#else + send_http_error(conn, 501, NULL); +#endif + } else { + send_http_error(conn, 403, NULL); + } + } else if (mg_match_prefix(cgi_pat, strlen(cgi_pat), path) > 0) { +#if !defined(MONGOOSE_NO_CGI) + open_cgi_endpoint(conn, path); +#else + send_http_error(conn, 501, NULL); +#endif // !MONGOOSE_NO_CGI +#ifndef MONGOOSE_NO_SSI + } else if (mg_match_prefix(conn->server->config_options[SSI_PATTERN], + strlen(conn->server->config_options[SSI_PATTERN]), + path) > 0) { + handle_ssi_request(conn, path); +#endif + } else if (is_not_modified(conn, st)) { + send_http_error(conn, 304, NULL); + } else if ((conn->endpoint.fd = open(path, O_RDONLY | O_BINARY, 0)) != -1) { + // O_BINARY is required for Windows, otherwise in default text mode + // two bytes \r\n will be read as one. + open_file_endpoint(conn, path, st, extra_headers); + } else { + send_http_error(conn, 404, NULL); + } +} +void mg_send_file(struct mg_connection *c, const char *file_name, + const char *extra_headers) { + file_stat_t st; + const int exists = stat(file_name, &st) == 0; + mg_send_file_internal(c, file_name, &st, exists, extra_headers); +} +#endif // !MONGOOSE_NO_FILESYSTEM + +static void open_local_endpoint(struct connection *conn, int skip_user) { +#ifndef MONGOOSE_NO_FILESYSTEM + char path[MAX_PATH_SIZE]; + file_stat_t st; + int exists = 0; #endif // If EP_USER was set in a prev call, reset it @@ -4142,39 +4457,54 @@ static void open_local_endpoint(struct connection *conn, int skip_user) { #if MONGOOSE_POST_SIZE_LIMIT > 1 { const char *cl = mg_get_header(&conn->mg_conn, "Content-Length"); - if (!strcmp(conn->mg_conn.request_method, "POST") && + if ((strcmp(conn->mg_conn.request_method, "POST") == 0 || + strcmp(conn->mg_conn.request_method, "PUT") == 0) && (cl == NULL || to64(cl) > MONGOOSE_POST_SIZE_LIMIT)) { - send_http_error(conn, 500, "POST size > %zu", - (size_t) MONGOOSE_POST_SIZE_LIMIT); + send_http_error(conn, 500, "POST size > %lu", + (unsigned long) MONGOOSE_POST_SIZE_LIMIT); } } #endif return; } -#ifdef MONGOOSE_NO_FILESYSTEM + if (strcmp(conn->mg_conn.request_method, "CONNECT") == 0 || + mg_strncasecmp(conn->mg_conn.uri, "http", 4) == 0) { + const char *enp = conn->server->config_options[ENABLE_PROXY]; + if (enp == NULL || strcmp(enp, "yes") != 0) { + send_http_error(conn, 405, NULL); + } else { + proxify_connection(conn); + } + return; + } + if (!strcmp(conn->mg_conn.request_method, "OPTIONS")) { send_options(conn); - } else { - send_http_error(conn, 404, NULL); + return; } + +#ifdef MONGOOSE_NO_FILESYSTEM + send_http_error(conn, 404, NULL); #else exists = convert_uri_to_file_name(conn, path, sizeof(path), &st); - is_directory = S_ISDIR(st.st_mode); if (!strcmp(conn->mg_conn.request_method, "OPTIONS")) { send_options(conn); } else if (conn->server->config_options[DOCUMENT_ROOT] == NULL) { send_http_error(conn, 404, NULL); #ifndef MONGOOSE_NO_AUTH - } else if ((!is_dav_mutation(conn) && !is_authorized(conn, path)) || - (is_dav_mutation(conn) && !is_authorized_for_dav(conn))) { + } else if ((!is_dav_request(conn) && !is_authorized(conn, path, + exists && S_ISDIR(st.st_mode))) || + (is_dav_request(conn) && !is_authorized_for_dav(conn))) { mg_send_digest_auth_request(&conn->mg_conn); close_local_endpoint(conn); #endif #ifndef MONGOOSE_NO_DAV + } else if (must_hide_file(conn, path)) { + send_http_error(conn, 404, NULL); } else if (!strcmp(conn->mg_conn.request_method, "PROPFIND")) { - handle_propfind(conn, path, &st); + handle_propfind(conn, path, &st, exists); } else if (!strcmp(conn->mg_conn.request_method, "MKCOL")) { handle_mkcol(conn, path); } else if (!strcmp(conn->mg_conn.request_method, "DELETE")) { @@ -4182,50 +4512,8 @@ static void open_local_endpoint(struct connection *conn, int skip_user) { } else if (!strcmp(conn->mg_conn.request_method, "PUT")) { handle_put(conn, path); #endif - } else if (!exists || must_hide_file(conn, path)) { - send_http_error(conn, 404, NULL); - } else if (is_directory && - conn->mg_conn.uri[strlen(conn->mg_conn.uri) - 1] != '/') { - conn->mg_conn.status_code = 301; - mg_printf(&conn->mg_conn, "HTTP/1.1 301 Moved Permanently\r\n" - "Location: %s/\r\n\r\n", conn->mg_conn.uri); - close_local_endpoint(conn); - } else if (is_directory && !find_index_file(conn, path, sizeof(path), &st)) { - if (!mg_strcasecmp(dir_lst, "yes")) { -#ifndef MONGOOSE_NO_DIRECTORY_LISTING - send_directory_listing(conn, path); -#else - send_http_error(conn, 501, NULL); -#endif - } else { - send_http_error(conn, 403, NULL); - } - } else if (match_prefix(lua_pat, sizeof(lua_pat) - 1, path) > 0) { -#ifdef MONGOOSE_USE_LUA - handle_lsp_request(conn, path, &st); -#else - send_http_error(conn, 501, NULL); -#endif - } else if (match_prefix(cgi_pat, strlen(cgi_pat), path) > 0) { -#if !defined(MONGOOSE_NO_CGI) - open_cgi_endpoint(conn, path); -#else - send_http_error(conn, 501, NULL); -#endif // !MONGOOSE_NO_CGI -#ifndef MONGOOSE_NO_SSI - } else if (match_prefix(conn->server->config_options[SSI_PATTERN], - strlen(conn->server->config_options[SSI_PATTERN]), - path) > 0) { - handle_ssi_request(conn, path); -#endif - } else if (is_not_modified(conn, &st)) { - send_http_error(conn, 304, NULL); - } else if ((conn->endpoint.fd = open(path, O_RDONLY | O_BINARY)) != -1) { - // O_BINARY is required for Windows, otherwise in default text mode - // two bytes \r\n will be read as one. - open_file_endpoint(conn, path, &st); } else { - send_http_error(conn, 404, NULL); + mg_send_file_internal(&conn->mg_conn, path, &st, exists, NULL); } #endif // MONGOOSE_NO_FILESYSTEM } @@ -4239,10 +4527,13 @@ static void send_continue_if_expected(struct connection *conn) { } } +// Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 static int is_valid_uri(const char *uri) { - // Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 - // URI can be an asterisk (*) or should start with slash. - return uri[0] == '/' || (uri[0] == '*' && uri[1] == '\0'); + unsigned short n; + return uri[0] == '/' || + strcmp(uri, "*") == 0 || // OPTIONS method can use asterisk URI + mg_strncasecmp(uri, "http", 4) == 0 || // Naive check for the absolute URI + sscanf(uri, "%*[^ :]:%hu", &n) > 0; // CONNECT method can use host:port } static void try_parse(struct connection *conn) { @@ -4255,24 +4546,45 @@ static void try_parse(struct connection *conn) { // become invalid. conn->request = (char *) malloc(conn->request_len); memcpy(conn->request, io->buf, conn->request_len); - DBG(("%p [%.*s]", conn, conn->request_len, conn->request)); + //DBG(("%p [%.*s]", conn, conn->request_len, conn->request)); iobuf_remove(io, conn->request_len); conn->request_len = parse_http_message(conn->request, conn->request_len, &conn->mg_conn); if (conn->request_len > 0) { const char *cl_hdr = mg_get_header(&conn->mg_conn, "Content-Length"); conn->cl = cl_hdr == NULL ? 0 : to64(cl_hdr); - conn->mg_conn.content_len = (long int) conn->cl; + conn->mg_conn.content_len = (size_t) conn->cl; } } } -static void process_request(struct connection *conn) { +static void do_proxy(struct connection *conn) { + if (0 && conn->request_len == 0) { + try_parse(conn); + DBG(("%p parsing -> %d", conn, conn->request_len)); + if (conn->request_len > 0 && call_user(conn, MG_REQUEST) == MG_FALSE) { + proxy_request(conn->endpoint.nc, &conn->mg_conn); + } else if (conn->request_len < 0) { + ns_forward(conn->ns_conn, conn->endpoint.nc); + } + } else { + DBG(("%p forwarding", conn)); + ns_forward(conn->ns_conn, conn->endpoint.nc); + } +} + +static void on_recv_data(struct connection *conn) { struct iobuf *io = &conn->ns_conn->recv_iobuf; + int n; + + if (conn->endpoint_type == EP_PROXY) { + if (conn->endpoint.nc != NULL) do_proxy(conn); + return; + } try_parse(conn); - DBG(("%p %d %d %d [%.*s]", conn, conn->request_len, io->len, - conn->ns_conn->flags, io->len, io->buf)); + DBG(("%p %d %lu %d", conn, conn->request_len, (unsigned long)io->len, + conn->ns_conn->flags)); if (conn->request_len < 0 || (conn->request_len > 0 && !is_valid_uri(conn->mg_conn.uri))) { send_http_error(conn, 400, NULL); @@ -4291,11 +4603,19 @@ static void process_request(struct connection *conn) { } #ifndef MONGOOSE_NO_CGI - if (conn->endpoint_type == EP_CGI && io->len > 0) { - forward_post_data(conn); + if (conn->endpoint_type == EP_CGI && conn->endpoint.nc != NULL) { + ns_forward(conn->ns_conn, conn->endpoint.nc); } #endif if (conn->endpoint_type == EP_USER) { + conn->mg_conn.content = io->buf; + conn->mg_conn.content_len = io->len; + n = call_user(conn, MG_RECV); + if (n < 0) { + conn->ns_conn->flags |= NSF_FINISHED_SENDING_DATA; + } else if ((size_t) n <= io->len) { + iobuf_remove(io, n); + } call_request_handler_if_data_is_buffered(conn); } #ifndef MONGOOSE_NO_DAV @@ -4317,7 +4637,7 @@ static void call_http_client_handler(struct connection *conn) { } iobuf_remove(&conn->ns_conn->recv_iobuf, conn->mg_conn.content_len); conn->mg_conn.status_code = 0; - conn->cl = conn->num_bytes_sent = conn->request_len = 0; + conn->cl = conn->num_bytes_recv = conn->request_len = 0; free(conn->request); conn->request = NULL; } @@ -4326,22 +4646,20 @@ static void process_response(struct connection *conn) { struct iobuf *io = &conn->ns_conn->recv_iobuf; try_parse(conn); - DBG(("%p %d %d [%.*s]", conn, conn->request_len, io->len, - io->len > 40 ? 40 : io->len, io->buf)); + DBG(("%p %d %lu", conn, conn->request_len, (unsigned long)io->len)); if (conn->request_len < 0 || (conn->request_len == 0 && io->len > MAX_REQUEST_SIZE)) { call_http_client_handler(conn); - } else if (io->len >= conn->cl) { + } else if ((int64_t) io->len >= conn->cl) { call_http_client_handler(conn); } } -struct mg_connection *mg_connect(struct mg_server *server, const char *host, - int port, int use_ssl) { +struct mg_connection *mg_connect(struct mg_server *server, const char *addr) { struct ns_connection *nsconn; struct connection *conn; - nsconn = ns_connect(&server->ns_server, host, port, use_ssl, NULL); + nsconn = ns_connect(&server->ns_mgr, addr, mg_ev_handler, NULL); if (nsconn == NULL) return 0; if ((conn = (struct connection *) calloc(1, sizeof(*conn))) == NULL) { @@ -4351,12 +4669,12 @@ struct mg_connection *mg_connect(struct mg_server *server, const char *host, // Interlink two structs conn->ns_conn = nsconn; - nsconn->connection_data = conn; + nsconn->user_data = conn; conn->server = server; conn->endpoint_type = EP_CLIENT; //conn->handler = handler; - conn->mg_conn.server_param = server->ns_server.server_data; + conn->mg_conn.server_param = server->ns_mgr.user_data; conn->ns_conn->flags = NSF_CONNECTING; return &conn->mg_conn; @@ -4387,12 +4705,12 @@ static void log_access(const struct connection *conn, const char *path) { flockfile(fp); mg_parse_header(mg_get_header(&conn->mg_conn, "Authorization"), "username", user, sizeof(user)); - fprintf(fp, "%s - %s [%s] \"%s %s%s%s HTTP/%s\" %d %" INT64_FMT, + fprintf(fp, "%s - %s [%s] \"%s %s%s%s HTTP/%s\" %d 0", c->remote_ip, user[0] == '\0' ? "-" : user, date, c->request_method ? c->request_method : "-", c->uri ? c->uri : "-", c->query_string ? "?" : "", c->query_string ? c->query_string : "", - c->http_version, c->status_code, conn->num_bytes_sent); + c->http_version, c->status_code); log_header(c, "Referer", fp); log_header(c, "User-Agent", fp); fputc('\n', fp); @@ -4417,9 +4735,11 @@ static void close_local_endpoint(struct connection *conn) { close(conn->endpoint.fd); break; case EP_CGI: - if (conn->endpoint.cgi_conn != NULL) { - conn->endpoint.cgi_conn->flags |= NSF_CLOSE_IMMEDIATELY; - conn->endpoint.cgi_conn->connection_data = NULL; + case EP_PROXY: + if (conn->endpoint.nc != NULL) { + DBG(("%p %p %p :-)", conn, conn->ns_conn, conn->endpoint.nc)); + conn->endpoint.nc->flags |= NSF_CLOSE_IMMEDIATELY; + conn->endpoint.nc->user_data = NULL; } break; default: break; @@ -4433,19 +4753,26 @@ static void close_local_endpoint(struct connection *conn) { #endif // Gobble possible POST data sent to the URI handler - iobuf_remove(&conn->ns_conn->recv_iobuf, conn->mg_conn.content_len); + iobuf_free(&conn->ns_conn->recv_iobuf); + free(conn->request); + free(conn->path_info); + conn->endpoint.nc = NULL; + conn->request = conn->path_info = NULL; + conn->endpoint_type = EP_NONE; - conn->cl = conn->num_bytes_sent = conn->request_len = 0; + conn->cl = conn->num_bytes_recv = conn->request_len = 0; conn->ns_conn->flags &= ~(NSF_FINISHED_SENDING_DATA | NSF_BUFFER_BUT_DONT_SEND | NSF_CLOSE_IMMEDIATELY | MG_HEADERS_SENT | MG_LONG_RUNNING); + + // Do not memset() the whole structure, as some of the fields + // (IP addresses & ports, server_param) must survive. Nullify the rest. c->request_method = c->uri = c->http_version = c->query_string = NULL; c->num_headers = c->status_code = c->is_websocket = c->content_len = 0; - free(conn->request); conn->request = NULL; - free(conn->path_info); conn->path_info = NULL; + c->connection_param = c->callback_param = NULL; if (keep_alive) { - process_request(conn); // Can call us recursively if pipelining is used + on_recv_data(conn); // Can call us recursively if pipelining is used } else { conn->ns_conn->flags |= conn->ns_conn->send_iobuf.len == 0 ? NSF_CLOSE_IMMEDIATELY : NSF_FINISHED_SENDING_DATA; @@ -4454,8 +4781,15 @@ static void close_local_endpoint(struct connection *conn) { static void transfer_file_data(struct connection *conn) { char buf[IOBUF_SIZE]; - int n = read(conn->endpoint.fd, buf, conn->cl < (int64_t) sizeof(buf) ? - (int) conn->cl : (int) sizeof(buf)); + int n; + + // If output buffer is too big, don't send anything. Wait until + // mongoose drains already buffered data to the client. + if (conn->ns_conn->send_iobuf.len > sizeof(buf) * 2) return; + + // Do not send anyt + n = read(conn->endpoint.fd, buf, conn->cl < (int64_t) sizeof(buf) ? + (int) conn->cl : (int) sizeof(buf)); if (n <= 0) { close_local_endpoint(conn); @@ -4469,7 +4803,7 @@ static void transfer_file_data(struct connection *conn) { } int mg_poll_server(struct mg_server *server, int milliseconds) { - return ns_server_poll(&server->ns_server, milliseconds); + return ns_mgr_poll(&server->ns_mgr, milliseconds); } void mg_destroy_server(struct mg_server **server) { @@ -4477,7 +4811,7 @@ void mg_destroy_server(struct mg_server **server) { struct mg_server *s = *server; int i; - ns_server_free(&s->ns_server); + ns_mgr_free(&s->ns_mgr); for (i = 0; i < (int) ARRAY_SIZE(s->config_options); i++) { free(s->config_options[i]); // It is OK to free(NULL) } @@ -4486,26 +4820,16 @@ void mg_destroy_server(struct mg_server **server) { } } -struct mg_iterator { - mg_handler_t cb; - void *param; -}; -union variant { mg_handler_t cb; void *p; }; - -static void iter(struct ns_connection *nsconn, enum ns_event ev, void *param) { - if (ev == NS_POLL) { - union variant *variant = (union variant *) param; - struct connection *c = (struct connection *) nsconn->connection_data; - variant->cb(&c->mg_conn, MG_POLL); +struct mg_connection *mg_next(struct mg_server *s, struct mg_connection *c) { + struct ns_connection *nc = ns_next(&s->ns_mgr, c == NULL ? NULL : + MG_CONN_2_CONN(c)->ns_conn); + if (nc != NULL && nc->user_data != NULL) { + return & ((struct connection *) nc->user_data)->mg_conn; + } else { + return NULL; } } -// Apply function to all active connections. -void mg_iterate_over_connections(struct mg_server *server, mg_handler_t cb) { - union variant variant = { cb }; - ns_iterate(&server->ns_server, iter, &variant); -} - static int get_var(const char *data, size_t data_len, const char *name, char *dst, size_t dst_len) { const char *p, *e, *s; @@ -4612,6 +4936,19 @@ const char **mg_get_valid_option_names(void) { return static_config_options; } +void mg_copy_listeners(struct mg_server *s, struct mg_server *to) { + struct ns_connection *c; + for (c = ns_next(&s->ns_mgr, NULL); c != NULL; c = ns_next(&s->ns_mgr, c)) { + struct ns_connection *tmp; + if ((c->flags & NSF_LISTENING) && + (tmp = (struct ns_connection *) malloc(sizeof(*tmp))) != NULL) { + memcpy(tmp, c, sizeof(*tmp)); + tmp->mgr = &to->ns_mgr; + ns_add_conn(tmp->mgr, tmp); + } + } +} + static int get_option_index(const char *name) { int i; @@ -4639,60 +4976,76 @@ const char *mg_set_option(struct mg_server *server, const char *name, const char *value) { int ind = get_option_index(name); const char *error_msg = NULL; + char **v = NULL; - if (ind < 0) { - error_msg = "No such option"; - } else { - if (server->config_options[ind] != NULL) { - free(server->config_options[ind]); - } - server->config_options[ind] = mg_strdup(value); - DBG(("%s [%s]", name, value)); + if (ind < 0) return "No such option"; + v = &server->config_options[ind]; + + // Return success immediately if setting to the same value + if ((*v == NULL && value == NULL) || + (value != NULL && *v != NULL && !strcmp(value, *v))) { + return NULL; + } - if (ind == LISTENING_PORT) { - int port = ns_bind(&server->ns_server, value); - if (port < 0) { + if (*v != NULL) { + free(*v); + *v = NULL; + } + + if (value == NULL || value[0] == '\0') return NULL; + + *v = mg_strdup(value); + DBG(("%s [%s]", name, *v)); + + if (ind == LISTENING_PORT) { + struct vec vec; + while ((value = next_option(value, &vec, NULL)) != NULL) { + struct ns_connection *c = ns_bind(&server->ns_mgr, vec.ptr, + mg_ev_handler, NULL); + if (c== NULL) { error_msg = "Cannot bind to port"; + break; } else { - sockaddr_to_string(server->local_ip, sizeof(server->local_ip), - &server->ns_server.listening_sa); - if (!strcmp(value, "0")) { - char buf[10]; - mg_snprintf(buf, sizeof(buf), "%d", port); - free(server->config_options[ind]); - server->config_options[ind] = mg_strdup(buf); - } - } -#ifndef _WIN32 - } else if (ind == RUN_AS_USER) { - struct passwd *pw; - if ((pw = getpwnam(value)) == NULL) { - error_msg = "Unknown user"; - } else if (setgid(pw->pw_gid) != 0) { - error_msg = "setgid() failed"; - } else if (setuid(pw->pw_uid) != 0) { - error_msg = "setuid() failed"; - } -#endif -#ifdef NS_ENABLE_SSL - } else if (ind == SSL_CERTIFICATE) { - int res = ns_set_ssl_cert(&server->ns_server, value); - if (res == -2) { - error_msg = "Cannot load PEM"; - } else if (res == -3) { - error_msg = "SSL not enabled"; - } else if (res == -1) { - error_msg = "SSL_CTX_new() failed"; + char buf[100]; + ns_sock_to_str(c->sock, buf, sizeof(buf), 2); + free(*v); + *v = mg_strdup(buf); } + } +#ifndef MONGOOSE_NO_FILESYSTEM + } else if (ind == HEXDUMP_FILE) { + server->ns_mgr.hexdump_file = *v; #endif +#ifndef _WIN32 + } else if (ind == RUN_AS_USER) { + struct passwd *pw; + if ((pw = getpwnam(value)) == NULL) { + error_msg = "Unknown user"; + } else if (setgid(pw->pw_gid) != 0) { + error_msg = "setgid() failed"; + } else if (setuid(pw->pw_uid) != 0) { + error_msg = "setuid() failed"; } +#endif } return error_msg; } +static void set_ips(struct ns_connection *nc, int is_rem) { + struct connection *conn = (struct connection *) nc->user_data; + struct mg_connection *c = &conn->mg_conn; + char buf[100]; + + ns_sock_to_str(nc->sock, buf, sizeof(buf), is_rem ? 7 : 3); + sscanf(buf, "%47[^:]:%hu", + is_rem ? c->remote_ip : c->local_ip, + is_rem ? &c->remote_port : &c->local_port); + //DBG(("%p %s %s", conn, is_rem ? "rem" : "loc", buf)); +} + static void on_accept(struct ns_connection *nc, union socket_address *sa) { - struct mg_server *server = (struct mg_server *) nc->server; + struct mg_server *server = (struct mg_server *) nc->mgr; struct connection *conn; if (!check_acl(server->config_options[ACCESS_CONTROL_LIST], @@ -4700,44 +5053,87 @@ static void on_accept(struct ns_connection *nc, union socket_address *sa) { (conn = (struct connection *) calloc(1, sizeof(*conn))) == NULL) { nc->flags |= NSF_CLOSE_IMMEDIATELY; } else { - conn->server = (struct mg_server *) nc->server; - sockaddr_to_string(conn->mg_conn.remote_ip, - sizeof(conn->mg_conn.remote_ip), sa); - conn->mg_conn.remote_port = ntohs(sa->sin.sin_port); - conn->mg_conn.server_param = nc->server->server_data; - conn->mg_conn.local_ip = server->local_ip; - conn->mg_conn.local_port = - ntohs(server->ns_server.listening_sa.sin.sin_port); - // Circularly link two connection structures - nc->connection_data = conn; + nc->user_data = conn; conn->ns_conn = nc; + + // Initialize the rest of connection attributes + conn->server = server; + conn->mg_conn.server_param = nc->mgr->user_data; + set_ips(nc, 1); + set_ips(nc, 0); } } -static void mg_ev_handler(struct ns_connection *nc, enum ns_event ev, void *p) { - struct connection *conn = (struct connection *) nc->connection_data; +static void process_udp(struct ns_connection *nc) { + struct iobuf *io = &nc->recv_iobuf; + struct connection conn; + + memset(&conn, 0, sizeof(conn)); + conn.ns_conn = nc; + conn.server = (struct mg_server *) nc->mgr; + conn.request_len = parse_http_message(io->buf, io->len, &conn.mg_conn); + on_recv_data(&conn); + //ns_printf(nc, "%s", "HTTP/1.0 200 OK\r\n\r\n"); +} + +static void mg_ev_handler(struct ns_connection *nc, int ev, void *p) { + struct connection *conn = (struct connection *) nc->user_data; + + // Send NS event to the handler. Note that call_user won't send an event + // if conn == NULL. Therefore, repeat this for NS_ACCEPT event as well. +#ifdef MONGOOSE_SEND_NS_EVENTS + { + struct connection *conn = (struct connection *) nc->user_data; + void *param[2] = { nc, p }; + if (conn != NULL) conn->mg_conn.callback_param = param; + call_user(conn, (enum mg_event) ev); + } +#endif switch (ev) { case NS_ACCEPT: on_accept(nc, (union socket_address *) p); +#ifdef MONGOOSE_SEND_NS_EVENTS + { + struct connection *conn = (struct connection *) nc->user_data; + void *param[2] = { nc, p }; + if (conn != NULL) conn->mg_conn.callback_param = param; + call_user(conn, (enum mg_event) ev); + } +#endif break; case NS_CONNECT: + if (nc->user_data != NULL) { + set_ips(nc, 1); + set_ips(nc, 0); + } conn->mg_conn.status_code = * (int *) p; if (conn->mg_conn.status_code != 0 || - call_user(conn, MG_CONNECT) == MG_FALSE) { + (!(nc->flags & MG_PROXY_CONN) && + call_user(conn, MG_CONNECT) == MG_FALSE)) { nc->flags |= NSF_CLOSE_IMMEDIATELY; } break; case NS_RECV: - if (nc->flags & NSF_ACCEPTED) { - process_request(conn); + if (conn != NULL) { + conn->num_bytes_recv += * (int *) p; + } + + if (nc->flags & NSF_UDP) { + process_udp(nc); + } else if (nc->listener != NULL) { + on_recv_data(conn); #ifndef MONGOOSE_NO_CGI } else if (nc->flags & MG_CGI_CONN) { on_cgi_data(nc); #endif + } else if (nc->flags & MG_PROXY_CONN) { + if (conn != NULL) { + ns_forward(nc, conn->ns_conn); + } } else { process_response(conn); } @@ -4747,14 +5143,17 @@ static void mg_ev_handler(struct ns_connection *nc, enum ns_event ev, void *p) { break; case NS_CLOSE: - nc->connection_data = NULL; - if ((nc->flags & MG_CGI_CONN) && conn && conn->ns_conn) { - conn->ns_conn->flags &= ~NSF_BUFFER_BUT_DONT_SEND; - conn->ns_conn->flags |= conn->ns_conn->send_iobuf.len > 0 ? - NSF_FINISHED_SENDING_DATA : NSF_CLOSE_IMMEDIATELY; - conn->endpoint.cgi_conn = NULL; + nc->user_data = NULL; + if (nc->flags & (MG_CGI_CONN | MG_PROXY_CONN)) { + DBG(("%p %p closing cgi/proxy conn", conn, nc)); + if (conn && conn->ns_conn) { + conn->ns_conn->flags &= ~NSF_BUFFER_BUT_DONT_SEND; + conn->ns_conn->flags |= conn->ns_conn->send_iobuf.len > 0 ? + NSF_FINISHED_SENDING_DATA : NSF_CLOSE_IMMEDIATELY; + conn->endpoint.nc = NULL; + } } else if (conn != NULL) { - DBG(("%p %d closing", conn, conn->endpoint_type)); + DBG(("%p %p %d closing", conn, nc, conn->endpoint_type)); if (conn->endpoint_type == EP_CLIENT && nc->recv_iobuf.len > 0) { call_http_client_handler(conn); @@ -4762,17 +5161,23 @@ static void mg_ev_handler(struct ns_connection *nc, enum ns_event ev, void *p) { call_user(conn, MG_CLOSE); close_local_endpoint(conn); + conn->ns_conn = NULL; free(conn); } break; case NS_POLL: - if (call_user(conn, MG_POLL) == MG_TRUE) { - nc->flags |= NSF_FINISHED_SENDING_DATA; - } + if (conn != NULL) { + if (call_user(conn, MG_POLL) == MG_TRUE) { + if (conn->ns_conn->flags & MG_HEADERS_SENT) { + write_terminating_chunk(conn); + } + close_local_endpoint(conn); + } - if (conn != NULL && conn->endpoint_type == EP_FILE) { - transfer_file_data(conn); + if (conn->endpoint_type == EP_FILE) { + transfer_file_data(conn); + } } // Expire idle connections @@ -4783,7 +5188,8 @@ static void mg_ev_handler(struct ns_connection *nc, enum ns_event ev, void *p) { ping_idle_websocket_connection(conn, current_time); } - if (nc->last_io_time + MONGOOSE_IDLE_TIMEOUT_SECONDS < current_time) { + if (nc->listener != NULL && + nc->last_io_time + MONGOOSE_IDLE_TIMEOUT_SECONDS < current_time) { mg_ev_handler(nc, NS_CLOSE, NULL); nc->flags |= NSF_CLOSE_IMMEDIATELY; } @@ -4795,19 +5201,38 @@ static void mg_ev_handler(struct ns_connection *nc, enum ns_event ev, void *p) { } } -void mg_wakeup_server(struct mg_server *server) { - ns_server_wakeup(&server->ns_server); -} +static void iter2(struct ns_connection *nc, int ev, void *param) { + mg_handler_t func = NULL; + struct connection *conn = (struct connection *) nc->user_data; + const char *msg = (const char *) param; + int n; + (void) ev; -void mg_set_listening_socket(struct mg_server *server, int sock) { - if (server->ns_server.listening_sock != INVALID_SOCKET) { - closesocket(server->ns_server.listening_sock); + //DBG(("%p [%s]", conn, msg)); + if (sscanf(msg, "%p %n", &func, &n) && func != NULL) { + conn->mg_conn.callback_param = (void *) (msg + n); + func(&conn->mg_conn, MG_POLL); } - server->ns_server.listening_sock = (sock_t) sock; } -int mg_get_listening_socket(struct mg_server *server) { - return server->ns_server.listening_sock; +void mg_wakeup_server_ex(struct mg_server *server, mg_handler_t cb, + const char *fmt, ...) { + va_list ap; + char buf[8 * 1024]; + int len; + + // Encode callback (cb) into a buffer + len = snprintf(buf, sizeof(buf), "%p ", cb); + va_start(ap, fmt); + len += vsnprintf(buf + len, sizeof(buf) - len, fmt, ap); + va_end(ap); + + // "len + 1" is to include terminating \0 in the message + ns_broadcast(&server->ns_mgr, iter2, buf, len + 1); +} + +void mg_wakeup_server(struct mg_server *server) { + ns_broadcast(&server->ns_mgr, NULL, (void *) "", 0); } const char *mg_get_option(const struct mg_server *server, const char *name) { @@ -4818,7 +5243,7 @@ const char *mg_get_option(const struct mg_server *server, const char *name) { struct mg_server *mg_create_server(void *server_data, mg_handler_t handler) { struct mg_server *server = (struct mg_server *) calloc(1, sizeof(*server)); - ns_server_init(&server->ns_server, server_data, mg_ev_handler); + ns_mgr_init(&server->ns_mgr, server_data); set_default_option_values(server->config_options); server->event_handler = handler; return server; diff --git a/3rdparty/mongoose/mongoose.h b/3rdparty/mongoose/mongoose.h index ca7175918..96c8c90b1 100644 --- a/3rdparty/mongoose/mongoose.h +++ b/3rdparty/mongoose/mongoose.h @@ -1,129 +1,153 @@ -// Copyright (c) 2004-2013 Sergey Lyubka -// Copyright (c) 2013-2014 Cesanta Software Limited -// All rights reserved -// -// This library is dual-licensed: you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 2 as -// published by the Free Software Foundation. For the terms of this -// license, see . -// -// You are free to use this library under the terms of the GNU General -// Public License, but WITHOUT ANY WARRANTY; without even the implied -// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// Alternatively, you can license this library under a commercial -// license, as set out in . -// -// NOTE: Detailed API documentation is at http://cesanta.com/#docs - -#ifndef MONGOOSE_HEADER_INCLUDED -#define MONGOOSE_HEADER_INCLUDED - -#define MONGOOSE_VERSION "5.3" - -#include // required for FILE -#include // required for size_t - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -// This structure contains information about HTTP request. -struct mg_connection { - const char *request_method; // "GET", "POST", etc - const char *uri; // URL-decoded URI - const char *http_version; // E.g. "1.0", "1.1" - const char *query_string; // URL part after '?', not including '?', or NULL - - char remote_ip[48]; // Max IPv6 string length is 45 characters - const char *local_ip; // Local IP address - unsigned short remote_port; // Client's port - unsigned short local_port; // Local port number - - int num_headers; // Number of HTTP headers - struct mg_header { - const char *name; // HTTP header name - const char *value; // HTTP header value - } http_headers[30]; - - char *content; // POST (or websocket message) data, or NULL - size_t content_len; // content length - - int is_websocket; // Connection is a websocket connection - int status_code; // HTTP status code for HTTP error handler - int wsbits; // First byte of the websocket frame - void *server_param; // Parameter passed to mg_add_uri_handler() - void *connection_param; // Placeholder for connection-specific data -}; - -struct mg_server; // Opaque structure describing server instance -enum mg_result { MG_FALSE, MG_TRUE, MG_MORE }; -enum mg_event { - MG_POLL = 100, // Callback return value is ignored - MG_CONNECT, // If callback returns MG_FALSE, connect fails - MG_AUTH, // If callback returns MG_FALSE, authentication fails - MG_REQUEST, // If callback returns MG_FALSE, Mongoose continues with req - MG_REPLY, // If callback returns MG_FALSE, Mongoose closes connection - MG_CLOSE, // Connection is closed - MG_LUA, // Called before LSP page invoked - MG_HTTP_ERROR // If callback returns MG_FALSE, Mongoose continues with err -}; -typedef int (*mg_handler_t)(struct mg_connection *, enum mg_event); - -// Server management functions -struct mg_server *mg_create_server(void *server_param, mg_handler_t handler); -void mg_destroy_server(struct mg_server **); -const char *mg_set_option(struct mg_server *, const char *opt, const char *val); -int mg_poll_server(struct mg_server *, int milliseconds); -const char **mg_get_valid_option_names(void); -const char *mg_get_option(const struct mg_server *server, const char *name); -void mg_set_listening_socket(struct mg_server *, int sock); -int mg_get_listening_socket(struct mg_server *); -void mg_iterate_over_connections(struct mg_server *, mg_handler_t); -void mg_wakeup_server(struct mg_server *); -struct mg_connection *mg_connect(struct mg_server *, const char *, int, int); - -// Connection management functions -void mg_send_status(struct mg_connection *, int status_code); -void mg_send_header(struct mg_connection *, const char *name, const char *val); -void mg_send_data(struct mg_connection *, const void *data, int data_len); -void mg_printf_data(struct mg_connection *, const char *format, ...); - -int mg_websocket_write(struct mg_connection *, int opcode, - const char *data, size_t data_len); - -// Deprecated in favor of mg_send_* interface -int mg_write(struct mg_connection *, const void *buf, int len); -int mg_printf(struct mg_connection *conn, const char *fmt, ...); - -const char *mg_get_header(const struct mg_connection *, const char *name); -const char *mg_get_mime_type(const char *name, const char *default_mime_type); -int mg_get_var(const struct mg_connection *conn, const char *var_name, - char *buf, size_t buf_len); -int mg_parse_header(const char *hdr, const char *var_name, char *buf, size_t); -int mg_parse_multipart(const char *buf, int buf_len, - char *var_name, int var_name_len, - char *file_name, int file_name_len, - const char **data, int *data_len); - -// Utility functions -void *mg_start_thread(void *(*func)(void *), void *param); -char *mg_md5(char buf[33], ...); -int mg_authorize_digest(struct mg_connection *c, FILE *fp); - -// Lua utility functions -#ifdef MONGOOSE_USE_LUA -#include -#include -void reg_string(lua_State *L, const char *name, const char *val); -void reg_int(lua_State *L, const char *name, int val); -void reg_function(lua_State *L, const char *, - lua_CFunction, struct mg_connection *); -#endif - -#ifdef __cplusplus -} -#endif // __cplusplus - -#endif // MONGOOSE_HEADER_INCLUDED +// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2013-2014 Cesanta Software Limited +// All rights reserved +// +// This library is dual-licensed: you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. For the terms of this +// license, see . +// +// You are free to use this library under the terms of the GNU General +// Public License, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// Alternatively, you can license this library under a commercial +// license, as set out in . +// +// $Date: 2014-09-09 17:07:55 UTC $ + +#ifndef MONGOOSE_HEADER_INCLUDED +#define MONGOOSE_HEADER_INCLUDED + +#define MONGOOSE_VERSION "5.5" + +#include // required for FILE +#include // required for size_t + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// This structure contains information about HTTP request. +struct mg_connection { + const char *request_method; // "GET", "POST", etc + const char *uri; // URL-decoded URI + const char *http_version; // E.g. "1.0", "1.1" + const char *query_string; // URL part after '?', not including '?', or NULL + + char remote_ip[48]; // Max IPv6 string length is 45 characters + char local_ip[48]; // Local IP address + unsigned short remote_port; // Client's port + unsigned short local_port; // Local port number + + int num_headers; // Number of HTTP headers + struct mg_header { + const char *name; // HTTP header name + const char *value; // HTTP header value + } http_headers[30]; + + char *content; // POST (or websocket message) data, or NULL + size_t content_len; // Data length + + int is_websocket; // Connection is a websocket connection + int status_code; // HTTP status code for HTTP error handler + int wsbits; // First byte of the websocket frame + void *server_param; // Parameter passed to mg_add_uri_handler() + void *connection_param; // Placeholder for connection-specific data + void *callback_param; +}; + +struct mg_server; // Opaque structure describing server instance +enum mg_result { MG_FALSE, MG_TRUE, MG_MORE }; +enum mg_event { + MG_POLL = 100, // Callback return value is ignored + MG_CONNECT, // If callback returns MG_FALSE, connect fails + MG_AUTH, // If callback returns MG_FALSE, authentication fails + MG_REQUEST, // If callback returns MG_FALSE, Mongoose continues with req + MG_REPLY, // If callback returns MG_FALSE, Mongoose closes connection + MG_RECV, // Mongoose has received POST data chunk. + // Callback should return a number of bytes to discard from + // the receive buffer, or -1 to close the connection. + MG_CLOSE, // Connection is closed, callback return value is ignored + MG_WS_HANDSHAKE, // New websocket connection, handshake request + MG_WS_CONNECT, // New websocket connection established + MG_HTTP_ERROR // If callback returns MG_FALSE, Mongoose continues with err +}; +typedef int (*mg_handler_t)(struct mg_connection *, enum mg_event); + +// Websocket opcodes, from http://tools.ietf.org/html/rfc6455 +enum { + WEBSOCKET_OPCODE_CONTINUATION = 0x0, + WEBSOCKET_OPCODE_TEXT = 0x1, + WEBSOCKET_OPCODE_BINARY = 0x2, + WEBSOCKET_OPCODE_CONNECTION_CLOSE = 0x8, + WEBSOCKET_OPCODE_PING = 0x9, + WEBSOCKET_OPCODE_PONG = 0xa +}; + +// Server management functions +struct mg_server *mg_create_server(void *server_param, mg_handler_t handler); +void mg_destroy_server(struct mg_server **); +const char *mg_set_option(struct mg_server *, const char *opt, const char *val); +int mg_poll_server(struct mg_server *, int milliseconds); +const char **mg_get_valid_option_names(void); +const char *mg_get_option(const struct mg_server *server, const char *name); +void mg_copy_listeners(struct mg_server *from, struct mg_server *to); +struct mg_connection *mg_next(struct mg_server *, struct mg_connection *); +void mg_wakeup_server(struct mg_server *); +void mg_wakeup_server_ex(struct mg_server *, mg_handler_t, const char *, ...); +struct mg_connection *mg_connect(struct mg_server *, const char *); + +// Connection management functions +void mg_send_status(struct mg_connection *, int status_code); +void mg_send_header(struct mg_connection *, const char *name, const char *val); +size_t mg_send_data(struct mg_connection *, const void *data, int data_len); +size_t mg_printf_data(struct mg_connection *, const char *format, ...); +size_t mg_write(struct mg_connection *, const void *buf, int len); +size_t mg_printf(struct mg_connection *conn, const char *fmt, ...); + +size_t mg_websocket_write(struct mg_connection *, int opcode, + const char *data, size_t data_len); +size_t mg_websocket_printf(struct mg_connection* conn, int opcode, + const char *fmt, ...); + +void mg_send_file(struct mg_connection *, const char *path, const char *); +void mg_send_file_data(struct mg_connection *, int fd); + +const char *mg_get_header(const struct mg_connection *, const char *name); +const char *mg_get_mime_type(const char *name, const char *default_mime_type); +int mg_get_var(const struct mg_connection *conn, const char *var_name, + char *buf, size_t buf_len); +int mg_parse_header(const char *hdr, const char *var_name, char *buf, size_t); +int mg_parse_multipart(const char *buf, int buf_len, + char *var_name, int var_name_len, + char *file_name, int file_name_len, + const char **data, int *data_len); + + +// Utility functions +void *mg_start_thread(void *(*func)(void *), void *param); +char *mg_md5(char buf[33], ...); +int mg_authorize_digest(struct mg_connection *c, FILE *fp); +int mg_url_encode(const char *src, size_t s_len, char *dst, size_t dst_len); +int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, int); +int mg_terminate_ssl(struct mg_connection *c, const char *cert); +int mg_forward(struct mg_connection *c, const char *addr); +void *mg_mmap(FILE *fp, size_t size); +void mg_munmap(void *p, size_t size); + + +// Templates support +struct mg_expansion { + const char *keyword; + void (*handler)(struct mg_connection *); +}; +void mg_template(struct mg_connection *, const char *text, + struct mg_expansion *expansions); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // MONGOOSE_HEADER_INCLUDED diff --git a/src/Network/http/httpd.cxx b/src/Network/http/httpd.cxx index 81f8c9aac..38860930d 100644 --- a/src/Network/http/httpd.cxx +++ b/src/Network/http/httpd.cxx @@ -596,8 +596,6 @@ int MongooseHttpd::staticRequestHandler(struct mg_connection * connection, mg_ev case MG_HTTP_ERROR: // If callback returns MG_FALSE, Mongoose continues with err return MG_FALSE; // we don't handle errors - let mongoose do the work - // LUA not implemented - and will go away anyway - case MG_LUA: // Called before LSP page invoked // client services not used/implemented. Signal 'close connection' to be sure case MG_CONNECT: // If callback returns MG_FALSE, connect fails case MG_REPLY: // If callback returns MG_FALSE, Mongoose closes connection -- 2.39.5