Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

9736 lines (8561 sloc) 335.478 kb
// Copyright (c) 2004-2012 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.
#define INSIDE_MONGOOSE_C 1
#include "mongoose.h"
#define MONGOOSE_VERSION "3.3"
#define PASSWORDS_FILE_NAME ".htpasswd"
#define CGI_ENVIRONMENT_SIZE MG_MAX(MG_BUF_LEN, 4096)
#define MAX_CGI_ENVIR_VARS 64
#define MAX_REQUEST_SIZE 16384 // Must be larger than 128 (heuristic lower bound)
/* buffer size used when copying data to/from file/socket/... */
#define DATA_COPY_BUFSIZ MG_MAX(MG_BUF_LEN, 4096)
/* buffer size used to load all HTTP headers into: if the client sends more header data than this, we'll barf a hairball! */
#define HTTP_HEADERS_BUFSIZ MG_MAX(MG_BUF_LEN, 16384)
/* buffer size used to extract/decode an SSI command line / file path; hence must be equal or larger than PATH_MAX, at least */
#define SSI_LINE_BUFSIZ MG_MAX(MG_BUF_LEN, PATH_MAX)
/* buffer size used to extract/decode/store a HTTP/1.1 'chunked transfer' header */
#define CHUNK_HEADER_BUFSIZ MG_MAX(MG_BUF_LEN, 80)
/* buffer size for domain names, users and password hashes */
#define USRDMNPWD_BUFSIZ 512
// The maximum amount of data we're willing to dump in a single mg_cry() log call.
// In embedded environments with limited RAM, you may want to override this
// value as this value determines the malloc() size used inside mg_vasprintf().
#ifndef MG_MAX_LOG_LINE_SIZE
#define MG_MAX_LOG_LINE_SIZE 1024 * 1024
#endif
// The number of msecs to wait inside select() when there's nothing to do.
#ifndef MG_SELECT_TIMEOUT_MSECS
#define MG_SELECT_TIMEOUT_MSECS 200
#endif
// The number of msecs to wait inside select() or cond_wait() when the
// connection queue is filled and there might be something to do elsewhere
// while we wait for 'this bunch'
#ifndef MG_SELECT_TIMEOUT_MSECS_TINY
#define MG_SELECT_TIMEOUT_MSECS_TINY 1
#endif
// The maximum length of a %[U] or %[U] component in a logfile path template.
// Should be at larger than 8 to make any sense.
#ifndef MG_LOGFILE_MAX_URI_COMPONENT_LEN
#define MG_LOGFILE_MAX_URI_COMPONENT_LEN 64
#endif
#if MG_DEBUG_TRACING
// 'data' exports don't work well for dynamic libs: use accessor function
unsigned int *mg_trace_level(void) {
static unsigned int trace_level = ~0u;
return &trace_level;
}
#endif
#if defined(_WIN32)
int mgW32_get_errno(void) {
DWORD e1 = GetLastError();
DWORD e2 = WSAGetLastError();
int e3 = errno;
return (e2 ? (int)e2 : e1 ? (int)e1 : e3);
}
static struct {
volatile int active;
CRITICAL_SECTION lock;
} global_log_file_lock = {0};
void mgW32_flockfile(UNUSED_PARAMETER(FILE *unused)) {
if (global_log_file_lock.active)
EnterCriticalSection(&global_log_file_lock.lock);
}
void mgW32_funlockfile(UNUSED_PARAMETER(FILE *unused)) {
if (global_log_file_lock.active)
LeaveCriticalSection(&global_log_file_lock.lock);
}
#if !defined(WSAID_DISCONNECTEX)
typedef BOOL (PASCAL * LPFN_DISCONNECTEX) (SOCKET s, LPOVERLAPPED lpOverlapped, DWORD dwFlags, DWORD dwReserved);
#define WSAID_DISCONNECTEX {0x7fda2e11,0x8630,0x436f,{0xa0, 0x31, 0xf5, 0x36, 0xa6, 0xee, 0xc1, 0x57}}
#endif
#ifndef SIO_GET_EXTENSION_FUNCTION_POINTER
#define SIO_GET_EXTENSION_FUNCTION_POINTER (0x80000000|0x40000000|0x08000000|6)
#endif
static LPFN_DISCONNECTEX DisconnectExPtr = 0;
static CRITICAL_SECTION DisconnectExPtrCS;
static BOOL PASCAL dummy_disconnectEx(SOCKET sock, LPOVERLAPPED lpOverlapped, DWORD dwFlags, DWORD dwReserved) {
return 0;
}
static LPFN_DISCONNECTEX get_DisconnectEx_funcptr(SOCKET sock) {
/*
Note The function pointer for the DisconnectEx function must be obtained
at run time by making a call to the WSAIoctl function with the
SIO_GET_EXTENSION_FUNCTION_POINTER opcode specified. The input buffer
passed to the WSAIoctl function must contain WSAID_DISCONNECTEX, a
globally unique identifier (GUID) whose value identifies the
DisconnectEx extension function.
On success, the output returned by the WSAIoctl function contains
a pointer to the DisconnectEx function. The WSAID_DISCONNECTEX GUID
is defined in the Mswsock.h header file.
*/
LPFN_DISCONNECTEX ret;
EnterCriticalSection(&DisconnectExPtrCS);
if (!DisconnectExPtr && sock) {
GUID dcex = WSAID_DISCONNECTEX;
LPFN_DISCONNECTEX DisconnectExPtr = 0;
DWORD len = 0;
int rv;
rv = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &dcex, sizeof(dcex),
&DisconnectExPtr, sizeof(DisconnectExPtr),
&len, 0, 0);
if (rv) {
DisconnectExPtr = dummy_disconnectEx;
}
}
if (DisconnectExPtr)
ret = DisconnectExPtr;
else
ret = dummy_disconnectEx;
LeaveCriticalSection(&DisconnectExPtrCS);
return ret;
}
static BOOL __DisconnectEx(SOCKET sock, LPOVERLAPPED lpOverlapped, DWORD dwFlags, DWORD dwReserved) {
LPFN_DISCONNECTEX fp = get_DisconnectEx_funcptr(sock);
return (*fp)(sock, lpOverlapped, dwFlags, dwReserved);
}
#else // _WIN32
static int __DisconnectEx(SOCKET sock, void *lpOverlapped, int dwFlags, int dwReserved) {
return 0;
}
#endif // _WIN32
#if defined(_MSC_VER)
// Fix buf in MSVC2010 malloc.h header: _malloca is not properly redefined when crtdbg is enabled in debug mode:
#if defined(_ALLOCA_S_MARKER_SIZE) && defined(_ALLOCA_S_MARKER_SIZE)
// fix:
#if defined(_DEBUG)
#if defined(_CRTDBG_MAP_ALLOC)
#undef _malloca
#define _malloca(size) \
__pragma(warning(suppress: 6255)) \
_MarkAllocaS(malloc((size) + _ALLOCA_S_MARKER_SIZE), _ALLOCA_S_HEAP_MARKER)
#endif
#endif
// end of fix
#endif
// these MUST be macros, NOT functions:
#define mg_malloca(size) _malloca(size)
#define mg_freea(ptr) _freea(ptr)
#elif defined(alloca) || defined(HAVE_ALLOCA)
// these MUST be macros, NOT functions:
#define mg_malloca(size) alloca(size)
#define mg_freea(ptr) // no-op
#else
void *mg_malloca(size_t size) {
return malloc(size);
}
void mg_freea(void *ptr) {
if (ptr)
free(ptr);
}
#endif
#if !defined(NO_SSL)
// Snatched from OpenSSL includes. I put the prototypes here to be independent
// from the OpenSSL source installation. Having this, mongoose + SSL can be
// built on any system with binary SSL libraries installed.
typedef struct ssl_st SSL;
typedef struct ssl_method_st SSL_METHOD;
typedef struct ssl_ctx_st SSL_CTX;
#define SSL_ERROR_NONE 0
#define SSL_ERROR_SSL 1
#define SSL_ERROR_WANT_READ 2
#define SSL_ERROR_WANT_WRITE 3
#define SSL_ERROR_WANT_X509_LOOKUP 4
#define SSL_ERROR_SYSCALL 5
#define SSL_ERROR_ZERO_RETURN 6
#define SSL_ERROR_WANT_CONNECT 7
#define SSL_ERROR_WANT_ACCEPT 8
#define SSL_FILETYPE_PEM 1
#define CRYPTO_LOCK 1
#if defined(NO_SSL_DL)
extern void SSL_free(SSL *);
extern int SSL_accept(SSL *);
extern int SSL_connect(SSL *);
extern int SSL_shutdown(SSL *);
extern int SSL_read(SSL *, void *, int);
extern int SSL_write(SSL *, const void *, int);
extern int SSL_peek(SSL *ssl,void *buf,int num);
extern int SSL_get_error(const SSL *, int);
extern int SSL_set_fd(SSL *, int);
extern SSL *SSL_new(SSL_CTX *);
extern SSL_CTX *SSL_CTX_new(SSL_METHOD *);
extern SSL_METHOD *SSLv23_server_method(void);
extern SSL_METHOD *SSLv23_client_method(void);
extern int SSL_library_init(void);
extern void SSL_load_error_strings(void);
extern int SSL_CTX_use_PrivateKey_file(SSL_CTX *, const char *, int);
extern int SSL_CTX_use_certificate_file(SSL_CTX *, const char *, int);
extern int SSL_CTX_use_certificate_chain_file(SSL_CTX *, const char *);
extern void SSL_CTX_set_default_passwd_cb(SSL_CTX *, mg_callback_t);
extern void SSL_CTX_free(SSL_CTX *);
extern unsigned long ERR_get_error(void);
extern char *ERR_error_string(unsigned long, char *);
extern int CRYPTO_num_locks(void);
extern void CRYPTO_set_locking_callback(void (*)(int, int, const char *, int));
extern void CRYPTO_set_id_callback(unsigned long (*)(void));
#else
// Dynamically loaded SSL functionality
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_shutdown (* (int (*)(SSL *)) ssl_sw[3].ptr)
#define SSL_read (* (int (*)(SSL *, void *, int)) ssl_sw[4].ptr)
#define SSL_write (* (int (*)(SSL *, const void *,int)) ssl_sw[5].ptr)
#define SSL_peek (* (int (*)(SSL *, void *, int)) ssl_sw[6].ptr)
#define SSL_get_error (* (int (*)(SSL *, int)) ssl_sw[7].ptr)
#define SSL_set_fd (* (int (*)(SSL *, SOCKET)) ssl_sw[8].ptr)
#define SSL_new (* (SSL * (*)(SSL_CTX *)) ssl_sw[9].ptr)
#define SSL_CTX_new (* (SSL_CTX * (*)(SSL_METHOD *)) ssl_sw[10].ptr)
#define SSLv23_server_method (* (SSL_METHOD * (*)(void)) ssl_sw[11].ptr)
#define SSL_library_init (* (int (*)(void)) ssl_sw[12].ptr)
#define SSL_CTX_use_PrivateKey_file (* (int (*)(SSL_CTX *, \
const char *, int)) ssl_sw[13].ptr)
#define SSL_CTX_use_certificate_file (* (int (*)(SSL_CTX *, \
const char *, int)) ssl_sw[14].ptr)
#define SSL_CTX_set_default_passwd_cb \
(* (void (*)(SSL_CTX *, mg_callback_t)) ssl_sw[15].ptr)
#define SSL_CTX_free (* (void (*)(SSL_CTX *)) ssl_sw[16].ptr)
#define SSL_load_error_strings (* (void (*)(void)) ssl_sw[17].ptr)
#define SSL_CTX_use_certificate_chain_file \
(* (int (*)(SSL_CTX *, const char *)) ssl_sw[18].ptr)
#define SSLv23_client_method (* (SSL_METHOD * (*)(void)) ssl_sw[19].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_shutdown", NULL},
{"SSL_read", NULL},
{"SSL_write", NULL},
{"SSL_peek", 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},
{NULL, NULL}
};
// Similar array as ssl_sw. These functions could be located in different lib.
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_DL
#else // NO_SSL
typedef struct bogus_ssl_st SSL;
typedef struct bogus_ssl_ctx_st SSL_CTX;
#define SSL_free(ssl) (void)0
#define SSL_shutdown(ssl) 0
#define SSL_read(ssl, p, l) (-1)
#define SSL_write(ssl, p, l) (-1)
#define SSL_peek(ssl, p, l) (-1)
#define SSL_CTX_free(ctx) (void)0
#endif // NO_SSL
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.
struct usa {
socklen_t len;
union {
struct sockaddr sa;
struct sockaddr_in sin;
#if defined(USE_IPV6)
struct sockaddr_in6 sin6;
#endif
} u;
};
// Describes a string (chunk of memory).
struct vec {
const char *ptr;
size_t len;
};
// Describes listening socket, or socket which was accept()-ed by the master
// thread and queued for future handling by the worker thread.
struct socket {
struct socket *next; // Linkage
SOCKET sock; // Listening socket
struct usa lsa; // Local socket address
struct usa rsa; // Remote socket address
int max_idle_seconds; // 'keep alive' timeout (used while monitoring the idle queue, used together with the recv()-oriented SO_RCVTIMEO, etc. socket options), 0 is infinity.
unsigned is_ssl: 1; // Is socket SSL-ed
unsigned read_error: 1; // Receive error occurred on this socket (recv())
unsigned write_error: 1; // Write error occurred on this socket (send())
unsigned has_read_data: 1; // 1 when active ~ when read data is available. This is used to 'signal' a node when a idle-test select() turns up multiple active nodes at once. (speedup)
unsigned was_idle: 1; // 1 when a socket has been pulled from the 'idle queue' just now: '1' means 'has_read_data' is valid (and can be used instead of select()).
unsigned idle_time_expired: 1; // 1 when the idle time (max_idle_seconds) has expired
};
// A 'pushed back' idle (HTTP keep-alive) socket connection: as we
// round-robin through the set of these while testing for activity,
// we use a cyclic linked list.
//
// This structure is used to persist all per-connection values beyond
// the single request.
struct mg_idle_connection {
// persisted mg_request_info bits:
void *req_user_data; // optional reference to user-defined data that's specific for this request. (The user_data reference passed to mg_start() is available through connection->ctx->user_functions in any user event handler!)
struct mg_ip_address remote_ip; // Client's IP address
struct mg_ip_address local_ip; // This machine's IP address which receives/services the request
int remote_port; // Client's port
int local_port; // Server's port
int seq_no; // number of requests served for this connection (1..N; can only be >1 for kept-alive connections)
// persisted mg_connection bits:
SSL *ssl; // SSL descriptor
struct socket client; // Connected client
time_t birth_time; // Time when connection was accepted
time_t last_active_time; // Time when connection was last active
unsigned is_inited: 1;
// book-keeping:
int next; // next in chain; cyclic linked list!
int prev; // previous in chain; cyclic linked list!
};
typedef enum {
CGI_EXTENSIONS,
ALLOWED_METHODS,
CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER,
PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS,
SSI_MARKER, ERROR_FILE,
ACCESS_LOG_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE,
GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE,
KEEP_ALIVE_TIMEOUT, SOCKET_LINGER_TIMEOUT,
ACCESS_CONTROL_LIST,
EXTRA_MIME_TYPES, LISTENING_PORTS, IGNORE_OCCUPIED_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE,
NUM_THREADS, RUN_AS_USER, REWRITE, HIDE_FILES,
NUM_OPTIONS
} mg_option_index_t;
static const char *config_options[(NUM_OPTIONS + 1/* sentinel*/) * MG_ENTRIES_PER_CONFIG_OPTION] = {
"C", "cgi_pattern", "**.cgi$|**.pl$|**.php$",
"D", "allowed_methods", NULL,
"E", "cgi_environment", NULL,
"G", "put_delete_passwords_file", NULL,
"I", "cgi_interpreter", NULL,
"P", "protect_uri", NULL,
"R", "authentication_domain", "mydomain.com",
"S", "ssi_pattern", "**.shtml$|**.shtm$",
"", "ssi_marker", NULL,
"Z", "error_file", "404=/error/404.shtml,0=/error/error.shtml",
"a", "access_log_file", NULL,
"d", "enable_directory_listing", "yes",
"e", "error_log_file", NULL,
"g", "global_passwords_file", NULL,
"i", "index_files", "index.html,index.htm,index.cgi,index.shtml,index.php",
"k", "enable_keep_alive", "yes",
"K", "keep_alive_timeout", "5",
"L", "socket_linger_timeout", "5",
"l", "access_control_list", NULL,
"m", "extra_mime_types", NULL,
"p", "listening_ports", "8080",
"", "ignore_occupied_ports", "no",
"r", "document_root", ".",
"s", "ssl_certificate", NULL,
"t", "num_threads", "10",
"u", "run_as_user", NULL,
"w", "url_rewrite_patterns", NULL,
"x", "hide_files_patterns", NULL,
NULL, NULL, NULL
};
struct mg_context {
volatile int stop_flag; // Should we stop event loop
SSL_CTX *ssl_ctx; // SSL context
SSL_CTX *client_ssl_ctx; // Client SSL context
char *config[NUM_OPTIONS]; // Mongoose configuration parameters
struct mg_user_class_t user_functions; // user-defined callbacks and data
struct socket *listening_sockets;
volatile int num_threads; // Number of threads
pthread_mutex_t mutex; // Protects (max|num)_threads
pthread_cond_t cond; // Condvar for tracking workers terminations
struct mg_idle_connection queue_store[128]; // Cut down on malloc()/free()ing cost by using a static queue.
volatile int sq_head; // Index to first node of cyclic linked list of 'pushed back' sockets which expect to serve more requests but are currently inactive. '-1' ~ empty!
int idle_q_store_free_slot; // index into the idle_queue_store[] where scanning for a free slot should start. Single linked list on '.next'.
pthread_cond_t sq_full; // Signaled when socket is produced
pthread_cond_t sq_empty; // Signaled when socket is consumed
};
struct mg_connection {
unsigned must_close: 1; // 1 if connection must be closed
unsigned is_inited: 1; // 1 when the connection been completely set up (SSL, local and remote peer info, ...)
unsigned is_client_conn: 2; // 0 when the connection is a server-side connection (responding to requests); 1: client connection (sending requests); 2: peer-to-peer connection (non-HTTP)
unsigned abort_when_server_stops: 1; // 1 when the connection should be dropped/fail when the server is being stopped (ctx->stop_flag)
unsigned tx_is_in_chunked_mode: 1; // 1 when transmission through the connection must be chunked (segmented)
unsigned rx_is_in_chunked_mode: 1; // 1 when reception through the connection is chunked (segmented)
unsigned tx_chunk_header_sent: 2; // 1 when the current chunk's header has already been transmitted, 2 when header transmit is in progress
unsigned rx_chunk_header_parsed: 2; // 1 when the current chunk's header has already been (received and) parsed, 2 when header reception/parsing is in progress, 3 when header was parsed and is now processed
unsigned tx_can_compact_hdrstore: 2; // signal whether a TX header store 'compact' operation would have any effect at all; 1: regular compact; 2: always pull the request uri and query string into the tx buffer space for persistence
unsigned nested_err_or_pagereq_count: 2; // 1 when we're requesting an error/'nested' page; > 1 when the page request is failing (nested errors)
struct mg_request_info request_info;
struct mg_context *ctx;
SSL *ssl; // SSL descriptor
struct socket client; // Connected client
time_t birth_time; // Time when connection was accepted
time_t last_active_time; // Time when connection was last active
int64_t num_bytes_sent; // Total bytes sent to client; negative number is the amount of header bytes sent; positive number is the amount of data bytes
int64_t content_len; // received Content-Length header value or chunk size; INT64_MAX means fetch as much as you can, mg_read() will act like a single pull(); -1 means we'd have to fetch (and decode) the (HTTP) headers first
int64_t consumed_content; // How many bytes of content have already been read
char *buf; // Buffer for received data [buf_size] / chunk header reception [CHUNK_HEADER_BUFSIZ] / headers to transmit [buf_size]
//char *body; // Pointer to not-read yet buffered body data
//char *next_request; // Pointer to the buffered next request
int buf_size; // Buffer size for received data + chunk header reception
int request_len; // Size of the request + headers in buffer buf[]
int rx_chunk_buf_size; // Maximum available number of bytes for the RX chunk header buffer, starting at buf[request_len]
int rx_buffer_loaded_len; // Number of bytes loaded into the RX chunk buffer
int rx_buffer_read_len; // Number of bytes already read from the RX chunk buffer (<= rx_buffer_loaded_len)
int tx_headers_len; // Size of the response headers (client: + possibly cached request URI+query string) in buffer buf[]
int64_t tx_remaining_chunksize; // How many bytes of content remain to be sent in the current chunk
int64_t rx_remaining_chunksize; // How many bytes of content remain to be received in the current chunk
int64_t tx_next_chunksize; // How many bytes of content will be sent in the next chunk
int tx_chunk_count; // The number of chunks transmitted so far.
int rx_chunk_count; // The number of chunks received so far.
char error_logfile_path[PATH_MAX+1]; // cached value: path to the error logfile designated to this connection/CTX
char access_logfile_path[PATH_MAX+1]; // cached value: path to the access logfile designated to this connection/CTX
};
const char **mg_get_valid_option_names(void) {
return config_options;
}
static void *call_user(struct mg_connection *conn, enum mg_event event) {
if (conn && conn->ctx && conn->ctx->user_functions.user_callback) {
return conn->ctx->user_functions.user_callback(event, conn);
} else {
return NULL;
}
}
static void *call_user_over_ctx(struct mg_context *ctx, SSL_CTX *ssl_ctx, enum mg_event event) {
if (ctx && ctx->user_functions.user_callback) {
struct mg_connection conn = {0};
void *rv;
SSL_CTX *old_ssl = ctx->ssl_ctx;
ctx->ssl_ctx = ssl_ctx;
conn.ctx = ctx;
rv = ctx->user_functions.user_callback(event, &conn);
ctx->ssl_ctx = old_ssl;
return rv;
} else {
return NULL;
}
}
static int call_user_option_decode(struct mg_context *ctx, const char *name, const char *value) {
if (ctx && ctx->user_functions.user_option_decode) {
return ctx->user_functions.user_option_decode(ctx, name, value);
} else {
return 0;
}
}
static int call_user_option_fill(struct mg_context *ctx) {
if (ctx && ctx->user_functions.user_option_fill) {
return ctx->user_functions.user_option_fill(ctx);
} else {
return !0;
}
}
static const char *call_user_option_get(struct mg_context *ctx, const char *name) {
if (ctx && ctx->user_functions.user_option_get) {
return ctx->user_functions.user_option_get(ctx, 0, name);
} else {
return NULL;
}
}
static const char *call_user_conn_option_get(struct mg_connection *conn, const char *name) {
if (conn && conn->ctx && conn->ctx->user_functions.user_option_get) {
return conn->ctx->user_functions.user_option_get(conn->ctx, conn, name);
} else {
return NULL;
}
}
static int call_user_ssi_command(struct mg_connection *conn, const char *ssi_commandline, const char *path, int include_level) {
if (conn && conn->ctx && conn->ctx->user_functions.user_ssi_command) {
return conn->ctx->user_functions.user_ssi_command(conn, ssi_commandline, path, include_level);
} else {
return 0;
}
}
static int is_empty(const char *str) {
return !str || !*str;
}
static int get_option_index(const char *name) {
int i;
if (!name)
return -1;
for (i = 0; config_options[i] != NULL; i += MG_ENTRIES_PER_CONFIG_OPTION) {
if ((config_options[i][0] && strcmp(config_options[i], name) == 0) ||
strcmp(config_options[i + 1], name) == 0) {
return i / MG_ENTRIES_PER_CONFIG_OPTION;
}
}
return -1;
}
const char *mg_get_option(struct mg_context *ctx, const char *name) {
const char *rv = call_user_option_get(ctx, name);
if (!rv) {
int i = get_option_index(name);
if (i == -1) {
return NULL;
} else if (ctx == NULL || ctx->config[i] == NULL) {
return "";
} else {
return ctx->config[i];
}
}
return rv;
}
const char *mg_get_conn_option(struct mg_connection *conn, const char *name) {
const char *rv = call_user_conn_option_get(conn, name);
if (!rv) {
int i = get_option_index(name);
if (i == -1) {
return NULL;
} else if (conn == NULL || conn->ctx == NULL || conn->ctx->config[i] == NULL) {
return "";
} else {
return conn->ctx->config[i];
}
}
return rv;
}
const char *mg_get_option_long_name(const char *name) {
int i = get_option_index(name);
if (i >= 0)
return config_options[i * MG_ENTRIES_PER_CONFIG_OPTION + 1];
return NULL;
}
static const char *get_option(struct mg_context *ctx, mg_option_index_t index) {
const char *rv;
assert((int)index >= 0 && (int)index < NUM_OPTIONS);
rv = call_user_option_get(ctx, config_options[index * MG_ENTRIES_PER_CONFIG_OPTION + 1]);
if (rv)
return rv;
if (ctx == NULL || ctx->config[index] == NULL)
return "";
else
return ctx->config[index];
}
static const char *get_conn_option(struct mg_connection *conn, mg_option_index_t index) {
const char *rv;
assert((int)index >= 0 && (int)index < NUM_OPTIONS);
rv = call_user_conn_option_get(conn, config_options[index * MG_ENTRIES_PER_CONFIG_OPTION + 1]);
if (rv)
return rv;
if (conn == NULL || conn->ctx == NULL || conn->ctx->config[index] == NULL)
return "";
else
return conn->ctx->config[index];
}
// ntop()/ntoa() replacement for IPv6 + IPv4 support:
static char *sockaddr_to_string(char *buf, size_t len, const struct usa *usa) {
buf[0] = '\0';
#if defined(USE_IPV6) && defined(HAVE_INET_NTOP)
// Only Windoze Vista (and newer) have inet_ntop()
inet_ntop(usa->u.sa.sa_family, (usa->u.sa.sa_family == AF_INET ?
(void *) &usa->u.sin.sin_addr :
(void *) &usa->u.sin6.sin6_addr), buf, len);
#elif defined(HAVE_GETNAMEINFO)
// Win32: do not use WSAAddressToString() as that one formats the output as [address]:port while we only want to print <address> here
if (getnameinfo(&usa->u.sa, usa->len, buf, len, NULL, 0, NI_NUMERICHOST))
buf[0] = '\0';
#elif defined(HAVE_INET_NTOP)
inet_ntop(usa->u.sa.sa_family, (void *) &usa->u.sin.sin_addr, buf, len);
#elif defined(_WIN32)
// WARNING: ntoa() is very probably not thread-safe on your platform!
// (we'll abuse the (DisconnectExPtrCS) critical section to cover this up as well...)
EnterCriticalSection(&DisconnectExPtrCS);
mg_strlcpy(buf, inet_ntoa(usa->u.sin.sin_addr), len);
LeaveCriticalSection(&DisconnectExPtrCS);
#else
#error check your platform for inet_ntop/etc.
#endif
buf[len - 1] = 0;
return buf;
}
// ntoh() replacement for IPv6 + IPv4 support:
static unsigned short int get_socket_port(const struct usa *usa)
{
#if defined(USE_IPV6)
return ntohs(usa->u.sa.sa_family == AF_INET ?
usa->u.sin.sin_port :
usa->u.sin6.sin6_port);
#else
return ntohs(usa->u.sin.sin_port);
#endif
}
// IPv4 + IPv6 support: produce the individual numbers of the IP address in a usable/portable (host) structure
static void get_socket_ip_address(struct mg_ip_address *dst, const struct usa *usa)
{
memset(&dst->ip_addr, 0, sizeof(dst->ip_addr));
#if defined(USE_IPV6)
// Note: According to RFC3493 the only specified member of the in6_addr structure is s6_addr.
dst->is_ip6 = (usa->u.sa.sa_family == AF_INET6);
if (dst->is_ip6) {
const uint16_t *s = (const uint16_t *)&usa->u.sin6.sin6_addr;
dst->ip_addr.v6[0] = ntohs(s[0]);
dst->ip_addr.v6[1] = ntohs(s[1]);
dst->ip_addr.v6[2] = ntohs(s[2]);
dst->ip_addr.v6[3] = ntohs(s[3]);
dst->ip_addr.v6[4] = ntohs(s[4]);
dst->ip_addr.v6[5] = ntohs(s[5]);
dst->ip_addr.v6[6] = ntohs(s[6]);
dst->ip_addr.v6[7] = ntohs(s[7]);
}
else
#endif
{
const uint8_t *s = (const uint8_t *)&usa->u.sin.sin_addr;
dst->ip_addr.v4[0] = s[0];
dst->ip_addr.v4[1] = s[1];
dst->ip_addr.v4[2] = s[2];
dst->ip_addr.v4[3] = s[3];
dst->is_ip6 = 0;
}
}
// Note: dst may reference the same memory as src
static void cvt_ipv4_to_ipv6(struct mg_ip_address *dst, const struct mg_ip_address *src)
{
// process IPv4 as IPv6 ::ffff:a0:a1:a2:a3
if (src->is_ip6) {
if (dst != src)
*dst = *src;
} else {
struct mg_ip_address d;
d.is_ip6 = 1;
d.ip_addr.v6[0] = 0;
d.ip_addr.v6[1] = 0;
d.ip_addr.v6[2] = 0;
d.ip_addr.v6[3] = 0xffffu;
d.ip_addr.v6[4] = src->ip_addr.v4[0];
d.ip_addr.v6[5] = src->ip_addr.v4[1];
d.ip_addr.v6[6] = src->ip_addr.v4[2];
d.ip_addr.v6[7] = src->ip_addr.v4[3];
*dst = d;
}
}
/*
Like strerror() but with included support for the same functionality for
Win32 system error codes, so that mg_strerror(ERROR) always delivers the
best possible description instead of a lot of 'Unknown error' messages.
NOTE: has the mg_ prefix to prevent collisions with system's strerror();
it is generally used in constructs like mg_strerror(ERRNO) where
ERRNO is a mongoose-internal #define.
*/
const char *mg_strerror(int errcode) {
#if defined(_WIN32) && !defined(__SYMBIAN32__)
const char *s = strerror(errcode);
if (is_empty(s) || GetLastError() == (DWORD)errcode) {
static __declspec(thread) char msg[256];
if (0 == FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
errcode, 0, msg, ARRAY_SIZE(msg), NULL)) {
snprintf(msg, ARRAY_SIZE(msg), "Unidentified error code %d", errcode);
} else {
// strip trailing whitespace off the message.
char *p = msg + strlen(msg) - 1;
while (p >= msg && isspace((unsigned char)*p))
p--;
p[1] = 0;
}
return msg;
}
return s;
#else
return strerror(errcode);
#endif
}
/*
Return fake connection structure. Used for logging, if connection
is not applicable at the moment of logging.
(Note: this is 'thread safe _enough_': we don't care that multiple threads
can bang up this 'connection', just as long as 'ctx' is
written atomically (the write is one opcode).)
*/
static struct mg_connection *fc(struct mg_context *ctx) {
static struct mg_connection fake_connection = {0};
fake_connection.ctx = ctx;
fake_connection.last_active_time = time(NULL);
if (fake_connection.birth_time == 0) {
fake_connection.birth_time = fake_connection.last_active_time;
}
return &fake_connection;
}
// Replace all illegal characters by '_'; reduce multiple
// occurrences of these by a single character (so you don't
// get filepaths like '_______/buggered.dir').
//
// Replaces '%' by '!' and otherwise only allows [a-z0-9-]
// and of course '/' and '\' (which is converted to '/')
// to pass, so as to create paths which are suitable for the
// most restrictive file systems.
//
// Does not allow the path to end at a '/'.
//
// Modifies the path in place; the result is always smaller
// or equal in size, compared to the input.
//
// Returns the length of the result.
static int powerscrub_filepath(char *path, int tolerate_dirsep)
{
char *d = path;
char *s = path;
for (;;) {
switch (*s++) {
case 0:
break;
case '%':
*d++ = '!';
continue;
case ':':
case '.':
// don't allow output sequences with multiple dots following one another,
// nor do we allow a dot at the start or end of the produced part (which would
// possibly generate hidden files/dirs and file create issues on some
// OS/storage formats):
if (d > path && !strchr("/.", d[-1]))
*d++ = '.';
continue;
case '/':
case '\\':
// don't allow output sequences with multiple dots following one another,
// nor do we allow a dot at the start or end of the produced part (which would
// possibly generate hidden files/dirs and file create issues on some
// OS/storage formats):
if (d > path && strchr("/.", d[-1]))
d[-1] = (tolerate_dirsep ? '/' : '.');
else
*d++ = (tolerate_dirsep ? '/' : '.');
continue;
default:
// be very conservative in our estimate what your filesystem will tolerate as valid characters in a filename:
if (strchr("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-", s[-1]))
*d++ = s[-1];
else if (d == path || d[-1] != '_')
*d++ = '_';
continue;
}
break;
}
// make sure there's no '/' or '.' dot at the very end to prevent file create issues on some platforms:
while (d > path && strchr("/.", d[-1]))
d--;
*d = 0;
return (int)(d - path);
}
// replace %[P] with client port number
// %[C] with client IP (sanitized for filesystem paths)
// %[p] with server port number
// %[s] with server IP (sanitized for filesystem paths)
// %[U] with the request URI path section (sanitized for filesystem paths and limited to MG_LOGFILE_MAX_URI_COMPONENT_LEN characters max. (last 8 characters on overflow: URL hash))
// %[Q] with the request URI query section (sanitized for filesystem paths and limited to MG_LOGFILE_MAX_URI_COMPONENT_LEN characters max. (last 8 characters on overflow: query hash))
//
// any other % parameter is processed by strftime.
const char *mg_get_logfile_path(char *dst, size_t dst_maxsize, const char *logfile_template, struct mg_connection *conn, time_t timestamp) {
char fnbuf[PATH_MAX+1];
char *d;
const char *s;
struct tm *tp;
if (!dst || dst_maxsize < 1) {
return NULL;
}
if (is_empty(logfile_template) || dst_maxsize <= 1) {
dst[0] = 0;
return NULL;
}
d = fnbuf;
d[PATH_MAX] = 0; // sentinel for odd moments with strncpy et al
s = logfile_template;
while (d - fnbuf < PATH_MAX) {
switch (*s) {
case 0:
if (d > fnbuf && d[-1] == '.')
d--;
break;
case '%':
if (s[1] == '[') {
int len = PATH_MAX - (int)(d - fnbuf); // assert(len > 0);
char *s_cont = NULL;
// Enough space for all: ntoa() output, URL path and it's limited-length copy + MD5 hash at the end:
// Note: +2 in case MG_LOGFILE_MAX_URI_COMPONENT_LEN is the biggest of the three: we want to be able to
// detect buffer overflow, i.e. clipping by snprintf()!
char addr_buf[MG_MAX(SOCKADDR_NTOA_BUFSIZE, MG_MAX(MG_LOGFILE_MAX_URI_COMPONENT_LEN + 2, PATH_MAX))];
char *old_d = d;
int abuflen, abufoffset;
int parlen = (int)strtol(s + 2, &s_cont, 10);
if (parlen > len)
parlen = len;
if (s_cont)
s = s_cont;
*d = 0;
switch (*s) {
case 'P':
if (conn) {
unsigned short int port = get_socket_port(&conn->client.rsa);
if (port != 0) {
if (parlen <= 0) parlen = 1;
(void)mg_snprintf(conn, d, len, "%0*u", parlen, (unsigned int)port);
d += strlen(d);
}
}
goto replacement_done;
case 'C':
if (conn) {
sockaddr_to_string(addr_buf, sizeof(addr_buf), &conn->client.rsa);
goto copy_partial2dst;
}
goto replacement_done;
case 'p':
if (conn) {
unsigned short int port = get_socket_port(&conn->client.lsa);
if (port != 0) {
if (parlen <= 0) parlen = 1;
(void)mg_snprintf(conn, d, len, "%0*u", parlen, (unsigned int)port);
d += strlen(d);
}
}
goto replacement_done;
case 's':
if (conn) {
sockaddr_to_string(addr_buf, sizeof(addr_buf), &conn->client.lsa);
goto copy_partial2dst;
}
goto replacement_done;
case 'U':
case 'Q':
// filter URI so the result is a valid filepath piece without any format codes
if (conn && !is_empty(conn->request_info.uri)) {
const char *q, *u;
char h[33];
u = conn->request_info.uri;
q = strchr(u, '?');
if (*s == 'Q') {
if (!q) {
// empty query section: replace as empty string.
u = "";
} else {
u = q + 1;
q = NULL;
}
}
// limit the length to process:
mg_strlcpy(addr_buf, u, sizeof(addr_buf));
if (q && q - u < (int)sizeof(addr_buf)) {
addr_buf[q - u] = 0;
}
// limit the string inserted into the filepath template to MG_LOGFILE_MAX_URI_COMPONENT_LEN characters or whatever the template said itself:
if (parlen <= 0)
parlen = MG_LOGFILE_MAX_URI_COMPONENT_LEN;
else if (parlen > (int)sizeof(addr_buf) - 1)
parlen = (int)sizeof(addr_buf) - 1;
if ((int)strlen(addr_buf) > parlen) {
mg_md5(h, addr_buf, NULL); // hash the 'raw' (clipped) URI component; only calc the hash when it MIGHT be needed
} else {
h[0] = 0;
}
// yet paste the hash into the 'scrubbed' URI component ONLY when the scrubbed component is overlarge
if (powerscrub_filepath(addr_buf, 0) > parlen) {
mg_strlcpy(addr_buf + MG_MAX(0, parlen - 8), h, 8 + 1);
}
goto copy_partial2dst;
}
goto replacement_done;
copy_partial2dst:
// addr_buf[] is guaranteed to be filled when we get here
powerscrub_filepath(addr_buf, 0);
abuflen = (int)strlen(addr_buf);
if (parlen <= 0)
parlen = abuflen;
assert(len > 0);
if (parlen > len)
parlen = len;
// take the right-most part when we must clip: determine the offset
if (abuflen > parlen)
abufoffset = abuflen - parlen;
else
abufoffset = 0;
d += mg_strlcpy(d, addr_buf + abufoffset, parlen + 1);
replacement_done:
// and the %[?] macros ALWAYS produce at least ONE character output in the template,
// otherwise you get screwed up paths with, f.e. 'a/%[Q]/b' --> 'a//b':
if (d == old_d && d - fnbuf < PATH_MAX)
*d++ = '_';
s += 2;
continue;
default:
// illegal format code: keep as is, but destroy the %:
if (len >= 2) {
*d++ = '!';
*d++ = '[';
}
continue;
}
}
// keep format code for strftime to struggle with: copy as is for now:
// (fallthrough)
default:
*d++ = *s++;
continue;
}
break;
}
*d = 0;
tp = localtime(&timestamp);
if (0 == strftime(dst, dst_maxsize, fnbuf, tp)) {
// error transforming the template to a filename: fall back to the literal thing, but ditch all '%' in the path now:
d = dst;
s = fnbuf;
dst_maxsize--; // reserve space for the sentinel NUL
while (d - dst < (int)dst_maxsize && *s) {
if (*s == '%') {
*d++ = '_';
s++;
} else {
*d++ = *s++;
}
}
*d = 0;
}
return dst;
}
const char *mg_get_default_error_logfile_path(struct mg_connection *conn) {
// Once determined, we stick with the given logfile for the current connection.
//
// We clear the cached filepath when the connection goes on to process another request (request URL *MAY* be a parameter in the logfile path template).
if (!conn->error_logfile_path[0]) {
return mg_get_logfile_path(conn->error_logfile_path, ARRAY_SIZE(conn->error_logfile_path), get_conn_option(conn, ERROR_LOG_FILE), conn, conn->birth_time);
}
return conn->error_logfile_path;
}
const char *mg_get_default_access_logfile_path(struct mg_connection *conn) {
// Once determined, we stick with the given logfile for the current connection.
//
// We clear the cached filepath when the connection goes on to process another request (request URL *MAY* be a parameter in the logfile path template).
if (!conn->access_logfile_path[0]) {
return mg_get_logfile_path(conn->access_logfile_path, ARRAY_SIZE(conn->access_logfile_path), get_conn_option(conn, ACCESS_LOG_FILE), conn, conn->birth_time);
}
return conn->access_logfile_path;
}
// write arbitrary formatted string to the specified logfile
int mg_write2log_raw(struct mg_connection *conn, const char *logfile, time_t timestamp, const char *severity, const char *msg) {
int rv = 0;
if (!conn) conn = fc(NULL);
logfile = (logfile == NULL ? mg_get_default_error_logfile_path(conn) : logfile);
severity = (severity ? severity : "error");
conn->request_info.log_severity = severity;
conn->request_info.log_dstfile = logfile;
conn->request_info.log_timestamp = timestamp;
conn->request_info.log_message = msg;
// 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 (call_user(conn, MG_EVENT_LOG) == NULL) {
FILE *fp;
severity = conn->request_info.log_severity;
logfile = conn->request_info.log_dstfile;
timestamp = conn->request_info.log_timestamp;
msg = conn->request_info.log_message;
fp = mg_fopen((logfile ? logfile : "-"), "a+");
if (fp != NULL) {
flockfile(fp);
if (timestamp != 0) {
char tbuf[40];
rv += (int)fwrite(tbuf, sizeof(tbuf[0]), strftime(tbuf, ARRAY_SIZE(tbuf), "[%Y%m%dT%H%M%S] ", gmtime(&timestamp)), fp);
}
rv += fprintf(fp, "[%s] ", severity);
if (conn != NULL) {
char addr_buf[SOCKADDR_NTOA_BUFSIZE];
sockaddr_to_string(addr_buf, sizeof(addr_buf), &conn->client.rsa);
if (addr_buf[0]) {
rv += fprintf(fp, "[client %s] ", addr_buf);
}
}
if (conn != NULL && conn->request_info.request_method != NULL && conn->request_info.uri != NULL) {
rv += fprintf(fp, "%s %s: ",
conn->request_info.request_method,
conn->request_info.uri);
}
rv += fprintf(fp, "%s\n", msg ? msg : "???");
fflush(fp);
funlockfile(fp);
mg_fclose(fp);
}
}
conn->request_info.log_severity = NULL;
conn->request_info.log_dstfile = NULL;
conn->request_info.log_timestamp = 0;
conn->request_info.log_message = NULL;
return rv;
}
// Print log message to the opened error log stream.
void mg_write2log(struct mg_connection *conn, const char *logfile, time_t timestamp, const char *severity, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
mg_vwrite2log(conn, logfile, timestamp, severity, fmt, ap);
va_end(ap);
}
// Print log message to the opened error log stream.
void mg_vwrite2log(struct mg_connection *conn, const char *logfile, time_t timestamp, const char *severity, const char *fmt, va_list args) {
// handle the special case where there's nothing to do in terms of formatting in order to accept arbitrary input lengths then without the malloc/speed penalty:
if (!strchr(fmt, '%')) {
mg_write2log_raw(conn, logfile, timestamp, severity, fmt);
} else if (!strcmp(fmt, "%s")) {
fmt = va_arg(args, const char *);
mg_write2log_raw(conn, logfile, timestamp, severity, fmt);
} else {
char *buf = NULL;
// the absolute max we're going to support is a dump of 1MByte of text!
int n = mg_vasprintf(conn, &buf, MG_MAX_LOG_LINE_SIZE, fmt, args);
if (buf) {
// make sure the log'line' is NEWLINE terminated (or not) when clipped, depending on the format string input
if (n > 0 && fmt[strlen(fmt) - 1] != '\n' && buf[n - 1] == '\n')
buf[n - 1] = 0;
mg_write2log_raw(conn, logfile, timestamp, severity, buf);
free(buf);
} else {
mg_write2log_raw(conn, logfile, timestamp, severity, "!out of memory in mg_vwrite2log!\n");
}
}
}
// Print formatted error message to the opened error log stream.
void mg_cry_raw(struct mg_connection *conn, const char *msg) {
time_t timestamp = time(NULL);
(void)mg_write2log_raw(conn, NULL, timestamp, NULL, msg);
}
// Print error message to the opened error log stream.
void mg_cry(struct mg_connection *conn, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
mg_vcry(conn, fmt, ap);
va_end(ap);
}
// Print error message to the opened error log stream.
void mg_vcry(struct mg_connection *conn, const char *fmt, va_list args) {
time_t timestamp = time(NULL);
(void)mg_vwrite2log(conn, NULL, timestamp, NULL, fmt, args);
}
const char *mg_version(void) {
return MONGOOSE_VERSION;
}
const struct mg_request_info *mg_get_request_info(const struct mg_connection *conn) {
return conn ? &conn->request_info : NULL;
}
size_t mg_strlcpy(register char *dst, register const char *src, size_t n) {
char *b = dst;
for (; *src != '\0' && n > 1; n--) {
*dst++ = *src++;
}
*dst = '\0';
return dst - b;
}
size_t mg_strnlen(const char *src, size_t maxlen) {
const char *p = (const char *)memchr(src, 0, maxlen);
if (p)
return p - src;
return maxlen;
}
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;
}
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;
}
char * mg_strndup(const char *ptr, size_t len) {
char *p;
if ((p = (char *) malloc(len + 1)) != NULL) {
mg_strlcpy(p, ptr, len + 1);
}
return p;
}
char * mg_strdup(const char *str) {
return mg_strndup(str, strlen(str));
}
const char *mg_memfind(const char *haystack, size_t haysize, const char *needle, size_t needlesize)
{
if (haysize < needlesize || !haystack || !needle)
return NULL;
haysize -= needlesize - 1;
while (haysize > 0) {
const char *p = memchr(haystack, needle[0], haysize);
if (!p)
return NULL;
// as we fixed haysize we can now simply check if the needle is here:
if (!memcmp(p, needle, needlesize))
return p;
// be blunt; no BM-like speedup for this search...
p++;
haysize -= p - haystack;
haystack = p;
}
return NULL;
}
// Find location of case-insensitive needle string in haystack string.
// Return NULL if needle wasn't found.
const char *mg_stristr(const char *haystack, const char *needle)
{
int nc;
size_t needlesize;
if (!haystack || !needle || !*haystack || !*needle)
return NULL;
needlesize = strlen(needle);
for (nc = lowercase(needle); *haystack; haystack++) {
int hc = lowercase(haystack);
if (hc == nc && !mg_strncasecmp(needle + 1, haystack + 1, needlesize - 1))
return haystack;
// be blunt; no BM-like speedup for this search...
}
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.
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;
// shortcut for speed:
if (!strchr(fmt, '%')) {
return (int)mg_strlcpy(buf, fmt, buflen);
} else if (!strcmp(fmt, "%s")) {
fmt = va_arg(ap, const char *);
if (!fmt) fmt = "???";
return (int)mg_strlcpy(buf, fmt, buflen);
}
buf[0] = 0;
n = vsnprintf(buf, buflen, fmt, ap);
buf[buflen - 1] = 0;
if (n < 0) {
mg_cry(conn, "vsnprintf error / overflow");
// MSVC produces -1 on printf("%s", str) for very long 'str'!
n = (int)strlen(buf);
} 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;
}
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;
}
int mg_vsnq0printf(UNUSED_PARAMETER(struct mg_connection *unused), char *buf, size_t buflen, const char *fmt, va_list ap) {
int n;
if (buflen == 0)
return 0;
// shortcut for speed:
if (!strchr(fmt, '%')) {
return (int)mg_strlcpy(buf, fmt, buflen);
} else if (!strcmp(fmt, "%s")) {
fmt = va_arg(ap, const char *);
if (!fmt) fmt = "???";
return (int)mg_strlcpy(buf, fmt, buflen);
}
buf[0] = 0;
n = vsnprintf(buf, buflen, fmt, ap);
buf[buflen - 1] = 0;
if (n < 0) {
// MSVC produces -1 on printf("%s", str) for very long 'str'!
n = (int)strlen(buf);
} else if (n >= (int) buflen) {
n = (int) buflen - 1;
}
buf[n] = '\0';
return n;
}
int mg_snq0printf(struct mg_connection *conn, char *buf, size_t buflen,
const char *fmt, ...) {
va_list ap;
int n;
va_start(ap, fmt);
n = mg_vsnq0printf(conn, buf, buflen, fmt, ap);
va_end(ap);
return n;
}
int mg_vasprintf(UNUSED_PARAMETER(struct mg_connection *unused), char **buf_ref, size_t max_buflen,
const char *fmt, va_list ap) {
va_list aq;
int n;
int size = MG_BUF_LEN;
char *buf = (char *)malloc(size);
if (max_buflen == 0 || max_buflen > INT_MAX) {
max_buflen = INT_MAX;
}
#ifdef _MSC_VER
VA_COPY(aq, ap);
// adjust size for NUL and set it up so the fallback loop further below does not cycle
size = _vscprintf(fmt, aq) + 2;
if (size < 2)
size = MG_BUF_LEN;
va_end(aq);
#endif
if (size > (int)max_buflen)
size = (int)max_buflen;
buf = (char *)malloc(size);
if (buf == NULL) {
*buf_ref = NULL;
return 0;
}
VA_COPY(aq, ap);
while ((((n = vsnprintf(buf, size, fmt, aq)) < 0) || (n >= size - 1)) && (size < (int)max_buflen)) {
va_end(aq);
// forego the extra cost in realloc() due to memcpy(): use free+malloc instead:
free(buf);
size *= 4; /* fast-growing buffer: we don't want to try too many times */
if (size > (int)max_buflen)
size = (int)max_buflen;
buf = (char *)malloc(size);
if (buf == NULL) {
*buf_ref = NULL;
return 0;
}
VA_COPY(aq, ap);
}
va_end(aq);
if (n < 0) {
// MSVC produces -1 on printf("%s", str) for very long 'str'!
n = size - 1;
buf[n] = '\0';
n = (int)strlen(buf);
} else if (n >= size) {
// truncated output:
// mark the string as clipped and take optional line ending from fmt string
char *d;
const char *le = fmt + strlen(fmt) - 1;
n = size - 1;
d = buf + n - 6;
assert(le > fmt);
assert(d > buf);
while (le > fmt && d > buf && strchr("\r\n", *le)) {
le--;
d--;
}
strcpy(d, " (...)");
d += 6;
strcpy(d, le);
} else {
buf[n] = '\0';
}
*buf_ref = buf;
return n;
}
int mg_asprintf(struct mg_connection *conn, char **buf_ref, size_t max_buflen,
const char *fmt, ...) {
va_list ap;
int n;
va_start(ap, fmt);
n = mg_vasprintf(conn, buf_ref, max_buflen, fmt, ap);
va_end(ap);
return n;
}
// skip RFC2616 LWS: (SP | HT | line-continuation)
static char *skip_lws(char *str) {
str += strspn(str, " \t");
for(;;) {
if (*str && strchr("\r\n", *str)) {
// Check whether this is a true 'line continuation' in the sense of RFC2616 sec. 2.2:
char *p = str;
// Only look *one* CRLF ahead; be tolerant of MACs & UNIXes and non-std input:
// accept single CR/LF as well, so we can reuse this for file-based I/O too.
if (*p == '\r')
p++;
if (*p == '\n')
p++;
if (*p && strchr(" \t", *p)) {
str = p + strspn(p, " \t");
continue;
}
}
break;
}
return str;
}
// skip LWS + n*CRLF, i.e. skip until end-of-line (where CRLF is NOT a line continuation)
static char *skip_eolws(char *str) {
str = skip_lws(str);
str += strspn(str, "\r\n");
return str;
}
// skip ALL whitespace
static char *skip_allws(char *str) {
str += strspn(str, " \t\r\n");
return str;
}
// cf. RFC2616 sec. 2.2
static const char *rfc2616_token_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!#$%^&*_-+'.|";
static const char *rfc2616_nonws_separator_charset = "@()={}[]:;,<>?/\\"; // plus <">, SP, HT
// Return 0 on success, -1 on failure.
int mg_unquote_header_value(char *str, char *sentinel, char **end_ref) {
char *p, *te;
te = str;
// extract UNQUOTED field-value, where field-value is either a *single* quoted-string or a token cf. RFC2616 sec 2.2
if (*str != '"') {
// we're looking at a token
//
// make sure token is actually cf. RC2616 sec. 2.2:
p = str + strspn(str, rfc2616_token_charset);
if (end_ref) {
*end_ref = p;
*sentinel = *p;
*p = 0;
} else if (*p && skip_allws(p)[0]) {
// when we can't tell caller where token ended, we must fail when input isn't a single token
return -1;
}
if (p > str && te != str)
memmove(te, str, p - str);
te[p - str] = 0;
} else {
// unquote the value; unescape \\-escaped characters
p = str + 1;
while (*p) {
if (*p == '"') {
break;
} else if (*p == '\\') {
// RFC2616 sec 2.2 says you can escape char NUL too, but we don't allow it, as it opens a floodgate of assault scenarios
if (p[1] <= 0 || p[1] >= 128)
return -1;
p++;
*str++ = *p++;
} else if (*p >= ' ' && *p < 128) {
*str++ = *p++;
} else {
// MUST be escaped to be POSSIBLY legal inside quoted-string
return -1;
}
}
if (*p != '"')
return -1; // quoted-string not terminated correctly!
p++;
*str = 0;
if (end_ref) {
*end_ref = p;
*sentinel = *p;
} else if (*p && skip_allws(p)[0]) {
// when we can't tell caller where quoted-string ended, we must fail when input isn't a single quoted-string
return -1;
}
}
return 0;
}
// Extract a token,value pair from the string buffer. (See mongoose.h for full doc)
// Return 0 on success, -1 on failure.
int mg_extract_token_qstring_value(char **buf, char *sentinel, const char **token_ref, const char **value_ref, const char *empty_string) {
char *te, *ve, *begin_word, *end_word;
char sep, ec;
if (token_ref) *token_ref = NULL;
if (*value_ref) *value_ref = NULL;
begin_word = *buf;
begin_word = skip_allws(begin_word);
// token [LWS] "=" [LWS] [quoted-string]
// make sure token is actually cf. RC2616 sec. 2.2:
end_word = begin_word + strspn(begin_word, rfc2616_token_charset);
if (end_word == begin_word)
return -1;
te = end_word;
end_word = skip_lws(end_word);
// now we should see the mandatory "="
if (*end_word != '=')
return -1;
end_word++;
if (token_ref)
*token_ref = begin_word;
begin_word = end_word = skip_lws(end_word);
// do we have a OPTIONAL field-value?
// extract UNQUOTED field-value, where field-value is either a *single* quoted-string or a token cf. RFC2616 sec 2.2
if (!*begin_word || (*begin_word != '"' && !strchr(rfc2616_token_charset, *begin_word))) {
// no value specified; we're looking at either a separator, WS, CR/LF or a NUL (EOS)
if (value_ref)
*value_ref = empty_string;
ve = end_word;
sep = *end_word;
end_word = skip_eolws(end_word);
} else if (*begin_word != '"') {
if (value_ref)
*value_ref = begin_word;
// accept rfc2616_token_charset
end_word += strspn(begin_word, rfc2616_token_charset);
ve = end_word;
sep = *end_word;
end_word = skip_eolws(end_word);
} else {
if (value_ref)
*value_ref = begin_word;
// unquote the value; unescape \\-escaped characters
if (mg_unquote_header_value(begin_word, &sep, &end_word) < 0)
return -1;
// 'end_word' will now point 1 char past the ending <">-quote.
assert(sep ? *end_word : 1);
assert(!sep ? !*end_word : 1);
ve = end_word;
end_word = skip_eolws(end_word);
}
// If there's any content following this token,value pair and 'end_word'
// is not pointing at a separator or NUL (can't point at a WS or CR/LF
// as we skipped 'em all!), then back it up 1 char and report 'sep' as
// the separator instead of *end_word.
//
// This helps the caller identify both invalid follow-up, e.g. [a="b"c=d]
// where [c=d] wasn't preceded by a separator, and identify, and process,
// any LWS-surrounded non-WS separator such as ';', e.g. [a=b ;c=d] where
// the caller would be best served by pointing buf at the ';' instead of
// the preceding ' ' space.
//
// Also note that <">-quotes are NOT considered true separators here, so
// [a=b"c=d] won't cut it.
ec = sep;
if (end_word > ve)
ec = *end_word;
if (ec && !strchr(rfc2616_nonws_separator_charset, ec)) {
end_word--;
if (end_word > ve) {
// we did skip extra CRLF/WS at the end there, so we can use the last WS/CRLF as separator:
*sentinel = *end_word;
} else {
// use 'sep':
*sentinel = sep;
// If 'sep' is not a legal separator, then report an error:
// this simplifies sanity checks as the caller will now catch errors
// when an expected single token.value pair has invalid trailing data.
if (sep && !strchr(" \t\r\n", sep) && !strchr(rfc2616_nonws_separator_charset, sep)) {
*buf = end_word;
return -1;
}
}
} else if (end_word > ve) {
*sentinel = *end_word;
} else {
*sentinel = sep;
}
*buf = end_word;
*te = 0;
*ve = 0;
return 0;
}
// Extract a HTTP header token + (optional) value cf. RFC2616 sec. 4.2 and sec. 2.2.
// (See mongoose.h for full doc)
// Return 0 on success, -1 on failure.
int mg_extract_raw_http_header(char **buf, char **token_ref, char **value_ref) {
char *p, *te, *begin_word, *end_word;
enum {
RFC2616_TOKEN,
RFC2616_SEPARATOR,
RFC2616_QSTRING
} field_mode;
if (token_ref) *token_ref = NULL;
if (*value_ref) *value_ref = NULL;
begin_word = *buf;
// RFC2616: header = token LWS ":" LWS field-value CRLF
end_word = begin_word + strcspn(begin_word, ": \t\r\n");
if (end_word == begin_word)
return -1;
te = end_word;
end_word = skip_lws(end_word);
// now we should see the mandatory ":"
if (*end_word != ':')
return -1;
*te = 0;
end_word++;
// make sure token is actually cf. RC2616 sec. 2.2:
p = begin_word + strspn(begin_word, rfc2616_token_charset);
if (*p)
return -1;
else if (token_ref)
*token_ref = begin_word;
end_word = skip_lws(end_word);
begin_word = p = end_word;
// do we have an OPTIONAL field-value?
// extract RAW field-value: find the first CRLF which is not a line-continuation
while (*p && !strchr("\r\n", *p)) {
end_word = p + strcspn(p, " \t\r\n");
p = skip_lws(end_word);
}
p = skip_eolws(end_word);
*end_word = 0;
*buf = p;
// make sure field-value is actually cf. RC2616 sec. 2.2:
// convert any 'line continuation' to single SP space.
field_mode = RFC2616_TOKEN;
p = begin_word;
te++;
begin_word = te;
while (*p) {
int clen;
switch (field_mode) {
case RFC2616_TOKEN:
if (*p == '"') {
field_mode = RFC2616_QSTRING;
p++;
if (!*p)
return -1;
continue;
}
// accept rfc2616_token_charset and any separators; process LWS to single SP though
clen = strcspn(p, " \t\r\n");
if (clen)
memmove(begin_word, p, clen);
p += clen;
begin_word += clen;
if (!*p)
break;
field_mode = RFC2616_SEPARATOR;
continue;
case RFC2616_SEPARATOR:
clen = strspn(p, rfc2616_nonws_separator_charset);
if (clen)
memmove(begin_word, p, clen);
if (!clen) {
char *q = skip_lws(p);
if (q > p)
*begin_word++ = ' ';
else
// tokens (and quoted strings) MUST be separated by at least 1 separator
return -1;
p = q;
} else {
p += clen;
begin_word += clen;
}
field_mode = RFC2616_TOKEN;
continue;
case RFC2616_QSTRING:
*begin_word++ = '"';
while (*p) {
if (*p == '"') {
*begin_word++ = *p++;
field_mode = RFC2616_SEPARATOR;
break;
} else if (*p == '\\') {
if (p[1] < 0 || p[1] >= 128)
return -1;
*begin_word++ = *p++;
*begin_word++ = *p++;
} else if (*p >= ' ' && *p < 128) {
*begin_word++ = *p++;
} else if (*p == '\r' || *p == '\n') {
p = skip_lws(p);
*begin_word++ = ' ';
} else {
// MUST be escaped to be POSSIBLY legal inside quoted-string
return -1;
}
}
if (field_mode != RFC2616_SEPARATOR)
return -1; // quoted-string not terminated correctly!
continue;
}
}
*begin_word = 0;
if (value_ref) {
*value_ref = te;
}
return 0;
}
// Skip the characters until one of the delimiters characters found.
// 0-terminate resulting word. Skip the trailing delimiters if any.
// Advance pointer to buffer to the next word. Return found 0-terminated word.
static char *skip(char **buf, const char *delimiters) {
char *begin_word, *end_word;
begin_word = *buf;
end_word = begin_word + strcspn(begin_word, delimiters);
if (*end_word == '\0') {
*buf = end_word;
} else {
*end_word++ = 0;
*buf = end_word + strspn(end_word, delimiters);
}
return begin_word;
}
// Return HTTP header value, or NULL if not found.
static const char *get_header(const struct mg_header *headers, int num_headers,
const char *name) {
int i;
for (i = 0; i < num_headers; i++)
if (!mg_strcasecmp(name, headers[i].name))
return headers[i].value;
return NULL;
}
const char *mg_get_header(const struct mg_connection *conn, const char *name) {
return get_header(conn->request_info.http_headers, conn->request_info.num_headers, 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 (is_empty(list)) {
// End of the list
val->ptr = 0;
val->len = 0;
if (eq_val != NULL) {
eq_val->ptr = 0;
eq_val->len = 0;
}
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;
}
static int match_string(const char *pattern, int pattern_len, const char *str) {
const char *or_str;
int i, j, len, res;
if (pattern_len == -1)
pattern_len = (int)strlen(pattern);
if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) {
res = match_string(pattern, or_str - pattern, str);
return res > 0 ? res :
match_string(or_str + 1, (pattern + pattern_len) - (or_str + 1), str);
}
i = j = 0;
res = -1;
for (; i < pattern_len; i++, j++) {
if (pattern[i] == '?' && str[j] != '\0') {
continue;
} else if (pattern[i] == '$') {
return str[j] == '\0' ? j : -1;
} else if (pattern[i] == '*') {
i++;
if (pattern[i] == '*') {
i++;
len = (int)strlen(str + j);
} else {
len = (int)strcspn(str + j, "/");
}
if (i == pattern_len) {
return j + len;
}
do {
res = match_string(pattern + i, pattern_len - i, str + j + len);
} while (res == -1 && len-- > 0);
return res == -1 ? -1 : j + res + len;
} else if (pattern[i] != str[j]) {
return -1;
}
}
return j;
}
// return non-zero when the given status_code is a probably legal
// HTTP/WebSockets/... response code, i.e. is a value in the range
// 1xx..5xx, 1xxx..4xxx
static int is_legal_response_code(int status) {
return (status >= 100 && status < 600) || (status >= 1000 && status < 5000);
}
int mg_set_response_code(struct mg_connection *conn, int status) {
// 5xx error codes win over everything else (1xx/2xx/3xx/4xx/>=1000)
// errors (4xx/5xx) win over signals and 'good' codes (1xx/2xx/3xx)
// signals (3xx) win over 'good' codes (1xx/2xx) but then 2xx codes also win over 3xx and 1xx codes!
// 1xx signals win over 'good' codes (2xx) but then 2xx codes also win over 3xx and 1xx codes!
// WebSocket codes (>=1000) win over everything but internal failures (5xx)
int old_status = conn->request_info.status_code;
if (!is_legal_response_code(old_status)) {
conn->request_info.status_code = status;
} else {
int old_series = old_status / 100;
int series = status / 100;
assert(old_series >= 1);
assert(series >= 1);
switch (series) {
case 2: // 2xx
// only overrides lower 2xx codes:
if (old_series == 2 && old_status < status)
conn->request_info.status_code = status;
else if (old_series == 3 || old_series == 1)
conn->request_info.status_code = status;
break;
case 1: // 1xx
if (old_series == 2)
conn->request_info.status_code = status;
break;
case 3: // 3xx
case 4: // 4xx
if (old_series < series)
conn->request_info.status_code = status;
break;
default: // WebSocket series
case 5: // 5xx
if (old_series != 5)
conn->request_info.status_code = status;
else if (old_status == 500) // more specific 5xx errors win over generic 500
conn->request_info.status_code = status;
break;
}
}
return conn->request_info.status_code;
}
// 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(struct mg_connection *conn) {
const char *http_version = conn->request_info.http_version;
if (conn->is_client_conn) {
const char *header = mg_get_tx_header(conn, "Connection");
DEBUG_TRACE(0x0002,
("CLIENT: must_close: %d, keep-alive: %s, header: %s / ver: %s, stop: %d",
(int)conn->must_close,
get_conn_option(conn, ENABLE_KEEP_ALIVE),
header, http_version,
mg_get_stop_flag(conn->ctx)));
return (!conn->must_close &&
!mg_strcasecmp(get_conn_option(conn, ENABLE_KEEP_ALIVE), "yes") &&
(header == NULL ?
(http_version && !strcmp(http_version, "1.1")) :
!mg_strcasecmp(header, "keep-alive")) &&
conn->ctx->stop_flag == 0);
} else {
const char *header = mg_get_header(conn, "Connection");
DEBUG_TRACE(0x0002,
("must_close: %d, status: %d, legal: %d, keep-alive: %s, header: %s / ver: %s, stop: %d",
(int)conn->must_close,
(int)conn->request_info.status_code,
(int)is_legal_response_code(conn->request_info.status_code),
get_conn_option(conn, ENABLE_KEEP_ALIVE),
header, http_version,
mg_get_stop_flag(conn->ctx)));
return (!conn->must_close &&
conn->request_info.status_code != 401 &&
conn->request_info.status_code != 400 &&
// only okay persistence when we see legal response codes;
// anything else means we're foobarred ourselves already,
// so it's time to close and let them retry.
conn->request_info.status_code < 500 &&
is_legal_response_code(conn->request_info.status_code) &&
!mg_strcasecmp(get_conn_option(conn, ENABLE_KEEP_ALIVE), "yes") &&
(header == NULL ?
(http_version && !strcmp(http_version, "1.1")) :
!mg_strcasecmp(header, "keep-alive")) &&
conn->ctx->stop_flag == 0);
}
}
static const char *suggest_connection_header(struct mg_connection *conn) {
int rv = should_keep_alive(conn);
DEBUG_TRACE(0x0002, (" --> %s", (rv ? "keep-alive" : "close")));
return rv ? "keep-alive" : "close";
}
static const char *mg_get_allowed_methods(struct mg_connection *conn) {
const char *allowed = get_conn_option(conn, ALLOWED_METHODS);
if (is_empty(allowed))
allowed = "GET,POST,HEAD,PUT,DELETE,OPTIONS";
return allowed;
}
// Return negative value on error; otherwise number of bytes saved by compacting.
//
// NOTE: we MAY also be storing the URI+QUERY strings in the TX buffer,
// which we can quickly detect inside here and then we take care to keep those
// strings intact as well, though these will be relocated, just like the
// tx_headers[] themselves.
// This is extremely useful for client-side mg_connect()-based HTTP connections
// as available in mongoose. (See also: mg_write_http_request_head())
static int compact_tx_headers(struct mg_connection *conn) {
// C89 doesn't allow run-time dimensioned arrays so we use alloca() instead:
char *buf;
int i, n, l;
struct mg_header hdrs[ARRAY_SIZE(conn->request_info.response_headers)];
int cache_uri_query_str_in_txbuf;
char *tx_buf;
char *d;
int space;
if (!conn->buf_size) // mg_connect() creates connections without header buffer space
return -1;
if (!conn->tx_can_compact_hdrstore)
return 0;
tx_buf = conn->buf + conn->buf_size + CHUNK_HEADER_BUFSIZ;
buf = mg_malloca(conn->buf_size);
assert(buf);
if (!buf) goto fail_dramatically;
// detect whether the URI+QUERY are stored in the TX section:
cache_uri_query_str_in_txbuf = ((conn->request_info.uri >= tx_buf &&
conn->request_info.uri < tx_buf + conn->buf_size) ||
(conn->tx_can_compact_hdrstore & 2));
d = buf;
space = conn->buf_size;
// when they are, copy them to the start of SCRATCH space if they aren't there already
if (cache_uri_query_str_in_txbuf) {
l = (int)mg_strlcpy(d, conn->request_info.uri, space) + 1;
conn->request_info.uri = tx_buf;
d += l;
tx_buf += l;
space -= l;
if (is_empty(conn->request_info.query_string)) {
conn->request_info.query_string = "";
l = 0;
} else if (space > 0) {
l = (int)mg_strlcpy(d, conn->request_info.query_string, space) + 1;
} else {
goto fail_dramatically;
}
conn->request_info.query_string = tx_buf;
d += l;
tx_buf += l;
space -= l;
if (space < 6)
goto fail_dramatically;
// remember offset:
cache_uri_query_str_in_txbuf = conn->buf_size - space;
}
// now perform the header compaction process:
n = conn->request_info.num_response_headers;
for (i = 0; i < n; i++) {
l = (int)mg_strlcpy(d, conn->request_info.response_headers[i].name, space) + 1;
d[l] = conn->request_info.response_headers[i].name[l]; // copy 'edited/added' marker too!
// calc new name+value pointers for when we're done with the compact cycle:
hdrs[i].name = tx_buf;
l++;
d += l;
tx_buf += l;
space -= l;
if (space <= 2)
goto fail_dramatically;
l = (int)mg_strlcpy(d, conn->request_info.response_headers[i].value, space);
hdrs[i].value = tx_buf;
l += 2;
d += l;
tx_buf += l;
space -= l;
if (space <= 2)
goto fail_dramatically;
}
conn->tx_can_compact_hdrstore = 0;
n = conn->buf_size - space;
memcpy(conn->request_info.response_headers, hdrs, sizeof(hdrs));
tx_buf = conn->buf + conn->buf_size + CHUNK_HEADER_BUFSIZ;
memcpy(tx_buf, buf, n);
l = conn->tx_headers_len - n; // how many bytes did we 'gain' by compacting?
conn->tx_headers_len = n;
// delta can be negative when URI+query_string were pulled into the buffer space!
if (l < 0)
l = 0;
mg_freea(buf);
return l;
fail_dramatically:
mg_freea(buf);
return -1;
}
int mg_remove_response_header(struct mg_connection *conn, const char *tag) {
int i = -1;
int found = 0;
if (is_empty(tag) || !conn->buf_size) // mg_connect() creates connections without header buffer space
return -1;
// check whether tag is already listed in the set:
for (i = conn->request_info.num_response_headers; i-- > 0; ) {
const char *key = conn->request_info.response_headers[i].name;
if (!mg_strcasecmp(tag, key)) {
// ditch the key + value:
found++;
conn->request_info.response_headers[i].name = NULL;
}
}
// keep the order of the keys intact: compact the header set once we've removed all 'tag' occurrences
if (found) {
int n = conn->request_info.num_response_headers;
for (i = 0; i < n; i++) {
if (!conn->request_info.response_headers[i].name) {
int j;
for (j = i + 1; j < n; j++) {
if (conn->request_info.response_headers[j].name) {
conn->request_info.response_headers[i++] = conn->request_info.response_headers[j];
}
}
break;
}
}
conn->request_info.num_response_headers = i;
conn->tx_can_compact_hdrstore |= 1;
}
return found;
}
int mg_add_response_header(struct mg_connection *conn, int force_add, const char *tag, const char *value_fmt, ...) {
va_list ap;
int rv;
va_start(ap, value_fmt);
rv = mg_vadd_response_header(conn, force_add, tag, value_fmt, ap);
va_end(ap);
return rv;
}
int mg_vadd_response_header(struct mg_connection *conn, int force_add, const char *tag, const char *value_fmt, va_list ap) {
int i = -1;
int n, space;
char *dst;
char *bufbase;
if (is_empty(tag) || !conn->buf_size) // mg_connect() creates connections without header buffer space
return -1;
if (!value_fmt)
value_fmt = "";
if (mg_have_headers_been_sent(conn) && !conn->tx_is_in_chunked_mode) {
mg_cry(conn, "%s: can't add headers after the header has been sent and the connection is NOT in chunked tranfer mode for outgoing traffic", __func__);
return -1;
}
bufbase = conn->buf + conn->buf_size + CHUNK_HEADER_BUFSIZ;
dst = bufbase + conn->tx_headers_len;
space = conn->buf_size - conn->tx_headers_len;
if (!force_add) {
// check whether tag is already listed in the set:
for (i = conn->request_info.num_response_headers; i-- > 0; ) {
const char *key = conn->request_info.response_headers[i].name;
if (!mg_strcasecmp(tag, key)) {
// re-use the tag, ditch the value:
conn->tx_can_compact_hdrstore |= 1;
(&bufbase[key - bufbase])[strlen(key) + 1] = '!'; // mark tag as edited/added
break;
}
}
}
if (i < 0) { // this tag wasn't found: add it
force_add = 1;
i = conn->request_info.num_response_headers;
if (i >= (int)ARRAY_SIZE(conn->request_info.response_headers)) {
mg_cry(conn, "%s: too many headers", __func__);
return -1;
}
for(;;) {
n = (int)mg_strlcpy(dst, tag, space);
if (n + 6 < space) // NUL+[?] + empty value + NUL+[?]+[?]+[?]
break;
// we need to compact and retry, and when it still fails then, we're toast.
if (compact_tx_headers(conn) <= 0) {
mg_cry(conn, "%s: header buffer overflow for key %s", __func__, tag);
return -1;
}
dst = bufbase + conn->tx_headers_len;
space = conn->buf_size - conn->tx_headers_len;
}
conn->request_info.response_headers[i].name = dst;
// To make sure that the optional compact routine
// in the next loop (write tag value) keeps this TAG,
// we need to make it a valid entry and account of it!
//
// To do so, we fake a NIL value for now, making the
// 'set value' loop below an UPDATE operation always.
conn->request_info.response_headers[i].value = dst + n; // point at the NUL sentinel
assert(i <= conn->request_info.num_response_headers);
if (i == conn->request_info.num_response_headers)
conn->request_info.num_response_headers++;
dst[n + 1] = '!'; // mark tag as edited/added
n += 2; // include NUL+[?] sentinel in count
conn->tx_headers_len += n;
dst += n;
space -= n;
}
// now store the value:
for(;;) {
n = mg_vsnq0printf(conn, dst, space, value_fmt, ap);
// n==0 is also possible when snprintf() fails dramatically (see notes in mg_snq0printf() et al)
if (n + 4 < space && n > 0) // + NUL+[?]+[?]+[?]
break;
// only accept n==0 when the value_fmt is empty and there's nothing to compact or (heuristic!) when there's 'sufficient space' to write:
if (n == 0 && 4 < space && (!conn->tx_can_compact_hdrstore || is_empty(value_fmt) || space >= MG_MAX(MG_BUF_LEN, conn->buf_size / 4)))
break;
// we need to compact and retry, and when it still fails then, we're toast.
if (compact_tx_headers(conn) <= 0) {
mg_cry(conn, "%s: header buffer overflow for key %s", __func__, tag);
return -1;
}
dst = bufbase + conn->tx_headers_len;
space = conn->buf_size - conn->tx_headers_len;
}
conn->request_info.response_headers[i].value = dst;
assert(i < conn->request_info.num_response_headers);
n += 2; // include NUL+[?] sentinel in count
conn->tx_headers_len += n;
//dst += n;
//space -= n;
// now we know we still have two extra bytes free space; this is used in mg_write_http_response_head()
// check for special headers: Content-Length and 'Transfer-Encoding: chunked' are mutually exclusive
if (mg_strcasecmp("Content-Length", tag) == 0) {
mg_remove_response_header(conn, "Transfer-Encoding");
} else if (mg_strcasecmp("Transfer-Encoding", tag) == 0 &&
mg_stristr(dst, "chunked")) {
mg_remove_response_header(conn, "Content-Length");
}
return 0;
}
const char *mg_get_response_header(const struct mg_connection *conn, const char *tag) {
int i;
if (is_empty(tag) || !conn->buf_size) // mg_connect() creates connections without header buffer space
return NULL;
for (i = conn->request_info.num_response_headers; i-- > 0; ) {
const char *key = conn->request_info.response_headers[i].name;
if (!mg_strcasecmp(tag, key)) {
return conn->request_info.response_headers[i].value;
}
}
return NULL;
}
static int write_http_head(struct mg_connection *conn, PRINTF_FORMAT_STRING(const char *first_line_fmt), ...) PRINTF_ARGS(2, 3);
// Return number of bytes sent; return 0 when nothing was done; -1 on error.
static int write_http_head(struct mg_connection *conn, const char *first_line_fmt, ...) {
int i, n, rv, rv2, tx_len;
char *buf;
const char *te_tag;
const char *cl_tag;
const char *ka_tag;
const char *cls;
va_list ap;
const char *http_version = conn->request_info.http_version;
if (mg_have_headers_been_sent(conn))
return 0;
assert(!is_empty(http_version));
// make sure must_close state and Connection: output are in sync
ka_tag = mg_get_response_header(conn, "Connection");
if (!conn->must_close) {
if (ka_tag && mg_strcasecmp(ka_tag, "close") == 0)
conn->must_close = 1;
}
cls = suggest_connection_header(conn);
// update/set the Connection: keep-alive header as we now know the Status Code:
if (!ka_tag || mg_strcasecmp(ka_tag, cls)) {
if (mg_add_response_header(conn, 0, "Connection", cls))
return -1;
}
// detect whether we're going to send in 'chunked' or 'full' mode:
// check for the appropriate headers
// or whether the user already set the chunked mode explicitly.
//
// Content-Length ALWAYS wins over chunked transfer mode.
//
// When you transmit in HTTP/1.1 and didn't specify the Content-Length
// header, then chunked transfer mode will be assumed implicitly.
te_tag = mg_get_response_header(conn, "Transfer-Encoding");
cl_tag = mg_get_response_header(conn, "Content-Length");
if (!is_empty(cl_tag)) {
assert(is_empty(te_tag)); // mg_add_response_header() must've taken care of this before
mg_set_tx_mode(conn, MG_IOMODE_STANDARD);
} else if (strcmp(http_version, "1.1") >= 0) {
// there's no absolute need to set 'chunked' transfer mode when
// we're closing the connection after this request (so you'd get
// HTTP/1.0 alike GET requests then); we do this to allow basic
// GET requests to be sent to non-fully HTTP/1.1 compliant servers,
// e.g. other (older or 'vanilla') mongoose servers, without them
// barfing a hairball over the chunked headers -- when they don't
// cope with the Transfer-Encoding header (e.g. older/vanilla mongoose).
//
// This is a KNOWN DEVIATION from the strict interpretation of
// the HTTP/1.1 spec. It is harmless.
if (conn->tx_is_in_chunked_mode || !conn->must_close ||
mg_strcasecmp(conn->request_info.request_method, "GET")) {
if (is_empty(te_tag)) {
if (mg_add_response_header(conn, 0, "Transfer-Encoding", "chunked"))
return -1;
}
if (!conn->tx_is_in_chunked_mode) {
mg_set_tx_mode(conn, MG_IOMODE_CHUNKED_HEADER);
}
}
} else if (!conn->must_close) {
// HTTP/1.0, but not marked as a 'closing' connection yet!
conn->must_close = 1;
if (mg_add_response_header(conn, 0, "Connection", "close"))
return -1;
}
/*
The code further below expects all headers to be stored in memory 'in order'.
This assumption holds when headers have only been added, never
removed or replaced, OR when compact_tx_headers() has run
after the last replace/remove operation.
*/
if (compact_tx_headers(conn) < 0)
return -1;
va_start(ap, first_line_fmt);
rv = mg_vprintf(conn, first_line_fmt, ap);
va_end(ap);
if (rv <= 0)
return -1; // malformed first line or transmit failure.
/*
Once we are sure of the header order assumption, this becomes an
'in place' operation, where NUL sentinels are temporarily replaced
with ": " and "\r\n" respectively.
Since the assumption above is now assured, we know that the very
first header starts at the beginning of the buffer, after the optionally
stored uri+query_string!
*/
n = conn->request_info.num_response_headers;
if (n) {
buf = conn->buf + conn->buf_size + CHUNK_HEADER_BUFSIZ;
assert(conn->request_info.response_headers[0].name >= conn->buf + conn->buf_size + CHUNK_HEADER_BUFSIZ);
assert(conn->request_info.response_headers[0].name < conn->buf + conn->buf_size + CHUNK_HEADER_BUFSIZ + conn->buf_size);
conn->request_info.response_headers[0].value[-2] = ':';
conn->request_info.response_headers[0].value[-1] = ' ';
for (i = 1; i < n; i++) {
struct mg_header *h = conn->request_info.response_headers + i;
h->name[-2] = '\r';
h->name[-1] = '\n';
h->value[-2] = ':';
h->value[-1] = ' ';
}
buf[conn->tx_headers_len - 2] = '\r';
buf[conn->tx_headers_len - 1] = '\n';
assert(conn->tx_headers_len + 2 + (buf - conn->buf - conn->buf_size - CHUNK_HEADER_BUFSIZ) <= conn->buf_size);
buf[conn->tx_headers_len] = '\r';
buf[conn->tx_headers_len + 1] = '\n';
tx_len = conn->tx_headers_len + 2 - (conn->request_info.response_headers[0].name - buf);
rv2 = mg_write(conn, conn->request_info.response_headers[0].name, tx_len);
if (rv2 != tx_len)
rv = -1;
else
rv += rv2;
/*
Error or success, always restore the header set to its original
glory.
*/
conn->request_info.response_headers[0].value[-2] = 0;
for (i = 1; i < n; i++) {
struct mg_header *h = conn->request_info.response_headers + i;
h->name[-2] = 0;
h->value[-2] = 0;
}
buf[conn->tx_headers_len - 2] = 0;
} else {
rv2 = mg_write(conn, "\r\n", 2);
if (rv2 != 2)
rv = -1;
else
rv += rv2;
}
mg_mark_end_of_header_transmission(conn);
return rv;
}
int mg_write_http_response_head(struct mg_connection *conn, int status_code, const char *status_text) {
const char *http_version = conn->request_info.http_version;
if (is_empty(http_version))
http_version = conn->request_info.http_version = "1.1";
if (status_code <= 0)
status_code = conn->request_info.status_code;
else
status_code = mg_set_response_code(conn, status_code);
if (is_empty(status_text))
status_text = mg_get_response_code_text(status_code);
mg_set_response_code(conn, status_code);
return write_http_head(conn, "HTTP/%s %d %s\r\n", http_version, status_code, status_text);
}
/*
Send HTTP error response headers, if we still can. Log the error anyway.
'reason' may be NULL, in which case the default RFC2616 response code text will be used instead.
'fmt' + args is the content sent along as error report (request response).
*/
static void vsend_http_error(struct mg_connection *conn, int status,
const char *reason, const char *fmt, va_list ap) {
char buf[MG_BUF_LEN];
int len;
int custom_len;
if (!reason) {
reason = mg_get_response_code_text(status);
}
buf[0] = '\0';
custom_len = 0;
len = mg_snprintf(conn, buf, sizeof(buf) - 2, "Error %d: %s", status, reason);
if (!is_empty(fmt)) {
custom_len = mg_vsnprintf(conn, buf + len + 1, sizeof(buf) - len - 1, fmt, ap);
if (custom_len > 0) {
buf[len++] ='\t';
len += custom_len;
}
assert(len == (int)strlen(buf));
}
status = mg_set_response_code(conn, status);
conn->request_info.status_custom_description = buf;
if (status == 405) {
mg_add_response_header(conn, 0, "Allow", mg_get_allowed_methods(conn));
}
if (call_user(conn, MG_HTTP_ERROR) == NULL) {
char *p;
// also override the 'reason' text when the status code
// didn't make it or was altered in the callback:
if (status != conn->request_info.status_code)
reason = mg_get_response_code_text(conn->request_info.status_code);
status = conn->request_info.status_code;
if (conn->request_info.status_custom_description)
len = (int)strlen(conn->request_info.status_custom_description);
else
conn->request_info.status_custom_description = buf;
assert(len == (int)strlen(conn->request_info.status_custom_description));
p = strchr(conn->request_info.status_custom_description, '\t');
if (p)
*p = 0;
mg_cry(conn, "%s: %s (HTTP v%s: %s %s%s%s) %s",
__func__, conn->request_info.status_custom_description,
(conn->request_info.http_version ? conn->request_info.http_version : "(unknown)"),
(conn->request_info.request_method ? conn->request_info.request_method : "???"),
(conn->request_info.uri ? conn->request_info.uri : "???"),
(!is_empty(conn->request_info.query_string) ? "?" : ""),
(!is_empty(conn->request_info.query_string) ? conn->request_info.query_string : ""),
(p ? p + 1 : ""));
// Errors 1xx, 204 and 304 MUST NOT send a body
if (status > 199 && status != 204 && status != 304) {
if (p)
*p = '\n';
} else {
len = 0;
}
DEBUG_TRACE(0x0401, ("[%s]", conn->request_info.status_custom_description));
// do NOT produce the nested error (allow parent to send its own error page/info to client):
if (!mg_have_headers_been_sent(conn) && !mg_is_producing_nested_page(conn)) {
const char *errflist = get_conn_option(conn, ERROR_FILE);
struct vec filename_vec;
struct vec status_vec;
// Traverse error files list. If an entry matches the given status_code, break the loop.
// '0' is treated as a wildcard match.
while ((errflist = next_option(errflist, &status_vec, &filename_vec)) != NULL) {
int v = atoi(status_vec.ptr);
if (v == 0 || v == status)
break;
}
// output basic HTTP response when either no error content is allowed (len == 0)
// or when the error page production failed and hasn't yet written the headers itself.
if (len == 0 ||
// no use making the effort of a custom page production when the connection is severely clobbered
conn->request_len <= 0 ||
conn->client.write_error ||
conn->client.read_error ||
mg_produce_nested_page(conn, filename_vec.ptr, filename_vec.len)) {
if (!mg_have_headers_been_sent(conn)) {
/* issue #229: Only include the content-length if there is a response body.
Otherwise an incorrect Content-Type generates a warning in
some browsers when a static file request returns a 304
"not modified" error. */
if (len > 0) {
mg_add_response_header(conn, 0, "Content-Length", "%d", len);
mg_add_response_header(conn, 0, "Content-Type", "text/plain");
}
//mg_add_response_header(conn, 0, "Connection", suggest_connection_header(conn)); -- not needed any longer
mg_write_http_response_head(conn, status, reason);
if (len > 0) {
assert(len == (int)strlen(conn->request_info.status_custom_description));
if (mg_write(conn, conn->request_info.status_custom_description, len) != len) {
conn->must_close = 1;
}
}
if (mg_flush(conn) != 0) {
conn->must_close = 1;
}
} else {
conn->must_close = 1;
}
}
} else if (mg_is_producing_nested_page(conn)) {
// mark nested error anyhow
conn->nested_err_or_pagereq_count = 2;
}
} else if (mg_is_producing_nested_page(conn)) {
// mark nested error anyhow
conn->nested_err_or_pagereq_count = 2;
}
// kill lingering reference to local storage:
conn->request_info.status_custom_description = NULL;
}
static void send_http_error(struct mg_connection *conn, int status,
const char *reason, 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, ...) {
va_list ap;
va_start(ap, fmt);
vsend_http_error(conn, status, reason, fmt, ap);
va_end(ap);
}
#if defined(_WIN32) && !defined(__SYMBIAN32__)
#if !defined(HAVE_PTHREAD)
int pthread_mutex_init(pthread_mutex_t *mutex, UNUSED_PARAMETER(void *unused)) {
*mutex = CreateMutex(NULL, FALSE, NULL);
return *mutex == NULL ? -1 : 0;
}
int pthread_mutex_destroy(pthread_mutex_t *mutex) {
return CloseHandle(*mutex) == 0 ? -1 : 0;
}
int pthread_mutex_lock(pthread_mutex_t *mutex) {
return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0 ? 0 : -1;
}
int pthread_mutex_unlock(pthread_mutex_t *mutex) {
return ReleaseMutex(*mutex) == 0 ? -1 : 0;
}
int pthread_spin_init(pthread_spinlock_t *lock, UNUSED_PARAMETER(int unused)) {
return pthread_mutex_init(lock, 0);
}
int pthread_spin_destroy(pthread_spinlock_t *lock) {
return pthread_mutex_destroy(lock);
}
int pthread_spin_lock(pthread_spinlock_t *lock) {
return pthread_mutex_lock(lock);
}
//int pthread_spin_trylock(pthread_spinlock_t *lock) {
// ...
//}
int pthread_spin_unlock(pthread_spinlock_t *lock) {
return pthread_mutex_unlock(lock);
}
int pthread_cond_init(pthread_cond_t *cv, UNUSED_PARAMETER(const void *unused)) {
cv->signal = CreateEvent(NULL, FALSE, FALSE, NULL);
cv->broadcast = CreateEvent(NULL, TRUE, FALSE, NULL);
return cv->signal != NULL && cv->broadcast != NULL ? 0 : -1;
}
int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) {
HANDLE handles[] = {cv->signal, cv->broadcast};
ReleaseMutex(*mutex);
WaitForMultipleObjects(2, handles, FALSE, INFINITE);
return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0 ? 0 : -1;
}
int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mutex, const struct timespec *abstime) {
HANDLE handles[] = {cv->signal, cv->broadcast};
DWORD period = abstime->tv_sec * 1000 + abstime->tv_nsec / 1000000;
DWORD rv;
ReleaseMutex(*mutex);
rv = WaitForMultipleObjects(2, handles, FALSE, period);
return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0 ? (rv == WAIT_TIMEOUT ? ETIMEOUT : 0) : -1;
}
int pthread_cond_signal(pthread_cond_t *cv) {
return SetEvent(cv->signal) == 0 ? -1 : 0;
}
int pthread_cond_broadcast(pthread_cond_t *cv) {
// Implementation with PulseEvent() has race condition, see
// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
return PulseEvent(cv->broadcast) == 0 ? -1 : 0;
}
int pthread_cond_destroy(pthread_cond_t *cv) {
return CloseHandle(cv->signal) && CloseHandle(cv->broadcast) ? 0 : -1;
}
pthread_t pthread_self(void) {
return GetCurrentThreadId();
}
struct __pthread_thread_func_args {
mg_thread_func_t func;
void *arg;
void *return_value;
unsigned return_value_set: 1;
pthread_t thread_id;
HANDLE thread_h;
struct __pthread_thread_func_args *next;
};
static pthread_spinlock_t __pthread_list_lock;
static struct __pthread_thread_func_args *__pthread_list = NULL;
static unsigned int __stdcall __pthread_starter_func(void *arg) {
struct __pthread_thread_func_args *a = (struct __pthread_thread_func_args *)arg;
DWORD id = GetCurrentThreadId();
void *rv;
struct __pthread_thread_func_args *l, *o;
pthread_spin_lock(&__pthread_list_lock);
l = __pthread_list;
while (l && l->thread_id != id) {
l = l->next;
}
pthread_spin_unlock(&__pthread_list_lock);
assert(l);
assert(l == a);
rv = a->func(a->arg);
if (a->return_value_set)
rv = a->return_value;
pthread_spin_lock(&__pthread_list_lock);
o = NULL;
l = __pthread_list;
while (l && l->thread_id != id) {
o = l;
l = l->next;
}
if (o && l) {
o->next = l->next;
l->next = NULL;
} else if (l) {
assert(l == __pthread_list);
__pthread_list = l->next;
l->next = NULL;
}
pthread_spin_unlock(&__pthread_list_lock);
_endthreadex((unsigned int)rv);
CloseHandle(l->thread_h);
free(l);
return (unsigned int)rv;
}
int pthread_create(pthread_t * tid, UNUSED_PARAMETER(const pthread_attr_t * attr), mg_thread_func_t start, void *arg) {
struct __pthread_thread_func_args *a = calloc(1, sizeof(*a));
unsigned int t;
uintptr_t rv;
if (!a)
return -1;
a->arg = arg;
a->func = start;
if (!__pthread_list) {
pthread_spin_init(&__pthread_list_lock, PTHREAD_PROCESS_PRIVATE);
}
rv = _beginthreadex(NULL, 0, __pthread_starter_func, a, CREATE_SUSPENDED, &t);
if (rv != 0) {
*tid = t;
a->thread_id = t;
a->thread_h = (HANDLE)rv;
pthread_spin_lock(&__pthread_list_lock);
a->next = __pthread_list;
__pthread_list = a;
pthread_spin_unlock(&__pthread_list_lock);
ResumeThread(a->thread_h);
}
return (rv != 0) ? 0 : errno;
}
void pthread_exit(void *value_ptr) {
DWORD id = GetCurrentThreadId();
struct __pthread_thread_func_args *l;
pthread_spin_lock(&__pthread_list_lock);
l = __pthread_list;
while (l && l->thread_id != id) {
l = l->next;
}
pthread_spin_unlock(&__pthread_list_lock);
assert(l);
if (!l->return_value_set) {
l->return_value = value_ptr;
l->return_value_set = 1;
}
}
// rwlock types have been moved to mongoose_sys_porting.h
#if USE_SRWLOCK // Windows 7 / Server 2008 with the correct header files, i.e. this also 'fixes' MingW casualties
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr) {
InitializeSRWLock(&rwlock->lock);
return 0;
}
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock) {
// empty
return 0;
}
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock) {
AcquireSRWLockShared(&rwlock->lock);
rwlock->rw = 0;
return 0;
}
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock) {
AcquireSRWLockExclusive(&rwlock->lock);
rwlock->rw = 1;
return 0;
}
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock) {
if (rwlock->rw) {
ReleaseSRWLockExclusive(&rwlock->lock);
} else {
ReleaseSRWLockShared(&rwlock->lock);
}
return 0;
}
#else // emulate methods for other Win systems / compiler platforms - use a very blunt approach.
int pthread_rwlock_init(pthread_rwlock_t *rwlock, UNUSED_PARAMETER(const pthread_rwlockattr_t *attr)) {
return pthread_mutex_init(&rwlock->mutex, NULL);
}
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock) {
return pthread_mutex_destroy(&rwlock->mutex);
}
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock) {
int rv = pthread_mutex_lock(&rwlock->mutex);
rwlock->rw = 0;
return rv;
}
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock) {
int rv = pthread_mutex_lock(&rwlock->mutex);
rwlock->rw = 1;
return rv;
}
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock) {
return pthread_mutex_unlock(&rwlock->mutex);
}
#endif
#endif
// 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], *p;
mg_strlcpy(buf, path, sizeof(buf));
change_slashes_to_backslashes(buf);
// Point p to the end of the file name
p = buf + strlen(buf) - 1;
// Trim trailing backslash character
while (p > buf && *p == '\\' && p[-1] != ':') {
*p-- = '\0';
}
// Protect from CGI code disclosure.
// This is very nasty hole. Windows happily opens files with
// some garbage in the end of file name. So fopen("a.cgi ", "r")
// actually opens "a.cgi", and does not return an error!
if (*p == 0x20 || // No space at the end
(*p == 0x2e && p > buf) || // No '.' but allow '.' as full path
*p == 0x2b || // No '+'
(*p & ~0x7f)) { // And generally no non-ASCII chars
mg_cry(NULL, "Rejecting suspicious path: [%s]", buf);
wbuf[0] = L'\0';
} else {
// 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)); // <bel>: fix otherwise an "uninitialized memory read in WideCharToMultiByte" occurs
if (!MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len) ||
!WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
NULL, NULL) ||
strcmp(buf, buf2) != 0) {
mg_cry(NULL, "Rejecting malicious path: [%s]", buf);
wbuf[0] = L'\0';
}
}
}
#if defined(_WIN32_WCE)
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;
}
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;
}
struct tm *gmtime(const time_t *ptime, struct tm *ptm) {
// FIXME(lsm): fix this.
return localtime(ptime, ptm);
}
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
int mg_mk_fullpath(char *buf, size_t buf_len) {
wchar_t woldbuf[PATH_MAX];
wchar_t wnewbuf[PATH_MAX];
int pos;
to_unicode(buf, woldbuf, ARRAY_SIZE(woldbuf));
pos = GetFullPathNameW(woldbuf, ARRAY_SIZE(wnewbuf), wnewbuf, NULL);
assert(pos < ARRAY_SIZE(wnewbuf));
wnewbuf[pos] = 0;
if (!WideCharToMultiByte(CP_UTF8, 0, wnewbuf, pos + 1 /* include NUL sentinel */, buf, (int)buf_len, NULL, NULL))
return -1;
pos = (int)strlen(buf);
while (pos-- > 0) {
if (buf[pos] == '\\')
buf[pos] = '/';
}
return 0;
}
int mg_rename(const char* oldname, const char* newname) {
wchar_t woldbuf[PATH_MAX];
wchar_t wnewbuf[PATH_MAX];
to_unicode(oldname, woldbuf, ARRAY_SIZE(woldbuf));
to_unicode(newname, wnewbuf, ARRAY_SIZE(wnewbuf));
return MoveFileW(woldbuf, wnewbuf) ? 0 : -1;
}
FILE *mg_fopen(const char *path, const char *mode) {
wchar_t wbuf[PATH_MAX], wmode[20];
if (!path || !path[0] || !mode || !mode[0]) {
return NULL;
}
if (0 == strcmp("-", path) && 0 == strcmp("a+", mode)) {
return stderr;
}
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
if (!MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode)))
return NULL;
// recursively create the included path when the file is to be created / appended to:
if (wmode[wcscspn(wmode, L"aw")]) {
size_t i;
// skip UNC path starters like '\\?\'
i = wcsspn(wbuf, L"\\:?");
// and skip drive specs like 'C:'
if (i == 0 && wbuf[i] && wbuf[i+1] == L':') {
i = 2;
i += wcsspn(wbuf + i, L"\\");
}
i += wcscspn(wbuf + i, L"\\");
while (wbuf[i]) {
int rv;
// skip ./ and ../ sections; CAVEAT: due to code simplification, we also skip entries like 'XYZ./' (note the dot at the end) which are flaky path specs anyway
if (wbuf[i - 1] == L'.') {
wbuf[i++] = L'\\';
i += wcscspn(wbuf + i, L"\\");
continue;
}
wbuf[i] = 0;
rv = _wmkdir(wbuf);
wbuf[i++] = L'\\';
if (0 != rv && errno != EEXIST)
break;
i += wcscspn(wbuf + i, L"\\");
}
}
return _wfopen(wbuf, wmode);
}
int mg_stat(const char *path, struct mgstat *stp) {
int ok = -1; // Error
wchar_t wbuf[PATH_MAX];
WIN32_FILE_ATTRIBUTE_DATA info;
if (!path || !stp)
return -1;
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {
stp->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
stp->mtime = SYS2UNIX_TIME(info.ftLastWriteTime.dwLowDateTime,
info.ftLastWriteTime.dwHighDateTime);
stp->is_directory =
info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
ok = 0; // Success
}
return ok;
}
int mg_remove(const char *path) {
wchar_t wbuf[PATH_MAX];
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
return DeleteFileW(wbuf) ? 0 : -1;
}
int mg_mkdir(const char *path, int mode) {
wchar_t wbuf[PATH_MAX];
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
mode = 0; // Unused
return CreateDirectoryW(wbuf, NULL) ? 0 : -1;
}
// Implementation of POSIX opendir/closedir/readdir for Windows.
static DIR * opendir(const char *name) {
DIR *dir = NULL;
wchar_t wpath[PATH_MAX];
DWORD attrs;
if (name == NULL) {
SetLastError(ERROR_BAD_ARGUMENTS);
} else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
} else {
to_unicode(name, wpath, ARRAY_SIZE(wpath));
attrs = GetFileAttributesW(wpath);
if (attrs != 0xFFFFFFFF &&
((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
(void) wcscat(wpath, L"\\*");
dir->handle = FindFirstFileW(wpath, &dir->info);
dir->result.d_name[0] = '\0';
} else {
free(dir);
dir = NULL;
}
}
return dir;
}
static int closedir(DIR *dir) {
int result = 0;
if (dir != NULL) {
if (dir->handle != INVALID_HANDLE_VALUE)
result = FindClose(dir->handle) ? 0 : -1;
free(dir);
} else {
result = -1;
SetLastError(ERROR_BAD_ARGUMENTS);
}
return result;
}
static struct dirent *readdir(DIR *dir) {
struct dirent *result = 0;
if (dir) {
if (dir->handle != INVALID_HANDLE_VALUE) {
result = &dir->result;
if (!WideCharToMultiByte(CP_UTF8, 0,
dir->info.cFileName, -1, result->d_name,
sizeof(result->d_name), NULL, NULL)) {
return 0;
}
if (!FindNextFileW(dir->handle, &dir->info)) {
(void) FindClose(dir->handle);
dir->handle = INVALID_HANDLE_VALUE;
}
} else {
SetLastError(ERROR_FILE_NOT_FOUND);
}
} else {
SetLastError(ERROR_BAD_ARGUMENTS);
}
return result;
}
#define set_close_on_exec(fd) // No FD_CLOEXEC on Windows
int mg_start_thread(struct mg_context *ctx, mg_thread_func_t func, void *param) {
int rv;
pthread_t thread_id;
pthread_attr_t attr;
#if defined(HAVE_PTHREAD)
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// (void) pthread_attr_setstacksize(&attr, sizeof(struct mg_connection) * 5);
#endif
rv = pthread_create(&thread_id, &attr, func, param);
if (rv == 0) {
// count this thread too so the master_thread will wait for this one to end as well when we stop.
(void) pthread_mutex_lock(&ctx->mutex);
ctx->num_threads++;
(void) pthread_mutex_unlock(&ctx->mutex);
}
return rv;
}
static HANDLE dlopen(const char *dll_name, int flags) {
wchar_t wbuf[PATH_MAX];
flags = 0; // Unused
to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf));
return LoadLibraryW(wbuf);
}
#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 pid_t spawn_process(struct mg_connection *conn, const char *prog,
char *envblk, char *envp[], int fd_stdin,
int fd_stdout, int fd_stderr, const char *dir) {
HANDLE me;
char *p;
const char *interp;
char cmdline[2 * PATH_MAX], buf[PATH_MAX];
FILE *fp;
STARTUPINFOA si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
envp = NULL; // Unused
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
me = GetCurrentProcess();
(void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdin), me,
&si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
(void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdout), me,
&si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
(void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stderr), me,
&si.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS);
// If CGI file is a script, try to read the interpreter line
interp = get_conn_option(conn, CGI_INTERPRETER);
if (is_empty(interp)) {
buf[2] = '\0';
mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%c%s", dir, DIRSEP, prog);
if ((fp = mg_fopen(cmdline, "r")) != NULL) {
(void) fgets(buf, sizeof(buf), fp);
if (buf[0] != '#' || buf[1] != '!') {
// First line does not start with "#!". Do not set interpreter.
buf[2] = '\0';
} else {
// Trim whitespace in interpreter name
for (p = &buf[strlen(buf) - 1]; p > buf && isspace(*p); p--) {
*p = '\0';
}
}
(void) mg_fclose(fp);
}
interp = buf + 2;
}
(void) mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%s%s%c%s",
interp, is_empty(interp) ? "" : " ", dir, DIRSEP, prog);
DEBUG_TRACE(0x0100, ("Running [%s]", cmdline));
if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
CREATE_NEW_PROCESS_GROUP, envblk, dir, &si, &pi) == 0) {
mg_cry(conn, "%s: CreateProcess(%s): %d (%s)",
__func__, cmdline, ERRNO, mg_strerror(ERRNO));
pi.hProcess = (pid_t) -1;
}
(void) close(fd_stdin);
(void) close(fd_stdout);
(void) close(fd_stderr);
(void) CloseHandle(si.hStdError);
(void) CloseHandle(si.hStdOutput);
(void) CloseHandle(si.hStdInput);
(void) CloseHandle(pi.hThread);
return (pid_t) pi.hProcess;
}
#endif // !NO_CGI
static int set_non_blocking_mode(SOCKET sock, int on) {
unsigned long _on = !!on;
return ioctlsocket(sock, FIONBIO, &_on);
}
#else // WIN32
int mg_mk_fullpath(char *buf, size_t buf_len) {
char newbuf[PATH_MAX + 1];
if (!realpath(buf, newbuf))
return -1;
mg_strlcpy(buf, newbuf, buf_len);
return 0;
}
FILE *mg_fopen(const char *path, const char *mode) {
if (!path || !path[0] || !mode || !mode[0]) {
return NULL;
}
if (0 == strcmp("-", path)) {
return stderr;
}
// recursively create the included path when the file is to be created / appended to:
if (mode[strcspn(mode, "aw")]) {
size_t i;
char *wbuf = strdup(path);
// skip UNC path starters like '\\?\'
i = strspn(wbuf, "/:?");
// and skip drive specs like 'C:'
if (i == 0 && wbuf[i] && wbuf[i+1] == ':') {
i = 2;
i += strspn(wbuf + i, "/");
}
i += strcspn(wbuf + i, "/");
while (wbuf[i]) {
int rv;
// skip ./ and ../ sections; CAVEAT: due to code simplification, we also skip entries like 'XYZ./' (note the dot at the end) which are flaky path specs anyway
if (wbuf[i - 1] == '.') {
wbuf[i++] = '/';
i += strcspn(wbuf + i, "/");
continue;
}
wbuf[i] = 0;
#ifndef S_IRWXU
#define S_IRWXU 0755
#endif
rv = mkdir(wbuf, S_IRWXU);
wbuf[i++] = '/';
if (0 != rv && errno != EEXIST)
break;
i += strcspn(wbuf + i, "/");
}
free(wbuf);
}
return fopen(path, mode);
}
int mg_stat(const char *path, struct mgstat *stp) {
struct stat st;
int ok;
if (!path || !stp)
return -1;
if (stat(path, &st) == 0) {
ok = 0;
stp->size = st.st_size;
stp->mtime = st.st_mtime;
stp->is_directory = S_ISDIR(st.st_mode);
} else {
ok = -1;
}
return ok;
}
static void set_close_on_exec(int fd) {
(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
}
int mg_start_thread(struct mg_context *ctx, mg_thread_func_t func, void *param) {
int rv;
pthread_t thread_id;
pthread_attr_t attr;
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// TODO(lsm): figure out why mongoose dies on Linux if next line is enabled
// (void) pthread_attr_setstacksize(&attr, sizeof(struct mg_connection) * 5);
rv = pthread_create(&thread_id, &attr, func, param);
if (rv == 0) {
// count this thread too so the master_thread will wait for this one to end as well when we stop.
(void) pthread_mutex_lock(&ctx->mutex);
ctx->num_threads++;
(void) pthread_mutex_unlock(&ctx->mutex);
}
return rv;
}
#ifndef NO_CGI
static pid_t spawn_process(struct mg_connection *conn, const char *prog,
char *envblk, char *envp[], int fd_stdin,
int fd_stdout, int fd_stderr, const char *dir) {
pid_t pid;
const char *interp;
envblk = NULL; // Unused
if ((pid = fork()) == -1) {
// Parent
send_http_error(conn, 500, NULL, "fork(): %s", mg_strerror(ERRNO));
(void) close(fd_stdin);
(void) close(fd_stdout);
(void) close(fd_stderr);
} else if (pid == 0) {
// Child
if (chdir(dir) != 0) {
mg_cry(conn, "%s: chdir(%s): %s", __func__, dir, mg_strerror(ERRNO));
} else if (dup2(fd_stdin, 0) == -1) {
mg_cry(conn, "%s: dup2(%d, 0): %s", __func__, fd_stdin, mg_strerror(ERRNO));
} else if (dup2(fd_stdout, 1) == -1) {
mg_cry(conn, "%s: dup2(%d, 1): %s", __func__, fd_stdout, mg_strerror(ERRNO));
} else if (dup2(fd_stderr, 2) == -1) {
mg_cry(conn, "%s: dup2(%d, 2): %s", __func__, fd_stderr, mg_strerror(ERRNO));
} else {
(void) close(fd_stdin);
(void) close(fd_stdout);
(void) close(fd_stderr);
fd_stdin = -1;
fd_stdout = -1;
fd_stderr = -1;
interp = get_conn_option(conn, CGI_INTERPRETER);
if (is_empty(interp)) {
(void) execle(prog, prog, NULL, envp);
mg_cry(conn, "%s: execle(%s): %s", __func__, prog, mg_strerror(ERRNO));
} else {
(void) execle(interp, interp, prog, NULL, envp);
mg_cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog,
mg_strerror(ERRNO));
}
}
if (fd_stdin != -1) {
(void) close(fd_stdin);
}
if (fd_stdout != -1) {
(void) close(fd_stdout);
}
if (fd_stderr != -1) {
(void) close(fd_stderr);
}
exit(EXIT_FAILURE);
} else {
// Parent. Close stdio descriptors
(void) close(fd_stdin);
(void) close(fd_stdout);
(void) close(fd_stderr);
}
return pid;
}
#endif // !NO_CGI
static int set_non_blocking_mode(SOCKET sock, int on) {
int flags = -1;
#if defined(FIONBIO) // VMS
flags = !!on;
flags = ioctl(sock, FIONBIO, &flags);
#endif
#if defined(SO_NONBLOCK) // BeOS et al
flags = !!on;
flags = setsockopt(sock, SOL_SOCKET, SO_NONBLOCK, &flags, sizeof(flags));
#endif
#if defined(F_GETFL) && defined(F_SETFL)
flags = fcntl(sock, F_GETFL, 0);
if (flags != -1) {
#if defined(O_NONBLOCK)
if (on)
flags |= O_NONBLOCK;
else
flags &= ~O_NONBLOCK;
#endif
#if defined(F_NDELAY)
if (on)
flags |= F_NDELAY;
else
flags &= ~F_NDELAY;
#endif
flags = fcntl(sock, F_SETFL, flags);
}
#endif
return flags;
}
#endif // _WIN32
int mg_fclose(FILE *fp) {
if (fp != NULL && fp != stderr && fp != stdout && fp != stdin) {
return fclose(fp);
}
return 0;
}
static void add_to_set(SOCKET fd, fd_set *set, int *max_fd) {
FD_SET(fd, set);
if (((int)fd) > *max_fd) {
*max_fd = (int) fd;
}
}
#if !defined(NO_SSL)
// Return !0 when the SSL I/O operation should be retried.
// This can happen, for instance, when a renegotiation occurs,
// which can happen at any time during SSL I/O.
//
// http://www.openssl.org/docs/ssl/SSL_get_error.html#
// http://www.openssl.org/docs/ssl/SSL_read.html
// http://www.openssl.org/docs/ssl/SSL_write.html
static int ssl_renegotiation_ongoing(struct mg_connection *conn, int *ret) {
int rv;
// renegotiation may occur at any time; facilitate this!
rv = SSL_get_error(conn->ssl, *ret);
switch (rv) {
case SSL_ERROR_NONE:
return 0;
case SSL_ERROR_ZERO_RETURN:
*ret = 0;
return 0;
case SSL_ERROR_WANT_READ:
{
char buf[256];
(void)SSL_peek(conn->ssl, buf, sizeof(buf));
}
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
case SSL_ERROR_WANT_X509_LOOKUP:
// retry the call with the exact same parameters:
*ret = 0;
return 1;
case SSL_ERROR_SYSCALL:
case SSL_ERROR_SSL:
default:
if (*ret >= 0)
*ret = -1;
return 0;
}
}
#else
#define ssl_renegotiation_ongoing(conn, ret) 0
#endif
// Write data to the IO channel - opened file descriptor, socket or SSL
// descriptor. Return number of bytes written.
static int64_t push(FILE *fp, struct mg_connection *conn, const char *buf,
int64_t len) {
int64_t sent;
int n, k;
sent = 0;
while (sent < len) {
// How many bytes we send in this iteration
k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
assert(conn ? !conn->client.is_ssl == !conn->ssl : 1);
if (conn && conn->ssl) {
do {
n = SSL_write(conn->ssl, buf + sent, k);
} while (ssl_renegotiation_ongoing(conn, &n));
conn->client.write_error = (n < 0);
if (n == 0)
break;
} else if (fp != NULL) {
n = (int)fwrite(buf + sent, 1, (size_t)k, fp);
if (ferror(fp))
n = -1;
} else if (conn && conn->client.sock != INVALID_SOCKET) {
/* Ignore "broken pipe" errors (i.e., clients that disconnect instead of waiting for their answer) */
n = send(conn->client.sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
conn->client.write_error = (n < 0);
} else {
n = -1;
}
if (n < 0)
break;
sent += n;
}
return sent;
}
// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
// Return number of bytes read, negative value on error
static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
int nread;
assert(conn ? !conn->client.is_ssl == !conn->ssl : 1);
if (conn && conn->ssl) {
do {
nread = SSL_read(conn->ssl, buf, len);
} while (ssl_renegotiation_ongoing(conn, &nread));
conn->client.read_error = (nread < 0);
// and reset the select() markers used by consume_socket() et al:
conn->client.was_idle = 0;
conn->client.has_read_data = 0;
} else 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);
if (ferror(fp))
nread = -1;
} else if (conn && conn->client.sock != INVALID_SOCKET) {
nread = 0;
// poll stop_flag to ensure that we'll be able to abort on server stop:
while (conn->ctx->stop_flag == 0 || !conn->abort_when_server_stops) {
int sn = 1;
// do we already know whether there's incoming data pending?
if (!(conn->client.was_idle && conn->client.has_read_data)) {
fd_set fdr;
int max_fh = 0;
struct timeval tv = {0};
tv.tv_sec = 0;
tv.tv_usec = MG_SELECT_TIMEOUT_MSECS * 1000;
FD_ZERO(&fdr);
add_to_set(conn->client.sock, &fdr, &max_fh);
sn = select(max_fh + 1, &fdr, NULL, NULL, &tv);
}
if (sn > 0) {
nread = recv(conn->client.sock, buf, (size_t) len, 0);
conn->client.read_error = (nread < 0);
break;
}
}
// ALWAYS reset the select() markers used by consume_socket() et al:
conn->client.was_idle = 0;
conn->client.has_read_data = 0;
} else {
nread = -1;
}
return nread;
}
// forward declaration:
static int read_and_parse_chunk_header(struct mg_connection *conn);
static int read_bytes(struct mg_connection *conn, void *buf, size_t len, int nonblocking) {
int n, buffered_len, nread;
const char *buffered;
assert((conn->content_len == -1) ||
conn->consumed_content <= conn->content_len);
//assert(conn->next_request != NULL &&
// conn->body != NULL &&
// conn->next_request >= conn->body);
nread = 0;
while (len > 0 && (conn->consumed_content < conn->content_len || conn->content_len == -1)) {
// Adjust number of bytes to read.
int64_t to_read = (conn->content_len == -1 ? INT_MAX : conn->content_len - conn->consumed_content);
int already_read_len = conn->rx_buffer_read_len;
if (to_read < (int64_t) len) {
len = (size_t) to_read;
}
// How many bytes of data we have buffered in the request buffer?
assert(conn->request_len >= 0);
buffered = conn->buf + conn->request_len + already_read_len;
buffered_len = conn->rx_buffer_loaded_len;
assert(buffered_len >= 0);
// Return buffered data back if we haven't done that yet.
if (already_read_len < buffered_len) {
buffered_len -= already_read_len;
if (len < (size_t) buffered_len) {
buffered_len = (int)len;
}
} else {
buffered_len = 0;
}
if (conn->rx_is_in_chunked_mode) {
if (conn->rx_chunk_header_parsed == 0) {
int cl;
assert(conn->rx_remaining_chunksize == 0);
// nonblocking: check if any data is pending; only then do we fetch one more chunk header...
if (nread == 0 || mg_is_read_data_available(conn) == 1 || !nonblocking) {
cl = read_and_parse_chunk_header(conn);
if (conn->rx_remaining_chunksize == 0) {
DEBUG_TRACE(0x0004,
("End Of Chunked Transmission @ chunk header %d @ nread = %d",
conn->rx_chunk_count, nread));
}
if (cl < 0)
return cl;
}
if (conn->rx_remaining_chunksize == 0) {
return nread;
}
continue; // it's easier to have another round figure it out, now that we have a new chunk
}
if (conn->rx_remaining_chunksize == 0) {
return nread;
}
if (buffered_len > conn->rx_remaining_chunksize)
buffered_len = conn->rx_remaining_chunksize;
}
if (buffered_len > 0) {
// as user-defined chunk readers may read data into the connection buffer,
// it CAN happen that buf == buffered. Otherwise, use memmove() instead
// of memcpy() to be on the safe side.
if (buf != buffered)
memmove(buf, buffered, (size_t)buffered_len);
len -= buffered_len;
buf = (char *) buf + buffered_len;
conn->rx_buffer_read_len += buffered_len;
if (conn->rx_chunk_header_parsed < 2) {
conn->consumed_content += buffered_len;
conn->rx_remaining_chunksize -= buffered_len;
if (conn->rx_remaining_chunksize == 0) {
// end of chunk data reached; mark the need for a fresh chunk:
conn->rx_chunk_header_parsed = 0;
}
}
nread += buffered_len;
}
// We have returned all buffered data. Read new data from the remote socket.
while (len > 0) {