Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id B67DB200D49 for ; Fri, 24 Nov 2017 11:59:13 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id B4E54160BEE; Fri, 24 Nov 2017 10:59:13 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 574AB160C17 for ; Fri, 24 Nov 2017 11:59:11 +0100 (CET) Received: (qmail 71977 invoked by uid 500); 24 Nov 2017 10:59:10 -0000 Mailing-List: contact commits-help@celix.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@celix.apache.org Delivered-To: mailing list commits@celix.apache.org Received: (qmail 71844 invoked by uid 99); 24 Nov 2017 10:59:10 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 24 Nov 2017 10:59:10 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id AF764F5E2A; Fri, 24 Nov 2017 10:59:09 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: pnoltes@apache.org To: commits@celix.apache.org Date: Fri, 24 Nov 2017 10:59:14 -0000 Message-Id: <27118a19393a4212893232be4c5a77d2@git.apache.org> In-Reply-To: <5ab566a8835c440cb81e1275003d2e60@git.apache.org> References: <5ab566a8835c440cb81e1275003d2e60@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [6/8] celix git commit: CELIX-417: Refactors CMake usage for the RSA bundles archived-at: Fri, 24 Nov 2017 10:59:13 -0000 http://git-wip-us.apache.org/repos/asf/celix/blob/27a2aa75/remote_services/civetweb/src/civetweb.c ---------------------------------------------------------------------- diff --git a/remote_services/civetweb/src/civetweb.c b/remote_services/civetweb/src/civetweb.c new file mode 100644 index 0000000..a6093b7 --- /dev/null +++ b/remote_services/civetweb/src/civetweb.c @@ -0,0 +1,7907 @@ + /* Copyright (c) 2013-2015 the Civetweb developers + * Copyright (c) 2004-2013 Sergey Lyubka + * + * 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: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 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. + */ + +#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 +#ifndef _LARGEFILE_SOURCE +#define _LARGEFILE_SOURCE /* For fseeko(), ftello() */ +#endif +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 /* Use 64-bit file offsets by default */ +#endif +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS /* wants this for C++ */ +#endif +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS /* C++ wants that for INT64_MAX */ +#endif +#endif + +#if defined (_MSC_VER) +/* 'type cast' : conversion from 'int' to 'HANDLE' of greater size */ +#pragma warning (disable : 4306 ) +/* 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 */ +#if defined(WIN32_LEAN_AND_MEAN) +#undef WIN32_LEAN_AND_MEAN +#endif + +#if defined USE_IPV6 && defined(_WIN32) +#include +#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 IGNORE_UNUSED_RESULT +#define IGNORE_UNUSED_RESULT(a) (void)((a) && 1) +#endif + +#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 + +#ifndef MAX_WORKER_THREADS +#define MAX_WORKER_THREADS (1024*64) +#endif + +#if defined(_WIN32) && !defined(__SYMBIAN32__) /* Windows specific */ +#if defined(_MSC_VER) && _MSC_VER <= 1400 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0400 /* To make it link in VS2005 */ +#endif +#include +typedef const char * SOCK_OPT_TYPE; + +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif + +#ifndef _IN_PORT_T +#ifndef in_port_t +#define in_port_t u_short +#endif +#endif + +#ifndef _WIN32_WCE +#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 +#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 +#define W_OK (2) /* http://msdn.microsoft.com/en-us/library/1w06ktdy.aspx */ +#if !defined(EWOULDBLOCK) +#define EWOULDBLOCK WSAEWOULDBLOCK +#endif /* !EWOULDBLOCK */ +#define _POSIX_ +#define INT64_FMT "I64d" + +#define WINCDECL __cdecl +#define SHUT_WR 1 +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#define access _access +#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) +#define rmdir(x) _rmdir(x) + +#if defined(USE_LUA) && defined(USE_WEBSOCKET) +#define USE_TIMERS +#endif + +#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 DWORD pthread_key_t; +typedef HANDLE pthread_t; +typedef struct { + CRITICAL_SECTION threadIdSec; + int waitingthreadcount; /* The number of threads queued. */ + pthread_t *waitingthreadhdls; /* The thread handles. */ +} pthread_cond_t; + +#ifndef __clockid_t_defined +typedef DWORD clockid_t; +#endif +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC (1) +#endif +#ifndef CLOCK_REALTIME +#define CLOCK_REALTIME (2) +#endif + +#ifndef _TIMESPEC_DEFINED +struct timespec { + time_t tv_sec; /* seconds */ + long tv_nsec; /* nanoseconds */ +}; +#endif + +#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; +typedef unsigned __int64 uint64_t; +typedef __int64 int64_t; +#define INT64_MAX 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; + +#if !defined(USE_IPV6) && defined(_WIN32) +#ifndef HAVE_POLL +struct pollfd { + SOCKET fd; + short events; + short revents; +}; +#define POLLIN 1 +#endif +#endif + +/* Mark required libraries */ +#ifdef _MSC_VER +#pragma comment(lib, "Ws2_32.lib") +#endif + +#else /* UNIX specific */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +typedef const void * SOCK_OPT_TYPE; + +//#if defined(ANDROID) +typedef unsigned short int in_port_t; +//#endif + +#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 +#define INVALID_SOCKET (-1) +#define INT64_FMT PRId64 +typedef int SOCKET; +#define WINCDECL + +#endif /* End of Windows and UNIX specific includes */ + +#ifdef _WIN32 +static CRITICAL_SECTION global_log_file_lock; +static DWORD pthread_self(void) +{ + return GetCurrentThreadId(); +} + +int pthread_key_create(pthread_key_t *key, void (*_must_be_zero)(void*) /* destructor function not supported for windows */) +{ + assert(_must_be_zero == NULL); + if ((key!=0) && (_must_be_zero == NULL)) { + *key = TlsAlloc(); + return (*key != TLS_OUT_OF_INDEXES) ? 0 : -1; + } + return -2; +} + +int pthread_key_delete(pthread_key_t key) +{ + return TlsFree(key) ? 0 : 1; +} + +int pthread_setspecific(pthread_key_t key, void * value) +{ + return TlsSetValue(key, value) ? 0 : 1; +} + +void *pthread_getspecific(pthread_key_t key) +{ + return TlsGetValue(key); +} +#endif /* _WIN32 */ + + +#include "civetweb.h" + +#define PASSWORDS_FILE_NAME ".htpasswd" +#define CGI_ENVIRONMENT_SIZE 4096 +#define MAX_CGI_ENVIR_VARS 64 +#define MG_BUF_LEN 8192 +#ifndef MAX_REQUEST_SIZE +#define MAX_REQUEST_SIZE 16384 +#endif +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) + +#if !defined(DEBUG_TRACE) +#if defined(CWDEBUG) + +static void DEBUG_TRACE_FUNC(const char *func, unsigned line, PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(3, 4); + +static void DEBUG_TRACE_FUNC(const char *func, unsigned line, const char *fmt, ...) { + + va_list args; + flockfile(stdout); + printf("*** %lu.%p.%s.%u: ", + (unsigned long) time(NULL), (void *) pthread_self(), + func, line); + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + putchar('\n'); + fflush(stdout); + funlockfile(stdout); +} + +#define DEBUG_TRACE(fmt, ...) DEBUG_TRACE_FUNC(__func__, __LINE__, fmt, __VA_ARGS__) + +#else +#define DEBUG_TRACE(fmt, ...) +#endif /* CWDEBUG */ +#endif /* DEBUG_TRACE */ + +#if defined(MEMORY_DEBUGGING) +static unsigned long blockCount = 0; +static unsigned long totalMemUsed = 0; + +static void * mg_malloc_ex(size_t size, const char * file, unsigned line) { + + void * data = malloc(size + sizeof(size_t)); + void * memory = 0; + char mallocStr[256]; + + if (data) { + *(size_t*)data = size; + totalMemUsed += size; + blockCount++; + memory = (void *)(((char*)data)+sizeof(size_t)); + } + + sprintf(mallocStr, "MEM: %p %5lu alloc %7lu %4lu --- %s:%u\n", memory, (unsigned long)size, totalMemUsed, blockCount, file, line); +#if defined(_WIN32) + OutputDebugStringA(mallocStr); +#else + DEBUG_TRACE("%s", mallocStr); +#endif + + return memory; +} + +static void * mg_calloc_ex(size_t count, size_t size, const char * file, unsigned line) { + + void * data = mg_malloc_ex(size*count, file, line); + if (data) memset(data, 0, size); + + return data; +} + +static void mg_free_ex(void * memory, const char * file, unsigned line) { + + char mallocStr[256]; + void * data = (void *)(((char*)memory)-sizeof(size_t)); + size_t size; + + if (memory) { + size = *(size_t*)data; + totalMemUsed -= size; + blockCount--; + sprintf(mallocStr, "MEM: %p %5lu free %7lu %4lu --- %s:%u\n", memory, (unsigned long)size, totalMemUsed, blockCount, file, line); +#if defined(_WIN32) + OutputDebugStringA(mallocStr); +#else + DEBUG_TRACE("%s", mallocStr); +#endif + + free(data); + } +} + +static void * mg_realloc_ex(void * memory, size_t newsize, const char * file, unsigned line) { + + char mallocStr[256]; + void * data; + void * _realloc; + size_t oldsize; + + if (newsize) { + if (memory) { + data = (void *)(((char*)memory)-sizeof(size_t)); + oldsize = *(size_t*)data; + _realloc = realloc(data, newsize+sizeof(size_t)); + if (_realloc) { + data = _realloc; + totalMemUsed -= oldsize; + sprintf(mallocStr, "MEM: %p %5lu r-free %7lu %4lu --- %s:%u\n", memory, (unsigned long)oldsize, totalMemUsed, blockCount, file, line); +#if defined(_WIN32) + OutputDebugStringA(mallocStr); +#else + DEBUG_TRACE("%s", mallocStr); +#endif + totalMemUsed += newsize; + sprintf(mallocStr, "MEM: %p %5lu r-alloc %7lu %4lu --- %s:%u\n", memory, (unsigned long)newsize, totalMemUsed, blockCount, file, line); +#if defined(_WIN32) + OutputDebugStringA(mallocStr); +#else + DEBUG_TRACE("%s", mallocStr); +#endif + *(size_t*)data = newsize; + data = (void *)(((char*)data)+sizeof(size_t)); + } else { +#if defined(_WIN32) + OutputDebugStringA("MEM: realloc failed\n"); +#else + DEBUG_TRACE("%s", "MEM: realloc failed\n"); +#endif + return _realloc; + } + } else { + data = mg_malloc_ex(newsize, file, line); + } + } else { + data = 0; + mg_free_ex(memory, file, line); + } + + return data; +} + +#define mg_malloc(a) mg_malloc_ex(a, __FILE__, __LINE__) +#define mg_calloc(a,b) mg_calloc_ex(a, b, __FILE__, __LINE__) +#define mg_realloc(a, b) mg_realloc_ex(a, b, __FILE__, __LINE__) +#define mg_free(a) mg_free_ex(a, __FILE__, __LINE__) + +#else +static __inline void * mg_malloc(size_t a) {return malloc(a);} +static __inline void * mg_calloc(size_t a, size_t b) {return calloc(a, b);} +static __inline void * mg_realloc(void * a, size_t b) {return realloc(a, b);} +static __inline void mg_free(void * a) {free(a);} +#endif + +/* This following lines are just meant as a reminder to use the mg-functions for memory management */ +#ifdef malloc + #undef malloc +#endif +#ifdef calloc + #undef calloc +#endif +#ifdef realloc + #undef realloc +#endif +#ifdef free + #undef free +#endif +#define malloc DO_NOT_USE_THIS_FUNCTION__USE_mg_malloc +#define calloc DO_NOT_USE_THIS_FUNCTION__USE_mg_calloc +#define realloc DO_NOT_USE_THIS_FUNCTION__USE_mg_realloc +#define free DO_NOT_USE_THIS_FUNCTION__USE_mg_free + + +#define MD5_STATIC static +#include "md5.inl" + +/* 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 +#endif + +#if !defined(SOMAXCONN) +#define SOMAXCONN 100 +#endif + +#if !defined(PATH_MAX) +#define PATH_MAX 4096 +#endif + +/* Size of the accepted socket queue */ +#if !defined(MGSQLEN) +#define MGSQLEN 20 +#endif + +#if defined(NO_SSL_DL) +#include +#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; +#endif +}; + +/* Describes a string (chunk of memory). */ +struct vec { + const char *ptr; + size_t 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 */ +}; + +/* NOTE(lsm): this enum shoulds be in sync with the config_options below. */ +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, + DECODE_URL, + +#if defined(USE_LUA) + LUA_PRELOAD_FILE, LUA_SCRIPT_EXTENSIONS, LUA_SERVER_PAGE_EXTENSIONS, +#endif +#if defined(USE_WEBSOCKET) + WEBSOCKET_ROOT, +#endif +#if defined(USE_LUA) && defined(USE_WEBSOCKET) + LUA_WEBSOCKET_EXTENSIONS, +#endif + ACCESS_CONTROL_ALLOW_ORIGIN, ERROR_PAGES, + + NUM_OPTIONS +}; + +/* Config option name, config types, default value */ +static struct mg_option config_options[] = { + {"cgi_pattern", CONFIG_TYPE_EXT_PATTERN, "**.cgi$|**.pl$|**.php$"}, + {"cgi_environment", CONFIG_TYPE_STRING, NULL}, + {"put_delete_auth_file", CONFIG_TYPE_FILE, NULL}, + {"cgi_interpreter", CONFIG_TYPE_FILE, NULL}, + {"protect_uri", CONFIG_TYPE_STRING, NULL}, + {"authentication_domain", CONFIG_TYPE_STRING, "mydomain.com"}, + {"ssi_pattern", CONFIG_TYPE_EXT_PATTERN, "**.shtml$|**.shtm$"}, + {"throttle", CONFIG_TYPE_STRING, NULL}, + {"access_log_file", CONFIG_TYPE_FILE, NULL}, + {"enable_directory_listing", CONFIG_TYPE_BOOLEAN, "yes"}, + {"error_log_file", CONFIG_TYPE_FILE, NULL}, + {"global_auth_file", CONFIG_TYPE_FILE, NULL}, + {"index_files", CONFIG_TYPE_STRING, +#ifdef USE_LUA + "index.xhtml,index.html,index.htm,index.lp,index.lsp,index.lua,index.cgi,index.shtml,index.php"}, +#else + "index.xhtml,index.html,index.htm,index.cgi,index.shtml,index.php"}, +#endif + {"enable_keep_alive", CONFIG_TYPE_BOOLEAN, "no"}, + {"access_control_list", CONFIG_TYPE_STRING, NULL}, + {"extra_mime_types", CONFIG_TYPE_STRING, NULL}, + {"listening_ports", CONFIG_TYPE_STRING, "8080"}, + {"document_root", CONFIG_TYPE_DIRECTORY, NULL}, + {"ssl_certificate", CONFIG_TYPE_FILE, NULL}, + {"num_threads", CONFIG_TYPE_NUMBER, "50"}, + {"run_as_user", CONFIG_TYPE_STRING, NULL}, + {"url_rewrite_patterns", CONFIG_TYPE_STRING, NULL}, + {"hide_files_patterns", CONFIG_TYPE_EXT_PATTERN, NULL}, + {"request_timeout_ms", CONFIG_TYPE_NUMBER, "30000"}, + {"decode_url", CONFIG_TYPE_BOOLEAN, "yes"}, + +#if defined(USE_LUA) + {"lua_preload_file", CONFIG_TYPE_FILE, NULL}, + {"lua_script_pattern", CONFIG_TYPE_EXT_PATTERN, "**.lua$"}, + {"lua_server_page_pattern", CONFIG_TYPE_EXT_PATTERN, "**.lp$|**.lsp$"}, +#endif +#if defined(USE_WEBSOCKET) + {"websocket_root", CONFIG_TYPE_DIRECTORY, NULL}, +#endif +#if defined(USE_LUA) && defined(USE_WEBSOCKET) + {"lua_websocket_pattern", CONFIG_TYPE_EXT_PATTERN, "**.lua$"}, +#endif + {"access_control_allow_origin", CONFIG_TYPE_STRING, "*"}, + {"error_pages", CONFIG_TYPE_DIRECTORY, NULL}, + + {NULL, CONFIG_TYPE_UNKNOWN, NULL} +}; + +struct mg_request_handler_info { + char *uri; + size_t uri_len; + mg_request_handler handler; + + void *cbdata; + struct mg_request_handler_info *next; +}; + +struct mg_context { + volatile int stop_flag; /* Should we stop event loop */ + SSL_CTX *ssl_ctx; /* SSL context */ + char *config[NUM_OPTIONS]; /* Civetweb configuration parameters */ + struct mg_callbacks callbacks; /* User-defined callback function */ + void *user_data; /* User-defined data */ + int context_type; /* 1 = server context, 2 = client context */ + + struct socket *listening_sockets; + in_port_t *listening_ports; + int num_listening_sockets; + + volatile int num_threads; /* Number of threads */ + pthread_mutex_t thread_mutex; /* Protects (max|num)_threads */ + pthread_cond_t thread_cond; /* Condvar for tracking workers terminations */ + + struct socket queue[MGSQLEN]; /* 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 */ + pthread_t masterthreadid; /* The master thread ID */ + int workerthreadcount; /* The amount of worker threads. */ + pthread_t *workerthreadids; /* The worker thread IDs */ + + unsigned long start_time; /* Server start time, used for authentication */ + pthread_mutex_t nonce_mutex; /* Protects nonce_count */ + unsigned long nonce_count; /* Used nonces, used for authentication */ + + char *systemName; /* What operating system is running */ + + /* linked list of uri handlers */ + struct mg_request_handler_info *request_handlers; + +#if defined(USE_LUA) && defined(USE_WEBSOCKET) + /* linked list of shared lua websockets */ + struct mg_shared_lua_websocket_list *shared_lua_websockets; +#endif + +#ifdef USE_TIMERS + struct ttimers * timers; +#endif +}; + +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 in_error_handler; /* 1 if in handler for user defined error pages */ + 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 */ + pthread_mutex_t mutex; /* Used by mg_lock_connection/mg_unlock_connection to ensure atomic transmissions for websockets */ +#if defined(USE_LUA) && defined(USE_WEBSOCKET) + void * lua_websocket_state; /* Lua_State for a websocket connection */ +#endif +}; + +static pthread_key_t sTlsKey; /* Thread local storage index */ +static int sTlsInit = 0; + +struct mg_workerTLS { + int is_master; +#if defined(_WIN32) && !defined(__SYMBIAN32__) + HANDLE pthread_cond_helper_mutex; +#endif +}; + +/* Directory entry */ +struct de { + struct mg_connection *conn; + char *file_name; + struct file file; +}; + +#if defined(USE_WEBSOCKET) +static int is_websocket_protocol(const struct mg_connection *conn); +#else +#define is_websocket_protocol(conn) (0) +#endif + +int mg_atomic_inc(volatile int * addr) +{ + int ret; +#if defined(_WIN32) && !defined(__SYMBIAN32__) + ret = InterlockedIncrement((volatile unsigned int *) addr); +#elif defined(__GNUC__) + ret = __sync_add_and_fetch(addr, 1); +#else + ret = (++(*addr)); +#endif + return ret; +} + +int mg_atomic_dec(volatile int * addr) +{ + int ret; +#if defined(_WIN32) && !defined(__SYMBIAN32__) + ret = InterlockedDecrement((volatile unsigned int *) addr); +#elif defined(__GNUC__) + ret = __sync_sub_and_fetch(addr, 1); +#else + ret = (--(*addr)); +#endif + return ret; +} + +#if !defined(NO_THREAD_NAME) +#if defined(_WIN32) && defined(_MSC_VER) +/* Set the thread name for debugging purposes in Visual Studio + http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx +*/ +#pragma pack(push,8) +typedef struct tagTHREADNAME_INFO +{ + DWORD dwType; /* Must be 0x1000. */ + LPCSTR szName; /* Pointer to name (in user addr space). */ + DWORD dwThreadID; /* Thread ID (-1=caller thread). */ + DWORD dwFlags; /* Reserved for future use, must be zero. */ +} THREADNAME_INFO; +#pragma pack(pop) +#elif defined(__linux__) +#include +#endif + +void mg_set_thread_name(const char* name) +{ + char threadName[16]; /* Max. thread length in Linux/OSX/.. */ + + if (snprintf(threadName, sizeof(threadName), "civetweb-%s", name)<0) return; + threadName[sizeof(threadName)-1] = 0; + +#if defined(_WIN32) +#if defined(_MSC_VER) + /* Windows and Visual Studio Compiler */ + __try + { + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = threadName; + info.dwThreadID = -1; + info.dwFlags = 0; + + RaiseException(0x406D1388, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } +#elif defined(__MINGW32__) + /* No option known to set thread name for MinGW */ +#endif +#elif defined(__linux__) + /* Linux */ + (void)prctl(PR_SET_NAME,threadName,0,0,0); +#elif defined(__APPLE__) || defined(__MACH__) + /* OS X (TODO: test) */ + (void)pthread_setname_np(threadName); +#elif defined(BSD) || defined(__FreeBSD__) || defined(__OpenBSD__) + /* BSD (TODO: test) */ + pthread_set_name_np(pthread_self(), threadName); +#else + /* POSIX */ + (void)pthread_setname_np(pthread_self(), threadName); +#endif +} +#else /* !defined(NO_THREAD_NAME) */ +void mg_set_thread_name(const char* threadName) {} +#endif + +#if defined(MG_LEGACY_INTERFACE) +const char **mg_get_valid_option_names(void) +{ + static const char * data[2 * sizeof(config_options) / sizeof(config_options[0])] = {0}; + int i; + + for (i=0; config_options[i].name != NULL; i++) { + data[i * 2] = config_options[i].name; + data[i * 2 + 1] = config_options[i].default_value; + } + + return data; +} +#endif + +const struct mg_option *mg_get_valid_options(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); +#endif + } + + return is_file_opened(filep); +} + +static void mg_fclose(struct file *filep) +{ + if (filep != NULL && filep->fp != NULL) { + fclose(filep->fp); + } +} + +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); +} + +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 *) mg_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 = (int)strlen(big_str), small_len = (int)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) { + mg_cry(conn, "vsnprintf error"); + n = 0; + } else if (n >= (int) buflen) { + mg_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; +} + +static int get_option_index(const char *name) +{ + int i; + + for (i = 0; config_options[i].name != NULL; i++) { + if (strcmp(config_options[i].name, 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]; + } +} + +struct mg_context *mg_get_context(struct mg_connection * conn) +{ + return (conn == NULL) ? (struct mg_context *)NULL : (conn->ctx); +} + +void *mg_get_user_data(struct mg_context *ctx) +{ + return (ctx == NULL) ? NULL : ctx->user_data; +} + +size_t mg_get_ports(const struct mg_context *ctx, size_t size, int* ports, int* ssl) +{ + size_t i; + for (i = 0; i < size && i < (size_t)ctx->num_listening_sockets; i++) + { + ssl[i] = ctx->listening_sockets[i].is_ssl; + ports[i] = ctx->listening_ports[i]; + } + return 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 Windows Vista (and newer) have inet_ntop() */ + mg_strlcpy(buf, inet_ntoa(usa->sin.sin_addr), len); +#else + inet_ntop(usa->sa.sa_family, (void *) &usa->sin.sin_addr, buf, len); +#endif +} + +/* Convert time_t to a string. According to RFC2616, Sec 14.18, this must be included in all responses other than 100, 101, 5xx. */ +static void gmt_time_string(char *buf, size_t buf_len, time_t *t) +{ + struct tm *tm; + + tm = gmtime(t); + if (tm != NULL) { + strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", tm); + } else { + mg_strlcpy(buf, "Thu, 01 Jan 1970 00:00:00 GMT", buf_len); + buf[buf_len - 1] = '\0'; + } +} + +/* Print error message to the opened error log stream. */ +void mg_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); + IGNORE_UNUSED_RESULT(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->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); + } + } +} + +/* 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; +} + +const char *mg_version(void) +{ + return CIVETWEB_VERSION; +} + +struct mg_request_info *mg_get_request_info(struct mg_connection *conn) +{ + return &conn->request_info; +} + +/* 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) { + /* TODO (bel): it seems this code is never reached, so quotechar is actually + not needed - check if this code may be droped */ + + /* 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); +} + +/* 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 + of the list found. + Value is stored in val vector. If value has form "x=y", then eq_val + 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; + } + + 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; +} + +/* Perform case-insensitive match of string against pattern */ +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, (int)(or_str - pattern), str); + return res > 0 ? res : + match_prefix(or_str + 1, (int)((pattern + pattern_len) - (or_str + 1)), str); + } + + i = j = 0; + 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 (lowercase(&pattern[i]) != lowercase(&str[j])) { + return -1; + } + } + return j; +} + +/* 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 && 0!=strcmp(http_version, "1.1"))) { + return 0; + } + return 1; +} + +static int should_decode_url(const struct mg_connection *conn) +{ + return (mg_strcasecmp(conn->ctx->config[DECODE_URL], "yes") == 0); +} + +static const char *suggest_connection_header(const struct mg_connection *conn) +{ + return should_keep_alive(conn) ? "keep-alive" : "close"; +} + +static void handle_file_based_request(struct mg_connection *conn, const char *path, struct file *filep); +static int mg_stat(struct mg_connection *conn, const char *path, struct file *filep); + +static const char *mg_get_response_code_text(int response_code, struct mg_connection *conn) +{ + switch (response_code) + { + /* RFC2616 Section 10.1 - Informational 1xx */ + case 100: return "Continue"; /* RFC2616 Section 10.1.1 */ + case 101: return "Switching Protocols"; /* RFC2616 Section 10.1.2 */ + case 102: return "Processing"; /* RFC2518 Section 10.1 */ + + /* RFC2616 Section 10.2 - Successful 2xx */ + case 200: return "OK"; /* RFC2616 Section 10.2.1 */ + case 201: return "Created"; /* RFC2616 Section 10.2.2 */ + case 202: return "Accepted"; /* RFC2616 Section 10.2.3 */ + case 203: return "Non-Authoritative Information"; /* RFC2616 Section 10.2.4 */ + case 204: return "No Content"; /* RFC2616 Section 10.2.5 */ + case 205: return "Reset Content"; /* RFC2616 Section 10.2.6 */ + case 206: return "Partial Content"; /* RFC2616 Section 10.2.7 */ + case 207: return "Multi-Status"; /* RFC2518 Section 10.2, RFC4918 Section 11.1 */ + + /* RFC2616 Section 10.3 - Redirection 3xx */ + case 300: return "Multiple Choices"; /* RFC2616 Section 10.3.1 */ + case 301: return "Moved Permanently"; /* RFC2616 Section 10.3.2 */ + case 302: return "Found"; /* RFC2616 Section 10.3.3 */ + case 303: return "See Other"; /* RFC2616 Section 10.3.4 */ + case 304: return "Not Modified"; /* RFC2616 Section 10.3.5 */ + case 305: return "Use Proxy"; /* RFC2616 Section 10.3.6 */ + case 307: return "Temporary Redirect"; /* RFC2616 Section 10.3.8 */ + + /* RFC2616 Section 10.4 - Client Error 4xx */ + case 400: return "Bad Request"; /* RFC2616 Section 10.4.1 */ + case 401: return "Unauthorized"; /* RFC2616 Section 10.4.2 */ + case 402: return "Payment Required"; /* RFC2616 Section 10.4.3 */ + case 403: return "Forbidden"; /* RFC2616 Section 10.4.4 */ + case 404: return "Not Found"; /* RFC2616 Section 10.4.5 */ + case 405: return "Method Not Allowed"; /* RFC2616 Section 10.4.6 */ + case 406: return "Not Acceptable"; /* RFC2616 Section 10.4.7 */ + case 407: return "Proxy Authentication Required"; /* RFC2616 Section 10.4.8 */ + case 408: return "Request Time-out"; /* RFC2616 Section 10.4.9 */ + case 409: return "Conflict"; /* RFC2616 Section 10.4.10 */ + case 410: return "Gone"; /* RFC2616 Section 10.4.11 */ + case 411: return "Length Required"; /* RFC2616 Section 10.4.12 */ + case 412: return "Precondition Failed"; /* RFC2616 Section 10.4.13 */ + case 413: return "Request Entity Too Large"; /* RFC2616 Section 10.4.14 */ + case 414: return "Request-URI Too Large"; /* RFC2616 Section 10.4.15 */ + case 415: return "Unsupported Media Type"; /* RFC2616 Section 10.4.16 */ + case 416: return "Requested range not satisfiable"; /* RFC2616 Section 10.4.17 */ + case 417: return "Expectation Failed"; /* RFC2616 Section 10.4.18 */ + case 422: return "Unproccessable entity"; /* RFC2518 Section 10.3, RFC4918 Section 11.2 */ + case 423: return "Locked"; /* RFC2518 Section 10.4, RFC4918 Section 11.3 */ + case 424: return "Failed Dependency"; /* RFC2518 Section 10.5, RFC4918 Section 11.4 */ + case 428: return "Precondition Required"; /* RFC 6585, Section 3 */ + case 429: return "Too Many Requests"; /* RFC 6585, Section 4 */ + case 431: return "Request Header Fields Too Large"; /* RFC 6585, Section 5 */ + + /* RFC2616 Section 10.5 - Server Error 5xx */ + case 500: return "Internal Server Error"; /* RFC2616 Section 10.5.1 */ + case 501: return "Not Implemented"; /* RFC2616 Section 10.5.2 */ + case 502: return "Bad Gateway"; /* RFC2616 Section 10.5.3 */ + case 503: return "Service Unavailable"; /* RFC2616 Section 10.5.4 */ + case 504: return "Gateway Time-out"; /* RFC2616 Section 10.5.5 */ + case 505: return "HTTP Version not supported"; /* RFC2616 Section 10.5.6 */ + case 507: return "Insufficient Storage"; /* RFC2518 Section 10.6, , RFC4918 Section 11.5 */ + case 511: return "Network Authentication Required"; /* RFC 6585, Section 6 */ + + /* Other RFCs */ + case 426: return "Upgrade Required"; /* RFC 2817 */ + + /* Return codes from non normative RFCs: */ + /* Informative and experimental RFCs, "de facto" standards due to common use, ... */ + case 208: return "Already Reported"; /* RFC5842 Section 7.1 */ + case 226: return "IM used"; /* RFC3229 Section 10.4.1 */ + case 308: return "Permanent Redirect"; /* RFC7238 Section 3 */ + case 418: return "I am a teapot"; /* RFC2324 Section 2.3.2 */ + case 419: return "Authentication Timeout"; /* common use */ + case 451: return "Unavailable For Legal Reasons"; /* draft-tbray-http-legally-restricted-status-05, Section 3 */ + case 506: return "Variant Also Negotiates"; /* RFC 2295, Section 8.1 */ + case 508: return "Loop Detected"; /* RFC5842 Section 7.1 */ + case 510: return "Not Extended"; /* RFC 2774, Section 7 */ + + default: + /* This error code is unknown. This should not happen. */ + if (conn) { + mg_cry(conn, "Unknown HTTP response code: %u", response_code); + } + + /* Return at least a category according to RFC 2616 Section 10. */ + if (response_code>=100 && response_code<200) { + /* Unknown informational status code */ + return "Information"; + } + if (response_code>=200 && response_code<300) { + /* Unknown success code */ + return "Success"; + } + if (response_code>=300 && response_code<400) { + /* Unknown redirection code */ + return "Redirection"; + } + if (response_code>=400 && response_code<500) { + /* Unknown request error code */ + return "Client Error"; + } + if (response_code>=500 && response_code<600) { + /* Unknown server error code */ + return "Server Error"; + } + + /* Response code not even within reasonable range */ + return ""; + } +} + + +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, i, page_handler_found, scope; + char date[64]; + time_t curtime = time(NULL); + const char *error_handler = NULL; + struct file error_page_file = STRUCT_FILE_INITIALIZER; + const char *error_page_file_ext, *tstr; + + if (!reason) { + reason = mg_get_response_code_text(status, conn); + } + + conn->status_code = status; + if (conn->in_error_handler || + conn->ctx->callbacks.http_error == NULL || + conn->ctx->callbacks.http_error(conn, status)) { + + if (!conn->in_error_handler) { + /* Send user defined error pages, if defined */ + error_handler = conn->ctx->config[ERROR_PAGES]; + error_page_file_ext = conn->ctx->config[INDEX_FILES]; + page_handler_found = 0; + if (error_handler != NULL) { + for (scope=1; (scope<=3) && !page_handler_found; scope++) { + switch (scope) { + case 1: + len = mg_snprintf(conn, buf, sizeof(buf)-32, "%serror%03u.", error_handler, status); + break; + case 2: + len = mg_snprintf(conn, buf, sizeof(buf)-32, "%serror%01uxx.", error_handler, status/100); + break; + default: + len = mg_snprintf(conn, buf, sizeof(buf)-32, "%serror.", error_handler); + break; + } + tstr = strchr(error_page_file_ext, '.'); + while (tstr) { + for (i=1; i<32 && tstr[i]!=0 && tstr[i]!=','; i++) buf[len+i-1]=tstr[i]; + buf[len+i-1]=0; + if (mg_stat(conn, buf, &error_page_file)) { + page_handler_found = 1; + break; + } + tstr = strchr(tstr+i, '.'); + } + } + } + + if (page_handler_found) { + conn->in_error_handler = 1; + handle_file_based_request(conn, buf, &error_page_file); + conn->in_error_handler = 0; + return; + } + } + + buf[0] = '\0'; + gmt_time_string(date, sizeof(date), &curtime); + + /* Errors 1xx, 204 and 304 MUST NOT send a body */ + if (status > 199 && status != 204 && status != 304) { + len = mg_snprintf(conn, buf, sizeof(buf)-1, "Error %d: %s", status, reason); + buf[len] = '\n'; + len++; + buf[len] = 0; + + 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" + "Date: %s\r\n" + "Connection: %s\r\n\r\n", + status, reason, len, date, + 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_trylock(pthread_mutex_t *mutex) +{ + switch (WaitForSingleObject(*mutex, 0)) { + case WAIT_OBJECT_0: + return 0; + case WAIT_TIMEOUT: + return -2; /* EBUSY */ + } + return -1; +} + +static int pthread_mutex_unlock(pthread_mutex_t *mutex) +{ + return ReleaseMutex(*mutex) == 0 ? -1 : 0; +} + +#ifndef WIN_PTHREADS_TIME_H +static int clock_gettime(clockid_t clk_id, struct timespec *tp) +{ + FILETIME ft; + ULARGE_INTEGER li; + BOOL ok = FALSE; + double d; + static double perfcnt_per_sec = 0.0; + + if (tp) { + if (clk_id == CLOCK_REALTIME) { + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + li.QuadPart -= 116444736000000000; /* 1.1.1970 in filedate */ + tp->tv_sec = (time_t)(li.QuadPart / 10000000); + tp->tv_nsec = (long)(li.QuadPart % 10000000) * 100; + ok = TRUE; + } else if (clk_id == CLOCK_MONOTONIC) { + if (perfcnt_per_sec == 0.0) { + QueryPerformanceFrequency((LARGE_INTEGER *) &li); + perfcnt_per_sec = 1.0 / li.QuadPart; + } + if (perfcnt_per_sec != 0.0) { + QueryPerformanceCounter((LARGE_INTEGER *) &li); + d = li.QuadPart * perfcnt_per_sec; + tp->tv_sec = (time_t)d; + d -= tp->tv_sec; + tp->tv_nsec = (long)(d*1.0E9); + ok = TRUE; + } + } + } + + return ok ? 0 : -1; +} +#endif + +static int pthread_cond_init(pthread_cond_t *cv, const void *unused) +{ + (void) unused; + InitializeCriticalSection(&cv->threadIdSec); + cv->waitingthreadcount = 0; + cv->waitingthreadhdls = (pthread_t *) mg_calloc(MAX_WORKER_THREADS, sizeof(pthread_t)); + return (cv->waitingthreadhdls!=NULL) ? 0 : -1; +} + +static int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mutex, const struct timespec * abstime) +{ + struct mg_workerTLS * tls = (struct mg_workerTLS *)TlsGetValue(sTlsKey); + int ok; + struct timespec tsnow; + int64_t nsnow, nswaitabs, nswaitrel; + DWORD mswaitrel; + + EnterCriticalSection(&cv->threadIdSec); + assert(cv->waitingthreadcount < MAX_WORKER_THREADS); + cv->waitingthreadhdls[cv->waitingthreadcount] = tls->pthread_cond_helper_mutex; + cv->waitingthreadcount++; + LeaveCriticalSection(&cv->threadIdSec); + + if (abstime) { + clock_gettime(CLOCK_REALTIME, &tsnow); + nsnow = (((uint64_t)tsnow.tv_sec)*1000000000) + tsnow.tv_nsec; + nswaitabs = (((uint64_t)abstime->tv_sec)*1000000000) + abstime->tv_nsec; + nswaitrel = nswaitabs - nsnow; + if (nswaitrel<0) nswaitrel=0; + mswaitrel = (DWORD)(nswaitrel / 1000000); + } else { + mswaitrel = INFINITE; + } + + pthread_mutex_unlock(mutex); + ok = (WAIT_OBJECT_0 == WaitForSingleObject(tls->pthread_cond_helper_mutex, mswaitrel)); + pthread_mutex_lock(mutex); + + return ok ? 0 : -1; +} + +static int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) +{ + return pthread_cond_timedwait(cv, mutex, NULL); +} + +static int pthread_cond_signal(pthread_cond_t *cv) +{ + int i; + HANDLE wkup = NULL; + BOOL ok = FALSE; + + EnterCriticalSection(&cv->threadIdSec); + if (cv->waitingthreadcount) { + wkup = cv->waitingthreadhdls[0]; + ok = SetEvent(wkup); + + for (i=1; iwaitingthreadcount; i++) { + cv->waitingthreadhdls[i-1] = cv->waitingthreadhdls[i]; + } + cv->waitingthreadcount--; + + assert(ok); + } + LeaveCriticalSection(&cv->threadIdSec); + + return ok ? 0 : 1; +} + +static int pthread_cond_broadcast(pthread_cond_t *cv) +{ + EnterCriticalSection(&cv->threadIdSec); + while (cv->waitingthreadcount) { + pthread_cond_signal(cv); + } + LeaveCriticalSection(&cv->threadIdSec); + + return 0; +} + +static int pthread_cond_destroy(pthread_cond_t *cv) +{ + EnterCriticalSection(&cv->threadIdSec); + assert(cv->waitingthreadcount==0); + mg_free(cv->waitingthreadhdls); + cv->waitingthreadhdls = 0; + LeaveCriticalSection(&cv->threadIdSec); + DeleteCriticalSection(&cv->threadIdSec); + + return 0; +} + +/* 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 *) mg_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 { + mg_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; + + mg_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; +} + +#ifndef HAVE_POLL +static int poll(struct pollfd *pfd, int n, int milliseconds) +{ + struct timeval tv; + fd_set set; + int i, result; + SOCKET 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((int)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 */ + +static void set_close_on_exec(SOCKET sock, struct mg_connection *conn /* may be null */) +{ + (void) conn; /* Unused. */ + (void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0); +} + +int mg_start_thread(mg_thread_func_t f, void *p) +{ +#if defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) + /* Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384 */ + return ((_beginthread((void (__cdecl *)(void *)) f, USE_STACK_SIZE, p) == ((uintptr_t)(-1L))) ? -1 : 0); +#else + return ((_beginthread((void (__cdecl *)(void *)) f, 0, p) == ((uintptr_t)(-1L))) ? -1 : 0); +#endif /* defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) */ +} + +/* Start a thread storing the thread context. */ + +static int mg_start_thread_with_id(unsigned (__stdcall *f)(void *), void *p, + pthread_t *threadidptr) +{ + uintptr_t uip; + HANDLE threadhandle; + int result = -1; + + uip = _beginthreadex(NULL, 0, (unsigned (__stdcall *)(void *)) f, p, 0, + NULL); + threadhandle = (HANDLE) uip; + if ((uip != (uintptr_t)(-1L)) && (threadidptr != NULL)) { + *threadidptr = threadhandle; + result = 0; + } + + return result; +} + +/* Wait for a thread to finish. */ + +static int mg_join_thread(pthread_t threadid) +{ + int result; + DWORD dwevent; + + result = -1; + dwevent = WaitForSingleObject(threadid, INFINITE); + if (dwevent == WAIT_FAILED) { + int err; + + err = GetLastError(); + DEBUG_TRACE("WaitForSingleObject() failed, error %d", err); + } else { + if (dwevent == WAIT_OBJECT_0) { + CloseHandle(threadid); + result = 0; + } + } + + return result; +} + +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); +} + +static int dlclose(void *handle) +{ + int result; + + if (FreeLibrary((HMODULE) handle) != 0) { + result = 0; + } else { + result = -1; + } + + return result; +} + +#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 fdin, + int fdout, 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(fdin), me, + &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS); + DuplicateHandle(me, (HANDLE) _get_osfhandle(fdout), 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) { + mg_cry(conn, "%s: CreateProcess(%s): %ld", + __func__, cmdline, ERRNO); + pi.hProcess = (pid_t) -1; + } + + (void) CloseHandle(si.hStdOutput); + (void) CloseHandle(si.hStdInput); + if (pi.hThread != NULL) + (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, struct mg_connection *conn /* may be null */) +{ + if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) { + if (conn) { + mg_cry(conn, "%s: fcntl(F_SETFD FD_CLOEXEC) failed: %s", __func__, strerror(ERRNO)); + } + } +} + +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); + +#if defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) + /* Compile-time option to control stack size, + e.g. -DUSE_STACK_SIZE=16384 */ + (void) pthread_attr_setstacksize(&attr, USE_STACK_SIZE); +#endif /* defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) */ + + result = pthread_create(&thread_id, &attr, func, param); + pthread_attr_destroy(&attr); + + return result; +} + +/* Start a thread storing the thread context. */ + +static int mg_start_thread_with_id(mg_thread_func_t func, void *param, pthread_t *threadidptr) +{ + pthread_t thread_id; + pthread_attr_t attr; + int result; + + (void) pthread_attr_init(&attr); + +#if defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) + /* Compile-time option to control stack size, + e.g. -DUSE_STACK_SIZE=16384 */ + (void) pthread_attr_setstacksize(&attr, USE_STACK_SIZE); +#endif /* defined(USE_STACK_SIZE) && USE_STACK_SIZE > 1 */ + + result = pthread_create(&thread_id, &attr, func, param); + pthread_attr_destroy(&attr); + if ((result == 0) && (threadidptr != NULL)) { + *threadidptr = thread_id; + } + return result; +} + +/* Wait for a thread to finish. */ + +static int mg_join_thread(pthread_t threadid) +{ + int result; + + result = pthread_join(threadid, NULL); + return result; +} + +#ifndef NO_CGI +static pid_t spawn_process(struct mg_connection *conn, const char *prog, + char *envblk, char *envp[], int fdin, + int fdout, const char *dir) +{ + pid_t pid; + const char *interp; + + (void) envblk; + + if ((pid = fork()) == -1) { + /* Parent */ + send_http_error(conn, 500, NULL, + "Error: Creating CGI process\nfork(): %s", strerror(ERRNO)); + } else if (pid == 0) { + /* Child */ + if (chdir(dir) != 0) { + mg_cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO)); + } else if (dup2(fdin, 0) == -1) { + mg_cry(conn, "%s: dup2(%d, 0): %s", __func__, fdin, strerror(ERRNO)); + } else if (dup2(fdout, 1) == -1) { + mg_cry(conn, "%s: dup2(%d, 1): %s", __func__, fdout, strerror(ERRNO)); + } else { + /* Not redirecting stderr to stdout, to avoid output being littered + with the error messages. */ + (void) close(fdin); + (void) close(fdout); + + /* 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); + mg_cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO)); + } else { + (void) execle(interp, interp, prog, NULL, envp); + mg_cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog, + strerror(ERRNO)); + } + } + exit(EXIT_FAILURE); + } + + 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 */ + +/* 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 && conn->ctx->stop_flag == 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) +{ + int64_t n, buffered_len, nread; + int64_t len64 = (int64_t)(len > INT_MAX ? INT_MAX : len); /* since the return value is int, we may not read more bytes */ + const char *body; + + /* If Content-Length is not set for a PUT or POST request, read until socket is closed */ + if (conn->consumed_content == 0 && conn->content_len == -1) { + 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 left_to_read = conn->content_len - conn->consumed_content; + if (left_to_read < len64) { + /* Do not reade more than the total content length of the request. */ + len64 = left_to_read; + } + + /* Return buffered data */ + buffered_len = (int64_t)(conn->data_len) - (int64_t)conn->request_len - conn->consumed_content; + if (buffered_len > 0) { + if (len64 < buffered_len) { + buffered_len = len64; + } + body = conn->buf + conn->request_len + conn->consumed_content; + memcpy(buf, body, (size_t) buffered_len); + len64 -= 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. */ + if ((n = pull_all(NULL, conn, (char *) buf, (int)len64)) >= 0) { + nread += n; + } else { + nread = (nread > 0 ? nread : n); + } + } + return (int)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; +} + +/* Alternative alloc_vprintf() for non-compliant C runtimes */ +static int alloc_vprintf2(char **buf, const char *fmt, va_list ap) +{ + va_list ap_copy; + int size = MG_BUF_LEN; + int len = -1; + + *buf = NULL; + while (len == -1) { + if (*buf) mg_free(*buf); + *buf = (char *)mg_malloc(size *= 4); + if (!*buf) break; + va_copy(ap_copy, ap); + len = vsnprintf(*buf, size, fmt, ap_copy); + va_end(ap_copy); + } + + return len; +} + +/* 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); + va_end(ap_copy); + + if (len < 0) { + /* C runtime is not standard compliant, vsnprintf() returned -1. + Switch to alternative code path that uses incremental allocations. + */ + va_copy(ap_copy, ap); + len = alloc_vprintf2(buf, fmt, ap); + va_end(ap_copy); + } else if (len > (int) size && + (size = len + 1) > 0 && + (*buf = (char *) mg_malloc(size)) == NULL) { + len = -1; /* Allocation failed, mark failure */ + } else { + va_copy(ap_copy, ap); + IGNORE_UNUSED_RESULT(vsnprintf(*buf, size, fmt, ap_copy)); + va_end(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) { + mg_free(buf); + } + + return len; +} + +int mg_printf(struct mg_connection *conn, const char *fmt, ...) +{ + va_list ap; + int result; + + va_start(ap, fmt); + result = mg_vprintf(conn, fmt, ap); + va_end(ap); + + return result; +} + +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 (i < src_len - 2 && src[i] == '%' && + 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) +{ + return mg_get_var2(data,data_len,name,dst,dst_len,0); +} + +int mg_get_var2(const char *data, size_t data_len, const char *name, + char *dst, size_t dst_len, size_t occurrence) +{ + 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) && 0 == occurrence--) { + + /* 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, (int)(s - p), dst, (int)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] == '=')