From ff99c901e9482217fddd445d3ec94be8fdba17a4 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Fri, 30 May 2014 10:18:47 +0000 Subject: [PATCH] Updated to latest mongoose code in agreement with author (nw) --- src/emu/webengine.c | 288 +- src/emu/webengine.h | 10 +- src/lib/web/mongoose.c | 9762 +++++++++++++++++++--------------------- src/lib/web/mongoose.h | 456 +- 4 files changed, 4904 insertions(+), 5612 deletions(-) diff --git a/src/emu/webengine.c b/src/emu/webengine.c index c43b5fe54ff..f179653a5e7 100644 --- a/src/emu/webengine.c +++ b/src/emu/webengine.c @@ -20,34 +20,6 @@ // WEB ENGINE //************************************************************************** -void web_engine::websocket_ready_handler(struct mg_connection *conn) { - static const char *message = "update_machine"; - mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, message, strlen(message)); - m_websockets.append(*global_alloc(simple_list_wrapper(conn))); -} - -// Arguments: -// flags: first byte of websocket frame, see websocket RFC, -// http://tools.ietf.org/html/rfc6455, section 5.2 -// data, data_len: payload data. Mask, if any, is already applied. -int web_engine::websocket_data_handler(struct mg_connection *conn, int flags, - char *data, size_t data_len) -{ - // just Echo example for now - if ((flags & 0x0f) == WEBSOCKET_OPCODE_TEXT) - mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, data, data_len); - - // Returning zero means stoping websocket conversation. - // Close the conversation if client has sent us "exit" string. - return memcmp(data, "exit", 4); -} - -static void get_qsvar(const struct mg_request_info *request_info, - const char *name, char *dst, size_t dst_len) { - const char *qs = request_info->query_string; - mg_get_var(qs, strlen(qs == NULL ? "" : qs), name, dst, dst_len); -} - char* websanitize_statefilename ( char* unsanitized ) { // It's important that we remove any dangerous characters from any filename @@ -107,7 +79,8 @@ int web_engine::json_game_handler(struct mg_connection *conn) // Returning non-zero tells mongoose that our function has replied to // the client, and mongoose should not send client any more data. - return 1; + + return MG_TRUE; } int web_engine::json_slider_handler(struct mg_connection *conn) @@ -116,7 +89,7 @@ int web_engine::json_slider_handler(struct mg_connection *conn) astring tempstring; Json::Value array(Json::arrayValue); - /* add all sliders */ + // add all sliders for (curslider = machine().ui().get_slider_list(); curslider != NULL; curslider = curslider->next) { INT32 curval = (*curslider->update)(machine(), curslider->arg, &tempstring, SLIDER_NOCHANGE); @@ -130,7 +103,7 @@ int web_engine::json_slider_handler(struct mg_connection *conn) array.append(data); } - /* add all sliders */ + // add all sliders for (curslider = (slider_state*)machine().osd().get_slider_list(); curslider != NULL; curslider = curslider->next) { INT32 curval = (*curslider->update)(machine(), curslider->arg, &tempstring, SLIDER_NOCHANGE); @@ -154,28 +127,27 @@ int web_engine::json_slider_handler(struct mg_connection *conn) "%s", (int)strlen(json), json); - return 1; + return MG_TRUE; } // This function will be called by mongoose on every new request. int web_engine::begin_request_handler(struct mg_connection *conn) { - const struct mg_request_info *request_info = mg_get_request_info(conn); - if (!strncmp(request_info->uri, "/json/",6)) + if (!strncmp(conn->uri, "/json/",6)) { - if (!strcmp(request_info->uri, "/json/game")) + if (!strcmp(conn->uri, "/json/game")) { return json_game_handler(conn); } - if (!strcmp(request_info->uri, "/json/slider")) + if (!strcmp(conn->uri, "/json/slider")) { return json_slider_handler(conn); } } - else if (!strncmp(request_info->uri, "/cmd",4)) + else if (!strncmp(conn->uri, "/cmd",4)) { char cmd_name[64]; - get_qsvar(request_info, "name", cmd_name, sizeof(cmd_name)); + mg_get_var(conn, "name", cmd_name, sizeof(cmd_name)); if(!strcmp(cmd_name,"softreset")) { @@ -199,14 +171,14 @@ int web_engine::begin_request_handler(struct mg_connection *conn) else if(!strcmp(cmd_name,"savestate")) { char cmd_val[64]; - get_qsvar(request_info, "val", cmd_val, sizeof(cmd_val)); + mg_get_var(conn, "val", cmd_val, sizeof(cmd_val)); char *filename = websanitize_statefilename(cmd_val); m_machine->schedule_save(filename); } else if(!strcmp(cmd_name,"loadstate")) { char cmd_val[64]; - get_qsvar(request_info, "val", cmd_val, sizeof(cmd_val)); + mg_get_var(conn, "val", cmd_val, sizeof(cmd_val)); char *filename = cmd_val; m_machine->schedule_load(filename); } @@ -226,14 +198,14 @@ int web_engine::begin_request_handler(struct mg_connection *conn) // Returning non-zero tells mongoose that our function has replied to // the client, and mongoose should not send client any more data. - return 1; + return MG_TRUE; } - else if (!strncmp(request_info->uri, "/slider",7)) + else if (!strncmp(conn->uri, "/slider",7)) { char cmd_id[64]; char cmd_val[64]; - get_qsvar(request_info, "id", cmd_id, sizeof(cmd_id)); - get_qsvar(request_info, "val", cmd_val, sizeof(cmd_val)); + mg_get_var(conn, "id", cmd_id, sizeof(cmd_id)); + mg_get_var(conn, "val", cmd_val, sizeof(cmd_val)); int cnt = 0; int id = atoi(cmd_id); const slider_state *curslider; @@ -260,106 +232,107 @@ int web_engine::begin_request_handler(struct mg_connection *conn) // Returning non-zero tells mongoose that our function has replied to // the client, and mongoose should not send client any more data. - return 1; + return MG_TRUE; } - else if (!strncmp(request_info->uri, "/screenshot.png",15)) + else if (!strncmp(conn->uri, "/screenshot.png",15)) { - screen_device_iterator iter(m_machine->root_device()); - screen_device *screen = iter.first(); - - if (screen == NULL) + FILE *fp = (FILE *) conn->connection_param; + char buf[200]; + size_t n = 0; + if (fp == NULL) { - return 0; + screen_device_iterator iter(m_machine->root_device()); + screen_device *screen = iter.first(); + + if (screen == NULL) + { + return 0; + } + + astring fname("screenshot.png"); + { + emu_file file(m_machine->options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + file_error filerr = file.open(fname); + + if (filerr != FILERR_NONE) + { + return 0; + } + + m_machine->video().save_snapshot(screen, file); + astring fullpath(file.fullpath()); + file.close(); + } + + { + emu_file file(m_machine->options().snapshot_directory(), OPEN_FLAG_READ); + file_error filerr = file.open(fname); + + if (filerr != FILERR_NONE) + { + return 0; + } + + file.seek(0, SEEK_SET); + mg_send_header(conn, "Content-Type", "image/png"); + mg_send_header(conn, "Cache-Control", "no-cache, no-store, must-revalidate"); + mg_send_header(conn, "Pragma", "no-cache"); + mg_send_header(conn, "Expires", "0"); + do + { + n = file.read(buf, sizeof(buf)); + mg_send_data(conn, buf, n); + } + while (n==sizeof(buf)); + file.close(); + } } - - astring fname("screenshot.png"); - emu_file file(m_machine->options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - file_error filerr = file.open(fname); - - if (filerr != FILERR_NONE) - { - return 0; - } - - m_machine->video().save_snapshot(screen, file); - astring fullpath(file.fullpath()); - file.close(); - - mg_send_file(conn,fullpath); - return 1; + return MG_TRUE; } return 0; } -void *web_engine::websocket_keepalive() -{ - while(!m_exiting_core) - { - osd_ticks_t curtime = osd_ticks(); - if ((curtime - m_lastupdatetime) > osd_ticks_per_second() * 5) - { - m_lastupdatetime = curtime; - for (simple_list_wrapper *curitem = m_websockets.first(); curitem != NULL; curitem = curitem->next()) - { - int status = mg_websocket_write(curitem->object(), WEBSOCKET_OPCODE_PING, NULL, 0); - if (status==0) m_websockets.detach(*curitem); // remove inactive clients - } - } - osd_sleep(osd_ticks_per_second()/5); +static int ev_handler(struct mg_connection *conn, enum mg_event ev) { + if (ev == MG_REQUEST) { + if (conn->is_websocket) { + // This handler is called for each incoming websocket frame, one or more + // times for connection lifetime. + // Echo websocket data back to the client. + //const char *msg = "update_machine"; + //mg_websocket_write(conn, 1, msg, strlen(msg)); + return conn->content_len == 4 && !memcmp(conn->content, "exit", 4) ? MG_FALSE : MG_TRUE; + } else { + web_engine *engine = static_cast(conn->server_param); + return engine->begin_request_handler(conn); } + } else if (ev == MG_AUTH) { + return MG_TRUE; + } else { + return MG_FALSE; + } +} + +static int iterate_callback(struct mg_connection *c, enum mg_event ev) { + if (ev == MG_POLL && c->is_websocket) { + char buf[20]; + int len = snprintf(buf, sizeof(buf), "%lu", + (unsigned long) * (time_t *) c->callback_param); + mg_websocket_write(c, 1, buf, len); + } + return MG_TRUE; +} + +static void *serve(void *server) { + time_t current_timer = 0, last_timer = time(NULL); + for (;;) mg_poll_server((struct mg_server *) server, 1000); + current_timer = time(NULL); + if (current_timer - last_timer > 0) { + last_timer = current_timer; + mg_iterate_over_connections((struct mg_server *)server, iterate_callback, ¤t_timer); + } return NULL; } -//------------------------------------------------- -// static callbacks -//------------------------------------------------- -static void websocket_ready_handler_static(struct mg_connection *conn) -{ - const struct mg_request_info *request_info = mg_get_request_info(conn); - web_engine *engine = static_cast(request_info->user_data); - engine->websocket_ready_handler(conn); -} - -static int websocket_data_handler_static(struct mg_connection *conn, int flags, - char *data, size_t data_len) -{ - const struct mg_request_info *request_info = mg_get_request_info(conn); - web_engine *engine = static_cast(request_info->user_data); - return engine->websocket_data_handler(conn, flags, data, data_len); -} - -static int begin_request_handler_static(struct mg_connection *conn) -{ - const struct mg_request_info *request_info = mg_get_request_info(conn); - web_engine *engine = static_cast(request_info->user_data); - return engine->begin_request_handler(conn); -} - -static int begin_http_error_handler_static(struct mg_connection *conn, int status) -{ - //const struct mg_request_info *request_info = mg_get_request_info(conn); - if (status == 404) // 404 -- File Not Found - { - { - mg_printf(conn, - "HTTP/1.1 404 Not Found\r\n" - "Content-Type: text/plain\r\n" - "Content-Length: 14\r\n" // Always set Content-Length - "\r\n" - "Nothing to do."); - } - } - // Returning non-zero tells mongoose that our function has replied to - // the client, and mongoose should not send client any more data. - return 1; -} - -static void *websocket_keepalive_static(void *thread_func_param) -{ - web_engine *engine = static_cast(thread_func_param); - return engine->websocket_keepalive(); -} - //------------------------------------------------- // web_engine - constructor //------------------------------------------------- @@ -367,32 +340,18 @@ static void *websocket_keepalive_static(void *thread_func_param) web_engine::web_engine(emu_options &options) : m_options(options), m_machine(NULL), - m_ctx(NULL), + m_server(NULL), m_lastupdatetime(0), m_exiting_core(false) { - struct mg_callbacks callbacks; - - // List of options. Last element must be NULL. - const char *web_options[] = { - "listening_ports", options.http_port(), - "document_root", options.http_path(), - NULL - }; - - // Prepare callbacks structure. - memset(&callbacks, 0, sizeof(callbacks)); - callbacks.begin_request = begin_request_handler_static; - callbacks.websocket_ready = websocket_ready_handler_static; - callbacks.websocket_data = websocket_data_handler_static; - callbacks.http_error = begin_http_error_handler_static; - - // Start the web server. if (m_options.http()) { - m_ctx = mg_start(&callbacks, this, web_options); - - mg_start_thread(websocket_keepalive_static, this); + m_server = mg_create_server(this, ev_handler); + + mg_set_option(m_server, "listening_port", options.http_port()); + mg_set_option(m_server, "document_root", options.http_path()); + + mg_start_thread(serve, m_server); } } @@ -414,21 +373,20 @@ web_engine::~web_engine() void web_engine::close() { m_exiting_core = 1; - osd_sleep(osd_ticks_per_second()/5); - for (simple_list_wrapper *curitem = m_websockets.first(); curitem != NULL; curitem = curitem->next()) - { - mg_websocket_write(curitem->object(), WEBSOCKET_OPCODE_CONNECTION_CLOSE, NULL, 0); - } - // Stop the server. - mg_stop(m_ctx); + // Cleanup, and free server instance + mg_destroy_server(&m_server); } +static int websocket_callback(struct mg_connection *c, enum mg_event ev) { + if (c->is_websocket) { + const char *message = (const char *)c->callback_param; + mg_websocket_write(c, 1, message, strlen(message)); + } + return MG_TRUE; +} void web_engine::push_message(const char *message) { - for (simple_list_wrapper *curitem = m_websockets.first(); curitem != NULL; curitem = curitem->next()) - { - int status = mg_websocket_write(curitem->object(), WEBSOCKET_OPCODE_TEXT, message, strlen(message)); - if (status==0) m_websockets.remove(*curitem); // remove inactive clients - } + if (m_server!=NULL) + mg_iterate_over_connections(m_server, websocket_callback, (void*)message); } diff --git a/src/emu/webengine.h b/src/emu/webengine.h index a30826eb92d..85b5efa8342 100644 --- a/src/emu/webengine.h +++ b/src/emu/webengine.h @@ -13,7 +13,7 @@ #ifndef __WEB_ENGINE_H__ #define __WEB_ENGINE_H__ -struct mg_context; // Handle for the HTTP service itself +struct mg_server; // Handle for the HTTP server itself struct mg_connection; // Handle for the individual connection class web_engine @@ -27,12 +27,7 @@ public: void close(); void set_machine(running_machine &machine) { m_machine = &machine; } - - void websocket_ready_handler(struct mg_connection *conn); - int websocket_data_handler(struct mg_connection *conn, int flags, char *data, size_t data_len); int begin_request_handler(struct mg_connection *conn); - int begin_http_error_handler(struct mg_connection *conn, int status); - void *websocket_keepalive(); protected: // getters running_machine &machine() const { return *m_machine; } @@ -43,10 +38,9 @@ private: // internal state emu_options & m_options; running_machine * m_machine; - struct mg_context * m_ctx; + struct mg_server * m_server; osd_ticks_t m_lastupdatetime; bool m_exiting_core; - simple_list > m_websockets; }; #endif /* __web_engine_H__ */ diff --git a/src/lib/web/mongoose.c b/src/lib/web/mongoose.c index 346c37a6d9f..9ff2b54aeec 100644 --- a/src/lib/web/mongoose.c +++ b/src/lib/web/mongoose.c @@ -1,862 +1,1399 @@ -// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2013-2014 Cesanta Software Limited +// All rights reserved // -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: +// 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 . // -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. +// 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. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. +// Alternatively, you can license this library under a commercial +// license, as set out in . -#define NO_SSL -#define USE_WEBSOCKET - -#if defined(_WIN32) -#define SETSOCKOPT_CAST const char * +#ifdef NOEMBED_NET_SKELETON +#include "net_skeleton.h" #else -#define SETSOCKOPT_CAST void * +// net_skeleton start +// Copyright (c) 2014 Cesanta Software Limited +// All rights reserved +// +// 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 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 software under a commercial +// license, as set out in . + +#ifndef NS_SKELETON_HEADER_INCLUDED +#define NS_SKELETON_HEADER_INCLUDED + +#define NS_SKELETON_VERSION "1.1" + +#undef UNICODE // Use ANSI WinAPI functions +#undef _UNICODE // Use multibyte encoding on Windows +#define _MBCS // Use multibyte encoding on Windows +#define _INTEGRAL_MAX_BITS 64 // Enable _stati64() on Windows +#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005+ +#undef WIN32_LEAN_AND_MEAN // Let windows.h always include winsock2.h +#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 +#define _LARGEFILE_SOURCE // Enable fseeko() and ftello() functions +#define _FILE_OFFSET_BITS 64 // Enable 64-bit file offsets + +#ifdef _MSC_VER +#pragma warning (disable : 4127) // FD_SET() emits warning, disable it +#pragma warning (disable : 4204) // missing c99 support #endif -#if defined(_WIN32) -#if !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005 -#endif -#else -#ifdef __linux__ -#define _XOPEN_SOURCE 600 // For flockfile() on Linux -#endif -#define _LARGEFILE_SOURCE // Enable 64-bit file offsets -#define __STDC_FORMAT_MACROS // wants this for C++ -#define __STDC_LIMIT_MACROS // C++ wants that for INT64_MAX -#endif - -#if defined (_MSC_VER) -// conditional expression is constant: introduced by FD_SET(..) -#pragma warning (disable : 4127) -// non-constant aggregate initializer: issued due to missing C99 support -#pragma warning (disable : 4204) -#endif - -// Disable WIN32_LEAN_AND_MEAN. -// This makes windows.h always include winsock2.h -#ifdef WIN32_LEAN_AND_MEAN -#undef WIN32_LEAN_AND_MEAN -#endif - -#if defined(__SYMBIAN32__) -#define NO_SSL // SSL is not supported -#define NO_CGI // CGI is not supported -#define PATH_MAX FILENAME_MAX -#endif // __SYMBIAN32__ - -#ifndef _WIN32_WCE // Some ANSI #includes are not available on Windows CE #include #include -#include -#include -#include -#endif // !_WIN32_WCE - -#include -#include -#include #include -#include -#include -#include +#include +#include +#include #include #include +#include +#include +#include +#include -#if defined(_WIN32) && !defined(__SYMBIAN32__) // Windows specific -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0400 // To make it link in VS2005 -#include - -#ifndef PATH_MAX -#define PATH_MAX MAX_PATH +#ifdef _WIN32 +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") // Linking with winsock library #endif - -#ifndef _WIN32_WCE +#include #include -#include -#include -#else // _WIN32_WCE -#define NO_CGI // WinCE has no pipes - -typedef long off_t; - -#define errno GetLastError() -#define strerror(x) _ultoa(x, (char *) _alloca(sizeof(x) *3 ), 10) -#endif // _WIN32_WCE - -#define MAKEUQUAD(lo, hi) ((uint64_t)(((uint32_t)(lo)) | \ - ((uint64_t)((uint32_t)(hi))) << 32)) -#define RATE_DIFF 10000000 // 100 nsecs -#define EPOCH_DIFF MAKEUQUAD(0xd53e8000, 0x019db1de) -#define SYS2UNIX_TIME(lo, hi) \ - (time_t) ((MAKEUQUAD((lo), (hi)) - EPOCH_DIFF) / RATE_DIFF) - -// Visual Studio 6 does not know __func__ or __FUNCTION__ -// The rest of MS compilers use __FUNCTION__, not C99 __func__ -// Also use _strtoui64 on modern M$ compilers -#if defined(_MSC_VER) && _MSC_VER < 1300 +#ifndef EINPROGRESS +#define EINPROGRESS WSAEINPROGRESS +#endif +#ifndef EWOULDBLOCK +#define EWOULDBLOCK WSAEWOULDBLOCK +#endif +#ifndef __func__ #define STRX(x) #x #define STR(x) STRX(x) #define __func__ __FILE__ ":" STR(__LINE__) -#define strtoull(x, y, z) (unsigned __int64) _atoi64(x) -#define strtoll(x, y, z) _atoi64(x) -#else -#define __func__ __FUNCTION__ -#define strtoull(x, y, z) _strtoui64(x, y, z) -#define strtoll(x, y, z) _strtoi64(x, y, z) -#endif // _MSC_VER - -#define ERRNO GetLastError() -#define NO_SOCKLEN_T -#define SSL_LIB "ssleay32.dll" -#define CRYPTO_LIB "libeay32.dll" -#define O_NONBLOCK 0 -#if !defined(EWOULDBLOCK) -#define EWOULDBLOCK WSAEWOULDBLOCK -#endif // !EWOULDBLOCK -#define _POSIX_ -#define INT64_FMT "I64d" - -#define WINCDECL __cdecl -#define SHUT_WR 1 +#endif +#ifndef va_copy +#define va_copy(x,y) x = y +#endif // MINGW #defines va_copy #define snprintf _snprintf #define vsnprintf _vsnprintf -#define mg_sleep(x) Sleep(x) - -#define pipe(x) _pipe(x, MG_BUF_LEN, _O_BINARY) -#ifndef popen -#define popen(x, y) _popen(x, y) -#endif -#ifndef pclose -#define pclose(x) _pclose(x) -#endif -#define close(x) _close(x) -#define dlsym(x,y) GetProcAddress((HINSTANCE) (x), (y)) -#define RTLD_LAZY 0 -#define fseeko(x, y, z) _lseeki64(_fileno(x), (y), (z)) -#define fdopen(x, y) _fdopen((x), (y)) -#define write(x, y, z) _write((x), (y), (unsigned) z) -#define read(x, y, z) _read((x), (y), (unsigned) z) -#define flockfile(x) EnterCriticalSection(&global_log_file_lock) -#define funlockfile(x) LeaveCriticalSection(&global_log_file_lock) #define sleep(x) Sleep((x) * 1000) - -#if !defined(va_copy) -#define va_copy(x, y) x = y -#endif // !va_copy MINGW #defines va_copy - -#if !defined(fileno) -#define fileno(x) _fileno(x) -#endif // !fileno MINGW #defines fileno - -typedef HANDLE pthread_mutex_t; -typedef struct {HANDLE signal, broadcast;} pthread_cond_t; -typedef DWORD pthread_t; -#define pid_t HANDLE // MINGW typedefs pid_t to int. Using #define here. - -static int pthread_mutex_lock(pthread_mutex_t *); -static int pthread_mutex_unlock(pthread_mutex_t *); -static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len); -struct file; -static char *mg_fgets(char *buf, size_t size, struct file *filep, char **p); - -#if defined(HAVE_STDINT) -#include -#else -typedef unsigned int uint32_t; -typedef unsigned short uint16_t; +#define to64(x) _atoi64(x) +typedef int socklen_t; +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +typedef unsigned short uint16_t; typedef unsigned __int64 uint64_t; typedef __int64 int64_t; -#if defined(__GNUC__) || defined(_MSC_VER) -#define U64(val) val##ULL +typedef SOCKET sock_t; #else -#define U64(val) val -#endif -#define INT64_MAX U64(9223372036854775807) -#endif // HAVE_STDINT - -// POSIX dirent interface -struct dirent { - char d_name[PATH_MAX]; -}; - -typedef struct DIR { - HANDLE handle; - WIN32_FIND_DATAW info; - struct dirent result; -} DIR; - - -// Mark required libraries -#ifdef _MSC_VER -#pragma comment(lib, "Ws2_32.lib") -#endif - -#else // UNIX specific -#include -#include -#ifdef HAVE_POLL -#include -#endif -#include -#include -#include -#include -#include +#include +#include #include - -#include -#include -#include -#if !defined(NO_SSL_DL) && !defined(NO_SSL) -#include -#endif #include -#if defined(__MACH__) -#define SSL_LIB "libssl.dylib" -#define CRYPTO_LIB "libcrypto.dylib" -#else -#if !defined(SSL_LIB) -#define SSL_LIB "libssl.so" -#endif -#if !defined(CRYPTO_LIB) -#define CRYPTO_LIB "libcrypto.so" -#endif -#endif -#ifndef O_BINARY -#define O_BINARY 0 -#endif // O_BINARY -#define closesocket(a) close(a) -#define mg_mkdir(x, y) mkdir(x, y) -#define mg_remove(x) remove(x) -#define mg_sleep(x) usleep((x) * 1000) -#define ERRNO errno +#include +#include +#include // For inet_pton() when NS_ENABLE_IPV6 is defined +#include +#include +#include +#define closesocket(x) close(x) +#define __cdecl #define INVALID_SOCKET (-1) -#define INT64_FMT PRId64 -typedef int SOCKET; -#define WINCDECL - -#endif // End of Windows and UNIX specific includes - -#ifdef __OS2__ -#define NO_SOCKLEN_T -#define SHUT_WR 1 +#define to64(x) strtoll(x, NULL, 10) +typedef int sock_t; #endif -#ifndef HAVE_POLL -struct pollfd { - int fd; - short events; - short revents; +#ifdef NS_ENABLE_DEBUG +#define DBG(x) do { printf("%-20s ", __func__); printf x; putchar('\n'); \ + fflush(stdout); } while(0) +#else +#define DBG(x) +#endif + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) + +#ifdef NS_ENABLE_SSL +#ifdef __APPLE__ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif +#include +#else +typedef void *SSL; +typedef void *SSL_CTX; +#endif + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +union socket_address { + struct sockaddr sa; + struct sockaddr_in sin; +#ifdef NS_ENABLE_IPV6 + struct sockaddr_in6 sin6; +#endif }; -#define POLLIN 1 + +// IO buffers interface +struct iobuf { + char *buf; + size_t len; + size_t size; +}; + +void iobuf_init(struct iobuf *, size_t initial_size); +void iobuf_free(struct iobuf *); +size_t iobuf_append(struct iobuf *, const void *data, size_t data_size); +void iobuf_remove(struct iobuf *, size_t data_size); + +// Net skeleton interface +// Events. Meaning of event parameter (evp) is given in the comment. +enum ns_event { + NS_POLL, // Sent to each connection on each call to ns_server_poll() + NS_ACCEPT, // New connection accept()-ed. union socket_address *remote_addr + NS_CONNECT, // connect() succeeded or failed. int *success_status + NS_RECV, // Data has benn received. int *num_bytes + NS_SEND, // Data has been written to a socket. int *num_bytes + NS_CLOSE // Connection is closed. NULL +}; + +// Callback function (event handler) prototype, must be defined by user. +// Net skeleton will call event handler, passing events defined above. +struct ns_connection; +typedef void (*ns_callback_t)(struct ns_connection *, enum ns_event, void *evp); + +struct ns_server { + void *server_data; + sock_t listening_sock; + struct ns_connection *active_connections; + ns_callback_t callback; + SSL_CTX *ssl_ctx; + SSL_CTX *client_ssl_ctx; + sock_t ctl[2]; +}; + +struct ns_connection { + struct ns_connection *prev, *next; + struct ns_server *server; + sock_t sock; + union socket_address sa; + struct iobuf recv_iobuf; + struct iobuf send_iobuf; + SSL *ssl; + void *connection_data; + time_t last_io_time; + 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_WANT_READ (1 << 6) +#define NSF_WANT_WRITE (1 << 7) + +#define NSF_USER_1 (1 << 26) +#define NSF_USER_2 (1 << 27) +#define NSF_USER_3 (1 << 28) +#define NSF_USER_4 (1 << 29) +#define NSF_USER_5 (1 << 30) +#define NSF_USER_6 (1 << 31) +}; + +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_server_wakeup_ex(struct ns_server *, ns_callback_t, void *, size_t); +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); + +int ns_bind(struct ns_server *, const char *addr); +int ns_set_ssl_cert(struct ns_server *, const char *ssl_cert); +int ns_set_ssl_ca_cert(struct ns_server *, const char *ssl_ca_cert); +struct ns_connection *ns_connect(struct ns_server *, const char *host, + int port, int ssl, void *connection_param); + +int ns_send(struct ns_connection *, const void *buf, int len); +int ns_printf(struct ns_connection *, const char *fmt, ...); +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); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // NS_SKELETON_HEADER_INCLUDED +// Copyright (c) 2014 Cesanta Software Limited +// All rights reserved +// +// 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 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 software under a commercial +// license, as set out in . + + +#ifndef NS_MALLOC +#define NS_MALLOC malloc #endif +#ifndef NS_REALLOC +#define NS_REALLOC realloc +#endif + +#ifndef NS_FREE +#define NS_FREE free +#endif + +struct ctl_msg { + ns_callback_t callback; + char message[1024 * 8]; +}; + +void iobuf_init(struct iobuf *iobuf, size_t size) { + iobuf->len = iobuf->size = 0; + iobuf->buf = NULL; + + if (size > 0 && (iobuf->buf = (char *) NS_MALLOC(size)) != NULL) { + iobuf->size = size; + } +} + +void iobuf_free(struct iobuf *iobuf) { + if (iobuf != NULL) { + if (iobuf->buf != NULL) NS_FREE(iobuf->buf); + iobuf_init(iobuf, 0); + } +} + +size_t iobuf_append(struct iobuf *io, const void *buf, size_t len) { + char *p = NULL; + + assert(io != NULL); + assert(io->len <= io->size); + + if (len <= 0) { + } else if (io->len + len <= io->size) { + memcpy(io->buf + io->len, buf, len); + 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 += len; + io->size = io->len; + } else { + len = 0; + } + + return 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; + } +} + +#ifndef NS_DISABLE_THREADS +void *ns_start_thread(void *(*f)(void *), void *p) { +#ifdef _WIN32 + return (void *) _beginthread((void (__cdecl *)(void *)) f, 0, p); +#else + pthread_t thread_id = (pthread_t) 0; + pthread_attr_t attr; + + (void) pthread_attr_init(&attr); + (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + +#if NS_STACK_SIZE > 1 + (void) pthread_attr_setstacksize(&attr, NS_STACK_SIZE); +#endif + + pthread_create(&thread_id, &attr, f, p); + pthread_attr_destroy(&attr); + + return (void *) thread_id; +#endif +} +#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; + 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) conn->prev->next = conn->next; + if (conn->next) conn->next->prev = conn->prev; +} + +// 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) { + va_list ap_copy; + int len; + + va_copy(ap_copy, ap); + len = vsnprintf(*buf, size, fmt, ap_copy); + va_end(ap_copy); + + if (len < 0) { + // eCos and Windows are not standard-compliant and return -1 when + // the buffer is too small. Keep allocating larger buffers until we + // succeed or out of memory. + *buf = NULL; + while (len < 0) { + if (*buf) free(*buf); + size *= 2; + if ((*buf = (char *) NS_MALLOC(size)) == NULL) break; + va_copy(ap_copy, ap); + len = vsnprintf(*buf, size, fmt, ap_copy); + va_end(ap_copy); + } + } else if (len > (int) size) { + // Standard-compliant code path. Allocate a buffer that is large enough. + if ((*buf = (char *) NS_MALLOC(len + 1)) == NULL) { + len = -1; + } else { + va_copy(ap_copy, ap); + len = vsnprintf(*buf, len + 1, fmt, ap_copy); + va_end(ap_copy); + } + } + + return len; +} + +int ns_vprintf(struct ns_connection *conn, const char *fmt, va_list ap) { + char mem[2000], *buf = mem; + int len; + + if ((len = ns_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) { + iobuf_append(&conn->send_iobuf, buf, len); + } + if (buf != mem && buf != NULL) { + free(buf); + } + + return len; +} + +int ns_printf(struct ns_connection *conn, const char *fmt, ...) { + int len; + va_list ap; + va_start(ap, fmt); + len = ns_vprintf(conn, fmt, ap); + va_end(ap); + 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 ns_close_conn(struct ns_connection *conn) { + DBG(("%p %d", conn, conn->flags)); + ns_call(conn, NS_CLOSE, NULL); + ns_remove_conn(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); + } +#endif + NS_FREE(conn); +} + +void ns_set_close_on_exec(sock_t sock) { +#ifdef _WIN32 + (void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0); +#else + fcntl(sock, F_SETFD, FD_CLOEXEC); +#endif +} + +static void ns_set_non_blocking_mode(sock_t sock) { +#ifdef _WIN32 + unsigned long on = 1; + ioctlsocket(sock, FIONBIO, &on); +#else + int flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); +#endif +} + +#ifndef NS_DISABLE_SOCKETPAIR +int ns_socketpair2(sock_t sp[2], int sock_type) { + union socket_address sa; + sock_t sock; + socklen_t len = sizeof(sa.sin); + int ret = 0; + + sp[0] = sp[1] = INVALID_SOCKET; + + (void) memset(&sa, 0, sizeof(sa)); + 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; + } else { + if (sp[0] != INVALID_SOCKET) closesocket(sp[0]); + if (sp[1] != INVALID_SOCKET) closesocket(sp[1]); + sp[0] = sp[1] = INVALID_SOCKET; + } + 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) { + unsigned int a, b, c, d, port; + int len = 0; +#ifdef NS_ENABLE_IPV6 + char buf[100]; +#endif + + // MacOS needs that. If we do not zero it, subsequent bind() will fail. + // Also, all-zeroes in the socket address means binding to all addresses + // for both IPv4 and IPv6 (INADDR_ANY and IN6ADDR_ANY_INIT). + memset(sa, 0, sizeof(*sa)); + sa->sin.sin_family = AF_INET; + + 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 && + 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, "%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'; +} + +// '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 sock = INVALID_SOCKET; +#ifndef _WIN32 + int on = 1; +#endif + + if ((sock = socket(sa->sa.sa_family, SOCK_STREAM, 6)) != INVALID_SOCKET && +#ifndef _WIN32 + !setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on)) && +#endif + !bind(sock, &sa->sa, sa->sa.sa_family == AF_INET ? + sizeof(sa->sin) : sizeof(sa->sa)) && + !listen(sock, SOMAXCONN)) { + ns_set_non_blocking_mode(sock); + // In case port was set to 0, get the real port number + (void) getsockname(sock, &sa->sa, &len); + } else if (sock != INVALID_SOCKET) { + closesocket(sock); + sock = INVALID_SOCKET; + } + + return sock; +} + +// Certificate generation script is at +// https://github.com/cesanta/net_skeleton/blob/master/examples/gen_certs.sh +int ns_set_ssl_ca_cert(struct ns_server *server, const char *cert) { +#ifdef NS_ENABLE_SSL + STACK_OF(X509_NAME) *list = SSL_load_client_CA_file(cert); + if (cert != NULL && server->ssl_ctx != NULL && list != NULL) { + SSL_CTX_set_client_CA_list(server->ssl_ctx, list); + SSL_CTX_set_verify(server->ssl_ctx, SSL_VERIFY_PEER | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + return 0; + } +#endif + return server != NULL && cert == NULL ? 0 : -1; +} + +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) { + 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) { + return -2; + } else { + SSL_CTX_use_certificate_chain_file(server->ssl_ctx, cert); + return 0; + } +#endif + return server != NULL && cert == NULL ? 0 : -3; +} + +int ns_bind(struct ns_server *server, const char *str) { + union socket_address sa; + ns_parse_port_string(str, &sa); + if (server->listening_sock != INVALID_SOCKET) { + closesocket(server->listening_sock); + } + server->listening_sock = ns_open_listening_socket(&sa); + return server->listening_sock == INVALID_SOCKET ? -1 : + (int) ntohs(sa.sin.sin_port); +} + + +static struct ns_connection *accept_conn(struct ns_server *server) { + 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) { + } else if ((c = (struct ns_connection *) NS_MALLOC(sizeof(*c))) == NULL || + memset(c, 0, sizeof(*c)) == NULL) { + closesocket(sock); +#ifdef NS_ENABLE_SSL + } else if (server->ssl_ctx != NULL && + ((c->ssl = SSL_new(server->ssl_ctx)) == NULL || + SSL_set_fd(c->ssl, sock) != 1)) { + DBG(("SSL error")); + closesocket(sock); + free(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); + ns_call(c, NS_ACCEPT, &sa); + DBG(("%p %d %p %p", c, c->sock, c->ssl, server->ssl_ctx)); + } + + return c; +} + +static int ns_is_error(int n) { + return n == 0 || + (n < 0 && errno != EINTR && errno != EINPROGRESS && + errno != EAGAIN && errno != EWOULDBLOCK +#ifdef _WIN32 + && WSAGetLastError() != WSAEINTR && WSAGetLastError() != WSAEWOULDBLOCK +#endif + ); +} + +void ns_sock_to_str(sock_t sock, char *buf, size_t len, int flags) { + union socket_address sa; + socklen_t slen = sizeof(sa); + + 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, len); +#endif + } + if (flags & 2) { + snprintf(buf + strlen(buf), len - (strlen(buf) + 1), ":%d", + (int) ntohs(sa.sin.sin_port)); + } + } +} + +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) 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) n += snprintf(dst + n, dst_len - n, "%s", " "); + n += snprintf(dst + n, dst_len - n, " %s\n\n", ascii); + + return n; +} + +static void ns_read_from_socket(struct ns_connection *conn) { + char buf[2048]; + int n = 0; + + if (conn->flags & NSF_CONNECTING) { + int ok = 1, ret; + socklen_t len = sizeof(ok); + + ret = getsockopt(conn->sock, SOL_SOCKET, SO_ERROR, (char *) &ok, &len); + (void) ret; +#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 %d wres %d %d", conn, conn->flags, res, ssl_err)); + if (ssl_err == SSL_ERROR_WANT_READ) conn->flags |= NSF_WANT_READ; + if (ssl_err == SSL_ERROR_WANT_WRITE) conn->flags |= NSF_WANT_WRITE; + if (res == 1) { + 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; + } + } +#endif + conn->flags &= ~NSF_CONNECTING; + DBG(("%p ok=%d", conn, ok)); + if (ok != 0) { + conn->flags |= NSF_CLOSE_IMMEDIATELY; + } + ns_call(conn, NS_CONNECT, &ok); + return; + } + +#ifdef NS_ENABLE_SSL + if (conn->ssl != NULL) { + if (conn->flags & NSF_SSL_HANDSHAKE_DONE) { + n = SSL_read(conn->ssl, buf, sizeof(buf)); + } else { + int res = SSL_accept(conn->ssl); + int ssl_err = SSL_get_error(conn->ssl, res); + DBG(("%p %d rres %d %d", conn, conn->flags, res, ssl_err)); + if (ssl_err == SSL_ERROR_WANT_READ) conn->flags |= NSF_WANT_READ; + if (ssl_err == SSL_ERROR_WANT_WRITE) conn->flags |= NSF_WANT_WRITE; + if (res == 1) { + 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 { + conn->flags |= NSF_CLOSE_IMMEDIATELY; + } + return; + } + } else +#endif + { + n = recv(conn->sock, buf, sizeof(buf), 0); + } + + DBG(("%p %d <- %d bytes", conn, conn->flags, n)); + + 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); + } +} + +static void ns_write_to_socket(struct ns_connection *conn) { + struct iobuf *io = &conn->send_iobuf; + int n = 0; + +#ifdef NS_ENABLE_SSL + if (conn->ssl != NULL) { + n = SSL_write(conn->ssl, io->buf, io->len); + if (n < 0) { + int ssl_err = SSL_get_error(conn->ssl, n); + DBG(("%p %d %d", conn, n, ssl_err)); + if (ssl_err == 2 || ssl_err == 3) { + return; // Call us again + } else { + conn->flags |= NSF_CLOSE_IMMEDIATELY; + } + } + } else +#endif + { n = send(conn->sock, io->buf, io->len, 0); } + + 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); + } + + if (io->len == 0 && conn->flags & NSF_FINISHED_SENDING_DATA) { + conn->flags |= NSF_CLOSE_IMMEDIATELY; + } +} + +int ns_send(struct ns_connection *conn, const void *buf, int len) { + return iobuf_append(&conn->send_iobuf, buf, len); +} + +static void ns_add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) { + if (sock != INVALID_SOCKET) { + FD_SET(sock, set); + if (*max_fd == INVALID_SOCKET || sock > *max_fd) { + *max_fd = sock; + } + } +} + +int ns_server_poll(struct ns_server *server, 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); + + for (conn = server->active_connections; conn != NULL; conn = tmp_conn) { + tmp_conn = conn->next; + ns_call(conn, NS_POLL, ¤t_time); + 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); + } + if (conn->flags & NSF_CLOSE_IMMEDIATELY) { + ns_close_conn(conn); + } + } + + tv.tv_sec = milli / 1000; + 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; + } + } + + // Read wakeup messages + if (server->ctl[1] != INVALID_SOCKET && + FD_ISSET(server->ctl[1], &read_set)) { + struct ctl_msg ctl_msg; + int len = recv(server->ctl[1], (char *) &ctl_msg, sizeof(ctl_msg), 0); + send(server->ctl[1], ctl_msg.message, 1, 0); + if (len >= (int) sizeof(ctl_msg.callback) && ctl_msg.callback != NULL) { + ns_iterate(server, ctl_msg.callback, ctl_msg.message); + } + } + + for (conn = server->active_connections; conn != NULL; conn = tmp_conn) { + tmp_conn = conn->next; + //DBG(("%p LOOP %p", conn, conn->ssl)); + if (FD_ISSET(conn->sock, &read_set)) { + 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); + } else if (!(conn->flags & NSF_BUFFER_BUT_DONT_SEND)) { + conn->last_io_time = current_time; + ns_write_to_socket(conn); + } + } + } + } + + for (conn = server->active_connections; conn != NULL; conn = tmp_conn) { + tmp_conn = conn->next; + num_active_connections++; + if (conn->flags & NSF_CLOSE_IMMEDIATELY) { + ns_close_conn(conn); + } + } + //DBG(("%d active connections", num_active_connections)); + + return num_active_connections; +} + +struct ns_connection *ns_connect(struct ns_server *server, const char *host, + int port, int use_ssl, void *param) { + sock_t sock = INVALID_SOCKET; + struct sockaddr_in sin; + struct hostent *he = NULL; + struct ns_connection *conn = NULL; + int connect_ret_val; + + (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))); + 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); + + connect_ret_val = connect(sock, (struct sockaddr *) &sin, sizeof(sin)); + if (ns_is_error(connect_ret_val)) { + closesocket(sock); + return NULL; + } else if ((conn = (struct ns_connection *) + NS_MALLOC(sizeof(*conn))) == 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); + +#ifdef NS_ENABLE_SSL + if (use_ssl && + (conn->ssl = SSL_new(server->client_ssl_ctx)) != NULL) { + SSL_set_fd(conn->ssl, sock); + } +#endif + + ns_add_conn(server, conn); + DBG(("%p %s:%d %d %p", conn, host, port, conn->sock, conn->ssl)); + + return conn; +} + +struct ns_connection *ns_add_sock(struct ns_server *s, sock_t sock, void *p) { + 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); + conn->sock = sock; + conn->connection_data = p; + conn->server = s; + conn->last_io_time = time(NULL); + ns_add_conn(s, conn); + DBG(("%p %d", conn, sock)); + } + 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); + } +} + +void ns_server_wakeup_ex(struct ns_server *server, ns_callback_t cb, + void *data, size_t len) { + struct ctl_msg ctl_msg; + if (server->ctl[0] != INVALID_SOCKET && data != NULL && + len < sizeof(ctl_msg.message)) { + ctl_msg.callback = cb; + memcpy(ctl_msg.message, data, len); + send(server->ctl[0], (char *) &ctl_msg, + offsetof(struct ctl_msg, message) + len, 0); + recv(server->ctl[0], (char *) &len, 1, 0); + } +} + +void ns_server_wakeup(struct ns_server *server) { + ns_server_wakeup_ex(server, NULL, (void *) "", 0); +} + +void ns_server_init(struct ns_server *s, void *server_data, ns_callback_t cb) { + memset(s, 0, sizeof(*s)); + s->listening_sock = s->ctl[0] = s->ctl[1] = INVALID_SOCKET; + s->server_data = server_data; + s->callback = cb; + +#ifdef _WIN32 + { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); } +#else + // Ignore SIGPIPE signal, so if client cancels the request, it + // won't kill the whole process. + signal(SIGPIPE, SIG_IGN); +#endif + +#ifndef NS_DISABLE_SOCKETPAIR + do { + 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()); +#endif +} + +void ns_server_free(struct ns_server *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); + + 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; + + 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); + s->ssl_ctx = s->client_ssl_ctx = NULL; +#endif +} +// net_skeleton end +#endif // NOEMBED_NET_SKELETON + +#include + +#ifdef _WIN32 //////////////// Windows specific defines and includes +#include // For _lseeki64 +#include // For _mkdir +#ifndef S_ISDIR +#define S_ISDIR(x) ((x) & _S_IFDIR) +#endif +#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 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) +#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)) +#define flockfile(x) ((void) (x)) +#define funlockfile(x) ((void) (x)) +typedef struct _stati64 file_stat_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" -#define MONGOOSE_VERSION "3.8" -#define PASSWORDS_FILE_NAME ".htpasswd" -#define CGI_ENVIRONMENT_SIZE 4096 -#define MAX_CGI_ENVIR_VARS 64 -#define MG_BUF_LEN 8192 #define MAX_REQUEST_SIZE 16384 -#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) +#define IOBUF_SIZE 8192 +#define MAX_PATH_SIZE 8192 +#define DEFAULT_CGI_PATTERN "**.cgi$|**.pl$|**.php$" +#define CGI_ENVIRONMENT_SIZE 8192 +#define MAX_CGI_ENVIR_VARS 64 +#define ENV_EXPORT_TO_CGI "MONGOOSE_CGI" +#define PASSWORDS_FILE_NAME ".htpasswd" -#ifdef _WIN32 -static CRITICAL_SECTION global_log_file_lock; -#if !defined(NO_SSL) -static pthread_t pthread_self(void) { - return GetCurrentThreadId(); -} -#endif -#endif // _WIN32 - -#ifdef DEBUG_TRACE -#undef DEBUG_TRACE -#define DEBUG_TRACE(x) -#else -#if defined(DEBUG) -#define DEBUG_TRACE(x) do { \ - flockfile(stdout); \ - printf("*** %lu.%p.%s.%d: ", \ - (unsigned long) time(NULL), (void *) pthread_self(), \ - __func__, __LINE__); \ - printf x; \ - putchar('\n'); \ - fflush(stdout); \ - funlockfile(stdout); \ -} while (0) -#else -#define DEBUG_TRACE(x) -#endif // DEBUG -#endif // DEBUG_TRACE - -// Darwin prior to 7.0 and Win32 do not have socklen_t -#ifdef NO_SOCKLEN_T -typedef int socklen_t; -#endif // NO_SOCKLEN_T -#define _DARWIN_UNLIMITED_SELECT - -#define IP_ADDR_STR_LEN 50 // IPv6 hex string is 46 chars - -#if !defined(MSG_NOSIGNAL) -#define MSG_NOSIGNAL 0 +#ifndef MONGOOSE_USE_WEBSOCKET_PING_INTERVAL +#define MONGOOSE_USE_WEBSOCKET_PING_INTERVAL 5 #endif -#if !defined(SOMAXCONN) -#define SOMAXCONN 100 +// Extra HTTP headers to send in every static file reply +#if !defined(MONGOOSE_USE_EXTRA_HTTP_HEADERS) +#define MONGOOSE_USE_EXTRA_HTTP_HEADERS "" #endif -#if !defined(PATH_MAX) -#define PATH_MAX 4096 +#ifndef MONGOOSE_POST_SIZE_LIMIT +#define MONGOOSE_POST_SIZE_LIMIT 0 #endif -static const char *http_500_error = "Internal Server Error"; - -#if defined(NO_SSL_DL) -#include -#else -// SSL loaded dynamically from DLL. -// I put the prototypes here to be independent from OpenSSL source installation. -typedef struct ssl_st SSL; -typedef struct ssl_method_st SSL_METHOD; -typedef struct ssl_ctx_st SSL_CTX; - -struct ssl_func { - const char *name; // SSL function name - void (*ptr)(void); // Function pointer -}; - -#define SSL_free (* (void (*)(SSL *)) ssl_sw[0].ptr) -#define SSL_accept (* (int (*)(SSL *)) ssl_sw[1].ptr) -#define SSL_connect (* (int (*)(SSL *)) ssl_sw[2].ptr) -#define SSL_read (* (int (*)(SSL *, void *, int)) ssl_sw[3].ptr) -#define SSL_write (* (int (*)(SSL *, const void *,int)) ssl_sw[4].ptr) -#define SSL_get_error (* (int (*)(SSL *, int)) ssl_sw[5].ptr) -#define SSL_set_fd (* (int (*)(SSL *, SOCKET)) ssl_sw[6].ptr) -#define SSL_new (* (SSL * (*)(SSL_CTX *)) ssl_sw[7].ptr) -#define SSL_CTX_new (* (SSL_CTX * (*)(SSL_METHOD *)) ssl_sw[8].ptr) -#define SSLv23_server_method (* (SSL_METHOD * (*)(void)) ssl_sw[9].ptr) -#define SSL_library_init (* (int (*)(void)) ssl_sw[10].ptr) -#define SSL_CTX_use_PrivateKey_file (* (int (*)(SSL_CTX *, \ - const char *, int)) ssl_sw[11].ptr) -#define SSL_CTX_use_certificate_file (* (int (*)(SSL_CTX *, \ - const char *, int)) ssl_sw[12].ptr) -#define SSL_CTX_set_default_passwd_cb \ - (* (void (*)(SSL_CTX *, mg_callback_t)) ssl_sw[13].ptr) -#define SSL_CTX_free (* (void (*)(SSL_CTX *)) ssl_sw[14].ptr) -#define SSL_load_error_strings (* (void (*)(void)) ssl_sw[15].ptr) -#define SSL_CTX_use_certificate_chain_file \ - (* (int (*)(SSL_CTX *, const char *)) ssl_sw[16].ptr) -#define SSLv23_client_method (* (SSL_METHOD * (*)(void)) ssl_sw[17].ptr) -#define SSL_pending (* (int (*)(SSL *)) ssl_sw[18].ptr) -#define SSL_CTX_set_verify (* (void (*)(SSL_CTX *, int, int)) ssl_sw[19].ptr) -#define SSL_shutdown (* (int (*)(SSL *)) ssl_sw[20].ptr) - -#define CRYPTO_num_locks (* (int (*)(void)) crypto_sw[0].ptr) -#define CRYPTO_set_locking_callback \ - (* (void (*)(void (*)(int, int, const char *, int))) crypto_sw[1].ptr) -#define CRYPTO_set_id_callback \ - (* (void (*)(unsigned long (*)(void))) crypto_sw[2].ptr) -#define ERR_get_error (* (unsigned long (*)(void)) crypto_sw[3].ptr) -#define ERR_error_string (* (char * (*)(unsigned long,char *)) crypto_sw[4].ptr) - -// set_ssl_option() function updates this array. -// It loads SSL library dynamically and changes NULLs to the actual addresses -// of respective functions. The macros above (like SSL_connect()) are really -// just calling these functions indirectly via the pointer. -static struct ssl_func ssl_sw[] = { - {"SSL_free", NULL}, - {"SSL_accept", NULL}, - {"SSL_connect", NULL}, - {"SSL_read", NULL}, - {"SSL_write", NULL}, - {"SSL_get_error", NULL}, - {"SSL_set_fd", NULL}, - {"SSL_new", NULL}, - {"SSL_CTX_new", NULL}, - {"SSLv23_server_method", NULL}, - {"SSL_library_init", NULL}, - {"SSL_CTX_use_PrivateKey_file", NULL}, - {"SSL_CTX_use_certificate_file",NULL}, - {"SSL_CTX_set_default_passwd_cb",NULL}, - {"SSL_CTX_free", NULL}, - {"SSL_load_error_strings", NULL}, - {"SSL_CTX_use_certificate_chain_file", NULL}, - {"SSLv23_client_method", NULL}, - {"SSL_pending", NULL}, - {"SSL_CTX_set_verify", NULL}, - {"SSL_shutdown", NULL}, - {NULL, NULL} -}; - -// Similar array as ssl_sw. These functions could be located in different lib. -#if !defined(NO_SSL) -static struct ssl_func crypto_sw[] = { - {"CRYPTO_num_locks", NULL}, - {"CRYPTO_set_locking_callback", NULL}, - {"CRYPTO_set_id_callback", NULL}, - {"ERR_get_error", NULL}, - {"ERR_error_string", NULL}, - {NULL, NULL} -}; -#endif // NO_SSL -#endif // NO_SSL_DL - -static const char *month_names[] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" -}; - -// Unified socket address. For IPv6 support, add IPv6 address structure -// in the union u. -union usa { - struct sockaddr sa; - struct sockaddr_in sin; -#if defined(USE_IPV6) - struct sockaddr_in6 sin6; +#ifndef MONGOOSE_IDLE_TIMEOUT_SECONDS +#define MONGOOSE_IDLE_TIMEOUT_SECONDS 30 +#endif + +#ifdef MONGOOSE_NO_SOCKETPAIR +#define MONGOOSE_NO_CGI +#endif + +#ifdef MONGOOSE_NO_FILESYSTEM +#define MONGOOSE_NO_AUTH +#define MONGOOSE_NO_CGI +#define MONGOOSE_NO_DAV +#define MONGOOSE_NO_DIRECTORY_LISTING +#define MONGOOSE_NO_LOGGING +#define MONGOOSE_NO_SSI +#define MONGOOSE_NO_DL #endif -}; -// Describes a string (chunk of memory). struct vec { - const char *ptr; - size_t len; + const char *ptr; + int len; }; -struct file { - int is_directory; - time_t modification_time; - int64_t size; - FILE *fp; - const char *membuf; // Non-NULL if file data is in memory - // set to 1 if the content is gzipped - // in which case we need a content-encoding: gzip header - int gzipped; -}; -#define STRUCT_FILE_INITIALIZER {0, 0, 0, NULL, NULL, 0} - -// Describes listening socket, or socket which was accept()-ed by the master -// thread and queued for future handling by the worker thread. -struct socket { - SOCKET sock; // Listening socket - union usa lsa; // Local socket address - union usa rsa; // Remote socket address - unsigned is_ssl:1; // Is port SSL-ed - unsigned ssl_redir:1; // Is port supposed to redirect everything to SSL port +// For directory listing and WevDAV support +struct dir_entry { + struct connection *conn; + char *file_name; + file_stat_t st; }; -// NOTE(lsm): this enum shoulds be in sync with the config_options below. +// NOTE(lsm): this enum shoulds be in sync with the config_options. enum { - CGI_EXTENSIONS, CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER, - PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS, THROTTLE, - ACCESS_LOG_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE, - GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST, - EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE, - NUM_THREADS, RUN_AS_USER, REWRITE, HIDE_FILES, REQUEST_TIMEOUT, - NUM_OPTIONS -}; - -static const char *config_options[] = { - "cgi_pattern", "**.cgi$|**.pl$|**.php$", - "cgi_environment", NULL, - "put_delete_auth_file", NULL, - "cgi_interpreter", NULL, - "protect_uri", NULL, - "authentication_domain", "mydomain.com", - "ssi_pattern", "**.shtml$|**.shtm$", - "throttle", NULL, - "access_log_file", NULL, - "enable_directory_listing", "yes", - "error_log_file", NULL, - "global_auth_file", NULL, - "index_files", - "index.html,index.htm,index.cgi,index.shtml,index.php,index.lp", - "enable_keep_alive", "no", - "access_control_list", NULL, - "extra_mime_types", NULL, - "listening_ports", "8080", - "document_root", ".", - "ssl_certificate", NULL, - "num_threads", "50", - "run_as_user", NULL, - "url_rewrite_patterns", NULL, - "hide_files_patterns", NULL, - "request_timeout_ms", "30000", - NULL -}; - -struct mg_context { - volatile int stop_flag; // Should we stop event loop - SSL_CTX *ssl_ctx; // SSL context - char *config[NUM_OPTIONS]; // Mongoose configuration parameters - struct mg_callbacks callbacks; // User-defined callback function - void *user_data; // User-defined data - - struct socket *listening_sockets; - int num_listening_sockets; - - volatile int num_threads; // Number of threads - pthread_mutex_t mutex; // Protects (max|num)_threads - pthread_cond_t cond; // Condvar for tracking workers terminations - - struct socket queue[20]; // Accepted sockets - volatile int sq_head; // Head of the socket queue - volatile int sq_tail; // Tail of the socket queue - pthread_cond_t sq_full; // Signaled when socket is produced - pthread_cond_t sq_empty; // Signaled when socket is consumed -}; - -struct mg_connection { - struct mg_request_info request_info; - struct mg_context *ctx; - SSL *ssl; // SSL descriptor - SSL_CTX *client_ssl_ctx; // SSL context for client connections - struct socket client; // Connected client - time_t birth_time; // Time when request was received - int64_t num_bytes_sent; // Total bytes sent to client - int64_t content_len; // Content-Length header value - int64_t consumed_content; // How many bytes of content have been read - char *buf; // Buffer for received data - char *path_info; // PATH_INFO part of the URL - int must_close; // 1 if connection must be closed - int buf_size; // Buffer size - int request_len; // Size of the request + headers in a buffer - int data_len; // Total size of data in a buffer - int status_code; // HTTP reply status code, e.g. 200 - int throttle; // Throttling, bytes/sec. <= 0 means no throttle - time_t last_throttle_time; // Last time throttled data was sent - int64_t last_throttle_bytes;// Bytes sent this second -}; - -// Directory entry -struct de { - struct mg_connection *conn; - char *file_name; - struct file file; -}; - -const char **mg_get_valid_option_names(void) { - return config_options; -} - -static int is_file_in_memory(struct mg_connection *conn, const char *path, - struct file *filep) { - size_t size = 0; - if ((filep->membuf = conn->ctx->callbacks.open_file == NULL ? NULL : - conn->ctx->callbacks.open_file(conn, path, &size)) != NULL) { - // NOTE: override filep->size only on success. Otherwise, it might break - // constructs like if (!mg_stat() || !mg_fopen()) ... - filep->size = size; - } - return filep->membuf != NULL; -} - -static int is_file_opened(const struct file *filep) { - return filep->membuf != NULL || filep->fp != NULL; -} - -static int mg_fopen(struct mg_connection *conn, const char *path, - const char *mode, struct file *filep) { - if (!is_file_in_memory(conn, path, filep)) { -#ifdef _WIN32 - wchar_t wbuf[PATH_MAX], wmode[20]; - to_unicode(path, wbuf, ARRAY_SIZE(wbuf)); - MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode)); - filep->fp = _wfopen(wbuf, wmode); -#else - filep->fp = fopen(path, mode); + ACCESS_CONTROL_LIST, +#ifndef MONGOOSE_NO_FILESYSTEM + ACCESS_LOG_FILE, +#ifndef MONGOOSE_NO_AUTH + AUTH_DOMAIN, #endif - } - - return is_file_opened(filep); -} - -static void mg_fclose(struct file *filep) { - if (filep != NULL && filep->fp != NULL) { - fclose(filep->fp); - } -} - -static int get_option_index(const char *name) { - int i; - - for (i = 0; config_options[i * 2] != NULL; i++) { - if (strcmp(config_options[i * 2], name) == 0) { - return i; - } - } - return -1; -} - -const char *mg_get_option(const struct mg_context *ctx, const char *name) { - int i; - if ((i = get_option_index(name)) == -1) { - return NULL; - } else if (ctx->config[i] == NULL) { - return ""; - } else { - return ctx->config[i]; - } -} - -static void sockaddr_to_string(char *buf, size_t len, - const union usa *usa) { - buf[0] = '\0'; -#if defined(USE_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); +#ifndef MONGOOSE_NO_CGI + CGI_INTERPRETER, + CGI_PATTERN, #endif + DAV_AUTH_FILE, + DOCUMENT_ROOT, +#ifndef MONGOOSE_NO_DIRECTORY_LISTING + ENABLE_DIRECTORY_LISTING, +#endif +#endif + EXTRA_MIME_TYPES, +#if !defined(MONGOOSE_NO_FILESYSTEM) && !defined(MONGOOSE_NO_AUTH) + GLOBAL_AUTH_FILE, +#endif +#ifndef MONGOOSE_NO_FILESYSTEM + HIDE_FILES_PATTERN, + HEXDUMP_FILE, + INDEX_FILES, +#endif + LISTENING_PORT, +#ifndef _WIN32 + RUN_AS_USER, +#endif +#ifndef MONGOOSE_NO_SSI + SSI_PATTERN, +#endif +#ifdef NS_ENABLE_SSL + SSL_CERTIFICATE, + SSL_CA_CERTIFICATE, + SSL_MITM_CERTS, +#endif + URL_REWRITES, + NUM_OPTIONS +}; + +static const char *static_config_options[] = { + "access_control_list", NULL, +#ifndef MONGOOSE_NO_FILESYSTEM + "access_log_file", NULL, +#ifndef MONGOOSE_NO_AUTH + "auth_domain", "mydomain.com", +#endif +#ifndef MONGOOSE_NO_CGI + "cgi_interpreter", NULL, + "cgi_pattern", DEFAULT_CGI_PATTERN, +#endif + "dav_auth_file", NULL, + "document_root", NULL, +#ifndef MONGOOSE_NO_DIRECTORY_LISTING + "enable_directory_listing", "yes", +#endif +#endif + "extra_mime_types", NULL, +#if !defined(MONGOOSE_NO_FILESYSTEM) && !defined(MONGOOSE_NO_AUTH) + "global_auth_file", NULL, +#endif +#ifndef MONGOOSE_NO_FILESYSTEM + "hide_files_patterns", NULL, + "hexdump_file", NULL, + "index_files","index.html,index.htm,index.shtml,index.cgi,index.php,index.lp", +#endif + "listening_port", NULL, +#ifndef _WIN32 + "run_as_user", NULL, +#endif +#ifndef MONGOOSE_NO_SSI + "ssi_pattern", "**.shtml$|**.shtm$", +#endif +#ifdef NS_ENABLE_SSL + "ssl_certificate", NULL, + "ssl_ca_certificate", NULL, + "ssl_mitm_certs", NULL, +#endif + "url_rewrites", NULL, + NULL +}; + +struct mg_server { + struct ns_server ns_server; + union socket_address lsa; // Listening socket address + mg_handler_t event_handler; + char *config_options[NUM_OPTIONS]; +}; + +// Local endpoint representation +union endpoint { + 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, 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 + +struct connection { + 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 cl; // Reply content length, for Range support + int request_len; // Request length, including last \r\n after last header +}; + +#define MG_CONN_2_CONN(c) ((struct connection *) ((char *) (c) - \ + offsetof(struct connection, mg_conn))) + +static void open_local_endpoint(struct connection *conn, int skip_user); +static void close_local_endpoint(struct connection *conn); + +static const struct { + const char *extension; + size_t ext_len; + const char *mime_type; +} static_builtin_mime_types[] = { + {".html", 5, "text/html"}, + {".htm", 4, "text/html"}, + {".shtm", 5, "text/html"}, + {".shtml", 6, "text/html"}, + {".css", 4, "text/css"}, + {".js", 3, "application/x-javascript"}, + {".ico", 4, "image/x-icon"}, + {".gif", 4, "image/gif"}, + {".jpg", 4, "image/jpeg"}, + {".jpeg", 5, "image/jpeg"}, + {".png", 4, "image/png"}, + {".svg", 4, "image/svg+xml"}, + {".txt", 4, "text/plain"}, + {".torrent", 8, "application/x-bittorrent"}, + {".wav", 4, "audio/x-wav"}, + {".mp3", 4, "audio/x-mp3"}, + {".mid", 4, "audio/mid"}, + {".m3u", 4, "audio/x-mpegurl"}, + {".ogg", 4, "application/ogg"}, + {".ram", 4, "audio/x-pn-realaudio"}, + {".xml", 4, "text/xml"}, + {".json", 5, "text/json"}, + {".xslt", 5, "application/xml"}, + {".xsl", 4, "application/xml"}, + {".ra", 3, "audio/x-pn-realaudio"}, + {".doc", 4, "application/msword"}, + {".exe", 4, "application/octet-stream"}, + {".zip", 4, "application/x-zip-compressed"}, + {".xls", 4, "application/excel"}, + {".tgz", 4, "application/x-tar-gz"}, + {".tar", 4, "application/x-tar"}, + {".gz", 3, "application/x-gunzip"}, + {".arj", 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"}, + {".mpg", 4, "video/mpeg"}, + {".webm", 5, "video/webm"}, + {".mpeg", 5, "video/mpeg"}, + {".mov", 4, "video/quicktime"}, + {".mp4", 4, "video/mp4"}, + {".m4v", 4, "video/x-m4v"}, + {".asf", 4, "video/x-ms-asf"}, + {".avi", 4, "video/x-msvideo"}, + {".bmp", 4, "image/bmp"}, + {".ttf", 4, "application/x-font-ttf"}, + {NULL, 0, NULL} +}; + +#ifndef MONGOOSE_NO_THREADS +void *mg_start_thread(void *(*f)(void *), void *p) { + return ns_start_thread(f, p); +} +#endif // MONGOOSE_NO_THREADS + +#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'; + + // Convert to Unicode and back. If doubly-converted string does not + // match the original, something is fishy, reject. + memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), + NULL, NULL); + if (strcmp(buf, buf2) != 0) { + wbuf[0] = L'\0'; + } } -static void cry(struct mg_connection *conn, - PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3); - -// Print error message to the opened error log stream. -static void cry(struct mg_connection *conn, const char *fmt, ...) { - char buf[MG_BUF_LEN], src_addr[IP_ADDR_STR_LEN]; - va_list ap; - FILE *fp; - time_t timestamp; - - va_start(ap, fmt); - (void) vsnprintf(buf, sizeof(buf), fmt, ap); - va_end(ap); - - // Do not lock when getting the callback value, here and below. - // I suppose this is fine, since function cannot disappear in the - // same way string option can. - if (conn->ctx->callbacks.log_message == NULL || - conn->ctx->callbacks.log_message(conn, buf) == 0) { - fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL : - fopen(conn->ctx->config[ERROR_LOG_FILE], "a+"); - - if (fp != NULL) { - flockfile(fp); - timestamp = time(NULL); - - sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa); - fprintf(fp, "[%010lu] [error] [client %s] ", (unsigned long) timestamp, - src_addr); - - if (conn->request_info.request_method != NULL) { - fprintf(fp, "%s %s: ", conn->request_info.request_method, - conn->request_info.uri); - } - - fprintf(fp, "%s", buf); - fputc('\n', fp); - funlockfile(fp); - fclose(fp); - } - } +static int mg_stat(const char *path, file_stat_t *st) { + wchar_t wpath[MAX_PATH_SIZE]; + to_wchar(path, wpath, ARRAY_SIZE(wpath)); + DBG(("[%ls] -> %d", wpath, _wstati64(wpath, st))); + return _wstati64(wpath, st); } -// Return fake connection structure. Used for logging, if connection -// is not applicable at the moment of logging. -static struct mg_connection *fc(struct mg_context *ctx) { - static struct mg_connection fake_connection; - fake_connection.ctx = ctx; - return &fake_connection; +static FILE *mg_fopen(const char *path, const char *mode) { + wchar_t wpath[MAX_PATH_SIZE], wmode[10]; + to_wchar(path, wpath, ARRAY_SIZE(wpath)); + to_wchar(mode, wmode, ARRAY_SIZE(wmode)); + return _wfopen(wpath, wmode); } -const char *mg_version(void) { - return MONGOOSE_VERSION; -} - -struct mg_request_info *mg_get_request_info(struct mg_connection *conn) { - return &conn->request_info; -} - -static void mg_strlcpy(register char *dst, register const char *src, size_t n) { - for (; *src != '\0' && n > 1; n--) { - *dst++ = *src++; - } - *dst = '\0'; -} - -static int lowercase(const char *s) { - return tolower(* (const unsigned char *) s); -} - -static int mg_strncasecmp(const char *s1, const char *s2, size_t len) { - int diff = 0; - - if (len > 0) - do { - diff = lowercase(s1++) - lowercase(s2++); - } while (diff == 0 && s1[-1] != '\0' && --len > 0); - - return diff; -} - -static int mg_strcasecmp(const char *s1, const char *s2) { - int diff; - - do { - diff = lowercase(s1++) - lowercase(s2++); - } while (diff == 0 && s1[-1] != '\0'); - - return diff; -} - -static char * mg_strndup(const char *ptr, size_t len) { - char *p; - - if ((p = (char *) malloc(len + 1)) != NULL) { - mg_strlcpy(p, ptr, len + 1); - } - - return p; -} - -static char * mg_strdup(const char *str) { - return mg_strndup(str, strlen(str)); -} - -static const char *mg_strcasestr(const char *big_str, const char *small_str) { - int i, big_len = strlen(big_str), small_len = strlen(small_str); - - for (i = 0; i <= big_len - small_len; i++) { - if (mg_strncasecmp(big_str + i, small_str, small_len) == 0) { - return big_str + i; - } - } - - return NULL; -} - -// Like snprintf(), but never returns negative value, or a value -// that is larger than a supplied buffer. -// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability -// in his audit report. -static int mg_vsnprintf(struct mg_connection *conn, char *buf, size_t buflen, - const char *fmt, va_list ap) { - int n; - - if (buflen == 0) - return 0; - - n = vsnprintf(buf, buflen, fmt, ap); - - if (n < 0) { - cry(conn, "vsnprintf error"); - n = 0; - } else if (n >= (int) buflen) { - cry(conn, "truncating vsnprintf buffer: [%.*s]", - n > 200 ? 200 : n, buf); - n = (int) buflen - 1; - } - buf[n] = '\0'; - - return n; -} - -static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen, - PRINTF_FORMAT_STRING(const char *fmt), ...) - PRINTF_ARGS(4, 5); - -static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen, - const char *fmt, ...) { - va_list ap; - int n; - - va_start(ap, fmt); - n = mg_vsnprintf(conn, buf, buflen, fmt, ap); - va_end(ap); - - return n; -} - -// Skip the characters until one of the delimiters characters found. -// 0-terminate resulting word. Skip the delimiter and following whitespaces. -// Advance pointer to buffer to the next word. Return found 0-terminated word. -// Delimiters can be quoted with quotechar. -static char *skip_quoted(char **buf, const char *delimiters, - const char *whitespace, char quotechar) { - char *p, *begin_word, *end_word, *end_whitespace; - - begin_word = *buf; - end_word = begin_word + strcspn(begin_word, delimiters); - - // Check for quotechar - if (end_word > begin_word) { - p = end_word - 1; - while (*p == quotechar) { - // If there is anything beyond end_word, copy it - if (*end_word == '\0') { - *p = '\0'; - break; - } else { - size_t end_off = strcspn(end_word + 1, delimiters); - memmove (p, end_word, end_off + 1); - p += end_off; // p must correspond to end_word - 1 - end_word += end_off + 1; - } - } - for (p++; p < end_word; p++) { - *p = '\0'; - } - } - - if (*end_word == '\0') { - *buf = end_word; - } else { - end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace); - - for (p = end_word; p < end_whitespace; p++) { - *p = '\0'; - } - - *buf = end_whitespace; - } - - return begin_word; -} - -// Simplified version of skip_quoted without quote char -// and whitespace == delimiters -static char *skip(char **buf, const char *delimiters) { - return skip_quoted(buf, delimiters, delimiters, 0); -} - - -// Return HTTP header value, or NULL if not found. -static const char *get_header(const struct mg_request_info *ri, - const char *name) { - int i; - - for (i = 0; i < ri->num_headers; i++) - if (!mg_strcasecmp(name, ri->http_headers[i].name)) - return ri->http_headers[i].value; - - return NULL; -} - -const char *mg_get_header(const struct mg_connection *conn, const char *name) { - return get_header(&conn->request_info, name); +static int mg_open(const char *path, int flag) { + wchar_t wpath[MAX_PATH_SIZE]; + to_wchar(path, wpath, ARRAY_SIZE(wpath)); + return _wopen(wpath, flag); } +#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 @@ -865,2337 +1402,397 @@ const char *mg_get_header(const struct mg_connection *conn, const char *name) { // vector is initialized to point to the "y" part, and val vector length // is adjusted to point only to "x". static const char *next_option(const char *list, struct vec *val, - struct vec *eq_val) { - if (list == NULL || *list == '\0') { - // End of the list - list = NULL; - } else { - val->ptr = list; - if ((list = strchr(val->ptr, ',')) != NULL) { - // Comma found. Store length and shift the list ptr - val->len = list - val->ptr; - list++; - } else { - // This value is the last one - list = val->ptr + strlen(val->ptr); - val->len = list - val->ptr; - } + struct vec *eq_val) { + if (list == NULL || *list == '\0') { + // End of the list + list = NULL; + } else { + val->ptr = list; + if ((list = strchr(val->ptr, ',')) != NULL) { + // Comma found. Store length and shift the list ptr + val->len = list - val->ptr; + list++; + } else { + // This value is the last one + list = val->ptr + strlen(val->ptr); + val->len = list - val->ptr; + } - if (eq_val != NULL) { - // Value has form "x=y", adjust pointers and lengths - // so that val points to "x", and eq_val points to "y". - eq_val->len = 0; - eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len); - if (eq_val->ptr != NULL) { - eq_val->ptr++; // Skip over '=' character - eq_val->len = val->ptr + val->len - eq_val->ptr; - val->len = (eq_val->ptr - val->ptr) - 1; - } - } - } + if (eq_val != NULL) { + // Value has form "x=y", adjust pointers and lengths + // so that val points to "x", and eq_val points to "y". + eq_val->len = 0; + eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len); + if (eq_val->ptr != NULL) { + eq_val->ptr++; // Skip over '=' character + eq_val->len = val->ptr + val->len - eq_val->ptr; + val->len = (eq_val->ptr - val->ptr) - 1; + } + } + } - return list; + return list; } -static int match_prefix(const char *pattern, int pattern_len, const char *str) { - const char *or_str; - int i, j, len, res; - - 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); - } - - i = j = 0; - res = -1; - for (; i < pattern_len; i++, j++) { - if (pattern[i] == '?' && str[j] != '\0') { - continue; - } else if (pattern[i] == '$') { - return str[j] == '\0' ? j : -1; - } else if (pattern[i] == '*') { - i++; - if (pattern[i] == '*') { - i++; - len = (int) strlen(str + j); - } else { - len = (int) strcspn(str + j, "/"); - } - if (i == pattern_len) { - return j + len; - } - do { - res = match_prefix(pattern + i, pattern_len - i, str + j + len); - } while (res == -1 && len-- > 0); - return res == -1 ? -1 : j + res + len; - } else if (pattern[i] != str[j]) { - return -1; - } - } - return j; +// Like snprintf(), but never returns negative value, or a value +// that is larger than a supplied buffer. +static int mg_vsnprintf(char *buf, size_t buflen, const char *fmt, va_list ap) { + int n; + if (buflen < 1) return 0; + n = vsnprintf(buf, buflen, fmt, ap); + if (n < 0) { + n = 0; + } else if (n >= (int) buflen) { + n = (int) buflen - 1; + } + buf[n] = '\0'; + return n; } -// HTTP 1.1 assumes keep alive if "Connection:" header is not set -// This function must tolerate situations when connection info is not -// set up, for example if request parsing failed. -static int should_keep_alive(const struct mg_connection *conn) { - const char *http_version = conn->request_info.http_version; - const char *header = mg_get_header(conn, "Connection"); - if (conn->must_close || - conn->status_code == 401 || - mg_strcasecmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes") != 0 || - (header != NULL && mg_strcasecmp(header, "keep-alive") != 0) || - (header == NULL && http_version && strcmp(http_version, "1.1"))) { - return 0; - } - return 1; -} - -static const char *suggest_connection_header(const struct mg_connection *conn) { - return should_keep_alive(conn) ? "keep-alive" : "close"; -} - -static void send_http_error(struct mg_connection *, int, const char *, - PRINTF_FORMAT_STRING(const char *fmt), ...) - PRINTF_ARGS(4, 5); - - -static void send_http_error(struct mg_connection *conn, int status, - const char *reason, const char *fmt, ...) { - char buf[MG_BUF_LEN]; - va_list ap; - int len = 0; - - conn->status_code = status; - if (conn->ctx->callbacks.http_error == NULL || - conn->ctx->callbacks.http_error(conn, status)) { - buf[0] = '\0'; - - // Errors 1xx, 204 and 304 MUST NOT send a body - if (status > 199 && status != 204 && status != 304) { - len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason); - buf[len++] = '\n'; - - va_start(ap, fmt); - len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap); - va_end(ap); - } - DEBUG_TRACE(("[%s]", buf)); - - mg_printf(conn, "HTTP/1.1 %d %s\r\n" - "Content-Length: %d\r\n" - "Connection: %s\r\n\r\n", status, reason, len, - suggest_connection_header(conn)); - conn->num_bytes_sent += mg_printf(conn, "%s", buf); - } -} - -#if defined(_WIN32) && !defined(__SYMBIAN32__) -static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) { - (void) unused; - *mutex = CreateMutex(NULL, FALSE, NULL); - return *mutex == NULL ? -1 : 0; -} - -static int pthread_mutex_destroy(pthread_mutex_t *mutex) { - return CloseHandle(*mutex) == 0 ? -1 : 0; -} - -static int pthread_mutex_lock(pthread_mutex_t *mutex) { - return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1; -} - -static int pthread_mutex_unlock(pthread_mutex_t *mutex) { - return ReleaseMutex(*mutex) == 0 ? -1 : 0; -} - -static int pthread_cond_init(pthread_cond_t *cv, const void *unused) { - (void) unused; - cv->signal = CreateEvent(NULL, FALSE, FALSE, NULL); - cv->broadcast = CreateEvent(NULL, TRUE, FALSE, NULL); - return cv->signal != NULL && cv->broadcast != NULL ? 0 : -1; -} - -static int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) { - HANDLE handles[] = {cv->signal, cv->broadcast}; - ReleaseMutex(*mutex); - WaitForMultipleObjects(2, handles, FALSE, INFINITE); - return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1; -} - -static int pthread_cond_signal(pthread_cond_t *cv) { - return SetEvent(cv->signal) == 0 ? -1 : 0; -} - -static int pthread_cond_broadcast(pthread_cond_t *cv) { - // Implementation with PulseEvent() has race condition, see - // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html - return PulseEvent(cv->broadcast) == 0 ? -1 : 0; -} - -static int pthread_cond_destroy(pthread_cond_t *cv) { - return CloseHandle(cv->signal) && CloseHandle(cv->broadcast) ? 0 : -1; -} - -// For Windows, change all slashes to backslashes in path names. -static void change_slashes_to_backslashes(char *path) { - int i; - - for (i = 0; path[i] != '\0'; i++) { - if (path[i] == '/') - path[i] = '\\'; - // i > 0 check is to preserve UNC paths, like \\server\file.txt - if (path[i] == '\\' && i > 0) - while (path[i + 1] == '\\' || path[i + 1] == '/') - (void) memmove(path + i + 1, - path + i + 2, strlen(path + i + 1)); - } -} - -// 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_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) { - char buf[PATH_MAX], buf2[PATH_MAX]; - - mg_strlcpy(buf, path, sizeof(buf)); - change_slashes_to_backslashes(buf); - - // Convert to Unicode and back. If doubly-converted string does not - // match the original, something is fishy, reject. - memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); - MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); - WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), - NULL, NULL); - if (strcmp(buf, buf2) != 0) { - wbuf[0] = L'\0'; - } -} - -#if defined(_WIN32_WCE) -static time_t time(time_t *ptime) { - time_t t; - SYSTEMTIME st; - FILETIME ft; - - GetSystemTime(&st); - SystemTimeToFileTime(&st, &ft); - t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime); - - if (ptime != NULL) { - *ptime = t; - } - - return t; -} - -static struct tm *localtime(const time_t *ptime, struct tm *ptm) { - int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF; - FILETIME ft, lft; - SYSTEMTIME st; - TIME_ZONE_INFORMATION tzinfo; - - if (ptm == NULL) { - return NULL; - } - - * (int64_t *) &ft = t; - FileTimeToLocalFileTime(&ft, &lft); - FileTimeToSystemTime(&lft, &st); - ptm->tm_year = st.wYear - 1900; - ptm->tm_mon = st.wMonth - 1; - ptm->tm_wday = st.wDayOfWeek; - ptm->tm_mday = st.wDay; - ptm->tm_hour = st.wHour; - ptm->tm_min = st.wMinute; - ptm->tm_sec = st.wSecond; - ptm->tm_yday = 0; // hope nobody uses this - ptm->tm_isdst = - GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0; - - return ptm; -} - -static struct tm *gmtime(const time_t *ptime, struct tm *ptm) { - // FIXME(lsm): fix this. - return localtime(ptime, ptm); -} - -static size_t strftime(char *dst, size_t dst_size, const char *fmt, - const struct tm *tm) { - (void) snprintf(dst, dst_size, "implement strftime() for WinCE"); - return 0; -} -#endif - -// Windows happily opens files with some garbage at the end of file name. -// For example, fopen("a.cgi ", "r") on Windows successfully opens -// "a.cgi", despite one would expect an error back. -// This function returns non-0 if path ends with some garbage. -static int path_cannot_disclose_cgi(const char *path) { - static const char *allowed_last_characters = "_-"; - int last = path[strlen(path) - 1]; - return isalnum(last) || strchr(allowed_last_characters, last) != NULL; -} - -static int mg_stat(struct mg_connection *conn, const char *path, - struct file *filep) { - wchar_t wbuf[PATH_MAX]; - WIN32_FILE_ATTRIBUTE_DATA info; - - if (!is_file_in_memory(conn, path, filep)) { - to_unicode(path, wbuf, ARRAY_SIZE(wbuf)); - if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) { - filep->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh); - filep->modification_time = SYS2UNIX_TIME( - info.ftLastWriteTime.dwLowDateTime, - info.ftLastWriteTime.dwHighDateTime); - filep->is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; - // If file name is fishy, reset the file structure and return error. - // Note it is important to reset, not just return the error, cause - // functions like is_file_opened() check the struct. - if (!filep->is_directory && !path_cannot_disclose_cgi(path)) { - memset(filep, 0, sizeof(*filep)); - } - } - } - - return filep->membuf != NULL || filep->modification_time != 0; -} - -static int mg_remove(const char *path) { - wchar_t wbuf[PATH_MAX]; - to_unicode(path, wbuf, ARRAY_SIZE(wbuf)); - return DeleteFileW(wbuf) ? 0 : -1; -} - -static int mg_mkdir(const char *path, int mode) { - char buf[PATH_MAX]; - wchar_t wbuf[PATH_MAX]; - - (void) mode; - mg_strlcpy(buf, path, sizeof(buf)); - change_slashes_to_backslashes(buf); - - (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, ARRAY_SIZE(wbuf)); - - return CreateDirectoryW(wbuf, NULL) ? 0 : -1; -} - -// Implementation of POSIX opendir/closedir/readdir for Windows. -static DIR * opendir(const char *name) { - DIR *dir = NULL; - wchar_t wpath[PATH_MAX]; - DWORD attrs; - - if (name == NULL) { - SetLastError(ERROR_BAD_ARGUMENTS); - } else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - } else { - to_unicode(name, wpath, ARRAY_SIZE(wpath)); - attrs = GetFileAttributesW(wpath); - if (attrs != 0xFFFFFFFF && - ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) { - (void) wcscat(wpath, L"\\*"); - dir->handle = FindFirstFileW(wpath, &dir->info); - dir->result.d_name[0] = '\0'; - } else { - free(dir); - dir = NULL; - } - } - - return dir; -} - -static int closedir(DIR *dir) { - int result = 0; - - if (dir != NULL) { - if (dir->handle != INVALID_HANDLE_VALUE) - result = FindClose(dir->handle) ? 0 : -1; - - free(dir); - } else { - result = -1; - SetLastError(ERROR_BAD_ARGUMENTS); - } - - return result; -} - -static struct dirent *readdir(DIR *dir) { - struct dirent *result = 0; - - if (dir) { - if (dir->handle != INVALID_HANDLE_VALUE) { - result = &dir->result; - (void) WideCharToMultiByte(CP_UTF8, 0, - dir->info.cFileName, -1, result->d_name, - sizeof(result->d_name), NULL, NULL); - - if (!FindNextFileW(dir->handle, &dir->info)) { - (void) FindClose(dir->handle); - dir->handle = INVALID_HANDLE_VALUE; - } - - } else { - SetLastError(ERROR_FILE_NOT_FOUND); - } - } else { - SetLastError(ERROR_BAD_ARGUMENTS); - } - - return result; -} - -#define set_close_on_exec(x) // No FD_CLOEXEC on Windows - -int mg_start_thread(mg_thread_func_t f, void *p) { - return (long)_beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0; -} - -#if !defined(NO_SSL) -static HANDLE dlopen(const char *dll_name, int flags) { - wchar_t wbuf[PATH_MAX]; - (void) flags; - to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf)); - return LoadLibraryW(wbuf); -} -#endif -#if !defined(NO_CGI) -#define SIGKILL 0 -static int kill(pid_t pid, int sig_num) { - (void) TerminateProcess(pid, sig_num); - (void) CloseHandle(pid); - return 0; -} - -static void trim_trailing_whitespaces(char *s) { - char *e = s + strlen(s) - 1; - while (e > s && isspace(* (unsigned char *) e)) { - *e-- = '\0'; - } -} - -static pid_t spawn_process(struct mg_connection *conn, const char *prog, - char *envblk, char *envp[], int fd_stdin, - int fd_stdout, const char *dir) { - HANDLE me; - char *p, *interp, full_interp[PATH_MAX], full_dir[PATH_MAX], - cmdline[PATH_MAX], buf[PATH_MAX]; - struct file file = STRUCT_FILE_INITIALIZER; - STARTUPINFOA si; - PROCESS_INFORMATION pi = { 0 }; - - (void) envp; - - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - - // TODO(lsm): redirect CGI errors to the error log file - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; - - me = GetCurrentProcess(); - DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdin), me, - &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS); - DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdout), me, - &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS); - - // If CGI file is a script, try to read the interpreter line - interp = conn->ctx->config[CGI_INTERPRETER]; - if (interp == NULL) { - buf[0] = buf[1] = '\0'; - - // Read the first line of the script into the buffer - snprintf(cmdline, sizeof(cmdline), "%s%c%s", dir, '/', prog); - if (mg_fopen(conn, cmdline, "r", &file)) { - p = (char *) file.membuf; - mg_fgets(buf, sizeof(buf), &file, &p); - mg_fclose(&file); - buf[sizeof(buf) - 1] = '\0'; - } - - if (buf[0] == '#' && buf[1] == '!') { - trim_trailing_whitespaces(buf + 2); - } else { - buf[2] = '\0'; - } - interp = buf + 2; - } - - if (interp[0] != '\0') { - GetFullPathNameA(interp, sizeof(full_interp), full_interp, NULL); - interp = full_interp; - } - GetFullPathNameA(dir, sizeof(full_dir), full_dir, NULL); - - mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%s%s\\%s", - interp, interp[0] == '\0' ? "" : " ", full_dir, prog); - - DEBUG_TRACE(("Running [%s]", cmdline)); - if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, - CREATE_NEW_PROCESS_GROUP, envblk, NULL, &si, &pi) == 0) { - cry(conn, "%s: CreateProcess(%s): %ld", - __func__, cmdline, ERRNO); - pi.hProcess = (pid_t) -1; - } - - // Always close these to prevent handle leakage. - (void) close(fd_stdin); - (void) close(fd_stdout); - - (void) CloseHandle(si.hStdOutput); - (void) CloseHandle(si.hStdInput); - (void) CloseHandle(pi.hThread); - - return (pid_t) pi.hProcess; -} -#endif // !NO_CGI - -static int set_non_blocking_mode(SOCKET sock) { - unsigned long on = 1; - return ioctlsocket(sock, FIONBIO, &on); -} - -#else -static int mg_stat(struct mg_connection *conn, const char *path, - struct file *filep) { - struct stat st; - - if (!is_file_in_memory(conn, path, filep) && !stat(path, &st)) { - filep->size = st.st_size; - filep->modification_time = st.st_mtime; - filep->is_directory = S_ISDIR(st.st_mode); - } else { - filep->modification_time = (time_t) 0; - } - - return filep->membuf != NULL || filep->modification_time != (time_t) 0; -} - -static void set_close_on_exec(int fd) { - fcntl(fd, F_SETFD, FD_CLOEXEC); -} - -int mg_start_thread(mg_thread_func_t func, void *param) { - pthread_t thread_id; - pthread_attr_t attr; - int result; - - (void) pthread_attr_init(&attr); - (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - // TODO(lsm): figure out why mongoose dies on Linux if next line is enabled - // (void) pthread_attr_setstacksize(&attr, sizeof(struct mg_connection) * 5); - - result = pthread_create(&thread_id, &attr, func, param); - pthread_attr_destroy(&attr); - - return result; -} - -#ifndef NO_CGI -static pid_t spawn_process(struct mg_connection *conn, const char *prog, - char *envblk, char *envp[], int fd_stdin, - int fd_stdout, const char *dir) { - pid_t pid; - const char *interp; - - (void) envblk; - - if ((pid = fork()) == -1) { - // Parent - send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO)); - } else if (pid == 0) { - // Child - if (chdir(dir) != 0) { - cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO)); - } else if (dup2(fd_stdin, 0) == -1) { - cry(conn, "%s: dup2(%d, 0): %s", __func__, fd_stdin, strerror(ERRNO)); - } else if (dup2(fd_stdout, 1) == -1) { - cry(conn, "%s: dup2(%d, 1): %s", __func__, fd_stdout, strerror(ERRNO)); - } else { - // Not redirecting stderr to stdout, to avoid output being littered - // with the error messages. - (void) close(fd_stdin); - (void) close(fd_stdout); - - // After exec, all signal handlers are restored to their default values, - // with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's - // implementation, SIGCHLD's handler will leave unchanged after exec - // if it was set to be ignored. Restore it to default action. - signal(SIGCHLD, SIG_DFL); - - interp = conn->ctx->config[CGI_INTERPRETER]; - if (interp == NULL) { - (void) execle(prog, prog, NULL, envp); - cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO)); - } else { - (void) execle(interp, interp, prog, NULL, envp); - cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog, - strerror(ERRNO)); - } - } - exit(EXIT_FAILURE); - } - - // Parent. Close stdio descriptors - (void) close(fd_stdin); - (void) close(fd_stdout); - - return pid; -} -#endif // !NO_CGI - -static int set_non_blocking_mode(SOCKET sock) { - int flags; - - flags = fcntl(sock, F_GETFL, 0); - (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK); - - return 0; -} -#endif // _WIN32 - -#ifndef HAVE_POLL -static int poll(struct pollfd *pfd, int n, int milliseconds) { - struct timeval tv; - fd_set set; - int i, result, maxfd = 0; - - tv.tv_sec = milliseconds / 1000; - tv.tv_usec = (milliseconds % 1000) * 1000; - FD_ZERO(&set); - - for (i = 0; i < n; i++) { - FD_SET((SOCKET) pfd[i].fd, &set); - pfd[i].revents = 0; - - if (pfd[i].fd > maxfd) { - maxfd = pfd[i].fd; - } - } - - if ((result = select(maxfd + 1, &set, NULL, NULL, &tv)) > 0) { - for (i = 0; i < n; i++) { - if (FD_ISSET(pfd[i].fd, &set)) { - pfd[i].revents = POLLIN; - } - } - } - - return result; -} -#endif // HAVE_POLL - -// Write data to the IO channel - opened file descriptor, socket or SSL -// descriptor. Return number of bytes written. -static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf, - int64_t len) { - int64_t sent; - int n, k; - - (void) ssl; // Get rid of warning - sent = 0; - while (sent < len) { - // How many bytes we send in this iteration - k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent); - -#ifndef NO_SSL - if (ssl != NULL) { - n = SSL_write(ssl, buf + sent, k); - } else -#endif - if (fp != NULL) { - n = (int) fwrite(buf + sent, 1, (size_t) k, fp); - if (ferror(fp)) - n = -1; - } else { - n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL); - } - - if (n <= 0) - break; - - sent += n; - } - - return sent; -} - -// Read from IO channel - opened file descriptor, socket, or SSL descriptor. -// Return negative value on error, or number of bytes read on success. -static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) { - int nread; - - if (fp != NULL) { - // Use read() instead of fread(), because if we're reading from the CGI - // pipe, fread() may block until IO buffer is filled up. We cannot afford - // to block and must pass all read bytes immediately to the client. - nread = read(fileno(fp), buf, (size_t) len); -#ifndef NO_SSL - } else if (conn->ssl != NULL) { - nread = SSL_read(conn->ssl, buf, len); -#endif - } else { - nread = recv(conn->client.sock, buf, (size_t) len, 0); - } - - return conn->ctx->stop_flag ? -1 : nread; -} - -static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) { - int n, nread = 0; - - while (len > 0) { - n = pull(fp, conn, buf + nread, len); - if (n < 0) { - nread = n; // Propagate the error - break; - } else if (n == 0) { - break; // No more data to read - } else { - conn->consumed_content += n; - nread += n; - len -= n; - } - } - - return nread; -} - -int mg_read(struct mg_connection *conn, void *buf, size_t len) { - int n, buffered_len, nread; - const char *body; - - // If Content-Length is not set, read until socket is closed - if (conn->consumed_content == 0 && conn->content_len == 0) { - conn->content_len = INT64_MAX; - conn->must_close = 1; - } - - nread = 0; - if (conn->consumed_content < conn->content_len) { - // Adjust number of bytes to read. - int64_t to_read = conn->content_len - conn->consumed_content; - if (to_read < (int64_t) len) { - len = (size_t) to_read; - } - - // Return buffered data - body = conn->buf + conn->request_len + conn->consumed_content; - buffered_len = &conn->buf[conn->data_len] - body; - if (buffered_len > 0) { - if (len < (size_t) buffered_len) { - buffered_len = (int) len; - } - memcpy(buf, body, (size_t) buffered_len); - len -= buffered_len; - conn->consumed_content += buffered_len; - nread += buffered_len; - buf = (char *) buf + buffered_len; - } - - // We have returned all buffered data. Read new data from the remote socket. - n = pull_all(NULL, conn, (char *) buf, (int) len); - nread = n >= 0 ? nread + n : n; - } - return nread; -} - -int mg_write(struct mg_connection *conn, const void *buf, size_t len) { - time_t now; - int64_t n, total, allowed; - - if (conn->throttle > 0) { - if ((now = time(NULL)) != conn->last_throttle_time) { - conn->last_throttle_time = now; - conn->last_throttle_bytes = 0; - } - allowed = conn->throttle - conn->last_throttle_bytes; - if (allowed > (int64_t) len) { - allowed = len; - } - if ((total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf, - (int64_t) allowed)) == allowed) { - buf = (char *) buf + total; - conn->last_throttle_bytes += total; - while (total < (int64_t) len && conn->ctx->stop_flag == 0) { - allowed = conn->throttle > (int64_t) len - total ? - (int64_t) len - total : conn->throttle; - if ((n = push(NULL, conn->client.sock, conn->ssl, (const char *) buf, - (int64_t) allowed)) != allowed) { - break; - } - sleep(1); - conn->last_throttle_bytes = allowed; - conn->last_throttle_time = time(NULL); - buf = (char *) buf + n; - total += n; - } - } - } else { - total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf, - (int64_t) len); - } - return (int) total; -} - -// 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 alloc_vprintf(char **buf, size_t size, const char *fmt, va_list ap) { - va_list ap_copy; - int len; - - // Windows is not standard-compliant, and vsnprintf() returns -1 if - // buffer is too small. Also, older versions of msvcrt.dll do not have - // _vscprintf(). However, if size is 0, vsnprintf() behaves correctly. - // Therefore, we make two passes: on first pass, get required message length. - // On second pass, actually print the message. - va_copy(ap_copy, ap); - len = vsnprintf(NULL, 0, fmt, ap_copy); - - if (len > (int) size && - (size = len + 1) > 0 && - (*buf = (char *) malloc(size)) == NULL) { - len = -1; // Allocation failed, mark failure - } else { - va_copy(ap_copy, ap); - vsnprintf(*buf, size, fmt, ap_copy); - } - - return len; -} - -int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) { - char mem[MG_BUF_LEN], *buf = mem; - int len; - - if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) { - len = mg_write(conn, buf, (size_t) len); - } - if (buf != mem && buf != NULL) { - free(buf); - } - - return len; -} - -int mg_printf(struct mg_connection *conn, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - return mg_vprintf(conn, fmt, ap); -} - -int mg_url_decode(const char *src, int src_len, char *dst, - int dst_len, int is_form_url_encoded) { - int i, j, a, b; -#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') - - for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) { - if (src[i] == '%' && i < src_len - 2 && - isxdigit(* (const unsigned char *) (src + i + 1)) && - isxdigit(* (const unsigned char *) (src + i + 2))) { - a = tolower(* (const unsigned char *) (src + i + 1)); - b = tolower(* (const unsigned char *) (src + i + 2)); - dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b)); - i += 2; - } else if (is_form_url_encoded && src[i] == '+') { - dst[j] = ' '; - } else { - dst[j] = src[i]; - } - } - - dst[j] = '\0'; // Null-terminate the destination - - return i >= src_len ? j : -1; -} - -int mg_get_var(const char *data, size_t data_len, const char *name, - char *dst, size_t dst_len) { - const char *p, *e, *s; - size_t name_len; - int len; - - if (dst == NULL || dst_len == 0) { - len = -2; - } else if (data == NULL || name == NULL || data_len == 0) { - len = -1; - dst[0] = '\0'; - } else { - name_len = strlen(name); - e = data + data_len; - len = -1; - dst[0] = '\0'; - - // data is "var1=val1&var2=val2...". Find variable first - for (p = data; p + name_len < e; p++) { - if ((p == data || p[-1] == '&') && p[name_len] == '=' && - !mg_strncasecmp(name, p, name_len)) { - // Point p to variable value - p += name_len + 1; - - // Point s to the end of the value - s = (const char *) memchr(p, '&', (size_t)(e - p)); - if (s == NULL) { - s = e; - } - assert(s >= p); - - // Decode variable into destination buffer - len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1); - - // Redirect error code from -1 to -2 (destination buffer too small). - if (len == -1) { - len = -2; - } - break; - } - } - } - - return len; -} - -int mg_get_cookie(const char *cookie_header, const char *var_name, - char *dst, size_t dst_size) { - const char *s, *p, *end; - int name_len, len = -1; - - if (dst == NULL || dst_size == 0) { - len = -2; - } else if (var_name == NULL || (s = cookie_header) == NULL) { - len = -1; - dst[0] = '\0'; - } else { - name_len = (int) strlen(var_name); - end = s + strlen(s); - dst[0] = '\0'; - - for (; (s = mg_strcasestr(s, var_name)) != NULL; s += name_len) { - if (s[name_len] == '=') { - s += name_len + 1; - if ((p = strchr(s, ' ')) == NULL) - p = end; - if (p[-1] == ';') - p--; - if (*s == '"' && p[-1] == '"' && p > s + 1) { - s++; - p--; - } - if ((size_t) (p - s) < dst_size) { - len = p - s; - mg_strlcpy(dst, s, (size_t) len + 1); - } else { - len = -3; - } - break; - } - } - } - return len; -} - -static void convert_uri_to_file_name(struct mg_connection *conn, char *buf, - size_t buf_len, struct file *filep) { - struct vec a, b; - const char *rewrite, *uri = conn->request_info.uri; - char *p; - int match_len; - char gz_path[PATH_MAX]; - char const* accept_encoding; - - // Using buf_len - 1 because memmove() for PATH_INFO may shift part - // of the path one byte on the right. - mg_snprintf(conn, buf, buf_len - 1, "%s%s", conn->ctx->config[DOCUMENT_ROOT], - uri); - - rewrite = conn->ctx->config[REWRITE]; - while ((rewrite = next_option(rewrite, &a, &b)) != NULL) { - if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) { - mg_snprintf(conn, buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr, - uri + match_len); - break; - } - } - - if (mg_stat(conn, buf, filep)) return; - - // if we can't find the actual file, look for the file - // with the same name but a .gz extension. If we find it, - // use that and set the gzipped flag in the file struct - // to indicate that the response need to have the content- - // encoding: gzip header - // we can only do this if the browser declares support - if ((accept_encoding = mg_get_header(conn, "Accept-Encoding")) != NULL) { - if (strstr(accept_encoding,"gzip") != NULL) { - snprintf(gz_path, sizeof(gz_path), "%s.gz", buf); - if (mg_stat(conn, gz_path, filep)) { - filep->gzipped = 1; - return; - } - } - } - - // Support PATH_INFO for CGI scripts. - for (p = buf + strlen(buf); p > buf + 1; p--) { - if (*p == '/') { - *p = '\0'; - if (match_prefix(conn->ctx->config[CGI_EXTENSIONS], - strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 && - mg_stat(conn, buf, filep)) { - // Shift PATH_INFO block one character right, e.g. - // "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00" - // conn->path_info is pointing to the local variable "path" declared - // in handle_request(), so PATH_INFO is not valid after - // handle_request returns. - conn->path_info = p + 1; - memmove(p + 2, p + 1, strlen(p + 1) + 1); // +1 is for trailing \0 - p[1] = '/'; - break; - } else { - *p = '/'; - } - } - } +static int mg_snprintf(char *buf, size_t buflen, const char *fmt, ...) { + va_list ap; + int n; + va_start(ap, fmt); + n = mg_vsnprintf(buf, buflen, fmt, ap); + va_end(ap); + return n; } // Check whether full request is buffered. Return: // -1 if request is malformed // 0 if request is not yet fully buffered // >0 actual request length, including last \r\n\r\n -static int get_request_len(const char *buf, int buflen) { - const char *s, *e; - int len = 0; +static int get_request_len(const char *s, int buf_len) { + const unsigned char *buf = (unsigned char *) s; + int i; - for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++) - // Control characters are not allowed but >=128 is. - if (!isprint(* (const unsigned char *) s) && *s != '\r' && - *s != '\n' && * (const unsigned char *) s < 128) { - len = -1; - break; // [i_a] abort scan as soon as one malformed character is found; - // don't let subsequent \r\n\r\n win us over anyhow - } else if (s[0] == '\n' && s[1] == '\n') { - len = (int) (s - buf) + 2; - } else if (s[0] == '\n' && &s[1] < e && - s[1] == '\r' && s[2] == '\n') { - len = (int) (s - buf) + 3; - } + for (i = 0; i < buf_len; i++) { + // Control characters are not allowed but >=128 are. + // Abort scan as soon as one malformed character is found. + if (!isprint(buf[i]) && buf[i] != '\r' && buf[i] != '\n' && buf[i] < 128) { + return -1; + } else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') { + return i + 2; + } else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' && + buf[i + 2] == '\n') { + return i + 3; + } + } - return len; + return 0; } -// Convert month to the month number. Return -1 on error, or month number -static int get_month_index(const char *s) { - size_t i; +// Skip the characters until one of the delimiters characters found. +// 0-terminate resulting word. Skip the rest of the delimiters if any. +// Advance pointer to buffer to the next word. Return found 0-terminated word. +static char *skip(char **buf, const char *delimiters) { + char *p, *begin_word, *end_word, *end_delimiters; - for (i = 0; i < ARRAY_SIZE(month_names); i++) - if (!strcmp(s, month_names[i])) - return (int) i; + begin_word = *buf; + end_word = begin_word + strcspn(begin_word, delimiters); + end_delimiters = end_word + strspn(end_word, delimiters); - return -1; + for (p = end_word; p < end_delimiters; p++) { + *p = '\0'; + } + + *buf = end_delimiters; + + return begin_word; } -static int num_leap_years(int year) { - return year / 4 - year / 100 + year / 400; -} - -// Parse UTC date-time string, and return the corresponding time_t value. -static time_t parse_date_string(const char *datetime) { - static const unsigned short days_before_month[] = { - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 - }; - char month_str[32]; - int second, minute, hour, day, month, year, leap_days, days; - time_t result = (time_t) 0; - - if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d", - &day, month_str, &year, &hour, &minute, &second) == 6) || - (sscanf(datetime, "%d %3s %d %d:%d:%d", - &day, month_str, &year, &hour, &minute, &second) == 6) || - (sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d", - &day, month_str, &year, &hour, &minute, &second) == 6) || - (sscanf(datetime, "%d-%3s-%d %d:%d:%d", - &day, month_str, &year, &hour, &minute, &second) == 6)) && - year > 1970 && - (month = get_month_index(month_str)) != -1) { - leap_days = num_leap_years(year) - num_leap_years(1970); - year -= 1970; - days = year * 365 + days_before_month[month] + (day - 1) + leap_days; - result = days * 24 * 3600 + hour * 3600 + minute * 60 + second; - } - - return result; -} - -// Protect against directory disclosure attack by removing '..', -// excessive '/' and '\' characters -static void remove_double_dots_and_double_slashes(char *s) { - char *p = s; - - while (*s != '\0') { - *p++ = *s++; - if (s[-1] == '/' || s[-1] == '\\') { - // Skip all following slashes, backslashes and double-dots - while (s[0] != '\0') { - if (s[0] == '/' || s[0] == '\\') { - s++; - } else if (s[0] == '.' && s[1] == '.') { - s += 2; - } else { - break; - } - } - } - } - *p = '\0'; -} - -static const struct { - const char *extension; - size_t ext_len; - const char *mime_type; -} builtin_mime_types[] = { - {".html", 5, "text/html"}, - {".htm", 4, "text/html"}, - {".shtm", 5, "text/html"}, - {".shtml", 6, "text/html"}, - {".css", 4, "text/css"}, - {".js", 3, "application/x-javascript"}, - {".ico", 4, "image/x-icon"}, - {".gif", 4, "image/gif"}, - {".jpg", 4, "image/jpeg"}, - {".jpeg", 5, "image/jpeg"}, - {".png", 4, "image/png"}, - {".svg", 4, "image/svg+xml"}, - {".txt", 4, "text/plain"}, - {".torrent", 8, "application/x-bittorrent"}, - {".wav", 4, "audio/x-wav"}, - {".mp3", 4, "audio/x-mp3"}, - {".mid", 4, "audio/mid"}, - {".m3u", 4, "audio/x-mpegurl"}, - {".ogg", 4, "audio/ogg"}, - {".ram", 4, "audio/x-pn-realaudio"}, - {".xml", 4, "text/xml"}, - {".json", 5, "text/json"}, - {".xslt", 5, "application/xml"}, - {".xsl", 4, "application/xml"}, - {".ra", 3, "audio/x-pn-realaudio"}, - {".doc", 4, "application/msword"}, - {".exe", 4, "application/octet-stream"}, - {".zip", 4, "application/x-zip-compressed"}, - {".xls", 4, "application/excel"}, - {".tgz", 4, "application/x-tar-gz"}, - {".tar", 4, "application/x-tar"}, - {".gz", 3, "application/x-gunzip"}, - {".arj", 4, "application/x-arj-compressed"}, - {".rar", 4, "application/x-arj-compressed"}, - {".rtf", 4, "application/rtf"}, - {".pdf", 4, "application/pdf"}, - {".swf", 4, "application/x-shockwave-flash"}, - {".mpg", 4, "video/mpeg"}, - {".webm", 5, "video/webm"}, - {".mpeg", 5, "video/mpeg"}, - {".mov", 4, "video/quicktime"}, - {".mp4", 4, "video/mp4"}, - {".m4v", 4, "video/x-m4v"}, - {".asf", 4, "video/x-ms-asf"}, - {".avi", 4, "video/x-msvideo"}, - {".bmp", 4, "image/bmp"}, - {".ttf", 4, "application/x-font-ttf"}, - {NULL, 0, NULL} -}; - -const char *mg_get_builtin_mime_type(const char *path) { - const char *ext; - size_t i, path_len; - - path_len = strlen(path); - - for (i = 0; builtin_mime_types[i].extension != NULL; i++) { - ext = path + (path_len - builtin_mime_types[i].ext_len); - if (path_len > builtin_mime_types[i].ext_len && - mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) { - return builtin_mime_types[i].mime_type; - } - } - - return "text/plain"; -} - -// Look at the "path" extension and figure what mime type it has. -// Store mime type in the vector. -static void get_mime_type(struct mg_context *ctx, const char *path, - struct vec *vec) { - struct vec ext_vec, mime_vec; - const char *list, *ext; - size_t path_len; - - path_len = strlen(path); - - // Scan user-defined mime types first, in case user wants to - // override default mime types. - list = ctx->config[EXTRA_MIME_TYPES]; - while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) { - // ext now points to the path suffix - ext = path + path_len - ext_vec.len; - if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) { - *vec = mime_vec; - return; - } - } - - vec->ptr = mg_get_builtin_mime_type(path); - vec->len = strlen(vec->ptr); -} - -static int is_big_endian(void) { - static const int n = 1; - return ((char *) &n)[0] == 0; -} - -#ifndef HAVE_MD5 -typedef struct MD5Context { - uint32_t buf[4]; - uint32_t bits[2]; - unsigned char in[64]; -} MD5_CTX; - -static void byteReverse(unsigned char *buf, unsigned longs) { - uint32_t t; - - // Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN - if (is_big_endian()) { - do { - t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | - ((unsigned) buf[1] << 8 | buf[0]); - * (uint32_t *) buf = t; - buf += 4; - } while (--longs); - } -} - -#define F1(x, y, z) (z ^ (x & (y ^ z))) -#define F2(x, y, z) F1(z, x, y) -#define F3(x, y, z) (x ^ y ^ z) -#define F4(x, y, z) (y ^ (x | ~z)) - -#define MD5STEP(f, w, x, y, z, data, s) \ - ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) - -// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious -// initialization constants. -static void MD5Init(MD5_CTX *ctx) { - ctx->buf[0] = 0x67452301; - ctx->buf[1] = 0xefcdab89; - ctx->buf[2] = 0x98badcfe; - ctx->buf[3] = 0x10325476; - - ctx->bits[0] = 0; - ctx->bits[1] = 0; -} - -static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) { - register uint32_t a, b, c, d; - - a = buf[0]; - b = buf[1]; - c = buf[2]; - d = buf[3]; - - MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); - MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); - MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); - MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); - MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); - MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); - MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); - MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); - MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); - MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); - MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); - MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); - MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); - MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); - MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); - MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); - - MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); - MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); - MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); - MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); - MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); - MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); - MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); - MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); - MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); - MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); - MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); - MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); - MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); - MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); - MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); - MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); - - MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); - MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); - MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); - MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); - MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); - MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); - MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); - MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); - MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); - MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); - MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); - MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); - MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); - MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); - MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); - MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); - - MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); - MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); - MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); - MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); - MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); - MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); - MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); - MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); - MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); - MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); - MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); - MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); - MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); - MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); - MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); - MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); - - buf[0] += a; - buf[1] += b; - buf[2] += c; - buf[3] += d; -} - -static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) { - uint32_t t; - - t = ctx->bits[0]; - if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) - ctx->bits[1]++; - ctx->bits[1] += len >> 29; - - t = (t >> 3) & 0x3f; - - if (t) { - unsigned char *p = (unsigned char *) ctx->in + t; - - t = 64 - t; - if (len < t) { - memcpy(p, buf, len); - return; - } - memcpy(p, buf, t); - byteReverse(ctx->in, 16); - MD5Transform(ctx->buf, (uint32_t *) ctx->in); - buf += t; - len -= t; - } - - while (len >= 64) { - memcpy(ctx->in, buf, 64); - byteReverse(ctx->in, 16); - MD5Transform(ctx->buf, (uint32_t *) ctx->in); - buf += 64; - len -= 64; - } - - memcpy(ctx->in, buf, len); -} - -static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) { - unsigned count; - unsigned char *p; - uint32_t *a; - - count = (ctx->bits[0] >> 3) & 0x3F; - - p = ctx->in + count; - *p++ = 0x80; - count = 64 - 1 - count; - if (count < 8) { - memset(p, 0, count); - byteReverse(ctx->in, 16); - MD5Transform(ctx->buf, (uint32_t *) ctx->in); - memset(ctx->in, 0, 56); - } else { - memset(p, 0, count - 8); - } - byteReverse(ctx->in, 14); - - a = (uint32_t *)ctx->in; - a[14] = ctx->bits[0]; - a[15] = ctx->bits[1]; - - MD5Transform(ctx->buf, (uint32_t *) ctx->in); - byteReverse((unsigned char *) ctx->buf, 4); - memcpy(digest, ctx->buf, 16); - memset((char *) ctx, 0, sizeof(*ctx)); -} -#endif // !HAVE_MD5 - -// Stringify binary data. Output buffer must be twice as big as input, -// because each byte takes 2 bytes in string representation -static void bin2str(char *to, const unsigned char *p, size_t len) { - static const char *hex = "0123456789abcdef"; - - for (; len--; p++) { - *to++ = hex[p[0] >> 4]; - *to++ = hex[p[0] & 0x0f]; - } - *to = '\0'; -} - -// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes. -char *mg_md5(char buf[33], ...) { - unsigned char hash[16]; - const char *p; - va_list ap; - MD5_CTX ctx; - - MD5Init(&ctx); - - va_start(ap, buf); - while ((p = va_arg(ap, const char *)) != NULL) { - MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p)); - } - va_end(ap); - - MD5Final(hash, &ctx); - bin2str(buf, hash, sizeof(hash)); - return buf; -} - -// Check the user's password, return 1 if OK -static int check_password(const char *method, const char *ha1, const char *uri, - const char *nonce, const char *nc, const char *cnonce, - const char *qop, const char *response) { - char ha2[32 + 1], expected_response[32 + 1]; - - // Some of the parameters may be NULL - if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL || - qop == NULL || response == NULL) { - return 0; - } - - // NOTE(lsm): due to a bug in MSIE, we do not compare the URI - // TODO(lsm): check for authentication timeout - if (// strcmp(dig->uri, c->ouri) != 0 || - strlen(response) != 32 - // || now - strtoul(dig->nonce, NULL, 10) > 3600 - ) { - return 0; - } - - mg_md5(ha2, method, ":", uri, NULL); - mg_md5(expected_response, ha1, ":", nonce, ":", nc, - ":", cnonce, ":", qop, ":", ha2, NULL); - - return mg_strcasecmp(response, expected_response) == 0; -} - -// Use the global passwords file, if specified by auth_gpass option, -// or search for .htpasswd in the requested directory. -static void open_auth_file(struct mg_connection *conn, const char *path, - struct file *filep) { - char name[PATH_MAX]; - const char *p, *e, *gpass = conn->ctx->config[GLOBAL_PASSWORDS_FILE]; - struct file file = STRUCT_FILE_INITIALIZER; - - if (gpass != NULL) { - // Use global passwords file - if (!mg_fopen(conn, gpass, "r", filep)) { - cry(conn, "fopen(%s): %s", gpass, strerror(ERRNO)); - } - // Important: using local struct file to test path for is_directory flag. - // If filep is used, mg_stat() makes it appear as if auth file was opened. - } else if (mg_stat(conn, path, &file) && file.is_directory) { - mg_snprintf(conn, name, sizeof(name), "%s%c%s", - path, '/', PASSWORDS_FILE_NAME); - mg_fopen(conn, name, "r", filep); - } else { - // Try to find .htpasswd in requested directory. - for (p = path, e = p + strlen(p) - 1; e > p; e--) - if (e[0] == '/') - break; - mg_snprintf(conn, name, sizeof(name), "%.*s%c%s", - (int) (e - p), p, '/', PASSWORDS_FILE_NAME); - mg_fopen(conn, name, "r", filep); - } -} - -// Parsed Authorization header -struct ah { - char *user, *uri, *cnonce, *response, *qop, *nc, *nonce; -}; - -// Return 1 on success. Always initializes the ah structure. -static int parse_auth_header(struct mg_connection *conn, char *buf, - size_t buf_size, struct ah *ah) { - char *name, *value, *s; - const char *auth_header; - - (void) memset(ah, 0, sizeof(*ah)); - if ((auth_header = mg_get_header(conn, "Authorization")) == NULL || - mg_strncasecmp(auth_header, "Digest ", 7) != 0) { - return 0; - } - - // Make modifiable copy of the auth header - (void) mg_strlcpy(buf, auth_header + 7, buf_size); - s = buf; - - // Parse authorization header - for (;;) { - // Gobble initial spaces - while (isspace(* (unsigned char *) s)) { - s++; - } - name = skip_quoted(&s, "=", " ", 0); - // Value is either quote-delimited, or ends at first comma or space. - if (s[0] == '\"') { - s++; - value = skip_quoted(&s, "\"", " ", '\\'); - if (s[0] == ',') { - s++; - } - } else { - value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces - } - if (*name == '\0') { - break; - } - - if (!strcmp(name, "username")) { - ah->user = value; - } else if (!strcmp(name, "cnonce")) { - ah->cnonce = value; - } else if (!strcmp(name, "response")) { - ah->response = value; - } else if (!strcmp(name, "uri")) { - ah->uri = value; - } else if (!strcmp(name, "qop")) { - ah->qop = value; - } else if (!strcmp(name, "nc")) { - ah->nc = value; - } else if (!strcmp(name, "nonce")) { - ah->nonce = value; - } - } - - // CGI needs it as REMOTE_USER - if (ah->user != NULL) { - conn->request_info.remote_user = mg_strdup(ah->user); - } else { - return 0; - } - - return 1; -} - -static char *mg_fgets(char *buf, size_t size, struct file *filep, char **p) { - char *eof; - size_t len; - - if (filep->membuf != NULL && *p != NULL) { - eof = (char*)memchr(*p, '\n', &filep->membuf[filep->size] - *p); - len = (size_t) (eof - *p) > size - 1 ? size - 1 : (size_t) (eof - *p); - memcpy(buf, *p, len); - buf[len] = '\0'; - *p = eof; - return eof; - } else if (filep->fp != NULL) { - return fgets(buf, size, filep->fp); - } else { - return NULL; - } -} - -// Authorize against the opened passwords file. Return 1 if authorized. -static int authorize(struct mg_connection *conn, struct file *filep) { - struct ah ah; - char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN], *p; - - if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) { - return 0; - } - - // Loop over passwords file - p = (char *) filep->membuf; - while (mg_fgets(line, sizeof(line), filep, &p) != NULL) { - if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) { - continue; - } - - if (!strcmp(ah.user, f_user) && - !strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain)) - return check_password(conn->request_info.request_method, ha1, ah.uri, - ah.nonce, ah.nc, ah.cnonce, ah.qop, ah.response); - } - - return 0; -} - -// Return 1 if request is authorised, 0 otherwise. -static int check_authorization(struct mg_connection *conn, const char *path) { - char fname[PATH_MAX]; - struct vec uri_vec, filename_vec; - const char *list; - struct file file = STRUCT_FILE_INITIALIZER; - int authorized = 1; - - list = conn->ctx->config[PROTECT_URI]; - while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) { - if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) { - mg_snprintf(conn, fname, sizeof(fname), "%.*s", - (int) filename_vec.len, filename_vec.ptr); - if (!mg_fopen(conn, fname, "r", &file)) { - cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno)); - } - break; - } - } - - if (!is_file_opened(&file)) { - open_auth_file(conn, path, &file); - } - - if (is_file_opened(&file)) { - authorized = authorize(conn, &file); - mg_fclose(&file); - } - - return authorized; -} - -static void send_authorization_request(struct mg_connection *conn) { - conn->status_code = 401; - mg_printf(conn, - "HTTP/1.1 401 Unauthorized\r\n" - "Content-Length: 0\r\n" - "WWW-Authenticate: Digest qop=\"auth\", " - "realm=\"%s\", nonce=\"%lu\"\r\n\r\n", - conn->ctx->config[AUTHENTICATION_DOMAIN], - (unsigned long) time(NULL)); -} - -static int is_authorized_for_put(struct mg_connection *conn) { - struct file file = STRUCT_FILE_INITIALIZER; - const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE]; - int ret = 0; - - if (passfile != NULL && mg_fopen(conn, passfile, "r", &file)) { - ret = authorize(conn, &file); - mg_fclose(&file); - } - - return ret; -} - -int mg_modify_passwords_file(const char *fname, const char *domain, - const char *user, const char *pass) { - int found; - char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX]; - FILE *fp, *fp2; - - found = 0; - fp = fp2 = NULL; - - // Regard empty password as no password - remove user record. - if (pass != NULL && pass[0] == '\0') { - pass = NULL; - } - - (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname); - - // Create the file if does not exist - if ((fp = fopen(fname, "a+")) != NULL) { - (void) fclose(fp); - } - - // Open the given file and temporary file - if ((fp = fopen(fname, "r")) == NULL) { - return 0; - } else if ((fp2 = fopen(tmp, "w+")) == NULL) { - fclose(fp); - return 0; - } - - // Copy the stuff to temporary file - while (fgets(line, sizeof(line), fp) != NULL) { - if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) { - continue; - } - - if (!strcmp(u, user) && !strcmp(d, domain)) { - found++; - if (pass != NULL) { - mg_md5(ha1, user, ":", domain, ":", pass, NULL); - fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); - } - } else { - fprintf(fp2, "%s", line); - } - } - - // If new user, just add it - if (!found && pass != NULL) { - mg_md5(ha1, user, ":", domain, ":", pass, NULL); - fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); - } - - // Close files - fclose(fp); - fclose(fp2); - - // Put the temp file in place of real file - remove(fname); - rename(tmp, fname); - - return 1; -} - -static int conn2(const char *host, int port, int use_ssl, - char *ebuf, size_t ebuf_len) { - struct sockaddr_in sin; - struct hostent *he; - SOCKET sock = INVALID_SOCKET; - - if (host == NULL) { - snprintf(ebuf, ebuf_len, "%s", "NULL host"); - } else if (use_ssl && SSLv23_client_method == NULL) { - snprintf(ebuf, ebuf_len, "%s", "SSL is not initialized"); - // TODO(lsm): use something threadsafe instead of gethostbyname() - } else if ((he = gethostbyname(host)) == NULL) { - snprintf(ebuf, ebuf_len, "gethostbyname(%s): %s", host, strerror(ERRNO)); - } else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { - snprintf(ebuf, ebuf_len, "socket(): %s", strerror(ERRNO)); - } else { - 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) { - snprintf(ebuf, ebuf_len, "connect(%s:%d): %s", - host, port, strerror(ERRNO)); - closesocket(sock); - sock = INVALID_SOCKET; - } - } - return sock; -} - - - -void mg_url_encode(const char *src, 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; - } - } - - *dst = '\0'; -} - -static void print_dir_entry(struct de *de) { - char size[64], mod[64], href[PATH_MAX]; - - if (de->file.is_directory) { - mg_snprintf(de->conn, size, sizeof(size), "%s", "[DIRECTORY]"); - } else { - // We use (signed) cast below because MSVC 6 compiler cannot - // convert unsigned __int64 to double. Sigh. - if (de->file.size < 1024) { - mg_snprintf(de->conn, size, sizeof(size), "%d", (int) de->file.size); - } else if (de->file.size < 0x100000) { - mg_snprintf(de->conn, size, sizeof(size), - "%.1fk", (double) de->file.size / 1024.0); - } else if (de->file.size < 0x40000000) { - mg_snprintf(de->conn, size, sizeof(size), - "%.1fM", (double) de->file.size / 1048576); - } else { - mg_snprintf(de->conn, size, sizeof(size), - "%.1fG", (double) de->file.size / 1073741824); - } - } - strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", - localtime(&de->file.modification_time)); - mg_url_encode(de->file_name, href, sizeof(href)); - de->conn->num_bytes_sent += mg_printf(de->conn, - "%s%s" - " %s  %s\n", - de->conn->request_info.uri, href, de->file.is_directory ? "/" : "", - de->file_name, de->file.is_directory ? "/" : "", mod, size); -} - -// This function is called from send_directory() and used for -// sorting directory entries by size, or name, or modification time. -// On windows, __cdecl specification is needed in case if project is built -// with __stdcall convention. qsort always requires __cdels callback. -static int WINCDECL compare_dir_entries(const void *p1, const void *p2) { - const struct de *a = (const struct de *) p1, *b = (const struct de *) p2; - const char *query_string = a->conn->request_info.query_string; - int cmp_result = 0; - - if (query_string == NULL) { - query_string = "na"; - } - - if (a->file.is_directory && !b->file.is_directory) { - return -1; // Always put directories on top - } else if (!a->file.is_directory && b->file.is_directory) { - return 1; // Always put directories on top - } else if (*query_string == 'n') { - cmp_result = strcmp(a->file_name, b->file_name); - } else if (*query_string == 's') { - cmp_result = a->file.size == b->file.size ? 0 : - a->file.size > b->file.size ? 1 : -1; - } else if (*query_string == 'd') { - cmp_result = a->file.modification_time == b->file.modification_time ? 0 : - a->file.modification_time > b->file.modification_time ? 1 : -1; - } - - return query_string[1] == 'd' ? -cmp_result : cmp_result; -} - -static int must_hide_file(struct mg_connection *conn, const char *path) { - const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$"; - const char *pattern = conn->ctx->config[HIDE_FILES]; - return match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 || - (pattern != NULL && match_prefix(pattern, strlen(pattern), path) > 0); -} - -static int scan_directory(struct mg_connection *conn, const char *dir, - void *data, void (*cb)(struct de *, void *)) { - char path[PATH_MAX]; - struct dirent *dp; - DIR *dirp; - struct de de; - - if ((dirp = opendir(dir)) == NULL) { - return 0; - } else { - de.conn = conn; - - while ((dp = readdir(dirp)) != NULL) { - // Do not show current dir and hidden files - if (!strcmp(dp->d_name, ".") || - !strcmp(dp->d_name, "..") || - must_hide_file(conn, dp->d_name)) { - continue; - } - - mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); - - // If we don't memset stat structure to zero, mtime will have - // garbage and strftime() will segfault later on in - // print_dir_entry(). memset is required only if mg_stat() - // fails. For more details, see - // http://code.google.com/p/mongoose/issues/detail?id=79 - memset(&de.file, 0, sizeof(de.file)); - mg_stat(conn, path, &de.file); - - de.file_name = dp->d_name; - cb(&de, data); - } - (void) closedir(dirp); - } - return 1; -} - -static int remove_directory(struct mg_connection *conn, const char *dir) { - char path[PATH_MAX]; - struct dirent *dp; - DIR *dirp; - struct de de; - - if ((dirp = opendir(dir)) == NULL) { - return 0; - } else { - de.conn = conn; - - while ((dp = readdir(dirp)) != NULL) { - // Do not show current dir (but show hidden files as they will also be removed) - if (!strcmp(dp->d_name, ".") || - !strcmp(dp->d_name, "..")) { - continue; - } - - mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); - - // If we don't memset stat structure to zero, mtime will have - // garbage and strftime() will segfault later on in - // print_dir_entry(). memset is required only if mg_stat() - // fails. For more details, see - // http://code.google.com/p/mongoose/issues/detail?id=79 - memset(&de.file, 0, sizeof(de.file)); - mg_stat(conn, path, &de.file); - if(de.file.modification_time) { - if(de.file.is_directory) { - remove_directory(conn, path); - } else { - mg_remove(path); - } - } - - } - (void) closedir(dirp); - - rmdir(dir); - } - - return 1; -} - -struct dir_scan_data { - struct de *entries; - int num_entries; - int arr_size; -}; - -// Behaves like realloc(), but frees original pointer on failure -static void *realloc2(void *ptr, size_t size) { - void *new_ptr = realloc(ptr, size); - if (new_ptr == NULL) { - free(ptr); - } - return new_ptr; -} - -static void dir_scan_callback(struct de *de, void *data) { - struct dir_scan_data *dsd = (struct dir_scan_data *) data; - - if (dsd->entries == NULL || dsd->num_entries >= dsd->arr_size) { - dsd->arr_size *= 2; - dsd->entries = (struct de *) realloc2(dsd->entries, dsd->arr_size * - sizeof(dsd->entries[0])); - } - if (dsd->entries == NULL) { - // TODO(lsm): propagate an error to the caller - dsd->num_entries = 0; - } else { - dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name); - dsd->entries[dsd->num_entries].file = de->file; - dsd->entries[dsd->num_entries].conn = de->conn; - dsd->num_entries++; - } -} - -static void handle_directory_request(struct mg_connection *conn, - const char *dir) { - int i, sort_direction; - struct dir_scan_data data = { NULL, 0, 128 }; - - if (!scan_directory(conn, dir, &data, dir_scan_callback)) { - send_http_error(conn, 500, "Cannot open directory", - "Error: opendir(%s): %s", dir, strerror(ERRNO)); - return; - } - - sort_direction = conn->request_info.query_string != NULL && - conn->request_info.query_string[1] == 'd' ? 'a' : 'd'; - - conn->must_close = 1; - mg_printf(conn, "%s", - "HTTP/1.1 200 OK\r\n" - "Connection: close\r\n" - "Content-Type: text/html; charset=utf-8\r\n\r\n"); - - conn->num_bytes_sent += mg_printf(conn, - "Index of %s" - "" - "

Index of %s

"
-		""
-		""
-		""
-		"",
-		conn->request_info.uri, conn->request_info.uri,
-		sort_direction, sort_direction, sort_direction);
-
-	// Print first entry - link to a parent directory
-	conn->num_bytes_sent += mg_printf(conn,
-		""
-		"\n",
-		conn->request_info.uri, "..", "Parent directory", "-", "-");
-
-	// Sort and print directory entries
-	qsort(data.entries, (size_t) data.num_entries, sizeof(data.entries[0]),
-		compare_dir_entries);
-	for (i = 0; i < data.num_entries; i++) {
-	print_dir_entry(&data.entries[i]);
-	free(data.entries[i].file_name);
-	}
-	free(data.entries);
-
-	conn->num_bytes_sent += mg_printf(conn, "%s", "
NameModifiedSize

%s %s  %s
"); - conn->status_code = 200; -} - -// Send len bytes from the opened file to the client. -static void send_file_data(struct mg_connection *conn, struct file *filep, - int64_t offset, int64_t len) { - char buf[MG_BUF_LEN]; - int to_read, num_read, num_written; - - // Sanity check the offset - offset = offset < 0 ? 0 : offset > filep->size ? filep->size : offset; - - if (len > 0 && filep->membuf != NULL && filep->size > 0) { - if (len > filep->size - offset) { - len = filep->size - offset; - } - mg_write(conn, filep->membuf + offset, (size_t) len); - } else if (len > 0 && filep->fp != NULL) { - fseeko(filep->fp, offset, SEEK_SET); - while (len > 0) { - // Calculate how much to read from the file in the buffer - to_read = sizeof(buf); - if ((int64_t) to_read > len) { - to_read = (int) len; - } - - // Read from file, exit the loop on error - if ((num_read = fread(buf, 1, (size_t) to_read, filep->fp)) <= 0) { - break; - } - - // Send read bytes to the client, exit the loop on error - if ((num_written = mg_write(conn, buf, (size_t) num_read)) != num_read) { - break; - } - - // Both read and were successful, adjust counters - conn->num_bytes_sent += num_written; - len -= num_written; - } - } -} - -static int parse_range_header(const char *header, int64_t *a, int64_t *b) { - return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b); -} - -static void gmt_time_string(char *buf, size_t buf_len, time_t *t) { - strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t)); -} - -static void construct_etag(char *buf, size_t buf_len, - const struct file *filep) { - snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"", - (unsigned long) filep->modification_time, filep->size); -} - -static void fclose_on_exec(struct file *filep) { - if (filep != NULL && filep->fp != NULL) { -#ifndef _WIN32 - fcntl(fileno(filep->fp), F_SETFD, FD_CLOEXEC); -#endif - } -} - -static void handle_file_request(struct mg_connection *conn, const char *path, - struct file *filep) { - char date[64], lm[64], etag[64], range[64]; - const char *msg = "OK", *hdr; - time_t curtime = time(NULL); - int64_t cl, r1, r2; - struct vec mime_vec; - int n; - char gz_path[PATH_MAX]; - char const* encoding = ""; - - get_mime_type(conn->ctx, path, &mime_vec); - cl = filep->size; - conn->status_code = 200; - range[0] = '\0'; - - // if this file is in fact a pre-gzipped file, rewrite its filename - // it's important to rewrite the filename after resolving - // the mime type from it, to preserve the actual file's type - if (filep->gzipped) { - snprintf(gz_path, sizeof(gz_path), "%s.gz", path); - path = gz_path; - encoding = "Content-Encoding: gzip\r\n"; - } - - if (!mg_fopen(conn, path, "rb", filep)) { - send_http_error(conn, 500, http_500_error, - "fopen(%s): %s", path, strerror(ERRNO)); - return; - } - - fclose_on_exec(filep); - - // If Range: header specified, act accordingly - r1 = r2 = 0; - hdr = mg_get_header(conn, "Range"); - if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 && - r1 >= 0 && r2 >= 0) { - // actually, range requests don't play well with a pre-gzipped - // file (since the range is specified in the uncmpressed space) - if (filep->gzipped) { - send_http_error(conn, 501, "Not Implemented", "range requests in gzipped files are not supported"); - return; - } - conn->status_code = 206; - cl = n == 2 ? (r2 > cl ? cl : r2) - r1 + 1: cl - r1; - mg_snprintf(conn, range, sizeof(range), - "Content-Range: bytes " - "%" INT64_FMT "-%" - INT64_FMT "/%" INT64_FMT "\r\n", - r1, r1 + cl - 1, filep->size); - msg = "Partial Content"; - } - - // Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 - gmt_time_string(date, sizeof(date), &curtime); - gmt_time_string(lm, sizeof(lm), &filep->modification_time); - construct_etag(etag, sizeof(etag), filep); - - (void) mg_printf(conn, - "HTTP/1.1 %d %s\r\n" - "Date: %s\r\n" - "Last-Modified: %s\r\n" - "Etag: %s\r\n" - "Content-Type: %.*s\r\n" - "Content-Length: %" INT64_FMT "\r\n" - "Connection: %s\r\n" - "Accept-Ranges: bytes\r\n" - "%s%s\r\n", - conn->status_code, msg, date, lm, etag, (int) mime_vec.len, - mime_vec.ptr, cl, suggest_connection_header(conn), range, encoding); - - if (strcmp(conn->request_info.request_method, "HEAD") != 0) { - send_file_data(conn, filep, r1, cl); - } - mg_fclose(filep); -} - -void mg_send_file(struct mg_connection *conn, const char *path) { - struct file file = STRUCT_FILE_INITIALIZER; - if (mg_stat(conn, path, &file)) { - handle_file_request(conn, path, &file); - } else { - send_http_error(conn, 404, "Not Found", "%s", "File not found"); - } -} - - // Parse HTTP headers from the given buffer, advance buffer to the point // where parsing stopped. -static void parse_http_headers(char **buf, struct mg_request_info *ri) { - int i; +static void parse_http_headers(char **buf, struct mg_connection *ri) { + size_t i; - for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) { - ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0); - ri->http_headers[i].value = skip(buf, "\r\n"); - if (ri->http_headers[i].name[0] == '\0') - break; - ri->num_headers = i + 1; - } + for (i = 0; i < ARRAY_SIZE(ri->http_headers); i++) { + ri->http_headers[i].name = skip(buf, ": "); + ri->http_headers[i].value = skip(buf, "\r\n"); + if (ri->http_headers[i].name[0] == '\0') + break; + ri->num_headers = i + 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 const char *status_code_to_str(int status_code) { + switch (status_code) { + case 200: return "OK"; + case 201: return "Created"; + case 204: return "No Content"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 304: return "Not Modified"; + case 400: return "Bad Request"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 409: return "Conflict"; + case 411: return "Length Required"; + case 413: return "Request Entity Too Large"; + case 415: return "Unsupported Media Type"; + case 423: return "Locked"; + case 500: return "Server Error"; + case 501: return "Not Implemented"; + default: return "Server Error"; + } } -// Parse HTTP request, fill in mg_request_info structure. -// This function modifies the buffer by NUL-terminating -// HTTP request components, header names and header values. -static int parse_http_message(char *buf, int len, struct mg_request_info *ri) { - int is_request, request_length = get_request_len(buf, len); - if (request_length > 0) { - // Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port - ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL; - ri->num_headers = 0; - - buf[request_length - 1] = '\0'; - - // RFC says that all initial whitespaces should be ingored - while (*buf != '\0' && isspace(* (unsigned char *) buf)) { - buf++; - } - ri->request_method = skip(&buf, " "); - ri->uri = skip(&buf, " "); - ri->http_version = skip(&buf, "\r\n"); - if (((is_request = is_valid_http_method(ri->request_method)) && - memcmp(ri->http_version, "HTTP/", 5) != 0) || - (!is_request && memcmp(ri->request_method, "HTTP/", 5)) != 0) { - request_length = -1; - } else { - if (is_request) { - ri->http_version += 5; - } - parse_http_headers(&buf, ri); - } - } - return request_length; +static int call_user(struct connection *conn, enum mg_event ev) { + return conn != NULL && conn->server != NULL && + conn->server->event_handler != NULL ? + conn->server->event_handler(&conn->mg_conn, ev) : MG_FALSE; } -// Keep reading the input (either opened file descriptor fd, or socket sock, -// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the -// buffer (which marks the end of HTTP request). Buffer buf may already -// have some data. The length of the data is stored in nread. -// Upon every read operation, increase nread by the number of bytes read. -static int read_request(FILE *fp, struct mg_connection *conn, - char *buf, int bufsiz, int *nread) { - int request_len, n = 0; +static void send_http_error(struct connection *conn, int code, + const char *fmt, ...) { + const char *message = status_code_to_str(code); + const char *rewrites = conn->server->config_options[URL_REWRITES]; + char headers[200], body[200]; + struct vec a, b; + va_list ap; + int body_len, headers_len, match_code; - request_len = get_request_len(buf, *nread); - while (*nread < bufsiz && request_len == 0 && - (n = pull(fp, conn, buf + *nread, bufsiz - *nread)) > 0) { - *nread += n; - assert(*nread <= bufsiz); - request_len = get_request_len(buf, *nread); - } + conn->mg_conn.status_code = code; - return request_len <= 0 && n <= 0 ? -1 : request_len; + // Invoke error handler if it is set + if (call_user(conn, MG_HTTP_ERROR) == MG_TRUE) { + close_local_endpoint(conn); + return; + } + + // Handle error code rewrites + while ((rewrites = next_option(rewrites, &a, &b)) != NULL) { + if ((match_code = atoi(a.ptr)) > 0 && match_code == code) { + struct mg_connection *c = &conn->mg_conn; + c->status_code = 302; + mg_printf(c, "HTTP/1.1 %d Moved\r\n" + "Location: %.*s?code=%d&orig_uri=%s&query_string=%s\r\n\r\n", + c->status_code, b.len, b.ptr, code, c->uri, + c->query_string == NULL ? "" : c->query_string); + close_local_endpoint(conn); + return; + } + } + + body_len = mg_snprintf(body, sizeof(body), "%d %s\n", code, message); + if (fmt != NULL) { + va_start(ap, fmt); + body_len += mg_vsnprintf(body + body_len, sizeof(body) - body_len, fmt, ap); + va_end(ap); + } + if ((code >= 300 && code <= 399) || code == 204) { + // 3xx errors do not have body + body_len = 0; + } + headers_len = mg_snprintf(headers, sizeof(headers), + "HTTP/1.1 %d %s\r\nContent-Length: %d\r\n" + "Content-Type: text/plain\r\n\r\n", + code, message, body_len); + ns_send(conn->ns_conn, headers, headers_len); + ns_send(conn->ns_conn, body, body_len); + close_local_endpoint(conn); // This will write to the log file } -// For given directory path, substitute it to valid index file. -// Return 0 if index file has been found, -1 if not found. -// If the file is found, it's stats is returned in stp. -static int substitute_index_file(struct mg_connection *conn, char *path, - size_t path_len, struct file *filep) { - const char *list = conn->ctx->config[INDEX_FILES]; - struct file file = STRUCT_FILE_INITIALIZER; - struct vec filename_vec; - size_t n = strlen(path); - int found = 0; - - // The 'path' given to us points to the directory. Remove all trailing - // directory separator characters from the end of the path, and - // then append single directory separator character. - while (n > 0 && path[n - 1] == '/') { - n--; - } - path[n] = '/'; - - // Traverse index files list. For each entry, append it to the given - // path and see if the file exists. If it exists, break the loop - while ((list = next_option(list, &filename_vec, NULL)) != NULL) { - // Ignore too long entries that may overflow path buffer - if (filename_vec.len > path_len - (n + 2)) - continue; - - // Prepare full path to the index file - mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1); - - // Does it exist? - if (mg_stat(conn, path, &file)) { - // Yes it does, break the loop - *filep = file; - found = 1; - break; - } - } - - // If no index file exists, restore directory path - if (!found) { - path[n] = '\0'; - } - - return found; +static void write_chunk(struct connection *conn, const char *buf, int len) { + char chunk_size[50]; + int n = mg_snprintf(chunk_size, sizeof(chunk_size), "%X\r\n", len); + ns_send(conn->ns_conn, chunk_size, n); + ns_send(conn->ns_conn, buf, len); + ns_send(conn->ns_conn, "\r\n", 2); } -// Return True if we should reply 304 Not Modified. -static int is_not_modified(const struct mg_connection *conn, - const struct file *filep) { - char etag[64]; - const char *ims = mg_get_header(conn, "If-Modified-Since"); - const char *inm = mg_get_header(conn, "If-None-Match"); - construct_etag(etag, sizeof(etag), filep); - return (inm != NULL && !mg_strcasecmp(etag, inm)) || - (ims != NULL && filep->modification_time <= parse_date_string(ims)); +int 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); + va_end(ap); + + return len; } -static int forward_body_data(struct mg_connection *conn, FILE *fp, - SOCKET sock, SSL *ssl) { - const char *expect, *body; - char buf[MG_BUF_LEN]; - int to_read, nread, buffered_len, success = 0; - - expect = mg_get_header(conn, "Expect"); - assert(fp != NULL); - - if (conn->content_len == -1) { - send_http_error(conn, 411, "Length Required", "%s", ""); - } else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) { - send_http_error(conn, 417, "Expectation Failed", "%s", ""); - } else { - if (expect != NULL) { - (void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n"); - } - - body = conn->buf + conn->request_len + conn->consumed_content; - buffered_len = &conn->buf[conn->data_len] - body; - assert(buffered_len >= 0); - assert(conn->consumed_content == 0); - - if (buffered_len > 0) { - if ((int64_t) buffered_len > conn->content_len) { - buffered_len = (int) conn->content_len; - } - push(fp, sock, ssl, body, (int64_t) buffered_len); - conn->consumed_content += buffered_len; - } - - nread = 0; - while (conn->consumed_content < conn->content_len) { - to_read = sizeof(buf); - if ((int64_t) to_read > conn->content_len - conn->consumed_content) { - to_read = (int) (conn->content_len - conn->consumed_content); - } - nread = pull(NULL, conn, buf, to_read); - if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) { - break; - } - conn->consumed_content += nread; - } - - if (conn->consumed_content == conn->content_len) { - success = nread >= 0; - } - - // Each error code path in this function must send an error - if (!success) { - send_http_error(conn, 577, http_500_error, "%s", ""); - } - } - - return success; +static void ns_forward(struct ns_connection *from, struct ns_connection *to) { + DBG(("%p -> %p %zu bytes", from, to, from->recv_iobuf.len)); + ns_send(to, from->recv_iobuf.buf, from->recv_iobuf.len); + iobuf_remove(&from->recv_iobuf, from->recv_iobuf.len); } -#if !defined(NO_CGI) +#ifndef MONGOOSE_NO_CGI +#ifdef _WIN32 +struct threadparam { + sock_t s; + HANDLE hPipe; +}; + +static int wait_until_ready(sock_t sock, int for_read) { + fd_set set; + FD_ZERO(&set); + FD_SET(sock, &set); + select(sock + 1, for_read ? &set : 0, for_read ? 0 : &set, 0, 0); + return 1; +} + +static void *push_to_stdin(void *arg) { + struct threadparam *tp = (struct threadparam *)arg; + int n, sent, stop = 0; + DWORD k; + char buf[IOBUF_SIZE]; + + while (!stop && wait_until_ready(tp->s, 1) && + (n = recv(tp->s, buf, sizeof(buf), 0)) > 0) { + if (n == -1 && GetLastError() == WSAEWOULDBLOCK) continue; + for (sent = 0; !stop && sent < n; sent += k) { + if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0)) stop = 1; + } + } + DBG(("%s", "FORWARED EVERYTHING TO CGI")); + CloseHandle(tp->hPipe); + free(tp); + _endthread(); + return NULL; +} + +static void *pull_from_stdout(void *arg) { + struct threadparam *tp = (struct threadparam *)arg; + int k, stop = 0; + DWORD n, sent; + char buf[IOBUF_SIZE]; + + while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) { + for (sent = 0; !stop && sent < n; sent += k) { + if (wait_until_ready(tp->s, 0) && + (k = send(tp->s, buf + sent, n - sent, 0)) <= 0) stop = 1; + } + } + DBG(("%s", "EOF FROM CGI")); + CloseHandle(tp->hPipe); + shutdown(tp->s, 2); // Without this, IO thread may get truncated data + closesocket(tp->s); + free(tp); + _endthread(); + return NULL; +} + +static void spawn_stdio_thread(sock_t sock, HANDLE hPipe, + void *(*func)(void *)) { + struct threadparam *tp = (struct threadparam *)malloc(sizeof(*tp)); + if (tp != NULL) { + tp->s = sock; + tp->hPipe = hPipe; + mg_start_thread(func, tp); + } +} + +static void abs_path(const char *utf8_path, char *abs_path, size_t len) { + wchar_t buf[MAX_PATH_SIZE], buf2[MAX_PATH_SIZE]; + to_wchar(utf8_path, buf, ARRAY_SIZE(buf)); + GetFullPathNameW(buf, ARRAY_SIZE(buf2), buf2, NULL); + WideCharToMultiByte(CP_UTF8, 0, buf2, wcslen(buf2) + 1, abs_path, len, 0, 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 = {0}; + PROCESS_INFORMATION pi = {0}; + 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], + cmdline[MAX_PATH_SIZE], *p; + DWORD flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS; + FILE *fp; + + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + CreatePipe(&a[0], &a[1], NULL, 0); + CreatePipe(&b[0], &b[1], NULL, 0); + DuplicateHandle(me, a[0], me, &si.hStdInput, 0, TRUE, flags); + DuplicateHandle(me, b[1], me, &si.hStdOutput, 0, TRUE, flags); + + if (interp == NULL && (fp = fopen(cmd, "r")) != NULL) { + buf[0] = buf[1] = '\0'; + fgets(buf, sizeof(buf), fp); + buf[sizeof(buf) - 1] = '\0'; + if (buf[0] == '#' && buf[1] == '!') { + interp = buf + 2; + for (p = interp + strlen(interp); + isspace(* (uint8_t *) p) && p > interp; p--) *p = '\0'; + } + fclose(fp); + } + + if (interp != NULL) { + abs_path(interp, buf4, ARRAY_SIZE(buf4)); + interp = buf4; + } + abs_path(dir, buf5, ARRAY_SIZE(buf5)); + to_wchar(dir, full_dir, ARRAY_SIZE(full_dir)); + mg_snprintf(cmdline, sizeof(cmdline), "%s%s\"%s\"", + interp ? interp : "", interp ? " " : "", cmd); + to_wchar(cmdline, wcmd, ARRAY_SIZE(wcmd)); + + if (CreateProcessW(NULL, wcmd, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP, + (void *) env, full_dir, &si, &pi) != 0) { + spawn_stdio_thread(sock, a[1], push_to_stdin); + spawn_stdio_thread(sock, b[0], pull_from_stdout); + } else { + CloseHandle(a[1]); + CloseHandle(b[0]); + closesocket(sock); + } + DBG(("CGI command: [%ls] -> %p", wcmd, pi.hProcess)); + + CloseHandle(si.hStdOutput); + CloseHandle(si.hStdInput); + CloseHandle(a[0]); + CloseHandle(b[1]); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + return pi.hProcess; +} +#else +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]; + process_id_t pid = fork(); + (void) env; + + if (pid == 0) { + (void) chdir(dir); + (void) dup2(sock, 0); + (void) dup2(sock, 1); + closesocket(sock); + + // After exec, all signal handlers are restored to their default values, + // with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's + // implementation, SIGCHLD's handler will leave unchanged after exec + // if it was set to be ignored. Restore it to default action. + signal(SIGCHLD, SIG_DFL); + + if (interp == NULL) { + execle(cmd, cmd, NULL, envp); + } else { + execle(interp, interp, cmd, NULL, envp); + } + snprintf(buf, sizeof(buf), "Status: 500\r\n\r\n" + "500 Server Error: %s%s%s: %s", interp == NULL ? "" : interp, + interp == NULL ? "" : " ", cmd, strerror(errno)); + send(1, buf, strlen(buf), 0); + exit(EXIT_FAILURE); // exec call failed + } + + return pid; +} +#endif // _WIN32 + // This structure helps to create an environment for the spawned CGI program. // Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings, // last element must be NULL. @@ -3205,639 +1802,642 @@ static int forward_body_data(struct mg_connection *conn, FILE *fp, // We satisfy both worlds: we create an envp array (which is vars), all // entries are actually pointers inside buf. struct cgi_env_block { - struct mg_connection *conn; - char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer - int len; // Space taken - char *vars[MAX_CGI_ENVIR_VARS]; // char **envp - int nvars; // Number of variables + struct mg_connection *conn; + char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer + const char *vars[MAX_CGI_ENVIR_VARS]; // char *envp[] + int len; // Space taken + int nvars; // Number of variables in envp[] }; -static char *addenv(struct cgi_env_block *block, - PRINTF_FORMAT_STRING(const char *fmt), ...) - PRINTF_ARGS(2, 3); - // Append VARIABLE=VALUE\0 string to the buffer, and add a respective // pointer into the vars array. static char *addenv(struct cgi_env_block *block, const char *fmt, ...) { - int n, space; - char *added; - va_list ap; + int n, space; + char *added; + va_list ap; - // Calculate how much space is left in the buffer - space = sizeof(block->buf) - block->len - 2; - assert(space >= 0); + // Calculate how much space is left in the buffer + space = sizeof(block->buf) - block->len - 2; + assert(space >= 0); - // Make a pointer to the free space int the buffer - added = block->buf + block->len; + // Make a pointer to the free space int the buffer + added = block->buf + block->len; - // Copy VARIABLE=VALUE\0 string into the free space - va_start(ap, fmt); - n = mg_vsnprintf(block->conn, added, (size_t) space, fmt, ap); - va_end(ap); + // Copy VARIABLE=VALUE\0 string into the free space + va_start(ap, fmt); + n = mg_vsnprintf(added, (size_t) space, fmt, ap); + va_end(ap); - // Make sure we do not overflow buffer and the envp array - if (n > 0 && n + 1 < space && - block->nvars < (int) ARRAY_SIZE(block->vars) - 2) { - // Append a pointer to the added string into the envp array - block->vars[block->nvars++] = added; - // Bump up used length counter. Include \0 terminator - block->len += n + 1; - } else { - cry(block->conn, "%s: CGI env buffer truncated for [%s]", __func__, fmt); - } + // Make sure we do not overflow buffer and the envp array + if (n > 0 && n + 1 < space && + block->nvars < (int) ARRAY_SIZE(block->vars) - 2) { + // Append a pointer to the added string into the envp array + block->vars[block->nvars++] = added; + // Bump up used length counter. Include \0 terminator + block->len += n + 1; + } - return added; + return added; } -static void prepare_cgi_environment(struct mg_connection *conn, - const char *prog, - struct cgi_env_block *blk) { - const char *s, *slash; - struct vec var_vec; - char *p, src_addr[IP_ADDR_STR_LEN]; - int i; +static void addenv2(struct cgi_env_block *blk, const char *name) { + const char *s; + if ((s = getenv(name)) != NULL) addenv(blk, "%s=%s", name, s); +} - blk->len = blk->nvars = 0; - blk->conn = conn; - sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa); +static void prepare_cgi_environment(struct connection *conn, + const char *prog, + struct cgi_env_block *blk) { + struct mg_connection *ri = &conn->mg_conn; + const char *s, *slash; + char *p, **opts = conn->server->config_options; + int i; - addenv(blk, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]); - addenv(blk, "SERVER_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]); - addenv(blk, "DOCUMENT_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]); + blk->len = blk->nvars = 0; + blk->conn = ri; - // Prepare the environment block - addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1"); - addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1"); - addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP + if ((s = getenv("SERVER_NAME")) != NULL) { + addenv(blk, "SERVER_NAME=%s", s); + } else { + addenv(blk, "SERVER_NAME=%s", ri->local_ip); + } + addenv(blk, "SERVER_ROOT=%s", opts[DOCUMENT_ROOT]); + addenv(blk, "DOCUMENT_ROOT=%s", opts[DOCUMENT_ROOT]); + addenv(blk, "SERVER_SOFTWARE=%s/%s", "Mongoose", MONGOOSE_VERSION); - // TODO(lsm): fix this for IPv6 case - addenv(blk, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin.sin_port)); + // Prepare the environment block + addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1"); + addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1"); + addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP - addenv(blk, "REQUEST_METHOD=%s", conn->request_info.request_method); - addenv(blk, "REMOTE_ADDR=%s", src_addr); - addenv(blk, "REMOTE_PORT=%d", conn->request_info.remote_port); - addenv(blk, "REQUEST_URI=%s", conn->request_info.uri); + // TODO(lsm): fix this for IPv6 case + //addenv(blk, "SERVER_PORT=%d", ri->remote_port); - // SCRIPT_NAME - assert(conn->request_info.uri[0] == '/'); - slash = strrchr(conn->request_info.uri, '/'); - if ((s = strrchr(prog, '/')) == NULL) - s = prog; - addenv(blk, "SCRIPT_NAME=%.*s%s", (int) (slash - conn->request_info.uri), - conn->request_info.uri, s); + addenv(blk, "REQUEST_METHOD=%s", ri->request_method); + addenv(blk, "REMOTE_ADDR=%s", ri->remote_ip); + addenv(blk, "REMOTE_PORT=%d", ri->remote_port); + addenv(blk, "REQUEST_URI=%s%s%s", ri->uri, + ri->query_string == NULL ? "" : "?", + ri->query_string == NULL ? "" : ri->query_string); - addenv(blk, "SCRIPT_FILENAME=%s", prog); - addenv(blk, "PATH_TRANSLATED=%s", prog); - addenv(blk, "HTTPS=%s", conn->ssl == NULL ? "off" : "on"); + // SCRIPT_NAME + if (conn->path_info != NULL) { + addenv(blk, "SCRIPT_NAME=%.*s", + (int) (strlen(ri->uri) - strlen(conn->path_info)), ri->uri); + addenv(blk, "PATH_INFO=%s", conn->path_info); + } else { + s = strrchr(prog, '/'); + slash = strrchr(ri->uri, '/'); + addenv(blk, "SCRIPT_NAME=%.*s%s", + slash == NULL ? 0 : (int) (slash - ri->uri), ri->uri, + s == NULL ? prog : s); + } - if ((s = mg_get_header(conn, "Content-Type")) != NULL) - addenv(blk, "CONTENT_TYPE=%s", s); + addenv(blk, "SCRIPT_FILENAME=%s", prog); + addenv(blk, "PATH_TRANSLATED=%s", prog); + addenv(blk, "HTTPS=%s", conn->ns_conn->ssl != NULL ? "on" : "off"); - if (conn->request_info.query_string != NULL) - addenv(blk, "QUERY_STRING=%s", conn->request_info.query_string); + if ((s = mg_get_header(ri, "Content-Type")) != NULL) + addenv(blk, "CONTENT_TYPE=%s", s); - if ((s = mg_get_header(conn, "Content-Length")) != NULL) - addenv(blk, "CONTENT_LENGTH=%s", s); + if (ri->query_string != NULL) + addenv(blk, "QUERY_STRING=%s", ri->query_string); - if ((s = getenv("PATH")) != NULL) - addenv(blk, "PATH=%s", s); + if ((s = mg_get_header(ri, "Content-Length")) != NULL) + addenv(blk, "CONTENT_LENGTH=%s", s); - if (conn->path_info != NULL) { - addenv(blk, "PATH_INFO=%s", conn->path_info); - } + addenv2(blk, "PATH"); + addenv2(blk, "TMP"); + addenv2(blk, "TEMP"); + addenv2(blk, "TMPDIR"); + addenv2(blk, "PERLLIB"); + addenv2(blk, ENV_EXPORT_TO_CGI); #if defined(_WIN32) - if ((s = getenv("COMSPEC")) != NULL) { - addenv(blk, "COMSPEC=%s", s); - } - if ((s = getenv("SYSTEMROOT")) != NULL) { - addenv(blk, "SYSTEMROOT=%s", s); - } - if ((s = getenv("SystemDrive")) != NULL) { - addenv(blk, "SystemDrive=%s", s); - } - if ((s = getenv("ProgramFiles")) != NULL) { - addenv(blk, "ProgramFiles=%s", s); - } - if ((s = getenv("ProgramFiles(x86)")) != NULL) { - addenv(blk, "ProgramFiles(x86)=%s", s); - } + addenv2(blk, "COMSPEC"); + addenv2(blk, "SYSTEMROOT"); + addenv2(blk, "SystemDrive"); + addenv2(blk, "ProgramFiles"); + addenv2(blk, "ProgramFiles(x86)"); + addenv2(blk, "CommonProgramFiles(x86)"); #else - if ((s = getenv("LD_LIBRARY_PATH")) != NULL) - addenv(blk, "LD_LIBRARY_PATH=%s", s); + addenv2(blk, "LD_LIBRARY_PATH"); #endif // _WIN32 - if ((s = getenv("PERLLIB")) != NULL) - addenv(blk, "PERLLIB=%s", s); + // Add all headers as HTTP_* variables + for (i = 0; i < ri->num_headers; i++) { + p = addenv(blk, "HTTP_%s=%s", + ri->http_headers[i].name, ri->http_headers[i].value); - if (conn->request_info.remote_user != NULL) { - addenv(blk, "REMOTE_USER=%s", conn->request_info.remote_user); - addenv(blk, "%s", "AUTH_TYPE=Digest"); - } + // Convert variable name into uppercase, and change - to _ + for (; *p != '=' && *p != '\0'; p++) { + if (*p == '-') + *p = '_'; + *p = (char) toupper(* (unsigned char *) p); + } + } - // Add all headers as HTTP_* variables - for (i = 0; i < conn->request_info.num_headers; i++) { - p = addenv(blk, "HTTP_%s=%s", - conn->request_info.http_headers[i].name, - conn->request_info.http_headers[i].value); + blk->vars[blk->nvars++] = NULL; + blk->buf[blk->len++] = '\0'; - // Convert variable name into uppercase, and change - to _ - for (; *p != '=' && *p != '\0'; p++) { - if (*p == '-') - *p = '_'; - *p = (char) toupper(* (unsigned char *) p); - } - } - - // Add user-specified variables - s = conn->ctx->config[CGI_ENVIRONMENT]; - while ((s = next_option(s, &var_vec, NULL)) != NULL) { - addenv(blk, "%.*s", (int) var_vec.len, var_vec.ptr); - } - - blk->vars[blk->nvars++] = NULL; - blk->buf[blk->len++] = '\0'; - - assert(blk->nvars < (int) ARRAY_SIZE(blk->vars)); - assert(blk->len > 0); - assert(blk->len < (int) sizeof(blk->buf)); + assert(blk->nvars < (int) ARRAY_SIZE(blk->vars)); + assert(blk->len > 0); + assert(blk->len < (int) sizeof(blk->buf)); } -static void handle_cgi_request(struct mg_connection *conn, const char *prog) { - int headers_len, data_len, i, fd_stdin[2], fd_stdout[2]; - const char *status, *status_text; - char buf[16384], *pbuf, dir[PATH_MAX], *p; - struct mg_request_info ri; - struct cgi_env_block blk; - FILE *in, *out; - struct file fout = STRUCT_FILE_INITIALIZER; - pid_t pid; - ri.mg_request_info::num_headers = 0; +static const char cgi_status[] = "HTTP/1.1 200 OK\r\n"; - prepare_cgi_environment(conn, prog, &blk); +static void open_cgi_endpoint(struct connection *conn, const char *prog) { + struct cgi_env_block blk; + char dir[MAX_PATH_SIZE]; + const char *p; + sock_t fds[2]; - // CGI must be executed in its own directory. 'dir' must point to the - // directory containing executable program, 'p' must point to the - // executable program name relative to 'dir'. - (void) mg_snprintf(conn, dir, sizeof(dir), "%s", prog); - if ((p = strrchr(dir, '/')) != NULL) { - *p++ = '\0'; - } else { - dir[0] = '.', dir[1] = '\0'; - p = (char *) prog; - } + prepare_cgi_environment(conn, prog, &blk); + // CGI must be executed in its own directory. 'dir' must point to the + // directory containing executable program, 'p' must point to the + // executable program name relative to 'dir'. + if ((p = strrchr(prog, '/')) == NULL) { + mg_snprintf(dir, sizeof(dir), "%s", "."); + } else { + mg_snprintf(dir, sizeof(dir), "%.*s", (int) (p - prog), prog); + } - pid = (pid_t) -1; - fd_stdin[0] = fd_stdin[1] = fd_stdout[0] = fd_stdout[1] = -1; - in = out = NULL; + // Try to create socketpair in a loop until success. ns_socketpair() + // can be interrupted by a signal and fail. + // TODO(lsm): use sigaction to restart interrupted syscall + do { + ns_socketpair(fds); + } while (fds[0] == INVALID_SOCKET); - if (pipe(fd_stdin) != 0 || pipe(fd_stdout) != 0) { - send_http_error(conn, 500, http_500_error, - "Cannot create CGI pipe: %s", strerror(ERRNO)); - goto done; - } + if (start_process(conn->server->config_options[CGI_INTERPRETER], + prog, blk.buf, blk.vars, dir, fds[1]) > 0) { + conn->endpoint_type = EP_CGI; + conn->endpoint.nc = ns_add_sock(&conn->server->ns_server, + fds[0], 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); + } - pid = spawn_process(conn, p, blk.buf, blk.vars, fd_stdin[0], fd_stdout[1], - dir); - // spawn_process() must close those! - // If we don't mark them as closed, close() attempt before - // return from this function throws an exception on Windows. - // Windows does not like when closed descriptor is closed again. - fd_stdin[0] = fd_stdout[1] = -1; - - if (pid == (pid_t) -1) { - send_http_error(conn, 500, http_500_error, - "Cannot spawn CGI process [%s]: %s", prog, strerror(ERRNO)); - goto done; - } - - if ((in = fdopen(fd_stdin[1], "wb")) == NULL || - (out = fdopen(fd_stdout[0], "rb")) == NULL) { - send_http_error(conn, 500, http_500_error, - "fopen: %s", strerror(ERRNO)); - goto done; - } - - setbuf(in, NULL); - setbuf(out, NULL); - fout.fp = out; - - // Send POST data to the CGI process if needed - if (!strcmp(conn->request_info.request_method, "POST") && - !forward_body_data(conn, in, INVALID_SOCKET, NULL)) { - goto done; - } - - // Close so child gets an EOF. - fclose(in); - in = NULL; - fd_stdin[1] = -1; - - // Now read CGI reply into a buffer. We need to set correct - // status code, thus we need to see all HTTP headers first. - // Do not send anything back to client, until we buffer in all - // HTTP headers. - data_len = 0; - headers_len = read_request(out, conn, buf, sizeof(buf), &data_len); - if (headers_len <= 0) { - send_http_error(conn, 500, http_500_error, - "CGI program sent malformed or too big (>%u bytes) " - "HTTP headers: [%.*s]", - (unsigned) sizeof(buf), data_len, buf); - goto done; - } - pbuf = buf; - buf[headers_len - 1] = '\0'; - parse_http_headers(&pbuf, &ri); - - // Make up and send the status line - status_text = "OK"; - if ((status = get_header(&ri, "Status")) != NULL) { - conn->status_code = atoi(status); - status_text = status; - while (isdigit(* (unsigned char *) status_text) || *status_text == ' ') { - status_text++; - } - } else if (get_header(&ri, "Location") != NULL) { - conn->status_code = 302; - } else { - conn->status_code = 200; - } - if (get_header(&ri, "Connection") != NULL && - !mg_strcasecmp(get_header(&ri, "Connection"), "keep-alive")) { - conn->must_close = 1; - } - (void) mg_printf(conn, "HTTP/1.1 %d %s\r\n", conn->status_code, - status_text); - - // Send headers - for (i = 0; i < ri.num_headers; i++) { - mg_printf(conn, "%s: %s\r\n", - ri.http_headers[i].name, ri.http_headers[i].value); - } - mg_write(conn, "\r\n", 2); - - // Send chunk of data that may have been read after the headers - conn->num_bytes_sent += mg_write(conn, buf + headers_len, - (size_t)(data_len - headers_len)); - - // Read the rest of CGI output and send to the client - send_file_data(conn, &fout, 0, INT64_MAX); - -done: - if (pid != (pid_t) -1) { - kill(pid, SIGKILL); - } - if (fd_stdin[0] != -1) { - close(fd_stdin[0]); - } - if (fd_stdout[1] != -1) { - close(fd_stdout[1]); - } - - if (in != NULL) { - fclose(in); - } else if (fd_stdin[1] != -1) { - close(fd_stdin[1]); - } - - if (out != NULL) { - fclose(out); - } else if (fd_stdout[0] != -1) { - close(fd_stdout[0]); - } -} -#endif // !NO_CGI - -// For a given PUT path, create all intermediate subdirectories -// for given path. Return 0 if the path itself is a directory, -// or -1 on error, 1 if OK. -static int put_dir(struct mg_connection *conn, const char *path) { - char buf[PATH_MAX]; - const char *s, *p; - struct file file = STRUCT_FILE_INITIALIZER; - int len, res = 1; - - for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) { - len = p - path; - if (len >= (int) sizeof(buf)) { - res = -1; - break; - } - memcpy(buf, path, len); - buf[len] = '\0'; - - // Try to create intermediate directory - DEBUG_TRACE(("mkdir(%s)", buf)); - if (!mg_stat(conn, buf, &file) && mg_mkdir(buf, 0755) != 0) { - res = -1; - break; - } - - // Is path itself a directory? - if (p[1] == '\0') { - res = 0; - } - } - - return res; +#ifndef _WIN32 + closesocket(fds[1]); // On Windows, CGI stdio thread closes that socket +#endif } -static void mkcol(struct mg_connection *conn, const char *path) { - int rc, body_len; - struct de de; - memset(&de.file, 0, sizeof(de.file)); - mg_stat(conn, path, &de.file); +static void on_cgi_data(struct ns_connection *nc) { + struct connection *conn = (struct connection *) nc->connection_data; + const char *status = "500"; + struct mg_connection c; - if(de.file.modification_time) { - send_http_error(conn, 405, "Method Not Allowed", - "mkcol(%s): %s", path, strerror(ERRNO)); - return; - } + if (!conn) return; - body_len = conn->data_len - conn->request_len; - if(body_len > 0) { - send_http_error(conn, 415, "Unsupported media type", - "mkcol(%s): %s", path, strerror(ERRNO)); - return; - } + // Copy CGI data from CGI socket to the client send buffer + ns_forward(nc, conn->ns_conn); - rc = mg_mkdir(path, 0755); + // If reply has not been parsed yet, parse it + if (conn->ns_conn->flags & NSF_BUFFER_BUT_DONT_SEND) { + struct iobuf *io = &conn->ns_conn->send_iobuf; + int s_len = sizeof(cgi_status) - 1; + int len = get_request_len(io->buf + s_len, io->len - s_len); + char buf[MAX_REQUEST_SIZE], *s = buf; - if (rc == 0) { - conn->status_code = 201; - mg_printf(conn, "HTTP/1.1 %d Created\r\n\r\n", conn->status_code); - } else if (rc == -1) { - if(errno == EEXIST) - send_http_error(conn, 405, "Method Not Allowed", - "mkcol(%s): %s", path, strerror(ERRNO)); - else if(errno == EACCES) - send_http_error(conn, 403, "Forbidden", - "mkcol(%s): %s", path, strerror(ERRNO)); - else if(errno == ENOENT) - send_http_error(conn, 409, "Conflict", - "mkcol(%s): %s", path, strerror(ERRNO)); - else - send_http_error(conn, 500, http_500_error, - "fopen(%s): %s", path, strerror(ERRNO)); - } + if (len == 0) return; + + if (len < 0 || len > (int) sizeof(buf)) { + len = io->len; + iobuf_remove(io, io->len); + 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); + buf[len - 1] = '\0'; + parse_http_headers(&s, &c); + if (mg_get_header(&c, "Location") != NULL) { + status = "302"; + } else if ((status = (char *) mg_get_header(&c, "Status")) == NULL) { + status = "200"; + } + memcpy(io->buf + 9, status, 3); + conn->mg_conn.status_code = atoi(status); + } + conn->ns_conn->flags &= ~NSF_BUFFER_BUT_DONT_SEND; + } +} +#endif // !MONGOOSE_NO_CGI + +static char *mg_strdup(const char *str) { + char *copy = (char *) malloc(strlen(str) + 1); + if (copy != NULL) { + strcpy(copy, str); + } + return copy; } -static void put_file(struct mg_connection *conn, const char *path) { - struct file file = STRUCT_FILE_INITIALIZER; - const char *range; - int64_t r1, r2; - int rc; - - conn->status_code = mg_stat(conn, path, &file) ? 200 : 201; - - if ((rc = put_dir(conn, path)) == 0) { - mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->status_code); - } else if (rc == -1) { - send_http_error(conn, 500, http_500_error, - "put_dir(%s): %s", path, strerror(ERRNO)); - } else if (!mg_fopen(conn, path, "wb+", &file) || file.fp == NULL) { - mg_fclose(&file); - send_http_error(conn, 500, http_500_error, - "fopen(%s): %s", path, strerror(ERRNO)); - } else { - fclose_on_exec(&file); - range = mg_get_header(conn, "Content-Range"); - r1 = r2 = 0; - if (range != NULL && parse_range_header(range, &r1, &r2) > 0) { - conn->status_code = 206; - fseeko(file.fp, r1, SEEK_SET); - } - if (forward_body_data(conn, file.fp, INVALID_SOCKET, NULL)) { - mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->status_code); - } - mg_fclose(&file); - } +static int isbyte(int n) { + return n >= 0 && n <= 255; } -static void send_ssi_file(struct mg_connection *, const char *, - struct file *, int); +static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) { + int n, a, b, c, d, slash = 32, len = 0; -static void do_ssi_include(struct mg_connection *conn, const char *ssi, - char *tag, int include_level) { - char file_name[MG_BUF_LEN], path[PATH_MAX], *p; - struct file file = STRUCT_FILE_INITIALIZER; + if ((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5 || + sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4) && + isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) && + slash >= 0 && slash < 33) { + len = n; + *net = ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | d; + *mask = slash ? 0xffffffffU << (32 - slash) : 0; + } - // sscanf() is safe here, since send_ssi_file() also uses buffer - // of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN. - if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) { - // File name is relative to the webserver root - (void) mg_snprintf(conn, path, sizeof(path), "%s%c%s", - conn->ctx->config[DOCUMENT_ROOT], '/', file_name); - } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1) { - // File name is relative to the webserver working directory - // or it is absolute system path - (void) mg_snprintf(conn, path, sizeof(path), "%s", file_name); - } else if (sscanf(tag, " \"%[^\"]\"", file_name) == 1) { - // File name is relative to the currect document - (void) mg_snprintf(conn, path, sizeof(path), "%s", ssi); - if ((p = strrchr(path, '/')) != NULL) { - p[1] = '\0'; - } - (void) mg_snprintf(conn, path + strlen(path), - sizeof(path) - strlen(path), "%s", file_name); - } else { - cry(conn, "Bad SSI #include: [%s]", tag); - return; - } - - if (!mg_fopen(conn, path, "rb", &file)) { - cry(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s", - tag, path, strerror(ERRNO)); - } else { - fclose_on_exec(&file); - if (match_prefix(conn->ctx->config[SSI_EXTENSIONS], - strlen(conn->ctx->config[SSI_EXTENSIONS]), path) > 0) { - send_ssi_file(conn, path, &file, include_level + 1); - } else { - send_file_data(conn, &file, 0, INT64_MAX); - } - mg_fclose(&file); - } + return len; } -#if !defined(NO_POPEN) -static void do_ssi_exec(struct mg_connection *conn, char *tag) { - char cmd[MG_BUF_LEN]; - struct file file = STRUCT_FILE_INITIALIZER; +// Verify given socket address against the ACL. +// Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed. +static int check_acl(const char *acl, uint32_t remote_ip) { + int allowed, flag; + uint32_t net, mask; + struct vec vec; - if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) { - cry(conn, "Bad SSI #exec: [%s]", tag); - } else if ((file.fp = popen(cmd, "r")) == NULL) { - cry(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(ERRNO)); - } else { - send_file_data(conn, &file, 0, INT64_MAX); - pclose(file.fp); - } -} -#endif // !NO_POPEN + // If any ACL is set, deny by default + allowed = acl == NULL ? '+' : '-'; -static int mg_fgetc(struct file *filep, int offset) { - if (filep->membuf != NULL && offset >=0 && offset < filep->size) { - return ((unsigned char *) filep->membuf)[offset]; - } else if (filep->fp != NULL) { - return fgetc(filep->fp); - } else { - return EOF; - } + while ((acl = next_option(acl, &vec, NULL)) != NULL) { + flag = vec.ptr[0]; + if ((flag != '+' && flag != '-') || + parse_net(&vec.ptr[1], &net, &mask) == 0) { + return -1; + } + + if (net == (remote_ip & mask)) { + allowed = flag; + } + } + + return allowed == '+'; } -static void send_ssi_file(struct mg_connection *conn, const char *path, - struct file *filep, int include_level) { - char buf[MG_BUF_LEN]; - int ch, offset, len, in_ssi_tag; +// Protect against directory disclosure attack by removing '..', +// excessive '/' and '\' characters +static void remove_double_dots_and_double_slashes(char *s) { + char *p = s; - if (include_level > 10) { - cry(conn, "SSI #include level is too deep (%s)", path); - return; - } - - in_ssi_tag = len = offset = 0; - while ((ch = mg_fgetc(filep, offset)) != EOF) { - if (in_ssi_tag && ch == '>') { - in_ssi_tag = 0; - buf[len++] = (char) ch; - buf[len] = '\0'; - assert(len <= (int) sizeof(buf)); - if (len < 6 || memcmp(buf, "|<--data_len-->| - // |<-conn->request_len->|<-----body_len----------->| - // |<-------------------conn->data_len------------->| + if (buffered) { + conn->mg_conn.content_len = data_len; + conn->mg_conn.content = (char *) buf + header_len; + conn->mg_conn.wsbits = buf[0]; - if (header_len > 0) { - // Allocate space to hold websocket payload - data = mem; - if (data_len > sizeof(mem) && (data = (char*)malloc(data_len)) == NULL) { - // Allocation failed, exit the loop and then close the connection - // TODO: notify user about the failure - break; - } + // Apply mask if necessary + if (mask_len > 0) { + for (i = 0; i < data_len; i++) { + buf[i + header_len] ^= (buf + header_len - mask_len)[i % 4]; + } + } - // Save mask and bits, otherwise it may be clobbered by memmove below - bits = buf[0]; - memcpy(mask, buf + header_len - mask_len, mask_len); + // Call the handler and remove frame from the iobuf + if (call_user(conn, MG_REQUEST) == MG_FALSE) { + conn->ns_conn->flags |= NSF_FINISHED_SENDING_DATA; + } + iobuf_remove(&conn->ns_conn->recv_iobuf, frame_len); + } - // Read frame payload into the allocated buffer. - assert(body_len >= header_len); - if (data_len + header_len > body_len) { - len = body_len - header_len; - memcpy(data, buf + header_len, len); - // TODO: handle pull error - pull_all(NULL, conn, data + len, data_len - len); - conn->data_len = conn->request_len; - } else { - len = data_len + header_len; - memcpy(data, buf + header_len, data_len); - memmove(buf, buf + len, body_len - len); - conn->data_len -= len; - } - - // Apply mask if necessary - if (mask_len > 0) { - for (i = 0; i < data_len; i++) { - data[i] ^= mask[i % 4]; - } - } - - // Exit the loop if callback signalled to exit, - // or "connection close" opcode received. - if ((conn->ctx->callbacks.websocket_data != NULL && - !conn->ctx->callbacks.websocket_data(conn, bits, data, data_len)) || - (bits & 0xf) == 8) { // Opcode == 8, connection close - stop = 1; - } - - if (data != mem) { - free(data); - } - // Not breaking the loop, process next websocket frame. - } else { - // Buffering websocket request - if ((n = pull(NULL, conn, conn->buf + conn->data_len, - conn->buf_size - conn->data_len)) <= 0) { - break; - } - conn->data_len += n; - } - } + return buffered; } int mg_websocket_write(struct mg_connection* conn, int opcode, - const char *data, size_t data_len) { - unsigned char *copy; - size_t copy_len = 0; - int retval = -1; + const char *data, size_t data_len) { + 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 -1; + } - copy[0] = 0x80 + (opcode & 0x0f); + copy[0] = 0x80 + (opcode & 0x0f); - // Frame format: http://tools.ietf.org/html/rfc6455#section-5.2 - if (data_len < 126) { - // Inline 7-bit length field - copy[1] = data_len; - memcpy(copy + 2, data, data_len); - copy_len = 2 + data_len; - } else if (data_len <= 0xFFFF) { - // 16-bit length field - copy[1] = 126; - * (uint16_t *) (copy + 2) = htons(data_len); - memcpy(copy + 4, data, data_len); - copy_len = 4 + data_len; - } else { - // 64-bit length field - copy[1] = 127; - * (uint32_t *) (copy + 2) = htonl((uint64_t) data_len >> 32); - * (uint32_t *) (copy + 6) = htonl(data_len & 0xffffffff); - memcpy(copy + 10, data, data_len); - copy_len = 10 + data_len; - } + // Frame format: http://tools.ietf.org/html/rfc6455#section-5.2 + if (data_len < 126) { + // Inline 7-bit length field + copy[1] = data_len; + memcpy(copy + 2, data, data_len); + copy_len = 2 + data_len; + } else if (data_len <= 0xFFFF) { + // 16-bit length field + copy[1] = 126; + * (uint16_t *) (copy + 2) = (uint16_t) htons((uint16_t) data_len); + memcpy(copy + 4, data, data_len); + copy_len = 4 + data_len; + } else { + // 64-bit length field + copy[1] = 127; + * (uint32_t *) (copy + 2) = (uint32_t) + htonl((uint32_t) ((uint64_t) data_len >> 32)); + * (uint32_t *) (copy + 6) = (uint32_t) htonl(data_len & 0xffffffff); + memcpy(copy + 10, data, data_len); + copy_len = 10 + data_len; + } - // Not thread safe - if (copy_len > 0) { - retval = mg_write(conn, copy, copy_len); - } - free(copy); + if (copy_len > 0) { + retval = mg_write(conn, copy, copy_len); + } + if (copy != mem) { + free(copy); + } - return retval; + return retval; } -static void handle_websocket_request(struct mg_connection *conn) { - const char *version = mg_get_header(conn, "Sec-WebSocket-Version"); - if (version == NULL || strcmp(version, "13") != 0) { - send_http_error(conn, 426, "Upgrade Required", "%s", "Upgrade Required"); - } else if (conn->ctx->callbacks.websocket_connect != NULL && - conn->ctx->callbacks.websocket_connect(conn) != 0) { - // Callback has returned non-zero, do not proceed with handshake - } else { - send_websocket_handshake(conn); - if (conn->ctx->callbacks.websocket_ready != NULL) { - conn->ctx->callbacks.websocket_ready(conn); - } - read_websocket(conn); - } +int 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 len; } -static int is_websocket_request(const struct mg_connection *conn) { - const char *host, *upgrade, *connection, *version, *key; - - host = mg_get_header(conn, "Host"); - upgrade = mg_get_header(conn, "Upgrade"); - connection = mg_get_header(conn, "Connection"); - key = mg_get_header(conn, "Sec-WebSocket-Key"); - version = mg_get_header(conn, "Sec-WebSocket-Version"); - - return host != NULL && upgrade != NULL && connection != NULL && - key != NULL && version != NULL && - mg_strcasestr(upgrade, "websocket") != NULL && - mg_strcasestr(connection, "Upgrade") != NULL; -} -#endif // !USE_WEBSOCKET - -static int isbyte(int n) { - return n >= 0 && n <= 255; +static void send_websocket_handshake_if_requested(struct mg_connection *conn) { + const char *ver = mg_get_header(conn, "Sec-WebSocket-Version"), + *key = mg_get_header(conn, "Sec-WebSocket-Key"); + if (ver != NULL && key != NULL) { + conn->is_websocket = 1; + if (call_user(MG_CONN_2_CONN(conn), MG_WS_HANDSHAKE) == MG_FALSE) { + send_websocket_handshake(conn, key); + } + } } -static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) { - int n, a, b, c, d, slash = 32, len = 0; - - if ((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5 || - sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4) && - isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) && - slash >= 0 && slash < 33) { - len = n; - *net = ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | d; - *mask = slash ? 0xffffffffU << (32 - slash) : 0; - } - - return len; +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, WEBSOCKET_OPCODE_PING, "", 0); + } } - -static int set_throttle(const char *spec, uint32_t remote_ip, const char *uri) { - int throttle = 0; - struct vec vec, val; - uint32_t net, mask; - char mult; - double v; - - while ((spec = next_option(spec, &vec, &val)) != NULL) { - mult = ','; - if (sscanf(val.ptr, "%lf%c", &v, &mult) < 1 || v < 0 || - (lowercase(&mult) != 'k' && lowercase(&mult) != 'm' && mult != ',')) { - continue; - } - v *= lowercase(&mult) == 'k' ? 1024 : lowercase(&mult) == 'm' ? 1048576 : 1; - if (vec.len == 1 && vec.ptr[0] == '*') { - throttle = (int) v; - } else if (parse_net(vec.ptr, &net, &mask) > 0) { - if ((remote_ip & mask) == net) { - throttle = (int) v; - } - } else if (match_prefix(vec.ptr, vec.len, uri) > 0) { - throttle = (int) v; - } - } - - return throttle; -} - -static uint32_t get_remote_ip(const struct mg_connection *conn) { - return ntohl(* (uint32_t *) &conn->client.rsa.sin.sin_addr); -} - -#ifdef USE_LUA -#include "mod_lua.c" -#endif // USE_LUA - -int mg_upload(struct mg_connection *conn, const char *destination_dir) { - const char *content_type_header, *boundary_start; - char buf[MG_BUF_LEN], path[PATH_MAX], fname[1024], boundary[100], *s; - FILE *fp; - int bl, n, i, j, headers_len, boundary_len, eof, - len = 0, num_uploaded_files = 0; - - // Request looks like this: - // - // POST /upload HTTP/1.1 - // Host: 127.0.0.1:8080 - // Content-Length: 244894 - // Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRVr - // - // ------WebKitFormBoundaryRVr - // Content-Disposition: form-data; name="file"; filename="accum.png" - // Content-Type: image/png - // - // <89>PNG - // - // ------WebKitFormBoundaryRVr - - // Extract boundary string from the Content-Type header - if ((content_type_header = mg_get_header(conn, "Content-Type")) == NULL || - (boundary_start = mg_strcasestr(content_type_header, - "boundary=")) == NULL || - (sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 && - sscanf(boundary_start, "boundary=%99s", boundary) == 0) || - boundary[0] == '\0') { - return num_uploaded_files; - } - - boundary_len = strlen(boundary); - bl = boundary_len + 4; // \r\n-- - for (;;) { - // Pull in headers - assert(len >= 0 && len <= (int) sizeof(buf)); - while ((n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0) { - len += n; - } - if ((headers_len = get_request_len(buf, len)) <= 0) { - break; - } - - // Fetch file name. - fname[0] = '\0'; - for (i = j = 0; i < headers_len; i++) { - if (buf[i] == '\r' && buf[i + 1] == '\n') { - buf[i] = buf[i + 1] = '\0'; - // TODO(lsm): don't expect filename to be the 3rd field, - // parse the header properly instead. - sscanf(&buf[j], "Content-Disposition: %*s %*s filename=\"%1023[^\"]", - fname); - j = i + 2; - } - } - - // Give up if the headers are not what we expect - if (fname[0] == '\0') { - break; - } - - // Move data to the beginning of the buffer - assert(len >= headers_len); - memmove(buf, &buf[headers_len], len - headers_len); - len -= headers_len; - - // We open the file with exclusive lock held. This guarantee us - // there is no other thread can save into the same file simultaneously. - fp = NULL; - // Construct destination file name. Do not allow paths to have slashes. - if ((s = strrchr(fname, '/')) == NULL && - (s = strrchr(fname, '\\')) == NULL) { - s = fname; - } - - // Open file in binary mode. TODO: set an exclusive lock. - snprintf(path, sizeof(path), "%s/%s", destination_dir, s); - if ((fp = fopen(path, "wb")) == NULL) { - break; - } - - // Read POST data, write into file until boundary is found. - eof = n = 0; - do { - len += n; - for (i = 0; i < len - bl; i++) { - if (!memcmp(&buf[i], "\r\n--", 4) && - !memcmp(&buf[i + 4], boundary, boundary_len)) { - // Found boundary, that's the end of file data. - fwrite(buf, 1, i, fp); - eof = 1; - memmove(buf, &buf[i + bl], len - (i + bl)); - len -= i + bl; - break; - } - } - if (!eof && len > bl) { - fwrite(buf, 1, len - bl, fp); - memmove(buf, &buf[len - bl], bl); - len = bl; - } - } while (!eof && (n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0); - fclose(fp); - if (eof) { - num_uploaded_files++; - if (conn->ctx->callbacks.upload != NULL) { - conn->ctx->callbacks.upload(conn, path); - } - } - } - - return num_uploaded_files; -} - -static int is_put_or_delete_request(const struct mg_connection *conn) { - const char *s = conn->request_info.request_method; - return s != NULL && (!strcmp(s, "PUT") || !strcmp(s, "DELETE") || !strcmp(s, "MKCOL")); -} - -static int get_first_ssl_listener_index(const struct mg_context *ctx) { - int i, index = -1; - for (i = 0; index == -1 && i < ctx->num_listening_sockets; i++) { - index = ctx->listening_sockets[i].is_ssl ? i : -1; - } - return index; -} - -static void redirect_to_https_port(struct mg_connection *conn, int ssl_index) { - char host[1025]; - const char *host_header; - - if ((host_header = mg_get_header(conn, "Host")) == NULL || - sscanf(host_header, "%1024[^:]", host) == 0) { - // Cannot get host from the Host: header. Fallback to our IP address. - sockaddr_to_string(host, sizeof(host), &conn->client.lsa); - } - - mg_printf(conn, "HTTP/1.1 302 Found\r\nLocation: https://%s:%d%s\r\n\r\n", - host, (int) ntohs(conn->ctx->listening_sockets[ssl_index]. - lsa.sin.sin_port), conn->request_info.uri); -} - -// This is the heart of the Mongoose's logic. -// This function is called when the request is read, parsed and validated, -// and Mongoose must decide what action to take: serve a file, or -// a directory, or call embedded function, etcetera. -static void handle_request(struct mg_connection *conn) { - struct mg_request_info *ri = &conn->request_info; - char path[PATH_MAX]; - int uri_len, ssl_index; - struct file file = STRUCT_FILE_INITIALIZER; - - if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) { - * ((char *) conn->request_info.query_string++) = '\0'; - } - uri_len = (int) strlen(ri->uri); - mg_url_decode(ri->uri, uri_len, (char *) ri->uri, uri_len + 1, 0); - remove_double_dots_and_double_slashes((char *) ri->uri); - convert_uri_to_file_name(conn, path, sizeof(path), &file); - conn->throttle = set_throttle(conn->ctx->config[THROTTLE], - get_remote_ip(conn), ri->uri); - - DEBUG_TRACE(("%s", ri->uri)); - // Perform redirect and auth checks before calling begin_request() handler. - // Otherwise, begin_request() would need to perform auth checks and redirects. - if (!conn->client.is_ssl && conn->client.ssl_redir && - (ssl_index = get_first_ssl_listener_index(conn->ctx)) > -1) { - redirect_to_https_port(conn, ssl_index); - } else if (!is_put_or_delete_request(conn) && - !check_authorization(conn, path)) { - send_authorization_request(conn); - } else if (conn->ctx->callbacks.begin_request != NULL && - conn->ctx->callbacks.begin_request(conn)) { - // Do nothing, callback has served the request -#if defined(USE_WEBSOCKET) - } else if (is_websocket_request(conn)) { - handle_websocket_request(conn); -#endif - } else if (!strcmp(ri->request_method, "OPTIONS")) { - send_options(conn); - } else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) { - send_http_error(conn, 404, "Not Found", "Not Found"); - } else if (is_put_or_delete_request(conn) && - (is_authorized_for_put(conn) != 1)) { - send_authorization_request(conn); - } else if (!strcmp(ri->request_method, "PUT")) { - put_file(conn, path); - } else if (!strcmp(ri->request_method, "MKCOL")) { - mkcol(conn, path); - } else if (!strcmp(ri->request_method, "DELETE")) { - struct de de; - memset(&de.file, 0, sizeof(de.file)); - if(!mg_stat(conn, path, &de.file)) { - send_http_error(conn, 404, "Not Found", "%s", "File not found"); - } else { - if(de.file.modification_time) { - if(de.file.is_directory) { - remove_directory(conn, path); - send_http_error(conn, 204, "No Content", "%s", ""); - } else if (mg_remove(path) == 0) { - send_http_error(conn, 204, "No Content", "%s", ""); - } else { - send_http_error(conn, 423, "Locked", "remove(%s): %s", path, - strerror(ERRNO)); - } - } - else { - send_http_error(conn, 500, http_500_error, "remove(%s): %s", path, - strerror(ERRNO)); - } - } - } else if ((file.membuf == NULL && file.modification_time == (time_t) 0) || - must_hide_file(conn, path)) { - send_http_error(conn, 404, "Not Found", "%s", "File not found"); - } else if (file.is_directory && ri->uri[uri_len - 1] != '/') { - mg_printf(conn, "HTTP/1.1 301 Moved Permanently\r\n" - "Location: %s/\r\n\r\n", ri->uri); - } else if (!strcmp(ri->request_method, "PROPFIND")) { - handle_propfind(conn, path, &file); - } else if (file.is_directory && - !substitute_index_file(conn, path, sizeof(path), &file)) { - if (!mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes")) { - handle_directory_request(conn, path); - } else { - send_http_error(conn, 403, "Directory Listing Denied", - "Directory listing denied"); - } -#ifdef USE_LUA - } else if (match_prefix("**.lp$", 6, path) > 0) { - handle_lsp_request(conn, path, &file, NULL); -#endif -#if !defined(NO_CGI) - } else if (match_prefix(conn->ctx->config[CGI_EXTENSIONS], - strlen(conn->ctx->config[CGI_EXTENSIONS]), - path) > 0) { - if (strcmp(ri->request_method, "POST") && - strcmp(ri->request_method, "HEAD") && - strcmp(ri->request_method, "GET")) { - send_http_error(conn, 501, "Not Implemented", - "Method %s is not implemented", ri->request_method); - } else { - handle_cgi_request(conn, path); - } -#endif // !NO_CGI - } else if (match_prefix(conn->ctx->config[SSI_EXTENSIONS], - strlen(conn->ctx->config[SSI_EXTENSIONS]), - path) > 0) { - handle_ssi_file_request(conn, path); - } else if (is_not_modified(conn, &file)) { - send_http_error(conn, 304, "Not Modified", "%s", ""); - } else { - handle_file_request(conn, path, &file); - } -} - -static void close_all_listening_sockets(struct mg_context *ctx) { - int i; - for (i = 0; i < ctx->num_listening_sockets; i++) { - closesocket(ctx->listening_sockets[i].sock); - } - free(ctx->listening_sockets); -} - -// Valid listening port specification is: [ip_address:]port[s] -// Examples: 80, 443s, 127.0.0.1:3128, 1.2.3.4:8080s -// TODO(lsm): add parsing of the IPv6 address -static int parse_port_string(const struct vec *vec, struct socket *so) { - int a, b, c, d, port, len; - - // MacOS needs that. If we do not zero it, subsequent bind() will fail. - // Also, all-zeroes in the socket address means binding to all addresses - // for both IPv4 and IPv6 (INADDR_ANY and IN6ADDR_ANY_INIT). - memset(so, 0, sizeof(*so)); - - if (sscanf(vec->ptr, "%d.%d.%d.%d:%d%n", &a, &b, &c, &d, &port, &len) == 5) { - // Bind to a specific IPv4 address - so->lsa.sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d); - } else if (sscanf(vec->ptr, "%d%n", &port, &len) != 1 || - len <= 0 || - len > (int) vec->len || - port < 1 || - port > 65535 || - (vec->ptr[len] && vec->ptr[len] != 's' && - vec->ptr[len] != 'r' && vec->ptr[len] != ',')) { - return 0; - } - - so->is_ssl = vec->ptr[len] == 's'; - so->ssl_redir = vec->ptr[len] == 'r'; -#if defined(USE_IPV6) - so->lsa.sin6.sin6_family = AF_INET6; - so->lsa.sin6.sin6_port = htons((uint16_t) port); #else - so->lsa.sin.sin_family = AF_INET; - so->lsa.sin.sin_port = htons((uint16_t) port); +#define ping_idle_websocket_connection(conn, t) +#endif // !MONGOOSE_NO_WEBSOCKET + +static void write_terminating_chunk(struct connection *conn) { + mg_write(&conn->mg_conn, "0\r\n\r\n", 5); +} + +static int call_request_handler(struct connection *conn) { + int result; + conn->mg_conn.content = conn->ns_conn->recv_iobuf.buf; + if ((result = call_user(conn, MG_REQUEST)) == MG_TRUE) { + if (conn->ns_conn->flags & MG_HEADERS_SENT) { + write_terminating_chunk(conn); + } + close_local_endpoint(conn); + } + return result; +} + +const char *mg_get_mime_type(const char *path, const char *default_mime_type) { + const char *ext; + size_t i, path_len; + + path_len = strlen(path); + + for (i = 0; static_builtin_mime_types[i].extension != NULL; i++) { + ext = path + (path_len - static_builtin_mime_types[i].ext_len); + if (path_len > static_builtin_mime_types[i].ext_len && + mg_strcasecmp(ext, static_builtin_mime_types[i].extension) == 0) { + return static_builtin_mime_types[i].mime_type; + } + } + + return default_mime_type; +} + +#ifndef MONGOOSE_NO_FILESYSTEM +// Convert month to the month number. Return -1 on error, or month number +static int get_month_index(const char *s) { + static const char *month_names[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + int i; + + for (i = 0; i < (int) ARRAY_SIZE(month_names); i++) + if (!strcmp(s, month_names[i])) + return i; + + return -1; +} + +static int num_leap_years(int year) { + return year / 4 - year / 100 + year / 400; +} + +// Parse UTC date-time string, and return the corresponding time_t value. +static time_t parse_date_string(const char *datetime) { + static const unsigned short days_before_month[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 + }; + char month_str[32]; + int second, minute, hour, day, month, year, leap_days, days; + time_t result = (time_t) 0; + + if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d", + &day, month_str, &year, &hour, &minute, &second) == 6) || + (sscanf(datetime, "%d %3s %d %d:%d:%d", + &day, month_str, &year, &hour, &minute, &second) == 6) || + (sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d", + &day, month_str, &year, &hour, &minute, &second) == 6) || + (sscanf(datetime, "%d-%3s-%d %d:%d:%d", + &day, month_str, &year, &hour, &minute, &second) == 6)) && + year > 1970 && + (month = get_month_index(month_str)) != -1) { + leap_days = num_leap_years(year) - num_leap_years(1970); + year -= 1970; + days = year * 365 + days_before_month[month] + (day - 1) + leap_days; + result = days * 24 * 3600 + hour * 3600 + minute * 60 + second; + } + + return result; +} + +// Look at the "path" extension and figure what mime type it has. +// Store mime type in the vector. +static void get_mime_type(const struct mg_server *server, const char *path, + struct vec *vec) { + struct vec ext_vec, mime_vec; + const char *list, *ext; + size_t path_len; + + path_len = strlen(path); + + // Scan user-defined mime types first, in case user wants to + // override default mime types. + list = server->config_options[EXTRA_MIME_TYPES]; + while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) { + // ext now points to the path suffix + ext = path + path_len - ext_vec.len; + if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) { + *vec = mime_vec; + return; + } + } + + vec->ptr = mg_get_mime_type(path, "text/plain"); + vec->len = strlen(vec->ptr); +} + +static const char *suggest_connection_header(const struct mg_connection *conn) { + return should_keep_alive(conn) ? "keep-alive" : "close"; +} + +static void construct_etag(char *buf, size_t buf_len, const file_stat_t *st) { + mg_snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"", + (unsigned long) st->st_mtime, (int64_t) st->st_size); +} + +// Return True if we should reply 304 Not Modified. +static int is_not_modified(const struct connection *conn, + const file_stat_t *stp) { + char etag[64]; + const char *ims = mg_get_header(&conn->mg_conn, "If-Modified-Since"); + const char *inm = mg_get_header(&conn->mg_conn, "If-None-Match"); + construct_etag(etag, sizeof(etag), stp); + return (inm != NULL && !mg_strcasecmp(etag, inm)) || + (ims != NULL && stp->st_mtime <= parse_date_string(ims)); +} + +// For given directory path, substitute it to valid index file. +// Return 0 if index file has been found, -1 if not found. +// If the file is found, it's stats is returned in stp. +static int find_index_file(struct connection *conn, char *path, + size_t path_len, file_stat_t *stp) { + const char *list = conn->server->config_options[INDEX_FILES]; + file_stat_t st; + struct vec filename_vec; + size_t n = strlen(path), found = 0; + + // The 'path' given to us points to the directory. Remove all trailing + // directory separator characters from the end of the path, and + // then append single directory separator character. + while (n > 0 && path[n - 1] == '/') { + n--; + } + path[n] = '/'; + + // Traverse index files list. For each entry, append it to the given + // path and see if the file exists. If it exists, break the loop + while ((list = next_option(list, &filename_vec, NULL)) != NULL) { + + // Ignore too long entries that may overflow path buffer + if (filename_vec.len > (int) (path_len - (n + 2))) + continue; + + // Prepare full path to the index file + strncpy(path + n + 1, filename_vec.ptr, filename_vec.len); + path[n + 1 + filename_vec.len] = '\0'; + + //DBG(("[%s]", path)); + + // Does it exist? + if (!stat(path, &st)) { + // Yes it does, break the loop + *stp = st; + found = 1; + break; + } + } + + // If no index file exists, restore directory path + if (!found) { + path[n] = '\0'; + } + + return found; +} + +static int parse_range_header(const char *header, int64_t *a, int64_t *b) { + return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b); +} + +static void gmt_time_string(char *buf, size_t buf_len, time_t *t) { + strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(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]; + const char *msg = "OK", *hdr; + time_t curtime = time(NULL); + int64_t r1, r2; + struct vec mime_vec; + int n; + + conn->endpoint_type = EP_FILE; + ns_set_close_on_exec(conn->endpoint.fd); + conn->mg_conn.status_code = 200; + + get_mime_type(conn->server, path, &mime_vec); + conn->cl = st->st_size; + range[0] = '\0'; + + // If Range: header specified, act accordingly + r1 = r2 = 0; + hdr = mg_get_header(&conn->mg_conn, "Range"); + if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 && + r1 >= 0 && r2 >= 0) { + conn->mg_conn.status_code = 206; + conn->cl = n == 2 ? (r2 > conn->cl ? conn->cl : r2) - r1 + 1: conn->cl - r1; + mg_snprintf(range, sizeof(range), "Content-Range: bytes " + "%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n", + r1, r1 + conn->cl - 1, (int64_t) st->st_size); + msg = "Partial Content"; + lseek(conn->endpoint.fd, r1, SEEK_SET); + } + + // Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 + gmt_time_string(date, sizeof(date), &curtime); + gmt_time_string(lm, sizeof(lm), &st->st_mtime); + construct_etag(etag, sizeof(etag), st); + + n = mg_snprintf(headers, sizeof(headers), + "HTTP/1.1 %d %s\r\n" + "Date: %s\r\n" + "Last-Modified: %s\r\n" + "Etag: %s\r\n" + "Content-Type: %.*s\r\n" + "Content-Length: %" INT64_FMT "\r\n" + "Connection: %s\r\n" + "Accept-Ranges: bytes\r\n" + "%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); + ns_send(conn->ns_conn, headers, n); + + if (!strcmp(conn->mg_conn.request_method, "HEAD")) { + conn->ns_conn->flags |= NSF_FINISHED_SENDING_DATA; + close(conn->endpoint.fd); + conn->endpoint_type = EP_NONE; + } +} +#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 - - return 1; + if ((size_t) loc->len >= c->content_len && + call_request_handler(conn) == MG_FALSE) { + open_local_endpoint(conn, 1); + } } -static int set_ports_option(struct mg_context *ctx) { - const char *list = ctx->config[LISTENING_PORTS]; - int on = 1, success = 1; -#if defined(USE_IPV6) - int off = 0; -#endif - struct vec vec; - struct socket so, *ptr; +#if !defined(MONGOOSE_NO_DIRECTORY_LISTING) || !defined(MONGOOSE_NO_DAV) - while (success && (list = next_option(list, &vec, NULL)) != NULL) { - if (!parse_port_string(&vec, &so)) { - cry(fc(ctx), "%s: %.*s: invalid port spec. Expecting list of: %s", - __func__, (int) vec.len, vec.ptr, "[IP_ADDRESS:]PORT[s|p]"); - success = 0; - } else if (so.is_ssl && ctx->ssl_ctx == NULL) { - cry(fc(ctx), "Cannot add SSL socket, is -ssl_certificate option set?"); - success = 0; - } else if ((so.sock = socket(so.lsa.sa.sa_family, SOCK_STREAM, 6)) == - INVALID_SOCKET || - // On Windows, SO_REUSEADDR is recommended only for - // broadcast UDP sockets - setsockopt(so.sock, SOL_SOCKET, SO_REUSEADDR, - (SETSOCKOPT_CAST) &on, sizeof(on)) != 0 || -#if defined(USE_IPV6) - setsockopt(so.sock, IPPROTO_IPV6, IPV6_V6ONLY, (SETSOCKOPT_CAST) &off, - sizeof(off)) != 0 || -#endif - bind(so.sock, &so.lsa.sa, sizeof(so.lsa)) != 0 || - listen(so.sock, SOMAXCONN) != 0) { - cry(fc(ctx), "%s: cannot bind to %.*s: %s", __func__, - (int) vec.len, vec.ptr, strerror(ERRNO)); - closesocket(so.sock); - success = 0; - } else if ((ptr = (struct socket*)realloc(ctx->listening_sockets, - (ctx->num_listening_sockets + 1) * - sizeof(ctx->listening_sockets[0]))) == NULL) { - closesocket(so.sock); - success = 0; - } else { - set_close_on_exec(so.sock); - ctx->listening_sockets = ptr; - ctx->listening_sockets[ctx->num_listening_sockets] = so; - ctx->num_listening_sockets++; - } - } - - if (!success) { - close_all_listening_sockets(ctx); - } - - return success; -} - -static void log_header(const struct mg_connection *conn, const char *header, - FILE *fp) { - const char *header_value; - - if ((header_value = mg_get_header(conn, header)) == NULL) { - (void) fprintf(fp, "%s", " -"); - } else { - (void) fprintf(fp, " \"%s\"", header_value); - } -} - -static void log_access(const struct mg_connection *conn) { - const struct mg_request_info *ri; - FILE *fp; - char date[64], src_addr[IP_ADDR_STR_LEN]; - - fp = conn->ctx->config[ACCESS_LOG_FILE] == NULL ? NULL : - fopen(conn->ctx->config[ACCESS_LOG_FILE], "a+"); - - if (fp == NULL) - return; - - strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z", - localtime(&conn->birth_time)); - - ri = &conn->request_info; - flockfile(fp); - - sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa); - fprintf(fp, "%s - %s [%s] \"%s %s HTTP/%s\" %d %" INT64_FMT, - src_addr, ri->remote_user == NULL ? "-" : ri->remote_user, date, - ri->request_method ? ri->request_method : "-", - ri->uri ? ri->uri : "-", ri->http_version, - conn->status_code, conn->num_bytes_sent); - log_header(conn, "Referer", fp); - log_header(conn, "User-Agent", fp); - fputc('\n', fp); - fflush(fp); - - funlockfile(fp); - fclose(fp); -} - -// Verify given socket address against the ACL. -// Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed. -static int check_acl(struct mg_context *ctx, uint32_t remote_ip) { - int allowed, flag; - uint32_t net, mask; - struct vec vec; - const char *list = ctx->config[ACCESS_CONTROL_LIST]; - - // If any ACL is set, deny by default - allowed = list == NULL ? '+' : '-'; - - while ((list = next_option(list, &vec, NULL)) != NULL) { - flag = vec.ptr[0]; - if ((flag != '+' && flag != '-') || - parse_net(&vec.ptr[1], &net, &mask) == 0) { - cry(fc(ctx), "%s: subnet must be [+|-]x.x.x.x[/x]", __func__); - return -1; - } - - if (net == (remote_ip & mask)) { - allowed = flag; - } - } - - return allowed == '+'; -} - -#if !defined(_WIN32) -static int set_uid_option(struct mg_context *ctx) { - struct passwd *pw; - const char *uid = ctx->config[RUN_AS_USER]; - int success = 0; - - if (uid == NULL) { - success = 1; - } else { - if ((pw = getpwnam(uid)) == NULL) { - cry(fc(ctx), "%s: unknown user [%s]", __func__, uid); - } else if (setgid(pw->pw_gid) == -1) { - cry(fc(ctx), "%s: setgid(%s): %s", __func__, uid, strerror(errno)); - } else if (setuid(pw->pw_uid) == -1) { - cry(fc(ctx), "%s: setuid(%s): %s", __func__, uid, strerror(errno)); - } else { - success = 1; - } - } - - return success; -} -#endif // !_WIN32 - -#if !defined(NO_SSL) -static pthread_mutex_t *ssl_mutexes; - -static int sslize(struct mg_connection *conn, SSL_CTX *s, int (*func)(SSL *)) { - return (conn->ssl = SSL_new(s)) != NULL && - SSL_set_fd(conn->ssl, conn->client.sock) == 1 && - func(conn->ssl) == 1; -} - -// Return OpenSSL error message -static const char *ssl_error(void) { - unsigned long err; - err = ERR_get_error(); - return err == 0 ? "" : ERR_error_string(err, NULL); -} - -static void ssl_locking_callback(int mode, int mutex_num, const char *file, - int line) { - (void) line; - (void) file; - - if (mode & 1) { // 1 is CRYPTO_LOCK - (void) pthread_mutex_lock(&ssl_mutexes[mutex_num]); - } else { - (void) pthread_mutex_unlock(&ssl_mutexes[mutex_num]); - } -} - -static unsigned long ssl_id_callback(void) { - return (unsigned long) pthread_self(); -} - -#if !defined(NO_SSL_DL) -static int load_dll(struct mg_context *ctx, const char *dll_name, - struct ssl_func *sw) { - union {void *p; void (*fp)(void);} u; - void *dll_handle; - struct ssl_func *fp; - - if ((dll_handle = dlopen(dll_name, RTLD_LAZY)) == NULL) { - cry(fc(ctx), "%s: cannot load %s", __func__, dll_name); - return 0; - } - - for (fp = sw; fp->name != NULL; fp++) { #ifdef _WIN32 - // GetProcAddress() returns pointer to function - u.fp = (void (*)(void)) dlsym(dll_handle, fp->name); +struct dirent { + char d_name[MAX_PATH_SIZE]; +}; + +typedef struct DIR { + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; +} DIR; + +// Implementation of POSIX opendir/closedir/readdir for Windows. +static DIR *opendir(const char *name) { + DIR *dir = NULL; + wchar_t wpath[MAX_PATH_SIZE]; + DWORD attrs; + + if (name == NULL) { + SetLastError(ERROR_BAD_ARGUMENTS); + } else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } else { + to_wchar(name, wpath, ARRAY_SIZE(wpath)); + attrs = GetFileAttributesW(wpath); + if (attrs != 0xFFFFFFFF && + ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) { + (void) wcscat(wpath, L"\\*"); + dir->handle = FindFirstFileW(wpath, &dir->info); + dir->result.d_name[0] = '\0'; + } else { + free(dir); + dir = NULL; + } + } + + return dir; +} + +static int closedir(DIR *dir) { + int result = 0; + + if (dir != NULL) { + if (dir->handle != INVALID_HANDLE_VALUE) + result = FindClose(dir->handle) ? 0 : -1; + + free(dir); + } else { + result = -1; + SetLastError(ERROR_BAD_ARGUMENTS); + } + + return result; +} + +static struct dirent *readdir(DIR *dir) { + struct dirent *result = 0; + + if (dir) { + if (dir->handle != INVALID_HANDLE_VALUE) { + result = &dir->result; + (void) WideCharToMultiByte(CP_UTF8, 0, + dir->info.cFileName, -1, result->d_name, + sizeof(result->d_name), NULL, NULL); + + if (!FindNextFileW(dir->handle, &dir->info)) { + (void) FindClose(dir->handle); + dir->handle = INVALID_HANDLE_VALUE; + } + + } else { + SetLastError(ERROR_FILE_NOT_FOUND); + } + } else { + SetLastError(ERROR_BAD_ARGUMENTS); + } + + return result; +} +#endif // _WIN32 POSIX opendir/closedir/readdir implementation + +static int scan_directory(struct connection *conn, const char *dir, + struct dir_entry **arr) { + char path[MAX_PATH_SIZE]; + struct dir_entry *p; + struct dirent *dp; + int arr_size = 0, arr_ind = 0, inc = 100; + DIR *dirp; + + *arr = NULL; + if ((dirp = (opendir(dir))) == NULL) return 0; + + while ((dp = readdir(dirp)) != NULL) { + // Do not show current dir and hidden files + if (!strcmp(dp->d_name, ".") || + !strcmp(dp->d_name, "..") || + must_hide_file(conn, dp->d_name)) { + continue; + } + mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); + + // Resize the array if nesessary + if (arr_ind >= arr_size) { + if ((p = (struct dir_entry *) + realloc(*arr, (inc + arr_size) * sizeof(**arr))) != NULL) { + // Memset new chunk to zero, otherwize st_mtime will have garbage which + // can make strftime() segfault, see + // http://code.google.com/p/mongoose/issues/detail?id=79 + memset(p + arr_size, 0, sizeof(**arr) * inc); + + *arr = p; + arr_size += inc; + } + } + + if (arr_ind < arr_size) { + (*arr)[arr_ind].conn = conn; + (*arr)[arr_ind].file_name = strdup(dp->d_name); + stat(path, &(*arr)[arr_ind].st); + arr_ind++; + } + } + closedir(dirp); + + return arr_ind; +} + +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"; + size_t i = 0, j = 0; + + for (i = j = 0; dst_len > 0 && i < s_len && j < 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[j] = '\0'; + return j; +} +#endif // !NO_DIRECTORY_LISTING || !MONGOOSE_NO_DAV + +#ifndef MONGOOSE_NO_DIRECTORY_LISTING + +static void print_dir_entry(const struct dir_entry *de) { + char size[64], mod[64], href[MAX_PATH_SIZE * 3]; + int64_t fsize = de->st.st_size; + int is_dir = S_ISDIR(de->st.st_mode); + const char *slash = is_dir ? "/" : ""; + + if (is_dir) { + mg_snprintf(size, sizeof(size), "%s", "[DIRECTORY]"); + } else { + // We use (signed) cast below because MSVC 6 compiler cannot + // convert unsigned __int64 to double. + if (fsize < 1024) { + mg_snprintf(size, sizeof(size), "%d", (int) fsize); + } else if (fsize < 0x100000) { + mg_snprintf(size, sizeof(size), "%.1fk", (double) fsize / 1024.0); + } else if (fsize < 0x40000000) { + mg_snprintf(size, sizeof(size), "%.1fM", (double) fsize / 1048576); + } else { + mg_snprintf(size, sizeof(size), "%.1fG", (double) fsize / 1073741824); + } + } + strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.st_mtime)); + mg_url_encode(de->file_name, strlen(de->file_name), href, sizeof(href)); + mg_printf_data(&de->conn->mg_conn, + "%s%s" + " %s  %s\n", + href, slash, de->file_name, slash, mod, size); +} + +// Sort directory entries by size, or name, or modification time. +// On windows, __cdecl specification is needed in case if project is built +// with __stdcall convention. qsort always requires __cdels callback. +static int __cdecl compare_dir_entries(const void *p1, const void *p2) { + const struct dir_entry *a = (const struct dir_entry *) p1, + *b = (const struct dir_entry *) p2; + const char *qs = a->conn->mg_conn.query_string ? + a->conn->mg_conn.query_string : "na"; + int cmp_result = 0; + + if (S_ISDIR(a->st.st_mode) && !S_ISDIR(b->st.st_mode)) { + return -1; // Always put directories on top + } else if (!S_ISDIR(a->st.st_mode) && S_ISDIR(b->st.st_mode)) { + return 1; // Always put directories on top + } else if (*qs == 'n') { + cmp_result = strcmp(a->file_name, b->file_name); + } else if (*qs == 's') { + cmp_result = a->st.st_size == b->st.st_size ? 0 : + a->st.st_size > b->st.st_size ? 1 : -1; + } else if (*qs == 'd') { + cmp_result = a->st.st_mtime == b->st.st_mtime ? 0 : + a->st.st_mtime > b->st.st_mtime ? 1 : -1; + } + + return qs[1] == 'd' ? -cmp_result : cmp_result; +} + +static void send_directory_listing(struct connection *conn, const char *dir) { + struct dir_entry *arr = NULL; + int i, num_entries, sort_direction = conn->mg_conn.query_string != NULL && + conn->mg_conn.query_string[1] == 'd' ? 'a' : 'd'; + + mg_send_header(&conn->mg_conn, "Transfer-Encoding", "chunked"); + mg_send_header(&conn->mg_conn, "Content-Type", "text/html; charset=utf-8"); + + mg_printf_data(&conn->mg_conn, + "Index of %s" + "" + "

Index of %s

"
+              ""
+              ""
+              ""
+              "",
+              conn->mg_conn.uri, conn->mg_conn.uri,
+              sort_direction, sort_direction, sort_direction);
+
+  num_entries = scan_directory(conn, dir, &arr);
+  qsort(arr, num_entries, sizeof(arr[0]), compare_dir_entries);
+  for (i = 0; i < num_entries; i++) {
+    print_dir_entry(&arr[i]);
+    free(arr[i].file_name);
+  }
+  free(arr);
+
+  write_terminating_chunk(conn);
+  close_local_endpoint(conn);
+}
+#endif  // MONGOOSE_NO_DIRECTORY_LISTING
+
+#ifndef MONGOOSE_NO_DAV
+static void print_props(struct connection *conn, const char *uri,
+                        file_stat_t *stp) {
+  char mtime[64];
+
+  gmt_time_string(mtime, sizeof(mtime), &stp->st_mtime);
+  mg_printf(&conn->mg_conn,
+      ""
+       "%s"
+       ""
+        ""
+         "%s"
+         "%" INT64_FMT ""
+         "%s"
+        ""
+        "HTTP/1.1 200 OK"
+       ""
+      "\n",
+      uri, S_ISDIR(stp->st_mode) ? "" : "",
+      (int64_t) stp->st_size, mtime);
+}
+
+static void handle_propfind(struct connection *conn, const char *path,
+                            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];
+
+  conn->mg_conn.status_code = 207;
+
+  // Print properties for the requested resource itself
+  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);
+      }
+    }
+    ns_send(conn->ns_conn, footer, sizeof(footer) - 1);
+  }
+
+  close_local_endpoint(conn);
+}
+
+static void handle_mkcol(struct connection *conn, const char *path) {
+  int status_code = 500;
+
+  if (conn->mg_conn.content_len > 0) {
+    status_code = 415;
+  } else if (!mkdir(path, 0755)) {
+    status_code = 201;
+  } else if (errno == EEXIST) {
+    status_code = 405;
+  } else if (errno == EACCES) {
+    status_code = 403;
+  } else if (errno == ENOENT) {
+    status_code = 409;
+  }
+  send_http_error(conn, status_code, NULL);
+}
+
+static int remove_directory(const char *dir) {
+  char path[MAX_PATH_SIZE];
+  struct dirent *dp;
+  file_stat_t st;
+  DIR *dirp;
+
+  if ((dirp = opendir(dir)) == NULL) return 0;
+
+  while ((dp = readdir(dirp)) != NULL) {
+    if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
+    mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
+    stat(path, &st);
+    if (S_ISDIR(st.st_mode)) {
+      remove_directory(path);
+    } else {
+      remove(path);
+    }
+  }
+  closedir(dirp);
+  rmdir(dir);
+
+  return 1;
+}
+
+static void handle_delete(struct connection *conn, const char *path) {
+  file_stat_t st;
+
+  if (stat(path, &st) != 0) {
+    send_http_error(conn, 404, NULL);
+  } else if (S_ISDIR(st.st_mode)) {
+    remove_directory(path);
+    send_http_error(conn, 204, NULL);
+  } else if (remove(path) == 0) {
+    send_http_error(conn, 204, NULL);
+  } else {
+    send_http_error(conn, 423, NULL);
+  }
+}
+
+// For a given PUT path, create all intermediate subdirectories
+// for given path. Return 0 if the path itself is a directory,
+// or -1 on error, 1 if OK.
+static int put_dir(const char *path) {
+  char buf[MAX_PATH_SIZE];
+  const char *s, *p;
+  file_stat_t st;
+
+  // Create intermediate directories if they do not exist
+  for (s = p = path + 1; (p = strchr(s, '/')) != NULL; s = ++p) {
+    if (p - path >= (int) sizeof(buf)) return -1; // Buffer overflow
+    memcpy(buf, path, p - path);
+    buf[p - path] = '\0';
+    if (stat(buf, &st) != 0 && mkdir(buf, 0755) != 0) return -1;
+    if (p[1] == '\0') return 0;  // Path is a directory itself
+  }
+
+  return 1;
+}
+
+static void handle_put(struct connection *conn, const char *path) {
+  file_stat_t st;
+  const char *range, *cl_hdr = mg_get_header(&conn->mg_conn, "Content-Length");
+  int64_t r1, r2;
+  int rc;
+
+  conn->mg_conn.status_code = !stat(path, &st) ? 200 : 201;
+  if ((rc = put_dir(path)) == 0) {
+    mg_printf(&conn->mg_conn, "HTTP/1.1 %d OK\r\n\r\n",
+              conn->mg_conn.status_code);
+    close_local_endpoint(conn);
+  } else if (rc == -1) {
+    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
-	// dlsym() on UNIX returns void *. ISO C forbids casts of data pointers to
-	// function pointers. We need to use a union to make a cast.
-	u.p = dlsym(dll_handle, fp->name);
-#endif // _WIN32
-	if (u.fp == NULL) {
-		cry(fc(ctx), "%s: %s: cannot find %s", __func__, dll_name, fp->name);
-		return 0;
-	} else {
-		fp->ptr = u.fp;
-	}
-	}
-
-	return 1;
-}
-#endif // NO_SSL_DL
-
-// Dynamically load SSL library. Set up ctx->ssl_ctx pointer.
-static int set_ssl_option(struct mg_context *ctx) {
-	int i, size;
-	const char *pem;
-
-	// If PEM file is not specified, skip SSL initialization.
-	if ((pem = ctx->config[SSL_CERTIFICATE]) == NULL) {
-	return 1;
-	}
-
-#if !defined(NO_SSL_DL)
-	if (!load_dll(ctx, SSL_LIB, ssl_sw) ||
-		!load_dll(ctx, CRYPTO_LIB, crypto_sw)) {
-	return 0;
-	}
-#endif // NO_SSL_DL
-
-	// Initialize SSL library
-	SSL_library_init();
-	SSL_load_error_strings();
-
-	if ((ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) {
-	cry(fc(ctx), "SSL_CTX_new (server) error: %s", ssl_error());
-	return 0;
-	}
-
-	// If user callback returned non-NULL, that means that user callback has
-	// set up certificate itself. In this case, skip sertificate setting.
-	if ((ctx->callbacks.init_ssl == NULL ||
-		!ctx->callbacks.init_ssl(ctx->ssl_ctx, ctx->user_data)) &&
-		(SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0 ||
-		SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0)) {
-	cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error());
-	return 0;
-	}
-
-	if (pem != NULL) {
-	(void) SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, pem);
-	}
-
-	// Initialize locking callbacks, needed for thread safety.
-	// http://www.openssl.org/support/faq.html#PROG1
-	size = sizeof(pthread_mutex_t) * CRYPTO_num_locks();
-	if ((ssl_mutexes = (pthread_mutex_t *) malloc((size_t)size)) == NULL) {
-	cry(fc(ctx), "%s: cannot allocate mutexes: %s", __func__, ssl_error());
-	return 0;
-	}
-
-	for (i = 0; i < CRYPTO_num_locks(); i++) {
-	pthread_mutex_init(&ssl_mutexes[i], NULL);
-	}
-
-	CRYPTO_set_locking_callback(&ssl_locking_callback);
-	CRYPTO_set_id_callback(&ssl_id_callback);
-
-	return 1;
-}
-
-static void uninitialize_ssl(struct mg_context *ctx) {
-	int i;
-	if (ctx->ssl_ctx != NULL) {
-	CRYPTO_set_locking_callback(NULL);
-	for (i = 0; i < CRYPTO_num_locks(); i++) {
-		pthread_mutex_destroy(&ssl_mutexes[i]);
-	}
-	CRYPTO_set_locking_callback(NULL);
-	CRYPTO_set_id_callback(NULL);
-	}
-}
-#endif // !NO_SSL
-
-static int set_gpass_option(struct mg_context *ctx) {
-	struct file file = STRUCT_FILE_INITIALIZER;
-	const char *path = ctx->config[GLOBAL_PASSWORDS_FILE];
-	if (path != NULL && !mg_stat(fc(ctx), path, &file)) {
-	cry(fc(ctx), "Cannot open %s: %s", path, strerror(ERRNO));
-	return 0;
-	}
-	return 1;
-}
-
-static int set_acl_option(struct mg_context *ctx) {
-	return check_acl(ctx, (uint32_t) 0x7f000001UL) != -1;
-}
-
-static void reset_per_request_attributes(struct mg_connection *conn) {
-	conn->path_info = NULL;
-	conn->num_bytes_sent = conn->consumed_content = 0;
-	conn->status_code = -1;
-	conn->must_close = conn->request_len = conn->throttle = 0;
-}
-
-static void close_socket_gracefully(struct mg_connection *conn) {
-#if defined(_WIN32)
-	char buf[MG_BUF_LEN];
-	int n;
+  } else if ((conn->endpoint.fd =
+              open(path, O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) {
 #endif
-	struct linger linger;
+    send_http_error(conn, 500, "open(%s): %s", path, strerror(errno));
+  } else {
+    DBG(("PUT [%s] %zu", path, 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");
+    conn->cl = to64(cl_hdr);
+    r1 = r2 = 0;
+    if (range != NULL && parse_range_header(range, &r1, &r2) > 0) {
+      conn->mg_conn.status_code = 206;
+      lseek(conn->endpoint.fd, r1, SEEK_SET);
+      conn->cl = r2 > r1 ? r2 - r1 + 1: conn->cl - r1;
+    }
+    mg_printf(&conn->mg_conn, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n",
+              conn->mg_conn.status_code);
+  }
+}
 
-	// Set linger option to avoid socket hanging out after close. This prevent
-	// ephemeral port exhaust problem under high QPS.
-	linger.l_onoff = 1;
-	linger.l_linger = 1;
-	setsockopt(conn->client.sock, SOL_SOCKET, SO_LINGER,
-				(char *) &linger, sizeof(linger));
+static void forward_put_data(struct connection *conn) {
+  struct iobuf *io = &conn->ns_conn->recv_iobuf;
+  size_t k = conn->cl < (int64_t) io->len ? conn->cl : 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);
+  }
+}
+#endif //  MONGOOSE_NO_DAV
 
-	// Send FIN to the client
-	shutdown(conn->client.sock, SHUT_WR);
-	set_non_blocking_mode(conn->client.sock);
+static void send_options(struct connection *conn) {
+  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);
+}
 
-#if defined(_WIN32)
-	// Read and discard pending incoming data. If we do not do that and close the
-	// socket, the data in the send buffer may be discarded. This
-	// behaviour is seen on Windows, when client keeps sending data
-	// when server decides to close the connection; then when client
-	// does recv() it gets no data back.
-	do {
-	n = pull(NULL, conn, buf, sizeof(buf));
-	} while (n > 0);
+#ifndef MONGOOSE_NO_AUTH
+void mg_send_digest_auth_request(struct mg_connection *c) {
+  struct connection *conn = MG_CONN_2_CONN(c);
+  c->status_code = 401;
+  mg_printf(c,
+            "HTTP/1.1 401 Unauthorized\r\n"
+            "WWW-Authenticate: Digest qop=\"auth\", "
+            "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
+            conn->server->config_options[AUTH_DOMAIN],
+            (unsigned long) time(NULL));
+  close_local_endpoint(conn);
+}
+
+// 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) {
+  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)) {
+    mg_snprintf(name, sizeof(name), "%s%c%s", path, '/', PASSWORDS_FILE_NAME);
+    fp = fopen(name, "r");
+  } else {
+    // Try to find .htpasswd in requested directory.
+    if ((p = strrchr(path, '/')) == NULL) p = path;
+    mg_snprintf(name, sizeof(name), "%.*s%c%s",
+                (int) (p - path), path, '/', PASSWORDS_FILE_NAME);
+    fp = fopen(name, "r");
+  }
+
+  return fp;
+}
+
+#if !defined(HAVE_MD5) && !defined(MONGOOSE_NO_AUTH)
+typedef struct MD5Context {
+  uint32_t buf[4];
+  uint32_t bits[2];
+  unsigned char in[64];
+} MD5_CTX;
+
+static void byteReverse(unsigned char *buf, unsigned longs) {
+  uint32_t t;
+
+  // Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
+  if (is_big_endian()) {
+    do {
+      t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+        ((unsigned) buf[1] << 8 | buf[0]);
+      * (uint32_t *) buf = t;
+      buf += 4;
+    } while (--longs);
+  }
+}
+
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+#define MD5STEP(f, w, x, y, z, data, s) \
+  ( w += f(x, y, z) + data,  w = w<>(32-s),  w += x )
+
+// Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+// initialization constants.
+static void MD5Init(MD5_CTX *ctx) {
+  ctx->buf[0] = 0x67452301;
+  ctx->buf[1] = 0xefcdab89;
+  ctx->buf[2] = 0x98badcfe;
+  ctx->buf[3] = 0x10325476;
+
+  ctx->bits[0] = 0;
+  ctx->bits[1] = 0;
+}
+
+static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
+  register uint32_t a, b, c, d;
+
+  a = buf[0];
+  b = buf[1];
+  c = buf[2];
+  d = buf[3];
+
+  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+  buf[0] += a;
+  buf[1] += b;
+  buf[2] += c;
+  buf[3] += d;
+}
+
+static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
+  uint32_t t;
+
+  t = ctx->bits[0];
+  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+    ctx->bits[1]++;
+  ctx->bits[1] += len >> 29;
+
+  t = (t >> 3) & 0x3f;
+
+  if (t) {
+    unsigned char *p = (unsigned char *) ctx->in + t;
+
+    t = 64 - t;
+    if (len < t) {
+      memcpy(p, buf, len);
+      return;
+    }
+    memcpy(p, buf, t);
+    byteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+    buf += t;
+    len -= t;
+  }
+
+  while (len >= 64) {
+    memcpy(ctx->in, buf, 64);
+    byteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+    buf += 64;
+    len -= 64;
+  }
+
+  memcpy(ctx->in, buf, len);
+}
+
+static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
+  unsigned count;
+  unsigned char *p;
+  uint32_t *a;
+
+  count = (ctx->bits[0] >> 3) & 0x3F;
+
+  p = ctx->in + count;
+  *p++ = 0x80;
+  count = 64 - 1 - count;
+  if (count < 8) {
+    memset(p, 0, count);
+    byteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+    memset(ctx->in, 0, 56);
+  } else {
+    memset(p, 0, count - 8);
+  }
+  byteReverse(ctx->in, 14);
+
+  a = (uint32_t *)ctx->in;
+  a[14] = ctx->bits[0];
+  a[15] = ctx->bits[1];
+
+  MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+  byteReverse((unsigned char *) ctx->buf, 4);
+  memcpy(digest, ctx->buf, 16);
+  memset((char *) ctx, 0, sizeof(*ctx));
+}
+#endif // !HAVE_MD5
+
+
+
+// Stringify binary data. Output buffer must be twice as big as input,
+// because each byte takes 2 bytes in string representation
+static void bin2str(char *to, const unsigned char *p, size_t len) {
+  static const char *hex = "0123456789abcdef";
+
+  for (; len--; p++) {
+    *to++ = hex[p[0] >> 4];
+    *to++ = hex[p[0] & 0x0f];
+  }
+  *to = '\0';
+}
+
+// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
+char *mg_md5(char buf[33], ...) {
+  unsigned char hash[16];
+  const char *p;
+  va_list ap;
+  MD5_CTX ctx;
+
+  MD5Init(&ctx);
+
+  va_start(ap, buf);
+  while ((p = va_arg(ap, const char *)) != NULL) {
+    MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
+  }
+  va_end(ap);
+
+  MD5Final(hash, &ctx);
+  bin2str(buf, hash, sizeof(hash));
+  return buf;
+}
+
+// Check the user's password, return 1 if OK
+static int check_password(const char *method, const char *ha1, const char *uri,
+                          const char *nonce, const char *nc, const char *cnonce,
+                          const char *qop, const char *response) {
+  char ha2[32 + 1], expected_response[32 + 1];
+
+#if 0
+  // Check for authentication timeout
+  if ((unsigned long) time(NULL) - (unsigned long) to64(nonce) > 3600 * 2) {
+    return 0;
+  }
 #endif
 
-	// Now we know that our FIN is ACK-ed, safe to close
-	closesocket(conn->client.sock);
+  mg_md5(ha2, method, ":", uri, NULL);
+  mg_md5(expected_response, ha1, ":", nonce, ":", nc,
+      ":", cnonce, ":", qop, ":", ha2, NULL);
+
+  return mg_strcasecmp(response, expected_response) == 0 ? MG_TRUE : MG_FALSE;
 }
 
-static void close_connection(struct mg_connection *conn) {
-	conn->must_close = 1;
 
-#ifndef NO_SSL
-	if (conn->ssl != NULL) {
-	// Run SSL_shutdown twice to ensure completly close SSL connection
-	SSL_shutdown(conn->ssl);
-	SSL_free(conn->ssl);
-	conn->ssl = NULL;
-	}
+// Authorize against the opened passwords file. Return 1 if authorized.
+int mg_authorize_digest(struct mg_connection *c, FILE *fp) {
+  struct connection *conn = MG_CONN_2_CONN(c);
+  const char *hdr;
+  char line[256], f_user[256], ha1[256], f_domain[256], user[100], nonce[100],
+       uri[MAX_REQUEST_SIZE], cnonce[100], resp[100], qop[100], nc[100];
+
+  if (c == NULL || fp == NULL) return 0;
+  if ((hdr = mg_get_header(c, "Authorization")) == NULL ||
+      mg_strncasecmp(hdr, "Digest ", 7) != 0) return 0;
+  if (!mg_parse_header(hdr, "username", user, sizeof(user))) return 0;
+  if (!mg_parse_header(hdr, "cnonce", cnonce, sizeof(cnonce))) return 0;
+  if (!mg_parse_header(hdr, "response", resp, sizeof(resp))) return 0;
+  if (!mg_parse_header(hdr, "uri", uri, sizeof(uri))) return 0;
+  if (!mg_parse_header(hdr, "qop", qop, sizeof(qop))) return 0;
+  if (!mg_parse_header(hdr, "nc", nc, sizeof(nc))) return 0;
+  if (!mg_parse_header(hdr, "nonce", nonce, sizeof(nonce))) return 0;
+
+  while (fgets(line, sizeof(line), fp) != NULL) {
+    if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) == 3 &&
+        !strcmp(user, f_user) &&
+        // NOTE(lsm): due to a bug in MSIE, we do not compare URIs
+        !strcmp(conn->server->config_options[AUTH_DOMAIN], f_domain))
+      return check_password(c->request_method, ha1, uri,
+                            nonce, nc, cnonce, qop, resp);
+  }
+  return MG_FALSE;
+}
+
+
+// Return 1 if request is authorised, 0 otherwise.
+static int is_authorized(struct connection *conn, const char *path) {
+  FILE *fp;
+  int authorized = MG_TRUE;
+
+  if ((fp = open_auth_file(conn, path)) != NULL) {
+    authorized = mg_authorize_digest(&conn->mg_conn, fp);
+    fclose(fp);
+  }
+
+  return authorized;
+}
+
+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 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);
+  }
+
+  return authorized;
+}
+
+static int is_dav_request(const struct connection *conn) {
+  const char *s = conn->mg_conn.request_method;
+  return !strcmp(s, "PUT") || !strcmp(s, "DELETE") ||
+    !strcmp(s, "MKCOL") || !strcmp(s, "PROPFIND");
+}
+#endif // MONGOOSE_NO_AUTH
+
+static int parse_header(const char *str, int str_len, const char *var_name,
+                        char *buf, size_t buf_size) {
+  int ch = ' ', len = 0, n = strlen(var_name);
+  const char *p, *end = str + str_len, *s = NULL;
+
+  if (buf != NULL && buf_size > 0) buf[0] = '\0';
+
+  // Find where variable starts
+  for (s = str; s != NULL && s + n < end; s++) {
+    if ((s == str || s[-1] == ' ' || s[-1] == ',') && s[n] == '=' &&
+        !memcmp(s, var_name, n)) break;
+  }
+
+  if (s != NULL && &s[n + 1] < end) {
+    s += n + 1;
+    if (*s == '"' || *s == '\'') ch = *s++;
+    p = s;
+    while (p < end && p[0] != ch && p[0] != ',' && len < (int) buf_size) {
+      if (p[0] == '\\' && p[1] == ch) p++;
+      buf[len++] = *p++;
+    }
+    if (len >= (int) buf_size || (ch != ' ' && *p != ch)) {
+      len = 0;
+    } else {
+      if (len > 0 && s[len - 1] == ',') len--;
+      if (len > 0 && s[len - 1] == ';') len--;
+      buf[len] = '\0';
+    }
+  }
+
+  return len;
+}
+
+int mg_parse_header(const char *s, const char *var_name, char *buf,
+                    size_t buf_size) {
+  return parse_header(s, s == NULL ? 0 : strlen(s), var_name, buf, buf_size);
+}
+
+#ifndef MONGOOSE_NO_SSI
+static void send_ssi_file(struct mg_connection *, const char *, FILE *, int);
+
+static void send_file_data(struct mg_connection *conn, FILE *fp) {
+  char buf[IOBUF_SIZE];
+  int n;
+  while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
+    mg_write(conn, buf, n);
+  }
+}
+
+static void do_ssi_include(struct mg_connection *conn, const char *ssi,
+                           char *tag, int include_level) {
+  char file_name[IOBUF_SIZE], path[MAX_PATH_SIZE], *p;
+  char **opts = (MG_CONN_2_CONN(conn))->server->config_options;
+  FILE *fp;
+
+  // sscanf() is safe here, since send_ssi_file() also uses buffer
+  // of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN.
+  if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) {
+    // File name is relative to the webserver root
+    mg_snprintf(path, sizeof(path), "%s%c%s",
+                opts[DOCUMENT_ROOT], '/', file_name);
+  } else if (sscanf(tag, " abspath=\"%[^\"]\"", file_name) == 1) {
+    // File name is relative to the webserver working directory
+    // or it is absolute system path
+    mg_snprintf(path, sizeof(path), "%s", file_name);
+  } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1 ||
+             sscanf(tag, " \"%[^\"]\"", file_name) == 1) {
+    // File name is relative to the currect document
+    mg_snprintf(path, sizeof(path), "%s", ssi);
+    if ((p = strrchr(path, '/')) != NULL) {
+      p[1] = '\0';
+    }
+    mg_snprintf(path + strlen(path), sizeof(path) - strlen(path), "%s",
+                file_name);
+  } else {
+    mg_printf(conn, "Bad SSI #include: [%s]", tag);
+    return;
+  }
+
+  if ((fp = fopen(path, "rb")) == NULL) {
+    mg_printf(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s",
+              tag, path, strerror(errno));
+  } else {
+    ns_set_close_on_exec(fileno(fp));
+    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);
+    }
+    fclose(fp);
+  }
+}
+
+#ifndef MONGOOSE_NO_POPEN
+static void do_ssi_exec(struct mg_connection *conn, char *tag) {
+  char cmd[IOBUF_SIZE];
+  FILE *fp;
+
+  if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) {
+    mg_printf(conn, "Bad SSI #exec: [%s]", tag);
+  } else if ((fp = popen(cmd, "r")) == NULL) {
+    mg_printf(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(errno));
+  } else {
+    send_file_data(conn, fp);
+    pclose(fp);
+  }
+}
+#endif // !MONGOOSE_NO_POPEN
+
+static void send_ssi_file(struct mg_connection *conn, const char *path,
+                          FILE *fp, int include_level) {
+  char buf[IOBUF_SIZE];
+  int ch, offset, len, in_ssi_tag;
+
+  if (include_level > 10) {
+    mg_printf(conn, "SSI #include level is too deep (%s)", path);
+    return;
+  }
+
+  in_ssi_tag = len = offset = 0;
+  while ((ch = fgetc(fp)) != EOF) {
+    if (in_ssi_tag && ch == '>') {
+      in_ssi_tag = 0;
+      buf[len++] = (char) ch;
+      buf[len] = '\0';
+      assert(len <= (int) sizeof(buf));
+      if (len < 6 || memcmp(buf, "
NameModifiedSize