<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2004-2009 Sergey Lyubka
+ * Portions Copyright (c) 2009 Gilbert Wellisch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the &quot;Software&quot;), to deal
@@ -19,18 +20,22 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  *
- * $Id: mongoose.c 275 2009-03-27 19:21:44Z valenok $
+ * $Id: mongoose.c 446 2009-07-08 21:06:56Z valenok $
  */
 
+#if defined(_WIN32)
+#define _CRT_SECURE_NO_WARNINGS	/* Disable deprecation warning in VS2005 */
+#endif /* _WIN32 */
+
 #ifndef _WIN32_WCE /* Some ANSI #includes are not available on Windows CE */
 #include &lt;sys/types.h&gt;
 #include &lt;sys/stat.h&gt;
-#include &lt;time.h&gt;
 #include &lt;errno.h&gt;
 #include &lt;signal.h&gt;
 #include &lt;fcntl.h&gt;
-#endif /* _WIN32_WCE */
+#endif /* !_WIN32_WCE */
 
+#include &lt;time.h&gt;
 #include &lt;stdlib.h&gt;
 #include &lt;stdarg.h&gt;
 #include &lt;assert.h&gt;
@@ -40,7 +45,8 @@
 #include &lt;stddef.h&gt;
 #include &lt;stdio.h&gt;
 
-#if defined(_WIN32)		/* Windows specific	*/
+#if defined(_WIN32)		/* Windows specific #includes and #defines */
+#define	_WIN32_WINNT	0x0400	/* To make it link in VS2005 */
 #include &lt;windows.h&gt;
 
 #ifndef _WIN32_WCE
@@ -49,51 +55,63 @@
 #include &lt;io.h&gt;
 #else /* _WIN32_WCE */
 /* Windows CE-specific definitions */
+#include &lt;winsock2.h&gt;
 #define NO_CGI	/* WinCE has no pipes */
-#define NO_GUI	/* temporarily until it is fixed */
-/* WinCE has both Unicode and ANSI versions of GetProcAddress */
-#undef GetProcAddress
-#define GetProcAddress GetProcAddressA
+#define NO_SSI	/* WinCE has no pipes */
+
+#define FILENAME_MAX	MAX_PATH
+#define BUFSIZ		4096
+typedef long off_t;
+
+#define errno			GetLastError()
+#define strerror(x)		_ultoa(x, (char *) _alloca(sizeof(x) *3 ), 10)
 #endif /* _WIN32_WCE */
 
+#define EPOCH_DIFF	0x019DB1DED53E8000 /* 116444736000000000 nsecs */
+#define RATE_DIFF	10000000 /* 100 nsecs */
+#define MAKEUQUAD(lo, hi)	((uint64_t)(((uint32_t)(lo)) | \
+				((uint64_t)((uint32_t)(hi))) &lt;&lt; 32))
+#define	SYS2UNIX_TIME(lo, hi) \
+	(time_t) ((MAKEUQUAD((lo), (hi)) - EPOCH_DIFF) / RATE_DIFF)
+
 /*
- * Do not allow holes in data structures!
- * This is needed so when Mongoose DLL is loaded, other languages that
- * describe struct mg_request_info from mongoose.h, agree with C code.
+ * Visual Studio 6 does not know __func__ or __FUNCTION__
+ * The rest of MS compilers use __FUNCTION__, not C99 __func__
  */
-#pragma pack(1)
-
+#if defined(_MSC_VER) &amp;&amp; _MSC_VER &lt; 1300
+#define	STRX(x)			#x
+#define	STR(x)			STRX(x)
+#define	__func__		&quot;line &quot; STR(__LINE__)
+#else
 #define	__func__		__FUNCTION__
+#endif /* _MSC_VER */
+
 #define	ERRNO			GetLastError()
 #define	NO_SOCKLEN_T
 #define	SSL_LIB			&quot;ssleay32.dll&quot;
+#define	CRYPTO_LIB		&quot;libeay32.dll&quot;
 #define	DIRSEP			'\\'
 #define	IS_DIRSEP_CHAR(c)	((c) == '/' || (c) == '\\')
 #define	O_NONBLOCK		0
 #define	EWOULDBLOCK		WSAEWOULDBLOCK
-#define	dlopen(x,y)		LoadLibrary(x)
-#define	dlsym(x,y)		GetProcAddress((HINSTANCE) (x), (y))
 #define	_POSIX_
-
-#if !defined(R_OK)
-#define	R_OK			04 /* for _access() */
-#endif /* !R_OK  MINGW #defines R_OK */
+#define UINT64_FMT		&quot;I64&quot;
 
 #define	SHUT_WR			1
 #define	snprintf		_snprintf
 #define	vsnprintf		_vsnprintf
+#define	sleep(x)		Sleep((x) * 1000)
+
 #define	popen(x, y)		_popen(x, y)
 #define	pclose(x)		_pclose(x)
-#define	access(x, y)		_access(x, y)
-#define	getcwd(x, y)		_getcwd(x, y)
-#define	write(x, y, z)		_write(x, y, (unsigned) z)
-#define	read(x, y, z)		_read(x, y, (unsigned) z)
-#define	open(x, y, z)		_open(x, y, z)
-#define	lseek(x, y, z)		_lseek(x, y, z)
 #define	close(x)		_close(x)
-#define	sleep(x)		Sleep((x) * 1000)
-#define	flockfile(x)		_lock_file(x)
-#define	funlockfile(x)		_unlock_file(x)
+#define	dlsym(x,y)		GetProcAddress((HINSTANCE) (x), (y))
+#define	RTLD_LAZY		0
+#define	fseeko(x, y, z)		fseek((x), (y), (z))
+#define	write(x, y, z)		_write((x), (y), (unsigned) z)
+#define	read(x, y, z)		_read((x), (y), (unsigned) z)
+#define	flockfile(x)		(void) 0
+#define	funlockfile(x)		(void) 0
 
 #ifdef HAVE_STRTOUI64
 #define	strtoull(x, y, z)	_strtoui64(x, y, z)
@@ -106,18 +124,29 @@
 #endif /* !fileno MINGW #defines fileno */
 
 typedef HANDLE pthread_mutex_t;
+typedef HANDLE pthread_cond_t;
+typedef DWORD pthread_t;
 typedef HANDLE pid_t;
 
-#if !defined(S_ISDIR)
-#define S_ISDIR(x)		((x) &amp; _S_IFDIR)
-#endif /* S_ISDIR */
+struct timespec {
+	long tv_nsec;
+	long tv_sec;
+};
+
+static int pthread_mutex_lock(pthread_mutex_t *);
+static int pthread_mutex_unlock(pthread_mutex_t *);
 
 #if defined(HAVE_STDINT)
 #include &lt;stdint.h&gt;
 #else
 typedef unsigned int		uint32_t;
 typedef unsigned short		uint16_t;
+#if _MSC_VER &gt; 1200
 typedef unsigned __int64	uint64_t;
+#else
+/* VC6 cannot cast double to unsigned __int64, needed by print_dir_entry() */
+typedef __int64	uint64_t;
+#endif /* _MSC_VER */
 #endif /* HAVE_STDINT */
 
 /*
@@ -148,30 +177,41 @@ typedef struct DIR {
 #include &lt;dlfcn.h&gt;
 #include &lt;pthread.h&gt;
 #define	SSL_LIB			&quot;libssl.so&quot;
+#define	CRYPTO_LIB		&quot;libcrypto.so&quot;
 #define	DIRSEP			'/'
 #define	IS_DIRSEP_CHAR(c)	((c) == '/')
 #define	O_BINARY		0
 #define	closesocket(a)		close(a)
+#define	mg_fopen(x, y)		fopen(x, y)
 #define	mg_mkdir(x, y)		mkdir(x, y)
-#define	mg_open(x, y, z)	open(x, y, z)
 #define	mg_remove(x)		remove(x)
+#define	mg_rename(x, y)		rename(x, y)
+#define	mg_getcwd(x, y)		getcwd(x, y)
 #define	ERRNO			errno
 #define	INVALID_SOCKET		(-1)
+#define UINT64_FMT		&quot;ll&quot;
 typedef int SOCKET;
 
 #endif /* End of Windows and UNIX specific includes */
 
 #include &quot;mongoose.h&quot;
 
-#define	MONGOOSE_VERSION	&quot;2.5&quot;
+#define	MONGOOSE_VERSION	&quot;2.8&quot;
 #define	PASSWORDS_FILE_NAME	&quot;.htpasswd&quot;
 #define	CGI_ENVIRONMENT_SIZE	4096
 #define	MAX_CGI_ENVIR_VARS	64
-#define	MAX_REQUEST_SIZE	16384
+#define	MAX_REQUEST_SIZE	8192
 #define	MAX_LISTENING_SOCKETS	10
 #define	MAX_CALLBACKS		20
 #define	ARRAY_SIZE(array)	(sizeof(array) / sizeof(array[0]))
-#define	UNKNOWN_CONTENT_LENGTH	((uint64_t) ~0ULL)
+#define	UNKNOWN_CONTENT_LENGTH	((uint64_t) ~0)
+#define	DEBUG_MGS_PREFIX	&quot;*** Mongoose debug *** &quot;
+
+#if defined(DEBUG)
+#define	DEBUG_TRACE(x) do {printf x; putchar('\n'); fflush(stdout);} while (0)
+#else
+#define DEBUG_TRACE(x)
+#endif /* DEBUG */
 
 /*
  * Darwin prior to 7.0 and Win32 do not have socklen_t
@@ -187,7 +227,6 @@ enum {FALSE, TRUE};
 typedef int bool_t;
 typedef void * (*mg_thread_func_t)(void *);
 
-static int tz_offset;
 static const char *http_500_error = &quot;Internal Server Error&quot;;
 
 /*
@@ -202,6 +241,7 @@ typedef struct ssl_ctx_st SSL_CTX;
 #define	SSL_ERROR_WANT_READ	2
 #define	SSL_ERROR_WANT_WRITE	3
 #define SSL_FILETYPE_PEM	1
+#define	CRYPTO_LOCK		1
 
 /*
  * Dynamically loaded SSL functionality
@@ -211,28 +251,40 @@ struct ssl_func {
 	void		(*ptr)(void);	/* Function pointer	*/
 };
 
-#define	FUNC(x)	ssl_sw[x].ptr
-
-#define	SSL_free(x)	(* (void (*)(SSL *)) FUNC(0))(x)
-#define	SSL_accept(x)	(* (int (*)(SSL *)) FUNC(1))(x)
-#define	SSL_connect(x)	(* (int (*)(SSL *)) FUNC(2))(x)
-#define	SSL_read(x,y,z)	(* (int (*)(SSL *, void *, int)) FUNC(3))((x),(y),(z))
-#define	SSL_write(x,y,z) \
-	(* (int (*)(SSL *, const void *,int)) FUNC(4))((x), (y), (z))
-#define	SSL_get_error(x,y)(* (int (*)(SSL *, int)) FUNC(5))((x), (y))
-#define	SSL_set_fd(x,y)	(* (int (*)(SSL *, SOCKET)) FUNC(6))((x), (y))
-#define	SSL_new(x)	(* (SSL * (*)(SSL_CTX *)) FUNC(7))(x)
-#define	SSL_CTX_new(x)	(* (SSL_CTX * (*)(SSL_METHOD *)) FUNC(8))(x)
-#define	SSLv23_server_method()	(* (SSL_METHOD * (*)(void)) FUNC(9))()
-#define	SSL_library_init() (* (int (*)(void)) FUNC(10))()
+#define	SSL_free(x)	(* (void (*)(SSL *)) ssl_sw[0].ptr)(x)
+#define	SSL_accept(x)	(* (int (*)(SSL *)) ssl_sw[1].ptr)(x)
+#define	SSL_connect(x)	(* (int (*)(SSL *)) ssl_sw[2].ptr)(x)
+#define	SSL_read(x,y,z)	(* (int (*)(SSL *, void *, int)) 		\
+				ssl_sw[3].ptr)((x),(y),(z))
+#define	SSL_write(x,y,z) (* (int (*)(SSL *, const void *,int))		\
+				ssl_sw[4].ptr)((x), (y), (z))
+#define	SSL_get_error(x,y)(* (int (*)(SSL *, int)) ssl_sw[5])((x), (y))
+#define	SSL_set_fd(x,y)	(* (int (*)(SSL *, SOCKET)) ssl_sw[6].ptr)((x), (y))
+#define	SSL_new(x)	(* (SSL * (*)(SSL_CTX *)) ssl_sw[7].ptr)(x)
+#define	SSL_CTX_new(x)	(* (SSL_CTX * (*)(SSL_METHOD *)) ssl_sw[8].ptr)(x)
+#define	SSLv23_server_method()	(* (SSL_METHOD * (*)(void)) ssl_sw[9].ptr)()
+#define	SSL_library_init() (* (int (*)(void)) ssl_sw[10].ptr)()
 #define	SSL_CTX_use_PrivateKey_file(x,y,z)	(* (int (*)(SSL_CTX *, \
-		const char *, int)) FUNC(11))((x), (y), (z))
+		const char *, int)) ssl_sw[11].ptr)((x), (y), (z))
 #define	SSL_CTX_use_certificate_file(x,y,z)	(* (int (*)(SSL_CTX *, \
-		const char *, int)) FUNC(12))((x), (y), (z))
+		const char *, int)) ssl_sw[12].ptr)((x), (y), (z))
 #define SSL_CTX_set_default_passwd_cb(x,y) \
-	(* (void (*)(SSL_CTX *, mg_spcb_t))FUNC(13))((x),(y))
-#define SSL_CTX_free(x) (* (void (*)(SSL_CTX *))FUNC(14))(x)
+	(* (void (*)(SSL_CTX *, mg_spcb_t)) ssl_sw[13].ptr)((x),(y))
+#define SSL_CTX_free(x) (* (void (*)(SSL_CTX *)) ssl_sw[14].ptr)(x)
+
+#define CRYPTO_num_locks() (* (int (*)(void)) crypto_sw[0].ptr)()
+#define CRYPTO_set_locking_callback(x)					\
+		(* (void (*)(void (*)(int, int, const char *, int)))	\
+	 	crypto_sw[1].ptr)(x)
+#define CRYPTO_set_id_callback(x)					\
+	(* (void (*)(unsigned long (*)(void))) crypto_sw[2].ptr)(x)
 
+/*
+ * set_ssl_option() function when called, 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[] = {
 	{&quot;SSL_free&quot;,			NULL},
 	{&quot;SSL_accept&quot;,			NULL},
@@ -253,6 +305,24 @@ static struct ssl_func	ssl_sw[] = {
 };
 
 /*
+ * Similar array as ssl_sw. These functions are located in different lib.
+ */
+static struct ssl_func	crypto_sw[] = {
+	{&quot;CRYPTO_num_locks&quot;,		NULL},
+	{&quot;CRYPTO_set_locking_callback&quot;,	NULL},
+	{&quot;CRYPTO_set_id_callback&quot;,	NULL},
+	{NULL,				NULL}
+};
+
+/*
+ * Month names
+ */
+static const char *month_names[] = {
+	&quot;Jan&quot;, &quot;Feb&quot;, &quot;Mar&quot;, &quot;Apr&quot;, &quot;May&quot;, &quot;Jun&quot;,
+	&quot;Jul&quot;, &quot;Aug&quot;, &quot;Sep&quot;, &quot;Oct&quot;, &quot;Nov&quot;, &quot;Dec&quot;
+};
+
+/*
  * Unified socket address. For IPv6 support, add IPv6 address structure
  * in the union u.
  */
@@ -265,6 +335,15 @@ struct usa {
 };
 
 /*
+ * Specifies a string (chunk of memory).
+ * Used to traverse comma separated lists of options.
+ */
+struct vec {
+	const char	*ptr;
+	size_t		len;
+};
+
+/*
  * Structure used by mg_stat() function. Uses 64 bit file length.
  */
 struct mgstat {
@@ -273,6 +352,14 @@ struct mgstat {
 	time_t		mtime;		/* Modification time		*/
 };
 
+struct mg_option {
+	const char	*name;
+	const char	*description;
+	const char	*default_value;
+	int		index;
+	bool_t (*setter)(struct mg_context *, const char *);
+};
+
 /*
  * Numeric indexes for the option values in context, ctx-&gt;options
  */
@@ -280,9 +367,9 @@ enum mg_option_index {
 	OPT_ROOT, OPT_INDEX_FILES, OPT_PORTS, OPT_DIR_LIST, OPT_CGI_EXTENSIONS,
 	OPT_CGI_INTERPRETER, OPT_CGI_ENV, OPT_SSI_EXTENSIONS, OPT_AUTH_DOMAIN,
 	OPT_AUTH_GPASSWD, OPT_AUTH_PUT, OPT_ACCESS_LOG, OPT_ERROR_LOG,
-	OPT_SSL_CERTIFICATE, OPT_ALIASES, OPT_ACL, OPT_UID,
-	OPT_PROTECT, OPT_SERVICE, OPT_HIDE, OPT_ADMIN_URI, OPT_MAX_THREADS,
-	OPT_IDLE_TIME,
+	OPT_SSL_CERTIFICATE, OPT_ALIASES, OPT_ACL, OPT_UID, OPT_PROTECT,
+	OPT_SERVICE, OPT_HIDE, OPT_ADMIN_URI, OPT_MAX_THREADS, OPT_IDLE_TIME,
+	OPT_MIME_TYPES,
 	NUM_OPTIONS
 };
 
@@ -293,7 +380,8 @@ enum mg_option_index {
  */
 struct socket {
 	SOCKET		sock;		/* Listening socket		*/
-	struct usa	usa;		/* Socket address		*/
+	struct usa	lsa;		/* Local socket address		*/
+	struct usa	rsa;		/* Remote socket address	*/
 	bool_t		is_ssl;		/* Is socket SSL-ed		*/
 };
 
@@ -324,16 +412,24 @@ struct mg_context {
 	struct callback	callbacks[MAX_CALLBACKS];
 	int		num_callbacks;
 
-	char	*options[NUM_OPTIONS];	/* Configured opions		*/
-	pthread_mutex_t	opt_mutex;	/* Option setter/getter guard	*/
+	char		*options[NUM_OPTIONS];	/* Configured opions	*/
+	pthread_mutex_t	opt_mutex[NUM_OPTIONS];	/* Option protector	*/
 
 	int		max_threads;	/* Maximum number of threads	*/
-	int		num_active;	/* Number of active threads	*/
+	int		num_threads;	/* Number of threads		*/
 	int		num_idle;	/* Number of idle threads	*/
+	pthread_mutex_t	thr_mutex;	/* Protects (max|num)_threads	*/
+	pthread_cond_t	thr_cond;
+	pthread_mutex_t	bind_mutex;	/* Protects bind operations	*/
 
-	int		ctl[2];		/* Control pipe			*/
+	struct socket	queue[20];	/* Accepted sockets		*/
+	int		sq_head;	/* Head of the socket queue	*/
+	int		sq_tail;	/* Tail of the socket queue	*/
+	pthread_cond_t	empty_cond;	/* Socket queue empty condvar	*/
+	pthread_cond_t	full_cond;	/* Socket queue full condvar	*/
 
 	mg_spcb_t	ssl_password_callback;
+	mg_callback_t	log_callback;
 };
 
 /*
@@ -344,54 +440,68 @@ struct mg_connection {
 	struct mg_context *ctx;		/* Mongoose context we belong to*/
 	SSL		*ssl;		/* SSL descriptor		*/
 	struct socket	client;		/* Connected client		*/
-	struct usa	lsa;		/* Local socket address		*/
 	time_t		birth_time;	/* Time connection was accepted	*/
 	bool_t		free_post_data;	/* post_data was malloc-ed	*/
-	bool_t		keep_alive;	/* Keep-Alive flag		*/
+	bool_t		embedded_auth;	/* Used for authorization	*/
 	uint64_t	num_bytes_sent;	/* Total bytes sent to client	*/
 };
 
 /*
- * In Mongoose, list of values are represented as comma separated
- * string. For example, list of CGI extensions can be represented as
- * &quot;.cgi,.php,.pl&quot;, FOR_EACH_WORD_IN_LIST macro allows to
- * loop through the individual values in that list.
- *
- * A &quot;const char *&quot; and &quot;int&quot; variables must be passed to the macro.
- *
- * In every iteration of the loop, &quot;s&quot; points to the current value, and
- * &quot;len&quot; specifies its length. Code inside loop must not change &quot;s&quot; and &quot;len&quot;.
+ * Print error message to the opened error log stream.
  */
-#define	FOR_EACH_WORD_IN_LIST(s, len)					\
-	for (; s != NULL &amp;&amp; (len = strcspn(s, &quot;,&quot;)) != 0;		\
-			s += len, s+= strspn(s, &quot;,&quot;))
+static void
+cry(struct mg_connection *conn, const char *fmt, ...)
+{
+	char	buf[BUFSIZ];
+	va_list	ap;
+
+	va_start(ap, fmt);
+	(void) vsnprintf(buf, sizeof(buf), fmt, ap);
+	conn-&gt;ctx-&gt;log_callback(conn, &amp;conn-&gt;request_info, buf);
+	va_end(ap);
+}
 
 /*
- * Print error message to the opened error log stream.
+ * Return fake connection structure. Used for logging, if connection
+ * is not applicable at the moment of logging.
+ */
+static struct mg_connection *
+fc(struct mg_context *ctx)
+{
+	static struct mg_connection fake_connection;
+	fake_connection.ctx = ctx;
+	return (&amp;fake_connection);
+}
+
+/*
+ * If an embedded code does not intercept logging by calling
+ * mg_set_log_callback(), this function is used for logging. It prints
+ * stuff to the conn-&gt;error_log, which is stderr unless &quot;error_log&quot;
+ * option was set.
  */
 static void
-cry(const struct mg_connection *conn, const char *fmt, ...)
+builtin_error_log(struct mg_connection *conn,
+		const struct mg_request_info *request_info, void *message)
 {
 	FILE	*fp;
 	time_t	timestamp;
-	va_list	ap;
-
-	fp = (conn == NULL || conn-&gt;ctx-&gt;error_log == NULL) ?
-		stderr : conn-&gt;ctx-&gt;error_log;
 
+	fp = conn-&gt;ctx-&gt;error_log;
 	flockfile(fp);
 
 	timestamp = time(NULL);
-	(void) fprintf(fp, &quot;[%.*s] [error] [client %s] &quot;,
-	    24, ctime(&amp;timestamp),  /* Print 25 characters, no trailing \n */
-	    conn == NULL ? &quot;-&quot;: inet_ntoa(conn-&gt;client.usa.u.sin.sin_addr));
-	if (conn != NULL)
+
+	(void) fprintf(fp,
+	    &quot;[%010lu] [error] [client %s] &quot;,
+	    (unsigned long) timestamp,
+	    inet_ntoa(conn-&gt;client.rsa.u.sin.sin_addr));
+
+	if (request_info-&gt;request_method != NULL)
 		(void) fprintf(fp, &quot;%s %s: &quot;,
-		    conn-&gt;request_info.request_method, conn-&gt;request_info.uri);
+		    request_info-&gt;request_method,
+		    request_info-&gt;uri);
 
-	va_start(ap, fmt);
-	(void) vfprintf(fp, fmt, ap);
-	va_end(ap);
+	(void) fprintf(fp, &quot;%s&quot;, (char *) message);
 
 	fputc('\n', fp);
 
@@ -468,7 +578,8 @@ mg_strdup(const char *str)
  * in his audit report.
  */
 static int
-mg_vsnprintf(char *buf, size_t buflen, const char *fmt, va_list ap)
+mg_vsnprintf(struct mg_connection *conn,
+		char *buf, size_t buflen, const char *fmt, va_list ap)
 {
 	int	n;
 
@@ -478,10 +589,10 @@ mg_vsnprintf(char *buf, size_t buflen, const char *fmt, va_list ap)
 	n = vsnprintf(buf, buflen, fmt, ap);
 
 	if (n &lt; 0) {
-		cry(NULL, &quot;vsnprintf error&quot;);
+		cry(conn, &quot;vsnprintf error&quot;);
 		n = 0;
 	} else if (n &gt;= (int) buflen) {
-		cry(NULL, &quot;truncating vsnprintf buffer: [%.*s]&quot;,
+		cry(conn, &quot;truncating vsnprintf buffer: [%.*s]&quot;,
 		    n &gt; 200 ? 200 : n, buf);
 		n = (int) buflen - 1;
 	}
@@ -491,13 +602,14 @@ mg_vsnprintf(char *buf, size_t buflen, const char *fmt, va_list ap)
 }
 
 static int
-mg_snprintf(char *buf, size_t buflen, const char *fmt, ...)
+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(buf, buflen, fmt, ap);
+	n = mg_vsnprintf(conn, buf, buflen, fmt, ap);
 	va_end(ap);
 
 	return (n);
@@ -509,11 +621,11 @@ mg_snprintf(char *buf, size_t buflen, const char *fmt, ...)
 static bool_t
 is_true(const char *str)
 {
-	static const char *trues[] = {&quot;1&quot;, &quot;yes&quot;, &quot;true&quot;, &quot;jawohl&quot;, NULL};
-	const char	**p;
+	static const char	*trues[] = {&quot;1&quot;, &quot;yes&quot;, &quot;true&quot;, &quot;ja&quot;, NULL};
+	int			i;
 
-	for (p = trues; *p != NULL; p++)
-		if (str &amp;&amp; !mg_strcasecmp(str, *p))
+	for (i = 0; trues[i] != NULL; i++)
+		if (str != NULL &amp;&amp; mg_strcasecmp(str, trues[i]) == 0)
 			return (TRUE);
 
 	return (FALSE);
@@ -562,6 +674,50 @@ mg_get_header(const struct mg_connection *conn, const char *name)
 	return (get_header(&amp;conn-&gt;request_info, name));
 }
 
+/*
+ * A helper function for traversing comma separated list of values.
+ * It returns a list pointer shifted to the next value, of NULL if the end
+ * of the list found.
+ * Value is stored in val vector. If value has form &quot;x=y&quot;, then eq_val
+ * vector is initialized to point to the &quot;y&quot; part, and val vector length
+ * is adjusted to point only to &quot;x&quot;.
+ */
+static const char *
+next_option(const char *list, struct vec *val, struct vec *eq_val)
+{
+	if (list == NULL || *list == '\0') {
+		/* End of the list */
+		list = NULL;
+	} else {
+		val-&gt;ptr = list;
+		if ((list = strchr(val-&gt;ptr, ',')) != NULL) {
+			/* Comma found. Store length and shift the list ptr */
+			val-&gt;len = list - val-&gt;ptr;
+			list++;
+		} else {
+			/* This value is the last one */
+			list = val-&gt;ptr + strlen(val-&gt;ptr);
+			val-&gt;len = list - val-&gt;ptr;
+		}
+
+		if (eq_val != NULL) {
+			/*
+			 * Value has form &quot;x=y&quot;, adjust pointers and lengths
+			 * so that val points to &quot;x&quot;, and eq_val points to &quot;y&quot;.
+			 */
+			eq_val-&gt;len = 0;
+			eq_val-&gt;ptr = memchr(val-&gt;ptr, '=', val-&gt;len);
+			if (eq_val-&gt;ptr != NULL) {
+				eq_val-&gt;ptr++;  /* Skip over '=' character */
+				eq_val-&gt;len = val-&gt;ptr + val-&gt;len - eq_val-&gt;ptr;
+				val-&gt;len = (eq_val-&gt;ptr - val-&gt;ptr) - 1;
+			}
+		}
+	}
+
+	return (list);
+}
+
 #if !(defined(NO_CGI) &amp;&amp; defined(NO_SSI))
 /*
  * Verify that given file has certain extension
@@ -569,19 +725,25 @@ mg_get_header(const struct mg_connection *conn, const char *name)
 static bool_t
 match_extension(const char *path, const char *ext_list)
 {
-	size_t		len, path_len;
+	struct vec	ext_vec;
+	size_t		path_len;
 
 	path_len = strlen(path);
 
-	FOR_EACH_WORD_IN_LIST(ext_list, len)
-		if (len &lt; path_len &amp;&amp; path[path_len - (len + 1)] == '.' &amp;&amp;
-		    !mg_strncasecmp(path + path_len - len, ext_list, len))
+	while ((ext_list = next_option(ext_list, &amp;ext_vec, NULL)) != NULL)
+		if (ext_vec.len &lt; path_len &amp;&amp;
+		    mg_strncasecmp(path + path_len - ext_vec.len,
+			    ext_vec.ptr, ext_vec.len) == 0)
 			return (TRUE);
 
 	return (FALSE);
 }
 #endif /* !(NO_CGI &amp;&amp; NO_SSI) */
 
+/*
+ * Return TRUE if &quot;uri&quot; matches &quot;regexp&quot;.
+ * '*' in the regexp means zero or more characters.
+ */
 static bool_t
 match_regex(const char *uri, const char *regexp)
 {
@@ -601,26 +763,45 @@ match_regex(const char *uri, const char *regexp)
 }
 
 static const struct callback *
-find_callback(const struct mg_context *ctx, bool_t is_auth,
+find_callback(struct mg_context *ctx, bool_t is_auth,
 		const char *uri, int status_code)
 {
-	const struct callback	*cb;
+	const struct callback	*cb, *found;
 	int			i;
 
+	found = NULL;
+	pthread_mutex_lock(&amp;ctx-&gt;bind_mutex);
 	for (i = 0; i &lt; ctx-&gt;num_callbacks; i++) {
 		cb = ctx-&gt;callbacks + i;
 		if ((uri != NULL &amp;&amp; cb-&gt;uri_regex != NULL &amp;&amp;
 		    ((is_auth &amp;&amp; cb-&gt;is_auth) || (!is_auth &amp;&amp; !cb-&gt;is_auth)) &amp;&amp;
 		    match_regex(uri, cb-&gt;uri_regex)) || (uri == NULL &amp;&amp;
-		     (cb-&gt;status_code == 0 || cb-&gt;status_code == status_code)))
-		    return (cb);
+		     (cb-&gt;status_code == 0 ||
+		      cb-&gt;status_code == status_code))) {
+			found = cb;
+			break;
+		}
 	}
+	pthread_mutex_unlock(&amp;ctx-&gt;bind_mutex);
 
-	return (NULL);
+	return (found);
 }
 
 /*
- * Send error message back to a client.
+ * For use by external application. This sets custom logging function.
+ */
+void
+mg_set_log_callback(struct mg_context *ctx, mg_callback_t log_callback)
+{
+	/* If NULL is specified as a callback, revert back to the default */
+	if (log_callback == NULL)
+		ctx-&gt;log_callback = &amp;builtin_error_log;
+	else
+		ctx-&gt;log_callback = log_callback;
+}
+
+/*
+ * Send error message back to the client.
  */
 static void
 send_error(struct mg_connection *conn, int status, const char *reason,
@@ -637,23 +818,28 @@ send_error(struct mg_connection *conn, int status, const char *reason,
 	if ((cb = find_callback(conn-&gt;ctx, FALSE, NULL, status)) != NULL) {
 		cb-&gt;func(conn, &amp;conn-&gt;request_info, cb-&gt;user_data);
 	} else {
-		(void) mg_printf(conn,
-		    &quot;HTTP/1.1 %d %s\r\n&quot;
-		    &quot;Content-Type: text/plain\r\n&quot;
-		    &quot;Connection: close\r\n&quot;
-		    &quot;\r\n&quot;, status, reason);
+		buf[0] = '\0';
+		len = 0;
 
 		/* Errors 1xx, 204 and 304 MUST NOT send a body */
 		if (status &gt; 199 &amp;&amp; status != 204 &amp;&amp; status != 304) {
-			conn-&gt;num_bytes_sent = mg_printf(conn,
+			len = mg_snprintf(conn, buf, sizeof(buf),
 			    &quot;Error %d: %s\n&quot;, status, reason);
+			cry(conn, &quot;%s&quot;, buf);
 
 			va_start(ap, fmt);
-			len = mg_vsnprintf(buf, sizeof(buf), fmt, ap);
+			len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len,
+			    fmt, ap);
 			va_end(ap);
-			conn-&gt;num_bytes_sent += mg_write(conn, buf, len);
-			cry(conn, &quot;%s&quot;, buf);
+			conn-&gt;num_bytes_sent = len;
 		}
+
+		(void) mg_printf(conn,
+		    &quot;HTTP/1.1 %d %s\r\n&quot;
+		    &quot;Content-Type: text/plain\r\n&quot;
+		    &quot;Content-Length: %d\r\n&quot;
+		    &quot;Connection: close\r\n&quot;
+		    &quot;\r\n%s&quot;, status, reason, len, buf);
 	}
 }
 
@@ -684,6 +870,61 @@ pthread_mutex_unlock(pthread_mutex_t *mutex)
 	return (ReleaseMutex(*mutex) == 0 ? -1 : 0);
 }
 
+static int
+pthread_cond_init(pthread_cond_t *cv, const void *unused)
+{
+	unused = NULL;
+	*cv = CreateEvent(NULL, FALSE, FALSE, NULL);
+	return (*cv == NULL ? -1 : 0);
+}
+
+static int
+pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mutex,
+	const struct timespec *ts)
+{
+	DWORD	status;
+	DWORD	msec = INFINITE;
+	time_t	now;
+	
+	if (ts != NULL) {
+		now = time(NULL);
+		msec = 1000 * (now &gt; ts-&gt;tv_sec ? 0 : ts-&gt;tv_sec - now);
+	}
+
+	(void) ReleaseMutex(*mutex);
+	status = WaitForSingleObject(*cv, msec);
+	(void) WaitForSingleObject(*mutex, INFINITE);
+	
+	return (status == WAIT_OBJECT_0 ? 0 : -1);
+}
+
+static int
+pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex)
+{
+	return (pthread_cond_timedwait(cv, mutex, NULL));
+}
+
+static int
+pthread_cond_signal(pthread_cond_t *cv)
+{
+	return (SetEvent(*cv) == 0 ? -1 : 0);
+}
+
+static int
+pthread_cond_destroy(pthread_cond_t *cv)
+{
+	return (CloseHandle(*cv) == 0 ? -1 : 0);
+}
+
+static pthread_t
+pthread_self(void)
+{
+	return (GetCurrentThreadId());
+}
+
+/*
+ * Change all slashes to backslashes. It is Windows.
+ */
 static void
 fix_directory_separators(char *path)
 {
@@ -700,6 +941,10 @@ fix_directory_separators(char *path)
 	}
 }
 
+/*
+ * 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)
 {
@@ -722,38 +967,128 @@ to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len)
 	 * actually opens &quot;a.cgi&quot;, and does not return an error!
 	 */
 	if (*p == 0x20 || *p == 0x2e || *p == 0x2b || (*p &amp; ~0x7f)) {
-		cry(NULL, &quot;Rejecting suspicious path: [%s]&quot;, buf);
+		(void) fprintf(stderr, &quot;Rejecting suspicious path: [%s]&quot;, buf);
 		buf[0] = '\0';
 	}
 
-	MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
+	(void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
 }
 
+#if defined(_WIN32_WCE)
+
+static time_t
+time(time_t *ptime)
+{
+	time_t		t;
+	SYSTEMTIME	st;
+	FILETIME	ft;
+
+	GetSystemTime(&amp;st);
+	SystemTimeToFileTime(&amp;st, &amp;ft);
+	t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime);
+
+	if (ptime != NULL)
+		*ptime = t;
+
+	return (t);
+}
+
+static time_t
+mktime(struct tm *ptm)
+{
+	SYSTEMTIME	st;
+	FILETIME	ft, lft;
+
+	st.wYear = ptm-&gt;tm_year + 1900;
+	st.wMonth = ptm-&gt;tm_mon + 1;
+	st.wDay = ptm-&gt;tm_mday;
+	st.wHour = ptm-&gt;tm_hour;
+	st.wMinute = ptm-&gt;tm_min;
+	st.wSecond = ptm-&gt;tm_sec;
+	st.wMilliseconds = 0;
+
+	SystemTimeToFileTime(&amp;st, &amp;ft);
+	LocalFileTimeToFileTime(&amp;ft, &amp;lft);
+	return (time_t)((MAKEUQUAD(lft.dwLowDateTime, lft.dwHighDateTime) -
+	    EPOCH_DIFF) / RATE_DIFF);
+}
+
+static struct tm *
+localtime(const time_t *ptime, struct tm *ptm)
+{
+	uint64_t	t = ((uint64_t)*ptime) * RATE_DIFF + EPOCH_DIFF;
+	FILETIME	ft, lft;
+	SYSTEMTIME	st;
+	TIME_ZONE_INFORMATION	tzinfo;
+
+	if (ptm == NULL)
+		return NULL;
+
+	* (uint64_t *) &amp;ft = t;
+	FileTimeToLocalFileTime(&amp;ft, &amp;lft);
+	FileTimeToSystemTime(&amp;lft, &amp;st);
+	ptm-&gt;tm_year = st.wYear - 1900;
+	ptm-&gt;tm_mon = st.wMonth - 1;
+	ptm-&gt;tm_wday = st.wDayOfWeek;
+	ptm-&gt;tm_mday = st.wDay;
+	ptm-&gt;tm_hour = st.wHour;
+	ptm-&gt;tm_min = st.wMinute;
+	ptm-&gt;tm_sec = st.wSecond;
+	ptm-&gt;tm_yday = 0; // hope nobody uses this
+	ptm-&gt;tm_isdst = ((GetTimeZoneInformation(&amp;tzinfo) ==
+	    TIME_ZONE_ID_DAYLIGHT) ? 1 : 0);
+
+	return ptm;
+}
+
+static size_t
+strftime(char *dst, size_t dst_size, const char *fmt, const struct tm *tm)
+{
+	(void) snprintf(dst, dst_size, &quot;implement strftime() for WinCE&quot;);
+	return (0);
+}	
+#endif
+
 static int
-mg_open(const char *path, int flags, int mode)
+mg_rename(const char* oldname, const char* newname)
 {
-	wchar_t	wbuf[FILENAME_MAX];
+	wchar_t	woldbuf[FILENAME_MAX];
+	wchar_t	wnewbuf[FILENAME_MAX];
+
+	to_unicode(oldname, woldbuf, ARRAY_SIZE(woldbuf));
+	to_unicode(newname, wnewbuf, ARRAY_SIZE(wnewbuf));
+
+	return (MoveFileW(woldbuf, wnewbuf) ? 0 : -1);
+}
+
+
+static FILE *
+mg_fopen(const char *path, const char *mode)
+{
+	wchar_t	wbuf[FILENAME_MAX], wmode[20];
 
 	to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
+	MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode));
 
-	return (_wopen(wbuf, flags, mode));
+	return (_wfopen(wbuf, wmode));
 }
 
 static int
 mg_stat(const char *path, struct mgstat *stp)
 {
-	struct	_stat64	st;
-	int		ok;
-	wchar_t		wbuf[FILENAME_MAX];
+	int				ok = -1; /* Error */
+	wchar_t				wbuf[FILENAME_MAX];
+	WIN32_FILE_ATTRIBUTE_DATA	info;
 
 	to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
-	if (_wstat64(wbuf, &amp;st) == 0) {
-		ok = 0;
-		stp-&gt;size = st.st_size;
-		stp-&gt;mtime = st.st_mtime;
-		stp-&gt;is_directory = S_ISDIR(st.st_mode);
-	} else {
-		ok = -1;
+
+	if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &amp;info) != 0) {
+		stp-&gt;size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
+		stp-&gt;mtime = SYS2UNIX_TIME(info.ftLastWriteTime.dwLowDateTime,
+		    info.ftLastWriteTime.dwHighDateTime);
+		stp-&gt;is_directory =
+		    info.dwFileAttributes &amp; FILE_ATTRIBUTE_DIRECTORY;
+		ok = 0;  /* Success */
 	}
 
 	return (ok);
@@ -766,26 +1101,62 @@ mg_remove(const char *path)
 
 	to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
 
-	return (_wremove(wbuf));
+	return (DeleteFileW(wbuf) ? 0 : -1);
 }
 
+static int
+mg_mkdir(const char *path, int mode)
+{
+	char	buf[FILENAME_MAX];
+	wchar_t	wbuf[FILENAME_MAX];
+
+	mode = 0; /* Unused */
+	mg_strlcpy(buf, path, sizeof(buf));
+	fix_directory_separators(buf);
+
+	(void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
+
+	return (CreateDirectoryW(wbuf, NULL) ? 0 : -1);
+}
+
+static char *
+mg_getcwd(char *buf, int buf_len)
+{
+	wchar_t		wbuf[FILENAME_MAX], *basename;
+
+	if (GetModuleFileNameW(NULL, wbuf, ARRAY_SIZE(wbuf))) {
+		if ((basename = wcsrchr(wbuf, DIRSEP)) != NULL) {
+			*basename = L'\0';
+			if (WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf,
+			    buf_len, NULL, NULL) &gt; 0)
+				return (buf);
+		}
+	}
+
+	return (NULL);
+}
+
+/*
+ * Implementation of POSIX opendir/closedir/readdir for Windows.
+ */
 static DIR *
 opendir(const char *name)
 {
 	DIR	*dir = NULL;
-	char	path[FILENAME_MAX];
 	wchar_t	wpath[FILENAME_MAX];
+	DWORD attrs;
 
-	if (name == NULL || name[0] == '\0') {
-		errno = EINVAL;
+	if (name == NULL) {
+		SetLastError(ERROR_BAD_ARGUMENTS);
 	} else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
-		errno = ENOMEM;
+		SetLastError(ERROR_NOT_ENOUGH_MEMORY);
 	} else {
-		mg_snprintf(path, sizeof(path), &quot;%s/*&quot;, name);
-		to_unicode(path, wpath, ARRAY_SIZE(wpath));
-		dir-&gt;handle = FindFirstFileW(wpath, &amp;dir-&gt;info);
-
-		if (dir-&gt;handle != INVALID_HANDLE_VALUE) {
+		to_unicode(name, wpath, ARRAY_SIZE(wpath));
+		attrs = GetFileAttributesW(wpath);
+		if (attrs != 0xFFFFFFFF &amp;&amp;
+		    ((attrs &amp; FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
+			(void) wcscat(wpath, L&quot;\\*&quot;);
+			dir-&gt;handle = FindFirstFileW(wpath, &amp;dir-&gt;info);
 			dir-&gt;result.d_name[0] = '\0';
 		} else {
 			free(dir);
@@ -799,18 +1170,18 @@ opendir(const char *name)
 static int
 closedir(DIR *dir)
 {
-	int result = -1;
+	int result = 0;
 
 	if (dir != NULL) {
 		if (dir-&gt;handle != INVALID_HANDLE_VALUE)
 			result = FindClose(dir-&gt;handle) ? 0 : -1;
 
 		free(dir);
+	} else {
+		result = -1;
+		SetLastError(ERROR_BAD_ARGUMENTS);
 	}
 
-	if (result == -1)
-		errno = EBADF;
-
 	return (result);
 }
 
@@ -819,17 +1190,23 @@ readdir(DIR *dir)
 {
 	struct dirent *result = 0;
 
-	if (dir &amp;&amp; dir-&gt;handle != INVALID_HANDLE_VALUE) {
-		if(!dir-&gt;result.d_name ||
-		    FindNextFileW(dir-&gt;handle, &amp;dir-&gt;info)) {
+	if (dir) {
+		if (dir-&gt;handle != INVALID_HANDLE_VALUE) {
 			result = &amp;dir-&gt;result;
-
-			WideCharToMultiByte(CP_UTF8, 0, dir-&gt;info.cFileName,
-			    -1, result-&gt;d_name,
+			(void) WideCharToMultiByte(CP_UTF8, 0,
+			    dir-&gt;info.cFileName, -1, result-&gt;d_name,
 			    sizeof(result-&gt;d_name), NULL, NULL);
+
+			if (!FindNextFileW(dir-&gt;handle, &amp;dir-&gt;info)) {
+				(void) FindClose(dir-&gt;handle);
+				dir-&gt;handle = INVALID_HANDLE_VALUE;
+			}
+
+		} else {
+			SetLastError(ERROR_FILE_NOT_FOUND);
 		}
 	} else {
-		errno = EBADF;
+		SetLastError(ERROR_BAD_ARGUMENTS);
 	}
 
 	return (result);
@@ -838,16 +1215,38 @@ readdir(DIR *dir)
 #define	set_close_on_exec(fd)	/* No FD_CLOEXEC on Windows */
 
 static int
-start_thread(void * (*func)(void *), void *param)
+start_thread(struct mg_context *ctx, mg_thread_func_t func, void *param)
 {
-	return (_beginthread((void (__cdecl *)( void *))func, 0, param) == 0);
+	HANDLE	hThread;
+
+	ctx = NULL;	/* Unused */
+	
+	hThread = CreateThread(NULL, 0,
+	    (LPTHREAD_START_ROUTINE) func, param, 0, NULL);
+
+	if (hThread != NULL)
+		(void) CloseHandle(hThread);
+
+	return (hThread == NULL ? -1 : 0);
+}
+
+static HANDLE
+dlopen(const char *dll_name, int flags)
+{
+	wchar_t	wbuf[FILENAME_MAX];
+
+	flags = 0; /* Unused */
+	to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf));
+
+	return (LoadLibraryW(wbuf));
 }
 
+#if !defined(NO_CGI)
 static int
 kill(pid_t pid, int sig_num)
 {
-	TerminateProcess(pid, sig_num);
-	CloseHandle(pid);
+	(void) TerminateProcess(pid, sig_num);
+	(void) CloseHandle(pid);
 	return (0);
 }
 
@@ -872,16 +1271,16 @@ spawn_process(struct mg_connection *conn, const char *prog, char *envblk,
 	si.wShowWindow	= SW_HIDE;
 
 	me = GetCurrentProcess();
-	DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdin), me,
+	(void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdin), me,
 	    &amp;si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
-	DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdout), me,
+	(void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdout), me,
 	    &amp;si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
 
 	/* If CGI file is a script, try to read the interpreter line */
 	interp = conn-&gt;ctx-&gt;options[OPT_CGI_INTERPRETER];
 	if (interp == NULL) {
 		line[2] = '\0';
-		(void) mg_snprintf(cmdline, sizeof(cmdline), &quot;%s%c%s&quot;,
+		(void) mg_snprintf(conn, cmdline, sizeof(cmdline), &quot;%s%c%s&quot;,
 		    dir, DIRSEP, prog);
 		if ((fp = fopen(cmdline, &quot;r&quot;)) != NULL) {
 			(void) fgets(line, sizeof(line), fp);
@@ -894,31 +1293,31 @@ spawn_process(struct mg_connection *conn, const char *prog, char *envblk,
 			(void) fclose(fp);
 		}
 		interp = line + 2;
-		(void) mg_snprintf(cmdline, sizeof(cmdline), &quot;%s%s%s&quot;,
-		    line + 2, line[2] == '\0' ? &quot;&quot; : &quot; &quot;, prog);
 	}
 
 	if ((p = (char *) strrchr(prog, '/')) != NULL)
 		prog = p + 1;
 
-	(void) mg_snprintf(cmdline, sizeof(cmdline), &quot;%s %s&quot;, interp, prog);
-	(void) mg_snprintf(line, sizeof(line), &quot;%s&quot;, dir);
+	(void) mg_snprintf(conn, cmdline, sizeof(cmdline), &quot;%s%s%s&quot;,
+	    interp, interp[0] == '\0' ? &quot;&quot; : &quot; &quot;, prog);
+
+	(void) mg_snprintf(conn, line, sizeof(line), &quot;%s&quot;, dir);
 	fix_directory_separators(line);
-	fix_directory_separators(cmdline);
 
+	DEBUG_TRACE((DEBUG_MGS_PREFIX &quot;%s: Running [%s]&quot;, __func__, cmdline));
 	if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
 	    CREATE_NEW_PROCESS_GROUP, envblk, line, &amp;si, &amp;pi) == 0) {
 		cry(conn, &quot;%s: CreateProcess(%s): %d&quot;,
 		    __func__, cmdline, ERRNO);
 		pi.hProcess = (pid_t) -1;
 	} else {
-		close(fd_stdin);
-		close(fd_stdout);
+		(void) close(fd_stdin);
+		(void) close(fd_stdout);
 	}
 
-	CloseHandle(si.hStdOutput);
-	CloseHandle(si.hStdInput);
-	CloseHandle(pi.hThread);
+	(void) CloseHandle(si.hStdOutput);
+	(void) CloseHandle(si.hStdInput);
+	(void) CloseHandle(pi.hThread);
 
 	return ((pid_t) pi.hProcess);
 }
@@ -928,20 +1327,15 @@ pipe(int *fds)
 {
 	return (_pipe(fds, BUFSIZ, _O_BINARY));
 }
+#endif /* !NO_CGI */
 
 static int
-mg_mkdir(const char *path, int mode)
+set_non_blocking_mode(struct mg_connection *conn, SOCKET sock)
 {
-	char	buf[FILENAME_MAX];
-	wchar_t	wbuf[FILENAME_MAX];
-
-	mode = 0; /* Unused */
-	mg_strlcpy(buf, path, sizeof(buf));
-	fix_directory_separators(buf);
-
-	MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
+	unsigned long	on = 1;
 
-	return (_wmkdir(wbuf));
+	conn = NULL; /* unused */
+	return (ioctlsocket(sock, FIONBIO, &amp;on));
 }
 
 #else
@@ -971,7 +1365,7 @@ set_close_on_exec(int fd)
 }
 
 static int
-start_thread(void * (*func)(void *), void *param)
+start_thread(struct mg_context *ctx, mg_thread_func_t func, void *param)
 {
 	pthread_t	thread_id;
 	pthread_attr_t	attr;
@@ -981,7 +1375,7 @@ start_thread(void * (*func)(void *), void *param)
 	(void) pthread_attr_setdetachstate(&amp;attr, PTHREAD_CREATE_DETACHED);
 
 	if ((retval = pthread_create(&amp;thread_id, &amp;attr, func, param)) != 0)
-		cry(NULL, &quot;%s: %s&quot;, __func__, strerror(retval));
+		cry(fc(ctx), &quot;%s: %s&quot;, __func__, strerror(retval));
 
 	return (retval);
 }
@@ -1041,20 +1435,36 @@ spawn_process(struct mg_connection *conn, const char *prog, char *envblk,
 	return (pid);
 }
 #endif /* !NO_CGI */
+
+static int
+set_non_blocking_mode(struct mg_connection *conn, SOCKET sock)
+{
+	int	flags, ok = -1;
+
+	if ((flags = fcntl(sock, F_GETFL, 0)) == -1) {
+		cry(conn, &quot;%s: fcntl(F_GETFL): %d&quot;, __func__, ERRNO);
+	} else if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) != 0) {
+		cry(conn, &quot;%s: fcntl(F_SETFL): %d&quot;, __func__, ERRNO);
+	} else {
+		ok = 0;	/* Success */
+	}
+
+	return (ok);
+}
 #endif /* _WIN32 */
 
 static void
-mg_lock(struct mg_context *ctx)
+lock_option(struct mg_context *ctx, int opt_index)
 {
-	if (pthread_mutex_lock(&amp;ctx-&gt;opt_mutex) != 0)
-		cry(NULL, &quot;pthread_mutex_lock: %s&quot;, strerror(ERRNO));
+	if (pthread_mutex_lock(&amp;ctx-&gt;opt_mutex[opt_index]) != 0)
+		cry(fc(ctx), &quot;pthread_mutex_lock: %s&quot;, strerror(ERRNO));
 }
 
 static void
-mg_unlock(struct mg_context *ctx)
+unlock_option(struct mg_context *ctx, int opt_index)
 {
-	if (pthread_mutex_unlock(&amp;ctx-&gt;opt_mutex) != 0)
-		cry(NULL, &quot;pthread_mutex_unlock: %s&quot;, strerror(ERRNO));
+	if (pthread_mutex_unlock(&amp;ctx-&gt;opt_mutex[opt_index]) != 0)
+		cry(fc(ctx), &quot;pthread_mutex_unlock: %s&quot;, strerror(ERRNO));
 }
 
 /*
@@ -1062,7 +1472,7 @@ mg_unlock(struct mg_context *ctx)
  * descriptor. Return number of bytes written.
  */
 static uint64_t
-push(int fd, SOCKET sock, SSL *ssl, const char *buf, uint64_t len)
+push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf, uint64_t len)
 {
 	uint64_t	sent;
 	int		n, k;
@@ -1075,8 +1485,10 @@ push(int fd, SOCKET sock, SSL *ssl, const char *buf, uint64_t len)
 
 		if (ssl != NULL) {
 			n = SSL_write(ssl, buf + sent, k);
-		} else if (fd != -1) {
-			n = write(fd, buf + sent, k);
+		} else if (fp != NULL) {
+			n = fwrite(buf + sent, 1, k, fp);
+			if (ferror(fp))
+				n = -1;
 		} else {
 			n = send(sock, buf + sent, k, 0);
 		}
@@ -1095,14 +1507,16 @@ push(int fd, SOCKET sock, SSL *ssl, const char *buf, uint64_t len)
  * Return number of bytes read.
  */
 static int
-pull(int fd, SOCKET sock, SSL *ssl, char *buf, int len)
+pull(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int len)
 {
 	int	nread;
 
 	if (ssl != NULL) {
 		nread = SSL_read(ssl, buf, len);
-	} else if (fd != -1) {
-		nread = read(fd, buf, (size_t) len);
+	} else if (fp != NULL) {
+		nread = fread(buf, 1, (size_t) len, fp);
+		if (ferror(fp))
+			nread = -1;
 	} else {
 		nread = recv(sock, buf, (size_t) len, 0);
 	}
@@ -1114,7 +1528,7 @@ int
 mg_write(struct mg_connection *conn, const void *buf, int len)
 {
 	assert(len &gt;= 0);
-	return ((int) push(-1, conn-&gt;client.sock, conn-&gt;ssl,
+	return ((int) push(NULL, conn-&gt;client.sock, conn-&gt;ssl,
 				(const char *) buf, (uint64_t) len));
 }
 
@@ -1126,7 +1540,7 @@ mg_printf(struct mg_connection *conn, const char *fmt, ...)
 	va_list	ap;
 
 	va_start(ap, fmt);
-	len = mg_vsnprintf(buf, sizeof(buf), fmt, ap);
+	len = mg_vsnprintf(conn, buf, sizeof(buf), fmt, ap);
 	va_end(ap);
 
 	return (mg_write(conn, buf, len));
@@ -1156,7 +1570,7 @@ url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
 {
 	size_t	i, j;
 	int	a, b;
-#define	HEXTOI(x)  (isdigit(x) ? x - '0' : x - 'W')
+#define	HEXTOI(x)	(isdigit(x) ? x - '0' : x - 'W')
 
 	for (i = j = 0; i &lt; src_len &amp;&amp; j &lt; dst_len - 1; i++, j++) {
 		if (src[i] == '%' &amp;&amp;
@@ -1217,6 +1631,17 @@ get_var(const char *name, const char *buf, size_t buf_len)
 }
 
 /*
+ * Free the pointer returned by mg_get_var(). This is needed for languages
+ * like python, to have an ability to free allocated data without
+ * loading C runtime library and calling free().
+ */
+void
+mg_free(char *data)
+{
+	free(data);
+}
+
+/*
  * Return form data variable.
  * It can be specified in query string, or in the POST data.
  * Return NULL if the variable not found, or allocated 0-terminated value.
@@ -1247,66 +1672,73 @@ mg_get_var(const struct mg_connection *conn, const char *name)
  * Transform URI to the file name.
  */
 static void
-make_path(struct mg_context *ctx, const char *uri, char *buf, size_t buf_len)
+convert_uri_to_file_name(struct mg_connection *conn, const char *uri,
+		char *buf, size_t buf_len)
 {
-	char	*p, *s;
-	size_t	len;
+	struct mg_context	*ctx = conn-&gt;ctx;
+	struct vec		uri_vec, path_vec;
+	const char		*list;
 
-	mg_snprintf(buf, buf_len, &quot;%s%s&quot;, ctx-&gt;options[OPT_ROOT], uri);
+	lock_option(ctx, OPT_ROOT);
+	mg_snprintf(conn, buf, buf_len, &quot;%s%s&quot;, ctx-&gt;options[OPT_ROOT], uri);
+	unlock_option(ctx, OPT_ROOT);
 
 	/* If requested URI has aliased prefix, use alternate root */
-	mg_lock(ctx);
-       	s = ctx-&gt;options[OPT_ALIASES];
-	FOR_EACH_WORD_IN_LIST(s, len) {
+	lock_option(ctx, OPT_ALIASES);
+	list = ctx-&gt;options[OPT_ALIASES];
 
-		p = (char *) memchr(s, '=', len);
-		if (p == NULL || p &gt;= s + len || p == s)
-			continue;
-
-		if (memcmp(uri, s, p - s) == 0) {
-			(void) mg_snprintf(buf, buf_len, &quot;%.*s%s&quot;,
-			    (s + len) - p - 1, p + 1, uri + (p - s));
+	while ((list = next_option(list, &amp;uri_vec, &amp;path_vec)) != NULL) {
+		if (memcmp(uri, uri_vec.ptr, uri_vec.len) == 0) {
+			(void) mg_snprintf(conn, buf, buf_len, &quot;%.*s%s&quot;,
+			    path_vec.len, path_vec.ptr, uri + uri_vec.len);
 			break;
 		}
 	}
-	mg_unlock(ctx);
-
-	/* Remove trailing '/' characters, if directory is requested */
-	for (p = buf + strlen(buf) - 1; p &gt; buf &amp;&amp; *p == '/'; p--)
-		*p = '\0';
+	unlock_option(ctx, OPT_ALIASES);
 
 #ifdef _WIN32
-	for (p = buf; *p != '\0'; p++)
-		if (*p == '/')
-			*p = '\\';
+	fix_directory_separators(buf);
 #endif /* _WIN32 */
 }
 
 /*
- * Setup listening socket on given port, return socket
+ * Setup listening socket on given address, return socket.
+ * Address format: [local_ip_address:]port_number
  */
 static SOCKET
-mg_open_listening_port(int port)
+mg_open_listening_port(struct mg_context *ctx, const char *str, struct usa *usa)
 {
 	SOCKET		sock;
-	int		on = 1;
-	struct usa	sa;
+	int		on = 1, a, b, c, d, port;
+
+	/* MacOS needs that. If we do not zero it, bind() will fail. */
+	(void) memset(usa, 0, sizeof(*usa));
+
+	if (sscanf(str, &quot;%d.%d.%d.%d:%d&quot;, &amp;a, &amp;b, &amp;c, &amp;d, &amp;port) == 5) {
+		/* IP address to bind to is specified */
+		usa-&gt;u.sin.sin_addr.s_addr =
+		    htonl((a &lt;&lt; 24) | (b &lt;&lt; 16) | (c &lt;&lt; 8) | d);
+	} else if (sscanf(str, &quot;%d&quot;, &amp;port) == 1) {
+		/* Only port number is specified. Bind to all addresses */
+		usa-&gt;u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
+	} else {
+		return (INVALID_SOCKET);
+	}
 
-	sa.len				= sizeof(sa.u.sin);
-	sa.u.sin.sin_family		= AF_INET;
-	sa.u.sin.sin_port		= htons((uint16_t) port);
-	sa.u.sin.sin_addr.s_addr	= htonl(INADDR_ANY);
+	usa-&gt;len			= sizeof(usa-&gt;u.sin);
+	usa-&gt;u.sin.sin_family		= AF_INET;
+	usa-&gt;u.sin.sin_port		= htons((uint16_t) port);
 
 	if ((sock = socket(PF_INET, SOCK_STREAM, 6)) != INVALID_SOCKET &amp;&amp;
 	    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
 	    (char *) &amp;on, sizeof(on)) == 0 &amp;&amp;
-	    bind(sock, &amp;sa.u.sa, sa.len) == 0 &amp;&amp;
+	    bind(sock, &amp;usa-&gt;u.sa, usa-&gt;len) == 0 &amp;&amp;
 	    listen(sock, 128) == 0) {
 		/* Success */
 		set_close_on_exec(sock);
 	} else {
 		/* Error */
-		cry(NULL, &quot;%s(%d): %s&quot;, __func__, port, strerror(errno));
+		cry(fc(ctx), &quot;%s(%d): %s&quot;, __func__, port, strerror(ERRNO));
 		if (sock != INVALID_SOCKET)
 			(void) closesocket(sock);
 		sock = INVALID_SOCKET;
@@ -1316,7 +1748,10 @@ mg_open_listening_port(int port)
 }
 
 /*
- * Check whether full request is buffered Return headers length, or 0
+ * Check whether full request is buffered. Return:
+ *   -1         if request is malformed
+ *    0         if request is not yet fully buffered
+ *   &gt;0         actual request length, including last \r\n\r\n
  */
 static int
 get_request_len(const char *buf, size_t buflen)
@@ -1344,10 +1779,6 @@ get_request_len(const char *buf, size_t buflen)
 static int
 montoi(const char *s)
 {
-	static const char *month_names[] = {
-		&quot;Jan&quot;, &quot;Feb&quot;, &quot;Mar&quot;, &quot;Apr&quot;, &quot;May&quot;, &quot;Jun&quot;,
-		&quot;Jul&quot;, &quot;Aug&quot;, &quot;Sep&quot;, &quot;Oct&quot;, &quot;Nov&quot;, &quot;Dec&quot;
-	};
 	size_t	i;
 
 	for (i = 0; i &lt; sizeof(month_names) / sizeof(month_names[0]); i++)
@@ -1363,8 +1794,8 @@ montoi(const char *s)
 static time_t
 date_to_epoch(const char *s)
 {
-	struct tm	tm, *tmp;
 	time_t		current_time;
+	struct tm	tm, *tmp;
 	char		mon[32];
 	int		sec, min, hour, mday, month, year;
 
@@ -1401,80 +1832,124 @@ date_to_epoch(const char *s)
 	return (mktime(&amp;tm));
 }
 
+/*
+ * Protect against directory disclosure attack by removing '..',
+ * excessive '/' and '\' characters
+ */
 static void
-remove_double_dots(char *s)
+remove_double_dots_and_double_slashes(char *s)
 {
 	char	*p = s;
 
 	while (*s != '\0') {
 		*p++ = *s++;
-		if (s[-1] == '/' || s[-1] == '\\')
-			while (*s == '.' || *s == '/' || *s == '\\')
+		if (s[-1] == '/' || s[-1] == '\\') {
+			/* Skip all following slashes and backslashes */
+			while (*s == '/' || *s == '\\')
 				s++;
+
+			/* Skip all double-dots */
+			while (*s == '.' &amp;&amp; s[1] == '.')
+				s += 2;
+		}
 	}
 	*p = '\0';
 }
 
+/*
+ * Built-in mime types
+ */
 static const struct {
 	const char	*extension;
+	size_t		ext_len;
 	const char	*mime_type;
+	size_t		mime_type_len;
 } mime_types[] = {
-	{&quot;html&quot;,	&quot;text/html&quot;			},
-	{&quot;htm&quot;,		&quot;text/html&quot;			},
-	{&quot;shtm&quot;,	&quot;text/html&quot;			},
-	{&quot;shtml&quot;,	&quot;text/html&quot;			},
-	{&quot;css&quot;,		&quot;text/css&quot;			},
-	{&quot;js&quot;,		&quot;application/x-javascript&quot;	},
-	{&quot;ico&quot;,		&quot;image/x-icon&quot;			},
-	{&quot;gif&quot;,		&quot;image/gif&quot;			},
-	{&quot;jpg&quot;,		&quot;image/jpeg&quot;			},
-	{&quot;jpeg&quot;,	&quot;image/jpeg&quot;			},
-	{&quot;png&quot;,		&quot;image/png&quot;			},
-	{&quot;svg&quot;,		&quot;image/svg+xml&quot;			},
-	{&quot;torrent&quot;,	&quot;application/x-bittorrent&quot;	},
-	{&quot;wav&quot;,		&quot;audio/x-wav&quot;			},
-	{&quot;mp3&quot;,		&quot;audio/x-mp3&quot;			},
-	{&quot;mid&quot;,		&quot;audio/mid&quot;			},
-	{&quot;m3u&quot;,		&quot;audio/x-mpegurl&quot;		},
-	{&quot;ram&quot;,		&quot;audio/x-pn-realaudio&quot;		},
-	{&quot;ra&quot;,		&quot;audio/x-pn-realaudio&quot;		},
-	{&quot;doc&quot;,		&quot;application/msword&quot;,		},
-	{&quot;exe&quot;,		&quot;application/octet-stream&quot;	},
-	{&quot;zip&quot;,		&quot;application/x-zip-compressed&quot;	},
-	{&quot;xls&quot;,		&quot;application/excel&quot;		},
-	{&quot;tgz&quot;,		&quot;application/x-tar-gz&quot;		},
-	{&quot;tar&quot;,		&quot;application/x-tar&quot;		},
-	{&quot;gz&quot;,		&quot;application/x-gunzip&quot;		},
-	{&quot;arj&quot;,		&quot;application/x-arj-compressed&quot;	},
-	{&quot;rar&quot;,		&quot;application/x-arj-compressed&quot;	},
-	{&quot;rtf&quot;,		&quot;application/rtf&quot;		},
-	{&quot;pdf&quot;,		&quot;application/pdf&quot;		},
-	{&quot;swf&quot;,		&quot;application/x-shockwave-flash&quot;	},
-	{&quot;mpg&quot;,		&quot;video/mpeg&quot;			},
-	{&quot;mpeg&quot;,	&quot;video/mpeg&quot;			},
-	{&quot;asf&quot;,		&quot;video/x-ms-asf&quot;		},
-	{&quot;avi&quot;,		&quot;video/x-msvideo&quot;		},
-	{&quot;bmp&quot;,		&quot;image/bmp&quot;			},
-	{NULL,		NULL				}
+	{&quot;.html&quot;,	5,	&quot;text/html&quot;,			9},
+	{&quot;.htm&quot;,	4,	&quot;text/html&quot;,			9},
+	{&quot;.shtm&quot;,	5,	&quot;text/html&quot;,			9},
+	{&quot;.shtml&quot;,	6,	&quot;text/html&quot;,			9},
+	{&quot;.css&quot;,	4,	&quot;text/css&quot;,			8},
+	{&quot;.js&quot;,		3,	&quot;application/x-javascript&quot;,	24},
+	{&quot;.ico&quot;,	4,	&quot;image/x-icon&quot;,			12},
+	{&quot;.gif&quot;,	4,	&quot;image/gif&quot;,			9},
+	{&quot;.jpg&quot;,	4,	&quot;image/jpeg&quot;,			10},
+	{&quot;.jpeg&quot;,	5,	&quot;image/jpeg&quot;,			10},
+	{&quot;.png&quot;,	4,	&quot;image/png&quot;,			9},
+	{&quot;.svg&quot;,	4,	&quot;image/svg+xml&quot;,		13},
+	{&quot;.torrent&quot;,	8,	&quot;application/x-bittorrent&quot;,	24},
+	{&quot;.wav&quot;,	4,	&quot;audio/x-wav&quot;,			11},
+	{&quot;.mp3&quot;,	4,	&quot;audio/x-mp3&quot;,			11},
+	{&quot;.mid&quot;,	4,	&quot;audio/mid&quot;,			9},
+	{&quot;.m3u&quot;,	4,	&quot;audio/x-mpegurl&quot;,		15},
+	{&quot;.ram&quot;,	4,	&quot;audio/x-pn-realaudio&quot;,		20},
+	{&quot;.ra&quot;,		3,	&quot;audio/x-pn-realaudio&quot;,		20},
+	{&quot;.doc&quot;,	4,	&quot;application/msword&quot;,		19},
+	{&quot;.exe&quot;,	4,	&quot;application/octet-stream&quot;,	24},
+	{&quot;.zip&quot;,	4,	&quot;application/x-zip-compressed&quot;,	28},
+	{&quot;.xls&quot;,	4,	&quot;application/excel&quot;,		17},
+	{&quot;.tgz&quot;,	4,	&quot;application/x-tar-gz&quot;,		20},
+	{&quot;.tar&quot;,	4,	&quot;application/x-tar&quot;,		17},
+	{&quot;.gz&quot;,		3,	&quot;application/x-gunzip&quot;,		20},
+	{&quot;.arj&quot;,	4,	&quot;application/x-arj-compressed&quot;,	28},
+	{&quot;.rar&quot;,	4,	&quot;application/x-arj-compressed&quot;,	28},
+	{&quot;.rtf&quot;,	4,	&quot;application/rtf&quot;,		15},
+	{&quot;.pdf&quot;,	4,	&quot;application/pdf&quot;,		15},
+	{&quot;.swf&quot;,	4,	&quot;application/x-shockwave-flash&quot;,29},
+	{&quot;.mpg&quot;,	4,	&quot;video/mpeg&quot;,			10},
+	{&quot;.mpeg&quot;,	5,	&quot;video/mpeg&quot;,			10},
+	{&quot;.asf&quot;,	4,	&quot;video/x-ms-asf&quot;,		14},
+	{&quot;.avi&quot;,	4,	&quot;video/x-msvideo&quot;,		15},
+	{&quot;.bmp&quot;,	4,	&quot;image/bmp&quot;,			9},
+	{NULL,		0,	NULL,				0}
 };
 
-static const char *
-get_mime_type(const char *path)
+/*
+ * Look at the &quot;path&quot; extension and figure what mime type it has.
+ * Store mime type in the vector.
+ */
+static void
+get_mime_type(struct mg_context *ctx, const char *path, struct vec *vec)
 {
-	size_t		i;
-	const char	*ext;
+	struct vec	ext_vec, mime_vec;
+	const char	*list, *ext;
+	size_t		i, path_len;
+
+	path_len = strlen(path);
 
-	if ((ext = strrchr(path, '.')) != NULL) {
-		ext++;
-		for (i = 0; mime_types[i].extension != NULL; i++)
-			if (!mg_strcasecmp(ext, mime_types[i].extension))
-				return (mime_types[i].mime_type);
+	/*
+	 * Scan user-defined mime types first, in case user wants to
+	 * override default mime types.
+	 */
+	lock_option(ctx, OPT_MIME_TYPES);
+	list = ctx-&gt;options[OPT_MIME_TYPES];
+	while ((list = next_option(list, &amp;ext_vec, &amp;mime_vec)) != NULL) {
+		/* ext now points to the path suffix */
+		ext = path + path_len - ext_vec.len;
+		if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
+			*vec = mime_vec;
+			unlock_option(ctx, OPT_MIME_TYPES);
+			return;
+		}
+	}
+	unlock_option(ctx, OPT_MIME_TYPES);
+
+	/* Now scan built-in mime types */
+	for (i = 0; mime_types[i].extension != NULL; i++) {
+		ext = path + (path_len - mime_types[i].ext_len);
+		if (path_len &gt; mime_types[i].ext_len &amp;&amp;
+		    mg_strcasecmp(ext, mime_types[i].extension) == 0) {
+			vec-&gt;ptr = mime_types[i].mime_type;
+			vec-&gt;len = mime_types[i].mime_type_len;
+			return;
+		}
 	}
 
-	return (&quot;text/plain&quot;);
+	/* Nothing found. Fall back to text/plain */
+	vec-&gt;ptr = &quot;text/plain&quot;;
+	vec-&gt;len = 10;
 }
 
-#if !defined(NO_AUTH)
 #ifndef HAVE_MD5
 typedef struct MD5Context {
 	uint32_t	buf[4];
@@ -1734,7 +2209,7 @@ bin2str(char *to, const unsigned char *p, size_t len)
  * Return stringified MD5 hash for list of vectors.
  * buf must point to 33-bytes long buffer
  */
-void
+static void
 mg_md5(char *buf, ...)
 {
 	unsigned char	hash[16];
@@ -1782,22 +2257,24 @@ check_password(const char *method, const char *ha1, const char *uri,
  * or search for .htpasswd in the requested directory.
  */
 static FILE *
-open_auth_file(struct mg_context *ctx, const char *path)
+open_auth_file(struct mg_connection *conn, const char *path)
 {
-	char 		name[FILENAME_MAX];
-	const char	*p, *e;
-	struct mgstat	st;
-	FILE		*fp;
+	struct mg_context	*ctx = conn-&gt;ctx;
+	char 			name[FILENAME_MAX];
+	const char		*p, *e;
+	struct mgstat		st;
+	FILE			*fp;
 
 	if (ctx-&gt;options[OPT_AUTH_GPASSWD] != NULL) {
 		/* Use global passwords file */
-		if ((fp = fopen(ctx-&gt;options[OPT_AUTH_GPASSWD], &quot;r&quot;)) == NULL)
-			cry(NULL, &quot;fopen(%s): %s&quot;,
+		fp =  mg_fopen(ctx-&gt;options[OPT_AUTH_GPASSWD], &quot;r&quot;);
+		if (fp == NULL)
+			cry(fc(ctx), &quot;fopen(%s): %s&quot;,
 			    ctx-&gt;options[OPT_AUTH_GPASSWD], strerror(ERRNO));
 	} else if (!mg_stat(path, &amp;st) &amp;&amp; st.is_directory) {
-		(void) mg_snprintf(name, sizeof(name), &quot;%s%c%s&quot;,
+		(void) mg_snprintf(conn, name, sizeof(name), &quot;%s%c%s&quot;,
 		    path, DIRSEP, PASSWORDS_FILE_NAME);
-		fp = fopen(name, &quot;r&quot;);
+		fp = mg_fopen(name, &quot;r&quot;);
 	} else {
 		/*
 		 * Try to find .htpasswd in requested directory.
@@ -1815,14 +2292,17 @@ open_auth_file(struct mg_context *ctx, const char *path)
 		 * Make up the path by concatenating directory name and
 		 * .htpasswd file name.
 		 */
-		(void) mg_snprintf(name, sizeof(name), &quot;%.*s%c%s&quot;,
+		(void) mg_snprintf(conn, name, sizeof(name), &quot;%.*s%c%s&quot;,
 		    (int) (e - p), p, DIRSEP, PASSWORDS_FILE_NAME);
-		fp = fopen(name, &quot;r&quot;);
+		fp = mg_fopen(name, &quot;r&quot;);
 	}
 
 	return (fp);
 }
 
+/*
+ * Parsed Authorization: header
+ */
 struct ah {
 	char	*user, *uri, *cnonce, *response, *qop, *nc, *nonce;
 };
@@ -1921,66 +2401,43 @@ static bool_t
 check_authorization(struct mg_connection *conn, const char *path)
 {
 	FILE		*fp;
-	size_t		len, n;
-	char		protected_path[FILENAME_MAX];
-	const char	*p, *s;
-	const struct callback *cb;
+	char		fname[FILENAME_MAX];
+	struct vec	uri_vec, filename_vec;
+	const char	*list;
 	bool_t		authorized;
 
 	fp = NULL;
 	authorized = TRUE;
 
-	mg_lock(conn-&gt;ctx);
-	s = conn-&gt;ctx-&gt;options[OPT_PROTECT];
-	FOR_EACH_WORD_IN_LIST(s, len) {
-
-		p = (const char *) memchr(s, '=', len);
-		if (p == NULL || p &gt;= s + len || p == s)
-			continue;
-
-		if (!memcmp(conn-&gt;request_info.uri, s, p - s)) {
-
-			n = (size_t) (s + len - p);
-			if (n &gt; sizeof(protected_path) - 1)
-				n = sizeof(protected_path) - 1;
-
-			mg_strlcpy(protected_path, p + 1, n);
-
-			if ((fp = fopen(protected_path, &quot;r&quot;)) == NULL)
+	lock_option(conn-&gt;ctx, OPT_PROTECT);
+	list = conn-&gt;ctx-&gt;options[OPT_PROTECT];
+	while ((list = next_option(list, &amp;uri_vec, &amp;filename_vec)) != NULL) {
+		if (!memcmp(conn-&gt;request_info.uri, uri_vec.ptr, uri_vec.len)) {
+			(void) mg_snprintf(conn, fname, sizeof(fname), &quot;%.*s&quot;,
+			    filename_vec.len, filename_vec.ptr);
+			if ((fp = mg_fopen(fname, &quot;r&quot;)) == NULL)
 				cry(conn, &quot;%s: cannot open %s: %s&quot;,
-				    __func__, protected_path, strerror(errno));
+				    __func__, fname, strerror(errno));
 			break;
 		}
 	}
-	mg_unlock(conn-&gt;ctx);
+	unlock_option(conn-&gt;ctx, OPT_PROTECT);
 
 	if (fp == NULL)
-		fp = open_auth_file(conn-&gt;ctx, path);
+		fp = open_auth_file(conn, path);
 
 	if (fp != NULL) {
 		authorized = authorize(conn, fp);
 		(void) fclose(fp);
 	}
 
-	if ((cb = find_callback(conn-&gt;ctx, TRUE,
-	    conn-&gt;request_info.uri, -1)) != NULL) {
-		struct ah	ah;
-		char		buf[MAX_REQUEST_SIZE];
-		void		*user_data = cb-&gt;user_data;
-
-		authorized = FALSE;
-		if (parse_auth_header(conn, buf, sizeof(buf), &amp;ah)) {
-			cb-&gt;func(conn, &amp;conn-&gt;request_info, &amp;user_data);
-			authorized = (bool_t) (long) user_data;
-		}
-	}
-
 	return (authorized);
 }
 
 static void
 send_authorization_request(struct mg_connection *conn)
 {
+	conn-&gt;request_info.status_code = 401;
 	(void) mg_printf(conn,
 	    &quot;HTTP/1.1 401 Unauthorized\r\n&quot;
 	    &quot;WWW-Authenticate: Digest qop=\&quot;auth\&quot;, &quot;
@@ -1994,7 +2451,7 @@ is_authorized_for_put(struct mg_connection *conn)
 	FILE	*fp;
 	int	ret = FALSE;
 
-	if ((fp = fopen(conn-&gt;ctx-&gt;options[OPT_AUTH_PUT], &quot;r&quot;)) != NULL) {
+	if ((fp = mg_fopen(conn-&gt;ctx-&gt;options[OPT_AUTH_PUT], &quot;r&quot;)) != NULL) {
 		set_close_on_exec(fileno(fp));
 		ret = authorize(conn, fp);
 		(void) fclose(fp);
@@ -2002,17 +2459,71 @@ is_authorized_for_put(struct mg_connection *conn)
 
 	return (ret);
 }
-#endif /* NO_AUTH */
 
-static bool_t
-does_client_want_keep_alive(const struct mg_connection *conn)
+int
+mg_modify_passwords_file(struct mg_context *ctx, const char *fname,
+		const char *user, const char *pass)
 {
-	const char *value = mg_get_header(conn, &quot;Connection&quot;);
+	int		found;
+	char		line[512], u[512], d[512], ha1[33], tmp[FILENAME_MAX];
+	const char	*domain;
+	FILE		*fp, *fp2;
 
-	/* HTTP/1.1 assumes keep-alive, if Connection header is not set */
-	return ((value == NULL &amp;&amp; conn-&gt;request_info.http_version_major == 1 &amp;&amp;
-	    conn-&gt;request_info.http_version_minor == 1) || (value != NULL &amp;&amp;
-	    !mg_strcasecmp(value, &quot;keep-alive&quot;)));
+	found = 0;
+	fp = fp2 = NULL;
+	domain = ctx-&gt;options[OPT_AUTH_DOMAIN];
+
+	/* Regard empty password as no password - remove user record. */
+	if (pass[0] == '\0')
+		pass = NULL;
+
+	(void) snprintf(tmp, sizeof(tmp), &quot;%s.tmp&quot;, fname);
+
+	/* Create the file if does not exist */
+	if ((fp = mg_fopen(fname, &quot;a+&quot;)) != NULL)
+		(void) fclose(fp);
+
+	/* Open the given file and temporary file */
+	if ((fp = mg_fopen(fname, &quot;r&quot;)) == NULL) {
+		cry(fc(ctx), &quot;Cannot open %s: %s&quot;, fname, strerror(errno));
+		return (0);
+	} else if ((fp2 = mg_fopen(tmp, &quot;w+&quot;)) == NULL) {
+		cry(fc(ctx), &quot;Cannot open %s: %s&quot;, tmp, strerror(errno));
+		return (0);
+	}
+
+	/* Copy the stuff to temporary file */
+	while (fgets(line, sizeof(line), fp) != NULL) {
+
+		if (sscanf(line, &quot;%[^:]:%[^:]:%*s&quot;, u, d) != 2)
+			continue;
+
+		if (!strcmp(u, user) &amp;&amp; !strcmp(d, domain)) {
+			found++;
+			if (pass != NULL) {
+				mg_md5(ha1, user, &quot;:&quot;, domain, &quot;:&quot;, pass, NULL);
+				fprintf(fp2, &quot;%s:%s:%s\n&quot;, user, domain, ha1);
+			}
+		} else {
+			(void) fprintf(fp2, &quot;%s&quot;, line);
+		}
+	}
+
+	/* If new user, just add it */
+	if (!found &amp;&amp; pass != NULL) {
+		mg_md5(ha1, user, &quot;:&quot;, domain, &quot;:&quot;, pass, NULL);
+		(void) fprintf(fp2, &quot;%s:%s:%s\n&quot;, user, domain, ha1);
+	}
+
+	/* Close files */
+	(void) fclose(fp);
+	(void) fclose(fp2);
+
+	/* Put the temp file in place of real file */
+	(void) mg_remove(fname);
+	(void) mg_rename(tmp, fname);
+
+	return (0);
 }
 
 struct de {
@@ -2021,26 +2532,35 @@ struct de {
 	struct mgstat		st;
 };
 
+/*
+ * This function is called from send_directory() and prints out
+ * single directory entry.
+ */
 static void
 print_dir_entry(struct de *de)
 {
 	char		size[64], mod[64];
 
 	if (de-&gt;st.is_directory) {
-		(void) mg_snprintf(size, sizeof(size), &quot;%s&quot;, &quot;[DIRECTORY]&quot;);
+		(void) mg_snprintf(de-&gt;conn,
+		    size, sizeof(size), &quot;%s&quot;, &quot;[DIRECTORY]&quot;);
 	} else {
+		/*
+		 * We use (signed) cast below because MSVC 6 compiler cannot
+		 * convert unsigned __int64 to double. Sigh.
+		 */
 		if (de-&gt;st.size &lt; 1024)
-			(void) mg_snprintf(size, sizeof(size),
+			(void) mg_snprintf(de-&gt;conn, size, sizeof(size),
 			    &quot;%lu&quot;, (unsigned long) de-&gt;st.size);
 		else if (de-&gt;st.size &lt; 1024 * 1024)
-			(void) mg_snprintf(size, sizeof(size),
-			    &quot;%.1fk&quot;, (double) de-&gt;st.size / 1024);
+			(void) mg_snprintf(de-&gt;conn, size, sizeof(size),
+			    &quot;%.1fk&quot;, (double) de-&gt;st.size / 1024.0);
 		else if (de-&gt;st.size &lt; 1024 * 1024 * 1024)
-			(void) mg_snprintf(size, sizeof(size),
+			(void) mg_snprintf(de-&gt;conn, size, sizeof(size),
 			    &quot;%.1fM&quot;, (double) de-&gt;st.size / 1048576);
 		else
-			(void) mg_snprintf(size, sizeof(size),
-			    &quot;%.1fG&quot;, (double) de-&gt;st.size / 1073741824);
+			(void) mg_snprintf(de-&gt;conn, size, sizeof(size),
+			  &quot;%.1fG&quot;, (double) de-&gt;st.size / 1073741824);
 	}
 	(void) strftime(mod, sizeof(mod), &quot;%d-%b-%Y %H:%M&quot;,
 		localtime(&amp;de-&gt;st.mtime));
@@ -2051,6 +2571,10 @@ print_dir_entry(struct de *de)
 	    de-&gt;st.is_directory ? &quot;/&quot; : &quot;&quot;, mod, size);
 }
 
+/*
+ * This function is called from send_directory() and used for
+ * sorting direcotory entries by size, or name, or modification time.
+ */
 static int
 compare_dir_entries(const void *p1, const void *p2)
 {
@@ -2078,14 +2602,17 @@ compare_dir_entries(const void *p1, const void *p2)
 	return (query_string[1] == 'd' ? -cmp_result : cmp_result);
 }
 
+/*
+ * Send directory contents.
+ */
 static void
 send_directory(struct mg_connection *conn, const char *dir)
 {
 	struct dirent	*dp;
 	DIR		*dirp;
 	struct de	*entries = NULL;
-	char		path[FILENAME_MAX], sort_direction;
-	int		i, num_entries = 0, arr_size = 128;
+	char		path[FILENAME_MAX];
+	int		i, sort_direction, num_entries = 0, arr_size = 128;
 
 	if ((dirp = opendir(dir)) == NULL) {
 		send_error(conn, 500, &quot;Cannot open directory&quot;,
@@ -2121,7 +2648,7 @@ send_directory(struct mg_connection *conn, const char *dir)
 			return;
 		}
 
-		(void) mg_snprintf(path, sizeof(path), &quot;%s%c%s&quot;,
+		(void) mg_snprintf(conn, path, sizeof(path), &quot;%s%c%s&quot;,
 		    dir, DIRSEP, dp-&gt;d_name);
 
 		(void) mg_stat(path, &amp;entries[num_entries].st);
@@ -2164,90 +2691,107 @@ send_directory(struct mg_connection *conn, const char *dir)
  * Send len bytes from the opened file to the client.
  */
 static void
-send_opened_file_stream(struct mg_connection *conn, int fd, uint64_t len)
+send_opened_file_stream(struct mg_connection *conn, FILE *fp, uint64_t len)
 {
 	char	buf[BUFSIZ];
-	int	n;
+	int	to_read, num_read, num_written;
 
 	while (len &gt; 0) {
-		n = sizeof(buf);
-		if ((uint64_t) n &gt; len)
-			n = (int) len;
-		if ((n = read(fd, buf, n)) &lt;= 0)
+		/* Calculate how much to read from the file in the buffer */
+		to_read = sizeof(buf);
+		if ((uint64_t) to_read &gt; len)
+			to_read = (int) len;
+
+		/* Read from file, exit the loop on error */
+		if ((num_read = fread(buf, 1, to_read, fp)) == 0)
 			break;
-		conn-&gt;num_bytes_sent += mg_write(conn, buf, n);
-		len -= n;
+
+		/* Send read bytes to the client, exit the loop on error */
+		if ((num_written = mg_write(conn, buf, num_read)) != num_read)
+			break;
+
+		/* Both read and were successful, adjust counters */
+		conn-&gt;num_bytes_sent += num_written;
+		len -= num_written;
 	}
 }
 
+/*
+ * Send regular file contents.
+ */
 static void
 send_file(struct mg_connection *conn, const char *path, struct mgstat *stp)
 {
 	char		date[64], lm[64], etag[64], range[64];
-	const char	*fmt = &quot;%a, %d %b %Y %H:%M:%S GMT&quot;, *msg = &quot;OK&quot;;
-	const char	*mime_type, *s;
+	const char	*fmt = &quot;%a, %d %b %Y %H:%M:%S %Z&quot;, *msg = &quot;OK&quot;, *hdr;
 	time_t		curtime = time(NULL);
-	unsigned long long cl, r1, r2;
-	int		fd, n;
+	uint64_t	cl, r1, r2;
+	struct vec	mime_vec;
+	FILE		*fp;
+	int		n;
 
-	mime_type = get_mime_type(path);
+	get_mime_type(conn-&gt;ctx, path, &amp;mime_vec);
 	cl = stp-&gt;size;
 	conn-&gt;request_info.status_code = 200;
 	range[0] = '\0';
 
-	if ((fd = mg_open(path, O_RDONLY | O_BINARY, 0644)) == -1) {
+	if ((fp = mg_fopen(path, &quot;rb&quot;)) == NULL) {
 		send_error(conn, 500, http_500_error,
 		    &quot;fopen(%s): %s&quot;, path, strerror(ERRNO));
 		return;
 	}
-	set_close_on_exec(fd);
+	set_close_on_exec(fileno(fp));
 
 	/* If Range: header specified, act accordingly */
-	s = mg_get_header(conn, &quot;Range&quot;);
 	r1 = r2 = 0;
-	if (s != NULL &amp;&amp; (n = sscanf(s,&quot;bytes=%llu-%llu&quot;, &amp;r1, &amp;r2)) &gt; 0) {
+	hdr = mg_get_header(conn, &quot;Range&quot;);
+	if (hdr != NULL &amp;&amp; (n = sscanf(hdr,
+	    &quot;bytes=%&quot; UINT64_FMT &quot;u-%&quot; UINT64_FMT &quot;u&quot;, &amp;r1, &amp;r2)) &gt; 0) {
 		conn-&gt;request_info.status_code = 206;
-		(void) lseek(fd, (long) r1, SEEK_SET);
+		(void) fseeko(fp, (off_t) r1, SEEK_SET);
 		cl = n == 2 ? r2 - r1 + 1: cl - r1;
-		(void) mg_snprintf(range, sizeof(range),
-		    &quot;Content-Range: bytes %llu-%llu/%llu\r\n&quot;,
-		    r1, r1 + cl - 1, cl);
+		(void) mg_snprintf(conn, range, sizeof(range),
+		    &quot;Content-Range: bytes &quot;
+		    &quot;%&quot; UINT64_FMT &quot;u-%&quot;
+		    UINT64_FMT &quot;u/%&quot; UINT64_FMT &quot;u\r\n&quot;,
+		    r1, r1 + cl - 1, stp-&gt;size);
 		msg = &quot;Partial Content&quot;;
 	}
 
 	/* Prepare Etag, Date, Last-Modified headers */
 	(void) strftime(date, sizeof(date), fmt, localtime(&amp;curtime));
 	(void) strftime(lm, sizeof(lm), fmt, localtime(&amp;stp-&gt;mtime));
-	(void) mg_snprintf(etag, sizeof(etag), &quot;%lx.%lx&quot;,
+	(void) mg_snprintf(conn, etag, sizeof(etag), &quot;%lx.%lx&quot;,
 	    (unsigned long) stp-&gt;mtime, (unsigned long) stp-&gt;size);
 
-	/* Since we send Content-Length, we can keep the connection alive */
-	conn-&gt;keep_alive = does_client_want_keep_alive(conn);
-
 	(void) mg_printf(conn,
 	    &quot;HTTP/1.1 %d %s\r\n&quot;
 	    &quot;Date: %s\r\n&quot;
 	    &quot;Last-Modified: %s\r\n&quot;
 	    &quot;Etag: \&quot;%s\&quot;\r\n&quot;
-	    &quot;Content-Type: %s\r\n&quot;
-	    &quot;Content-Length: %llu\r\n&quot;
-	    &quot;Connection: %s\r\n&quot;
+	    &quot;Content-Type: %.*s\r\n&quot;
+	    &quot;Content-Length: %&quot; UINT64_FMT &quot;u\r\n&quot;
+	    &quot;Connection: close\r\n&quot;
 	    &quot;Accept-Ranges: bytes\r\n&quot;
 	    &quot;%s\r\n&quot;,
-	    conn-&gt;request_info.status_code, msg, date, lm, etag, mime_type, cl,
-	    conn-&gt;keep_alive ? &quot;keep-alive&quot; : &quot;close&quot;, range);
+	    conn-&gt;request_info.status_code, msg, date, lm, etag,
+	    mime_vec.len, mime_vec.ptr, cl, range);
 
 	if (strcmp(conn-&gt;request_info.request_method, &quot;HEAD&quot;) != 0)
-		send_opened_file_stream(conn, fd, cl);
-	(void) close(fd);
+		send_opened_file_stream(conn, fp, cl);
+	(void) fclose(fp);
 }
 
+/*
+ * Parse HTTP headers from the given buffer, advance buffer to the point
+ * where parsing stopped.
+ */
 static void
 parse_http_headers(char **buf, struct mg_request_info *ri)
 {
 	int	i;
 
-	for (i = 0; i &lt; MAX_HTTP_HEADERS; i++) {
+	for (i = 0; i &lt; (int) ARRAY_SIZE(ri-&gt;http_headers); i++) {
 		ri-&gt;http_headers[i].name = skip(buf, &quot;: &quot;);
 		ri-&gt;http_headers[i].value = skip(buf, &quot;\r\n&quot;);
 		if (ri-&gt;http_headers[i].name[0] == '\0')
@@ -2266,6 +2810,9 @@ is_known_http_method(const char *method)
 	    !strcmp(method, &quot;DELETE&quot;));
 }
 
+/*
+ * Parse HTTP request, fill in mg_request_info structure.
+ */
 static bool_t
 parse_http_request(char *buf, struct mg_request_info *ri, const struct usa *usa)
 {
@@ -2291,14 +2838,21 @@ parse_http_request(char *buf, struct mg_request_info *ri, const struct usa *usa)
 	return (success_code);
 }
 
+/*
+ * Keep reading the input (either opened file descriptor fd, or socket sock,
+ * or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
+ * buffer (which marks the end of HTTP request). Buffer buf may already
+ * have some data. The length of the data is stored in nread.
+ * Upon every read operation, increase nread by the number of bytes read.
+ */
 static int
-read_request(int fd, SOCKET sock, SSL *ssl, char *buf, int bufsiz, int *nread)
+read_request(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int bufsiz, int *nread)
 {
 	int	n, request_len;
 
 	request_len = 0;
 	while (*nread &lt; bufsiz &amp;&amp; request_len == 0) {
-		n = pull(fd, sock, ssl, buf + *nread, bufsiz - *nread);
+		n = pull(fp, sock, ssl, buf + *nread, bufsiz - *nread);
 		if (n &lt;= 0) {
 			break;
 		} else {
@@ -2312,35 +2866,59 @@ read_request(int fd, SOCKET sock, SSL *ssl, char *buf, int bufsiz, int *nread)
 
 /*
  * For given directory path, substitute it to valid index file.
- * Return 0 if index file has been found, -1 if not found
+ * Return 0 if index file has been found, -1 if not found.
+ * If the file is found, it's stats is returned in stp.
  */
 static bool_t
 substitute_index_file(struct mg_connection *conn,
 		char *path, size_t path_len, struct mgstat *stp)
 {
-	const char	*s;
+	const char	*list;
 	struct mgstat	st;
-	size_t		len, n;
+	struct vec	filename_vec;
+	size_t		n;
 	bool_t		found;
 
 	n = strlen(path);
+
+	/*
+	 * The 'path' given to us points to the directory. Remove all trailing
+	 * directory separator characters from the end of the path, and
+	 * then append single directory separator character.
+	 */
+	while (n &gt; 0 &amp;&amp; IS_DIRSEP_CHAR(path[n - 1]))
+		n--;
 	path[n] = DIRSEP;
+
+	/*
+	 * Traverse index files list. For each entry, append it to the given
+	 * path and see if the file exists. If it exists, break the loop
+	 */
+	lock_option(conn-&gt;ctx, OPT_INDEX_FILES);
+	list = conn-&gt;ctx-&gt;options[OPT_INDEX_FILES];
 	found = FALSE;
 
-	mg_lock(conn-&gt;ctx);
-	s = conn-&gt;ctx-&gt;options[OPT_INDEX_FILES];
-	FOR_EACH_WORD_IN_LIST(s, len) {
-		if (len &gt; path_len - n - 1)
+	while ((list = next_option(list, &amp;filename_vec, NULL)) != NULL) {
+
+		/* Ignore too long entries that may overflow path buffer */
+		if (filename_vec.len &gt; path_len - n)
 			continue;
-		(void) mg_strlcpy(path + n + 1, s, len + 1);
+
+		/* Prepare full path to the index file  */
+		(void) mg_strlcpy(path + n + 1,
+		    filename_vec.ptr, filename_vec.len + 1);
+
+		/* Does it exist ? */
 		if (mg_stat(path, &amp;st) == 0) {
+			/* Yes it does, break the loop */
 			*stp = st;
 			found = TRUE;
 			break;
 		}
 	}
-	mg_unlock(conn-&gt;ctx);
+	unlock_option(conn-&gt;ctx, OPT_INDEX_FILES);
 
+	/* If no index file exists, restore directory path */
 	if (found == FALSE)
 		path[n] = '\0';
 
@@ -2348,13 +2926,38 @@ substitute_index_file(struct mg_connection *conn,
 }
 
 static void
-mg_bind(struct mg_context *ctx, const char *uri_regex, int status_code,
+remove_callback(struct mg_context *ctx,
+		const char *uri_regex, int status_code, bool_t is_auth)
+{
+	struct callback	*cb;
+	int		i;
+
+	for (i = 0; i &lt; ctx-&gt;num_callbacks; i++) {
+		cb = ctx-&gt;callbacks + i;
+		if ((uri_regex != NULL &amp;&amp; cb-&gt;uri_regex != NULL &amp;&amp;
+		    ((is_auth &amp;&amp; cb-&gt;is_auth) || (!is_auth &amp;&amp; !cb-&gt;is_auth)) &amp;&amp;
+		    !strcmp(uri_regex, cb-&gt;uri_regex)) || (uri_regex == NULL &amp;&amp;
+		     (cb-&gt;status_code == 0 ||
+		      cb-&gt;status_code == status_code))) {
+			(void) memmove(cb, cb + 1,
+			    (char *) (ctx-&gt;callbacks + ctx-&gt;num_callbacks) -
+			    (char *) (cb + 1));
+			break;
+		}
+	}
+}
+
+static void
+add_callback(struct mg_context *ctx, const char *uri_regex, int status_code,
 		mg_callback_t func, bool_t is_auth, void *user_data)
 {
 	struct callback	*cb;
 
-	if (ctx-&gt;num_callbacks &gt;= (int) ARRAY_SIZE(ctx-&gt;callbacks) - 1) {
-		cry(NULL, &quot;Too many callbacks! Increase MAX_CALLBACKS.&quot;);
+	pthread_mutex_lock(&amp;ctx-&gt;bind_mutex);
+	if (func == NULL) {
+		remove_callback(ctx, uri_regex, status_code, is_auth);
+	} else if (ctx-&gt;num_callbacks &gt;= (int) ARRAY_SIZE(ctx-&gt;callbacks) - 1) {
+		cry(fc(ctx), &quot;Too many callbacks! Increase MAX_CALLBACKS.&quot;);
 	} else {
 		cb = &amp;ctx-&gt;callbacks[ctx-&gt;num_callbacks];
 		cb-&gt;uri_regex = uri_regex ? mg_strdup(uri_regex) : NULL;
@@ -2363,55 +2966,58 @@ mg_bind(struct mg_context *ctx, const char *uri_regex, int status_code,
 		cb-&gt;status_code = status_code;
 		cb-&gt;user_data = user_data;
 		ctx-&gt;num_callbacks++;
+		DEBUG_TRACE((DEBUG_MGS_PREFIX &quot;%s: uri %s code %d&quot;,
+		    __func__, uri_regex ? uri_regex : &quot;NULL&quot;, status_code));
 	}
+	pthread_mutex_unlock(&amp;ctx-&gt;bind_mutex);
 }
 
 void
-mg_bind_to_uri(struct mg_context *ctx, const char *uri_regex,
+mg_set_uri_callback(struct mg_context *ctx, const char *uri_regex,
 		mg_callback_t func, void *user_data)
 {
-	assert(func != NULL);
 	assert(uri_regex != NULL);
-	mg_bind(ctx, uri_regex, -1, func, FALSE, user_data);
+	add_callback(ctx, uri_regex, -1, func, FALSE, user_data);
 }
 
 void
-mg_bind_to_error_code(struct mg_context *ctx, int error_code,
+mg_set_error_callback(struct mg_context *ctx, int error_code,
 		mg_callback_t func, void *user_data)
 {
 	assert(error_code &gt;= 0 &amp;&amp; error_code &lt; 1000);
-	assert(func != NULL);
-	mg_bind(ctx, NULL, error_code, func, FALSE, user_data);
+	add_callback(ctx, NULL, error_code, func, FALSE, user_data);
 }
 
 void
-mg_protect_uri(struct mg_context *ctx, const char *uri_regex,
+mg_set_auth_callback(struct mg_context *ctx, const char *uri_regex,
 		mg_callback_t func, void *user_data)
 {
-	assert(func != NULL);
 	assert(uri_regex != NULL);
-	mg_bind(ctx, uri_regex, -1, func, TRUE, user_data);
+	add_callback(ctx, uri_regex, -1, func, TRUE, user_data);
 }
 
-static int
-not_modified(const struct mg_connection *conn, const struct mgstat *stp)
+/*
+ * Return True if we should reply 304 Not Modified.
+ */
+static bool_t
+is_not_modified(const struct mg_connection *conn, const struct mgstat *stp)
 {
 	const char *ims = mg_get_header(conn, &quot;If-Modified-Since&quot;);
 	return (ims != NULL &amp;&amp; stp-&gt;mtime &lt; date_to_epoch(ims));
 }
 
 static bool_t
-append_chunk(struct mg_request_info *ri, int fd, const char *buf, int len)
+append_chunk(struct mg_request_info *ri, FILE *fp, const char *buf, int len)
 {
 	bool_t	ret_code = TRUE;
 
-	if (fd == -1) {
+	if (fp == NULL) {
 		/* TODO: check for NULL here */
 		ri-&gt;post_data = (char *) realloc(ri-&gt;post_data,
 		    ri-&gt;post_data_len + len);
 		(void) memcpy(ri-&gt;post_data + ri-&gt;post_data_len, buf, len);
 		ri-&gt;post_data_len += len;
-	} else if (push(fd, INVALID_SOCKET,
+	} else if (push(fp, INVALID_SOCKET,
 	    NULL, buf, (uint64_t) len) != (uint64_t) len) {
 		ret_code = FALSE;
 	}
@@ -2420,7 +3026,7 @@ append_chunk(struct mg_request_info *ri, int fd, const char *buf, int len)
 }
 
 static bool_t
-handle_request_body(struct mg_connection *conn, int fd)
+handle_request_body(struct mg_connection *conn, FILE *fp)
 {
 	struct mg_request_info	*ri = &amp;conn-&gt;request_info;
 	const char	*expect, *tmp;
@@ -2446,24 +3052,24 @@ handle_request_body(struct mg_connection *conn, int fd)
 		if (content_len &lt;= (uint64_t) already_read) {
 			ri-&gt;post_data_len = (int) content_len;
 			/*
-			 * If fd == -1, this is embedded mode, and we do not
+			 * If fp is NULL, this is embedded mode, and we do not
 			 * have to do anything: POST data is already there,
 			 * no need to allocate a buffer and copy it in.
-			 * If fd != -1, we need to write the data.
+			 * If fp != NULL, we need to write the data.
 			 */
-			success_code = (fd == -1) || (push(fd, INVALID_SOCKET,
+			success_code = fp == NULL || (push(fp, INVALID_SOCKET,
 			    NULL, ri-&gt;post_data, content_len) == content_len) ?
 			    TRUE : FALSE;
 		} else {
 
-			if (fd == -1) {
+			if (fp == NULL) {
 				conn-&gt;free_post_data = TRUE;
 				tmp = ri-&gt;post_data;
 				/* +1 in case if already_read == 0 */
 				ri-&gt;post_data = (char*)malloc(already_read + 1);
 				(void) memcpy(ri-&gt;post_data, tmp, already_read);
 			} else {
-				(void) push(fd, INVALID_SOCKET, NULL,
+				(void) push(fp, INVALID_SOCKET, NULL,
 				    ri-&gt;post_data, (uint64_t) already_read);
 			}
 
@@ -2473,11 +3079,11 @@ handle_request_body(struct mg_connection *conn, int fd)
 				to_read = sizeof(buf);
 				if ((uint64_t) to_read &gt; content_len)
 					to_read = (int) content_len;
-				nread = pull(-1, conn-&gt;client.sock,
+				nread = pull(NULL, conn-&gt;client.sock,
 				    conn-&gt;ssl, buf, to_read);
 				if (nread &lt;= 0)
 					break;
-				if (!append_chunk(ri, fd, buf, nread))
+				if (!append_chunk(ri, fp, buf, nread))
 					break;
 				content_len -= nread;
 			}
@@ -2487,20 +3093,36 @@ handle_request_body(struct mg_connection *conn, int fd)
 		/* Each error code path in this function must send an error */
 		if (success_code != TRUE)
 			send_error(conn, 577, http_500_error,
-			   &quot;%s&quot;, &quot;Error handling body data&quot;);
+			    &quot;%s&quot;, &quot;Error handling body data&quot;);
 	}
 
 	return (success_code);
 }
 
 #if !defined(NO_CGI)
+
+/*
+ * This structure helps to create an environment for the spawned CGI program.
+ * Environment is an array of &quot;VARIABLE=VALUE\0&quot; ASCIIZ strings,
+ * last element must be NULL.
+ * However, on Windows there is a requirement that all these VARIABLE=VALUE\0
+ * strings must reside in a contiguous buffer. The end of the buffer is
+ * marked by two '\0' characters.
+ * We satisfy both worlds: we create an envp array (which is vars), all
+ * entries are actually pointers inside buf.
+ */
 struct cgi_env_block {
+	struct mg_connection *conn;
 	char	buf[CGI_ENVIRONMENT_SIZE];	/* Environment buffer	*/
 	int	len;				/* Space taken		*/
 	char	*vars[MAX_CGI_ENVIR_VARS];	/* char **envp		*/
 	int	nvars;				/* Number of variables	*/
 };
 
+/*
+ * Append VARIABLE=VALUE\0 string to the buffer, and add a respective
+ * pointer into the vars array.
+ */
 static char *
 addenv(struct cgi_env_block *block, const char *fmt, ...)
 {
@@ -2508,18 +3130,25 @@ addenv(struct cgi_env_block *block, const char *fmt, ...)
 	char	*added;
 	va_list	ap;
 
+	/* Calculate how much space is left in the buffer */
 	space = sizeof(block-&gt;buf) - block-&gt;len - 2;
 	assert(space &gt;= 0);
+
+	/* Make a pointer to the free space int the buffer */
 	added = block-&gt;buf + block-&gt;len;
 
+	/* Copy VARIABLE=VALUE\0 string into the free space */
 	va_start(ap, fmt);
-	n = mg_vsnprintf(added, (size_t) space, fmt, ap);
+	n = mg_vsnprintf(block-&gt;conn, added, (size_t) space, fmt, ap);
 	va_end(ap);
 
+	/* Make sure we do not overflow buffer and the envp array */
 	if (n &gt; 0 &amp;&amp; n &lt; space &amp;&amp;
 	    block-&gt;nvars &lt; (int) ARRAY_SIZE(block-&gt;vars) - 2) {
+		/* Append a pointer to the added string into the envp array */
 		block-&gt;vars[block-&gt;nvars++] = block-&gt;buf + block-&gt;len;
-		block-&gt;len += n + 1;	/* Include \0 terminator */
+		/* Bump up used length counter. Include \0 terminator */
+		block-&gt;len += n + 1;
 	}
 
 	return (added);
@@ -2530,32 +3159,33 @@ prepare_cgi_environment(struct mg_connection *conn, const char *prog,
 		struct cgi_env_block *blk)
 {
 	const char	*s, *script_filename, *root;
+	struct vec	var_vec;
 	char		*p;
 	int		i;
-	size_t		len;
 
 	blk-&gt;len = blk-&gt;nvars = 0;
+	blk-&gt;conn = conn;
 
 	/* SCRIPT_FILENAME */
 	script_filename = prog;
 	if ((s = strrchr(prog, '/')) != NULL)
 		script_filename = s + 1;
 
-	mg_lock(conn-&gt;ctx);
+	lock_option(conn-&gt;ctx, OPT_ROOT);
 	root = conn-&gt;ctx-&gt;options[OPT_ROOT];
 	addenv(blk, &quot;SERVER_NAME=%s&quot;, conn-&gt;ctx-&gt;options[OPT_AUTH_DOMAIN]);
-	mg_unlock(conn-&gt;ctx);
+	unlock_option(conn-&gt;ctx, OPT_ROOT);
 
 	/* Prepare the environment block */
 	addenv(blk, &quot;%s&quot;, &quot;GATEWAY_INTERFACE=CGI/1.1&quot;);
 	addenv(blk, &quot;%s&quot;, &quot;SERVER_PROTOCOL=HTTP/1.1&quot;);
 	addenv(blk, &quot;%s&quot;, &quot;REDIRECT_STATUS=200&quot;);	/* PHP */
-	addenv(blk, &quot;SERVER_PORT=%d&quot;, ntohs(conn-&gt;lsa.u.sin.sin_port));
+	addenv(blk, &quot;SERVER_PORT=%d&quot;, ntohs(conn-&gt;client.lsa.u.sin.sin_port));
 	addenv(blk, &quot;SERVER_ROOT=%s&quot;, root);
 	addenv(blk, &quot;DOCUMENT_ROOT=%s&quot;, root);
 	addenv(blk, &quot;REQUEST_METHOD=%s&quot;, conn-&gt;request_info.request_method);
 	addenv(blk, &quot;REMOTE_ADDR=%s&quot;,
-	    inet_ntoa(conn-&gt;client.usa.u.sin.sin_addr));
+	    inet_ntoa(conn-&gt;client.rsa.u.sin.sin_addr));
 	addenv(blk, &quot;REMOTE_PORT=%d&quot;, conn-&gt;request_info.remote_port);
 	addenv(blk, &quot;REQUEST_URI=%s&quot;, conn-&gt;request_info.uri);
 	addenv(blk, &quot;SCRIPT_NAME=%s&quot;, prog + strlen(root));
@@ -2603,19 +3233,16 @@ prepare_cgi_environment(struct mg_connection *conn, const char *prog,
 		for (; *p != '=' &amp;&amp; *p != '\0'; p++) {
 			if (*p == '-')
 				*p = '_';
-			*p = toupper(* (unsigned char *) p) &amp; 0xff;
+			*p = (char) toupper(* (unsigned char *) p);
 		}
 	}
 
 	/* Add user-specified variables */
-	mg_lock(conn-&gt;ctx);
-	if (conn-&gt;ctx-&gt;options[OPT_CGI_ENV] != NULL) {
-		s = conn-&gt;ctx-&gt;options[OPT_CGI_ENV];
-		FOR_EACH_WORD_IN_LIST(s, len) {
-			addenv(blk, &quot;%.*s&quot;, len, s);
-		}
-	}
-	mg_unlock(conn-&gt;ctx);
+	lock_option(conn-&gt;ctx, OPT_CGI_ENV);
+	s = conn-&gt;ctx-&gt;options[OPT_CGI_ENV];
+	while ((s = next_option(s, &amp;var_vec, NULL)) != NULL)
+		addenv(blk, &quot;%.*s&quot;, var_vec.len, var_vec.ptr);
+	unlock_option(conn-&gt;ctx, OPT_CGI_ENV);
 
 	blk-&gt;vars[blk-&gt;nvars++] = NULL;
 	blk-&gt;buf[blk-&gt;len++] = '\0';
@@ -2635,28 +3262,37 @@ send_cgi(struct mg_connection *conn, const char *prog)
 	struct cgi_env_block	blk;
 	char			dir[FILENAME_MAX], *p;
 	int			fd_stdin[2], fd_stdout[2];
+	FILE			*in, *out;
 	pid_t			pid;
 
 	prepare_cgi_environment(conn, prog, &amp;blk);
 
 	/* CGI must be executed in its own directory */
-	(void) mg_snprintf(dir, sizeof(dir), &quot;%s&quot;, prog);
+	(void) mg_snprintf(conn, dir, sizeof(dir), &quot;%s&quot;, prog);
 	if ((p = strrchr(dir, DIRSEP)) != NULL)
 		*p++ = '\0';
 
 	pid = (pid_t) -1;
 	fd_stdin[0] = fd_stdin[1] = fd_stdout[0] = fd_stdout[1] = -1;
+	in = out = NULL;
+
 	if (pipe(fd_stdin) != 0 || pipe(fd_stdout) != 0) {
 		send_error(conn, 500, http_500_error,
 		    &quot;Cannot create CGI pipe: %s&quot;, strerror(ERRNO));
 		goto done;
-	}
-
-	if ((pid = spawn_process(conn, p, blk.buf, blk.vars,
+	} else if ((pid = spawn_process(conn, p, blk.buf, blk.vars,
 	    fd_stdin[0], fd_stdout[1], dir)) == (pid_t) -1) {
 		goto done;
+	} else if ((in = fdopen(fd_stdin[1], &quot;wb&quot;)) == NULL ||
+	    (out = fdopen(fd_stdout[0], &quot;rb&quot;)) == NULL) {
+		send_error(conn, 500, http_500_error,
+		    &quot;fopen: %s&quot;, strerror(ERRNO));
+		goto done;
 	}
 
+	setbuf(in, NULL);
+	setbuf(out, NULL);
+
 	/*
 	 * spawn_process() must close those!
 	 * If we don't mark them as closed, close() attempt before
@@ -2667,7 +3303,7 @@ send_cgi(struct mg_connection *conn, const char *prog)
 
 	/* Send POST data to the CGI process if needed */
 	if (!strcmp(conn-&gt;request_info.request_method, &quot;POST&quot;) &amp;&amp;
-	    !handle_request_body(conn, fd_stdin[1])) {
+	    !handle_request_body(conn, in)) {
 		goto done;
 	}
 
@@ -2678,7 +3314,7 @@ send_cgi(struct mg_connection *conn, const char *prog)
 	 * HTTP headers.
 	 */
 	data_len = 0;
-	headers_len = read_request(fd_stdout[0], INVALID_SOCKET, NULL,
+	headers_len = read_request(out, INVALID_SOCKET, NULL,
 	    buf, sizeof(buf), &amp;data_len);
 	if (headers_len &lt;= 0) {
 		send_error(conn, 500, http_500_error,
@@ -2716,7 +3352,7 @@ send_cgi(struct mg_connection *conn, const char *prog)
 	 * In all such cases, stop data exchange and do cleanup.
 	 */
 	do {
-		n = pull(fd_stdout[0], INVALID_SOCKET, NULL, buf, sizeof(buf));
+		n = pull(out, INVALID_SOCKET, NULL, buf, sizeof(buf));
 		if (n &gt; 0)
 			n = mg_write(conn, buf, n);
 		if (n &gt; 0)
@@ -2724,20 +3360,25 @@ send_cgi(struct mg_connection *conn, const char *prog)
 	} while (n &gt; 0);
 
 done:
-	if (pid &gt; 0)
+	if (pid != (pid_t) -1)
 		kill(pid, SIGTERM);
 	if (fd_stdin[0] != -1)
 		(void) close(fd_stdin[0]);
-	if (fd_stdin[1] != -1)
-		(void) close(fd_stdin[1]);
-	if (fd_stdout[0] != -1)
-		(void) close(fd_stdout[0]);
 	if (fd_stdout[1] != -1)
 		(void) close(fd_stdout[1]);
+
+	if (in != NULL)
+		(void) fclose(in);
+	else if (fd_stdin[1] != -1)
+		(void) close(fd_stdin[1]);
+
+	if (out != NULL)
+		(void) fclose(out);
+	else if (fd_stdout[0] != -1)
+		(void) close(fd_stdout[0]);
 }
 #endif /* !NO_CGI */
 
-#if !defined(NO_AUTH)
 /*
  * For a given PUT path, create all intermediate subdirectories
  * for given path. Return 0 if the path itself is a directory,
@@ -2773,7 +3414,8 @@ static void
 put_file(struct mg_connection *conn, const char *path)
 {
 	struct mgstat	st;
-	int		rc, fd;
+	FILE		*fp;
+	int		rc;
 
 	conn-&gt;request_info.status_code = mg_stat(path, &amp;st) == 0 ? 200 : 201;
 
@@ -2781,27 +3423,29 @@ put_file(struct mg_connection *conn, const char *path)
 		send_error(conn, 501, &quot;Not Implemented&quot;,
 		    &quot;%s&quot;, &quot;Range support for PUT requests is not implemented&quot;);
 	} else if ((rc = put_dir(path)) == 0) {
-		send_error(conn, 200, &quot;OK&quot;, &quot;&quot;);
+		(void) mg_printf(conn, &quot;HTTP/1.1 %d OK\r\n\r\n&quot;,
+		    conn-&gt;request_info.status_code);
 	} else if (rc == -1) {
 		send_error(conn, 500, http_500_error,
 		    &quot;put_dir(%s): %s&quot;, path, strerror(ERRNO));
-	} else if ((fd = mg_open(path,
-	    O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0644)) == -1) {
+	} else if ((fp = mg_fopen(path, &quot;wb+&quot;)) == NULL) {
 		send_error(conn, 500, http_500_error,
-		    &quot;open(%s): %s&quot;, path, strerror(ERRNO));
+		    &quot;fopen(%s): %s&quot;, path, strerror(ERRNO));
 	} else {
-		set_close_on_exec(fd);
-		if (handle_request_body(conn, fd))
-			send_error(conn, conn-&gt;request_info.status_code,
-			    &quot;OK&quot;, &quot;&quot;);
-		(void) close(fd);
+		set_close_on_exec(fileno(fp));
+		if (handle_request_body(conn, fp))
+			(void) mg_printf(conn, &quot;HTTP/1.1 %d OK\r\n\r\n&quot;,
+			    conn-&gt;request_info.status_code);
+		(void) fclose(fp);
 	}
 }
-#endif /* NO_AUTH */
 
 #if !defined(NO_SSI)
+static void send_ssi_file(struct mg_connection *, const char *, FILE *, int);
+
 static void
-do_ssi_include(struct mg_connection *conn, const char *ssi, char *tag)
+do_ssi_include(struct mg_connection *conn, const char *ssi, char *tag,
+		int include_level)
 {
 	char	file_name[BUFSIZ], path[FILENAME_MAX], *p;
 	FILE	*fp;
@@ -2812,34 +3456,40 @@ do_ssi_include(struct mg_connection *conn, const char *ssi, char *tag)
 	 */
 	if (sscanf(tag, &quot; virtual=\&quot;%[^\&quot;]\&quot;&quot;, file_name) == 1) {
 		/* File name is relative to the webserver root */
-		mg_lock(conn-&gt;ctx);
-		(void) mg_snprintf(path, sizeof(path), &quot;%s%c%s&quot;,
+		lock_option(conn-&gt;ctx, OPT_ROOT);
+		(void) mg_snprintf(conn, path, sizeof(path), &quot;%s%c%s&quot;,
 		    conn-&gt;ctx-&gt;options[OPT_ROOT], DIRSEP, file_name);
-		mg_unlock(conn-&gt;ctx);
+		unlock_option(conn-&gt;ctx, OPT_ROOT);
 	} else if (sscanf(tag, &quot; file=\&quot;%[^\&quot;]\&quot;&quot;, file_name) == 1) {
 		/*
 		 * File name is relative to the webserver working directory
 		 * or it is absolute system path
 		 */
-		(void) mg_snprintf(path, sizeof(path), &quot;%s&quot;, file_name);
+		(void) mg_snprintf(conn, path, sizeof(path), &quot;%s&quot;, file_name);
 	} else if (sscanf(tag, &quot; \&quot;%[^\&quot;]\&quot;&quot;, file_name) == 1) {
 		/* File name is relative to the currect document */
-		(void) mg_snprintf(path, sizeof(path), &quot;%s&quot;, ssi);
+		(void) mg_snprintf(conn, path, sizeof(path), &quot;%s&quot;, ssi);
 		if ((p = strrchr(path, DIRSEP)) != NULL)
 			p[1] = '\0';
-		(void) mg_snprintf(path + strlen(path),
+		(void) mg_snprintf(conn, path + strlen(path),
 		    sizeof(path) - strlen(path), &quot;%s&quot;, file_name);
 	} else {
 		cry(conn, &quot;Bad SSI #include: [%s]&quot;, tag);
 		return;
 	}
 
-	if ((fp = fopen(path, &quot;rb&quot;)) == NULL) {
+	if ((fp = mg_fopen(path, &quot;rb&quot;)) == NULL) {
 		cry(conn, &quot;Cannot open SSI #include: [%s]: fopen(%s): %s&quot;,
 		    tag, path, strerror(ERRNO));
 	} else {
 		set_close_on_exec(fileno(fp));
-		send_opened_file_stream(conn, fileno(fp), ~0ULL);
+		if (match_extension(path,
+		    conn-&gt;ctx-&gt;options[OPT_SSI_EXTENSIONS])) {
+			send_ssi_file(conn, path, fp, include_level + 1);
+		} else {
+			send_opened_file_stream(conn, fp,
+			    UNKNOWN_CONTENT_LENGTH);
+		}
 		(void) fclose(fp);
 	}
 }
@@ -2855,17 +3505,23 @@ do_ssi_exec(struct mg_connection *conn, char *tag)
 	} else if ((fp = popen(cmd, &quot;r&quot;)) == NULL) {
 		cry(conn, &quot;Cannot SSI #exec: [%s]: %s&quot;, cmd, strerror(ERRNO));
 	} else {
-		send_opened_file_stream(conn, fileno(fp), ~0ULL);
+		send_opened_file_stream(conn, fp, UNKNOWN_CONTENT_LENGTH);
 		(void) pclose(fp);
 	}
 }
 
 static void
-send_ssi_file(struct mg_connection *conn, const char *path, FILE *fp)
+send_ssi_file(struct mg_connection *conn, const char *path, FILE *fp,
+		int include_level)
 {
 	char	buf[BUFSIZ];
 	int	ch, len, in_ssi_tag;
 
+	if (include_level &gt; 10) {
+		cry(conn, &quot;SSI #include level is too deep (%s)&quot;, path);
+		return;
+	}
+
 	in_ssi_tag = FALSE;
 	len = 0;
 
@@ -2880,7 +3536,8 @@ send_ssi_file(struct mg_connection *conn, const char *path, FILE *fp)
 				(void) mg_write(conn, buf, len);
 			} else {
 				if (!memcmp(buf + 5, &quot;include&quot;, 7)) {
-					do_ssi_include(conn, path, buf + 12);
+					do_ssi_include(conn, path, buf + 12,
+					    include_level);
 				} else if (!memcmp(buf + 5, &quot;exec&quot;, 4)) {
 					do_ssi_exec(conn, buf + 9);
 				} else {
@@ -2924,19 +3581,48 @@ send_ssi(struct mg_connection *conn, const char *path)
 {
 	FILE	*fp;
 
-	if ((fp = fopen(path, &quot;rb&quot;)) == NULL) {
+	if ((fp = mg_fopen(path, &quot;rb&quot;)) == NULL) {
 		send_error(conn, 500, http_500_error,
 		    &quot;fopen(%s): %s&quot;, path, strerror(ERRNO));
 	} else {
 		set_close_on_exec(fileno(fp));
 		(void) mg_printf(conn, &quot;%s&quot;, &quot;HTTP/1.1 200 OK\r\n&quot;
 		    &quot;Content-Type: text/html\r\nConnection: close\r\n\r\n&quot;);
-		send_ssi_file(conn, path, fp);
+		send_ssi_file(conn, path, fp, 0);
 		(void) fclose(fp);
 	}
 }
 #endif /* !NO_SSI */
 
+void
+mg_authorize(struct mg_connection *conn)
+{
+	conn-&gt;embedded_auth = TRUE;
+}
+
+static bool_t
+check_embedded_authorization(struct mg_connection *conn)
+{
+	const struct callback	*cb;
+	bool_t			authorized;
+
+	authorized = TRUE;
+	cb = find_callback(conn-&gt;ctx, TRUE, conn-&gt;request_info.uri, -1);
+
+	if (cb != NULL) {
+		cb-&gt;func(conn, &amp;conn-&gt;request_info, cb-&gt;user_data);
+		authorized = conn-&gt;embedded_auth;
+	}
+
+	return (authorized);
+}
+
+/*
+ * This is the heart of the Mongoose's logic.
+ * This function is called when the request is read, parsed and validated,
+ * and Mongoose must decide what action to take: serve a file, or
+ * a directory, or call embedded function, etcetera.
+ */
 static void
 analyze_request(struct mg_connection *conn)
 {
@@ -2949,22 +3635,24 @@ analyze_request(struct mg_connection *conn)
 		* conn-&gt;request_info.query_string++ = '\0';
 
 	(void) url_decode(uri, (int) strlen(uri), uri, strlen(uri) + 1, FALSE);
-	remove_double_dots(uri);
-	make_path(conn-&gt;ctx, uri, path, sizeof(path));
+	remove_double_dots_and_double_slashes(uri);
+	convert_uri_to_file_name(conn, uri, path, sizeof(path));
 
-#if !defined(NO_AUTH)
 	if (!check_authorization(conn, path)) {
 		send_authorization_request(conn);
-	} else
-#endif /* !NO_AUTH */
-	if ((cb = find_callback(conn-&gt;ctx, FALSE, uri, -1)) != NULL) {
-		if (strcmp(ri-&gt;request_method, &quot;POST&quot;) ||
-		    (!strcmp(ri-&gt;request_method, &quot;POST&quot;) &amp;&amp;
-		    handle_request_body(conn, -1)))
+	} else if (check_embedded_authorization(conn) == FALSE) {
+		/*
+		 * Embedded code failed authorization. Do nothing here, since
+		 * an embedded code must handle this itself by either
+		 * showing proper error message, or redirecting to some
+		 * sort of login page, or something else.
+		 */
+	} else if ((cb = find_callback(conn-&gt;ctx, FALSE, uri, -1)) != NULL) {
+		if ((strcmp(ri-&gt;request_method, &quot;POST&quot;) != 0 &amp;&amp;
+		    strcmp(ri-&gt;request_method, &quot;PUT&quot;) != 0) ||
+		    handle_request_body(conn, NULL))
 			cb-&gt;func(conn, &amp;conn-&gt;request_info, cb-&gt;user_data);
-	} else
-#if !defined(NO_AUTH)
-	if (strstr(path, PASSWORDS_FILE_NAME)) {
+	} else if (strstr(path, PASSWORDS_FILE_NAME)) {
 		/* Do not allow to view passwords files */
 		send_error(conn, 403, &quot;Forbidden&quot;, &quot;Access Forbidden&quot;);
 	} else if ((!strcmp(ri-&gt;request_method, &quot;PUT&quot;) ||
@@ -2980,9 +3668,7 @@ analyze_request(struct mg_connection *conn)
 		else
 			send_error(conn, 500, http_500_error,
 			    &quot;remove(%s): %s&quot;, path, strerror(ERRNO));
-	} else
-#endif /* NO_AUTH */
-	if (mg_stat(path, &amp;st) != 0) {
+	} else if (mg_stat(path, &amp;st) != 0) {
 		send_error(conn, 404, &quot;Not Found&quot;, &quot;%s&quot;, &quot;File not found&quot;);
 	} else if (st.is_directory &amp;&amp; uri[strlen(uri) - 1] != '/') {
 		(void) mg_printf(conn,
@@ -3012,7 +3698,7 @@ analyze_request(struct mg_connection *conn)
 	    conn-&gt;ctx-&gt;options[OPT_SSI_EXTENSIONS])) {
 		send_ssi(conn, path);
 #endif /* NO_SSI */
-	} else if (not_modified(conn, &amp;st)) {
+	} else if (is_not_modified(conn, &amp;st)) {
 		send_error(conn, 304, &quot;Not Modified&quot;, &quot;&quot;);
 	} else {
 		send_file(conn, path, &amp;st);
@@ -3030,34 +3716,37 @@ close_all_listening_sockets(struct mg_context *ctx)
 }
 
 static bool_t
-set_ports_option(struct mg_context *ctx, const char *p)
+set_ports_option(struct mg_context *ctx, const char *list)
 {
-	SOCKET	sock;
-	size_t	len;
-	int	is_ssl, port;
+	SOCKET		sock;
+	int		is_ssl;
+	struct vec	vec;
+	struct socket	*listener;
 
 	close_all_listening_sockets(ctx);
+	assert(ctx-&gt;num_listeners == 0);
 
-	FOR_EACH_WORD_IN_LIST(p, len) {
+	while ((list = next_option(list, &amp;vec, NULL)) != NULL) {
 
-		is_ssl	= p[len - 1] == 's' ? TRUE : FALSE;
-		port	= atoi(p);
+		is_ssl	= vec.ptr[vec.len - 1] == 's' ? TRUE : FALSE;
+		listener = ctx-&gt;listeners + ctx-&gt;num_listeners;
 
 		if (ctx-&gt;num_listeners &gt;=
 		    (int) (ARRAY_SIZE(ctx-&gt;listeners) - 1)) {
-			cry(NULL, &quot;%s&quot;, &quot;Too many listeninig sockets&quot;);
+			cry(fc(ctx), &quot;%s&quot;, &quot;Too many listeninig sockets&quot;);
 			return (FALSE);
-		} else if ((sock = mg_open_listening_port(port)) == -1) {
-			cry(NULL, &quot;cannot open port %d&quot;, port);
+		} else if ((sock = mg_open_listening_port(ctx,
+		    vec.ptr, &amp;listener-&gt;lsa)) == INVALID_SOCKET) {
+			cry(fc(ctx), &quot;cannot bind to %.*s&quot;, vec.len, vec.ptr);
 			return (FALSE);
 		} else if (is_ssl == TRUE &amp;&amp; ctx-&gt;ssl_ctx == NULL) {
 			(void) closesocket(sock);
-			cry(NULL, &quot;cannot add SSL socket, &quot;
-			    &quot;please specify certificate file&quot;);
+			cry(fc(ctx), &quot;cannot add SSL socket, please specify &quot;
+			    &quot;-ssl_cert option BEFORE -ports option&quot;);
 			return (FALSE);
 		} else {
-			ctx-&gt;listeners[ctx-&gt;num_listeners].sock = sock;
-			ctx-&gt;listeners[ctx-&gt;num_listeners].is_ssl = is_ssl;
+			listener-&gt;sock = sock;
+			listener-&gt;is_ssl = is_ssl;
 			ctx-&gt;num_listeners++;
 		}
 	}
@@ -3086,23 +3775,22 @@ log_access(const struct mg_connection *conn)
 	if (conn-&gt;ctx-&gt;access_log == NULL)
 		return;
 
-	(void) strftime(date, sizeof(date), &quot;%d/%b/%Y:%H:%M:%S&quot;,
-			localtime(&amp;conn-&gt;birth_time));
+	(void) strftime(date, sizeof(date), &quot;%d/%b/%Y:%H:%M:%S %z&quot;,
+	    localtime(&amp;conn-&gt;birth_time));
 
 	ri = &amp;conn-&gt;request_info;
 
 	flockfile(conn-&gt;ctx-&gt;access_log);
 
 	(void) fprintf(conn-&gt;ctx-&gt;access_log,
-	    &quot;%s - %s [%s %+05d] \&quot;%s %s HTTP/%d.%d\&quot; %d %llu&quot;,
-	    inet_ntoa(conn-&gt;client.usa.u.sin.sin_addr),
+	    &quot;%s - %s [%s] \&quot;%s %s HTTP/%d.%d\&quot; %d %&quot; UINT64_FMT &quot;u&quot;,
+	    inet_ntoa(conn-&gt;client.rsa.u.sin.sin_addr),
 	    ri-&gt;remote_user == NULL ? &quot;-&quot; : ri-&gt;remote_user,
-	    date, tz_offset,
+	    date,
 	    ri-&gt;request_method ? ri-&gt;request_method : &quot;-&quot;,
 	    ri-&gt;uri ? ri-&gt;uri : &quot;-&quot;,
 	    ri-&gt;http_version_major, ri-&gt;http_version_minor,
-	    conn-&gt;request_info.status_code,
-	    (unsigned long long) conn-&gt;num_bytes_sent);
+	    conn-&gt;request_info.status_code, conn-&gt;num_bytes_sent);
 	log_header(conn, &quot;Referer&quot;, conn-&gt;ctx-&gt;access_log);
 	log_header(conn, &quot;User-Agent&quot;, conn-&gt;ctx-&gt;access_log);
 	(void) fputc('\n', conn-&gt;ctx-&gt;access_log);
@@ -3121,37 +3809,40 @@ isbyte(int n) {
  * Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.
  */
 static int
-check_acl(const char *acl, const struct usa *usa)
+check_acl(struct mg_context *ctx, const char *list, const struct usa *usa)
 {
 	int		a, b, c, d, n, mask, allowed;
 	char		flag;
-	size_t		len;
 	uint32_t	acl_subnet, acl_mask, remote_ip;
+	struct vec	vec;
 
 	(void) memcpy(&amp;remote_ip, &amp;usa-&gt;u.sin.sin_addr, sizeof(remote_ip));
 
 	/* If any ACL is set, deny by default */
 	allowed = '-';
-	FOR_EACH_WORD_IN_LIST(acl, len) {
+
+	while ((list = next_option(list, &amp;vec, NULL)) != NULL) {
 
 		mask = 32;
 
-		if (sscanf(acl, &quot;%c%d.%d.%d.%d%n&quot;,&amp;flag,&amp;a,&amp;b,&amp;c,&amp;d,&amp;n) != 5) {
-			cry(NULL, &quot;%s: subnet must be [+|-]x.x.x.x[/x]&quot;,
-			    __func__);
+		if (sscanf(vec.ptr, &quot;%c%d.%d.%d.%d%n&quot;,
+		    &amp;flag, &amp;a, &amp;b, &amp;c, &amp;d, &amp;n) != 5) {
+			cry(fc(ctx),
+			    &quot;%s: subnet must be [+|-]x.x.x.x[/x]&quot;, __func__);
 			return (-1);
 		} else if (flag != '+' &amp;&amp; flag != '-') {
-			cry(NULL, &quot;%s: flag must be + or -: [%s]&quot;,
-			    __func__, acl);
+			cry(fc(ctx), &quot;%s: flag must be + or -: [%s]&quot;,
+			    __func__, vec.ptr);
 			return (-1);
 		} else if (!isbyte(a)||!isbyte(b)||!isbyte(c)||!isbyte(d)) {
-			cry(NULL, &quot;%s: bad ip address: [%s]&quot;, __func__, acl);
+			cry(fc(ctx),
+			    &quot;%s: bad ip address: [%s]&quot;, __func__, vec.ptr);
 			return (-1);
-		} else if (sscanf(acl + n, &quot;/%d&quot;, &amp;mask) == 0) {
+		} else if (sscanf(vec.ptr + n, &quot;/%d&quot;, &amp;mask) == 0) {
 			/* Do nothing, no mask specified */
 		} else if (mask &lt; 0 || mask &gt; 32) {
-			cry(NULL, &quot;%s: bad subnet mask: %d [%s]&quot;,
-			    __func__, n, acl);
+			cry(fc(ctx), &quot;%s: bad subnet mask: %d [%s]&quot;,
+			    __func__, n, vec.ptr);
 			return (-1);
 		}
 
@@ -3184,11 +3875,10 @@ mg_fini(struct mg_context *ctx)
 	close_all_listening_sockets(ctx);
 
 	/* Wait until all threads finish */
-	while (ctx-&gt;num_active + ctx-&gt;num_idle &gt; 0)
-		sleep(0);
-
-	assert(ctx-&gt;num_active == 0);
-	assert(ctx-&gt;num_idle == 0);
+	(void) pthread_mutex_lock(&amp;ctx-&gt;thr_mutex);
+	while (ctx-&gt;num_threads &gt; 0)
+		(void) pthread_cond_wait(&amp;ctx-&gt;thr_cond, &amp;ctx-&gt;thr_mutex);
+	(void) pthread_mutex_unlock(&amp;ctx-&gt;thr_mutex);
 
 	/* Deallocate all registered callbacks */
 	for (i = 0; i &lt; ctx-&gt;num_callbacks; i++)
@@ -3210,11 +3900,15 @@ mg_fini(struct mg_context *ctx)
 	if (ctx-&gt;ssl_ctx)
 		SSL_CTX_free(ctx-&gt;ssl_ctx);
 
-	/* Close control pipe */
-	(void) close(ctx-&gt;ctl[0]);
-	(void) close(ctx-&gt;ctl[1]);
+	/* Deallocate mutexes and condvars */
+	for (i = 0; i &lt; NUM_OPTIONS; i++)
+		(void) pthread_mutex_destroy(&amp;ctx-&gt;opt_mutex[i]);
 
-	(void) pthread_mutex_destroy(&amp;ctx-&gt;opt_mutex);
+	(void) pthread_mutex_destroy(&amp;ctx-&gt;thr_mutex);
+	(void) pthread_mutex_destroy(&amp;ctx-&gt;bind_mutex);
+	(void) pthread_cond_destroy(&amp;ctx-&gt;thr_cond);
+	(void) pthread_cond_destroy(&amp;ctx-&gt;empty_cond);
+	(void) pthread_cond_destroy(&amp;ctx-&gt;full_cond);
 
 	/* Signal mg_stop() that we're done */
 	ctx-&gt;stop_flag = 2;
@@ -3227,14 +3921,14 @@ set_uid_option(struct mg_context *ctx, const char *uid)
 	struct passwd	*pw;
 	int		retval = FALSE;
 
-	ctx = NULL; /* Unused */
-
 	if ((pw = getpwnam(uid)) == NULL)
-		cry(NULL, &quot;%s: unknown user [%s]&quot;, __func__, uid);
+		cry(fc(ctx), &quot;%s: unknown user [%s]&quot;, __func__, uid);
 	else if (setgid(pw-&gt;pw_gid) == -1)
-		cry(NULL, &quot;%s: setgid(%s): %s&quot;, __func__, uid, strerror(errno));
+		cry(fc(ctx), &quot;%s: setgid(%s): %s&quot;,
+		    __func__, uid, strerror(errno));
 	else if (setuid(pw-&gt;pw_uid) == -1)
-		cry(NULL, &quot;%s: setuid(%s): %s&quot;, __func__, uid, strerror(errno));
+		cry(fc(ctx), &quot;%s: setuid(%s): %s&quot;,
+		    __func__, uid, strerror(errno));
 	else
 		retval = TRUE;
 
@@ -3249,61 +3943,108 @@ mg_set_ssl_password_callback(struct mg_context *ctx, mg_spcb_t func)
 	ctx-&gt;ssl_password_callback = func;
 }
 
-/*
- * Dynamically load SSL library. Set up ctx-&gt;ssl_ctx pointer.
- */
+static pthread_mutex_t *ssl_mutexes;
+
+static void
+ssl_locking_callback(int mode, int mutex_num, const char *file, int line)
+{
+	line = 0;	/* Unused */
+	file = NULL;	/* Unused */
+
+	if (mode &amp; CRYPTO_LOCK)
+		(void) pthread_mutex_lock(&amp;ssl_mutexes[mutex_num]);
+	else
+		(void) pthread_mutex_unlock(&amp;ssl_mutexes[mutex_num]);
+}
+
+static unsigned long
+ssl_id_callback(void)
+{
+	return ((unsigned long) pthread_self());
+}
+
 static bool_t
-set_ssl_option(struct mg_context *ctx, const char *pem)
+load_dll(struct mg_context *ctx, const char *dll_name, struct ssl_func *sw)
 {
-	SSL_CTX		*CTX;
-	void		*lib;
 	union {void *p; void (*fp)(void);} u;
+	void		*dll_handle;
 	struct ssl_func	*fp;
-	int		retval = FALSE;
 
-	/* Load SSL library dynamically */
-	if ((lib = dlopen(SSL_LIB, RTLD_LAZY)) == NULL) {
-		cry(NULL, &quot;%s: cannot load %s&quot;, __func__, SSL_LIB);
+	if ((dll_handle = dlopen(dll_name, RTLD_LAZY)) == NULL) {
+		cry(fc(ctx), &quot;%s: cannot load %s&quot;, __func__, dll_name);
 		return (FALSE);
 	}
 
-	for (fp = ssl_sw; fp-&gt;name != NULL; fp++) {
+	for (fp = sw; fp-&gt;name != NULL; fp++) {
 #ifdef _WIN32
 		/* GetProcAddress() returns pointer to function */
-		u.fp = (void (*)(void)) dlsym(lib, fp-&gt;name);
+		u.fp = (void (*)(void)) dlsym(dll_handle, fp-&gt;name);
 #else
 		/*
 		 * dlsym() on UNIX returns void *.
 		 * ISO C forbids casts of data pointers to function
 		 * pointers. We need to use a union to make a cast.
 		 */
-		u.p = dlsym(lib, fp-&gt;name);
+		u.p = dlsym(dll_handle, fp-&gt;name);
 #endif /* _WIN32 */
 		if (u.fp == NULL) {
-			cry(NULL, &quot;%s: cannot find %s&quot;, __func__, fp-&gt;name);
+			cry(fc(ctx), &quot;%s: cannot find %s&quot;, __func__, fp-&gt;name);
 			return (FALSE);
 		} else {
 			fp-&gt;ptr = u.fp;
 		}
 	}
 
+	return (TRUE);
+}
+
+/*
+ * Dynamically load SSL library. Set up ctx-&gt;ssl_ctx pointer.
+ */
+static bool_t
+set_ssl_option(struct mg_context *ctx, const char *pem)
+{
+	SSL_CTX		*CTX;
+	int		i, size, retval = FALSE;
+
+	if (load_dll(ctx, SSL_LIB, ssl_sw) == FALSE ||
+	    load_dll(ctx, CRYPTO_LIB, crypto_sw) == FALSE)
+		return (FALSE);
+
 	/* Initialize SSL crap */
 	SSL_library_init();
 
 	if ((CTX = SSL_CTX_new(SSLv23_server_method())) == NULL)
-		cry(NULL, &quot;SSL_CTX_new error&quot;);
+		cry(fc(ctx), &quot;SSL_CTX_new error&quot;);
 	else if (ctx-&gt;ssl_password_callback != NULL)
 		SSL_CTX_set_default_passwd_cb(CTX, ctx-&gt;ssl_password_callback);
 
 	if (CTX != NULL &amp;&amp; SSL_CTX_use_certificate_file(
 	    CTX, pem, SSL_FILETYPE_PEM) == 0)
-		cry(NULL, &quot;%s: cannot open %s&quot;, __func__, pem);
+		cry(fc(ctx), &quot;%s: cannot open %s&quot;, __func__, pem);
 	else if (CTX != NULL &amp;&amp; SSL_CTX_use_PrivateKey_file(
 	    CTX, pem, SSL_FILETYPE_PEM) == 0)
-		cry(NULL, &quot;%s: cannot open %s&quot;, NULL, pem);
+		cry(fc(ctx), &quot;%s: cannot open %s&quot;, NULL, pem);
 	else
 		retval = TRUE;
 
+	/*
+	 * Initialize locking callbacks, needed for thread safety.
+	 * http://www.openssl.org/support/faq.html#PROG1
+	 */
+	size = sizeof(pthread_mutex_t) * CRYPTO_num_locks();
+	if ((ssl_mutexes = (pthread_mutex_t *) malloc(size)) == NULL) {
+		cry(fc(ctx), &quot;%s: cannot allocate mutexes&quot;, __func__);
+		return (FALSE);
+	}
+
+	for (i = 0; i &lt; CRYPTO_num_locks(); i++)
+		pthread_mutex_init(&amp;ssl_mutexes[i], NULL);
+
+	CRYPTO_set_locking_callback(&amp;ssl_locking_callback);
+	CRYPTO_set_id_callback(&amp;ssl_id_callback);
+
+	/* Done with everything. Save the context. */
 	ctx-&gt;ssl_ctx = CTX;
 
 	return (retval);
@@ -3311,7 +4052,7 @@ set_ssl_option(struct mg_context *ctx, const char *pem)
 #endif /* !NO_SSL */
 
 static bool_t
-open_log_file(FILE **fpp, const char *path)
+open_log_file(struct mg_context *ctx, FILE **fpp, const char *path)
 {
 	bool_t	retval = TRUE;
 
@@ -3320,8 +4061,8 @@ open_log_file(FILE **fpp, const char *path)
 
 	if (path == NULL) {
 		*fpp = NULL;
-	} else if ((*fpp = fopen(path, &quot;a&quot;)) == NULL) {
-		cry(NULL, &quot;%s(%s): %s&quot;, __func__, path, strerror(errno));
+	} else if ((*fpp = mg_fopen(path, &quot;a&quot;)) == NULL) {
+		cry(fc(ctx), &quot;%s(%s): %s&quot;, __func__, path, strerror(errno));
 		retval = FALSE;
 	} else {
 		set_close_on_exec(fileno(*fpp));
@@ -3333,23 +4074,22 @@ open_log_file(FILE **fpp, const char *path)
 static bool_t
 set_alog_option(struct mg_context *ctx, const char *path)
 {
-	return (open_log_file(&amp;ctx-&gt;access_log, path));
+	return (open_log_file(ctx, &amp;ctx-&gt;access_log, path));
 }
 
 static bool_t
 set_elog_option(struct mg_context *ctx, const char *path)
 {
-	return (open_log_file(&amp;ctx-&gt;error_log, path));
+	return (open_log_file(ctx, &amp;ctx-&gt;error_log, path));
 }
 
-#if !defined(NO_AUTH)
 static bool_t
 set_gpass_option(struct mg_context *ctx, const char *path)
 {
+	struct mgstat	mgstat;
 	ctx = NULL;
-	return (access(path, R_OK) == 0);
+	return (mg_stat(path, &amp;mgstat) == 0);
 }
-#endif /* !NO_AUTH */
 
 static bool_t
 set_max_threads_option(struct mg_context *ctx, const char *str)
@@ -3358,142 +4098,94 @@ set_max_threads_option(struct mg_context *ctx, const char *str)
 	return (TRUE);
 }
 
-static void
-admin_page(struct mg_connection *conn, const struct mg_request_info *ri,
-		void *user_data)
+static bool_t
+set_acl_option(struct mg_context *ctx, const char *acl)
 {
-	const struct mg_option	*list;
-	const char		*option_name, *option_value;
-	int			i;
-
-	user_data = NULL; /* Unused */
-
-	(void) mg_printf(conn,
-	    &quot;HTTP/1.1 200 OK\r\n&quot;
-	    &quot;Content-Type: text/html\r\n\r\n&quot;
-	    &quot;&lt;html&gt;&lt;body&gt;&lt;h1&gt;Mongoose v. %s&lt;/h1&gt;&quot;, mg_version());
-
-	if (!strcmp(ri-&gt;request_method, &quot;POST&quot;)) {
-		option_name = mg_get_var(conn, &quot;o&quot;);
-		option_value = mg_get_var(conn, &quot;v&quot;);
-		if (mg_set_option(conn-&gt;ctx,
-		    option_name, option_value) == -1) {
-			(void) mg_printf(conn,
-			    &quot;&lt;p style=\&quot;background: red\&quot;&gt;Error setting &quot;
-			    &quot;option \&quot;%s\&quot;&lt;/p&gt;&quot;,
-			    option_name ? option_name : &quot;(null)&quot;);
-		} else {
-			(void) mg_printf(conn,
-			    &quot;&lt;p style=\&quot;color: green\&quot;&gt;Saved: %s=%s&lt;/p&gt;&quot;,
-			    option_name, option_value ? option_value : &quot;NULL&quot;);
-		}
-	}
-
-	/* Print table with all options */
-	list = mg_get_option_list();
-	(void) mg_printf(conn, &quot;%s&quot;, &quot;&lt;table border=\&quot;1\&quot;&quot;
-	    &quot;&lt;tr&gt;&lt;th&gt;Option&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&quot;
-	    &quot;&lt;th colspan=2&gt;Value&lt;/th&gt;&lt;/tr&gt;&quot;);
-
-	for (i = 0; list[i].name != NULL; i++) {
-		option_value = mg_get_option(conn-&gt;ctx, list[i].name);
-		if (option_value == NULL)
-			option_value = &quot;&quot;;
-		(void) mg_printf(conn,
-		    &quot;&lt;form method=post&gt;&lt;tr&gt;&lt;td&gt;%s&lt;/td&gt;&lt;td&gt;%s&lt;/td&gt;&quot;
-		    &quot;&lt;input type=hidden name=o value='%s'&gt;&quot;
-		    &quot;&lt;td&gt;&lt;input type=text name=v value='%s'&gt;&lt;/td&gt;&quot;
-		    &quot;&lt;td&gt;&lt;input type=submit value=save&gt;&lt;/td&gt;&lt;/form&gt;&lt;/tr&gt;&quot;,
-		    list[i].name, list[i].description, list[i].name,
-		    option_value);
-	}
+	struct usa	fake;
 
-	(void) mg_printf(conn, &quot;%s&quot;, &quot;&lt;/table&gt;&lt;/body&gt;&lt;/html&gt;&quot;);
+	return (check_acl(ctx, acl, &amp;fake) != -1);
 }
 
+static void admin_page(struct mg_connection *,
+		const struct mg_request_info *, void *);
 static bool_t
 set_admin_uri_option(struct mg_context *ctx, const char *uri)
 {
-	mg_bind_to_uri(ctx, uri, &amp;admin_page, NULL);
+	mg_set_uri_callback(ctx, uri, &amp;admin_page, NULL);
 	return (TRUE);
 }
 
+/*
+ * Check if the comma-separated list of options has a format of key-value
+ * pairs: &quot;k1=v1,k2=v2&quot;. Return FALSE if any entry has invalid key or value.
+ */
 static bool_t
-set_acl_option(struct mg_context *ctx, const char *acl)
+set_kv_list_option(struct mg_context *ctx, const char *str)
 {
-	struct usa	fake;
+	const char	*list;
+	struct vec	key, value;
 
-	ctx = NULL;
-	return (check_acl(acl, &amp;fake) != -1);
+	list = str;
+	while ((list = next_option(list, &amp;key, &amp;value)) != NULL)
+		if (key.len == 0 || value.len == 0) {
+			cry(fc(ctx), &quot;Invalid list specified: [%s], &quot;
+			    &quot;expecting key1=value1,key2=value2,...&quot;, str);
+			return (FALSE);
+		}
+
+	return (TRUE);
 }
 
 static const struct mg_option known_options[] = {
-	{&quot;root&quot;, &quot;\tWeb root directory&quot;, NULL},
-	{&quot;index_files&quot;,	&quot;Index files&quot;, &quot;index.html,index.htm,index.cgi&quot;},
+	{&quot;root&quot;, &quot;\tWeb root directory&quot;, NULL, OPT_ROOT, NULL},
+	{&quot;index_files&quot;,	&quot;Index files&quot;, &quot;index.html,index.htm,index.cgi&quot;,
+		OPT_INDEX_FILES, NULL},
 #if !defined(NO_SSL)
-	{&quot;ssl_cert&quot;, &quot;SSL certificate file&quot;, NULL},
+	{&quot;ssl_cert&quot;, &quot;SSL certificate file&quot;, NULL,
+		OPT_SSL_CERTIFICATE, &amp;set_ssl_option},
 #endif /* !NO_SSL */
-	{&quot;ports&quot;, &quot;Listening ports&quot;, NULL},
-	{&quot;dir_list&quot;, &quot;Directory listing&quot;, &quot;yes&quot;},
-	{&quot;protect&quot;, &quot;URI to htpasswd mapping&quot;, NULL},
+	{&quot;ports&quot;, &quot;Listening ports&quot;, NULL,
+		OPT_PORTS, &amp;set_ports_option},
+	{&quot;dir_list&quot;, &quot;Directory listing&quot;, &quot;yes&quot;,
+		OPT_DIR_LIST, NULL},
+	{&quot;protect&quot;, &quot;URI to htpasswd mapping&quot;, NULL,
+		OPT_PROTECT, &amp;set_kv_list_option},
 #if !defined(NO_CGI)
-	{&quot;cgi_ext&quot;, &quot;CGI extensions&quot;, &quot;cgi,pl,php&quot;},
-	{&quot;cgi_interp&quot;, &quot;CGI interpreter to use with all CGI scripts&quot;, NULL},
-	{&quot;cgi_env&quot;, &quot;Custom CGI enviroment variables&quot;, NULL},
+	{&quot;cgi_ext&quot;, &quot;CGI extensions&quot;, &quot;.cgi,.pl,.php&quot;,
+		OPT_CGI_EXTENSIONS, NULL},
+	{&quot;cgi_interp&quot;, &quot;CGI interpreter to use with all CGI scripts&quot;, NULL,
+		OPT_CGI_INTERPRETER, NULL},
+	{&quot;cgi_env&quot;, &quot;Custom CGI enviroment variables&quot;, NULL,
+		OPT_CGI_ENV, &amp;set_kv_list_option},
 #endif /* NO_CGI */
-	{&quot;ssi_ext&quot;, &quot;SSI extensions&quot;, &quot;shtml,shtm&quot;},
-#if !defined(NO_AUTH)
-	{&quot;auth_realm&quot;, &quot;Authentication domain name&quot;, &quot;mydomain.com&quot;},
-	{&quot;auth_gpass&quot;, &quot;Global passwords file&quot;, NULL},
-	{&quot;auth_PUT&quot;, &quot;PUT,DELETE auth file&quot;, NULL},
-#endif /* !NO_AUTH */
+	{&quot;ssi_ext&quot;, &quot;SSI extensions&quot;, &quot;.shtml,.shtm&quot;,
+		OPT_SSI_EXTENSIONS, NULL},
+	{&quot;auth_realm&quot;, &quot;Authentication domain name&quot;, &quot;mydomain.com&quot;,
+		OPT_AUTH_DOMAIN, NULL},
+	{&quot;auth_gpass&quot;, &quot;Global passwords file&quot;, NULL,
+		OPT_AUTH_GPASSWD, &amp;set_gpass_option},
+	{&quot;auth_PUT&quot;, &quot;PUT,DELETE auth file&quot;, NULL,
+		OPT_AUTH_PUT, NULL},
 #if !defined(_WIN32)
-	{&quot;uid&quot;, &quot;\tRun as user&quot;, NULL},
+	{&quot;uid&quot;, &quot;\tRun as user&quot;, NULL, OPT_UID, &amp;set_uid_option},
 #endif /* !_WIN32 */
-	{&quot;access_log&quot;, &quot;Access log file&quot;, NULL},
-	{&quot;error_log&quot;, &quot;Error log file&quot;, NULL},
-	{&quot;aliases&quot;, &quot;Path=URI mappings&quot;, NULL},
-	{&quot;admin_uri&quot;, &quot;Administration page URI&quot;, NULL},
-	{&quot;acl&quot;, &quot;\tAllow/deny IP addresses/subnets&quot;, NULL},
-	{&quot;max_threads&quot;, &quot;Maximum simultaneous threads to spawn&quot;, &quot;100&quot;},
-	{&quot;idle_time&quot;, &quot;Seconds to wait before idle thread exits&quot;, &quot;10&quot;},
-	{NULL, NULL, NULL}
-};
-
-static const struct option_setter {
-	int	context_index;
-	bool_t (*setter)(struct mg_context *, const char *);
-} setters[] = {
-	{OPT_ROOT,		NULL},
-	{OPT_INDEX_FILES,	NULL},
-#if !defined(NO_SSL)
-	{OPT_SSL_CERTIFICATE,	&amp;set_ssl_option},
-#endif /* !NO_SSL */
-	{OPT_PORTS,		&amp;set_ports_option},
-	{OPT_DIR_LIST,		NULL},
-	{OPT_PROTECT,		NULL},
-#if !defined(NO_CGI)
-	{OPT_CGI_EXTENSIONS,	NULL},
-	{OPT_CGI_INTERPRETER,	NULL},
-	{OPT_CGI_ENV,		NULL},
-#endif /* NO_CGI */
-	{OPT_SSI_EXTENSIONS,	NULL},
-#if !defined(NO_AUTH)
-	{OPT_AUTH_DOMAIN,	NULL},
-	{OPT_AUTH_GPASSWD,	&amp;set_gpass_option},
-	{OPT_AUTH_PUT,		NULL},
-#endif /* !NO_AUTH */
-#if !defined(_WIN32)
-	{OPT_UID,		&amp;set_uid_option},
-#endif /* !_WIN32 */
-	{OPT_ACCESS_LOG,	&amp;set_alog_option},
-	{OPT_ERROR_LOG,		&amp;set_elog_option},
-	{OPT_ALIASES,		NULL},
-	{OPT_ADMIN_URI,		&amp;set_admin_uri_option},
-	{OPT_ACL,		&amp;set_acl_option},
-	{OPT_MAX_THREADS,	&amp;set_max_threads_option},
-	{OPT_IDLE_TIME,		NULL},
-	{-1,			NULL}
+	{&quot;access_log&quot;, &quot;Access log file&quot;, NULL,
+		OPT_ACCESS_LOG, &amp;set_alog_option},
+	{&quot;error_log&quot;, &quot;Error log file&quot;, NULL,
+		OPT_ERROR_LOG, &amp;set_elog_option},
+	{&quot;aliases&quot;, &quot;Path=URI mappings&quot;, NULL,
+		OPT_ALIASES, &amp;set_kv_list_option},
+	{&quot;admin_uri&quot;, &quot;Administration page URI&quot;, NULL,
+		OPT_ADMIN_URI, &amp;set_admin_uri_option},
+	{&quot;acl&quot;, &quot;\tAllow/deny IP addresses/subnets&quot;, NULL,
+		OPT_ACL, &amp;set_acl_option},
+	{&quot;max_threads&quot;, &quot;Maximum simultaneous threads to spawn&quot;, &quot;100&quot;,
+		OPT_MAX_THREADS, &amp;set_max_threads_option},
+	{&quot;idle_time&quot;, &quot;Time in seconds connection stays idle&quot;, &quot;10&quot;,
+		OPT_IDLE_TIME, NULL},
+	{&quot;mime_types&quot;, &quot;Comma separated list of ext=mime_type pairs&quot;, NULL,
+		OPT_MIME_TYPES, &amp;set_kv_list_option},
+	{NULL, NULL, NULL, 0, NULL}
 };
 
 static const struct mg_option *
@@ -3512,70 +4204,139 @@ int
 mg_set_option(struct mg_context *ctx, const char *opt, const char *val)
 {
 	const struct mg_option	*option;
-	int			i, ctx_index, retval;
+	int			i, retval;
 
-	mg_lock(ctx);
+	DEBUG_TRACE((DEBUG_MGS_PREFIX &quot;%s: [%s]-&gt;[%s]&quot;, __func__, opt, val));
 	if (opt != NULL &amp;&amp; (option = find_opt(opt)) != NULL) {
 		i = (int) (option - known_options);
+		lock_option(ctx, i);
 
-		if (setters[i].setter != NULL)
-			retval = setters[i].setter(ctx, val);
+		if (option-&gt;setter != NULL)
+			retval = option-&gt;setter(ctx, val);
 		else
 			retval = TRUE;
 
 		/* Free old value if any */
-		ctx_index = setters[i].context_index;
-		if (ctx-&gt;options[ctx_index] != NULL)
-			free(ctx-&gt;options[ctx_index]);
+		if (ctx-&gt;options[option-&gt;index] != NULL)
+			free(ctx-&gt;options[option-&gt;index]);
 
 		/* Set new option value */
-		ctx-&gt;options[ctx_index] = val ? mg_strdup(val) : NULL;
+		ctx-&gt;options[option-&gt;index] = val ? mg_strdup(val) : NULL;
+		unlock_option(ctx, i);
+
+		if (retval == FALSE)
+			cry(fc(ctx), &quot;%s(%s): failure&quot;, __func__, opt);
 	} else {
+		cry(fc(ctx), &quot;%s: No such option: [%s]&quot;, __func__, opt);
 		retval = -1;
 	}
-	mg_unlock(ctx);
 
 	return (retval);
 }
 
-const struct mg_option *
-mg_get_option_list(void)
+void
+mg_show_usage_string(FILE *fp)
 {
-	return (known_options);
+	const struct mg_option	*o;
+
+	(void) fprintf(stderr,
+	    &quot;Mongoose version %s (c) Sergey Lyubka\n&quot;
+	    &quot;usage: mongoose [options] [config_file]\n&quot;, mg_version());
+
+	fprintf(fp, &quot;  -A &lt;htpasswd_file&gt; &lt;realm&gt; &lt;user&gt; &lt;passwd&gt;\n&quot;);
+
+	for (o = known_options; o-&gt;name != NULL; o++) {
+		(void) fprintf(fp, &quot;  -%s\t%s&quot;, o-&gt;name, o-&gt;description);
+		if (o-&gt;default_value != NULL)
+			fprintf(fp, &quot; (default: \&quot;%s\&quot;)&quot;, o-&gt;default_value);
+		fputc('\n', fp);
+	}
 }
 
 const char *
-mg_get_option(struct mg_context *ctx, const char *option_name)
+mg_get_option(const struct mg_context *ctx, const char *option_name)
 {
-	const struct mg_option	*o;
-	const char			*value = NULL;
+	const struct mg_option	*option;
+
+	if ((option = find_opt(option_name)) != NULL)
+		return (ctx-&gt;options[option-&gt;index]);
+	else
+		return (NULL);
+}
 
-	mg_lock(ctx);
-	value = NULL;
-	if ((o = find_opt(option_name)) != NULL)
-		value = ctx-&gt;options[setters[o - known_options].context_index];
-	mg_unlock(ctx);
+static void
+admin_page(struct mg_connection *conn, const struct mg_request_info *ri,
+			   void *user_data)
+{
+	const struct mg_option	*option;
+	const char		*option_name, *option_value;
+
+	user_data = NULL; /* Unused */
+
+	(void) mg_printf(conn,
+	&quot;HTTP/1.1 200 OK\r\n&quot;
+			&quot;Content-Type: text/html\r\n\r\n&quot;
+			&quot;&lt;html&gt;&lt;body&gt;&lt;h1&gt;Mongoose v. %s&lt;/h1&gt;&quot;, mg_version());
 
-	return (value);
+	if (!strcmp(ri-&gt;request_method, &quot;POST&quot;)) {
+		option_name = mg_get_var(conn, &quot;o&quot;);
+		option_value = mg_get_var(conn, &quot;v&quot;);
+		if (mg_set_option(conn-&gt;ctx,
+		    option_name, option_value) == -1) {
+			(void) mg_printf(conn,
+			    &quot;&lt;p style=\&quot;background: red\&quot;&gt;Error setting &quot;
+			    &quot;option \&quot;%s\&quot;&lt;/p&gt;&quot;,
+			    option_name ? option_name : &quot;(null)&quot;);
+		} else {
+			(void) mg_printf(conn,
+			    &quot;&lt;p style=\&quot;color: green\&quot;&gt;Saved: %s=%s&lt;/p&gt;&quot;,
+			    option_name, option_value ? option_value : &quot;NULL&quot;);
+		}
+	}
+
+	/* Print table with all options */
+	(void) mg_printf(conn, &quot;%s&quot;, &quot;&lt;table border=\&quot;1\&quot;&quot;
+			&quot;&lt;tr&gt;&lt;th&gt;Option&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&quot;
+					&quot;&lt;th colspan=2&gt;Value&lt;/th&gt;&lt;/tr&gt;&quot;);
+
+	for (option = known_options; option-&gt;name != NULL; option++) {
+		option_value = mg_get_option(conn-&gt;ctx, option-&gt;name);
+		if (option_value == NULL)
+			option_value = &quot;&quot;;
+		(void) mg_printf(conn,
+		    &quot;&lt;form method=post&gt;&lt;tr&gt;&lt;td&gt;%s&lt;/td&gt;&lt;td&gt;%s&lt;/td&gt;&quot;
+		    &quot;&lt;input type=hidden name=o value='%s'&gt;&quot;
+		    &quot;&lt;td&gt;&lt;input type=text name=v value='%s'&gt;&lt;/td&gt;&quot;
+		    &quot;&lt;td&gt;&lt;input type=submit value=save&gt;&lt;/td&gt;&lt;/form&gt;&lt;/tr&gt;&quot;,
+		    option-&gt;name, option-&gt;description,
+		    option-&gt;name, option_value);
+	}
+
+	(void) mg_printf(conn, &quot;%s&quot;, &quot;&lt;/table&gt;&lt;/body&gt;&lt;/html&gt;&quot;);
 }
 
 static void
 reset_per_request_attributes(struct mg_connection *conn)
 {
-	if (conn-&gt;request_info.remote_user != NULL)
-		free(conn-&gt;request_info.remote_user);
-	if (conn-&gt;free_post_data &amp;&amp; conn-&gt;request_info.post_data != NULL)
-		free((void *)conn-&gt;request_info.post_data);
+	if (conn-&gt;request_info.remote_user != NULL) {
+		free((void *) conn-&gt;request_info.remote_user);
+		conn-&gt;request_info.remote_user = NULL;
+	}
+	if (conn-&gt;free_post_data &amp;&amp; conn-&gt;request_info.post_data != NULL) {
+		free((void *) conn-&gt;request_info.post_data);
+		conn-&gt;request_info.post_data = NULL;
+	}
 }
 
 static void
-close_socket_gracefully(SOCKET sock)
+close_socket_gracefully(struct mg_connection *conn, SOCKET sock)
 {
 	char	buf[BUFSIZ];
 	int	n;
 
 	/* Send FIN to the client */
 	(void) shutdown(sock, SHUT_WR);
+	set_non_blocking_mode(conn, sock);
 
 	/*
 	 * Read and discard pending data. If we do not do that and close the
@@ -3585,7 +4346,7 @@ close_socket_gracefully(SOCKET sock)
 	 * does recv() it gets no data back.
 	 */
 	do {
-		n = pull(-1, sock, NULL, buf, sizeof(buf));
+		n = pull(NULL, sock, NULL, buf, sizeof(buf));
 	} while (n &gt; 0);
 
 	/* Now we know that our FIN is ACK-ed, safe to close */
@@ -3596,11 +4357,12 @@ static void
 close_connection(struct mg_connection *conn)
 {
 	reset_per_request_attributes(conn);
+
 	if (conn-&gt;ssl)
 		SSL_free(conn-&gt;ssl);
-	if (conn-&gt;client.sock != INVALID_SOCKET) {
-		close_socket_gracefully(conn-&gt;client.sock);
-	}
+
+	if (conn-&gt;client.sock != INVALID_SOCKET)
+		close_socket_gracefully(conn, conn-&gt;client.sock);
 }
 
 static void
@@ -3609,7 +4371,6 @@ reset_connection_attributes(struct mg_connection *conn)
 	reset_per_request_attributes(conn);
 	conn-&gt;free_post_data = FALSE;
 	conn-&gt;request_info.status_code = -1;
-	conn-&gt;keep_alive = FALSE;
 	conn-&gt;num_bytes_sent = 0;
 	(void) memset(&amp;conn-&gt;request_info, 0, sizeof(conn-&gt;request_info));
 }
@@ -3644,190 +4405,197 @@ process_new_connection(struct mg_connection *conn)
 	int	request_len, nread;
 
 	nread = 0;
-	do {
-		/* If next request is not pipelined, read it in */
-		if ((request_len = get_request_len(buf, (size_t) nread)) == 0)
-			request_len = read_request(-1, conn-&gt;client.sock,
-			    conn-&gt;ssl, buf, sizeof(buf), &amp;nread);
-		assert(nread &gt;= request_len);
-
-		if (request_len == 0)
-			break;	/* Remote end closed the connection */
-
-		/*
-		 * This sets conn-&gt;keep_alive to FALSE, so by default
-		 * we break the loop.
-		 */
-		reset_connection_attributes(conn);
-
-		/* 0-terminate the request: parse_request uses sscanf */
-		buf[request_len - 1] = '\0';
-
-		if (parse_http_request(buf, ri, &amp;conn-&gt;client.usa)) {
-			if (ri-&gt;http_version_major != 1 ||
-			     (ri-&gt;http_version_major == 1 &amp;&amp;
-			     (ri-&gt;http_version_minor &lt; 0 ||
-			     ri-&gt;http_version_minor &gt; 1))) {
-				send_error(conn, 505,
-				    &quot;HTTP version not supported&quot;,
-				    &quot;%s&quot;, &quot;Weird HTTP version&quot;);
-				log_access(conn);
-			} else {
-				ri-&gt;post_data = buf + request_len;
-				ri-&gt;post_data_len = nread - request_len;
-				analyze_request(conn);
-				log_access(conn);
-				shift_to_next(conn, buf, request_len, &amp;nread);
-			}
+	reset_connection_attributes(conn);
+
+	/* If next request is not pipelined, read it in */
+	if ((request_len = get_request_len(buf, (size_t) nread)) == 0)
+		request_len = read_request(NULL, conn-&gt;client.sock,
+		    conn-&gt;ssl, buf, sizeof(buf), &amp;nread);
+	assert(nread &gt;= request_len);
+
+	if (request_len &lt;= 0)
+		return;	/* Remote end closed the connection */
+
+	/* 0-terminate the request: parse_request uses sscanf */
+	buf[request_len - 1] = '\0';
+
+	if (parse_http_request(buf, ri, &amp;conn-&gt;client.rsa)) {
+		if (ri-&gt;http_version_major != 1 ||
+		    (ri-&gt;http_version_major == 1 &amp;&amp;
+		    (ri-&gt;http_version_minor &lt; 0 ||
+		    ri-&gt;http_version_minor &gt; 1))) {
+			send_error(conn, 505,
+			    &quot;HTTP version not supported&quot;,
+			    &quot;%s&quot;, &quot;Weird HTTP version&quot;);
+			log_access(conn);
 		} else {
-			/* Do not put garbage in the access log */
-			send_error(conn, 400, &quot;Bad Request&quot;,
-			    &quot;Can not parse request: [%.*s]&quot;, nread, buf);
+			ri-&gt;post_data = buf + request_len;
+			ri-&gt;post_data_len = nread - request_len;
+			conn-&gt;birth_time = time(NULL);
+			analyze_request(conn);
+			log_access(conn);
+			shift_to_next(conn, buf, request_len, &amp;nread);
 		}
+	} else {
+		/* Do not put garbage in the access log */
+		send_error(conn, 400, &quot;Bad Request&quot;,
+		    &quot;Can not parse request: [%.*s]&quot;, nread, buf);
+	}
 
-	} while (conn-&gt;keep_alive);
 }
 
 /*
- * Worker thread function. It blocks on reading from the control pipe. Every
- * second master thread sends fake invalid sockets to all idle threads, so
- * they can unblock, check for idle timeout and exit if necessary.
- * This function increments and decrements global counters - for idle and
- * active threads. Atomic functions were not used, since:
- *    a) there is no portable interface for atomic increment and decrement
- *    b) the increments and decrements are blind with no return values, and I
- *       believe in this case atomic ops are not needed.
+ * Worker threads take accepted socket from the queue
  */
+static bool_t
+get_socket(struct mg_context *ctx, struct socket *sp)
+{
+	struct timespec	ts;
+
+	(void) pthread_mutex_lock(&amp;ctx-&gt;thr_mutex);
+	DEBUG_TRACE((DEBUG_MGS_PREFIX &quot;%s: thread %p: going idle&quot;,
+	    __func__, (void *) pthread_self()));
+
+	/* If the queue is empty, wait. We're idle at this point. */
+	ctx-&gt;num_idle++;
+	while (ctx-&gt;sq_head == ctx-&gt;sq_tail) {
+		ts.tv_nsec = 0;
+		ts.tv_sec = time(NULL) + atoi(ctx-&gt;options[OPT_IDLE_TIME]) + 1;
+		if (pthread_cond_timedwait(&amp;ctx-&gt;empty_cond,
+		    &amp;ctx-&gt;thr_mutex, &amp;ts) != 0) {
+			/* Timeout! release the mutex and return */
+			(void) pthread_mutex_unlock(&amp;ctx-&gt;thr_mutex);
+			return (FALSE);
+		}
+	}
+	assert(ctx-&gt;sq_head &gt; ctx-&gt;sq_tail);
+
+	/* We're going busy now: got a socket to process! */
+	ctx-&gt;num_idle--;
+
+	/* Copy socket from the queue and increment tail */
+	*sp = ctx-&gt;queue[ctx-&gt;sq_tail % ARRAY_SIZE(ctx-&gt;queue)];
+	ctx-&gt;sq_tail++;
+	DEBUG_TRACE((DEBUG_MGS_PREFIX
+	    &quot;%s: thread %p grabbed socket %d, going busy&quot;,
+	    __func__, (void *) pthread_self(), sp-&gt;sock));
+
+	/* Wrap pointers if needed */
+	while (ctx-&gt;sq_tail &gt; (int) ARRAY_SIZE(ctx-&gt;queue)) {
+		ctx-&gt;sq_tail -= ARRAY_SIZE(ctx-&gt;queue);
+		ctx-&gt;sq_head -= ARRAY_SIZE(ctx-&gt;queue);
+	}
+
+	pthread_cond_signal(&amp;ctx-&gt;full_cond);
+	(void) pthread_mutex_unlock(&amp;ctx-&gt;thr_mutex);
+
+	return (TRUE);
+}
+
 static void
-worker_loop(struct mg_context *ctx)
+worker_thread(struct mg_context *ctx)
 {
 	struct mg_connection	conn;
-	time_t			expire;
 
-	expire = time(NULL) + atoi(ctx-&gt;options[OPT_IDLE_TIME]) + 1;
+	DEBUG_TRACE((DEBUG_MGS_PREFIX &quot;%s: thread %p starting&quot;,
+	    __func__, (void *) pthread_self()));
 
-	while (time(NULL) &lt; expire &amp;&amp; ctx-&gt;stop_flag == 0) {
-		(void) memset(&amp;conn, 0, sizeof(conn));
+	(void) memset(&amp;conn, 0, sizeof(conn));
 
-		/*
-		 * Theoretically, read() may return less than requested.
-		 * It should not happen, but if it will, it is a problem.
-		 * Then we should revert to using UDP sockets, which
-		 * are datagram channels, so other thread never reads
-		 * some part of the data in the control pipe.
-		 */
-		if (read(ctx-&gt;ctl[0], (void *) &amp;conn.client,
-		    sizeof(conn.client)) != sizeof(conn.client)) {
-			cry(NULL, &quot;%s: Error reading from control pipe: %d&quot;,
-			    __func__, ERRNO);
-			expire = 0;
-		} else if (conn.client.sock == INVALID_SOCKET) {
-			/*
-			 * This is a wakeup from the master thread, or from
-			 * the mg_stop() function.
-			 */
-			continue;
+	while (get_socket(ctx, &amp;conn.client) == TRUE) {
+		conn.birth_time = time(NULL);
+		conn.ctx = ctx;
+
+		if (conn.client.is_ssl &amp;&amp;
+		    (conn.ssl = SSL_new(conn.ctx-&gt;ssl_ctx)) == NULL) {
+			cry(&amp;conn, &quot;%s: SSL_new: %d&quot;, __func__, ERRNO);
+		} else if (conn.client.is_ssl &amp;&amp;
+		    SSL_set_fd(conn.ssl, conn.client.sock) != 1) {
+			cry(&amp;conn, &quot;%s: SSL_set_fd: %d&quot;, __func__, ERRNO);
+		} else if (conn.client.is_ssl &amp;&amp; SSL_accept(conn.ssl) != 1) {
+			cry(&amp;conn, &quot;%s: SSL handshake error&quot;, __func__);
 		} else {
-			ctx-&gt;num_active++;
-			ctx-&gt;num_idle--;
-			assert(ctx-&gt;num_idle &gt;= 0);
-
-			/* Initialize the rest of attributes */
-			conn.ctx = ctx;
-			conn.birth_time = time(NULL);
-
-			if (conn.client.is_ssl &amp;&amp;
-			    (conn.ssl = SSL_new(ctx-&gt;ssl_ctx)) == NULL) {
-				cry(&amp;conn, &quot;%s: SSL_new: %d&quot;, __func__, ERRNO);
-			} else if (conn.client.is_ssl &amp;&amp;
-			    SSL_set_fd(conn.ssl, conn.client.sock) != 1) {
-				cry(&amp;conn, &quot;%s: SSL_set_fd: %d&quot;,
-				    __func__, ERRNO);
-			} else if (conn.client.is_ssl &amp;&amp;
-			    SSL_accept(conn.ssl) != 1) {
-				cry(&amp;conn, &quot;%s: SSL handshake error&quot;, __func__);
-			} else {
-				process_new_connection(&amp;conn);
-			}
+			process_new_connection(&amp;conn);
+		}
 
-			close_connection(&amp;conn);
+		close_connection(&amp;conn);
+	}
 
-			ctx-&gt;num_idle++;
-			ctx-&gt;num_active--;
-			assert(ctx-&gt;num_active &gt;= 0);
+	/* Signal master that we're done with connection and exiting */
+	pthread_mutex_lock(&amp;conn.ctx-&gt;thr_mutex);
+	conn.ctx-&gt;num_threads--;
+	conn.ctx-&gt;num_idle--;
+	pthread_cond_signal(&amp;conn.ctx-&gt;thr_cond);
+	assert(conn.ctx-&gt;num_threads &gt;= 0);
+	pthread_mutex_unlock(&amp;conn.ctx-&gt;thr_mutex);
 
-			/* Advance expiration time in the future */
-			expire = time(NULL) +
-			    atoi(ctx-&gt;options[OPT_IDLE_TIME]) + 1;
-		}
+	DEBUG_TRACE((DEBUG_MGS_PREFIX &quot;%s: thread %p exiting&quot;,
+	    __func__, (void *) pthread_self()));
+}
+
+/*
+ * Master thread adds accepted socket to a queue
+ */
+static void
+put_socket(struct mg_context *ctx, const struct socket *sp)
+{
+	(void) pthread_mutex_lock(&amp;ctx-&gt;thr_mutex);
+
+	/* If the queue is full, wait */
+	while (ctx-&gt;sq_head - ctx-&gt;sq_tail &gt;= (int) ARRAY_SIZE(ctx-&gt;queue))
+		(void) pthread_cond_wait(&amp;ctx-&gt;full_cond, &amp;ctx-&gt;thr_mutex);
+	assert(ctx-&gt;sq_head - ctx-&gt;sq_tail &lt; (int) ARRAY_SIZE(ctx-&gt;queue));
+
+	/* Copy socket to the queue and increment head */
+	ctx-&gt;queue[ctx-&gt;sq_head % ARRAY_SIZE(ctx-&gt;queue)] = *sp;
+	ctx-&gt;sq_head++;
+	DEBUG_TRACE((DEBUG_MGS_PREFIX &quot;%s: queued socket %d&quot;,
+	    __func__, sp-&gt;sock));
+
+	/* If there are no idle threads, start one */
+	if (ctx-&gt;num_idle == 0 &amp;&amp; ctx-&gt;num_threads &lt; ctx-&gt;max_threads) {
+		if (start_thread(ctx,
+		    (mg_thread_func_t) worker_thread, ctx) != 0)
+			cry(fc(ctx), &quot;Cannot start thread: %d&quot;, ERRNO);
+		else
+			ctx-&gt;num_threads++;
 	}
 
-	ctx-&gt;num_idle--;
+	pthread_cond_signal(&amp;ctx-&gt;empty_cond);
+	(void) pthread_mutex_unlock(&amp;ctx-&gt;thr_mutex);
 }
 
 static void
 accept_new_connection(const struct socket *listener, struct mg_context *ctx)
 {
-	struct socket	accepted;
-
-	accepted.usa.len = sizeof(accepted.usa.u.sin);
+	struct socket		accepted;
 
+	accepted.rsa.len = sizeof(accepted.rsa.u.sin);
+	accepted.lsa = listener-&gt;lsa;
 	if ((accepted.sock = accept(listener-&gt;sock,
-	    &amp;accepted.usa.u.sa, &amp;accepted.usa.len)) == INVALID_SOCKET) {
-		cry(NULL, &quot;%s: accept: %d&quot;, __func__, ERRNO);
-	} else if (ctx-&gt;options[OPT_ACL] != NULL &amp;&amp;
-	    !check_acl(ctx-&gt;options[OPT_ACL], &amp;accepted.usa)) {
-		cry(NULL, &quot;%s: %s is not allowed to connect&quot;,
-		    __func__, inet_ntoa(accepted.usa.u.sin.sin_addr));
-		(void) closesocket(accepted.sock);
-	} else {
-		/*
-		 * If we need to start a new thread and it is maximum allowed
-		 * already running, wait until some become idle.
-		 */
-		while (ctx-&gt;num_active &gt;= ctx-&gt;max_threads)
-			sleep(0);
-
-		/* Start a new thread if needed */
-		if (ctx-&gt;num_idle == 0) {
-			/*
-			 * Sequence is important here: first num_idle must
-			 * be incremented, and then new thread started.
-			 * Otherwise, worker thread may do num_idle--
-			 * before master does num_idle++, breaking the assert.
-			 * Thanks to blavier@adeneo.eu for helping to
-			 * debug this.
-			 * TODO: add error check for start_thread().
-			 */
-			ctx-&gt;num_idle++;
-			start_thread((mg_thread_func_t) worker_loop, ctx);
-		}
+	    &amp;accepted.rsa.u.sa, &amp;accepted.rsa.len)) == INVALID_SOCKET)
+		return;
 
-		/* Send accepted socket to some of the worker threads */
-		accepted.is_ssl = listener-&gt;is_ssl;
-		if (write(ctx-&gt;ctl[1], (void *) &amp;accepted,
-		    sizeof(accepted)) != sizeof(accepted)) {
-			cry(NULL, &quot;%s control socket error: %s&quot;,
-			    __func__, strerror(errno));
-			(void) closesocket(accepted.sock);
-		}
+	lock_option(ctx, OPT_ACL);
+	if (ctx-&gt;options[OPT_ACL] != NULL &amp;&amp;
+	    !check_acl(ctx, ctx-&gt;options[OPT_ACL], &amp;accepted.rsa)) {
+		cry(fc(ctx), &quot;%s: %s is not allowed to connect&quot;,
+		    __func__, inet_ntoa(accepted.rsa.u.sin.sin_addr));
+		(void) closesocket(accepted.sock);
+		unlock_option(ctx, OPT_ACL);
+		return;
 	}
-}
+	unlock_option(ctx, OPT_ACL);
 
-static void
-wakeup_worker_threads(const struct mg_context *ctx, int num_wakeups)
-{
-	struct socket	fake;
-	int		i;
-
-	/* Send INVALID_SOCKET to threads */
-	fake.sock = INVALID_SOCKET;
-	for (i = 0; i &lt; num_wakeups; i++)
-		(void) write(ctx-&gt;ctl[1], (void *) &amp;fake, sizeof(fake));
+	/* Put accepted socket structure into the queue */
+	DEBUG_TRACE((DEBUG_MGS_PREFIX &quot;%s: accepted socket %d&quot;,
+	    __func__, accepted.sock));
+	accepted.is_ssl = listener-&gt;is_ssl;
+	put_socket(ctx, &amp;accepted);
 }
 
 static void
-listening_loop(struct mg_context *ctx)
+master_thread(struct mg_context *ctx)
 {
 	fd_set		read_set;
 	struct timeval	tv;
@@ -3838,10 +4606,10 @@ listening_loop(struct mg_context *ctx)
 		max_fd = -1;
 
 		/* Add listening sockets to the read set */
-		mg_lock(ctx);
+		lock_option(ctx, OPT_PORTS);
 		for (i = 0; i &lt; ctx-&gt;num_listeners; i++)
 			add_to_set(ctx-&gt;listeners[i].sock, &amp;read_set, &amp;max_fd);
-		mg_unlock(ctx);
+		unlock_option(ctx, OPT_PORTS);
 
 		tv.tv_sec = 1;
 		tv.tv_usec = 0;
@@ -3854,19 +4622,16 @@ listening_loop(struct mg_context *ctx)
 			 * (at least on my Windows XP Pro). So in this case,
 			 * we sleep here.
 			 */
-			Sleep(1000);
+			sleep(1);
 #endif /* _WIN32 */
 		} else {
-			mg_lock(ctx);
+			lock_option(ctx, OPT_PORTS);
 			for (i = 0; i &lt; ctx-&gt;num_listeners; i++)
 				if (FD_ISSET(ctx-&gt;listeners[i].sock, &amp;read_set))
 					accept_new_connection(
 					    ctx-&gt;listeners + i, ctx);
-			mg_unlock(ctx);
+			unlock_option(ctx, OPT_PORTS);
 		}
-
-		/* Send wakeups to idle threads only */
-		wakeup_worker_threads(ctx, ctx-&gt;num_idle);
 	}
 
 	/* Stop signal received: somebody called mg_stop. Quit. */
@@ -3878,22 +4643,24 @@ mg_stop(struct mg_context *ctx)
 {
 	ctx-&gt;stop_flag = 1;
 
-	wakeup_worker_threads(ctx, ctx-&gt;num_active + ctx-&gt;num_idle);
-
 	/* Wait until mg_fini() stops */
 	while (ctx-&gt;stop_flag != 2)
-		sleep(0);
-
-	assert(ctx-&gt;num_active == 0);
-	assert(ctx-&gt;num_idle == 0);
+		(void) sleep(1);
 
+	assert(ctx-&gt;num_threads == 0);
 	free(ctx);
+
+#if defined(_WIN32)
+	(void) WSACleanup();
+#endif /* _WIN32 */
 }
 
 struct mg_context *
 mg_start(void)
 {
 	struct mg_context	*ctx;
+	const struct mg_option	*option;
+	char			web_root[FILENAME_MAX];
 	int			i;
 
 #if defined(_WIN32)
@@ -3902,37 +4669,40 @@ mg_start(void)
 #endif /* _WIN32 */
 
 	if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) {
-		cry(NULL, &quot;cannot allocate mongoose context&quot;);
-		return (NULL);
-	} else if (pipe(ctx-&gt;ctl) != 0) {
-		cry(NULL, &quot;Cannot create control pipe: %d&quot;, ERRNO);
-		free(ctx);
+		cry(fc(ctx), &quot;cannot allocate mongoose context&quot;);
 		return (NULL);
 	}
 
+	ctx-&gt;error_log = stderr;
+	mg_set_log_callback(ctx, builtin_error_log);
+
 	/* Initialize options. First pass: set default option values */
-	for (i = 0; known_options[i].name != NULL; i++)
-		ctx-&gt;options[setters[i].context_index] =
-			known_options[i].default_value  == NULL ?
-			NULL : mg_strdup(known_options[i].default_value);
+	for (option = known_options; option-&gt;name != NULL; option++)
+		ctx-&gt;options[option-&gt;index] = option-&gt;default_value == NULL ?
+			NULL : mg_strdup(option-&gt;default_value);
 
 	/* Call setter functions */
-	for (i = 0; known_options[i].name != NULL; i++)
-		if (setters[i].setter &amp;&amp;
-		    ctx-&gt;options[setters[i].context_index] != NULL)
-			if (setters[i].setter(ctx,
-			    ctx-&gt;options[setters[i].context_index]) == FALSE) {
+	for (option = known_options; option-&gt;name != NULL; option++)
+		if (option-&gt;setter != NULL &amp;&amp;
+		    ctx-&gt;options[option-&gt;index] != NULL)
+			if (option-&gt;setter(ctx,
+			    ctx-&gt;options[option-&gt;index]) == FALSE) {
 				mg_fini(ctx);
 				return (NULL);
 			}
 
 	/* Initial document root is set to current working directory */
-	if (ctx-&gt;options[OPT_ROOT] == NULL)
-		ctx-&gt;options[OPT_ROOT] = getcwd(NULL, 0);
+	if (ctx-&gt;options[OPT_ROOT] == NULL) {
+		if (mg_getcwd(web_root, sizeof(web_root)) == NULL) {
+			cry(fc(ctx), &quot;%s: getcwd: %s&quot;,
+			    __func__, strerror(errno));
+			mg_strlcpy(web_root, &quot;.&quot;, sizeof(web_root));
+		}
+		ctx-&gt;options[OPT_ROOT] = mg_strdup(web_root);
+	}
 
-#if 0
-	tm-&gt;tm_gmtoff - 3600 * (tm-&gt;tm_isdst &gt; 0 ? 1 : 0);
-#endif
+	DEBUG_TRACE((DEBUG_MGS_PREFIX &quot;%s: root [%s]&quot;,
+	    __func__, ctx-&gt;options[OPT_ROOT]));
 
 #if !defined(_WIN32)
 	/*
@@ -3942,9 +4712,18 @@ mg_start(void)
 	(void) signal(SIGPIPE, SIG_IGN);
 #endif /* _WIN32 */
 
+	/* Initialize options mutexes */
+	for (i = 0; i &lt; NUM_OPTIONS; i++)
+		(void) pthread_mutex_init(&amp;ctx-&gt;opt_mutex[i], NULL);
+
+	(void) pthread_mutex_init(&amp;ctx-&gt;thr_mutex, NULL);
+	(void) pthread_mutex_init(&amp;ctx-&gt;bind_mutex, NULL);
+	(void) pthread_cond_init(&amp;ctx-&gt;thr_cond, NULL);
+	(void) pthread_cond_init(&amp;ctx-&gt;empty_cond, NULL);
+	(void) pthread_cond_init(&amp;ctx-&gt;full_cond, NULL);
+
 	/* Start master (listening) thread */
-	(void) pthread_mutex_init(&amp;ctx-&gt;opt_mutex, NULL);
-	start_thread((mg_thread_func_t) listening_loop, ctx);
+	start_thread(ctx, (mg_thread_func_t) master_thread, ctx);
 
 	return (ctx);
 }</diff>
      <filename>mongoose.c</filename>
    </modified>
    <modified>
      <diff>@@ -19,7 +19,7 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  *
- * $Id: mongoose.h 154 2008-12-22 20:14:28Z valenok $
+ * $Id: mongoose.h 404 2009-05-28 10:05:48Z valenok $
  */
 
 #ifndef MONGOOSE_HEADER_INCLUDED
@@ -32,6 +32,7 @@ extern &quot;C&quot; {
 struct mg_context;	/* Handle for the HTTP service itself	*/
 struct mg_connection;	/* Handle for the individual connection	*/
 
+
 /*
  * This structure contains full information about the HTTP request.
  * It is passed to the user-specified callback function as a parameter.
@@ -49,88 +50,197 @@ struct mg_request_info {
 	int	http_version_minor;
 	int	status_code;		/* HTTP status code	*/
 	int	num_headers;		/* Number of headers	*/
-#define	MAX_HTTP_HEADERS	64
 	struct mg_header {
 		char	*name;		/* HTTP header name	*/
 		char	*value;		/* HTTP header value	*/
-	} http_headers[MAX_HTTP_HEADERS];
+	} http_headers[64];		/* Maximum 64 headers	*/
 };
 
+
 /*
- * Mongoose configuration option.
- * Array of those is returned by mg_get_option_list().
+ * User-defined callback function prototype for URI handling, error handling,
+ * or logging server messages.
  */
-struct mg_option {
-	char	*name;
-	char	*description;
-	char	*default_value;
-};
+typedef void (*mg_callback_t)(struct mg_connection *,
+		const struct mg_request_info *info, void *user_data);
+
 
 /*
- * Functions dealing with initialization, starting and stopping Mongoose
- *
- * mg_start		Start serving thread. Return server context.
- * mg_stop		Stop server thread, and release the context.
- * mg_set_option	Set an option for the running context.
- * mg_get_option	Get an option for the running context.
- * mg_get_option_list	Get a list of all known options.
- * mg_handle_uri	Associate user function with paticular URI.
- *			'*' in regex matches zero or more characters.
- * mg_handle_error_code	Associate user function with HTTP error code.
- *			Passing 0 as error code binds function to all codes.
- *			Error code is passed as status_code in request info.
- * mg_protect_uri	Similar to &quot;protect&quot; option, but uses a user
- *			specified function instead of the passwords file.
- *			User specified function is usual callback, which
- *			does use its third argument to pass the result back.
+ * Start the web server.
+ * This must be the first function called by the application.
+ * It creates a serving thread, and returns a context structure that
+ * can be used to alter the configuration, and stop the server.
  */
-
 struct mg_context *mg_start(void);
+
+
+/*
+ * Stop the web server.
+ * Must be called last, when an application wants to stop the web server and
+ * release all associated resources. This function blocks until all Mongoose
+ * threads are stopped. Context pointer becomes invalid.
+ */
 void mg_stop(struct mg_context *);
-const struct mg_option *mg_get_option_list(void);
-const char *mg_get_option(struct mg_context *, const char *);
-int mg_set_option(struct mg_context *, const char *, const char *);
 
-typedef void (*mg_callback_t)(struct mg_connection *,
-		const struct mg_request_info *info, void *user_data);
 
-void mg_bind_to_uri(struct mg_context *ctx, const char *uri_regex,
+/*
+ * Return current value of a particular option.
+ */
+const char *mg_get_option(const struct mg_context *, const char *option_name);
+
+
+/*
+ * Set a value for a particular option.
+ * Mongoose makes an internal copy of the option value string, which must be
+ * valid nul-terminated ASCII or UTF-8 string. It is safe to change any option
+ * at any time. The order of setting various options is also irrelevant with
+ * one exception: if &quot;ports&quot; option contains SSL listening ports, a &quot;ssl_cert&quot;
+ * option must be set BEFORE the &quot;ports&quot; option.
+ * Return value:
+ *	-1 if option is unknown
+ *	0  if mg_set_option() failed
+ *	1  if mg_set_option() succeeded 
+ */
+int mg_set_option(struct mg_context *, const char *opt_name, const char *value);
+
+
+/*
+ * Add, edit or delete the entry in the passwords file.
+ * This function allows an application to manipulate .htpasswd files on the
+ * fly by adding, deleting and changing user records. This is one of the two
+ * ways of implementing authentication on the server side. For another,
+ * cookie-based way please refer to the examples/authentication.c in the
+ * source tree.
+ * If password is not NULL, entry is added (or modified if already exists).
+ * If password is NULL, entry is deleted. Return:
+ *	1 on success
+ *	0 on error 
+ */
+int mg_modify_passwords_file(struct mg_context *ctx, const char *file_name,
+		const char *user_name, const char *password);
+
+
+/*
+ * Register URI handler.
+ * It is possible to handle many URIs if using * in the uri_regex, which
+ * matches zero or more characters. user_data pointer will be passed to the
+ * handler as a third parameter. If func is NULL, then the previously installed
+ * handler for this uri_regex is removed.
+ */
+void mg_set_uri_callback(struct mg_context *ctx, const char *uri_regex,
 		mg_callback_t func, void *user_data);
-void mg_bind_to_error_code(struct mg_context *ctx, int error_code,
+
+
+/*
+ * Register HTTP error handler.
+ * An application may use that function if it wants to customize the error
+ * page that user gets on the browser (for example, 404 File Not Found message).
+ * It is possible to specify a error handler for all errors by passing 0 as
+ * error_code. That '0' error handler must be set last, if more specific error
+ * handlers are also used. The actual error code value can be taken from
+ * the request info structure that is passed to the callback.
+ */
+void mg_set_error_callback(struct mg_context *ctx, int error_code,
 		mg_callback_t func, void *user_data);
-void mg_protect_uri(struct mg_context *ctx, const char *uri_regex,
+
+
+/*
+ * Register authorization handler.
+ * This function provides a mechanism to implement custom authorization,
+ * for example cookie based (look at examples/authorization.c).
+ * The callback function must analyze the request, and make its own judgement
+ * on wether it should be authorized or not. After the decision is made, a
+ * callback must call mg_authorize() if the request is authorized.
+ */
+void mg_set_auth_callback(struct mg_context *ctx, const char *uri_regex,
 		mg_callback_t func, void *user_data);
 
+
 /*
- * Needed only if SSL certificate asks for a password.
- * Instead of prompting for a password, specified function will be called.
+ * Register log handler.
+ * By default, Mongoose logs all error messages to stderr. If &quot;error_log&quot;
+ * option is specified, the errors are written in the specified file. However,
+ * if an application registers its own log handler, Mongoose will not log
+ * anything but call the handler function, passing an error message as
+ * &quot;user_data&quot; callback argument.
+ */
+void mg_set_log_callback(struct mg_context *ctx, mg_callback_t func);
+
+
+/*
+ * Register SSL password handler.
+ * This is needed only if SSL certificate asks for a password. Instead of
+ * prompting for a password on a console a specified function will be called.
  */
 typedef int (*mg_spcb_t)(char *buf, int num, int w, void *key);
 void mg_set_ssl_password_callback(struct mg_context *ctx, mg_spcb_t func);
 
+
 /*
- * Functions that can be used within the user URI callback
- *
- * mg_write	Send data to the remote end.
- * mg_printf	Send data, using printf() semantics.
- * mg_get_header Helper function to get HTTP header value
- * mg_get_var	Helper function to get form variable value.
- *		Returned value must be free-d by the caller.
+ * Send data to the browser.
+ * Return number of bytes sent. If the number of bytes sent is less then
+ * requested or equals to -1, network error occured, usually meaning the
+ * remote side has closed the connection.
  */
 int mg_write(struct mg_connection *, const void *buf, int len);
+
+
+/*
+ * Send data to the browser using printf() semantics.
+ * Works exactly like mg_write(), but allows to do message formatting.
+ * Note that mg_printf() uses internal buffer of size MAX_REQUEST_SIZE
+ * (8 Kb by default) as temporary message storage for formatting. Do not
+ * print data that is bigger than that, otherwise it will be truncated.
+ * Return number of bytes sent.
+ */
 int mg_printf(struct mg_connection *, const char *fmt, ...);
+
+
+/*
+ * Get the value of particular HTTP header.
+ * This is a helper function. It traverses request_info-&gt;http_headers array,
+ * and if the header is present in the array, returns its value. If it is
+ * not present, NULL is returned.
+ */
 const char *mg_get_header(const struct mg_connection *, const char *hdr_name);
+
+
+/*
+ * Authorize the request.
+ * See the documentation for mg_set_auth_callback() function.
+ */
+void mg_authorize(struct mg_connection *);
+
+
+/*
+ * Get a value of particular form variable.
+ * Both query string (whatever comes after '?' in the URL) and a POST buffer
+ * are scanned. If a variable is specified in both query string and POST
+ * buffer, POST buffer wins. Return value:
+ *	NULL      if the variable is not found
+ *	non-NULL  if found. NOTE: this returned value is dynamically allocated
+ *		  and is subject to mg_free() when no longer needed. It is
+ *		  an application's responsibility to mg_free() the variable. 
+ */
 char *mg_get_var(const struct mg_connection *, const char *var_name);
 
+
 /*
- * General helper functions
- * mg_version	Return current version.
- * mg_md5	Helper function. buf must be 33 bytes in size. Expects
- *		a NULL terminated list of asciz strings.
- *		Fills buf with stringified \0 terminated MD5 hash.
+ * Free up memory returned by mg_get_var().
+ */
+void mg_free(char *var);
+
+
+/*
+ * Return Mongoose version.
  */
 const char *mg_version(void);
-void mg_md5(char *buf, ...);
+
+
+/*
+ * Print command line usage string.
+ */
+void mg_show_usage_string(FILE *fp);
 
 #ifdef __cplusplus
 }</diff>
      <filename>mongoose.h</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>1496141b674e651ceaebef66cb135d2e47bd3ff0</id>
    </parent>
  </parents>
  <author>
    <name>Rama McIntosh</name>
    <email>rama@myutil.com</email>
  </author>
  <url>http://github.com/face/MongooseDaemon/commit/5d4ef0d2f57f49fea71477de85e904682c19dc8f</url>
  <id>5d4ef0d2f57f49fea71477de85e904682c19dc8f</id>
  <committed-date>2009-07-19T02:28:33-07:00</committed-date>
  <authored-date>2009-07-19T02:28:33-07:00</authored-date>
  <message>Upgraded mongoose to mongoose-2.8 from http://code.google.com/p/mongoose/)</message>
  <tree>e4f2b8e409fddbd5dc220b37c213f83e39a9f0d3</tree>
  <committer>
    <name>Rama McIntosh</name>
    <email>rama@myutil.com</email>
  </committer>
</commit>
