From a69a2da39dcca63ad1a77553d3bc3a05f42e0e66 Mon Sep 17 00:00:00 2001 From: Sergey Lyubka Date: Mon, 3 May 2010 21:46:42 +0100 Subject: [PATCH] Initial import - converting from Subversion. --- Makefile | 97 + bindings/csharp/example.cs | 52 + bindings/csharp/mongoose.cs | 134 + bindings/python/example.py | 49 + bindings/python/mongoose.py | 177 ++ examples/Makefile | 8 + examples/authentication.c | 106 + examples/chat.c | 254 ++ examples/example.c | 302 +++ examples/html/index.html | 68 + examples/html/jquery.js | 154 ++ examples/html/login.html | 45 + examples/html/main.js | 86 + examples/html/style.css | 131 + main.c | 221 ++ mongoose.1 | 184 ++ mongoose.c | 4553 ++++++++++++++++++++++++++++++++++ mongoose.h | 242 ++ test/.leading.dot.txt | 1 + test/bad.cgi | 5 + test/embed.c | 197 ++ test/env.cgi | 50 + test/exploit.pl | 69 + test/hello.cgi | 6 + test/hello.txt | 1 + test/passfile | 3 + test/sh.cgi | 6 + test/ssi1.shtml | 5 + test/ssi2.shtml | 5 + test/ssi3.shtml | 5 + test/ssi4.shtml | 5 + test/ssi5.shtml | 5 + test/ssi6.shtml | 5 + test/ssi7.shtml | 6 + test/ssi8.shtml | 1 + test/ssi9.shtml | 3 + test/test.pl | 458 ++++ test/test_all_build_flags.pl | 28 + win32/README.txt | 4 + win32/dll.def | 20 + win32/installer.nsi | 69 + win32/libeay32.dll | Bin 0 -> 716800 bytes win32/mongoose.conf | 31 + win32/srvany.exe | Bin 0 -> 8192 bytes win32/ssl_cert.pem | 50 + win32/ssleay32.dll | Bin 0 -> 155648 bytes 46 files changed, 7901 insertions(+) create mode 100644 Makefile create mode 100644 bindings/csharp/example.cs create mode 100644 bindings/csharp/mongoose.cs create mode 100644 bindings/python/example.py create mode 100644 bindings/python/mongoose.py create mode 100644 examples/Makefile create mode 100644 examples/authentication.c create mode 100644 examples/chat.c create mode 100644 examples/example.c create mode 100644 examples/html/index.html create mode 100644 examples/html/jquery.js create mode 100644 examples/html/login.html create mode 100644 examples/html/main.js create mode 100644 examples/html/style.css create mode 100644 main.c create mode 100644 mongoose.1 create mode 100644 mongoose.c create mode 100644 mongoose.h create mode 100644 test/.leading.dot.txt create mode 100755 test/bad.cgi create mode 100644 test/embed.c create mode 100755 test/env.cgi create mode 100644 test/exploit.pl create mode 100755 test/hello.cgi create mode 100644 test/hello.txt create mode 100644 test/passfile create mode 100755 test/sh.cgi create mode 100644 test/ssi1.shtml create mode 100644 test/ssi2.shtml create mode 100644 test/ssi3.shtml create mode 100644 test/ssi4.shtml create mode 100644 test/ssi5.shtml create mode 100644 test/ssi6.shtml create mode 100644 test/ssi7.shtml create mode 100644 test/ssi8.shtml create mode 100644 test/ssi9.shtml create mode 100644 test/test.pl create mode 100644 test/test_all_build_flags.pl create mode 100644 win32/README.txt create mode 100644 win32/dll.def create mode 100644 win32/installer.nsi create mode 100644 win32/libeay32.dll create mode 100644 win32/mongoose.conf create mode 100644 win32/srvany.exe create mode 100644 win32/ssl_cert.pem create mode 100644 win32/ssleay32.dll 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