Merge pull request #1 from valenok/master

update
This commit is contained in:
jmucchiello committed 2013-02-10 11:51:24 -08:00
commit 9178fbd153
22 files changed
+148354 -1382

No files matched your search

+103 -102
View File
@@ -1,12 +1,10 @@
# 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|cygwin)"
# Possible COPT values: (in brackets are rough numbers for 'gcc -O2' on i386)
# This Makefile is part of Mongoose web server project,
# https://github.com/valenok/mongoose
#
# Example custom build:
# COPT="-g -O0 -DNO_SSL_DL -DUSE_LUA -llua -lcrypto -lssl" make linux
#
# Flags are:
# -DHAVE_MD5 - use system md5 library (-2kb)
# -DNDEBUG - strip off all debug code (-5kb)
# -DDEBUG - build debug version (very noisy) (+7kb)
@@ -18,61 +16,12 @@ all:
# -DCRYPTO_LIB=\"libcrypto.so.<version>\" - use system versioned CRYPTO so
# -DUSE_LUA - embed Lua in Mongoose (+100kb)
##########################################################################
### UNIX build: linux, bsd, mac, rtems
##########################################################################
PROG = mongoose
CFLAGS = -std=c99 -O2 -W -Wall -pedantic -pthread $(COPT)
# To build with Lua, download and unzip Lua 5.2.1 source code into the
# mongoose directory, and then add $(LUA_FLAGS) to CFLAGS below
# mongoose directory, and then add $(LUA_SOURCES) to CFLAGS
LUA = lua-5.2.1/src
LUA_FLAGS = -DUSE_LUA -I$(LUA) -L$(LUA) -llua -lm
CFLAGS = -std=c99 -O2 -W -Wall -pedantic $(COPT)
LIB = lib$(PROG).so$(MONGOOSE_LIB_SUFFIX)
# Make sure that the compiler flags come last in the compilation string.
# If not so, this can break some on some Linux distros which use
# "-Wl,--as-needed" turned on by default in cc command.
# Also, this is turned in many other distros in static linkage builds.
linux:
$(CC) mongoose.c -shared -fPIC -fpic -o $(LIB) -Wl,-soname,$(LIB) -ldl -pthread $(CFLAGS)
$(CC) mongoose.c main.c -o $(PROG) -ldl -pthread $(CFLAGS)
bsd:
$(CC) mongoose.c -shared -pthread -fpic -fPIC -o $(LIB) $(CFLAGS)
$(CC) mongoose.c main.c -pthread -o $(PROG) $(CFLAGS)
mac:
$(CC) mongoose.c -pthread -o $(LIB) -flat_namespace -bundle -undefined suppress $(CFLAGS)
$(CC) mongoose.c main.c -DUSE_COCOA -pthread $(CFLAGS) -framework Cocoa -ObjC -arch i386 -arch x86_64 -o $(PROG)
V=`perl -lne '/define\s+MONGOOSE_VERSION\s+"(\S+)"/ and print $$1' mongoose.c`; DIR=dmg/Mongoose.app && rm -rf $$DIR && mkdir -p $$DIR/Contents/{MacOS,Resources} && install -m 644 build/mongoose_*.png $$DIR/Contents/Resources/ && install -m 644 build/Info.plist $$DIR/Contents/ && install -m 755 $(PROG) $$DIR/Contents/MacOS/ && ln -fs /Applications dmg/ ; hdiutil create $(PROG)_$$V.dmg -volname "Mongoose $$V" -srcfolder dmg -ov #; rm -rf dmg
solaris:
$(CC) mongoose.c -pthread -lnsl -lsocket -fpic -fPIC -shared -o $(LIB) $(CFLAGS)
$(CC) mongoose.c main.c -pthread -lnsl -lsocket -o $(PROG) $(CFLAGS)
##########################################################################
### WINDOWS build: Using Visual Studio or Mingw
##########################################################################
# Using Visual Studio 6.0. To build Mongoose:
# o Set MSVC variable below to where VS 6.0 is installed on your system
# o Run "PATH_TO_VC6\bin\nmake windows"
MSVC = e:/vc6
CYA = e:/cyassl-2.0.0rc2
#DBG = /Zi /DDEBUG /Od
DBG = /DNDEBUG /O1
CL = $(MSVC)/bin/cl /MD /TC /nologo $(DBG) /Gz /W3 /DNO_SSL_DL \
/I$(MSVC)/include /DUSE_LUA /I$(LUA)
GUILIB= user32.lib shell32.lib
LINK = /link /incremental:no /libpath:$(MSVC)/lib /machine:IX86 \
/subsystem:windows ws2_32.lib advapi32.lib cyassl.lib lua.lib
CYAFL = /c /I $(CYA)/include -I $(CYA)/include/openssl /I$(MSVC)/INCLUDE \
/I $(CYA)/ctaocrypt/include /D _LIB /D OPENSSL_EXTRA
LUA_SOURCES = $(LUA)/lapi.c $(LUA)/lcode.c $(LUA)/lctype.c \
$(LUA)/ldebug.c $(LUA)/ldo.c $(LUA)/ldump.c \
$(LUA)/lfunc.c $(LUA)/lgc.c $(LUA)/llex.c \
@@ -86,47 +35,104 @@ LUA_SOURCES = $(LUA)/lapi.c $(LUA)/lcode.c $(LUA)/lctype.c \
$(LUA)/loadlib.c $(LUA)/linit.c
LUA_OBJECTS = $(LUA_SOURCES:%.c=%.o)
CYA_SOURCES = $(CYA)/src/cyassl_int.c $(CYA)/src/cyassl_io.c \
$(CYA)/src/keys.c $(CYA)/src/tls.c $(CYA)/src/ssl.c \
$(CYA)/ctaocrypt/src/aes.c $(CYA)/ctaocrypt/src/arc4.c \
$(CYA)/ctaocrypt/src/asn.c $(CYA)/ctaocrypt/src/coding.c \
$(CYA)/ctaocrypt/src/ctc_asm.c $(CYA)/ctaocrypt/src/ctc_misc.c \
$(CYA)/ctaocrypt/src/cyassl_memory.c $(CYA)/ctaocrypt/src/des3.c \
$(CYA)/ctaocrypt/src/dh.c $(CYA)/ctaocrypt/src/dsa.c \
$(CYA)/ctaocrypt/src/ecc.c $(CYA)/ctaocrypt/src/hc128.c \
$(CYA)/ctaocrypt/src/hmac.c $(CYA)/ctaocrypt/src/integer.c \
$(CYA)/ctaocrypt/src/md4.c $(CYA)/ctaocrypt/src/md5.c \
$(CYA)/ctaocrypt/src/pwdbased.c $(CYA)/ctaocrypt/src/rabbit.c \
$(CYA)/ctaocrypt/src/random.c $(CYA)/ctaocrypt/src/ripemd.c \
$(CYA)/ctaocrypt/src/rsa.c $(CYA)/ctaocrypt/src/sha.c \
$(CYA)/ctaocrypt/src/sha256.c $(CYA)/ctaocrypt/src/sha512.c \
$(CYA)/ctaocrypt/src/tfm.c
# Using Visual Studio 6.0. To build Mongoose:
# Set MSVC variable below to where VS 6.0 is installed on your system
# Run "PATH_TO_VC6\bin\nmake windows"
MSVC = ../vc6
#DBG = /Zi /Od
DBG = /DNDEBUG /O1
CL = $(MSVC)/bin/cl /MD /TC /nologo $(DBG) /Gz /W3 \
/I$(MSVC)/include /I$(LUA) /I. /I$(YASSL) /I$(YASSL)/cyassl /GA
MSLIB = /link /incremental:no /libpath:$(MSVC)/lib /machine:IX86 \
user32.lib shell32.lib comdlg32.lib ws2_32.lib advapi32.lib
cyassl.lib:
$(CL) /Fo$(CYA)/ $(CYA_SOURCES) $(CYAFL) $(DEF)
$(MSVC)/bin/lib $(CYA)/*.obj /out:$@
# Stock windows binary builds with Lua and YASSL library.
YASSL = ../cyassl-2.4.6
YASSL_FLAGS = -I $(YASSL) -I $(YASSL)/cyassl \
-D _LIB -D OPENSSL_EXTRA -D HAVE_ERRNO_H \
-D HAVE_GETHOSTBYNAME -D HAVE_INET_NTOA -D HAVE_LIMITS_H \
-D HAVE_MEMSET -D HAVE_SOCKET -D HAVE_STDDEF_H -D HAVE_STDLIB_H \
-D HAVE_STRING_H -D HAVE_SYS_STAT_H -D HAVE_SYS_TYPES_H
YASSL_SOURCES = \
$(YASSL)/src/internal.c $(YASSL)/src/io.c $(YASSL)/src/keys.c \
$(YASSL)/src/ssl.c $(YASSL)/src/tls.c $(YASSL)/ctaocrypt/src/hmac.c \
$(YASSL)/ctaocrypt/src/random.c $(YASSL)/ctaocrypt/src/sha.c \
$(YASSL)/ctaocrypt/src/sha256.c $(YASSL)/ctaocrypt/src/logging.c \
$(YASSL)/ctaocrypt/src/error.c $(YASSL)/ctaocrypt/src/rsa.c \
$(YASSL)/ctaocrypt/src/des3.c $(YASSL)/ctaocrypt/src/asn.c \
$(YASSL)/ctaocrypt/src/coding.c $(YASSL)/ctaocrypt/src/arc4.c \
$(YASSL)/ctaocrypt/src/md4.c $(YASSL)/ctaocrypt/src/md5.c \
$(YASSL)/ctaocrypt/src/dh.c $(YASSL)/ctaocrypt/src/dsa.c \
$(YASSL)/ctaocrypt/src/pwdbased.c $(YASSL)/ctaocrypt/src/aes.c \
$(YASSL)/ctaocrypt/src/md2.c $(YASSL)/ctaocrypt/src/ripemd.c \
$(YASSL)/ctaocrypt/src/sha512.c $(YASSL)/src/sniffer.c \
$(YASSL)/ctaocrypt/src/rabbit.c $(YASSL)/ctaocrypt/src/misc.c \
$(YASSL)/ctaocrypt/src/tfm.c $(YASSL)/ctaocrypt/src/integer.c \
$(YASSL)/ctaocrypt/src/ecc.c $(YASSL)/src/ocsp.c $(YASSL)/src/crl.c \
$(YASSL)/ctaocrypt/src/hc128.c $(YASSL)/ctaocrypt/src/memory.c
lua.lib:
$(CL) /c /Fo$(LUA)/ $(LUA_SOURCES)
$(MSVC)/bin/lib $(LUA_SOURCES:%.c=%.obj) /out:$@
all:
@echo "make (linux|bsd|solaris|mac|windows|mingw|cygwin)"
windows: cyassl.lib lua.lib
# Make sure that the compiler flags come last in the compilation string.
# If not so, this can break some on some Linux distros which use
# "-Wl,--as-needed" turned on by default in cc command.
# Also, this is turned in many other distros in static linkage builds.
linux:
$(CC) mongoose.c main.c -o $(PROG) -ldl $(CFLAGS)
mac: bsd
bsd:
$(CC) mongoose.c main.c -o $(PROG) $(CFLAGS)
bsd_yassl:
$(CC) mongoose.c main.c build/lsqlite3.c build/sqlite3.c -o $(PROG) \
$(CFLAGS) -I$(LUA) -Ibuild \
$(YASSL_SOURCES) $(YASSL_FLAGS) -DNO_SSL_DL \
$(LUA_SOURCES) -DUSE_LUA -DUSE_LUA_SQLITE3 -DLUA_COMPAT_ALL
solaris:
$(CC) mongoose.c main.c -lnsl -lsocket -o $(PROG) $(CFLAGS)
# For codesign to work in non-interactive mode, unlock login keychain:
# security unlock ~/Library/Keychains/login.keychain
# See e.g. http://lists.apple.com/archives/apple-cdsa/2008/Jan/msg00027.html
cocoa:
$(CC) mongoose.c main.c build/lsqlite3.c build/sqlite3.c \
-DUSE_COCOA $(CFLAGS) -I$(LUA) -Ibuild \
$(YASSL_SOURCES) $(YASSL_FLAGS) -DNO_SSL_DL \
$(LUA_SOURCES) -DUSE_LUA -DUSE_LUA_SQLITE3 -DLUA_COMPAT_ALL \
-framework Cocoa -ObjC -arch i386 -arch x86_64 -o Mongoose
V=`perl -lne '/define\s+MONGOOSE_VERSION\s+"(\S+)"/ and print $$1' mongoose.c`; DIR=dmg/Mongoose.app && rm -rf $$DIR && mkdir -p $$DIR/Contents/{MacOS,Resources} && install -m 644 build/mongoose_*.png $$DIR/Contents/Resources/ && install -m 644 build/Info.plist $$DIR/Contents/ && install -m 755 Mongoose $$DIR/Contents/MacOS/ && ln -fs /Applications dmg/ ; hdiutil create Mongoose_$$V.dmg -volname "Mongoose $$V" -srcfolder dmg -ov #; rm -rf dmg
u:
$(CC) test/unit_test.c -o unit_test -I. -I$(LUA) $(LUA_SOURCES) \
$(CFLAGS) -g -O0
./unit_test
w:
$(CL) test/unit_test.c $(LUA_SOURCES) \
$(YASSL_SOURCES) $(YASSL_FLAGS) /DNO_SSL_DL \
$(MSLIB) /out:unit_test.exe
./unit_test.exe
windows:
$(MSVC)/bin/rc build\res.rc
$(CL) /Ibuild main.c mongoose.c /GA $(LINK) build\res.res \
$(GUILIB) /out:$(PROG).exe
$(CL) mongoose.c /GD $(LINK) /DLL /DEF:build\dll.def /out:$(PROG).dll
$(CL) main.c mongoose.c build/lsqlite3.c build/sqlite3.c \
$(YASSL_SOURCES) $(YASSL_FLAGS) /DNO_SSL_DL \
$(LUA_SOURCES) /DUSE_LUA /DUSE_LUA_SQLITE3 /DLUA_COMPAT_ALL \
$(MSLIB) build\res.res /out:$(PROG).exe /subsystem:windows
# Build for Windows under MinGW
#MINGWDBG= -DDEBUG -O0 -ggdb
MINGWDBG= -DNDEBUG -Os
MINGWOPT= -W -Wall -mthreads -Wl,--subsystem,console $(MINGWDBG) -DHAVE_STDINT $(GCC_WARNINGS) $(COPT)
#MINGWOPT= -W -Wall -mthreads -Wl,--subsystem,windows $(MINGWDBG) -DHAVE_STDINT $(GCC_WARNINGS) $(COPT)
mingw:
windres build\res.rc build\res.o
$(CC) $(MINGWOPT) mongoose.c -lws2_32 \
-shared -Wl,--out-implib=$(PROG).lib -o $(PROG).dll
$(CC) $(MINGWOPT) -build mongoose.c main.c build\res.o \
-lws2_32 -ladvapi32 -o $(PROG).exe
$(CC) $(MINGWOPT) mongoose.c main.c build\res.o \
-lws2_32 -ladvapi32 -lcomdlg32 -o $(PROG).exe
# Build for Windows under Cygwin
#CYGWINDBG= -DDEBUG -O0 -ggdb
@@ -139,22 +145,17 @@ cygwin:
$(CC) $(CYGWINOPT) -Ibuild mongoose.c main.c ./build/res.o \
-lws2_32 -ladvapi32 -o $(PROG).exe
##########################################################################
### Manuals, cleanup, test, release
##########################################################################
man:
groff -man -T ascii mongoose.1 | col -b > mongoose.txt
groff -man -T ascii mongoose.1 | 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..)
tests:
perl test/test.pl $(TEST)
release: clean
tarball: clean
F=mongoose-`perl -lne '/define\s+MONGOOSE_VERSION\s+"(\S+)"/ and print $$1' mongoose.c`.tgz ; cd .. && tar -czf x mongoose/{LICENSE,Makefile,examples,test,build,*.[ch],*.md} && mv x mongoose/$$F
release: tarball cocoa
wine make windows
V=`perl -lne '/define\s+MONGOOSE_VERSION\s+"(\S+)"/ and print $$1' mongoose.c`; upx mongoose.exe; cp mongoose.exe mongoose-$$V.exe; cp mongoose.exe mongoose_php_bundle/; zip -r mongoose_php_bundle_$$V.zip mongoose_php_bundle/
clean:
rm -rf *.o *.core $(PROG) *.obj *.so $(PROG).txt *.dSYM *.tgz $(PROG).exe *.dll *.lib build/res.o build/res.RES
cd examples && $(MAKE) clean
rm -rf *.o *.core $(PROG) *.obj *.so $(PROG).txt *.dSYM *.tgz \
$(PROG).exe *.dll *.lib build/res.o build/res.RES *.dSYM
+39 -20
View File
@@ -1,42 +1,61 @@
Project Mission
---------------
# Project Mission
Project mission is to provide simple, functional, embeddable web server to
make it easy for application and device developers to implement web interface for their
application and devices, and to offer a simple web development environment.
make it easy for application and device developers to implement web interface
for their application and devices, and to offer a simple web development
environment.
Overview
--------
# Overview
To accomplish it's mission, Mongoose keeps balance on functionality and
simplicity by carefully selected list of features:
- Liberal, commercial-friendly [MIT license](http://en.wikipedia.org/wiki/MIT_License)
- Liberal, commercial-friendly
[MIT license](http://en.wikipedia.org/wiki/MIT_License)
- Works on Windows, Mac, UNIX, iPhone, Android, and many other platforms
- Support for CGI, SSL, SSI, Digest (MD5) authorization, Websocket, WEbDAV
- Lua server pages (PHP-like functionality using Lua), see [page.lp](https://github.com/valenok/mongoose/blob/master/test/page.lp)
- Lua server pages (PHP-like functionality using Lua), see
[page.lp](https://github.com/valenok/mongoose/blob/master/test/page.lp)
- Resumed download, URL rewrite, IP-based ACL, Windows service
- Excluding files from serving by URI pattern (file blacklist)
- Download speed limit based on client subnet or URI pattern
- Small footprint: executable size is 50 kB on Linux 2.6 i386 system
- 130 kilobytes Windows executable with all of the above and no dependencies
- Simple and clean embedding API ([mongoose.h](https://github.com/valenok/mongoose/blob/master/mongoose.h)). The source is in single [mongoose.c](https://github.com/valenok/mongoose/blob/master/mongoose.c) file to make things easy.
- Embedding examples: [hello.c](https://github.com/valenok/mongoose/blob/master/examples/hello.c), [post.c](https://github.com/valenok/mongoose/blob/master/examples/post.c), [upload.c](https://github.com/valenok/mongoose/blob/master/examples/upload.c), [websocket.c](https://github.com/valenok/mongoose/blob/master/examples/websocket.c)
- Extensive documentation in form of [User Manual](https://github.com/valenok/mongoose/blob/master/UserManual.md)
- Simple and clean embedding API,
[mongoose.h](https://github.com/valenok/mongoose/blob/master/mongoose.h).
The source is in single
[mongoose.c](https://github.com/valenok/mongoose/blob/master/mongoose.c) file
to make things easy
- Embedding examples:
[hello.c](https://github.com/valenok/mongoose/blob/master/examples/hello.c),
[post.c](https://github.com/valenok/mongoose/blob/master/examples/post.c),
[upload.c](https://github.com/valenok/mongoose/blob/master/examples/upload.c),
[websocket.c](https://github.com/valenok/mongoose/blob/master/examples/websocket.c)
- HTTP client functionality for embedded usage, capable of
sending arbitrary HTTP/HTTPS requests
- [User Manual](https://github.com/valenok/mongoose/blob/master/UserManual.md)
Questions can be asked at
[mongoose-users@google.com](http://groups.google.com/group/mongoose-users) mailing list.
[mongoose-users@google.com](http://groups.google.com/group/mongoose-users)
mailing list.
Keep Sergey happy
-----------------
# Keep Sergey happy
Since 2004, Mongoose is being constantly improved by me, Sergey Lyubka, a software engineer
from Galway, Ireland. My other software I give to the community for free is
I am Sergey Lyubka, a software engineer from Galway, Ireland. I started
working on Mongoose in 2004, and since then continuously improve it,
investing thousands of hours of work. My other project I'm contributing to the
community for free is
[Super Light Regular Expression library](http://code.google.com/p/slre).
If you feel grateful for the stuff I've done, you can buy me a book from my
[Amazon wishlist](http://amzn.com/w/1OC2ZCPTQYIEP?sort=priority). Many thanks to all who
already did so: T.Barmann, D.Hughes, J.C.Sloan, R.Romeo and 4 others.
Appreciated guys, you keep my brains going!
[Amazon wishlist](http://amzn.com/w/1OC2ZCPTQYIEP?sort=priority). Many thanks
to all who already did so: T.Barmann, D.Hughes, J.C.Sloan, R.Romeo,
L.E.Spencer, S.Kotay and 7 others.
Appreciated guys, you keep my brains going! Cash is also welcome indeed.
Press [<img src="http://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif">](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DGZ2FMP95TAL6)
button to donate. Donation progress: 39/1000 &euro;
(thanks to O.M.Vilhunen, C.Radik, G.Woodcock, M.Szczepkowski,
Eternal Lands Development Team)
![Progress](http://chart.googleapis.com/chart?chxr=0,0,1000&chxt=x&chbh=30,0,0&chs=300x35&cht=bhs&chco=90c0f0&chd=t:3.9)
+349 -25
View File
@@ -1,15 +1,15 @@
**NOTE: THIS MANUAL IS WORK IN PROGRESS**
Overview
--------
# Overview
Mongoose is small and easy to use web server. It is self-contained, and does
not require any external software to run.
On Windows, mongoose iconifies itself to the system tray icon when started.
Right-click on the icon pops up a menu, where it is possible to stop
mongoose, or configure it, or install it as Windows service.
mongoose, or configure it, or install it as Windows service. The easiest way
to share a folder on Windows is to copy `mongoose.exe` to a folder,
double-click the exe, and launch a browser at
[http://localhost:8080](http://localhost:8080). Note that 'localhost' should
be changed to a machine's name if a folder is accessed from other computer.
On UNIX and Mac, mongoose is a command line utility. Running `mongoose` in
terminal, optionally followed by configuration parameters
@@ -22,12 +22,13 @@ When started, mongoose first searches for the configuration file.
If configuration file is specified explicitly in the command line, i.e.
`mongoose path_to_config_file`, then specified configuration file is used.
Otherwise, mongoose would search for file `mongoose.conf` in the same directory
where binary is located, and use it.
where binary is located, and use it. Configuration file can be absent.
Configuration file is a sequence of lines, each line containing
command line argument name and it's value. Empty lines, and lines beginning
with `#`, are ignored. Here is the example of `mongoose.conf` file:
# mongoose.conf file
document_root c:\www
listening_ports 8080,8043s
ssl_certificate c:\mongoose\ssl_cert.pem
@@ -39,33 +40,356 @@ For example, if `mongoose.conf` has line
`document_root /var/www`, and mongoose has been started as
`mongoose -document_root /etc`, then `/etc` directory will be served as
document root, because command line options take priority over
configuration file.
configuration file. Configuration options section below provide a good
overview of Mongoose features.
Note that configuration options on the command line must start with `-`,
but their names are the same as in the config file. All option names are
listed in the next section. Thus, the following two setups are equivalent:
# Using command line arguments
$ mongoose -listening_ports 1234 -document_root /var/www
# Using config file
$ cat mongoose.conf
listening_ports 1234
document_root /var/www
$ mongoose
Mongoose can also be used to modify `.htpasswd` passwords file:
mongoose -A <htpasswd_file> <realm> <user> <passwd>
Usage Examples
--------------
Unlike other web servers, mongoose does not require CGI scripts be located in
a special directory. CGI scripts can be anywhere. CGI (and SSI) files are
recognized by the file name pattern. Mongoose uses shell-like glob
patterns. Pattern match starts at the beginning of the string, so essentially
patterns are prefix patterns. Syntax is as follows:
- How to share a Windows folder: copy mongoose executable to a folder and
double-click the executable. The folder should be accessible via
[http://localhost:8080](http://localhost:8080) in any browser.
- How to start mongoose at UNIX startup time in daemon mode, serving
directory `/var/www`: put this line in the system startup script,
`/path/to/mongoose -listening_ports 80 -document_root /var/www &`
** Matches everything
* Matches everything but slash character, '/'
? Matches any character
$ Matches the end of the string
| Matches if pattern on the left side or the right side matches.
Command Line Options
--------------------
All other characters in the pattern match themselves. Examples:
Common Problems
---------------
**.cgi$ Any string that ends with .cgi
/foo Any string that begins with /foo
**a$|**b$ Any string that ends with a or b
Embedding
---------
# Configuration Options
Other Resources
---------------
Below is a list of configuration options Mongoose understands. Every option
is followed by it's default value. If default value is not present, then
it is empty.
### cgi_pattern `**.cgi$|**.pl$|**.php$`
All files that match `cgi_pattern` are treated as CGI files. Default pattern
allows CGI files be anywhere. To restrict CGIs to a certain directory,
use `/path/to/cgi-bin/**.cgi` as pattern. Note that full file path is
matched against the pattern, not the URI.
### cgi_environment
Extra environment variables to be passed to the CGI script in
addition to standard ones. The list must be comma-separated list
of name=value pairs, like this: `VARIABLE1=VALUE1,VARIABLE2=VALUE2`.
### put\_delete\_passwords_file
Passwords file for PUT and DELETE requests. Without it, PUT and DELETE requests
will fail.
### cgi_interpreter
Path to an executable to use as CGI interpreter for __all__ CGI scripts
regardless script extension. If this option is not set (which is a default),
Mongoose looks at first line of a CGI script,
[shebang line](http://en.wikipedia.org/wiki/Shebang_(Unix)), for an interpreter.
For example, if both PHP and perl CGIs are used, then
`#!/path/to/php-cgi.exe` and `#!/path/to/perl.exe` must be first lines of the
respective CGI scripts. Note that paths should be either full file paths,
or file paths relative to the current working directory of mongoose server.
If mongoose is started by mouse double-click on Windows, current working
directory is a directory where mongoose executable is located.
If all CGIs use the same interpreter, for example they are all PHP, then
`cgi_interpreter` can be set to the path to `php-cgi.exe` executable and
shebang line in the CGI scripts can be omitted.
Note that PHP scripts must use `php-cgi.exe` executable, not `php.exe`.
### protect_uri
Comma separated list of URI=PATH pairs, specifying that given
URIs must be protected with respected password files. Paths must be full
file paths.
### authentication_domain `mydomain.com`
Authorization realm used in `.htpasswd` authorization.
### ssi_pattern `**.shtml$|**.shtm$`
All files that match `ssi_pattern` are treated as SSI.
Server Side Includes (SSI) is a simple interpreted server-side scripting
language which is most commonly used to include the contents of a file into
a web page. It can be useful when it is desirable to include a common piece
of code throughout a website, for example, headers and footers.
In order for a webpage to recognize an SSI-enabled HTML file, the filename
should end with a special extension, by default the extension should be
either `.shtml` or `.shtm`.
Unknown SSI directives are silently ignored by mongoose. Currently, two SSI
directives are supported, `<!--#include ...>` and
`<!--#exec "command">`. Note that `<!--#include ...>` directive supports
three path specifications:
<!--#include virtual="path"> Path is relative to web server root
<!--#include file="path"> Path is relative to web server working dir
<!--#include "path"> Path is relative to current document
The `include` directive may be used to include the contents of a file or the
result of running a CGI script. The `exec` directive is used to execute a
command on a server, and show command's output. Example:
<!--#exec "ls -l" -->
For more information on Server Side Includes, take a look at the Wikipedia:
[Server Side Includes](http://en.wikipedia.org/wiki/Server_Side_Includes)
### throttle
Limit download speed for clients. `throttle` is a comma-separated
list of key=value pairs, where key could be:
* limit speed for all connections
x.x.x.x/mask limit speed for specified subnet
uri_prefix_pattern limit speed for given URIs
The value is a floating-point number of bytes per second, optionally
followed by a `k` or `m` character, meaning kilobytes and
megabytes respectively. A limit of 0 means unlimited rate. The
last matching rule wins. Examples:
*=1k,10.0.0.0/8=0 limit all accesses to 1 kilobyte per second,
but give connections from 10.0.0.0/8 subnet
unlimited speed
/downloads/=5k limit accesses to all URIs in `/downloads/` to
5 kilobytes per secods. All other accesses are unlimited
### access\_log\_file
Path to a file for access logs. Either full path, or relative to current
working directory. If absent (default), then accesses are not logged.
### error\_log\_file
Path to a file for error logs. Either full path, or relative to current
working directory. If absent (default), then errors are not logged.
### enable\_directory\_listing `yes`
Enable directory listing, either `yes` or `no`.
### global\_passwords\_file
Path to a global passwords file, either full path or relative to the current
working directory. If set, per-directory `.htpasswd` files are ignored,
and all requests are authorised against that file.
### index_files `index.html,index.htm,index.cgi,index.shtml,index.php`
Comma-separated list of files to be treated as directory index
files.
### access\_control\_list
An Access Control List (ACL) allows restrictions to be put on the list of IP
addresses which have access to the web server. In the case of the Mongoose
web server, the ACL is a comma separated list of IP subnets, where each
subnet is prepended by either a `-` or a `+` sign. A plus sign means allow,
where a minus sign means deny. If a subnet mask is omitted, such as `-1.2.3.4`,
this means to deny only that single IP address.
Subnet masks may vary from 0 to 32, inclusive. The default setting is to allow
all accesses. On each request the full list is traversed, and
the last match wins. Examples:
-0.0.0.0/0,+192.168/16 deny all acccesses, only allow 192.168/16 subnet
To learn more about subnet masks, see the
[Wikipedia page on Subnetwork](http://en.wikipedia.org/wiki/Subnetwork)
### extra\_mime\_types
Extra mime types to recognize, in form `extension1=type1,exten-
sion2=type2,...`. Extension must include dot. Example:
`.cpp=plain/text,.java=plain/text`
### listening_ports `8080`
Comma-separated list of ports to listen on. If the port is SSL, a
letter `s` must be appeneded, for example, `80,443s` will open
port 80 and port 443, and connections on port 443 will be SSL-ed.
For non-SSL ports, it is allowed to append letter `r`, meaning 'redirect'.
Redirect ports will redirect all their traffic to the first configured
SSL port. For example, if `listening_ports` is `80r,443s`, then all
HTTP traffic coming at port 80 will be redirected to HTTPS port 443.
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, to bind to a loopback interface on port 80 and to
all interfaces on HTTPS port 443, use `127.0.0.1:80,443s`.
### document_root `.`
A directory to serve. By default, currect directory is served. Current
directory is commonly referenced as dot (`.`).
### ssl_certificate
Path to SSL certificate file. This option is only required when at least one
of the `listening_ports` is SSL.
### num_threads `50`
Number of worker threads. Mongoose handles each incoming connection in a
separate thread. Therefore, the value of this option is effectively a number
of concurrent HTTP connections Mongoose can handle.
### run\_as\_user
Switch to given user credentials after startup. Usually, this option is
required when mongoose needs to bind on privileged port on UNIX. To do
that, mongoose needs to be started as root. But running as root is a bad idea,
therefore this option can be used to drop privileges. Example:
mongoose -listening_ports 80 -run_as_user nobody
### url\_rewrite\_patterns
Comma-separated list of URL rewrites in the form of
`uri_pattern=file_or_directory_path`. When Mongoose receives the request,
it constructs the file name to show by combining `document_root` and the URI.
However, if the rewrite option is used and `uri_pattern` matches the
requested URI, then `document_root` is ignored. Insted,
`file_or_directory_path` is used, which should be a full path name or
a path relative to the web server's current working directory. Note that
`uri_pattern`, as all mongoose patterns, is a prefix pattern.
This makes it possible to serve many directories outside from `document_root`,
redirect all requests to scripts, and do other tricky things. For example,
to redirect all accesses to `.doc` files to a special script, do:
mongoose -url_rewrite_patterns **.doc$=/path/to/cgi-bin/handle_doc.cgi
Or, to imitate user home directories support, do:
mongoose -url_rewrite_patterns /~joe/=/home/joe/,/~bill=/home/bill/
### hide\_files\_patterns
A pattern for the files to hide. Files that match the pattern will not
show up in directory listing and return `404 Not Found` if requested. Pattern
must be for a file name only, not including directory name. Example:
mongoose -hide_files_patterns secret.txt|even_more_secret.txt
# Common Problems
- PHP doesn't work - getting empty page, or 'File not found' error. The
reason for that is wrong paths to the interpreter. Remember that with PHP,
correct interpreter is `php-cgi.exe` (`php-cgi` on UNIX). Solution: specify
full path to the PHP interpreter, e.g.:
`mongoose -cgi_interpreter /full/path/to/php-cgi`
- Mongoose fails to start. If Mongoose exits immediately when run, this
usually indicates a syntax error in the configuration file
(named `mongoose.conf` by default) or the command-line arguments.
Syntax checking is omitted from Mongoose to keep its size low. However,
the Manual should be of help. Note: the syntax changes from time to time,
so updating the config file might be necessary after executable update.
# Embedding
Embedding Mongoose is easy. Copy
[mongoose.c](https://github.com/valenok/mongoose/blob/master/mongoose.c) and
[mongoose.h](https://github.com/valenok/mongoose/blob/master/mongoose.h)
to your application's source tree and include them in the build. For
example, your application's code lives in C++ file `my_app.cpp`, then on UNIX
this command embeds Mongoose:
$ ls
my_app.cpp mongoose.c mongoose.h
$ g++ my_app.cc mongoose.c -o my_app
Somewhere in the application code, call `mg_start()` to start the server.
Pass configuration options and event handlers to `mg_start()`.
Mongoose then calls handlers when certain events happen.
For example, when new request arrives, Mongoose calls `begin_request`
handler function to let user handle the request. In the handler, user code
can get all information about the request -- parsed headers, etcetera.
Mongoose API is logically divided in three categories: server setup/shutdown
functions, functions to be used by user-written event handlers, and
convenience utility functions.
### Starting and stopping embedded web server
To start the embedded web server, call `mg_start()`. To stop it, call
`mg_stop()`.
// This structure needs to be passed to mg_start(), to let mongoose know
// which callbacks to invoke. For detailed description, see
// https://github.com/valenok/mongoose/blob/master/UserManual.md
struct mg_callbacks {
int (*begin_request)(struct mg_connection *);
void (*end_request)(const struct mg_connection *, int reply_status_code);
int (*log_message)(const struct mg_connection *, const char *message);
int (*init_ssl)(void *ssl_context);
int (*websocket_connect)(const struct mg_connection *);
void (*websocket_ready)(struct mg_connection *);
int (*websocket_data)(struct mg_connection *);
const char * (*open_file)(const struct mg_connection *,
const char *path, size_t *data_len);
void (*init_lua)(struct mg_connection *, void *lua_context);
void (*upload)(struct mg_connection *, const char *file_name);
};
[hello.c](https://github.com/valenok/mongoose/blob/master/examples/hello.c)
provides a minimalistic example.
Common pattern is to implement `begin_request` callback, and serve static files
from memory, and/or construct dynamic replies on the fly. Here is
my [embed.c](https://gist.github.com/valenok/4714740) gist
that shows how to easily any data can be embedded
directly into the executable. If such data needs to be encrypted, then
encrypted database or encryption dongles would be a better choice.
# Build on Android
This is a small guide to help you run mongoose on Android. Currently it is
tested on the HTC Wildfire. If you have managed to run it on other devices
as well, please comment or drop an email in the mailing list.
Note : You dont need root access to run mongoose on Android.
- Download the source from the Downloads page.
- Download the Android NDK from here
- Make a folder (e.g. mongoose) and inside that make a folder named "jni".
- Add `mongoose.h`, `mongoose.c` and `main.c` from the source to the jni folder.
- Make a new file in the jni folder named "Android.mk".
This is the make file for ndk-build.
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := mongoose
LOCAL_SRC_FILES := main.c mongoose.c
include $(BUILD_EXECUTABLE)
- Run `./ndk-build -C /path/to/mongoose/`.
This should generate mongoose/lib/armeabi/mongoose
- Using the adb tool, push the generated mongoose binary to `/data/local`
folder on device.
- From adb shell, navigate to `/data/local` and execute `./mongoose`.
- To test if the server is running fine, visit your web-browser and
navigate to `http://127.0.0.1:8080` You should see the `Index of /` page.
![screenshot](https://a248.e.akamai.net/camo.github.com/b88428bf009a2b6141000937ab684e04cc8586af/687474703a2f2f692e696d6775722e636f6d2f62676f6b702e706e67)
Notes:
- jni stands for Java Native Interface. Read up on Android NDK if you want
to know how to interact with the native C functions of mongoose in Android
Java applications.
- Download android-sdk for the adb tool.
- TODO: A Java application that interacts with the native binary or a
shared library.
# Other Resources
- Presentation made by Arnout Vandecappelle at FOSDEM 2011 on 2011-02-06
in Brussels, Belgium, called
"Creating secure web based user interfaces for Embedded Devices"
+14 -12
View File
@@ -2,17 +2,19 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>mongoose</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleTypeRole</key>
<string>None</string>
<key>CFBundleIconFiles</key>
<array>
<string>mongoose_16x16.png</string>
</array>
<key>LSUIElement</key>
<true/>
<key>CFBundleExecutable</key> <string>Mongoose</string>
<key>CFBundlePackageType</key> <string>APPL</string>
<key>CFBundleTypeRole</key> <string>None</string>
<key>CFBundleIconFiles</key> <array>
<string>mongoose_16x16.png</string>
<string>mongoose_22x22.png</string>
<string>mongoose_32x32.png</string>
<string>mongoose_64x64.png</string>
</array>
<key>LSUIElement</key> <true/>
<key>RunAtLoad</key> <true/>
<key>Label</key> <string>com.kolkin.mongoose</string>
<key>ProgramArguments</key> <array> </array>
<key>KeepAlive</key> <true/>
</dict>
</plist>
+1885
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1 +1 @@
200 ICON DISCARDABLE "systray.ico"
100 ICON DISCARDABLE "systray.ico"
+137414
View File
File diff suppressed because it is too large. Load diff
+7160
View File
File diff suppressed because it is too large. Load diff
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 17 KiB

+1 -1
View File
@@ -25,4 +25,4 @@ windows:
$(CL) /DUSE_WEBSOCKET websocket.c ../mongoose.c $(CLFLAGS)
clean:
rm -rf hello hello.exe upload upload.exe post post.exe websocket websocket.exe chat chat.exe *.dSYM *.obj
rm -rf hello upload post websocket chat *.exe *.dSYM *.obj
+19 -25
View File
@@ -325,34 +325,25 @@ static void redirect_to_ssl(struct mg_connection *conn,
}
}
static void *event_handler(enum mg_event event,
struct mg_connection *conn) {
static int begin_request_handler(struct mg_connection *conn) {
const struct mg_request_info *request_info = mg_get_request_info(conn);
void *processed = "yes";
int processed = 1;
if (event == MG_NEW_REQUEST) {
if (!request_info->is_ssl) {
redirect_to_ssl(conn, request_info);
} else if (!is_authorized(conn, request_info)) {
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 = NULL;
}
} else if (event == MG_EVENT_LOG) {
printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data);
processed = NULL;
if (!request_info->is_ssl) {
redirect_to_ssl(conn, request_info);
} else if (!is_authorized(conn, request_info)) {
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 {
processed = NULL;
// No suitable handler found, mark as not processed. Mongoose will
// try to serve the request.
processed = 0;
}
return processed;
}
@@ -365,6 +356,7 @@ static const char *options[] = {
};
int main(void) {
struct mg_callbacks callbacks;
struct mg_context *ctx;
// Initialize random number generator. It will be used later on for
@@ -372,7 +364,9 @@ int main(void) {
srand((unsigned) time(0));
// Setup and start Mongoose
if ((ctx = mg_start(&event_handler, NULL, options)) == NULL) {
memset(&callbacks, 0, sizeof(callbacks));
callbacks.begin_request = begin_request_handler;
if ((ctx = mg_start(&callbacks, NULL, options)) == NULL) {
printf("%s\n", "Cannot start chat server, fatal exit");
exit(EXIT_FAILURE);
}
+35 -21
View File
@@ -2,35 +2,49 @@
#include <string.h>
#include "mongoose.h"
static void *callback(enum mg_event event,
struct mg_connection *conn) {
// This function will be called by mongoose on every new request.
static int begin_request_handler(struct mg_connection *conn) {
const struct mg_request_info *request_info = mg_get_request_info(conn);
char content[100];
if (event == MG_NEW_REQUEST) {
char content[1024];
int content_length = snprintf(content, sizeof(content),
"Hello from mongoose! Remote port: %d",
request_info->remote_port);
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: %d\r\n" // Always set Content-Length
"\r\n"
"%s",
content_length, content);
// Mark as processed
return "";
} else {
return NULL;
}
// Prepare the message we're going to send
int content_length = snprintf(content, sizeof(content),
"Hello from mongoose! Remote port: %d",
request_info->remote_port);
// Send HTTP reply to the client
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: %d\r\n" // Always set Content-Length
"\r\n"
"%s",
content_length, content);
// Returning non-zero tells mongoose that our function has replied to
// the client, and mongoose should not send client any more data.
return 1;
}
int main(void) {
struct mg_context *ctx;
struct mg_callbacks callbacks;
// List of options. Last element must be NULL.
const char *options[] = {"listening_ports", "8080", NULL};
ctx = mg_start(&callback, NULL, options);
getchar(); // Wait until user hits "enter"
// Prepare callbacks structure. We have only one callback, the rest are NULL.
memset(&callbacks, 0, sizeof(callbacks));
callbacks.begin_request = begin_request_handler;
// Start the web server.
ctx = mg_start(&callbacks, NULL, options);
// Wait until user hits "enter". Server is running in separate thread.
// Navigating to http://localhost:8080 will invoke begin_request_handler().
getchar();
// Stop the server.
mg_stop(ctx);
return 0;
+27 -32
View File
@@ -10,50 +10,45 @@ static const char *html_form =
"<input type=\"submit\" />"
"</form></body></html>";
static void *callback(enum mg_event event,
struct mg_connection *conn) {
static int begin_request_handler(struct mg_connection *conn) {
const struct mg_request_info *ri = mg_get_request_info(conn);
char post_data[1024], input1[sizeof(post_data)], input2[sizeof(post_data)];
int post_data_len;
if (event == MG_NEW_REQUEST) {
if (!strcmp(ri->uri, "/handle_post_request")) {
// User has submitted a form, show submitted data and a variable value
char post_data[1024],
input1[sizeof(post_data)], input2[sizeof(post_data)];
int post_data_len;
if (!strcmp(ri->uri, "/handle_post_request")) {
// User has submitted a form, show submitted data and a variable value
post_data_len = mg_read(conn, post_data, sizeof(post_data));
// Read POST data
post_data_len = mg_read(conn, post_data, sizeof(post_data));
// Parse form data. input1 and input2 are guaranteed to be NUL-terminated
mg_get_var(post_data, post_data_len, "input_1", input1, sizeof(input1));
mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2));
// Parse form data. input1 and input2 are guaranteed to be NUL-terminated
mg_get_var(post_data, post_data_len, "input_1", input1, sizeof(input1));
mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2));
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
"Content-Type: text/plain\r\n\r\n"
"Submitted data: [%.*s]\n"
"Submitted data length: %d bytes\n"
"input_1: [%s]\n"
"input_2: [%s]\n",
post_data_len, post_data, post_data_len, input1, input2);
} else {
// Show HTML form.
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
"Content-Length: %d\r\n"
"Content-Type: text/html\r\n\r\n%s",
(int) strlen(html_form), html_form);
}
// Mark as processed
return "";
// Send reply to the client, showing submitted form values.
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
"Content-Type: text/plain\r\n\r\n"
"Submitted data: [%.*s]\n"
"Submitted data length: %d bytes\n"
"input_1: [%s]\n"
"input_2: [%s]\n",
post_data_len, post_data, post_data_len, input1, input2);
} else {
return NULL;
// Show HTML form.
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
"Content-Length: %d\r\n"
"Content-Type: text/html\r\n\r\n%s",
(int) strlen(html_form), html_form);
}
return 1; // Mark request as processed
}
int main(void) {
struct mg_context *ctx;
const char *options[] = {"listening_ports", "8080", NULL};
struct mg_callbacks callbacks;
ctx = mg_start(&callback, NULL, options);
memset(&callbacks, 0, sizeof(callbacks));
callbacks.begin_request = begin_request_handler;
ctx = mg_start(&callbacks, NULL, options);
getchar(); // Wait until user hits "enter"
mg_stop(ctx);
+28 -26
View File
@@ -17,42 +17,44 @@ typedef __int64 int64_t;
#include "mongoose.h"
static void *callback(enum mg_event event, struct mg_connection *conn) {
if (event == MG_NEW_REQUEST) {
if (!strcmp(mg_get_request_info(conn)->uri, "/handle_post_request")) {
mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n");
mg_upload(conn, "/tmp");
} else {
// Show HTML form. Make sure it has enctype="multipart/form-data" attr.
static const char *html_form =
"<html><body>Upload example."
"<form method=\"POST\" action=\"/handle_post_request\" "
" enctype=\"multipart/form-data\">"
"<input type=\"file\" name=\"file\" /> <br/>"
"<input type=\"submit\" value=\"Upload\" />"
"</form></body></html>";
static int begin_request_handler(struct mg_connection *conn) {
if (!strcmp(mg_get_request_info(conn)->uri, "/handle_post_request")) {
mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n");
mg_upload(conn, "/tmp");
} else {
// Show HTML form. Make sure it has enctype="multipart/form-data" attr.
static const char *html_form =
"<html><body>Upload example."
"<form method=\"POST\" action=\"/handle_post_request\" "
" enctype=\"multipart/form-data\">"
"<input type=\"file\" name=\"file\" /> <br/>"
"<input type=\"submit\" value=\"Upload\" />"
"</form></body></html>";
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
"Content-Length: %d\r\n"
"Content-Type: text/html\r\n\r\n%s",
(int) strlen(html_form), html_form);
}
// Mark as processed
return "";
} else if (event == MG_UPLOAD) {
mg_printf(conn, "Saved [%s]", mg_get_request_info(conn)->ev_data);
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
"Content-Length: %d\r\n"
"Content-Type: text/html\r\n\r\n%s",
(int) strlen(html_form), html_form);
}
return NULL;
// Mark request as processed
return 1;
}
static void upload_handler(struct mg_connection *conn, const char *path) {
mg_printf(conn, "Saved [%s]", path);
}
int main(void) {
struct mg_context *ctx;
const char *options[] = {"listening_ports", "8080", NULL};
struct mg_callbacks callbacks;
ctx = mg_start(&callback, NULL, options);
memset(&callbacks, 0, sizeof(callbacks));
callbacks.begin_request = begin_request_handler;
callbacks.upload = upload_handler;
ctx = mg_start(&callbacks, NULL, options);
getchar(); // Wait until user hits "enter"
pause();
mg_stop(ctx);
return 0;
+49 -48
View File
@@ -5,69 +5,70 @@
#include <string.h>
#include "mongoose.h"
static void *callback(enum mg_event event, struct mg_connection *conn) {
if (event == MG_WEBSOCKET_READY) {
unsigned char buf[40];
buf[0] = 0x81;
buf[1] = snprintf((char *) buf + 2, sizeof(buf) - 2, "%s", "server ready");
mg_write(conn, buf, 2 + buf[1]);
return ""; // MG_WEBSOCKET_READY return value is ignored
} else if (event == MG_WEBSOCKET_MESSAGE) {
unsigned char buf[200], reply[200];
int n, i, mask_len, xor, msg_len, len;
static void websocket_ready_handler(struct mg_connection *conn) {
unsigned char buf[40];
buf[0] = 0x81;
buf[1] = snprintf((char *) buf + 2, sizeof(buf) - 2, "%s", "server ready");
mg_write(conn, buf, 2 + buf[1]);
}
// Read message from the client.
// Accept only small (<126 bytes) messages.
len = 0;
msg_len = mask_len = 0;
for (;;) {
if ((n = mg_read(conn, buf + len, sizeof(buf) - len)) <= 0) {
return ""; // Read error, close websocket
static int websocket_data_handler(struct mg_connection *conn) {
unsigned char buf[200], reply[200];
int n, i, mask_len, xor, msg_len, len;
// Read message from the client.
// Accept only small (<126 bytes) messages.
len = 0;
msg_len = mask_len = 0;
for (;;) {
if ((n = mg_read(conn, buf + len, sizeof(buf) - len)) <= 0) {
return 0; // Read error, close websocket
}
len += n;
if (len >= 2) {
msg_len = buf[1] & 127;
mask_len = (buf[1] & 128) ? 4 : 0;
if (msg_len > 125) {
return 0; // Message is too long, close websocket
}
len += n;
if (len >= 2) {
msg_len = buf[1] & 127;
mask_len = (buf[1] & 128) ? 4 : 0;
if (msg_len > 125) {
return ""; // Message is too long, close websocket
}
// If we've buffered the whole message, exit the loop
if (len >= 2 + mask_len + msg_len) {
break;
}
// If we've buffered the whole message, exit the loop
if (len >= 2 + mask_len + msg_len) {
break;
}
}
// Prepare frame
reply[0] = 0x81; // text, FIN set
reply[1] = msg_len;
// Copy message from request to reply, applying the mask if required.
for (i = 0; i < msg_len; i++) {
xor = mask_len == 0 ? 0 : buf[2 + (i % 4)];
reply[i + 2] = buf[i + 2 + mask_len] ^ xor;
}
// Echo the message back to the client
mg_write(conn, reply, 2 + msg_len);
// Return non-NULL means stoping websocket conversation.
// Close the conversation if client has sent us "exit" string.
return memcmp(reply + 2, "exit", 4) == 0 ? "" : NULL;
} else {
return NULL;
}
// Prepare frame
reply[0] = 0x81; // text, FIN set
reply[1] = msg_len;
// Copy message from request to reply, applying the mask if required.
for (i = 0; i < msg_len; i++) {
xor = mask_len == 0 ? 0 : buf[2 + (i % 4)];
reply[i + 2] = buf[i + 2 + mask_len] ^ xor;
}
// Echo the message back to the client
mg_write(conn, reply, 2 + msg_len);
// Returnint zero means stoping websocket conversation.
// Close the conversation if client has sent us "exit" string.
return memcmp(reply + 2, "exit", 4);
}
int main(void) {
struct mg_context *ctx;
struct mg_callbacks callbacks;
const char *options[] = {
"listening_ports", "8080",
"document_root", "websocket_html_root",
NULL
};
ctx = mg_start(&callback, NULL, options);
memset(&callbacks, 0, sizeof(callbacks));
callbacks.websocket_ready = websocket_ready_handler;
callbacks.websocket_data = websocket_data_handler;
ctx = mg_start(&callbacks, NULL, options);
getchar(); // Wait until user hits "enter"
mg_stop(ctx);
+352 -52
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2004-2011 Sergey Lyubka
// Copyright (c) 2004-2013 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
@@ -40,6 +40,7 @@
#ifdef _WIN32
#include <windows.h>
#include <winsvc.h>
#include <shlobj.h>
#define PATH_MAX MAX_PATH
#define S_ISDIR(x) ((x) & _S_IFDIR)
#define DIRSEP '\\'
@@ -104,13 +105,36 @@ static void show_usage_and_exit(void) {
fprintf(stderr, " -%s %s (default: \"%s\")\n",
names[i], names[i + 1], names[i + 2] == NULL ? "" : names[i + 2]);
}
fprintf(stderr, "\nSee http://code.google.com/p/mongoose/wiki/MongooseManual"
" for more details.\n");
fprintf(stderr, "Example:\n mongoose -s cert.pem -p 80,443s -d no\n");
exit(EXIT_FAILURE);
}
#if defined(_WIN32) || defined(USE_COCOA)
static const char *config_file_top_comment =
"# Mongoose web server configuration file.\n"
"# For detailed description of every option, visit\n"
"# https://github.com/valenok/mongoose/blob/master/UserManual.md\n"
"# Lines starting with '#' and empty lines are ignored.\n"
"# To make a change, remove leading '#', modify option's value,\n"
"# save this file and then restart Mongoose.\n\n";
static const char *get_url_to_first_open_port(const struct mg_context *ctx) {
static char url[100];
const char *open_ports = mg_get_option(ctx, "listening_ports");
int a, b, c, d, port, n;
if (sscanf(open_ports, "%d.%d.%d.%d:%d%n", &a, &b, &c, &d, &port, &n) == 5) {
snprintf(url, sizeof(url), "%s://%d.%d.%d.%d:%d",
open_ports[n] == 's' ? "https" : "http", a, b, c, d, port);
} else if (sscanf(open_ports, "%d%n", &port, &n) == 1) {
snprintf(url, sizeof(url), "%s://localhost:%d",
open_ports[n] == 's' ? "https" : "http", port);
} else {
snprintf(url, sizeof(url), "%s", "http://localhost:8080");
}
return url;
}
static void create_config_file(const char *path) {
const char **names, *value;
FILE *fp;
@@ -120,13 +144,7 @@ static void create_config_file(const char *path) {
if ((fp = fopen(path, "r")) != NULL) {
fclose(fp);
} else if ((fp = fopen(path, "a+")) != NULL) {
fprintf(fp, "%s",
"# Mongoose web server configuration file.\n"
"# For detailed description of every option, visit\n"
"# https://github.com/valenok/mongoose/blob/master/UserManual.md\n"
"# Lines starting with '#' and empty lines are ignored.\n"
"# To make a change, remove leading '#', modify option's value,\n"
"# save this file and then restart Mongoose.\n\n");
fprintf(fp, "%s", config_file_top_comment);
names = mg_get_valid_option_names();
for (i = 0; names[i] != NULL; i += 3) {
value = mg_get_option(ctx, names[i]);
@@ -215,29 +233,37 @@ static void process_command_line_arguments(char *argv[], char **options) {
// Loop over the lines in config file
while (fgets(line, sizeof(line), fp) != NULL) {
line_no++;
// Ignore empty lines and comments
for (i = 0; isspace(* (unsigned char *) &line[i]); ) i++;
if (line[i] == '#' || line[i] == '\0')
for (i = 0; isspace(* (unsigned char *) &line[i]); ) i++;
if (line[i] == '#' || line[i] == '\0') {
continue;
}
if (sscanf(line, "%s %[^\r\n#]", opt, val) != 2) {
die("%s: line %d is invalid", config_file, (int) line_no);
printf("%s: line %d is invalid, ignoring it:\n %s",
config_file, (int) line_no, line);
} else {
set_option(options, opt, val);
}
set_option(options, opt, val);
}
(void) fclose(fp);
}
// Handle command line flags. They override config file and default settings.
for (i = cmd_line_opts_start; argv[i] != NULL; i += 2) {
if (argv[i][0] != '-' || argv[i + 1] == NULL) {
show_usage_and_exit();
// If we're under MacOS and started by launchd, then the second
// argument is process serial number, -psn_.....
// In this case, don't process arguments at all.
if (argv[1] == NULL || memcmp(argv[1], "-psn_", 5) != 0) {
// Handle command line flags.
// They override config file and default settings.
for (i = cmd_line_opts_start; argv[i] != NULL; i += 2) {
if (argv[i][0] != '-' || argv[i + 1] == NULL) {
show_usage_and_exit();
}
set_option(options, &argv[i][1], argv[i + 1]);
}
set_option(options, &argv[i][1], argv[i + 1]);
}
}
@@ -246,17 +272,14 @@ static void init_server_name(void) {
mg_version());
}
static void *mongoose_callback(enum mg_event ev, struct mg_connection *conn) {
if (ev == MG_EVENT_LOG) {
printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data);
}
// Returning NULL marks request as not handled, signalling mongoose to
// proceed with handling it.
return NULL;
static int log_message(const struct mg_connection *conn, const char *message) {
(void) conn;
printf("%s\n", message);
return 0;
}
static void start_mongoose(int argc, char *argv[]) {
struct mg_callbacks callbacks;
char *options[MAX_OPTIONS];
int i;
@@ -282,7 +305,9 @@ static void start_mongoose(int argc, char *argv[]) {
signal(SIGINT, signal_handler);
/* Start Mongoose */
ctx = mg_start(&mongoose_callback, NULL, (const char **) options);
memset(&callbacks, 0, sizeof(callbacks));
callbacks.log_message = &log_message;
ctx = mg_start(&callbacks, NULL, (const char **) options);
for (i = 0; options[i] != NULL; i++) {
free(options[i]);
}
@@ -293,9 +318,25 @@ static void start_mongoose(int argc, char *argv[]) {
}
#ifdef _WIN32
enum {
ID_ICON = 100, ID_QUIT, ID_SETTINGS, ID_SEPARATOR, ID_INSTALL_SERVICE,
ID_REMOVE_SERVICE, ID_STATIC, ID_GROUP, ID_SAVE, ID_RESET_DEFAULTS,
ID_STATUS, ID_CONNECT,
// All dynamically created text boxes for options have IDs starting from
// ID_CONTROLS, incremented by one.
ID_CONTROLS = 200,
// Text boxes for files have "..." buttons to open file browser. These
// buttons have IDs that are ID_FILE_BUTTONS_DELTA higher than associated
// text box ID.
ID_FILE_BUTTONS_DELTA = 1000
};
static HICON hIcon;
static SERVICE_STATUS ss;
static SERVICE_STATUS_HANDLE hStatus;
static const char *service_magic_argument = "--";
static NOTIFYICONDATA TrayIcon;
static void WINAPI ControlHandler(DWORD code) {
if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) {
@@ -323,21 +364,6 @@ static void WINAPI ServiceMain(void) {
SetServiceStatus(hStatus, &ss);
}
#define ID_TRAYICON 100
#define ID_QUIT 101
#define ID_EDIT_CONFIG 102
#define ID_SEPARATOR 103
#define ID_INSTALL_SERVICE 104
#define ID_REMOVE_SERVICE 105
#define ID_ICON 200
static NOTIFYICONDATA TrayIcon;
static void edit_config_file(void) {
char cmd[200];
create_config_file(config_file);
snprintf(cmd, sizeof(cmd), "notepad.exe %s", config_file);
WinExec(cmd, SW_SHOW);
}
static void show_error(void) {
char buf[256];
@@ -348,6 +374,272 @@ static void show_error(void) {
MessageBox(NULL, buf, "Error", MB_OK);
}
static void *align(void *ptr, DWORD alig) {
ULONG ul = (ULONG) ptr;
ul += alig;
ul &= ~alig;
return ((void *) ul);
}
static int is_boolean_option(const char *option_name) {
return !strcmp(option_name, "enable_directory_listing") ||
!strcmp(option_name, "enable_keep_alive");
}
static int is_filename_option(const char *option_name) {
return !strcmp(option_name, "cgi_interpreter") ||
!strcmp(option_name, "global_auth_file") ||
!strcmp(option_name, "put_delete_auth_file") ||
!strcmp(option_name, "access_log_file") ||
!strcmp(option_name, "error_log_file") ||
!strcmp(option_name, "ssl_certificate");
}
static int is_directory_option(const char *option_name) {
return !strcmp(option_name, "document_root");
}
static int is_numeric_options(const char *option_name) {
return !strcmp(option_name, "num_threads");
}
static void save_config(HWND hDlg, FILE *fp) {
char value[2000];
const char **options, *name, *default_value;
int i, id;
fprintf(fp, "%s", config_file_top_comment);
options = mg_get_valid_option_names();
for (i = 0; options[i] != NULL; i += 3) {
name = options[i + 1];
id = ID_CONTROLS + i / 3;
if (is_boolean_option(name)) {
snprintf(value, sizeof(value), "%s",
IsDlgButtonChecked(hDlg, id) ? "yes" : "no");
} else {
GetDlgItemText(hDlg, id, value, sizeof(value));
}
default_value = options[i + 2] == NULL ? "" : options[i + 2];
// If value is the same as default, skip it
if (strcmp(value, default_value) != 0) {
fprintf(fp, "%s %s\n", name, value);
}
}
}
static BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP) {
FILE *fp;
int i;
const char *name, *value, **options = mg_get_valid_option_names();
switch (msg) {
case WM_CLOSE:
DestroyWindow(hDlg);
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case ID_SAVE:
EnableWindow(GetDlgItem(hDlg, ID_SAVE), FALSE);
if ((fp = fopen(config_file, "w+")) != NULL) {
save_config(hDlg, fp);
fclose(fp);
mg_stop(ctx);
start_mongoose(__argc, __argv);
}
EnableWindow(GetDlgItem(hDlg, ID_SAVE), TRUE);
break;
case ID_RESET_DEFAULTS:
for (i = 0; options[i] != NULL; i += 3) {
name = options[i + 1];
value = options[i + 2] == NULL ? "" : options[i + 2];
if (is_boolean_option(name)) {
CheckDlgButton(hDlg, ID_CONTROLS + i / 3, !strcmp(value, "yes") ?
BST_CHECKED : BST_UNCHECKED);
} else {
SetWindowText(GetDlgItem(hDlg, ID_CONTROLS + i / 3), value);
}
}
break;
}
for (i = 0; options[i] != NULL; i += 3) {
name = options[i + 1];
if ((is_filename_option(name) || is_directory_option(name)) &&
LOWORD(wParam) == ID_CONTROLS + i / 3 + ID_FILE_BUTTONS_DELTA) {
OPENFILENAME of;
BROWSEINFO bi;
char path[PATH_MAX] = "";
memset(&of, 0, sizeof(of));
of.lStructSize = sizeof(of);
of.hwndOwner = (HWND) hDlg;
of.lpstrFile = path;
of.nMaxFile = sizeof(path);
of.lpstrInitialDir = mg_get_option(ctx, "document_root");
of.Flags = OFN_CREATEPROMPT | OFN_NOCHANGEDIR;
memset(&bi, 0, sizeof(bi));
bi.hwndOwner = (HWND) hDlg;
bi.lpszTitle = "Choose WWW root directory:";
bi.ulFlags = BIF_RETURNONLYFSDIRS;
if (is_directory_option(name)) {
SHGetPathFromIDList(SHBrowseForFolder(&bi), path);
} else {
GetOpenFileName(&of);
}
if (path[0] != '\0') {
SetWindowText(GetDlgItem(hDlg, ID_CONTROLS + i / 3), path);
}
}
}
break;
case WM_INITDIALOG:
SendMessage(hDlg, WM_SETICON,(WPARAM) ICON_SMALL, (LPARAM) hIcon);
SendMessage(hDlg, WM_SETICON,(WPARAM) ICON_BIG, (LPARAM) hIcon);
SetWindowText(hDlg, "Mongoose settings");
SetFocus(GetDlgItem(hDlg, ID_SAVE));
for (i = 0; options[i] != NULL; i += 3) {
name = options[i + 1];
value = mg_get_option(ctx, name);
if (is_boolean_option(name)) {
CheckDlgButton(hDlg, ID_CONTROLS + i / 3, !strcmp(value, "yes") ?
BST_CHECKED : BST_UNCHECKED);
} else {
SetDlgItemText(hDlg, ID_CONTROLS + i / 3, value == NULL ? "" : value);
}
}
break;
default:
break;
}
return FALSE;
}
static void add_control(unsigned char **mem, DLGTEMPLATE *dia, WORD type,
DWORD id, DWORD style, WORD x, WORD y,
WORD cx, WORD cy, const char *caption) {
DLGITEMTEMPLATE *tp;
LPWORD p;
dia->cdit++;
*mem = align(*mem, 3);
tp = (DLGITEMTEMPLATE *) *mem;
tp->id = (WORD)id;
tp->style = style;
tp->dwExtendedStyle = 0;
tp->x = x;
tp->y = y;
tp->cx = cx;
tp->cy = cy;
p = align(*mem + sizeof(*tp), 1);
*p++ = 0xffff;
*p++ = type;
while (*caption != '\0') {
*p++ = (WCHAR) *caption++;
}
*p++ = 0;
p = align(p, 1);
*p++ = 0;
*mem = (unsigned char *) p;
}
static void show_settings_dialog() {
#define HEIGHT 15
#define WIDTH 400
#define LABEL_WIDTH 80
unsigned char mem[4096], *p;
const char **option_names, *long_option_name;
DWORD style;
DLGTEMPLATE *dia = (DLGTEMPLATE *) mem;
WORD i, cl, x, y, width, nelems = 0;
static int guard;
static struct {
DLGTEMPLATE template; // 18 bytes
WORD menu, class;
wchar_t caption[1];
WORD fontsiz;
wchar_t fontface[7];
} dialog_header = {{WS_CAPTION | WS_POPUP | WS_SYSMENU | WS_VISIBLE |
DS_SETFONT | WS_DLGFRAME, WS_EX_TOOLWINDOW, 0, 200, 200, WIDTH, 0},
0, 0, L"", 8, L"Tahoma"};
if (guard == 0) {
guard++;
} else {
return;
}
(void) memset(mem, 0, sizeof(mem));
(void) memcpy(mem, &dialog_header, sizeof(dialog_header));
p = mem + sizeof(dialog_header);
option_names = mg_get_valid_option_names();
for (i = 0; option_names[i] != NULL; i += 3) {
long_option_name = option_names[i + 1];
style = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
x = 10 + (WIDTH / 2) * (nelems % 2);
y = (nelems/2 + 1) * HEIGHT + 5;
width = WIDTH / 2 - 20 - LABEL_WIDTH;
if (is_numeric_options(long_option_name)) {
style |= ES_NUMBER;
cl = 0x81;
style |= WS_BORDER | ES_AUTOHSCROLL;
} else if (is_boolean_option(long_option_name)) {
cl = 0x80;
style |= BS_AUTOCHECKBOX;
} else if (is_filename_option(long_option_name) ||
is_directory_option(long_option_name)) {
style |= WS_BORDER | ES_AUTOHSCROLL;
width -= 20;
cl = 0x81;
add_control(&p, dia, 0x80,
ID_CONTROLS + (i / 3) + ID_FILE_BUTTONS_DELTA,
WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
(WORD) (x + width + LABEL_WIDTH + 5),
y, 15, 12, "...");
} else {
cl = 0x81;
style |= WS_BORDER | ES_AUTOHSCROLL;
}
add_control(&p, dia, 0x82, ID_STATIC, WS_VISIBLE | WS_CHILD,
x, y, LABEL_WIDTH, HEIGHT, long_option_name);
add_control(&p, dia, cl, ID_CONTROLS + (i / 3), style,
(WORD) (x + LABEL_WIDTH), y, width, 12, "");
nelems++;
}
y = (WORD) (((nelems + 1) / 2 + 1) * HEIGHT + 5);
add_control(&p, dia, 0x80, ID_GROUP, WS_CHILD | WS_VISIBLE |
BS_GROUPBOX, 5, 5, WIDTH - 10, y, " Settings ");
y += 10;
add_control(&p, dia, 0x80, ID_SAVE,
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP,
WIDTH - 70, y, 65, 12, "Save Settings");
add_control(&p, dia, 0x80, ID_RESET_DEFAULTS,
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP,
WIDTH - 140, y, 65, 12, "Reset to defaults");
add_control(&p, dia, 0x82, ID_STATIC,
WS_CHILD | WS_VISIBLE | WS_DISABLED,
5, y, 180, 12, server_name);
dia->cy = ((nelems + 1) / 2 + 1) * HEIGHT + 30;
DialogBoxIndirectParam(NULL, dia, NULL, DlgProc, (LPARAM) NULL);
guard--;
}
static int manage_service(int action) {
static const char *service_name = "Mongoose";
SC_HANDLE hSCM = NULL, hService = NULL;
@@ -417,13 +709,18 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
Shell_NotifyIcon(NIM_DELETE, &TrayIcon);
PostQuitMessage(0);
return 0;
case ID_EDIT_CONFIG:
edit_config_file();
case ID_SETTINGS:
show_settings_dialog();
break;
case ID_INSTALL_SERVICE:
case ID_REMOVE_SERVICE:
manage_service(LOWORD(wParam));
break;
case ID_CONNECT:
printf("[%s]\n", get_url_to_first_open_port(ctx));
ShellExecute(NULL, "open", get_url_to_first_open_port(ctx),
NULL, NULL, SW_SHOW);
break;
}
break;
case WM_USER:
@@ -443,7 +740,9 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
AppendMenu(hMenu, MF_STRING | (!service_installed ? MF_GRAYED : 0),
ID_REMOVE_SERVICE, "Deinstall service");
AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, "");
AppendMenu(hMenu, MF_STRING, ID_EDIT_CONFIG, "Edit config file");
AppendMenu(hMenu, MF_STRING, ID_CONNECT, "Start browser");
AppendMenu(hMenu, MF_STRING, ID_SETTINGS, "Edit Settings");
AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, "");
AppendMenu(hMenu, MF_STRING, ID_QUIT, "Exit");
GetCursorPos(&pt);
SetForegroundWindow(hWnd);
@@ -480,10 +779,11 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) {
ShowWindow(hWnd, SW_HIDE);
TrayIcon.cbSize = sizeof(TrayIcon);
TrayIcon.uID = ID_TRAYICON;
TrayIcon.uID = ID_ICON;
TrayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
TrayIcon.hIcon = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(ID_ICON),
IMAGE_ICON, 16, 16, 0);
TrayIcon.hIcon = hIcon = LoadImage(GetModuleHandle(NULL),
MAKEINTRESOURCE(ID_ICON),
IMAGE_ICON, 16, 16, 0);
TrayIcon.hWnd = hWnd;
snprintf(TrayIcon.szTip, sizeof(TrayIcon.szTip), "%s", server_name);
TrayIcon.uCallbackMessage = WM_USER;
@@ -509,7 +809,7 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) {
- (void) openBrowser {
[[NSWorkspace sharedWorkspace]
openURL:[NSURL URLWithString:
[NSString stringWithUTF8String:"http://www.yahoo.com"]]];
[NSString stringWithUTF8String:get_url_to_first_open_port(ctx)]]];
}
- (void) editConfig {
create_config_file(config_file);
-184
View File
@@ -1,184 +0,0 @@
.\" Process this file with
.\" groff -man -Tascii mongoose.1
.\" $Id: mongoose.1,v 1.12 2008/11/29 15:32:42 drozd Exp $
.Dd Sep 23, 2012
.Dt mongoose 1
.Sh NAME
.Nm mongoose
.Nd lightweight web server
.Sh SYNOPSIS
.Nm
.Op Ar config_file
.Op Ar OPTIONS
.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, MD5 authorization,
and basic SSI support.
.Pp
.Nm
does not detach from terminal, and uses current working directory
as the web root, unless
.Fl r
option is specified.
It is possible to specify multiple ports to listen on. For example, to make
mongoose listen on HTTP port 80 and HTTPS port 443, one should start it as:
.Nm
.Fl s Ar cert.pem Fl p Ar 80,443s
.Pp
Unlike other web servers,
.Nm
does not require CGI scripts be put in a special directory. CGI scripts can
be anywhere. CGI (and SSI) files are recognized by the file name pattern.
.Nm
uses shell-like glob patterns with the following syntax:
.Bl -tag -compact -width indent
.It **
Matches everything
.It *
Matches everything but slash character, '/'
.It ?
Matches any character
.It $
Matches the end of the string
.It |
Matches if pattern on the left side or the right side matches. Pattern on the
left side is matched first
.El
All other characters in the pattern match themselves.
.Pp
If no arguments are given,
.Nm
searches for a configuration file called "mongoose.conf" in the same directory
where mongoose binary is located. Alternatively, a file name could be
specified in the command line. Format of the configuration file is the same
as for the command line options except that each option must be specified
on a separate line, leading dashes for option names must be omitted.
Lines beginning with '#' and empty lines are 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 is similar to Apache's
.Ic htdigest
utility.
.It Fl C Ar cgi_pattern
All files that fully match cgi_pattern are treated as CGI.
Default pattern allows CGI files be
anywhere. To restrict CGIs to certain directory, use e.g. "-C /cgi-bin/**.cgi".
Default: "**.cgi$|**.pl$|**.php$"
.It Fl E Ar cgi_environment
Extra environment variables to be passed 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: ""
.It Fl G Ar put_delete_passwords_file
PUT and DELETE passwords file. This must be specified if PUT or
DELETE methods are used. Default: ""
.It Fl I Ar cgi_interpreter
Use
.Ar cgi_interpreter
as a CGI interpreter for all CGI scripts regardless script extension.
Mongoose decides which interpreter to use by looking at
the first line of a CGI script. Default: "".
.It Fl P Ar protect_uri
Comma separated list of URI=PATH pairs, specifying that given URIs
must be protected with respected password files. Default: ""
.It Fl R Ar authentication_domain
Authorization realm. Default: "mydomain.com"
.It Fl S Ar ssi_pattern
All files that fully match ssi_pattern are treated as SSI.
Unknown SSI directives are silently ignored. Currently, two SSI directives
are supported, "include" and "exec". Default: "**.shtml$|**.shtm$"
.It Fl T Ar throttle
Limit download speed for clients.
.Ar throttle
is a comma-separated list of key=value pairs, where
key could be a '*' character (limit for all connections), a subnet in form
x.x.x.x/mask (limit for a given subnet, for example 10.0.0.0/8), or an
URI prefix pattern (limit for the set of URIs, for example /foo/**). The value
is a floating-point number of bytes per second, optionally followed by a
`k' or `m' character, meaning kilobytes and megabytes respectively. A limit
of 0 means unlimited rate. The last matching rule wins. For example,
"*=1k,10.0.0.0/8" means limit everybody to 1 kilobyte per second, but give
people from 10/8 subnet unlimited speed. Default: ""
.It Fl a Ar access_log_file
Access log file. Default: "", no logging is done.
.It Fl d Ar enable_directory_listing
Enable/disable directory listing. Default: "yes"
.It Fl e Ar error_log_file
Error log file. Default: "", no errors are logged.
.It Fl g Ar global_passwords_file
Location of a global passwords file. If set, per-directory .htpasswd files are
ignored, and all requests must be authorised against that file. Default: ""
.It Fl i Ar index_files
Comma-separated list of files to be treated as directory index files.
Default: "index.html,index.htm,index.cgi"
.It Fl l Ar access_control_list
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 setting is to allow all. For example, to allow only
192.168/16 subnet to connect, run "mongoose -0.0.0.0/0,+192.168/16".
Default: ""
.It Fl m Ar extra_mime_types
Extra mime types to recognize, in form
"extension1=type1,extension2=type2,...". Extension must include dot.
Example: "mongoose -m .cpp=plain/text,.java=plain/text". Default: ""
.It Fl p Ar listening_ports
Comma-separated list of ports to listen on. If the port is SSL, a letter 's'
must be appeneded, for example, "-p 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, to bind to a loopback interface
on port 80 and to all interfaces on HTTPS port 443, use
"mongoose -p 127.0.0.1:80,443s". Default: "8080"
.It Fl r Ar document_root
Location of the WWW root directory. Default: "."
.It Fl s Ar ssl_certificate
Location of SSL certificate file. Default: ""
.It Fl t Ar num_threads
Number of worker threads to start. Default: "10"
.It Fl u Ar run_as_user
Switch to given user's credentials after startup. Default: ""
.It Fl w Ar url_rewrite_patterns
Comma-separated list of URL rewrites in the form of
"pattern=substitution,..." If the "pattern" matches some prefix
of the requested URL, then matched prefix gets substituted with "substitution".
For example, "-w /config=/etc,**.doc|**.rtf=/path/to/cgi-bin/handle_doc.cgi"
will serve all URLs that start with "/config" from the "/etc" directory, and
call handle_doc.cgi script for .doc and .rtf file requests. If some pattern
matches, no further matching/substitution is performed
(first matching pattern wins). Use full paths in substitutions. Default: ""
.It Fl x Ar hide_files_patterns
A prefix pattern for the files to hide. Files that match the pattern will not
show up in directory listing and return 404 Not Found if requested. Default: ""
.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 follow the updates. Please refer to http://code.google.com/p/mongoose
for details.
.Pp
.Sh EXAMPLES
.Bl -tag -width indent
.It Nm Fl r Ar /var/www Fl s Ar /etc/cert.pem Fl p Ar 8080,8043s
Start serving files from /var/www. Listen on port 8080 for HTTP, and 8043
for HTTPS connections. Use /etc/cert.pem as SSL certificate file.
.It Nm Fl l 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 w Ar **=/usr/bin/script.cgi
Invoke /usr/bin/script.cgi for every incoming request, regardless of the URL.
.El
.Pp
.Sh COPYRIGHT
.Nm
is licensed under the terms of the MIT license.
.Sh AUTHOR
.An Sergey Lyubka Aq valenok@gmail.com .
+460 -465
View File
File diff suppressed because it is too large. Load diff
+45 -132
View File
@@ -42,122 +42,37 @@ struct mg_request_info {
long remote_ip; // Client's IP address
int remote_port; // Client's port
int is_ssl; // 1 if SSL-ed, 0 if not
int num_headers; // Number of headers
void *user_data; // User data pointer passed to mg_start()
int num_headers; // Number of HTTP headers
struct mg_header {
const char *name; // HTTP header name
const char *value; // HTTP header value
} http_headers[64]; // Maximum 64 headers
void *user_data; // User data pointer passed to mg_start()
void *ev_data; // Event-specific data pointer
};
// Various events on which user-defined callback function is called by Mongoose.
enum mg_event {
// New HTTP request has arrived from the client.
// If callback returns non-NULL, Mongoose stops handling current request.
// ev_data contains NULL.
MG_NEW_REQUEST,
// Mongoose has finished handling the request.
// Callback return value is ignored.
// ev_data contains integer HTTP status code:
// int http_reply_status_code = (long) request_info->ev_data;
MG_REQUEST_COMPLETE,
// HTTP error must be returned to the client.
// If callback returns non-NULL, Mongoose stops handling error.
// ev_data contains HTTP error code:
// int http_reply_status_code = (long) request_info->ev_data;
MG_HTTP_ERROR,
// Mongoose logs a message.
// If callback returns non-NULL, Mongoose stops handling that event.
// ev_data contains a message to be logged:
// const char *log_message = request_info->ev_data;
MG_EVENT_LOG,
// SSL initialization, sent before certificate setup.
// If callback returns non-NULL, Mongoose does not set up certificates.
// ev_data contains server's OpenSSL context:
// SSL_CTX *ssl_context = request_info->ev_data;
MG_INIT_SSL,
// Sent on HTTP connect, before websocket handshake.
// If user callback returns NULL, then mongoose proceeds
// with handshake, otherwise it closes the connection.
// ev_data contains NULL.
MG_WEBSOCKET_CONNECT,
// Handshake has been successfully completed.
// Callback's return value is ignored.
// ev_data contains NULL.
MG_WEBSOCKET_READY,
// Incoming message from the client, data could be read with mg_read().
// If user callback returns non-NULL, mongoose closes the websocket.
// ev_data contains NULL.
MG_WEBSOCKET_MESSAGE,
// Client has closed the connection.
// Callback's return value is ignored.
// ev_data contains NULL.
MG_WEBSOCKET_CLOSE,
// Mongoose tries to open file.
// If callback returns non-NULL, Mongoose will not try to open it, but
// will use the returned value as a pointer to the file data. This allows
// for example to serve files from memory.
// ev_data contains file path, including document root path.
// Upon return, ev_data should return file size, which should be a long int.
//
// const char *file_name = request_info->ev_data;
// if (strcmp(file_name, "foo.txt") == 0) {
// request_info->ev_data = (void *) (long) 4;
// return "data";
// }
// return NULL;
//
// Note that this even is sent multiple times during one request. Each
// time mongoose tries to open or stat the file, this event is sent, e.g.
// for opening .htpasswd file, stat-ting requested file, opening requested
// file, etc.
MG_OPEN_FILE,
// Mongoose initializes Lua server page. Sent only if Lua support is enabled.
// Callback's return value is ignored.
// ev_data contains lua_State pointer.
MG_INIT_LUA,
// Mongoose has uploaded file to a temporary directory.
// Callback's return value is ignored.
// ev_data contains NUL-terminated file name.
MG_UPLOAD,
// This structure needs to be passed to mg_start(), to let mongoose know
// which callbacks to invoke. For detailed description, see
// https://github.com/valenok/mongoose/blob/master/UserManual.md
struct mg_callbacks {
int (*begin_request)(struct mg_connection *);
void (*end_request)(const struct mg_connection *, int reply_status_code);
int (*log_message)(const struct mg_connection *, const char *message);
int (*init_ssl)(void *ssl_context);
int (*websocket_connect)(const struct mg_connection *);
void (*websocket_ready)(struct mg_connection *);
int (*websocket_data)(struct mg_connection *);
const char * (*open_file)(const struct mg_connection *,
const char *path, size_t *data_len);
void (*init_lua)(struct mg_connection *, void *lua_context);
void (*upload)(struct mg_connection *, const char *file_name);
};
// Prototype for the user-defined function. Mongoose calls this function
// on every MG_* event.
//
// Parameters:
// event: which event has been triggered.
// conn: opaque connection handler. Could be used to read, write data to the
// client, etc. See functions below that have "mg_connection *" arg.
//
// Return:
// If handler returns non-NULL, that means that handler has processed the
// request by sending appropriate HTTP reply to the client. Mongoose treats
// the request as served.
// If handler returns NULL, that means that handler has not processed
// the request. Handler must not send any data to the client in this case.
// Mongoose proceeds with request handling as if nothing happened.
typedef void *(*mg_callback_t)(enum mg_event event, struct mg_connection *conn);
// Start web server.
//
// Parameters:
// callback: user defined event handling function or NULL.
// callbacks: mg_callbacks structure with user-defined callbacks.
// options: NULL terminated list of option_name, option_value pairs that
// specify Mongoose configuration parameters.
//
@@ -179,8 +94,9 @@ typedef void *(*mg_callback_t)(enum mg_event event, struct mg_connection *conn);
//
// Return:
// web server context, or NULL on error.
struct mg_context *mg_start(mg_callback_t callback, void *user_data,
const char **options);
struct mg_context *mg_start(const struct mg_callbacks *callbacks,
void *user_data,
const char **configuration_options);
// Stop the web server.
@@ -236,12 +152,6 @@ struct mg_request_info *mg_get_request_info(struct mg_connection *);
int mg_write(struct mg_connection *, const void *buf, size_t len);
// Send data to the browser using printf() semantics.
//
// Works exactly like mg_write(), but allows to do message formatting.
// Below are the macros for enabling compiler-specific checks for
// printf-like arguments.
#undef PRINTF_FORMAT_STRING
#if _MSC_VER >= 1400
#include <sal.h>
@@ -260,6 +170,11 @@ int mg_write(struct mg_connection *, const void *buf, size_t len);
#define PRINTF_ARGS(x, y)
#endif
// Send data to the browser using printf() semantics.
//
// Works exactly like mg_write(), but allows to do message formatting.
// Below are the macros for enabling compiler-specific checks for
// printf-like arguments.
int mg_printf(struct mg_connection *,
PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
@@ -316,32 +231,30 @@ int mg_get_cookie(const struct mg_connection *,
const char *cookie_name, char *buf, size_t buf_len);
// Connect to the remote web server.
// Download data from the remote web server.
// host: host name to connect to, e.g. "foo.com", or "10.12.40.1".
// port: port number, e.g. 80.
// use_ssl: wether to use SSL connection.
// error_buffer, error_buffer_size: error message placeholder.
// request_fmt,...: HTTP request.
// Return:
// On success, valid pointer to the new connection
// On error, NULL
struct mg_connection *mg_connect(struct mg_context *ctx,
const char *host, int port, int use_ssl);
// On success, valid pointer to the new connection, suitable for mg_read().
// On error, NULL. error_buffer contains error message.
// Example:
// char ebuf[100];
// struct mg_connection *conn;
// conn = mg_download("google.com", 80, 0, ebuf, sizeof(ebuf),
// "%s", "GET / HTTP/1.0\r\nHost: google.com\r\n\r\n");
struct mg_connection *mg_download(const char *host, int port, int use_ssl,
char *error_buffer, size_t error_buffer_size,
PRINTF_FORMAT_STRING(const char *request_fmt),
...) PRINTF_ARGS(6, 7);
// Close the connection opened by mg_connect().
// Close the connection opened by mg_download().
void mg_close_connection(struct mg_connection *conn);
// Download given URL to a given file.
// url: URL to download
// path: file name where to save the data
// request_info: pointer to a structure that will hold parsed reply headers
// buf, bul_len: a buffer for the reply headers
// Return:
// On error, NULL
// On success, opened file stream to the downloaded contents. The stream
// is positioned to the end of the file. It is the user's responsibility
// to fclose() the opened file stream.
FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path,
char *buf, size_t buf_len, struct mg_request_info *request_info);
// File upload functionality. Each uploaded file gets saved into a temporary
// file and MG_UPLOAD event is sent.
// Return number of uploaded files.
+45 -33
View File
@@ -1,40 +1,52 @@
<html>
<p>Prime numbers from 0 to 100, calculated by Lua:</p>
<?
<?
-- Lua server pages have full control over the output, including HTTP
-- headers they send to the client. Send HTTP headers:
print('HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n')
?><html><body>
function is_prime(n)
if n <= 0 then return false end
if n <= 2 then return true end
if (n % 2 == 0) then return false end
for i = 3, n / 2, 2 do
if (n % i == 0) then return false end
end
return true
end
<p>This is an example Lua server page served by
<a href="http://code.google.com/p/mongoose">Mongoose web server</a>.
Mongoose has Lua, Sqlite, and other functionality built in the binary.
This example page stores the request in the Sqlite database, and shows
all requests done previously.</p>
for i = 1, 100 do
if is_prime(i) then print('<span>' .. i .. '</span>&nbsp;') end
end
<pre>
<?
-- Open database
local db = sqlite3.open('requests.db')
?>
-- Setup a trace callback, to show SQL statements we'll be executing.
-- db:trace(function(data, sql) print('Executing: ' .. sql .. '\n') end, nil)
<p>Reading POST data from Lua (click submit):</p>
<form method="POST"><input type="text" name="t1"/><input type="submit"></form>
-- Create a table if it is not created already
db:exec([[
CREATE TABLE IF NOT EXISTS requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp NOT NULL,
method NOT NULL,
uri NOT NULL,
user_agent
);
]])
<pre>
POST data: [<? post_data = read() print(post_data) ?>]
request method: [<? print(request_info.request_method) ?>]
IP/port: [<? print(request_info.remote_ip, ':', request_info.remote_port) ?>]
URI: [<? print(request_info.uri) ?>]
HTTP version [<? print(request_info.http_version) ?>]
HEADERS:
<?
for name, value in pairs(request_info.http_headers) do
print(name, ':', value, '\n')
end
?>
</pre>
-- Add entry about this request
local stmt = db:prepare(
'INSERT INTO requests VALUES(NULL, datetime("now"), ?, ?, ?);');
stmt:bind_values(request_info.request_method, request_info.uri,
request_info.http_headers['User-Agent'])
stmt:step()
stmt:finalize()
-- Show all previous records
print('Previous requests:\n')
stmt = db:prepare('SELECT * FROM requests ORDER BY id DESC;')
while stmt:step() == sqlite3.ROW do
local v = stmt:get_values()
print(v[1] .. ' ' .. v[2] .. ' ' .. v[3] .. ' '
.. v[4] .. ' ' .. v[5] .. '\n')
end
</html>
-- Close database
db:close()
?>
</pre></body></html>
+11 -88
View File
@@ -146,11 +146,6 @@ if ($^O =~ /darwin|bsd|linux/) {
}
}
if (scalar(@ARGV) > 0 and $ARGV[0] eq 'embedded') {
do_embedded_test();
exit 0;
}
if (scalar(@ARGV) > 0 and $ARGV[0] eq 'unit') {
do_unit_test();
exit 0;
@@ -158,20 +153,21 @@ if (scalar(@ARGV) > 0 and $ARGV[0] eq 'unit') {
# Make sure we load config file if no options are given.
# Command line options override config files settings
write_file($config, "access_log_file access.log\nlistening_ports 12345\n");
spawn("$exe -p $port");
write_file($config, "access_log_file access.log\n" .
"listening_ports 127.0.0.1:12345\n");
spawn("$exe -p 127.0.0.1:$port");
o("GET /test/hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'Loading config file');
unlink $config;
kill_spawned_child();
# Spawn the server on port $port
my $cmd = "$exe ".
"-listening_ports $port ".
"-listening_ports 127.0.0.1:$port ".
"-access_log_file access.log ".
"-error_log_file debug.log ".
"-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
"-extra_mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " .
'-put_delete_passwords_file test/passfile ' .
'-put_delete_auth_file test/passfile ' .
'-access_control_list -0.0.0.0/0,+127.0.0.1 ' .
"-document_root $root ".
"-hide_files_patterns **exploit.pl ".
@@ -219,11 +215,10 @@ write_file("$root/a+.txt", '');
o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in 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');
o("GET / HTTPX/1.0\r\n\r\n", '^HTTP/1.1 500', 'Bad HTTP Version', 0);
o("GET / HTTP/x.1\r\n\r\n", '^HTTP/1.1 505', 'Bad HTTP maj Version', 0);
o("GET / HTTP/1.1z\r\n\r\n", '^HTTP/1.1 505', 'Bad HTTP min Version', 0);
o("GET / HTTP/02.0\r\n\r\n", '^HTTP/1.1 505', 'HTTP Version >1.1', 0);
# File with leading single dot
o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1');
@@ -426,7 +421,6 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
do_PUT_test();
kill_spawned_child();
do_unit_test();
do_embedded_test();
}
sub do_PUT_test {
@@ -456,79 +450,8 @@ sub do_PUT_test {
}
sub do_unit_test {
my $cmd = "cc -g -W -Wall -o $unit_test_exe $root/unit_test.c -I. ".
"-pthread -DNO_SSL ";
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 unit test");
system($unit_test_exe) == 0 or fail("Unit test failed!");
}
sub do_embedded_test {
my $cmd = "cc -W -Wall -o $embed_exe $root/embed.c mongoose.c -I. ".
"-pthread -DNO_SSL -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 9', 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 10', 0);
# Other methods should also work
o("PUT /test_get_var HTTP/1.0\nContent-Length: 10\n\n".
"my_var=foo", 'Value: \[foo\]', 'mg_get_var 11', 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\].'.
'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 /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);
kill_spawned_child();
my $target = on_windows() ? 'w' : 'u';
system("make $target") == 0 or fail("Unit test failed!");
}
print "SUCCESS! All tests passed.\n";
+317 -115
View File
@@ -1,4 +1,27 @@
// Copyright (c) 2004-2013 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.
//
// Unit test for the mongoose web server. Tests embedded API.
#define USE_WEBSOCKET
#define USE_LUA
#include "mongoose.c"
#define FATAL(str, line) do { \
@@ -7,24 +30,43 @@
} while (0)
#define ASSERT(expr) do { if (!(expr)) FATAL(#expr, __LINE__); } while (0)
#define UNUSED_PORT "33796"
#define HTTP_PORT "56789"
#define HTTPS_PORT "56790"
#define HTTP_PORT2 "56791"
#define LISTENING_ADDR \
"127.0.0.1:" HTTP_PORT "r" \
",127.0.0.1:" HTTPS_PORT "s" \
",127.0.0.1:" HTTP_PORT2
static void test_parse_http_request() {
#ifndef _WIN32
#define __cdecl
#endif
static void test_parse_http_message() {
struct mg_request_info ri;
char req1[] = "GET / HTTP/1.1\r\n\r\n";
char req2[] = "BLAH / HTTP/1.1\r\n\r\n";
char req3[] = "GET / HTTP/1.1\r\nBah\r\n";
char req4[] = "GET / HTTP/1.1\r\nA: foo bar\r\nB: bar\r\nbaz\r\n\r\n";
char req5[] = "GET / HTTP/1.1\r\n\r\n";
char req6[] = "G";
char req7[] = " blah ";
char req8[] = " HTTP/1.1 200 OK \n\n";
ASSERT(parse_http_request(req1, sizeof(req1), &ri) == sizeof(req1) - 1);
ASSERT(parse_http_message(req1, sizeof(req1), &ri) == sizeof(req1) - 1);
ASSERT(strcmp(ri.http_version, "1.1") == 0);
ASSERT(ri.num_headers == 0);
ASSERT(parse_http_request(req2, sizeof(req2), &ri) == -1);
ASSERT(parse_http_request(req3, sizeof(req3), &ri) == -1);
ASSERT(parse_http_message(req2, sizeof(req2), &ri) == -1);
ASSERT(parse_http_message(req3, sizeof(req3), &ri) == 0);
ASSERT(parse_http_message(req6, sizeof(req6), &ri) == 0);
ASSERT(parse_http_message(req7, sizeof(req7), &ri) == 0);
ASSERT(parse_http_message("", 0, &ri) == 0);
ASSERT(parse_http_message(req8, sizeof(req8), &ri) == sizeof(req8) - 1);
// TODO(lsm): Fix this. Header value may span multiple lines.
ASSERT(parse_http_request(req4, sizeof(req4), &ri) == sizeof(req4) - 1);
ASSERT(parse_http_message(req4, sizeof(req4), &ri) == sizeof(req4) - 1);
ASSERT(strcmp(ri.http_version, "1.1") == 0);
ASSERT(ri.num_headers == 3);
ASSERT(strcmp(ri.http_headers[0].name, "A") == 0);
ASSERT(strcmp(ri.http_headers[0].value, "foo bar") == 0);
@@ -33,7 +75,9 @@ static void test_parse_http_request() {
ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0);
ASSERT(strcmp(ri.http_headers[2].value, "") == 0);
// TODO(lsm): add more tests.
ASSERT(parse_http_message(req5, sizeof(req5), &ri) == sizeof(req5) - 1);
ASSERT(strcmp(ri.request_method, "GET") == 0);
ASSERT(strcmp(ri.http_version, "1.1") == 0);
}
static void test_should_keep_alive(void) {
@@ -46,7 +90,8 @@ static void test_should_keep_alive(void) {
memset(&conn, 0, sizeof(conn));
conn.ctx = &ctx;
parse_http_request(req1, sizeof(req1), &conn.request_info);
ASSERT(parse_http_message(req1, sizeof(req1), &conn.request_info) ==
sizeof(req1) - 1);
ctx.config[ENABLE_KEEP_ALIVE] = "no";
ASSERT(should_keep_alive(&conn) == 0);
@@ -58,13 +103,13 @@ static void test_should_keep_alive(void) {
ASSERT(should_keep_alive(&conn) == 0);
conn.must_close = 0;
parse_http_request(req2, sizeof(req2), &conn.request_info);
parse_http_message(req2, sizeof(req2), &conn.request_info);
ASSERT(should_keep_alive(&conn) == 0);
parse_http_request(req3, sizeof(req3), &conn.request_info);
parse_http_message(req3, sizeof(req3), &conn.request_info);
ASSERT(should_keep_alive(&conn) == 0);
parse_http_request(req4, sizeof(req4), &conn.request_info);
parse_http_message(req4, sizeof(req4), &conn.request_info);
ASSERT(should_keep_alive(&conn) == 1);
conn.status_code = 401;
@@ -121,106 +166,205 @@ static void test_remove_double_dots() {
size_t i;
for (i = 0; i < ARRAY_SIZE(data); i++) {
printf("[%s] -> [%s]\n", data[i].before, data[i].after);
remove_double_dots_and_double_slashes(data[i].before);
ASSERT(strcmp(data[i].before, data[i].after) == 0);
}
}
static char *read_file(const char *path, int *size) {
FILE *fp;
struct stat st;
char *data = NULL;
if ((fp = fopen(path, "rb")) != NULL && !fstat(fileno(fp), &st)) {
*size = (int) st.st_size;
ASSERT((data = malloc(*size)) != NULL);
ASSERT(fread(data, 1, *size, fp) == (size_t) *size);
fclose(fp);
}
return data;
}
static const char *fetch_data = "hello world!\n";
static const char *inmemory_file_data = "hi there";
static void *event_handler(enum mg_event event, struct mg_connection *conn) {
const struct mg_request_info *request_info = mg_get_request_info(conn);
static const char *upload_filename = "upload_test.txt";
static const char *upload_ok_message = "upload successful";
if (event == MG_NEW_REQUEST && !strcmp(request_info->uri, "/data")) {
static const char *open_file_cb(const struct mg_connection *conn,
const char *path, size_t *size) {
(void) conn;
if (!strcmp(path, "./blah")) {
*size = strlen(inmemory_file_data);
return inmemory_file_data;
}
return NULL;
}
static void upload_cb(struct mg_connection *conn, const char *path) {
char *p1, *p2;
int len1, len2;
ASSERT(!strcmp(path, "./upload_test.txt"));
ASSERT((p1 = read_file("mongoose.c", &len1)) != NULL);
ASSERT((p2 = read_file(path, &len2)) != NULL);
ASSERT(len1 == len2);
ASSERT(memcmp(p1, p2, len1) == 0);
free(p1), free(p2);
remove(upload_filename);
mg_printf(conn, "HTTP/1.0 200 OK\r\nContent-Length: %d\r\n\r\n%s",
(int) strlen(upload_ok_message), upload_ok_message);
}
static int begin_request_handler_cb(struct mg_connection *conn) {
const struct mg_request_info *ri = mg_get_request_info(conn);
if (!strcmp(ri->uri, "/data")) {
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
"Content-Length: %d\r\n"
"Content-Type: text/plain\r\n\r\n"
"%s", (int) strlen(fetch_data), fetch_data);
return "";
} else if (event == MG_OPEN_FILE) {
const char *path = request_info->ev_data;
printf("%s: [%s]\n", __func__, path);
if (strcmp(path, "./blah") == 0) {
mg_get_request_info(conn)->ev_data =
(void *) (int) strlen(inmemory_file_data);
return (void *) inmemory_file_data;
}
} else if (event == MG_EVENT_LOG) {
printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data);
return 1;
}
return NULL;
if (!strcmp(ri->uri, "/upload")) {
ASSERT(mg_upload(conn, ".") == 1);
}
return 0;
}
static void test_mg_fetch(void) {
static const char *options[] = {
"document_root", ".",
"listening_ports", UNUSED_PORT,
NULL,
};
char buf[2000], buf2[2000];
int n, length;
static int log_message_cb(const struct mg_connection *conn, const char *msg) {
(void) conn;
printf("%s\n", msg);
return 0;
}
static const struct mg_callbacks CALLBACKS = {
&begin_request_handler_cb, NULL, &log_message_cb, NULL, NULL, NULL, NULL,
&open_file_cb, NULL, &upload_cb
};
static const char *OPTIONS[] = {
"document_root", ".",
"listening_ports", LISTENING_ADDR,
"enable_keep_alive", "yes",
"ssl_certificate", "build/ssl_cert.pem",
NULL,
};
static char *read_conn(struct mg_connection *conn, int *size) {
char buf[100], *data = NULL;
int len;
*size = 0;
while ((len = mg_read(conn, buf, sizeof(buf))) > 0) {
*size += len;
ASSERT((data = realloc(data, *size)) != NULL);
memcpy(data + *size - len, buf, len);
}
return data;
}
static void test_mg_download(void) {
char *p1, *p2, ebuf[100];
int len1, len2, port = atoi(HTTPS_PORT);
struct mg_connection *conn;
struct mg_context *ctx;
struct mg_request_info ri;
const char *tmp_file = "temporary_file_name_for_unit_test.txt";
struct file file;
FILE *fp;
ASSERT((ctx = mg_start(event_handler, NULL, options)) != NULL);
ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
// Failed fetch, pass invalid URL
ASSERT(mg_fetch(ctx, "localhost", tmp_file, buf, sizeof(buf), &ri) == NULL);
ASSERT(mg_fetch(ctx, "localhost:" UNUSED_PORT, tmp_file,
buf, sizeof(buf), &ri) == NULL);
ASSERT(mg_fetch(ctx, "http://$$$.$$$", tmp_file,
buf, sizeof(buf), &ri) == NULL);
ASSERT(mg_download(NULL, port, 0, ebuf, sizeof(ebuf), "%s", "") == NULL);
ASSERT(mg_download("localhost", 0, 0, ebuf, sizeof(ebuf), "%s", "") == NULL);
ASSERT(mg_download("localhost", port, 1, ebuf, sizeof(ebuf),
"%s", "") == NULL);
// Failed fetch, pass invalid file name
ASSERT(mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/data",
"/this/file/must/not/exist/ever",
buf, sizeof(buf), &ri) == NULL);
// Fetch nonexistent file, should see 404
ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
"GET /gimbec HTTP/1.0\r\n\r\n")) != NULL);
ASSERT(strcmp(conn->request_info.uri, "404") == 0);
mg_close_connection(conn);
// Successful fetch
ASSERT((fp = mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/data",
tmp_file, buf, sizeof(buf), &ri)) != NULL);
ASSERT(ri.num_headers == 2);
ASSERT(!strcmp(ri.request_method, "HTTP/1.1"));
ASSERT(!strcmp(ri.uri, "200"));
ASSERT(!strcmp(ri.http_version, "OK"));
ASSERT((length = ftell(fp)) == (int) strlen(fetch_data));
fseek(fp, 0, SEEK_SET);
ASSERT(fread(buf2, 1, length, fp) == (size_t) length);
ASSERT(memcmp(buf2, fetch_data, length) == 0);
fclose(fp);
ASSERT((conn = mg_download("google.com", 443, 1, ebuf, sizeof(ebuf), "%s",
"GET / HTTP/1.0\r\n\r\n")) != NULL);
mg_close_connection(conn);
// Fetch big file, mongoose.c
ASSERT((fp = mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/mongoose.c",
tmp_file, buf, sizeof(buf), &ri)) != NULL);
ASSERT(mg_stat(fc(ctx), "mongoose.c", &file));
ASSERT(file.size == ftell(fp));
ASSERT(!strcmp(ri.request_method, "HTTP/1.1"));
// Fetch mongoose.c, should succeed
ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
"GET /mongoose.c HTTP/1.0\r\n\r\n")) != NULL);
ASSERT(!strcmp(conn->request_info.uri, "200"));
ASSERT((p1 = read_conn(conn, &len1)) != NULL);
ASSERT((p2 = read_file("mongoose.c", &len2)) != NULL);
ASSERT(len1 == len2);
ASSERT(memcmp(p1, p2, len1) == 0);
free(p1), free(p2);
mg_close_connection(conn);
// Fetch nonexistent file, /blah
ASSERT((fp = mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/boo",
tmp_file, buf, sizeof(buf), &ri)) != NULL);
ASSERT(!mg_strcasecmp(ri.uri, "404"));
fclose(fp);
// Fetch in-memory file, should succeed.
ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
"GET /blah HTTP/1.1\r\n\r\n")) != NULL);
ASSERT((p1 = read_conn(conn, &len1)) != NULL);
ASSERT(len1 == (int) strlen(inmemory_file_data));
ASSERT(memcmp(p1, inmemory_file_data, len1) == 0);
free(p1);
mg_close_connection(conn);
// Fetch existing inmemory file
ASSERT((fp = mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/blah",
tmp_file, buf, sizeof(buf), &ri)) != NULL);
ASSERT(!mg_strcasecmp(ri.uri, "200"));
n = ftell(fp);
fseek(fp, 0, SEEK_SET);
printf("%s %d %d [%.*s]\n", __func__, n, (int) feof(fp), n, buf2);
n = fread(buf2, 1, n, fp);
printf("%s %d %d [%.*s]\n", __func__, n, (int) feof(fp), n, buf2);
ASSERT((size_t) ftell(fp) == (size_t) strlen(inmemory_file_data));
ASSERT(!memcmp(inmemory_file_data, buf2, ftell(fp)));
fclose(fp);
// Test SSL redirect, IP address
ASSERT((conn = mg_download("localhost", atoi(HTTP_PORT), 0,
ebuf, sizeof(ebuf), "%s",
"GET /foo HTTP/1.1\r\n\r\n")) != NULL);
ASSERT(strcmp(conn->request_info.uri, "302") == 0);
ASSERT(strcmp(mg_get_header(conn, "Location"),
"https://127.0.0.1:" HTTPS_PORT "/foo") == 0);
mg_close_connection(conn);
remove(tmp_file);
// Test SSL redirect, Host:
ASSERT((conn = mg_download("localhost", atoi(HTTP_PORT), 0,
ebuf, sizeof(ebuf), "%s",
"GET /foo HTTP/1.1\r\nHost: a.b:77\n\n")) != NULL);
ASSERT(strcmp(conn->request_info.uri, "302") == 0);
ASSERT(strcmp(mg_get_header(conn, "Location"),
"https://a.b:" HTTPS_PORT "/foo") == 0);
mg_close_connection(conn);
mg_stop(ctx);
}
static int alloc_printf(char **buf, size_t size, char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
return alloc_vprintf(buf, size, fmt, ap);
}
static void test_mg_upload(void) {
static const char *boundary = "OOO___MY_BOUNDARY___OOO";
struct mg_context *ctx;
struct mg_connection *conn;
char ebuf[100], buf[20], *file_data, *post_data = NULL;
int file_len, post_data_len;
ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
ASSERT((file_data = read_file("mongoose.c", &file_len)) != NULL);
post_data_len = alloc_printf(&post_data, 0,
"--%s\r\n"
"Content-Disposition: form-data; "
"name=\"file\"; "
"filename=\"%s\"\r\n\r\n"
"%.*s\r\n"
"--%s\r\n",
boundary, upload_filename,
file_len, file_data, boundary);
ASSERT(post_data_len > 0);
ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
ebuf, sizeof(ebuf),
"POST /upload HTTP/1.1\r\n"
"Content-Length: %d\r\n"
"Content-Type: multipart/form-data; "
"boundary=%s\r\n\r\n"
"%.*s", post_data_len, boundary,
post_data_len, post_data)) != NULL);
free(file_data), free(post_data);
ASSERT(mg_read(conn, buf, sizeof(buf)) == (int) strlen(upload_ok_message));
ASSERT(memcmp(buf, upload_ok_message, strlen(upload_ok_message)) == 0);
mg_close_connection(conn);
mg_stop(ctx);
}
@@ -232,7 +376,6 @@ static void test_base64_encode(void) {
for (i = 0; in[i] != NULL; i++) {
base64_encode((unsigned char *) in[i], strlen(in[i]), buf);
printf("[%s] [%s]\n", out[i], buf);
ASSERT(!strcmp(buf, out[i]));
}
}
@@ -297,10 +440,9 @@ static void check_lua_expr(lua_State *L, const char *expr, const char *value) {
char buf[100];
snprintf(buf, sizeof(buf), "%s = %s", var_name, expr);
luaL_dostring(L, buf);
(void) luaL_dostring(L, buf);
lua_getglobal(L, var_name);
v = lua_tostring(L, -1);
printf("%s: %s: [%s] [%s]\n", __func__, expr, v == NULL ? "null" : v, value);
ASSERT((value == NULL && v == NULL) ||
(value != NULL && v != NULL && !strcmp(value, v)));
}
@@ -312,13 +454,12 @@ static void test_lua(void) {
char http_request[] = "POST /foo/bar HTTP/1.1\r\n"
"Content-Length: 12\r\n"
"Connection: close\r\n\r\nhello world!";
const char *page = "<? print('hi') ?>";
lua_State *L = luaL_newstate();
conn.ctx = &ctx;
conn.buf = http_request;
conn.buf_size = conn.data_len = strlen(http_request);
conn.request_len = parse_http_request(conn.buf, conn.data_len,
conn.request_len = parse_http_message(conn.buf, conn.data_len,
&conn.request_info);
conn.content_len = conn.data_len - conn.request_len;
@@ -332,33 +473,16 @@ static void test_lua(void) {
check_lua_expr(L, "request_info.remote_ip", "0");
check_lua_expr(L, "request_info.http_headers['Content-Length']", "12");
check_lua_expr(L, "request_info.http_headers['Connection']", "close");
luaL_dostring(L, "post = read()");
(void) luaL_dostring(L, "post = read()");
check_lua_expr(L, "# post", "12");
check_lua_expr(L, "post", "hello world!");
lua_close(L);
}
#endif
static void *user_data_tester(enum mg_event event, struct mg_connection *conn) {
struct mg_request_info *ri = mg_get_request_info(conn);
ASSERT(ri->user_data == (void *) 123);
ASSERT(event == MG_NEW_REQUEST);
return NULL;
}
static void test_user_data(void) {
static const char *options[] = {"listening_ports", UNUSED_PORT, NULL};
struct mg_context *ctx;
ASSERT((ctx = mg_start(user_data_tester, (void *) 123, options)) != NULL);
ASSERT(ctx->user_data == (void *) 123);
call_user(fc(ctx), MG_NEW_REQUEST);
}
static void test_mg_stat(void) {
static struct mg_context ctx;
struct file file;
memset(&file, 'A', sizeof(file));
struct file file = STRUCT_FILE_INITIALIZER;
ASSERT(!mg_stat(fc(&ctx), " does not exist ", &file));
}
@@ -380,21 +504,99 @@ static void test_skip_quoted(void) {
#endif
}
int main(void) {
static void test_alloc_vprintf(void) {
char buf[MG_BUF_LEN], *p = buf;
ASSERT(alloc_printf(&p, sizeof(buf), "%s", "hi") == 2);
ASSERT(p == buf);
ASSERT(alloc_printf(&p, sizeof(buf), "%s", "") == 0);
ASSERT(alloc_printf(&p, sizeof(buf), "") == 0);
// Pass small buffer, make sure alloc_printf allocates
ASSERT(alloc_printf(&p, 1, "%s", "hello") == 5);
ASSERT(p != buf);
free(p);
}
static void test_request_replies(void) {
char ebuf[100];
int i, port = atoi(HTTPS_PORT);
struct mg_connection *conn;
struct mg_context *ctx;
static struct { const char *request, *reply_regex; } tests[] = {
{
"GET test/hello.txt HTTP/1.0\r\nRange: bytes=3-5\r\n\r\n",
"^HTTP/1.1 206 Partial Content"
},
{NULL, NULL},
};
ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
for (i = 0; tests[i].request != NULL; i++) {
ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
tests[i].request)) != NULL);
mg_close_connection(conn);
}
mg_stop(ctx);
}
static int api_callback(struct mg_connection *conn) {
struct mg_request_info *ri = mg_get_request_info(conn);
char post_data[100] = "";
ASSERT(ri->user_data == (void *) 123);
ASSERT(ri->num_headers == 2);
ASSERT(strcmp(mg_get_header(conn, "host"), "blah.com") == 0);
ASSERT(mg_read(conn, post_data, sizeof(post_data)) == 3);
ASSERT(memcmp(post_data, "b=1", 3) == 0);
ASSERT(ri->query_string != NULL);
ASSERT(ri->remote_ip > 0);
ASSERT(ri->remote_port > 0);
ASSERT(strcmp(ri->http_version, "1.0") == 0);
mg_printf(conn, "HTTP/1.0 200 OK\r\n\r\n");
return 1;
}
static void test_api_calls(void) {
char ebuf[100];
struct mg_callbacks callbacks;
struct mg_connection *conn;
struct mg_context *ctx;
static const char *request = "POST /?a=%20&b=&c=xx HTTP/1.0\r\n"
"Host: blah.com\n" // More spaces before
"content-length: 3\r\n" // Lower case header name
"\r\nb=123456"; // Content size > content-length, test for mg_read()
memset(&callbacks, 0, sizeof(callbacks));
callbacks.begin_request = api_callback;
ASSERT((ctx = mg_start(&callbacks, (void *) 123, OPTIONS)) != NULL);
ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
ebuf, sizeof(ebuf), "%s", request)) != NULL);
mg_close_connection(conn);
mg_stop(ctx);
}
int __cdecl main(void) {
test_alloc_vprintf();
test_base64_encode();
test_match_prefix();
test_remove_double_dots();
test_should_keep_alive();
test_parse_http_request();
test_mg_fetch();
test_parse_http_message();
test_mg_download();
test_mg_get_var();
test_set_throttle();
test_next_option();
test_user_data();
test_mg_stat();
test_skip_quoted();
test_mg_upload();
test_request_replies();
test_api_calls();
#ifdef USE_LUA
test_lua();
#endif
test_skip_quoted();
printf("%s\n", "PASSED");
return 0;
}