mirror of
https://github.com/lammertb/libhttp.git
synced 2026-01-27 08:02:47 +03:00
1786 lines
48 KiB
C
1786 lines
48 KiB
C
/*
|
|
* Copyright (c) 2016 Lammert Bies
|
|
* Copyright (c) 2013-2016 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.
|
|
*/
|
|
|
|
|
|
|
|
#include "libhttp-private.h"
|
|
|
|
#ifdef __MACH__
|
|
|
|
/* clock_gettime is not implemented on OSX prior to 10.12 */
|
|
int _civet_clock_gettime(int clk_id, struct timespec *t);
|
|
|
|
int _civet_clock_gettime(int clk_id, struct timespec *t) {
|
|
|
|
memset(t, 0, sizeof(*t));
|
|
if (clk_id == CLOCK_REALTIME) {
|
|
struct timeval now;
|
|
int rv = gettimeofday(&now, NULL);
|
|
if (rv) return rv;
|
|
t->tv_sec = now.tv_sec;
|
|
t->tv_nsec = now.tv_usec * 1000;
|
|
return 0;
|
|
|
|
} else if (clk_id == CLOCK_MONOTONIC) {
|
|
static uint64_t clock_start_time = 0;
|
|
static mach_timebase_info_data_t timebase_ifo = {0, 0};
|
|
|
|
uint64_t now = mach_absolute_time();
|
|
|
|
if (clock_start_time == 0) {
|
|
kern_return_t mach_status = mach_timebase_info(&timebase_ifo);
|
|
#if defined(DEBUG)
|
|
assert(mach_status == KERN_SUCCESS);
|
|
#else /* DEBUG */
|
|
/* appease "unused variable" warning for release builds */
|
|
(void)mach_status;
|
|
#endif /* DEBUG */
|
|
clock_start_time = now;
|
|
}
|
|
|
|
now = (uint64_t)((double)(now - clock_start_time) * (double)timebase_ifo.numer / (double)timebase_ifo.denom);
|
|
|
|
t->tv_sec = now / 1000000000;
|
|
t->tv_nsec = now % 1000000000;
|
|
return 0;
|
|
}
|
|
return -1; /* EINVAL - Clock ID is unknown */
|
|
|
|
} /* _civet_clock_gettime */
|
|
|
|
/* if clock_gettime is declared, then __CLOCK_AVAILABILITY will be defined */
|
|
#ifdef __CLOCK_AVAILABILITY
|
|
/* If we compiled with Mac OSX 10.12 or later, then clock_gettime will be
|
|
* declared
|
|
* but it may be NULL at runtime. So we need to check before using it. */
|
|
int _civet_safe_clock_gettime(int clk_id, struct timespec *t);
|
|
|
|
int _civet_safe_clock_gettime(int clk_id, struct timespec *t) {
|
|
|
|
if (clock_gettime) return clock_gettime(clk_id, t);
|
|
return _civet_clock_gettime(clk_id, t);
|
|
|
|
} /* _civet_safe_clock_gettime */
|
|
|
|
#define clock_gettime _civet_safe_clock_gettime
|
|
#else /* __CLOCK_AVAILABILITY */
|
|
#define clock_gettime _civet_clock_gettime
|
|
#endif /* __CLOCK_AVAILABILITY */
|
|
|
|
#endif /* __MACH__ */
|
|
|
|
|
|
mg_static_assert(MAX_WORKER_THREADS >= 1, "worker threads must be a positive number");
|
|
|
|
mg_static_assert(sizeof(size_t) == 4 || sizeof(size_t) == 8, "size_t data type size check");
|
|
|
|
/* va_copy should always be a macro, C99 and C++11 - DTL */
|
|
#ifndef va_copy
|
|
#define va_copy(x, y) ((x) = (y))
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
/* Create substitutes for POSIX functions in Win32. */
|
|
|
|
#if defined(__MINGW32__)
|
|
/* Show no warning in case system functions are not used. */
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunused-function"
|
|
#endif /* __MINGW32__ */
|
|
|
|
|
|
static CRITICAL_SECTION global_log_file_lock;
|
|
|
|
static DWORD pthread_self(void) {
|
|
|
|
return GetCurrentThreadId();
|
|
}
|
|
|
|
|
|
static int pthread_key_create( pthread_key_t *key, void (*_ignored)(void *)) {
|
|
|
|
(void)_ignored;
|
|
|
|
if ((key != 0)) {
|
|
*key = TlsAlloc();
|
|
return (*key != TLS_OUT_OF_INDEXES) ? 0 : -1;
|
|
}
|
|
return -2;
|
|
|
|
} /* pthread_key_create */
|
|
|
|
|
|
static int pthread_key_delete(pthread_key_t key) {
|
|
|
|
return TlsFree(key) ? 0 : 1;
|
|
|
|
} /* pthread_key_delete */
|
|
|
|
|
|
static int pthread_setspecific(pthread_key_t key, void *value) {
|
|
|
|
return TlsSetValue(key, value) ? 0 : 1;
|
|
|
|
} /* pthread_setspecific */
|
|
|
|
|
|
static void * pthread_getspecific(pthread_key_t key) {
|
|
|
|
return TlsGetValue(key);
|
|
|
|
} /* pthread_getspecific */
|
|
|
|
#if defined(__MINGW32__)
|
|
/* Enable unused function warning again */
|
|
#pragma GCC diagnostic pop
|
|
#endif /* __MINGW32__ */
|
|
|
|
struct pthread_mutex_undefined_struct *XX_httplib_pthread_mutex_attr = NULL;
|
|
#else /* _WIN32 */
|
|
pthread_mutexattr_t XX_httplib_pthread_mutex_attr;
|
|
#endif /* _WIN32 */
|
|
|
|
|
|
#if !defined(DEBUG_TRACE)
|
|
#if defined(DEBUG)
|
|
|
|
|
|
#if defined(_WIN32_WCE)
|
|
/* Create substitutes for POSIX functions in Win32. */
|
|
|
|
#if defined(__MINGW32__)
|
|
/* Show no warning in case system functions are not used. */
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunused-function"
|
|
#endif /* __MINGW32__ */
|
|
|
|
|
|
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_s( const time_t *ptime, struct tm *ptm ) {
|
|
|
|
int64_t t = ((int64_t)*ptime) * RATE_DIFF + EPOCH_DIFF;
|
|
FILETIME ft;
|
|
FILETIME 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;
|
|
|
|
} /* localtime_s */
|
|
|
|
|
|
static struct tm * gmtime_s(const time_t *ptime, struct tm *ptm) {
|
|
/* FIXME(lsm): fix this. */
|
|
return localtime_s(ptime, ptm);
|
|
}
|
|
|
|
static struct tm tm_array[MAX_WORKER_THREADS];
|
|
static int tm_index = 0;
|
|
|
|
static struct tm * localtime( const time_t *ptime ) {
|
|
|
|
int i = XX_httplib_atomic_inc(&tm_index) % (sizeof(tm_array) / sizeof(tm_array[0]));
|
|
return localtime_s(ptime, tm_array + i);
|
|
|
|
} /* localtime */
|
|
|
|
|
|
static struct tm * gmtime(const time_t *ptime) {
|
|
|
|
int i = XX_httplib_atomic_inc(&tm_index) % ARRAY_SIZE(tm_array);
|
|
return gmtime_s(ptime, tm_array + i);
|
|
|
|
} /* strftime */
|
|
|
|
|
|
static size_t strftime( char *dst, size_t dst_size, const char *fmt, const struct tm *tm ) {
|
|
|
|
/* TODO */ //(void)XX_httplib_snprintf(NULL, dst, dst_size, "implement strftime()
|
|
// for WinCE");
|
|
return 0;
|
|
}
|
|
|
|
#define _beginthreadex(psec, stack, func, prm, flags, ptid) \
|
|
(uintptr_t) CreateThread(psec, stack, func, prm, flags, ptid)
|
|
|
|
#define remove(f) mg_remove(NULL, f)
|
|
|
|
static int rename(const char *a, const char *b) {
|
|
|
|
wchar_t wa[PATH_MAX];
|
|
wchar_t wb[PATH_MAX];
|
|
|
|
XX_httplib_path_to_unicode( NULL, a, wa, ARRAY_SIZE(wa) );
|
|
XX_httplib_path_to_unicode( NULL, b, wb, ARRAY_SIZE(wb) );
|
|
|
|
return MoveFileW( wa, wb ) ? 0 : -1;
|
|
|
|
} /* rename */
|
|
|
|
struct stat {
|
|
int64_t st_size;
|
|
time_t st_mtime;
|
|
};
|
|
|
|
static int stat(const char *name, struct stat *st) {
|
|
|
|
wchar_t wbuf[PATH_MAX];
|
|
WIN32_FILE_ATTRIBUTE_DATA attr;
|
|
time_t creation_time, write_time;
|
|
|
|
XX_httplib_path_to_unicode(NULL, name, wbuf, ARRAY_SIZE(wbuf));
|
|
memset(&attr, 0, sizeof(attr));
|
|
|
|
GetFileAttributesExW(wbuf, GetFileExInfoStandard, &attr);
|
|
st->st_size = (((int64_t)attr.nFileSizeHigh) << 32) + (int64_t)attr.nFileSizeLow;
|
|
|
|
write_time = SYS2UNIX_TIME( attr.ftLastWriteTime.dwLowDateTime, attr.ftLastWriteTime.dwHighDateTime );
|
|
creation_time = SYS2UNIX_TIME( attr.ftCreationTime.dwLowDateTime, attr.ftCreationTime.dwHighDateTime );
|
|
|
|
if ( creation_time > write_time ) st->st_mtime = creation_time;
|
|
else st->st_mtime = write_time;
|
|
|
|
return 0;
|
|
|
|
} /* stat */
|
|
|
|
#define access(x, a) 1 /* not required anyway */
|
|
|
|
/* WinCE-TODO: define stat, remove, rename, _rmdir, _lseeki64 */
|
|
#define EEXIST 1 /* TODO: See Windows error codes */
|
|
#define EACCES 2 /* TODO: See Windows error codes */
|
|
#define ENOENT 3 /* TODO: See Windows Error codes */
|
|
|
|
#if defined(__MINGW32__)
|
|
/* Enable unused function warning again */
|
|
#pragma GCC diagnostic pop
|
|
#endif /* __MINGW32__ */
|
|
|
|
#endif /* defined(_WIN32_WCE) */
|
|
|
|
#endif /* DEBUG */
|
|
#endif /* DEBUG_TRACE */
|
|
|
|
#if defined(MEMORY_DEBUGGING)
|
|
unsigned long mg_memory_debug_blockCount = 0;
|
|
unsigned long mg_memory_debug_totalMemUsed = 0;
|
|
|
|
|
|
void *XX_httplib_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;
|
|
mg_memory_debug_totalMemUsed += size;
|
|
mg_memory_debug_blockCount++;
|
|
memory = (void *)(((char *)data) + sizeof(size_t));
|
|
}
|
|
|
|
return memory;
|
|
|
|
} /* XX_httplib_malloc_ex */
|
|
|
|
|
|
void *XX_httplib_calloc_ex( size_t count, size_t size, const char *file, unsigned line ) {
|
|
|
|
void *data = XX_httplib_malloc_ex(size * count, file, line);
|
|
if ( data != NULL ) memset( data, 0, size * count );
|
|
|
|
return data;
|
|
|
|
} /* XX_httplib_calloc_ex */
|
|
|
|
|
|
void XX_httplib_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;
|
|
mg_memory_debug_totalMemUsed -= size;
|
|
mg_memory_debug_blockCount--;
|
|
|
|
free(data);
|
|
}
|
|
|
|
} /* XX_httplib_free_ex */
|
|
|
|
|
|
void *XX_httplib_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;
|
|
mg_memory_debug_totalMemUsed -= oldsize;
|
|
mg_memory_debug_totalMemUsed += newsize;
|
|
*(size_t *)data = newsize;
|
|
data = (void *)(((char *)data) + sizeof(size_t));
|
|
} else {
|
|
return _realloc;
|
|
}
|
|
} else {
|
|
data = XX_httplib_malloc_ex(newsize, file, line);
|
|
}
|
|
} else {
|
|
data = 0;
|
|
XX_httplib_free_ex(memory, file, line);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
|
|
#else /* MEMORY_DEBUGGING */
|
|
|
|
void * XX_httplib_malloc( size_t a ) {
|
|
|
|
return malloc(a);
|
|
|
|
} /* XX_httplib_malloc */
|
|
|
|
void *XX_httplib_calloc( size_t a, size_t b ) {
|
|
|
|
return calloc(a, b);
|
|
|
|
} /* XX_httplib_calloc */
|
|
|
|
void * XX_httplib_realloc(void *a, size_t b) {
|
|
|
|
return realloc(a, b);
|
|
|
|
} /* XX_httplib_realloc */
|
|
|
|
void XX_httplib_free( void *a ) {
|
|
|
|
free(a);
|
|
|
|
} /* XX_httplib_free */
|
|
|
|
#endif /* MEMORY_DEBUGGING */
|
|
|
|
|
|
|
|
/* 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
|
|
#ifdef snprintf
|
|
#undef snprintf
|
|
#endif
|
|
#ifdef vsnprintf
|
|
#undef vsnprintf
|
|
#endif
|
|
#define malloc DO_NOT_USE_THIS_FUNCTION__USE_httplib_malloc
|
|
#define calloc DO_NOT_USE_THIS_FUNCTION__USE_httplib_calloc
|
|
#define realloc DO_NOT_USE_THIS_FUNCTION__USE_XX_httplib_realloc
|
|
#define free DO_NOT_USE_THIS_FUNCTION__USE_httplib_free
|
|
#define snprintf DO_NOT_USE_THIS_FUNCTION__USE_httplib_snprintf
|
|
#ifdef _WIN32 /* vsnprintf must not be used in any system, * \ \ \ \
|
|
* but this define only works well for Windows. */
|
|
#define vsnprintf DO_NOT_USE_THIS_FUNCTION__USE_httplib_vsnprintf
|
|
#endif
|
|
|
|
/* 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
|
|
|
|
|
|
#if !defined(NO_SSL) && !defined(NO_SSL_DL)
|
|
|
|
/* XX_httplib_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. */
|
|
struct ssl_func XX_httplib_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},
|
|
{"SSL_CTX_load_verify_locations", NULL},
|
|
{"SSL_CTX_set_default_verify_paths", NULL},
|
|
{"SSL_CTX_set_verify_depth", NULL},
|
|
{"SSL_get_peer_certificate", NULL},
|
|
{"SSL_get_version", NULL},
|
|
{"SSL_get_current_cipher", NULL},
|
|
{"SSL_CIPHER_get_name", NULL},
|
|
{"SSL_CTX_check_private_key", NULL},
|
|
{"SSL_CTX_set_session_id_context", NULL},
|
|
{"SSL_CTX_ctrl", NULL},
|
|
{"SSL_CTX_set_cipher_list", NULL},
|
|
{NULL, NULL}};
|
|
|
|
|
|
/* Similar array as XX_httplib_ssl_sw. These functions could be located in different
|
|
* lib. */
|
|
struct ssl_func XX_httplib_crypto_sw[] = {{"CRYPTO_num_locks", NULL},
|
|
{"CRYPTO_set_locking_callback", NULL},
|
|
{"CRYPTO_set_id_callback", NULL},
|
|
{"ERR_get_error", NULL},
|
|
{"ERR_error_string", NULL},
|
|
{"ERR_remove_state", NULL},
|
|
{"ERR_free_strings", NULL},
|
|
{"ENGINE_cleanup", NULL},
|
|
{"CONF_modules_unload", NULL},
|
|
{"CRYPTO_cleanup_all_ex_data", NULL},
|
|
{"EVP_cleanup", NULL},
|
|
{"X509_free", NULL},
|
|
{"X509_get_subject_name", NULL},
|
|
{"X509_get_issuer_name", NULL},
|
|
{"X509_NAME_oneline", NULL},
|
|
{"X509_get_serialNumber", NULL},
|
|
{"i2c_ASN1_INTEGER", NULL},
|
|
{"EVP_get_digestbyname", NULL},
|
|
{"ASN1_digest", NULL},
|
|
{"i2d_X509", NULL},
|
|
{NULL, NULL}};
|
|
|
|
#endif /* !defined(NO_SSL) && !defined(NO_SSL_DL) */
|
|
|
|
|
|
|
|
|
|
|
|
/* Config option name, config types, default value */
|
|
struct mg_option XX_httplib_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, "index.xhtml,index.html,index.htm,index.cgi,index.shtml,index.php"},
|
|
{"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"},
|
|
{"ssl_verify_peer", CONFIG_TYPE_BOOLEAN, "no"},
|
|
{"ssl_ca_path", CONFIG_TYPE_DIRECTORY, NULL},
|
|
{"ssl_ca_file", CONFIG_TYPE_FILE, NULL},
|
|
{"ssl_verify_depth", CONFIG_TYPE_NUMBER, "9"},
|
|
{"ssl_default_verify_paths", CONFIG_TYPE_BOOLEAN, "yes"},
|
|
{"ssl_cipher_list", CONFIG_TYPE_STRING, NULL},
|
|
{"ssl_protocol_version", CONFIG_TYPE_NUMBER, "0"},
|
|
{"ssl_short_trust", CONFIG_TYPE_BOOLEAN, "no"},
|
|
#if defined(USE_WEBSOCKET)
|
|
{"websocket_timeout_ms", CONFIG_TYPE_NUMBER, "30000"},
|
|
#endif
|
|
{"decode_url", CONFIG_TYPE_BOOLEAN, "yes"},
|
|
|
|
#if defined(USE_WEBSOCKET)
|
|
{"websocket_root", CONFIG_TYPE_DIRECTORY, NULL},
|
|
#endif
|
|
{"access_control_allow_origin", CONFIG_TYPE_STRING, "*"},
|
|
{"error_pages", CONFIG_TYPE_DIRECTORY, NULL},
|
|
{"tcp_nodelay", CONFIG_TYPE_NUMBER, "0"},
|
|
#if !defined(NO_CACHING)
|
|
{"static_file_max_age", CONFIG_TYPE_NUMBER, "3600"},
|
|
#endif
|
|
#if defined(__linux__)
|
|
{"allow_sendfile_call", CONFIG_TYPE_BOOLEAN, "yes"},
|
|
#endif
|
|
|
|
{NULL, CONFIG_TYPE_UNKNOWN, NULL}};
|
|
|
|
/* Check if the XX_httplib_config_options and the corresponding enum have compatible
|
|
* sizes. */
|
|
mg_static_assert((sizeof(XX_httplib_config_options) / sizeof(XX_httplib_config_options[0]))
|
|
== (NUM_OPTIONS + 1),
|
|
"XX_httplib_config_options and enum not sync");
|
|
|
|
|
|
|
|
pthread_key_t XX_httplib_sTlsKey; /* Thread local storage index */
|
|
int XX_httplib_sTlsInit = 0;
|
|
int XX_httplib_thread_idx_max = 0;
|
|
|
|
|
|
|
|
|
|
|
|
const struct uriprot_tp XX_httplib_abs_uri_protocols[] = {{"http://", 7, 80},
|
|
{"https://", 8, 443},
|
|
{"ws://", 5, 80},
|
|
{"wss://", 6, 443},
|
|
{NULL, 0, 0}};
|
|
|
|
|
|
int XX_httplib_atomic_inc( volatile int *addr ) {
|
|
|
|
int ret;
|
|
#if defined(_WIN32)
|
|
/* Depending on the SDK, this function uses either
|
|
* (volatile unsigned int *) or (volatile LONG *),
|
|
* so whatever you use, the other SDK is likely to raise a warning. */
|
|
ret = InterlockedIncrement((volatile long *)addr);
|
|
#elif defined(__GNUC__) \
|
|
&& ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 0)))
|
|
ret = __sync_add_and_fetch(addr, 1);
|
|
#else
|
|
ret = (++(*addr));
|
|
#endif
|
|
return ret;
|
|
|
|
} /* XX_httplib_atomic_inc */
|
|
|
|
|
|
int XX_httplib_atomic_dec( volatile int *addr ) {
|
|
|
|
int ret;
|
|
#if defined(_WIN32)
|
|
/* Depending on the SDK, this function uses either
|
|
* (volatile unsigned int *) or (volatile LONG *),
|
|
* so whatever you use, the other SDK is likely to raise a warning. */
|
|
ret = InterlockedDecrement((volatile long *)addr);
|
|
#elif defined(__GNUC__) \
|
|
&& ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 0)))
|
|
ret = __sync_sub_and_fetch(addr, 1);
|
|
#else
|
|
ret = (--(*addr));
|
|
#endif
|
|
return ret;
|
|
|
|
} /* XX_httplib_atomic_dec */
|
|
|
|
#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 <sys/prctl.h>
|
|
#include <sys/sendfile.h>
|
|
#include <sys/eventfd.h>
|
|
|
|
|
|
#if defined(ALTERNATIVE_QUEUE)
|
|
|
|
static void * event_create(void) {
|
|
|
|
int ret = eventfd(0, EFD_CLOEXEC);
|
|
if (ret == -1) {
|
|
/* Linux uses -1 on error, Windows NULL. */
|
|
/* However, Linux does not return 0 on success either. */
|
|
return 0;
|
|
}
|
|
return (void *)ret;
|
|
|
|
} /* event_create */
|
|
|
|
|
|
static int event_wait(void *eventhdl) {
|
|
|
|
uint64_t u;
|
|
int s = (int)read((int)eventhdl, &u, sizeof(u));
|
|
if (s != sizeof(uint64_t)) {
|
|
/* error */
|
|
return 0;
|
|
}
|
|
(void)u; /* the value is not required */
|
|
return 1;
|
|
|
|
} /* event_wait */
|
|
|
|
|
|
static int event_signal(void *eventhdl) {
|
|
|
|
uint64_t u = 1;
|
|
int s = (int)write((int)eventhdl, &u, sizeof(u));
|
|
|
|
if (s != sizeof(uint64_t)) {
|
|
/* error */
|
|
return 0;
|
|
}
|
|
return 1;
|
|
|
|
} /* event_signal */
|
|
|
|
|
|
static void event_destroy(void *eventhdl) {
|
|
|
|
close((int)eventhdl);
|
|
} /* event_destroy */
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
#if !defined(__linux__) && !defined(_WIN32) && defined(ALTERNATIVE_QUEUE)
|
|
|
|
struct posix_event {
|
|
pthread_mutex_t mutex;
|
|
pthread_cond_t cond;
|
|
};
|
|
|
|
|
|
static void * event_create(void) {
|
|
|
|
struct posix_event *ret = XX_httplib_malloc(sizeof(struct posix_event));
|
|
if ( ret == NULL ) return NULL;
|
|
|
|
if (0 != pthread_mutex_init(&(ret->mutex), NULL)) {
|
|
/* pthread mutex not available */
|
|
XX_httplib_free(ret);
|
|
return NULL;
|
|
}
|
|
if (0 != pthread_cond_init(&(ret->cond), NULL)) {
|
|
/* pthread cond not available */
|
|
pthread_mutex_destroy(&(ret->mutex));
|
|
XX_httplib_free(ret);
|
|
return NULL;
|
|
}
|
|
return (void *)ret;
|
|
|
|
} /* event_create */
|
|
|
|
|
|
static int event_wait(void *eventhdl) {
|
|
|
|
struct posix_event *ev = (struct posix_event *)eventhdl;
|
|
pthread_mutex_lock(&(ev->mutex));
|
|
pthread_cond_wait(&(ev->cond), &(ev->mutex));
|
|
pthread_mutex_unlock(&(ev->mutex));
|
|
return 1;
|
|
|
|
} /* event_wait */
|
|
|
|
|
|
static int event_signal(void *eventhdl) {
|
|
|
|
struct posix_event *ev = (struct posix_event *)eventhdl;
|
|
pthread_mutex_lock(&(ev->mutex));
|
|
pthread_cond_signal(&(ev->cond));
|
|
pthread_mutex_unlock(&(ev->mutex));
|
|
return 1;
|
|
|
|
} /* event_signal */
|
|
|
|
|
|
static void event_destroy(void *eventhdl) {
|
|
|
|
struct posix_event *ev = (struct posix_event *)eventhdl;
|
|
pthread_cond_destroy(&(ev->cond));
|
|
pthread_mutex_destroy(&(ev->mutex));
|
|
XX_httplib_free(ev);
|
|
|
|
} /* event_destroy */
|
|
|
|
#endif
|
|
|
|
|
|
void XX_httplib_set_thread_name(const char *name) {
|
|
|
|
char threadName[16 + 1]; /* 16 = Max. thread length in Linux/OSX/.. */
|
|
|
|
XX_httplib_snprintf( NULL, NULL, threadName, sizeof(threadName), "libhttp-%s", name);
|
|
|
|
#if defined(_WIN32)
|
|
#if defined(_MSC_VER)
|
|
/* Windows and Visual Studio Compiler */
|
|
__try
|
|
{
|
|
THREADNAME_INFO info;
|
|
info.dwType = 0x1000;
|
|
info.szName = threadName;
|
|
info.dwThreadID = ~0U;
|
|
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(__GLIBC__) \
|
|
&& ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 12)))
|
|
/* pthread_setname_np first appeared in glibc in version 2.12*/
|
|
pthread_setname_np(pthread_self(), threadName);
|
|
#elif defined(__linux__)
|
|
/* on linux we can use the old prctl function */
|
|
prctl(PR_SET_NAME, threadName, 0, 0, 0);
|
|
#endif
|
|
} /* XX_httplib_set_thread_name */
|
|
|
|
#else /* !defined(NO_THREAD_NAME) */
|
|
void XX_httplib_set_thread_name(const char *threadName) {
|
|
|
|
} /* XX_httplib_set_thread_name */
|
|
#endif
|
|
|
|
|
|
|
|
void XX_httplib_fclose( struct file *filep ) {
|
|
|
|
if (filep != NULL && filep->fp != NULL) fclose(filep->fp);
|
|
|
|
} /* XX_httplib_fclose */
|
|
|
|
|
|
void XX_httplib_strlcpy( register char *dst, register const char *src, size_t n ) {
|
|
|
|
for (; *src != '\0' && n > 1; n--) { *dst++ = *src++; }
|
|
*dst = '\0';
|
|
|
|
} /* XX_httplib_strlcpy */
|
|
|
|
|
|
int XX_httplib_lowercase(const char *s) {
|
|
|
|
return tolower(*(const unsigned char *)s);
|
|
|
|
} /* XX_httplib_lowercase */
|
|
|
|
|
|
int mg_strncasecmp(const char *s1, const char *s2, size_t len) {
|
|
|
|
int diff = 0;
|
|
|
|
if (len > 0) {
|
|
do {
|
|
diff = XX_httplib_lowercase(s1++) - XX_httplib_lowercase(s2++);
|
|
} while (diff == 0 && s1[-1] != '\0' && --len > 0);
|
|
}
|
|
|
|
return diff;
|
|
}
|
|
|
|
|
|
int mg_strcasecmp(const char *s1, const char *s2) {
|
|
|
|
int diff;
|
|
|
|
do {
|
|
diff = XX_httplib_lowercase(s1++) - XX_httplib_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 *)XX_httplib_malloc(len + 1)) != NULL) XX_httplib_strlcpy(p, ptr, len + 1);
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
char * XX_httplib_strdup( const char *str ) {
|
|
|
|
return mg_strndup(str, strlen(str));
|
|
}
|
|
|
|
|
|
const char * XX_httplib_strcasestr( const char *big_str, const char *small_str ) {
|
|
|
|
size_t i;
|
|
size_t big_len = strlen(big_str);
|
|
size_t small_len = strlen(small_str);
|
|
|
|
if (big_len >= small_len) {
|
|
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;
|
|
|
|
} /* XX_httplib_strcasestr */
|
|
|
|
|
|
|
|
/* Return null terminated string of given maximum length.
|
|
* Report errors if length is exceeded. */
|
|
void XX_httplib_vsnprintf( const struct mg_connection *conn, int *truncated, char *buf, size_t buflen, const char *fmt, va_list ap ) {
|
|
|
|
int n;
|
|
int ok;
|
|
|
|
if (buflen == 0) return;
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wformat-nonliteral"
|
|
/* Using fmt as a non-literal is intended here, since it is mostly called
|
|
* indirectly by XX_httplib_snprintf */
|
|
#endif
|
|
|
|
n = (int)vsnprintf_impl(buf, buflen, fmt, ap);
|
|
ok = (n >= 0) && ((size_t)n < buflen);
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
if (ok) {
|
|
if (truncated) *truncated = 0;
|
|
} else {
|
|
if (truncated) *truncated = 1;
|
|
mg_cry(conn, "truncating vsnprintf buffer: [%.*s]", (int)((buflen > 200) ? 200 : (buflen - 1)), buf);
|
|
n = (int)buflen - 1;
|
|
}
|
|
buf[n] = '\0';
|
|
|
|
} /* XX_httplib_vsnprintf */
|
|
|
|
|
|
void XX_httplib_snprintf( const struct mg_connection *conn, int *truncated, char *buf, size_t buflen, const char *fmt, ... ) {
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
XX_httplib_vsnprintf(conn, truncated, buf, buflen, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
|
|
int XX_httplib_get_option_index( const char *name ) {
|
|
|
|
int i;
|
|
|
|
for (i = 0; XX_httplib_config_options[i].name != NULL; i++) {
|
|
if (strcmp(XX_httplib_config_options[i].name, name) == 0) return i;
|
|
}
|
|
return -1;
|
|
|
|
} /* XX_httplib_get_option_index */
|
|
|
|
|
|
const char *mg_get_option(const struct mg_context *ctx, const char *name) {
|
|
|
|
int i;
|
|
|
|
if ( (i = XX_httplib_get_option_index(name)) == -1 ) return NULL;
|
|
else if ( ctx == NULL || ctx->config[i] == NULL ) return "";
|
|
else return ctx->config[i];
|
|
}
|
|
|
|
|
|
struct mg_context * mg_get_context(const struct mg_connection *conn) {
|
|
|
|
return (conn == NULL) ? (struct mg_context *)NULL : (conn->ctx);
|
|
}
|
|
|
|
|
|
void * mg_get_user_data(const struct mg_context *ctx) {
|
|
|
|
return (ctx == NULL) ? NULL : ctx->user_data;
|
|
}
|
|
|
|
|
|
void mg_set_user_connection_data(struct mg_connection *conn, void *data) {
|
|
|
|
if (conn != NULL) conn->request_info.conn_data = data;
|
|
}
|
|
|
|
|
|
void * mg_get_user_connection_data(const struct mg_connection *conn) {
|
|
|
|
if (conn != NULL) return conn->request_info.conn_data;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int mg_get_server_ports(const struct mg_context *ctx, int size, struct mg_server_ports *ports) {
|
|
|
|
int i;
|
|
int cnt = 0;
|
|
|
|
if (size <= 0) { return -1; }
|
|
memset(ports, 0, sizeof(*ports) * (size_t)size);
|
|
if (!ctx) { return -1; }
|
|
if (!ctx->listening_sockets) { return -1; }
|
|
|
|
for (i = 0; (i < size) && (i < (int)ctx->num_listening_sockets); i++) {
|
|
|
|
ports[cnt].port =
|
|
#if defined(USE_IPV6)
|
|
(ctx->listening_sockets[i].lsa.sa.sa_family == AF_INET6)
|
|
? ntohs(ctx->listening_sockets[i].lsa.sin6.sin6_port)
|
|
:
|
|
#endif
|
|
ntohs(ctx->listening_sockets[i].lsa.sin.sin_port);
|
|
ports[cnt].is_ssl = ctx->listening_sockets[i].is_ssl;
|
|
ports[cnt].is_redirect = ctx->listening_sockets[i].ssl_redir;
|
|
|
|
if (ctx->listening_sockets[i].lsa.sa.sa_family == AF_INET) {
|
|
/* IPv4 */
|
|
ports[cnt].protocol = 1;
|
|
cnt++;
|
|
} else if (ctx->listening_sockets[i].lsa.sa.sa_family == AF_INET6) {
|
|
/* IPv6 */
|
|
ports[cnt].protocol = 3;
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
|
|
void XX_httplib_sockaddr_to_string(char *buf, size_t len, const union usa *usa) {
|
|
|
|
buf[0] = '\0';
|
|
|
|
if (!usa) return;
|
|
|
|
if (usa->sa.sa_family == AF_INET) {
|
|
getnameinfo(&usa->sa, sizeof(usa->sin), buf, (unsigned)len, NULL, 0, NI_NUMERICHOST);
|
|
}
|
|
#if defined(USE_IPV6)
|
|
else if (usa->sa.sa_family == AF_INET6) {
|
|
getnameinfo(&usa->sa, sizeof(usa->sin6), buf, (unsigned)len, NULL, 0, NI_NUMERICHOST);
|
|
}
|
|
#endif
|
|
|
|
} /* XX_httplib_sockaddr_to_string */
|
|
|
|
|
|
/* Convert time_t to a string. According to RFC2616, Sec 14.18, this must be
|
|
* included in all responses other than 100, 101, 5xx. */
|
|
void XX_httplib_gmt_time_string( char *buf, size_t buf_len, time_t *t ) {
|
|
|
|
struct tm *tm;
|
|
|
|
tm = ((t != NULL) ? gmtime(t) : NULL);
|
|
if (tm != NULL) {
|
|
strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", tm);
|
|
} else {
|
|
XX_httplib_strlcpy(buf, "Thu, 01 Jan 1970 00:00:00 GMT", buf_len);
|
|
buf[buf_len - 1] = '\0';
|
|
}
|
|
|
|
} /* XX_httplib_gmt_time_string */
|
|
|
|
|
|
/* difftime for struct timespec. Return value is in seconds. */
|
|
double XX_httplib_difftimespec(const struct timespec *ts_now, const struct timespec *ts_before) {
|
|
|
|
return (double)(ts_now->tv_nsec - ts_before->tv_nsec) * 1.0E-9
|
|
+ (double)(ts_now->tv_sec - ts_before->tv_sec);
|
|
|
|
} /* XX_httplib_difftimespec */
|
|
|
|
|
|
/* Print error message to the opened error log stream. */
|
|
void mg_cry(const struct mg_connection *conn, const char *fmt, ...) {
|
|
|
|
char buf[MG_BUF_LEN];
|
|
char src_addr[IP_ADDR_STR_LEN];
|
|
va_list ap;
|
|
struct file fi;
|
|
time_t timestamp;
|
|
|
|
va_start(ap, fmt);
|
|
IGNORE_UNUSED_RESULT(vsnprintf_impl(buf, sizeof(buf), fmt, ap));
|
|
va_end(ap);
|
|
buf[sizeof(buf) - 1] = 0;
|
|
|
|
if (!conn) {
|
|
puts(buf);
|
|
return;
|
|
}
|
|
|
|
/* 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)) {
|
|
|
|
if (conn->ctx->config[ERROR_LOG_FILE] != NULL) {
|
|
if (XX_httplib_fopen(conn, conn->ctx->config[ERROR_LOG_FILE], "a+", &fi)
|
|
== 0) {
|
|
fi.fp = NULL;
|
|
}
|
|
} else fi.fp = NULL;
|
|
|
|
if (fi.fp != NULL) {
|
|
flockfile(fi.fp);
|
|
timestamp = time(NULL);
|
|
|
|
XX_httplib_sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
|
|
fprintf(fi.fp,
|
|
"[%010lu] [error] [client %s] ",
|
|
(unsigned long)timestamp,
|
|
src_addr);
|
|
|
|
if (conn->request_info.request_method != NULL) {
|
|
fprintf(fi.fp,
|
|
"%s %s: ",
|
|
conn->request_info.request_method,
|
|
conn->request_info.request_uri);
|
|
}
|
|
|
|
fprintf(fi.fp, "%s", buf);
|
|
fputc('\n', fi.fp);
|
|
fflush(fi.fp);
|
|
funlockfile(fi.fp);
|
|
XX_httplib_fclose(&fi);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Return fake connection structure. Used for logging, if connection
|
|
* is not applicable at the moment of logging. */
|
|
struct mg_connection * XX_httplib_fc( struct mg_context *ctx ) {
|
|
|
|
static struct mg_connection fake_connection;
|
|
|
|
fake_connection.ctx = ctx;
|
|
return &fake_connection;
|
|
|
|
} /* XX_httplib_fc */
|
|
|
|
|
|
const struct mg_request_info * mg_get_request_info( const struct mg_connection *conn ) {
|
|
|
|
if ( conn == NULL ) return NULL;
|
|
|
|
return & conn->request_info;
|
|
|
|
} /* mg_get_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. */
|
|
char * XX_httplib_skip_quoted( char **buf, const char *delimiters, const char *whitespace, char quotechar ) {
|
|
|
|
char *p;
|
|
char *begin_word;
|
|
char *end_word;
|
|
char *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) {
|
|
/* While the delimiter is quoted, look for the next delimiter. */
|
|
/* This happens, e.g., in calls from XX_httplib_parse_auth_header,
|
|
* if the user name contains a " character. */
|
|
|
|
/* If there is anything beyond end_word, copy it. */
|
|
if (*end_word != '\0') {
|
|
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;
|
|
} else {
|
|
*p = '\0';
|
|
break;
|
|
}
|
|
}
|
|
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;
|
|
|
|
} /* XX_httplib_skip_quoted */
|
|
|
|
|
|
/* Simplified version of XX_httplib_skip_quoted without quote char
|
|
* and whitespace == delimiters */
|
|
char *XX_httplib_skip( char **buf, const char *delimiters ) {
|
|
|
|
return XX_httplib_skip_quoted( buf, delimiters, delimiters, 0 );
|
|
|
|
} /* XX_httplib_skip */
|
|
|
|
|
|
/* Return HTTP header value, or NULL if not found. */
|
|
const char * XX_httplib_get_header( const struct mg_request_info *ri, const char *name ) {
|
|
|
|
int i;
|
|
if (ri) {
|
|
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;
|
|
|
|
} /* XX_httplib_get_header */
|
|
|
|
|
|
const char *mg_get_header( const struct mg_connection *conn, const char *name ) {
|
|
|
|
if ( conn == NULL ) return NULL;
|
|
|
|
return XX_httplib_get_header( & conn->request_info, name );
|
|
|
|
} /* mg_get_header */
|
|
|
|
|
|
/* 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". */
|
|
const char *XX_httplib_next_option( const char *list, struct vec *val, struct vec *eq_val ) {
|
|
|
|
int end;
|
|
|
|
reparse:
|
|
if (val == NULL || list == NULL || *list == '\0') {
|
|
/* End of the list */
|
|
list = NULL;
|
|
} else {
|
|
/* Skip over leading LWS */
|
|
while (*list == ' ' || *list == '\t') list++;
|
|
|
|
val->ptr = list;
|
|
if ((list = strchr(val->ptr, ',')) != NULL) {
|
|
/* Comma found. Store length and shift the list ptr */
|
|
val->len = ((size_t)(list - val->ptr));
|
|
list++;
|
|
} else {
|
|
/* This value is the last one */
|
|
list = val->ptr + strlen(val->ptr);
|
|
val->len = ((size_t)(list - val->ptr));
|
|
}
|
|
|
|
/* Adjust length for trailing LWS */
|
|
end = (int)val->len - 1;
|
|
while (end >= 0 && (val->ptr[end] == ' ' || val->ptr[end] == '\t'))
|
|
end--;
|
|
val->len = (size_t)(end + 1);
|
|
|
|
if (val->len == 0) {
|
|
/* Ignore any empty entries. */
|
|
goto reparse;
|
|
}
|
|
|
|
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 = ((size_t)(val->ptr - eq_val->ptr)) + val->len;
|
|
val->len = ((size_t)(eq_val->ptr - val->ptr)) - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
|
|
} /* XX_httplib_next_option */
|
|
|
|
|
|
|
|
/* A helper function for checking if a comma separated list of values contains
|
|
* the given option (case insensitvely).
|
|
* 'header' can be NULL, in which case false is returned. */
|
|
int XX_httplib_header_has_option( const char *header, const char *option ) {
|
|
|
|
struct vec opt_vec;
|
|
struct vec eq_vec;
|
|
|
|
assert(option != NULL);
|
|
assert(option[0] != '\0');
|
|
|
|
while ((header = XX_httplib_next_option(header, &opt_vec, &eq_vec)) != NULL) {
|
|
if (mg_strncasecmp(option, opt_vec.ptr, opt_vec.len) == 0) return 1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
} /* XX_httplib_header_has_option */
|
|
|
|
|
|
/* Perform case-insensitive match of string against pattern */
|
|
int XX_httplib_match_prefix(const char *pattern, size_t pattern_len, const char *str) {
|
|
|
|
const char *or_str;
|
|
size_t i;
|
|
int j;
|
|
int len;
|
|
int res;
|
|
|
|
if ((or_str = (const char *)memchr(pattern, '|', pattern_len)) != NULL) {
|
|
res = XX_httplib_match_prefix(pattern, (size_t)(or_str - pattern), str);
|
|
return (res > 0) ? res : XX_httplib_match_prefix(or_str + 1, (size_t)((pattern + pattern_len) - (or_str + 1)), str);
|
|
}
|
|
|
|
for (i = 0, j = 0; 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 = XX_httplib_match_prefix(pattern + i, pattern_len - i, str + j + len);
|
|
} while (res == -1 && len-- > 0);
|
|
return (res == -1) ? -1 : j + res + len;
|
|
} else if (XX_httplib_lowercase(&pattern[i]) != XX_httplib_lowercase(&str[j])) {
|
|
return -1;
|
|
}
|
|
}
|
|
return j;
|
|
|
|
} /* XX_httplib_match_prefix */
|
|
|
|
|
|
/* 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. */
|
|
int XX_httplib_should_keep_alive( const struct mg_connection *conn ) {
|
|
|
|
if (conn != NULL) {
|
|
const char *http_version = conn->request_info.http_version;
|
|
const char *header = mg_get_header(conn, "Connection");
|
|
if (conn->must_close || conn->internal_error || conn->status_code == 401
|
|
|| mg_strcasecmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes") != 0
|
|
|| (header != NULL && !XX_httplib_header_has_option(header, "keep-alive"))
|
|
|| (header == NULL && http_version
|
|
&& 0 != strcmp(http_version, "1.1"))) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
|
|
} /* XX_httplib_should_keep_alive */
|
|
|
|
|
|
int XX_httplib_should_decode_url( const struct mg_connection *conn ) {
|
|
|
|
if ( conn == NULL || conn->ctx == NULL ) return 0;
|
|
|
|
return (mg_strcasecmp(conn->ctx->config[DECODE_URL], "yes") == 0);
|
|
|
|
} /* XX_httplib_should_decode_url */
|
|
|
|
|
|
const char * XX_httplib_suggest_connection_header( const struct mg_connection *conn ) {
|
|
|
|
return XX_httplib_should_keep_alive(conn) ? "keep-alive" : "close";
|
|
|
|
} /* XX_httplib_suggest_connection_header */
|
|
|
|
|
|
int XX_httplib_send_no_cache_header( struct mg_connection *conn ) {
|
|
|
|
/* Send all current and obsolete cache opt-out directives. */
|
|
return mg_printf(conn,
|
|
"Cache-Control: no-cache, no-store, "
|
|
"must-revalidate, private, max-age=0\r\n"
|
|
"Pragma: no-cache\r\n"
|
|
"Expires: 0\r\n");
|
|
|
|
} /* XX_httplib_send_no_cache_header */
|
|
|
|
|
|
int XX_httplib_send_static_cache_header(struct mg_connection *conn) {
|
|
|
|
#if !defined(NO_CACHING)
|
|
/* Read the server config to check how long a file may be cached.
|
|
* The configuration is in seconds. */
|
|
int max_age = atoi(conn->ctx->config[STATIC_FILE_MAX_AGE]);
|
|
if (max_age <= 0) {
|
|
/* 0 means "do not cache". All values <0 are reserved
|
|
* and may be used differently in the future. */
|
|
/* If a file should not be cached, do not only send
|
|
* max-age=0, but also pragmas and Expires headers. */
|
|
return XX_httplib_send_no_cache_header(conn);
|
|
}
|
|
|
|
/* Use "Cache-Control: max-age" instead of "Expires" header.
|
|
* Reason: see https://www.mnot.net/blog/2007/05/15/expires_max-age */
|
|
/* See also https://www.mnot.net/cache_docs/ */
|
|
/* According to RFC 2616, Section 14.21, caching times should not exceed
|
|
* one year. A year with 365 days corresponds to 31536000 seconds, a leap
|
|
* year to 31622400 seconds. For the moment, we just send whatever has
|
|
* been configured, still the behavior for >1 year should be considered
|
|
* as undefined. */
|
|
return mg_printf(conn, "Cache-Control: max-age=%u\r\n", (unsigned)max_age);
|
|
#else /* NO_CACHING */
|
|
return XX_httplib_send_no_cache_header(conn);
|
|
#endif /* !NO_CACHING */
|
|
|
|
} /* XX_httplib_send_static_cache_header */
|
|
|
|
|
|
void XX_httplib_send_http_error( struct mg_connection *conn, int status, const char *fmt, ... ) {
|
|
|
|
char buf[MG_BUF_LEN];
|
|
va_list ap;
|
|
int len;
|
|
int i;
|
|
int page_handler_found;
|
|
int scope;
|
|
int truncated;
|
|
int has_body;
|
|
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;
|
|
|
|
const char *status_text = mg_get_response_code_text(conn, status);
|
|
|
|
if (conn == NULL) return;
|
|
|
|
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: /* Handler for specific error, e.g. 404 error */
|
|
XX_httplib_snprintf(conn, &truncated, buf, sizeof(buf) - 32, "%serror%03u.", error_handler, status);
|
|
break;
|
|
case 2: /* Handler for error group, e.g., 5xx error handler
|
|
* for all server errors (500-599) */
|
|
XX_httplib_snprintf(conn, &truncated, buf, sizeof(buf) - 32, "%serror%01uxx.", error_handler, status / 100);
|
|
break;
|
|
default: /* Handler for all errors */
|
|
XX_httplib_snprintf(conn, &truncated, buf, sizeof(buf) - 32, "%serror.", error_handler);
|
|
break;
|
|
}
|
|
|
|
/* String truncation in buf may only occur if error_handler
|
|
* is too long. This string is from the config, not from a
|
|
* client. */
|
|
(void)truncated;
|
|
|
|
len = (int)strlen(buf);
|
|
|
|
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 (XX_httplib_stat(conn, buf, &error_page_file)) {
|
|
page_handler_found = 1;
|
|
break;
|
|
}
|
|
tstr = strchr(tstr + i, '.');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (page_handler_found) {
|
|
conn->in_error_handler = 1;
|
|
XX_httplib_handle_file_based_request(conn, buf, &error_page_file);
|
|
conn->in_error_handler = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* No custom error page. Send default error page. */
|
|
XX_httplib_gmt_time_string(date, sizeof(date), &curtime);
|
|
|
|
/* Errors 1xx, 204 and 304 MUST NOT send a body */
|
|
has_body = (status > 199 && status != 204 && status != 304);
|
|
|
|
conn->must_close = 1;
|
|
mg_printf(conn, "HTTP/1.1 %d %s\r\n", status, status_text);
|
|
XX_httplib_send_no_cache_header(conn);
|
|
if (has_body) mg_printf(conn, "%s", "Content-Type: text/plain; charset=utf-8\r\n");
|
|
mg_printf(conn, "Date: %s\r\n" "Connection: close\r\n\r\n", date);
|
|
|
|
/* Errors 1xx, 204 and 304 MUST NOT send a body */
|
|
if (has_body) {
|
|
mg_printf(conn, "Error %d: %s\n", status, status_text);
|
|
|
|
if (fmt != NULL) {
|
|
va_start(ap, fmt);
|
|
XX_httplib_vsnprintf(conn, NULL, buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
mg_write(conn, buf, strlen(buf));
|
|
}
|
|
|
|
} else {
|
|
/* No body allowed. Close the connection. */
|
|
}
|
|
}
|
|
|
|
} /* XX_httplib_send_http_error */
|
|
|
|
#if defined(_WIN32)
|
|
/* Create substitutes for POSIX functions in Win32. */
|
|
|
|
#if defined(__MINGW32__)
|
|
/* Show no warning in case system functions are not used. */
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunused-function"
|
|
#endif
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
#ifdef ENABLE_UNUSED_PTHREAD_FUNCTIONS
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
|
|
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) {
|
|
memset(tp, 0, sizeof(*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->waiting_thread = NULL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mutex, const struct timespec *abstime) {
|
|
|
|
struct mg_workerTLS **ptls;
|
|
struct mg_workerTLS *tls = (struct mg_workerTLS *)pthread_getspecific(XX_httplib_sTlsKey);
|
|
int ok;
|
|
struct timespec tsnow;
|
|
int64_t nsnow;
|
|
int64_t nswaitabs;
|
|
int64_t nswaitrel;
|
|
DWORD mswaitrel;
|
|
|
|
EnterCriticalSection(&cv->threadIdSec);
|
|
/* Add this thread to cv's waiting list */
|
|
ptls = &cv->waiting_thread;
|
|
for (; *ptls != NULL; ptls = &(*ptls)->next_waiting_thread)
|
|
;
|
|
tls->next_waiting_thread = NULL;
|
|
*ptls = tls;
|
|
LeaveCriticalSection(&cv->threadIdSec);
|
|
|
|
if (abstime) {
|
|
clock_gettime(CLOCK_REALTIME, &tsnow);
|
|
nsnow = (((int64_t)tsnow.tv_sec) * 1000000000) + tsnow.tv_nsec;
|
|
nswaitabs =
|
|
(((int64_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));
|
|
if (!ok) {
|
|
ok = 1;
|
|
EnterCriticalSection(&cv->threadIdSec);
|
|
ptls = &cv->waiting_thread;
|
|
for (; *ptls != NULL; ptls = &(*ptls)->next_waiting_thread) {
|
|
if (*ptls == tls) {
|
|
*ptls = tls->next_waiting_thread;
|
|
ok = 0;
|
|
break;
|
|
}
|
|
}
|
|
LeaveCriticalSection(&cv->threadIdSec);
|
|
if (ok) WaitForSingleObject(tls->pthread_cond_helper_mutex, INFINITE);
|
|
}
|
|
/* This thread has been removed from cv's waiting list */
|
|
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) {
|
|
|
|
HANDLE wkup = NULL;
|
|
BOOL ok = FALSE;
|
|
|
|
EnterCriticalSection(&cv->threadIdSec);
|
|
if (cv->waiting_thread) {
|
|
wkup = cv->waiting_thread->pthread_cond_helper_mutex;
|
|
cv->waiting_thread = cv->waiting_thread->next_waiting_thread;
|
|
|
|
ok = SetEvent(wkup);
|
|
assert(ok);
|
|
}
|
|
LeaveCriticalSection(&cv->threadIdSec);
|
|
|
|
return ok ? 0 : 1;
|
|
}
|
|
|
|
|
|
static int pthread_cond_broadcast(pthread_cond_t *cv) {
|
|
|
|
EnterCriticalSection(&cv->threadIdSec);
|
|
while (cv->waiting_thread) pthread_cond_signal(cv);
|
|
LeaveCriticalSection(&cv->threadIdSec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int pthread_cond_destroy(pthread_cond_t *cv) {
|
|
|
|
EnterCriticalSection(&cv->threadIdSec);
|
|
assert(cv->waiting_thread == NULL);
|
|
LeaveCriticalSection(&cv->threadIdSec);
|
|
DeleteCriticalSection(&cv->threadIdSec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef ALTERNATIVE_QUEUE
|
|
static void * event_create(void) {
|
|
|
|
return (void *)CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
|
|
} /* event_create */
|
|
|
|
|
|
static int event_wait(void *eventhdl) {
|
|
|
|
int res = WaitForSingleObject((HANDLE)eventhdl, INFINITE);
|
|
return (res == WAIT_OBJECT_0);
|
|
|
|
} /* event_wait */
|
|
|
|
|
|
static int event_signal(void *eventhdl) {
|
|
|
|
return (int)SetEvent((HANDLE)eventhdl);
|
|
|
|
}a /* event_signal */
|
|
|
|
|
|
static void event_destroy(void *eventhdl) {
|
|
|
|
CloseHandle((HANDLE)eventhdl);
|
|
|
|
} /* event_destroy */
|
|
#endif
|
|
|
|
#endif /* _WIN32 */
|