commit a69a2da39dcca63ad1a77553d3bc3a05f42e0e66 Author: Sergey Lyubka Date: Mon May 3 21:46:42 2010 +0100 Initial import - converting from Subversion. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..e0a222cc --- /dev/null +++ b/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 diff --git a/bindings/csharp/example.cs b/bindings/csharp/example.cs new file mode 100644 index 00000000..7e294fc7 --- /dev/null +++ b/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("Calling Info"); + conn.write("

Request: " + ri.request_method + "

"); + conn.write("

URI: " + ri.uri + "

"); + conn.write("

Query: " + ri.query_string + "

"); + if (ri.post_data_len > 0) conn.write("

Post(" + ri.post_data_len + ")[@" + ri.post_data + "]: '" + Marshal.PtrToStringAnsi(ri.post_data) + "'

"); + conn.write("

User:" + ri.remote_user + "

"); + conn.write("

IP: " + ri.remote_ip + "

"); + conn.write("

HTTP: " + ri.http_version + "

"); + conn.write("

Port: " + ri.remote_port + "

"); + conn.write("

NUM Headers: " + ri.num_headers + "

"); + for (int i = 0; i < Math.Min(64, ri.num_headers); i++) + { + conn.write("

" + i + ":" + Marshal.PtrToStringAnsi(ri.http_headers[i].name) + + ":" + Marshal.PtrToStringAnsi(ri.http_headers[i].value) + "

"); + } + conn.write(""); + } + + 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(); + } +} diff --git a/bindings/csharp/mongoose.cs b/bindings/csharp/mongoose.cs new file mode 100644 index 00000000..efdbb6b1 --- /dev/null +++ b/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); + } +} diff --git a/bindings/python/example.py b/bindings/python/example.py new file mode 100644 index 00000000..ee584668 --- /dev/null +++ b/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 '') + 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 diff --git a/bindings/python/mongoose.py b/bindings/python/mongoose.py new file mode 100644 index 00000000..2f2557eb --- /dev/null +++ b/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)) diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 00000000..b01a969d --- /dev/null +++ b/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) diff --git a/examples/authentication.c b/examples/authentication.c new file mode 100644 index 00000000..d0f419aa --- /dev/null +++ b/examples/authentication.c @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#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)
" + "
" + "Name:
" + "Password:
" + "" + "
"); + } + + 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); +} diff --git a/examples/chat.c b/examples/chat.c new file mode 100644 index 00000000..c0784bba --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +#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 diff --git a/examples/example.c b/examples/example.c new file mode 100644 index 00000000..a12d61e9 --- /dev/null +++ b/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 +#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 +#include +#include +#define ALIAS_URI "/my_etc" +#define ALIAS_DIR "/etc/" +#endif + +#ifndef _WIN32_WCE /* Some ANSI #includes are not available on Windows CE */ +#include +#include +#include +#endif + +#include +#include +#include +#include + +#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" + "

Welcome to embedded example of Mongoose"); + mg_printf(conn, " v. %s

    ", mg_version()); + + mg_printf(conn, "
  • REQUEST_METHOD: %s " + "REQUEST_URI: \"%s\" QUERY_STRING: \"%s\"" + " REMOTE_ADDR: %lx REMOTE_USER: \"(null)\"
    ", + request_info->request_method, request_info->uri, + request_info->query_string ? request_info->query_string : "(null)", + request_info->remote_ip); + mg_printf(conn, "
  • Internal int variable value: %d", + * (int *) user_data); + + mg_printf(conn, "%s", + "
    Enter new value: " + "" + "
    "); + mg_printf(conn, "%s", + "
    Enter new value: " + "" + "
    "); + + mg_printf(conn, "%s", + "
  • " + "Protected page (guest:guest)
    " + "
  • Output lots of data
    " + "
  • Aliased " + ALIAS_DIR " directory
    "); + mg_printf(conn, "%s", + "
  • Regular file (Makefile)
    " + "
  • SSI file " + "(ssi_test.shtml)
    " + "
  • Wildcard URI example
    " + "
  • Custom 404 handler
    "); + + host = mg_get_header(conn, "Host"); + mg_printf(conn, "
  • 'Host' header value: [%s]
    ", + host ? host : "NOT SET"); + + mg_printf(conn, "
  • Upload file example. " + "
    " + "
    "); + + mg_printf(conn, "%s", ""); +} + +/* + * 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", ""); + mg_printf(conn, "%s", "

    This is a protected page"); +} + +/* + * 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", ""); + mg_printf(conn, "%s", "

    Hi. This is a wildcard uri handler" + "for the URI /users/*/

    "); + mg_printf(conn, "

    URI: %s

    ", 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); +} diff --git a/examples/html/index.html b/examples/html/index.html new file mode 100644 index 00000000..c0e4ef8b --- /dev/null +++ b/examples/html/index.html @@ -0,0 +1,68 @@ + + + + + Mongoose chat server + + + + + + + + + +
    +
    + + + +
    + +
    +
    + Main room +
    +
    +
    + + + Type your message here and press enter +
    +
    +
    + + + +
    +
    + + + + + diff --git a/examples/html/jquery.js b/examples/html/jquery.js new file mode 100644 index 00000000..7c243080 --- /dev/null +++ b/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)[^>]*$|^#([\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
    a"; +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="";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-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=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=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-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;j0){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":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=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 hl[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=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";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=""; +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="

    ";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="
    ";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=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},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-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=/"},F={option:[1,""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_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
    ","
    "];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;b0||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;e0?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]===""&&!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=//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("
    ").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").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=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.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); diff --git a/examples/html/login.html b/examples/html/login.html new file mode 100644 index 00000000..94162f9b --- /dev/null +++ b/examples/html/login.html @@ -0,0 +1,45 @@ + + + + + Mongoose chat: login + + + + + + + +
    +

    Mongoose chat server login

    +
    + 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". +
    +
    +
    +
    +
    + +
    +
    + + diff --git a/examples/html/main.js b/examples/html/main.js new file mode 100644 index 00000000..135d352d --- /dev/null +++ b/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 = $('
    ').addClass('message-row').appendTo('#mml'); + var timestamp = (new Date(entry.timestamp * 1000)).toLocaleTimeString(); + $('').addClass('message-timestamp').html( + '[' + timestamp + ']').prependTo(row); + $('').addClass('message-user').html(entry.user + ':').appendTo(row); + $('').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 diff --git a/examples/html/style.css b/examples/html/style.css new file mode 100644 index 00000000..630d9b7e --- /dev/null +++ b/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; +} diff --git a/main.c b/main.c new file mode 100644 index 00000000..9a4e8f1d --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +#include "mongoose.h" + +#ifdef _WIN32 +#include +#include +#define DIRSEP '\\' +#define snprintf _snprintf +#if !defined(__LCC__) +#define strdup(x) _strdup(x) +#endif /* !MINGW */ +#define sleep(x) Sleep((x) * 1000) +#else +#include +#include /* 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); +} diff --git a/mongoose.1 b/mongoose.1 new file mode 100644 index 00000000..787f7714 --- /dev/null +++ b/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 + Execute shell command. +.It + File path must be relative to the current document. +.It + File path must be relative to the document root. +.It + 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 . diff --git a/mongoose.c b/mongoose.c new file mode 100644 index 00000000..2332f21f --- /dev/null +++ b/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 +#include +#include +#include +#include +#endif /* !_WIN32_WCE */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) /* Windows specific #includes and #defines */ +#define _WIN32_WINNT 0x0400 /* To make it link in VS2005 */ +#include + +#ifndef _WIN32_WCE +#include +#include +#include +#else /* _WIN32_WCE */ +/* Windows CE-specific definitions */ +#include +#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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#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(¤t_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<>(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, + "%s%s" + " %s  %s\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, + "Index of %s" + "" + "

    Index of %s

    "
    +	    ""
    +	    ""
    +	    ""
    +	    "",
    +	    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,
    +	    ""
    +	    "\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", "
    NameModifiedSize

    %s %s  %s
    "); + 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, " +ssi_end +
    diff --git a/test/ssi2.shtml b/test/ssi2.shtml new file mode 100644 index 00000000..787f3020 --- /dev/null +++ b/test/ssi2.shtml @@ -0,0 +1,5 @@ +
    +ssi_begin
    +
    +ssi_end
    +
    diff --git a/test/ssi3.shtml b/test/ssi3.shtml new file mode 100644 index 00000000..c7beb0f3 --- /dev/null +++ b/test/ssi3.shtml @@ -0,0 +1,5 @@ +
    +ssi_begin
    +
    +ssi_end
    +
    diff --git a/test/ssi4.shtml b/test/ssi4.shtml new file mode 100644 index 00000000..5ad63329 --- /dev/null +++ b/test/ssi4.shtml @@ -0,0 +1,5 @@ +
    +ssi_begin
    +
    +ssi_end
    +
    diff --git a/test/ssi5.shtml b/test/ssi5.shtml new file mode 100644 index 00000000..de3e49b8 --- /dev/null +++ b/test/ssi5.shtml @@ -0,0 +1,5 @@ +
    +ssi_begin
    +
    +ssi_end
    +
    diff --git a/test/ssi6.shtml b/test/ssi6.shtml new file mode 100644 index 00000000..2fd8b510 --- /dev/null +++ b/test/ssi6.shtml @@ -0,0 +1,5 @@ +
    +ssi_begin
    +
    +ssi_end
    +
    diff --git a/test/ssi7.shtml b/test/ssi7.shtml new file mode 100644 index 00000000..44a5add5 --- /dev/null +++ b/test/ssi7.shtml @@ -0,0 +1,6 @@ + +
    +ssi_begin
    +
    +ssi_end
    +
    diff --git a/test/ssi8.shtml b/test/ssi8.shtml new file mode 100644 index 00000000..ec7f8b3b --- /dev/null +++ b/test/ssi8.shtml @@ -0,0 +1 @@ + diff --git a/test/ssi9.shtml b/test/ssi9.shtml new file mode 100644 index 00000000..92030759 --- /dev/null +++ b/test/ssi9.shtml @@ -0,0 +1,3 @@ +ssi_begin + +ssi_end diff --git a/test/test.pl b/test/test.pl new file mode 100644 index 00000000..263fd87e --- /dev/null +++ b/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 = (); + 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 = ; + 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"; diff --git a/test/test_all_build_flags.pl b/test/test_all_build_flags.pl new file mode 100644 index 00000000..5aef1462 --- /dev/null +++ b/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"; diff --git a/win32/README.txt b/win32/README.txt new file mode 100644 index 00000000..398a7036 --- /dev/null +++ b/win32/README.txt @@ -0,0 +1,4 @@ +For detailed documentation, please visit +http://code.google.com/p/mongoose/wiki/WindowsUsage + +Thanks for using Mongoose! diff --git a/win32/dll.def b/win32/dll.def new file mode 100644 index 00000000..30e026b5 --- /dev/null +++ b/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 diff --git a/win32/installer.nsi b/win32/installer.nsi new file mode 100644 index 00000000..343ad016 --- /dev/null +++ b/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 diff --git a/win32/libeay32.dll b/win32/libeay32.dll new file mode 100644 index 00000000..f30465a0 Binary files /dev/null and b/win32/libeay32.dll differ diff --git a/win32/mongoose.conf b/win32/mongoose.conf new file mode 100644 index 00000000..001f790f --- /dev/null +++ b/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 diff --git a/win32/srvany.exe b/win32/srvany.exe new file mode 100644 index 00000000..c6c106b3 Binary files /dev/null and b/win32/srvany.exe differ diff --git a/win32/ssl_cert.pem b/win32/ssl_cert.pem new file mode 100644 index 00000000..f7e15a0e --- /dev/null +++ b/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----- diff --git a/win32/ssleay32.dll b/win32/ssleay32.dll new file mode 100644 index 00000000..132bbfeb Binary files /dev/null and b/win32/ssleay32.dll differ