Commit 9178fbd1 authored by jmucchiello's avatar jmucchiello

Merge pull request #1 from valenok/master

update
parents fcbc36ec b2b43ab0
# This file is part of Mongoose project, http://code.google.com/p/mongoose # This Makefile is part of Mongoose web server project,
# $Id: Makefile 473 2009-09-02 11:20:06Z valenok $ # https://github.com/valenok/mongoose
#
PROG= mongoose # Example custom build:
# COPT="-g -O0 -DNO_SSL_DL -DUSE_LUA -llua -lcrypto -lssl" make linux
all: #
@echo "make (linux|bsd|solaris|mac|windows|mingw|cygwin)" # Flags are:
# Possible COPT values: (in brackets are rough numbers for 'gcc -O2' on i386)
# -DHAVE_MD5 - use system md5 library (-2kb) # -DHAVE_MD5 - use system md5 library (-2kb)
# -DNDEBUG - strip off all debug code (-5kb) # -DNDEBUG - strip off all debug code (-5kb)
# -DDEBUG - build debug version (very noisy) (+7kb) # -DDEBUG - build debug version (very noisy) (+7kb)
...@@ -18,61 +16,12 @@ all: ...@@ -18,61 +16,12 @@ all:
# -DCRYPTO_LIB=\"libcrypto.so.<version>\" - use system versioned CRYPTO so # -DCRYPTO_LIB=\"libcrypto.so.<version>\" - use system versioned CRYPTO so
# -DUSE_LUA - embed Lua in Mongoose (+100kb) # -DUSE_LUA - embed Lua in Mongoose (+100kb)
PROG = mongoose
########################################################################## CFLAGS = -std=c99 -O2 -W -Wall -pedantic -pthread $(COPT)
### UNIX build: linux, bsd, mac, rtems
##########################################################################
# To build with Lua, download and unzip Lua 5.2.1 source code into the # 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 = 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_SOURCES = $(LUA)/lapi.c $(LUA)/lcode.c $(LUA)/lctype.c \
$(LUA)/ldebug.c $(LUA)/ldo.c $(LUA)/ldump.c \ $(LUA)/ldebug.c $(LUA)/ldo.c $(LUA)/ldump.c \
$(LUA)/lfunc.c $(LUA)/lgc.c $(LUA)/llex.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 \ ...@@ -86,47 +35,104 @@ LUA_SOURCES = $(LUA)/lapi.c $(LUA)/lcode.c $(LUA)/lctype.c \
$(LUA)/loadlib.c $(LUA)/linit.c $(LUA)/loadlib.c $(LUA)/linit.c
LUA_OBJECTS = $(LUA_SOURCES:%.c=%.o) LUA_OBJECTS = $(LUA_SOURCES:%.c=%.o)
CYA_SOURCES = $(CYA)/src/cyassl_int.c $(CYA)/src/cyassl_io.c \ # Using Visual Studio 6.0. To build Mongoose:
$(CYA)/src/keys.c $(CYA)/src/tls.c $(CYA)/src/ssl.c \ # Set MSVC variable below to where VS 6.0 is installed on your system
$(CYA)/ctaocrypt/src/aes.c $(CYA)/ctaocrypt/src/arc4.c \ # Run "PATH_TO_VC6\bin\nmake windows"
$(CYA)/ctaocrypt/src/asn.c $(CYA)/ctaocrypt/src/coding.c \ MSVC = ../vc6
$(CYA)/ctaocrypt/src/ctc_asm.c $(CYA)/ctaocrypt/src/ctc_misc.c \ #DBG = /Zi /Od
$(CYA)/ctaocrypt/src/cyassl_memory.c $(CYA)/ctaocrypt/src/des3.c \ DBG = /DNDEBUG /O1
$(CYA)/ctaocrypt/src/dh.c $(CYA)/ctaocrypt/src/dsa.c \ CL = $(MSVC)/bin/cl /MD /TC /nologo $(DBG) /Gz /W3 \
$(CYA)/ctaocrypt/src/ecc.c $(CYA)/ctaocrypt/src/hc128.c \ /I$(MSVC)/include /I$(LUA) /I. /I$(YASSL) /I$(YASSL)/cyassl /GA
$(CYA)/ctaocrypt/src/hmac.c $(CYA)/ctaocrypt/src/integer.c \ MSLIB = /link /incremental:no /libpath:$(MSVC)/lib /machine:IX86 \
$(CYA)/ctaocrypt/src/md4.c $(CYA)/ctaocrypt/src/md5.c \ user32.lib shell32.lib comdlg32.lib ws2_32.lib advapi32.lib
$(CYA)/ctaocrypt/src/pwdbased.c $(CYA)/ctaocrypt/src/rabbit.c \
$(CYA)/ctaocrypt/src/random.c $(CYA)/ctaocrypt/src/ripemd.c \ # Stock windows binary builds with Lua and YASSL library.
$(CYA)/ctaocrypt/src/rsa.c $(CYA)/ctaocrypt/src/sha.c \ YASSL = ../cyassl-2.4.6
$(CYA)/ctaocrypt/src/sha256.c $(CYA)/ctaocrypt/src/sha512.c \ YASSL_FLAGS = -I $(YASSL) -I $(YASSL)/cyassl \
$(CYA)/ctaocrypt/src/tfm.c -D _LIB -D OPENSSL_EXTRA -D HAVE_ERRNO_H \
-D HAVE_GETHOSTBYNAME -D HAVE_INET_NTOA -D HAVE_LIMITS_H \
cyassl.lib: -D HAVE_MEMSET -D HAVE_SOCKET -D HAVE_STDDEF_H -D HAVE_STDLIB_H \
$(CL) /Fo$(CYA)/ $(CYA_SOURCES) $(CYAFL) $(DEF) -D HAVE_STRING_H -D HAVE_SYS_STAT_H -D HAVE_SYS_TYPES_H
$(MSVC)/bin/lib $(CYA)/*.obj /out:$@ YASSL_SOURCES = \
$(YASSL)/src/internal.c $(YASSL)/src/io.c $(YASSL)/src/keys.c \
lua.lib: $(YASSL)/src/ssl.c $(YASSL)/src/tls.c $(YASSL)/ctaocrypt/src/hmac.c \
$(CL) /c /Fo$(LUA)/ $(LUA_SOURCES) $(YASSL)/ctaocrypt/src/random.c $(YASSL)/ctaocrypt/src/sha.c \
$(MSVC)/bin/lib $(LUA_SOURCES:%.c=%.obj) /out:$@ $(YASSL)/ctaocrypt/src/sha256.c $(YASSL)/ctaocrypt/src/logging.c \
$(YASSL)/ctaocrypt/src/error.c $(YASSL)/ctaocrypt/src/rsa.c \
windows: cyassl.lib lua.lib $(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
all:
@echo "make (linux|bsd|solaris|mac|windows|mingw|cygwin)"
# 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 $(MSVC)/bin/rc build\res.rc
$(CL) /Ibuild main.c mongoose.c /GA $(LINK) build\res.res \ $(CL) main.c mongoose.c build/lsqlite3.c build/sqlite3.c \
$(GUILIB) /out:$(PROG).exe $(YASSL_SOURCES) $(YASSL_FLAGS) /DNO_SSL_DL \
$(CL) mongoose.c /GD $(LINK) /DLL /DEF:build\dll.def /out:$(PROG).dll $(LUA_SOURCES) /DUSE_LUA /DUSE_LUA_SQLITE3 /DLUA_COMPAT_ALL \
$(MSLIB) build\res.res /out:$(PROG).exe /subsystem:windows
# Build for Windows under MinGW # Build for Windows under MinGW
#MINGWDBG= -DDEBUG -O0 -ggdb #MINGWDBG= -DDEBUG -O0 -ggdb
MINGWDBG= -DNDEBUG -Os MINGWDBG= -DNDEBUG -Os
MINGWOPT= -W -Wall -mthreads -Wl,--subsystem,console $(MINGWDBG) -DHAVE_STDINT $(GCC_WARNINGS) $(COPT) 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: mingw:
windres build\res.rc build\res.o windres build\res.rc build\res.o
$(CC) $(MINGWOPT) mongoose.c -lws2_32 \ $(CC) $(MINGWOPT) mongoose.c -lws2_32 \
-shared -Wl,--out-implib=$(PROG).lib -o $(PROG).dll -shared -Wl,--out-implib=$(PROG).lib -o $(PROG).dll
$(CC) $(MINGWOPT) -build mongoose.c main.c build\res.o \ $(CC) $(MINGWOPT) mongoose.c main.c build\res.o \
-lws2_32 -ladvapi32 -o $(PROG).exe -lws2_32 -ladvapi32 -lcomdlg32 -o $(PROG).exe
# Build for Windows under Cygwin # Build for Windows under Cygwin
#CYGWINDBG= -DDEBUG -O0 -ggdb #CYGWINDBG= -DDEBUG -O0 -ggdb
...@@ -139,22 +145,17 @@ cygwin: ...@@ -139,22 +145,17 @@ cygwin:
$(CC) $(CYGWINOPT) -Ibuild mongoose.c main.c ./build/res.o \ $(CC) $(CYGWINOPT) -Ibuild mongoose.c main.c ./build/res.o \
-lws2_32 -ladvapi32 -o $(PROG).exe -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: tests:
perl test/test.pl $(TEST) 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 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: 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
Project Mission # Project Mission
---------------
Project mission is to provide simple, functional, embeddable web server to 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 make it easy for application and device developers to implement web interface
application and devices, and to offer a simple web development environment. 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 To accomplish it's mission, Mongoose keeps balance on functionality and
simplicity by carefully selected list of features: 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 - Works on Windows, Mac, UNIX, iPhone, Android, and many other platforms
- Support for CGI, SSL, SSI, Digest (MD5) authorization, Websocket, WEbDAV - 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 - Resumed download, URL rewrite, IP-based ACL, Windows service
- Excluding files from serving by URI pattern (file blacklist) - Excluding files from serving by URI pattern (file blacklist)
- Download speed limit based on client subnet or URI pattern - Download speed limit based on client subnet or URI pattern
- Small footprint: executable size is 50 kB on Linux 2.6 i386 system - 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 - 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. - Simple and clean embedding API,
- 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) [mongoose.h](https://github.com/valenok/mongoose/blob/master/mongoose.h).
- Extensive documentation in form of [User Manual](https://github.com/valenok/mongoose/blob/master/UserManual.md) 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 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 I am Sergey Lyubka, a software engineer from Galway, Ireland. I started
from Galway, Ireland. My other software I give to the community for free is 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). [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 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 [Amazon wishlist](http://amzn.com/w/1OC2ZCPTQYIEP?sort=priority). Many thanks
already did so: T.Barmann, D.Hughes, J.C.Sloan, R.Romeo and 4 others. to all who already did so: T.Barmann, D.Hughes, J.C.Sloan, R.Romeo,
Appreciated guys, you keep my brains going! 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)
# Overview
**NOTE: THIS MANUAL IS WORK IN PROGRESS**
Overview
--------
Mongoose is small and easy to use web server. It is self-contained, and does Mongoose is small and easy to use web server. It is self-contained, and does
not require any external software to run. not require any external software to run.
On Windows, mongoose iconifies itself to the system tray icon when started. 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 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 On UNIX and Mac, mongoose is a command line utility. Running `mongoose` in
terminal, optionally followed by configuration parameters terminal, optionally followed by configuration parameters
...@@ -22,12 +22,13 @@ When started, mongoose first searches for the configuration file. ...@@ -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. If configuration file is specified explicitly in the command line, i.e.
`mongoose path_to_config_file`, then specified configuration file is used. `mongoose path_to_config_file`, then specified configuration file is used.
Otherwise, mongoose would search for file `mongoose.conf` in the same directory 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 Configuration file is a sequence of lines, each line containing
command line argument name and it's value. Empty lines, and lines beginning command line argument name and it's value. Empty lines, and lines beginning
with `#`, are ignored. Here is the example of `mongoose.conf` file: with `#`, are ignored. Here is the example of `mongoose.conf` file:
# mongoose.conf file
document_root c:\www document_root c:\www
listening_ports 8080,8043s listening_ports 8080,8043s
ssl_certificate c:\mongoose\ssl_cert.pem ssl_certificate c:\mongoose\ssl_cert.pem
...@@ -39,33 +40,356 @@ For example, if `mongoose.conf` has line ...@@ -39,33 +40,356 @@ For example, if `mongoose.conf` has line
`document_root /var/www`, and mongoose has been started as `document_root /var/www`, and mongoose has been started as
`mongoose -document_root /etc`, then `/etc` directory will be served as `mongoose -document_root /etc`, then `/etc` directory will be served as
document root, because command line options take priority over 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 can also be used to modify `.htpasswd` passwords file:
mongoose -A <htpasswd_file> <realm> <user> <passwd> 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:
** 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.
All other characters in the pattern match themselves. Examples:
**.cgi$ Any string that ends with .cgi
/foo Any string that begins with /foo
**a$|**b$ Any string that ends with a or b
# Configuration Options
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.
- How to share a Windows folder: copy mongoose executable to a folder and ![screenshot](https://a248.e.akamai.net/camo.github.com/b88428bf009a2b6141000937ab684e04cc8586af/687474703a2f2f692e696d6775722e636f6d2f62676f6b702e706e67)
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 &`
Command Line Options
--------------------
Common Problems 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.
Embedding
---------
Other Resources # Other Resources
---------------
- Presentation made by Arnout Vandecappelle at FOSDEM 2011 on 2011-02-06 - Presentation made by Arnout Vandecappelle at FOSDEM 2011 on 2011-02-06
in Brussels, Belgium, called in Brussels, Belgium, called
"Creating secure web based user interfaces for Embedded Devices" "Creating secure web based user interfaces for Embedded Devices"
......
...@@ -2,17 +2,19 @@ ...@@ -2,17 +2,19 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key> <string>Mongoose</string>
<string>mongoose</string> <key>CFBundlePackageType</key> <string>APPL</string>
<key>CFBundlePackageType</key> <key>CFBundleTypeRole</key> <string>None</string>
<string>APPL</string> <key>CFBundleIconFiles</key> <array>
<key>CFBundleTypeRole</key>
<string>None</string>
<key>CFBundleIconFiles</key>
<array>
<string>mongoose_16x16.png</string> <string>mongoose_16x16.png</string>
<string>mongoose_22x22.png</string>
<string>mongoose_32x32.png</string>
<string>mongoose_64x64.png</string>
</array> </array>
<key>LSUIElement</key> <key>LSUIElement</key> <true/>
<true/> <key>RunAtLoad</key> <true/>
<key>Label</key> <string>com.kolkin.mongoose</string>
<key>ProgramArguments</key> <array> </array>
<key>KeepAlive</key> <true/>
</dict> </dict>
</plist> </plist>
/************************************************************************
* lsqlite3 *
* Copyright (C) 2002-2007 Tiago Dionizio, Doug Currie *
* All rights reserved. *
* Author : Tiago Dionizio <tiago.dionizio@ist.utl.pt> *
* Author : Doug Currie <doug.currie@alum.mit.edu> *
* Library : lsqlite3 - a SQLite 3 database binding for Lua 5 *
* *
* 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. *
************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define LUA_LIB
#include "lua.h"
#include "lauxlib.h"
#include "sqlite3.h"
/* compile time features */
#if !defined(SQLITE_OMIT_PROGRESS_CALLBACK)
#define SQLITE_OMIT_PROGRESS_CALLBACK 0
#endif
typedef struct sdb sdb;
typedef struct sdb_vm sdb_vm;
typedef struct sdb_func sdb_func;
/* to use as C user data so i know what function sqlite is calling */
struct sdb_func {
/* references to associated lua values */
int fn_step;
int fn_finalize;
int udata;
sdb *db;
char aggregate;
sdb_func *next;
};
/* information about database */
struct sdb {
/* associated lua state */
lua_State *L;
/* sqlite database handle */
sqlite3 *db;
/* sql functions stack usage */
sdb_func *func; /* top SQL function being called */
/* references */
int busy_cb; /* busy callback */
int busy_udata;
int progress_cb; /* progress handler */
int progress_udata;
int trace_cb; /* trace callback */
int trace_udata;
};
static const char *sqlite_meta = ":sqlite3";
static const char *sqlite_vm_meta = ":sqlite3:vm";
static const char *sqlite_ctx_meta = ":sqlite3:ctx";
static int sqlite_ctx_meta_ref;
/*
** =======================================================
** Database Virtual Machine Operations
** =======================================================
*/
static void vm_push_column(lua_State *L, sqlite3_stmt *vm, int idx) {
switch (sqlite3_column_type(vm, idx)) {
case SQLITE_INTEGER:
{
sqlite_int64 i64 = sqlite3_column_int64(vm, idx);
lua_Number n = (lua_Number)i64;
if (n == i64)
lua_pushnumber(L, n);
else
lua_pushlstring(L, (const char*)sqlite3_column_text(vm, idx), sqlite3_column_bytes(vm, idx));
}
break;
case SQLITE_FLOAT:
lua_pushnumber(L, sqlite3_column_double(vm, idx));
break;
case SQLITE_TEXT:
lua_pushlstring(L, (const char*)sqlite3_column_text(vm, idx), sqlite3_column_bytes(vm, idx));
break;
case SQLITE_BLOB:
lua_pushlstring(L, sqlite3_column_blob(vm, idx), sqlite3_column_bytes(vm, idx));
break;
case SQLITE_NULL:
lua_pushnil(L);
break;
default:
lua_pushnil(L);
break;
}
}
/* virtual machine information */
struct sdb_vm {
sdb *db; /* associated database handle */
sqlite3_stmt *vm; /* virtual machine */
/* sqlite3_step info */
int columns; /* number of columns in result */
char has_values; /* true when step succeeds */
char temp; /* temporary vm used in db:rows */
};
/* called with sql text on the lua stack */
static sdb_vm *newvm(lua_State *L, sdb *db) {
sdb_vm *svm = (sdb_vm*)lua_newuserdata(L, sizeof(sdb_vm));
luaL_getmetatable(L, sqlite_vm_meta);
lua_setmetatable(L, -2); /* set metatable */
svm->db = db;
svm->columns = 0;
svm->has_values = 0;
svm->vm = NULL;
svm->temp = 0;
/* add an entry on the database table: svm -> sql text */
lua_pushlightuserdata(L, db);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushlightuserdata(L, svm);
lua_pushvalue(L, -4); /* the sql text */
lua_rawset(L, -3);
lua_pop(L, 1);
return svm;
}
static int cleanupvm(lua_State *L, sdb_vm *svm) {
/* remove entry in database table - no harm if not present in the table */
lua_pushlightuserdata(L, svm->db);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushlightuserdata(L, svm);
lua_pushnil(L);
lua_rawset(L, -3);
lua_pop(L, 1);
svm->columns = 0;
svm->has_values = 0;
if (!svm->vm) return 0;
lua_pushnumber(L, sqlite3_finalize(svm->vm));
svm->vm = NULL;
return 1;
}
static int stepvm(lua_State *L, sdb_vm *svm) {
int result;
int loop_limit = 3;
while ( loop_limit-- ) {
result = sqlite3_step(svm->vm);
if ( result==SQLITE_ERROR ) {
result = sqlite3_reset (svm->vm);
}
if ( result==SQLITE_SCHEMA ) {
sqlite3_stmt *vn;
const char *sql;
/* recover sql text */
lua_pushlightuserdata(L, svm->db);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushlightuserdata(L, svm);
lua_rawget(L, -2); /* sql text */
sql = luaL_checkstring(L, -1);
/* re-prepare */
result = sqlite3_prepare(svm->db->db, sql, -1, &vn, NULL);
if (result != SQLITE_OK) break;
sqlite3_transfer_bindings(svm->vm, vn);
sqlite3_finalize(svm->vm);
svm->vm = vn;
lua_pop(L,2);
} else {
break;
}
}
return result;
}
static sdb_vm *lsqlite_getvm(lua_State *L, int index) {
sdb_vm *svm = (sdb_vm*)luaL_checkudata(L, index, sqlite_vm_meta);
if (svm == NULL) luaL_argerror(L, index, "bad sqlite virtual machine");
return svm;
}
static sdb_vm *lsqlite_checkvm(lua_State *L, int index) {
sdb_vm *svm = lsqlite_getvm(L, index);
if (svm->vm == NULL) luaL_argerror(L, index, "attempt to use closed sqlite virtual machine");
return svm;
}
static int dbvm_isopen(lua_State *L) {
sdb_vm *svm = lsqlite_getvm(L, 1);
lua_pushboolean(L, svm->vm != NULL ? 1 : 0);
return 1;
}
static int dbvm_tostring(lua_State *L) {
char buff[39];
sdb_vm *svm = lsqlite_getvm(L, 1);
if (svm->vm == NULL)
strcpy(buff, "closed");
else
sprintf(buff, "%p", svm);
lua_pushfstring(L, "sqlite virtual machine (%s)", buff);
return 1;
}
static int dbvm_gc(lua_State *L) {
sdb_vm *svm = lsqlite_getvm(L, 1);
if (svm->vm != NULL) /* ignore closed vms */
cleanupvm(L, svm);
return 0;
}
static int dbvm_step(lua_State *L) {
int result;
sdb_vm *svm = lsqlite_checkvm(L, 1);
result = stepvm(L, svm);
svm->has_values = result == SQLITE_ROW ? 1 : 0;
svm->columns = sqlite3_data_count(svm->vm);
lua_pushnumber(L, result);
return 1;
}
static int dbvm_finalize(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
return cleanupvm(L, svm);
}
static int dbvm_reset(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_reset(svm->vm);
lua_pushnumber(L, sqlite3_errcode(svm->db->db));
return 1;
}
static void dbvm_check_contents(lua_State *L, sdb_vm *svm) {
if (!svm->has_values) {
luaL_error(L, "misuse of function");
}
}
static void dbvm_check_index(lua_State *L, sdb_vm *svm, int index) {
if (index < 0 || index >= svm->columns) {
luaL_error(L, "index out of range [0..%d]", svm->columns - 1);
}
}
static void dbvm_check_bind_index(lua_State *L, sdb_vm *svm, int index) {
if (index < 1 || index > sqlite3_bind_parameter_count(svm->vm)) {
luaL_error(L, "bind index out of range [1..%d]", sqlite3_bind_parameter_count(svm->vm));
}
}
/*
** =======================================================
** Virtual Machine - generic info
** =======================================================
*/
static int dbvm_columns(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
lua_pushnumber(L, sqlite3_column_count(svm->vm));
return 1;
}
/*
** =======================================================
** Virtual Machine - getters
** =======================================================
*/
static int dbvm_get_value(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
int index = luaL_checkint(L, 2);
dbvm_check_contents(L, svm);
dbvm_check_index(L, svm, index);
vm_push_column(L, svm->vm, index);
return 1;
}
static int dbvm_get_name(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
int index = luaL_checknumber(L, 2);
dbvm_check_index(L, svm, index);
lua_pushstring(L, sqlite3_column_name(svm->vm, index));
return 1;
}
static int dbvm_get_type(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
int index = luaL_checknumber(L, 2);
dbvm_check_index(L, svm, index);
lua_pushstring(L, sqlite3_column_decltype(svm->vm, index));
return 1;
}
static int dbvm_get_values(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = svm->columns;
int n;
dbvm_check_contents(L, svm);
lua_newtable(L);
for (n = 0; n < columns;) {
vm_push_column(L, vm, n++);
lua_rawseti(L, -2, n);
}
return 1;
}
static int dbvm_get_names(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = sqlite3_column_count(vm); /* valid as soon as statement prepared */
int n;
lua_newtable(L);
for (n = 0; n < columns;) {
lua_pushstring(L, sqlite3_column_name(vm, n++));
lua_rawseti(L, -2, n);
}
return 1;
}
static int dbvm_get_types(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = sqlite3_column_count(vm); /* valid as soon as statement prepared */
int n;
lua_newtable(L);
for (n = 0; n < columns;) {
lua_pushstring(L, sqlite3_column_decltype(vm, n++));
lua_rawseti(L, -2, n);
}
return 1;
}
static int dbvm_get_uvalues(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = svm->columns;
int n;
dbvm_check_contents(L, svm);
lua_checkstack(L, columns);
for (n = 0; n < columns; ++n)
vm_push_column(L, vm, n);
return columns;
}
static int dbvm_get_unames(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = sqlite3_column_count(vm); /* valid as soon as statement prepared */
int n;
lua_checkstack(L, columns);
for (n = 0; n < columns; ++n)
lua_pushstring(L, sqlite3_column_name(vm, n));
return columns;
}
static int dbvm_get_utypes(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = sqlite3_column_count(vm); /* valid as soon as statement prepared */
int n;
lua_checkstack(L, columns);
for (n = 0; n < columns; ++n)
lua_pushstring(L, sqlite3_column_decltype(vm, n));
return columns;
}
static int dbvm_get_named_values(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = svm->columns;
int n;
dbvm_check_contents(L, svm);
lua_newtable(L);
for (n = 0; n < columns; ++n) {
lua_pushstring(L, sqlite3_column_name(vm, n));
vm_push_column(L, vm, n);
lua_rawset(L, -3);
}
return 1;
}
static int dbvm_get_named_types(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int columns = sqlite3_column_count(vm);
int n;
lua_newtable(L);
for (n = 0; n < columns; ++n) {
lua_pushstring(L, sqlite3_column_name(vm, n));
lua_pushstring(L, sqlite3_column_decltype(vm, n));
lua_rawset(L, -3);
}
return 1;
}
/*
** =======================================================
** Virtual Machine - Bind
** =======================================================
*/
static int dbvm_bind_index(lua_State *L, sqlite3_stmt *vm, int index, int lindex) {
switch (lua_type(L, lindex)) {
case LUA_TSTRING:
return sqlite3_bind_text(vm, index, lua_tostring(L, lindex), lua_strlen(L, lindex), SQLITE_TRANSIENT);
case LUA_TNUMBER:
return sqlite3_bind_double(vm, index, lua_tonumber(L, lindex));
case LUA_TBOOLEAN:
return sqlite3_bind_int(vm, index, lua_toboolean(L, lindex) ? 1 : 0);
case LUA_TNONE:
case LUA_TNIL:
return sqlite3_bind_null(vm, index);
default:
luaL_error(L, "index (%d) - invalid data type for bind (%s)", index, lua_typename(L, lua_type(L, lindex)));
return SQLITE_MISUSE; /*!*/
}
}
static int dbvm_bind_parameter_count(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
lua_pushnumber(L, sqlite3_bind_parameter_count(svm->vm));
return 1;
}
static int dbvm_bind_parameter_name(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
int index = luaL_checknumber(L, 2);
dbvm_check_bind_index(L, svm, index);
lua_pushstring(L, sqlite3_bind_parameter_name(svm->vm, index));
return 1;
}
static int dbvm_bind(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int index = luaL_checkint(L, 2);
int result;
dbvm_check_bind_index(L, svm, index);
result = dbvm_bind_index(L, vm, index, 3);
lua_pushnumber(L, result);
return 1;
}
static int dbvm_bind_blob(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
int index = luaL_checkint(L, 2);
const char *value = luaL_checkstring(L, 3);
int len = lua_strlen(L, 3);
lua_pushnumber(L, sqlite3_bind_blob(svm->vm, index, value, len, SQLITE_TRANSIENT));
return 1;
}
static int dbvm_bind_values(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int top = lua_gettop(L);
int result, n;
if (top - 1 != sqlite3_bind_parameter_count(vm))
luaL_error(L,
"incorrect number of parameters to bind (%d given, %d to bind)",
top - 1,
sqlite3_bind_parameter_count(vm)
);
for (n = 2; n <= top; ++n) {
if ((result = dbvm_bind_index(L, vm, n - 1, n)) != SQLITE_OK) {
lua_pushnumber(L, result);
return 1;
}
}
lua_pushnumber(L, SQLITE_OK);
return 1;
}
static int dbvm_bind_names(lua_State *L) {
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm = svm->vm;
int count = sqlite3_bind_parameter_count(vm);
const char *name;
int result, n;
luaL_checktype(L, 2, LUA_TTABLE);
for (n = 1; n <= count; ++n) {
name = sqlite3_bind_parameter_name(vm, n);
if (name && (name[0] == ':' || name[0] == '$')) {
lua_pushstring(L, ++name);
lua_gettable(L, 2);
result = dbvm_bind_index(L, vm, n, -1);
lua_pop(L, 1);
}
else {
lua_pushnumber(L, n);
lua_gettable(L, 2);
result = dbvm_bind_index(L, vm, n, -1);
lua_pop(L, 1);
}
if (result != SQLITE_OK) {
lua_pushnumber(L, result);
return 1;
}
}
lua_pushnumber(L, SQLITE_OK);
return 1;
}
/*
** =======================================================
** Database (internal management)
** =======================================================
*/
/*
** When creating database handles, always creates a `closed' database handle
** before opening the actual database; so, if there is a memory error, the
** database is not left opened.
**
** Creates a new 'table' and leaves it in the stack
*/
static sdb *newdb (lua_State *L) {
sdb *db = (sdb*)lua_newuserdata(L, sizeof(sdb));
db->L = L;
db->db = NULL; /* database handle is currently `closed' */
db->func = NULL;
db->busy_cb =
db->busy_udata =
db->progress_cb =
db->progress_udata =
db->trace_cb =
db->trace_udata = LUA_NOREF;
luaL_getmetatable(L, sqlite_meta);
lua_setmetatable(L, -2); /* set metatable */
/* to keep track of 'open' virtual machines */
lua_pushlightuserdata(L, db);
lua_newtable(L);
lua_rawset(L, LUA_REGISTRYINDEX);
return db;
}
static int cleanupdb(lua_State *L, sdb *db) {
sdb_func *func;
sdb_func *func_next;
int top;
int result;
/* free associated virtual machines */
lua_pushlightuserdata(L, db);
lua_rawget(L, LUA_REGISTRYINDEX);
/* close all used handles */
top = lua_gettop(L);
lua_pushnil(L);
while (lua_next(L, -2)) {
sdb_vm *svm = lua_touserdata(L, -2); /* key: vm; val: sql text */
cleanupvm(L, svm);
lua_settop(L, top);
lua_pushnil(L);
}
lua_pop(L, 1); /* pop vm table */
/* remove entry in lua registry table */
lua_pushlightuserdata(L, db);
lua_pushnil(L);
lua_rawset(L, LUA_REGISTRYINDEX);
/* 'free' all references */
luaL_unref(L, LUA_REGISTRYINDEX, db->busy_cb);
luaL_unref(L, LUA_REGISTRYINDEX, db->busy_udata);
luaL_unref(L, LUA_REGISTRYINDEX, db->progress_cb);
luaL_unref(L, LUA_REGISTRYINDEX, db->progress_udata);
luaL_unref(L, LUA_REGISTRYINDEX, db->trace_cb);
luaL_unref(L, LUA_REGISTRYINDEX, db->trace_udata);
/* close database */
result = sqlite3_close(db->db);
db->db = NULL;
/* free associated memory with created functions */
func = db->func;
while (func) {
func_next = func->next;
luaL_unref(L, LUA_REGISTRYINDEX, func->fn_step);
luaL_unref(L, LUA_REGISTRYINDEX, func->fn_finalize);
luaL_unref(L, LUA_REGISTRYINDEX, func->udata);
free(func);
func = func_next;
}
db->func = NULL;
return result;
}
static sdb *lsqlite_getdb(lua_State *L, int index) {
sdb *db = (sdb*)luaL_checkudata(L, index, sqlite_meta);
// TODO lsm if (db == NULL) luaL_typerror(L, index, "sqlite database");
return db;
}
static sdb *lsqlite_checkdb(lua_State *L, int index) {
sdb *db = lsqlite_getdb(L, index);
if (db->db == NULL) luaL_argerror(L, index, "attempt to use closed sqlite database");
return db;
}
/*
** =======================================================
** User Defined Functions - Context Methods
** =======================================================
*/
typedef struct {
sqlite3_context *ctx;
int ud;
} lcontext;
static lcontext *lsqlite_make_context(lua_State *L) {
lcontext *ctx = (lcontext*)lua_newuserdata(L, sizeof(lcontext));
lua_rawgeti(L, LUA_REGISTRYINDEX, sqlite_ctx_meta_ref);
lua_setmetatable(L, -2);
ctx->ctx = NULL;
ctx->ud = LUA_NOREF;
return ctx;
}
static lcontext *lsqlite_getcontext(lua_State *L, int index) {
lcontext *ctx = (lcontext*)luaL_checkudata(L, index, sqlite_ctx_meta);
// TODO lsm if (ctx == NULL) luaL_typerror(L, index, "sqlite context");
return ctx;
}
static lcontext *lsqlite_checkcontext(lua_State *L, int index) {
lcontext *ctx = lsqlite_getcontext(L, index);
if (ctx->ctx == NULL) luaL_argerror(L, index, "invalid sqlite context");
return ctx;
}
static int lcontext_tostring(lua_State *L) {
char buff[39];
lcontext *ctx = lsqlite_getcontext(L, 1);
if (ctx->ctx == NULL)
strcpy(buff, "closed");
else
sprintf(buff, "%p", ctx->ctx);
lua_pushfstring(L, "sqlite function context (%s)", buff);
return 1;
}
static void lcontext_check_aggregate(lua_State *L, lcontext *ctx) {
sdb_func *func = (sdb_func*)sqlite3_user_data(ctx->ctx);
if (!func->aggregate) {
luaL_error(L, "attempt to call aggregate method from scalar function");
}
}
static int lcontext_user_data(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
sdb_func *func = (sdb_func*)sqlite3_user_data(ctx->ctx);
lua_rawgeti(L, LUA_REGISTRYINDEX, func->udata);
return 1;
}
static int lcontext_get_aggregate_context(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
lcontext_check_aggregate(L, ctx);
lua_rawgeti(L, LUA_REGISTRYINDEX, ctx->ud);
return 1;
}
static int lcontext_set_aggregate_context(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
lcontext_check_aggregate(L, ctx);
lua_settop(L, 2);
luaL_unref(L, LUA_REGISTRYINDEX, ctx->ud);
ctx->ud = luaL_ref(L, LUA_REGISTRYINDEX);
return 0;
}
static int lcontext_aggregate_count(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
lcontext_check_aggregate(L, ctx);
lua_pushnumber(L, sqlite3_aggregate_count(ctx->ctx));
return 1;
}
#if 0
void *sqlite3_get_auxdata(sqlite3_context*, int);
void sqlite3_set_auxdata(sqlite3_context*, int, void*, void (*)(void*));
#endif
static int lcontext_result(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
switch (lua_type(L, 2)) {
case LUA_TNUMBER:
sqlite3_result_double(ctx->ctx, luaL_checknumber(L, 2));
break;
case LUA_TSTRING:
sqlite3_result_text(ctx->ctx, luaL_checkstring(L, 2), lua_strlen(L, 2), SQLITE_TRANSIENT);
break;
case LUA_TNIL:
case LUA_TNONE:
sqlite3_result_null(ctx->ctx);
break;
default:
luaL_error(L, "invalid result type %s", lua_typename(L, 2));
break;
}
return 0;
}
static int lcontext_result_blob(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
const char *blob = luaL_checkstring(L, 2);
int size = lua_strlen(L, 2);
sqlite3_result_blob(ctx->ctx, (const void*)blob, size, SQLITE_TRANSIENT);
return 0;
}
static int lcontext_result_double(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
double d = luaL_checknumber(L, 2);
sqlite3_result_double(ctx->ctx, d);
return 0;
}
static int lcontext_result_error(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
const char *err = luaL_checkstring(L, 2);
int size = lua_strlen(L, 2);
sqlite3_result_error(ctx->ctx, err, size);
return 0;
}
static int lcontext_result_int(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
int i = luaL_checkint(L, 2);
sqlite3_result_int(ctx->ctx, i);
return 0;
}
static int lcontext_result_null(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
sqlite3_result_null(ctx->ctx);
return 0;
}
static int lcontext_result_text(lua_State *L) {
lcontext *ctx = lsqlite_checkcontext(L, 1);
const char *text = luaL_checkstring(L, 2);
int size = lua_strlen(L, 2);
sqlite3_result_text(ctx->ctx, text, size, SQLITE_TRANSIENT);
return 0;
}
/*
** =======================================================
** Database Methods
** =======================================================
*/
static int db_isopen(lua_State *L) {
sdb *db = lsqlite_getdb(L, 1);
lua_pushboolean(L, db->db != NULL ? 1 : 0);
return 1;
}
static int db_last_insert_rowid(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
/* conversion warning: int64 -> luaNumber */
sqlite_int64 rowid = sqlite3_last_insert_rowid(db->db);
lua_Number n = (lua_Number)rowid;
if (n == rowid)
lua_pushnumber(L, n);
else
lua_pushfstring(L, "%ll", rowid);
return 1;
}
static int db_changes(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
lua_pushnumber(L, sqlite3_changes(db->db));
return 1;
}
static int db_total_changes(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
lua_pushnumber(L, sqlite3_total_changes(db->db));
return 1;
}
static int db_errcode(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
lua_pushnumber(L, sqlite3_errcode(db->db));
return 1;
}
static int db_errmsg(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
lua_pushstring(L, sqlite3_errmsg(db->db));
return 1;
}
static int db_interrupt(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
sqlite3_interrupt(db->db);
return 0;
}
/*
** Registering SQL functions:
*/
static void db_push_value(lua_State *L, sqlite3_value *value) {
switch (sqlite3_value_type(value)) {
case SQLITE_TEXT:
lua_pushlstring(L, (const char*)sqlite3_value_text(value), sqlite3_value_bytes(value));
break;
case SQLITE_INTEGER:
{
sqlite_int64 i64 = sqlite3_value_int64(value);
lua_Number n = (lua_Number)i64;
if (n == i64)
lua_pushnumber(L, n);
else
lua_pushlstring(L, (const char*)sqlite3_value_text(value), sqlite3_value_bytes(value));
}
break;
case SQLITE_FLOAT:
lua_pushnumber(L, sqlite3_value_double(value));
break;
case SQLITE_BLOB:
lua_pushlstring(L, sqlite3_value_blob(value), sqlite3_value_bytes(value));
break;
case SQLITE_NULL:
lua_pushnil(L);
break;
default:
/* things done properly (SQLite + Lua SQLite)
** this should never happen */
lua_pushnil(L);
break;
}
}
/*
** callback functions used when calling registered sql functions
*/
/* scalar function to be called
** callback params: context, values... */
static void db_sql_normal_function(sqlite3_context *context, int argc, sqlite3_value **argv) {
sdb_func *func = (sdb_func*)sqlite3_user_data(context);
lua_State *L = func->db->L;
int n;
lcontext *ctx;
int top = lua_gettop(L);
/* ensure there is enough space in the stack */
lua_checkstack(L, argc + 3);
lua_rawgeti(L, LUA_REGISTRYINDEX, func->fn_step); /* function to call */
if (!func->aggregate) {
ctx = lsqlite_make_context(L); /* push context - used to set results */
}
else {
/* reuse context userdata value */
void *p = sqlite3_aggregate_context(context, 1);
/* i think it is OK to use assume that using a light user data
** as an entry on LUA REGISTRY table will be unique */
lua_pushlightuserdata(L, p);
lua_rawget(L, LUA_REGISTRYINDEX); /* context table */
if (lua_isnil(L, -1)) { /* not yet created? */
lua_pop(L, 1);
ctx = lsqlite_make_context(L);
lua_pushlightuserdata(L, p);
lua_pushvalue(L, -2);
lua_rawset(L, LUA_REGISTRYINDEX);
}
else
ctx = lsqlite_getcontext(L, -1);
}
/* push params */
for (n = 0; n < argc; ++n) {
db_push_value(L, argv[n]);
}
/* set context */
ctx->ctx = context;
if (lua_pcall(L, argc + 1, 0, 0)) {
const char *errmsg = lua_tostring(L, -1);
int size = lua_strlen(L, -1);
sqlite3_result_error(context, errmsg, size);
}
/* invalidate context */
ctx->ctx = NULL;
if (!func->aggregate) {
luaL_unref(L, LUA_REGISTRYINDEX, ctx->ud);
}
lua_settop(L, top);
}
static void db_sql_finalize_function(sqlite3_context *context) {
sdb_func *func = (sdb_func*)sqlite3_user_data(context);
lua_State *L = func->db->L;
void *p = sqlite3_aggregate_context(context, 1); /* minimal mem usage */
lcontext *ctx;
int top = lua_gettop(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, func->fn_finalize); /* function to call */
/* i think it is OK to use assume that using a light user data
** as an entry on LUA REGISTRY table will be unique */
lua_pushlightuserdata(L, p);
lua_rawget(L, LUA_REGISTRYINDEX); /* context table */
if (lua_isnil(L, -1)) { /* not yet created? - shouldn't happen in finalize function */
lua_pop(L, 1);
ctx = lsqlite_make_context(L);
lua_pushlightuserdata(L, p);
lua_pushvalue(L, -2);
lua_rawset(L, LUA_REGISTRYINDEX);
}
else
ctx = lsqlite_getcontext(L, -1);
/* set context */
ctx->ctx = context;
if (lua_pcall(L, 1, 0, 0)) {
sqlite3_result_error(context, lua_tostring(L, -1), -1);
}
/* invalidate context */
ctx->ctx = NULL;
/* cleanup context */
luaL_unref(L, LUA_REGISTRYINDEX, ctx->ud);
/* remove it from registry */
lua_pushlightuserdata(L, p);
lua_pushnil(L);
lua_rawset(L, LUA_REGISTRYINDEX);
lua_settop(L, top);
}
/*
** Register a normal function
** Params: db, function name, number arguments, [ callback | step, finalize], user data
** Returns: true on sucess
**
** Normal function:
** Params: context, params
**
** Aggregate function:
** Params of step: context, params
** Params of finalize: context
*/
static int db_register_function(lua_State *L, int aggregate) {
sdb *db = lsqlite_checkdb(L, 1);
const char *name;
int args;
int result;
sdb_func *func;
/* safety measure */
if (aggregate) aggregate = 1;
name = luaL_checkstring(L, 2);
args = luaL_checkint(L, 3);
luaL_checktype(L, 4, LUA_TFUNCTION);
if (aggregate) luaL_checktype(L, 5, LUA_TFUNCTION);
/* maybe an alternative way to allocate memory should be used/avoided */
func = (sdb_func*)malloc(sizeof(sdb_func));
if (func == NULL) {
luaL_error(L, "out of memory");
}
result = sqlite3_create_function(
db->db, name, args, SQLITE_UTF8, func,
aggregate ? NULL : db_sql_normal_function,
aggregate ? db_sql_normal_function : NULL,
aggregate ? db_sql_finalize_function : NULL
);
if (result == SQLITE_OK) {
/* safety measures for userdata field to be present in the stack */
lua_settop(L, 5 + aggregate);
/* save registered function in db function list */
func->db = db;
func->aggregate = aggregate;
func->next = db->func;
db->func = func;
/* save the setp/normal function callback */
lua_pushvalue(L, 4);
func->fn_step = luaL_ref(L, LUA_REGISTRYINDEX);
/* save user data */
lua_pushvalue(L, 5+aggregate);
func->udata = luaL_ref(L, LUA_REGISTRYINDEX);
if (aggregate) {
lua_pushvalue(L, 5);
func->fn_finalize = luaL_ref(L, LUA_REGISTRYINDEX);
}
else
func->fn_finalize = LUA_NOREF;
}
else {
/* free allocated memory */
free(func);
}
lua_pushboolean(L, result == SQLITE_OK ? 1 : 0);
return 1;
}
static int db_create_function(lua_State *L) {
return db_register_function(L, 0);
}
static int db_create_aggregate(lua_State *L) {
return db_register_function(L, 1);
}
/* create_collation; contributed by Thomas Lauer
*/
typedef struct {
lua_State *L;
int ref;
} scc;
static int collwrapper(scc *co,int l1,const void *p1,
int l2,const void *p2) {
int res=0;
lua_State *L=co->L;
lua_rawgeti(L,LUA_REGISTRYINDEX,co->ref);
lua_pushlstring(L,p1,l1);
lua_pushlstring(L,p2,l2);
if (lua_pcall(L,2,1,0)==0) res=(int)lua_tonumber(L,-1);
lua_pop(L,1);
return res;
}
static void collfree(scc *co) {
if (co) {
luaL_unref(co->L,LUA_REGISTRYINDEX,co->ref);
free(co);
}
}
static int db_create_collation(lua_State *L) {
sdb *db=lsqlite_checkdb(L,1);
const char *collname=luaL_checkstring(L,2);
scc *co=NULL;
int (*collfunc)(scc *,int,const void *,int,const void *)=NULL;
lua_settop(L,3); /* default args to nil, and exclude extras */
if (lua_isfunction(L,3)) collfunc=collwrapper;
else if (!lua_isnil(L,3))
luaL_error(L,"create_collation: function or nil expected");
if (collfunc != NULL) {
co=(scc *)malloc(sizeof(scc)); /* userdata is a no-no as it
will be garbage-collected */
if (co) {
co->L=L;
/* lua_settop(L,3) above means we don't need: lua_pushvalue(L,3); */
co->ref=luaL_ref(L,LUA_REGISTRYINDEX);
}
else luaL_error(L,"create_collation: could not allocate callback");
}
sqlite3_create_collation_v2(db->db, collname, SQLITE_UTF8,
(void *)co,
(int(*)(void*,int,const void*,int,const void*))collfunc,
(void(*)(void*))collfree);
return 0;
}
/*
** trace callback:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata, sql
*/
static void db_trace_callback(void *user, const char *sql) {
sdb *db = (sdb*)user;
lua_State *L = db->L;
int top = lua_gettop(L);
/* setup lua callback call */
lua_rawgeti(L, LUA_REGISTRYINDEX, db->trace_cb); /* get callback */
lua_rawgeti(L, LUA_REGISTRYINDEX, db->trace_udata); /* get callback user data */
lua_pushstring(L, sql); /* traced sql statement */
/* call lua function */
lua_pcall(L, 2, 0, 0);
/* ignore any error generated by this function */
lua_settop(L, top);
}
static int db_trace(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
if (lua_gettop(L) < 2 || lua_isnil(L, 2)) {
luaL_unref(L, LUA_REGISTRYINDEX, db->trace_cb);
luaL_unref(L, LUA_REGISTRYINDEX, db->trace_udata);
db->trace_cb =
db->trace_udata = LUA_NOREF;
/* clear busy handler */
sqlite3_trace(db->db, NULL, NULL);
}
else {
luaL_checktype(L, 2, LUA_TFUNCTION);
/* make sure we have an userdata field (even if nil) */
lua_settop(L, 3);
luaL_unref(L, LUA_REGISTRYINDEX, db->trace_cb);
luaL_unref(L, LUA_REGISTRYINDEX, db->trace_udata);
db->trace_udata = luaL_ref(L, LUA_REGISTRYINDEX);
db->trace_cb = luaL_ref(L, LUA_REGISTRYINDEX);
/* set busy handler */
sqlite3_trace(db->db, db_trace_callback, db);
}
return 0;
}
#if !defined(SQLITE_OMIT_PROGRESS_CALLBACK) || !SQLITE_OMIT_PROGRESS_CALLBACK
/*
** progress handler:
** Params: database, number of opcodes, callback function, userdata
**
** callback function:
** Params: userdata
** returns: 0 to return immediatly and return SQLITE_ABORT, non-zero to continue
*/
static int db_progress_callback(void *user) {
int result = 1; /* abort by default */
sdb *db = (sdb*)user;
lua_State *L = db->L;
int top = lua_gettop(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, db->progress_cb);
lua_rawgeti(L, LUA_REGISTRYINDEX, db->progress_udata);
/* call lua function */
if (!lua_pcall(L, 1, 1, 0))
result = lua_toboolean(L, -1);
lua_settop(L, top);
return result;
}
static int db_progress_handler(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
if (lua_gettop(L) < 2 || lua_isnil(L, 2)) {
luaL_unref(L, LUA_REGISTRYINDEX, db->progress_cb);
luaL_unref(L, LUA_REGISTRYINDEX, db->progress_udata);
db->progress_cb =
db->progress_udata = LUA_NOREF;
/* clear busy handler */
sqlite3_progress_handler(db->db, 0, NULL, NULL);
}
else {
int nop = luaL_checkint(L, 2); /* number of opcodes */
luaL_checktype(L, 3, LUA_TFUNCTION);
/* make sure we have an userdata field (even if nil) */
lua_settop(L, 4);
luaL_unref(L, LUA_REGISTRYINDEX, db->progress_cb);
luaL_unref(L, LUA_REGISTRYINDEX, db->progress_udata);
db->progress_udata = luaL_ref(L, LUA_REGISTRYINDEX);
db->progress_cb = luaL_ref(L, LUA_REGISTRYINDEX);
/* set progress callback */
sqlite3_progress_handler(db->db, nop, db_progress_callback, db);
}
return 0;
}
#else /* #if !defined(SQLITE_OMIT_PROGRESS_CALLBACK) || !SQLITE_OMIT_PROGRESS_CALLBACK */
static int db_progress_handler(lua_State *L) {
lua_pushliteral(L, "progress callback support disabled at compile time");
lua_error(L);
return 0;
}
#endif /* #if !defined(SQLITE_OMIT_PROGRESS_CALLBACK) || !SQLITE_OMIT_PROGRESS_CALLBACK */
/*
** busy handler:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata, number of tries
** returns: 0 to return immediatly and return SQLITE_BUSY, non-zero to try again
*/
static int db_busy_callback(void *user, int tries) {
int retry = 0; /* abort by default */
sdb *db = (sdb*)user;
lua_State *L = db->L;
int top = lua_gettop(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, db->busy_cb);
lua_rawgeti(L, LUA_REGISTRYINDEX, db->busy_udata);
lua_pushnumber(L, tries);
/* call lua function */
if (!lua_pcall(L, 2, 1, 0))
retry = lua_toboolean(L, -1);
lua_settop(L, top);
return retry;
}
static int db_busy_handler(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
if (lua_gettop(L) < 2 || lua_isnil(L, 2)) {
luaL_unref(L, LUA_REGISTRYINDEX, db->busy_cb);
luaL_unref(L, LUA_REGISTRYINDEX, db->busy_udata);
db->busy_cb =
db->busy_udata = LUA_NOREF;
/* clear busy handler */
sqlite3_busy_handler(db->db, NULL, NULL);
}
else {
luaL_checktype(L, 2, LUA_TFUNCTION);
/* make sure we have an userdata field (even if nil) */
lua_settop(L, 3);
luaL_unref(L, LUA_REGISTRYINDEX, db->busy_cb);
luaL_unref(L, LUA_REGISTRYINDEX, db->busy_udata);
db->busy_udata = luaL_ref(L, LUA_REGISTRYINDEX);
db->busy_cb = luaL_ref(L, LUA_REGISTRYINDEX);
/* set busy handler */
sqlite3_busy_handler(db->db, db_busy_callback, db);
}
return 0;
}
static int db_busy_timeout(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
int timeout = luaL_checkint(L, 2);
sqlite3_busy_timeout(db->db, timeout);
/* if there was a timeout callback registered, it is now
** invalid/useless. free any references we may have */
luaL_unref(L, LUA_REGISTRYINDEX, db->busy_cb);
luaL_unref(L, LUA_REGISTRYINDEX, db->busy_udata);
db->busy_cb =
db->busy_udata = LUA_NOREF;
return 0;
}
/*
** Params: db, sql, callback, user
** returns: code [, errmsg]
**
** Callback:
** Params: user, number of columns, values, names
** Returns: 0 to continue, other value will cause abort
*/
static int db_exec_callback(void* user, int columns, char **data, char **names) {
int result = SQLITE_ABORT; /* abort by default */
lua_State *L = (lua_State*)user;
int n;
int top = lua_gettop(L);
lua_pushvalue(L, 3); /* function to call */
lua_pushvalue(L, 4); /* user data */
lua_pushnumber(L, columns); /* total number of rows in result */
/* column values */
lua_pushvalue(L, 6);
for (n = 0; n < columns;) {
lua_pushstring(L, data[n++]);
lua_rawseti(L, -2, n);
}
/* columns names */
lua_pushvalue(L, 5);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
lua_newtable(L);
lua_pushvalue(L, -1);
lua_replace(L, 5);
for (n = 0; n < columns;) {
lua_pushstring(L, names[n++]);
lua_rawseti(L, -2, n);
}
}
/* call lua function */
if (!lua_pcall(L, 4, 1, 0)) {
if (lua_isnumber(L, -1))
result = lua_tonumber(L, -1);
}
lua_settop(L, top);
return result;
}
static int db_exec(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
const char *sql = luaL_checkstring(L, 2);
int result;
if (!lua_isnoneornil(L, 3)) {
/* stack:
** 3: callback function
** 4: userdata
** 5: column names
** 6: reusable column values
*/
luaL_checktype(L, 3, LUA_TFUNCTION);
lua_settop(L, 4); /* 'trap' userdata - nil extra parameters */
lua_pushnil(L); /* column names not known at this point */
lua_newtable(L); /* column values table */
result = sqlite3_exec(db->db, sql, db_exec_callback, L, NULL);
}
else {
/* no callbacks */
result = sqlite3_exec(db->db, sql, NULL, NULL, NULL);
}
lua_pushnumber(L, result);
return 1;
}
/*
** Params: db, sql
** returns: code, compiled length or error message
*/
static int db_prepare(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
const char *sql = luaL_checkstring(L, 2);
int sql_len = lua_strlen(L, 2);
const char *sqltail;
sdb_vm *svm;
lua_settop(L,2); /* sql is on top of stack for call to newvm */
svm = newvm(L, db);
if (sqlite3_prepare(db->db, sql, sql_len, &svm->vm, &sqltail) != SQLITE_OK) {
cleanupvm(L, svm);
lua_pushnil(L);
lua_pushnumber(L, sqlite3_errcode(db->db));
return 2;
}
/* vm already in the stack */
lua_pushstring(L, sqltail);
return 2;
}
static int db_do_next_row(lua_State *L, int packed) {
int result;
sdb_vm *svm = lsqlite_checkvm(L, 1);
sqlite3_stmt *vm;
int columns;
int i;
result = stepvm(L, svm);
vm = svm->vm; /* stepvm may change svm->vm if re-prepare is needed */
svm->has_values = result == SQLITE_ROW ? 1 : 0;
svm->columns = columns = sqlite3_data_count(vm);
if (result == SQLITE_ROW) {
if (packed) {
lua_newtable(L);
if (packed == 1) {
for (i = 0; i < columns;) {
vm_push_column(L, vm, i);
lua_rawseti(L, -2, ++i);
}
}
else {
for (i = 0; i < columns; ++i) {
lua_pushstring(L, sqlite3_column_name(vm, i));
vm_push_column(L, vm, i);
lua_rawset(L, -3);
}
}
return 1;
}
else {
lua_checkstack(L, columns);
for (i = 0; i < columns; ++i)
vm_push_column(L, vm, i);
return svm->columns;
}
}
if (svm->temp) {
/* finalize and check for errors */
result = sqlite3_finalize(vm);
svm->vm = NULL;
cleanupvm(L, svm);
}
else if (result == SQLITE_DONE) {
result = sqlite3_reset(vm);
}
if (result != SQLITE_OK) {
lua_pushstring(L, sqlite3_errmsg(svm->db->db));
lua_error(L);
}
return 0;
}
static int db_next_row(lua_State *L) {
return db_do_next_row(L, 0);
}
static int db_next_packed_row(lua_State *L) {
return db_do_next_row(L, 1);
}
static int db_next_named_row(lua_State *L) {
return db_do_next_row(L, 2);
}
static int dbvm_do_rows(lua_State *L, int(*f)(lua_State *)) {
/* sdb_vm *svm = */
lsqlite_checkvm(L, 1);
lua_pushvalue(L,1);
lua_pushcfunction(L, f);
lua_insert(L, -2);
return 2;
}
static int dbvm_rows(lua_State *L) {
return dbvm_do_rows(L, db_next_packed_row);
}
static int dbvm_nrows(lua_State *L) {
return dbvm_do_rows(L, db_next_named_row);
}
static int dbvm_urows(lua_State *L) {
return dbvm_do_rows(L, db_next_row);
}
static int db_do_rows(lua_State *L, int(*f)(lua_State *)) {
sdb *db = lsqlite_checkdb(L, 1);
const char *sql = luaL_checkstring(L, 2);
sdb_vm *svm;
lua_settop(L,2); /* sql is on top of stack for call to newvm */
svm = newvm(L, db);
svm->temp = 1;
if (sqlite3_prepare(db->db, sql, -1, &svm->vm, NULL) != SQLITE_OK) {
cleanupvm(L, svm);
lua_pushstring(L, sqlite3_errmsg(svm->db->db));
lua_error(L);
}
lua_pushcfunction(L, f);
lua_insert(L, -2);
return 2;
}
static int db_rows(lua_State *L) {
return db_do_rows(L, db_next_packed_row);
}
static int db_nrows(lua_State *L) {
return db_do_rows(L, db_next_named_row);
}
/* unpacked version of db:rows */
static int db_urows(lua_State *L) {
return db_do_rows(L, db_next_row);
}
static int db_tostring(lua_State *L) {
char buff[32];
sdb *db = lsqlite_getdb(L, 1);
if (db->db == NULL)
strcpy(buff, "closed");
else
sprintf(buff, "%p", lua_touserdata(L, 1));
lua_pushfstring(L, "sqlite database (%s)", buff);
return 1;
}
static int db_close(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
lua_pushnumber(L, cleanupdb(L, db));
return 1;
}
static int db_close_vm(lua_State *L) {
sdb *db = lsqlite_checkdb(L, 1);
/* cleanup temporary only tables? */
int temp = lua_toboolean(L, 2);
/* free associated virtual machines */
lua_pushlightuserdata(L, db);
lua_rawget(L, LUA_REGISTRYINDEX);
/* close all used handles */
lua_pushnil(L);
while (lua_next(L, -2)) {
sdb_vm *svm = lua_touserdata(L, -2); /* key: vm; val: sql text */
if ((!temp || svm->temp) && svm->vm)
{
sqlite3_finalize(svm->vm);
svm->vm = NULL;
}
/* leave key in the stack */
lua_pop(L, 1);
}
return 0;
}
static int db_gc(lua_State *L) {
sdb *db = lsqlite_getdb(L, 1);
if (db->db != NULL) /* ignore closed databases */
cleanupdb(L, db);
return 0;
}
/*
** =======================================================
** General library functions
** =======================================================
*/
static int lsqlite_version(lua_State *L) {
lua_pushstring(L, sqlite3_libversion());
return 1;
}
static int lsqlite_complete(lua_State *L) {
const char *sql = luaL_checkstring(L, 1);
lua_pushboolean(L, sqlite3_complete(sql));
return 1;
}
#ifndef WIN32
static int lsqlite_temp_directory(lua_State *L) {
const char *oldtemp = sqlite3_temp_directory;
if (!lua_isnone(L, 1)) {
const char *temp = luaL_optstring(L, 1, NULL);
if (sqlite3_temp_directory) {
sqlite3_free((char*)sqlite3_temp_directory);
}
if (temp) {
sqlite3_temp_directory = sqlite3_mprintf("%s", temp);
}
else {
sqlite3_temp_directory = NULL;
}
}
lua_pushstring(L, oldtemp);
return 1;
}
#endif
static int lsqlite_do_open(lua_State *L, const char *filename) {
sdb *db = newdb(L); /* create and leave in stack */
if (sqlite3_open(filename, &db->db) == SQLITE_OK) {
/* database handle already in the stack - return it */
return 1;
}
/* failed to open database */
lua_pushnil(L); /* push nil */
lua_pushnumber(L, sqlite3_errcode(db->db));
lua_pushstring(L, sqlite3_errmsg(db->db)); /* push error message */
/* clean things up */
cleanupdb(L, db);
/* return */
return 3;
}
static int lsqlite_open(lua_State *L) {
const char *filename = luaL_checkstring(L, 1);
return lsqlite_do_open(L, filename);
}
static int lsqlite_open_memory(lua_State *L) {
return lsqlite_do_open(L, ":memory:");
}
static int lsqlite_newindex(lua_State *L) {
lua_pushliteral(L, "attempt to change readonly table");
lua_error(L);
return 0;
}
/*
** =======================================================
** Register functions
** =======================================================
*/
#define SC(s) { #s, SQLITE_ ## s },
#define LSC(s) { #s, LSQLITE_ ## s },
static const struct {
const char* name;
int value;
} sqlite_constants[] = {
/* error codes */
SC(OK) SC(ERROR) SC(INTERNAL) SC(PERM)
SC(ABORT) SC(BUSY) SC(LOCKED) SC(NOMEM)
SC(READONLY) SC(INTERRUPT) SC(IOERR) SC(CORRUPT)
SC(NOTFOUND) SC(FULL) SC(CANTOPEN) SC(PROTOCOL)
SC(EMPTY) SC(SCHEMA) SC(TOOBIG) SC(CONSTRAINT)
SC(MISMATCH) SC(MISUSE) SC(NOLFS)
SC(FORMAT) SC(NOTADB)
/* sqlite_step specific return values */
SC(RANGE) SC(ROW) SC(DONE)
/* column types */
SC(INTEGER) SC(FLOAT) SC(TEXT) SC(BLOB)
SC(NULL)
/* terminator */
{ NULL, 0 }
};
/* ======================================================= */
static const luaL_Reg dblib[] = {
{"isopen", db_isopen },
{"last_insert_rowid", db_last_insert_rowid },
{"changes", db_changes },
{"total_changes", db_total_changes },
{"errcode", db_errcode },
{"error_code", db_errcode },
{"errmsg", db_errmsg },
{"error_message", db_errmsg },
{"interrupt", db_interrupt },
{"create_function", db_create_function },
{"create_aggregate", db_create_aggregate },
{"create_collation", db_create_collation },
{"trace", db_trace },
{"progress_handler", db_progress_handler },
{"busy_timeout", db_busy_timeout },
{"busy_handler", db_busy_handler },
{"prepare", db_prepare },
{"rows", db_rows },
{"urows", db_urows },
{"nrows", db_nrows },
{"exec", db_exec },
{"execute", db_exec },
{"close", db_close },
{"close_vm", db_close_vm },
{"__tostring", db_tostring },
{"__gc", db_gc },
{NULL, NULL}
};
static const luaL_Reg vmlib[] = {
{"isopen", dbvm_isopen },
{"step", dbvm_step },
{"reset", dbvm_reset },
{"finalize", dbvm_finalize },
{"columns", dbvm_columns },
{"bind", dbvm_bind },
{"bind_values", dbvm_bind_values },
{"bind_names", dbvm_bind_names },
{"bind_blob", dbvm_bind_blob },
{"bind_parameter_count",dbvm_bind_parameter_count},
{"bind_parameter_name", dbvm_bind_parameter_name},
{"get_value", dbvm_get_value },
{"get_values", dbvm_get_values },
{"get_name", dbvm_get_name },
{"get_names", dbvm_get_names },
{"get_type", dbvm_get_type },
{"get_types", dbvm_get_types },
{"get_uvalues", dbvm_get_uvalues },
{"get_unames", dbvm_get_unames },
{"get_utypes", dbvm_get_utypes },
{"get_named_values", dbvm_get_named_values },
{"get_named_types", dbvm_get_named_types },
{"rows", dbvm_rows },
{"urows", dbvm_urows },
{"nrows", dbvm_nrows },
/* compatibility names (added by request) */
{"idata", dbvm_get_values },
{"inames", dbvm_get_names },
{"itypes", dbvm_get_types },
{"data", dbvm_get_named_values },
{"type", dbvm_get_named_types },
{"__tostring", dbvm_tostring },
{"__gc", dbvm_gc },
{ NULL, NULL }
};
static const luaL_Reg ctxlib[] = {
{"user_data", lcontext_user_data },
{"get_aggregate_data", lcontext_get_aggregate_context },
{"set_aggregate_data", lcontext_set_aggregate_context },
{"aggregate_count", lcontext_aggregate_count },
{"result", lcontext_result },
{"result_null", lcontext_result_null },
{"result_number", lcontext_result_double },
{"result_double", lcontext_result_double },
{"result_int", lcontext_result_int },
{"result_text", lcontext_result_text },
{"result_blob", lcontext_result_blob },
{"result_error", lcontext_result_error },
{"__tostring", lcontext_tostring },
{NULL, NULL}
};
static const luaL_Reg sqlitelib[] = {
{"version", lsqlite_version },
{"complete", lsqlite_complete },
#ifndef WIN32
{"temp_directory", lsqlite_temp_directory },
#endif
{"open", lsqlite_open },
{"open_memory", lsqlite_open_memory },
{"__newindex", lsqlite_newindex },
{NULL, NULL}
};
static void create_meta(lua_State *L, const char *name, const luaL_Reg *lib) {
luaL_newmetatable(L, name);
lua_pushstring(L, "__index");
lua_pushvalue(L, -2); /* push metatable */
lua_rawset(L, -3); /* metatable.__index = metatable */
/* register metatable functions */
luaL_openlib(L, NULL, lib, 0);
/* remove metatable from stack */
lua_pop(L, 1);
}
LUALIB_API int luaopen_lsqlite3(lua_State *L) {
create_meta(L, sqlite_meta, dblib);
create_meta(L, sqlite_vm_meta, vmlib);
create_meta(L, sqlite_ctx_meta, ctxlib);
luaL_getmetatable(L, sqlite_ctx_meta);
sqlite_ctx_meta_ref = luaL_ref(L, LUA_REGISTRYINDEX);
/* register (local) sqlite metatable */
luaL_register(L, "sqlite3", sqlitelib);
{
int i = 0;
/* add constants to global table */
while (sqlite_constants[i].name) {
lua_pushstring(L, sqlite_constants[i].name);
lua_pushnumber(L, sqlite_constants[i].value);
lua_rawset(L, -3);
++i;
}
}
/* set sqlite's metatable to itself - set as readonly (__newindex) */
lua_pushvalue(L, -1);
lua_setmetatable(L, -2);
return 1;
}
200 ICON DISCARDABLE "systray.ico" 100 ICON DISCARDABLE "systray.ico"
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
build/systray.ico

1.05 KB | W: | H:

build/systray.ico

16.6 KB | W: | H:

build/systray.ico
build/systray.ico
build/systray.ico
build/systray.ico
  • 2-up
  • Swipe
  • Onion skin
...@@ -25,4 +25,4 @@ windows: ...@@ -25,4 +25,4 @@ windows:
$(CL) /DUSE_WEBSOCKET websocket.c ../mongoose.c $(CLFLAGS) $(CL) /DUSE_WEBSOCKET websocket.c ../mongoose.c $(CLFLAGS)
clean: 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
...@@ -325,12 +325,10 @@ static void redirect_to_ssl(struct mg_connection *conn, ...@@ -325,12 +325,10 @@ static void redirect_to_ssl(struct mg_connection *conn,
} }
} }
static void *event_handler(enum mg_event event, static int begin_request_handler(struct mg_connection *conn) {
struct mg_connection *conn) {
const struct mg_request_info *request_info = mg_get_request_info(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) { if (!request_info->is_ssl) {
redirect_to_ssl(conn, request_info); redirect_to_ssl(conn, request_info);
} else if (!is_authorized(conn, request_info)) { } else if (!is_authorized(conn, request_info)) {
...@@ -344,15 +342,8 @@ static void *event_handler(enum mg_event event, ...@@ -344,15 +342,8 @@ static void *event_handler(enum mg_event event,
} else { } else {
// No suitable handler found, mark as not processed. Mongoose will // No suitable handler found, mark as not processed. Mongoose will
// try to serve the request. // try to serve the request.
processed = NULL; processed = 0;
} }
} else if (event == MG_EVENT_LOG) {
printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data);
processed = NULL;
} else {
processed = NULL;
}
return processed; return processed;
} }
...@@ -365,6 +356,7 @@ static const char *options[] = { ...@@ -365,6 +356,7 @@ static const char *options[] = {
}; };
int main(void) { int main(void) {
struct mg_callbacks callbacks;
struct mg_context *ctx; struct mg_context *ctx;
// Initialize random number generator. It will be used later on for // Initialize random number generator. It will be used later on for
...@@ -372,7 +364,9 @@ int main(void) { ...@@ -372,7 +364,9 @@ int main(void) {
srand((unsigned) time(0)); srand((unsigned) time(0));
// Setup and start Mongoose // 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"); printf("%s\n", "Cannot start chat server, fatal exit");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
......
...@@ -2,15 +2,17 @@ ...@@ -2,15 +2,17 @@
#include <string.h> #include <string.h>
#include "mongoose.h" #include "mongoose.h"
static void *callback(enum mg_event event, // This function will be called by mongoose on every new request.
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); const struct mg_request_info *request_info = mg_get_request_info(conn);
char content[100];
if (event == MG_NEW_REQUEST) { // Prepare the message we're going to send
char content[1024];
int content_length = snprintf(content, sizeof(content), int content_length = snprintf(content, sizeof(content),
"Hello from mongoose! Remote port: %d", "Hello from mongoose! Remote port: %d",
request_info->remote_port); request_info->remote_port);
// Send HTTP reply to the client
mg_printf(conn, mg_printf(conn,
"HTTP/1.1 200 OK\r\n" "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n" "Content-Type: text/plain\r\n"
...@@ -18,19 +20,31 @@ static void *callback(enum mg_event event, ...@@ -18,19 +20,31 @@ static void *callback(enum mg_event event,
"\r\n" "\r\n"
"%s", "%s",
content_length, content); content_length, content);
// Mark as processed
return ""; // Returning non-zero tells mongoose that our function has replied to
} else { // the client, and mongoose should not send client any more data.
return NULL; return 1;
}
} }
int main(void) { int main(void) {
struct mg_context *ctx; struct mg_context *ctx;
struct mg_callbacks callbacks;
// List of options. Last element must be NULL.
const char *options[] = {"listening_ports", "8080", NULL}; const char *options[] = {"listening_ports", "8080", NULL};
ctx = mg_start(&callback, NULL, options); // Prepare callbacks structure. We have only one callback, the rest are NULL.
getchar(); // Wait until user hits "enter" 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); mg_stop(ctx);
return 0; return 0;
......
...@@ -10,24 +10,20 @@ static const char *html_form = ...@@ -10,24 +10,20 @@ static const char *html_form =
"<input type=\"submit\" />" "<input type=\"submit\" />"
"</form></body></html>"; "</form></body></html>";
static void *callback(enum mg_event event, static int begin_request_handler(struct mg_connection *conn) {
struct mg_connection *conn) {
const struct mg_request_info *ri = mg_get_request_info(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")) { if (!strcmp(ri->uri, "/handle_post_request")) {
// User has submitted a form, show submitted data and a variable value // 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;
// Read POST data
post_data_len = mg_read(conn, post_data, sizeof(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 // 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_1", input1, sizeof(input1));
mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2)); mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2));
// Send reply to the client, showing submitted form values.
mg_printf(conn, "HTTP/1.0 200 OK\r\n" mg_printf(conn, "HTTP/1.0 200 OK\r\n"
"Content-Type: text/plain\r\n\r\n" "Content-Type: text/plain\r\n\r\n"
"Submitted data: [%.*s]\n" "Submitted data: [%.*s]\n"
...@@ -42,18 +38,17 @@ static void *callback(enum mg_event event, ...@@ -42,18 +38,17 @@ static void *callback(enum mg_event event,
"Content-Type: text/html\r\n\r\n%s", "Content-Type: text/html\r\n\r\n%s",
(int) strlen(html_form), html_form); (int) strlen(html_form), html_form);
} }
// Mark as processed return 1; // Mark request as processed
return "";
} else {
return NULL;
}
} }
int main(void) { int main(void) {
struct mg_context *ctx; struct mg_context *ctx;
const char *options[] = {"listening_ports", "8080", NULL}; 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" getchar(); // Wait until user hits "enter"
mg_stop(ctx); mg_stop(ctx);
......
...@@ -17,8 +17,7 @@ typedef __int64 int64_t; ...@@ -17,8 +17,7 @@ typedef __int64 int64_t;
#include "mongoose.h" #include "mongoose.h"
static void *callback(enum mg_event event, struct mg_connection *conn) { static int begin_request_handler(struct mg_connection *conn) {
if (event == MG_NEW_REQUEST) {
if (!strcmp(mg_get_request_info(conn)->uri, "/handle_post_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_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n");
mg_upload(conn, "/tmp"); mg_upload(conn, "/tmp");
...@@ -37,22 +36,25 @@ static void *callback(enum mg_event event, struct mg_connection *conn) { ...@@ -37,22 +36,25 @@ static void *callback(enum mg_event event, struct mg_connection *conn) {
"Content-Type: text/html\r\n\r\n%s", "Content-Type: text/html\r\n\r\n%s",
(int) strlen(html_form), html_form); (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);
}
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) { int main(void) {
struct mg_context *ctx; struct mg_context *ctx;
const char *options[] = {"listening_ports", "8080", NULL}; 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" getchar(); // Wait until user hits "enter"
pause();
mg_stop(ctx); mg_stop(ctx);
return 0; return 0;
......
...@@ -5,14 +5,14 @@ ...@@ -5,14 +5,14 @@
#include <string.h> #include <string.h>
#include "mongoose.h" #include "mongoose.h"
static void *callback(enum mg_event event, struct mg_connection *conn) { static void websocket_ready_handler(struct mg_connection *conn) {
if (event == MG_WEBSOCKET_READY) {
unsigned char buf[40]; unsigned char buf[40];
buf[0] = 0x81; buf[0] = 0x81;
buf[1] = snprintf((char *) buf + 2, sizeof(buf) - 2, "%s", "server ready"); buf[1] = snprintf((char *) buf + 2, sizeof(buf) - 2, "%s", "server ready");
mg_write(conn, buf, 2 + buf[1]); mg_write(conn, buf, 2 + buf[1]);
return ""; // MG_WEBSOCKET_READY return value is ignored }
} else if (event == MG_WEBSOCKET_MESSAGE) {
static int websocket_data_handler(struct mg_connection *conn) {
unsigned char buf[200], reply[200]; unsigned char buf[200], reply[200];
int n, i, mask_len, xor, msg_len, len; int n, i, mask_len, xor, msg_len, len;
...@@ -22,14 +22,14 @@ static void *callback(enum mg_event event, struct mg_connection *conn) { ...@@ -22,14 +22,14 @@ static void *callback(enum mg_event event, struct mg_connection *conn) {
msg_len = mask_len = 0; msg_len = mask_len = 0;
for (;;) { for (;;) {
if ((n = mg_read(conn, buf + len, sizeof(buf) - len)) <= 0) { if ((n = mg_read(conn, buf + len, sizeof(buf) - len)) <= 0) {
return ""; // Read error, close websocket return 0; // Read error, close websocket
} }
len += n; len += n;
if (len >= 2) { if (len >= 2) {
msg_len = buf[1] & 127; msg_len = buf[1] & 127;
mask_len = (buf[1] & 128) ? 4 : 0; mask_len = (buf[1] & 128) ? 4 : 0;
if (msg_len > 125) { if (msg_len > 125) {
return ""; // Message is too long, close websocket return 0; // Message is too long, close websocket
} }
// If we've buffered the whole message, exit the loop // If we've buffered the whole message, exit the loop
if (len >= 2 + mask_len + msg_len) { if (len >= 2 + mask_len + msg_len) {
...@@ -51,23 +51,24 @@ static void *callback(enum mg_event event, struct mg_connection *conn) { ...@@ -51,23 +51,24 @@ static void *callback(enum mg_event event, struct mg_connection *conn) {
// Echo the message back to the client // Echo the message back to the client
mg_write(conn, reply, 2 + msg_len); mg_write(conn, reply, 2 + msg_len);
// Return non-NULL means stoping websocket conversation. // Returnint zero means stoping websocket conversation.
// Close the conversation if client has sent us "exit" string. // Close the conversation if client has sent us "exit" string.
return memcmp(reply + 2, "exit", 4) == 0 ? "" : NULL; return memcmp(reply + 2, "exit", 4);
} else {
return NULL;
}
} }
int main(void) { int main(void) {
struct mg_context *ctx; struct mg_context *ctx;
struct mg_callbacks callbacks;
const char *options[] = { const char *options[] = {
"listening_ports", "8080", "listening_ports", "8080",
"document_root", "websocket_html_root", "document_root", "websocket_html_root",
NULL 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" getchar(); // Wait until user hits "enter"
mg_stop(ctx); mg_stop(ctx);
......
// 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 // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#include <winsvc.h> #include <winsvc.h>
#include <shlobj.h>
#define PATH_MAX MAX_PATH #define PATH_MAX MAX_PATH
#define S_ISDIR(x) ((x) & _S_IFDIR) #define S_ISDIR(x) ((x) & _S_IFDIR)
#define DIRSEP '\\' #define DIRSEP '\\'
...@@ -104,13 +105,36 @@ static void show_usage_and_exit(void) { ...@@ -104,13 +105,36 @@ static void show_usage_and_exit(void) {
fprintf(stderr, " -%s %s (default: \"%s\")\n", fprintf(stderr, " -%s %s (default: \"%s\")\n",
names[i], names[i + 1], names[i + 2] == NULL ? "" : names[i + 2]); 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); exit(EXIT_FAILURE);
} }
#if defined(_WIN32) || defined(USE_COCOA) #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) { static void create_config_file(const char *path) {
const char **names, *value; const char **names, *value;
FILE *fp; FILE *fp;
...@@ -120,13 +144,7 @@ static void create_config_file(const char *path) { ...@@ -120,13 +144,7 @@ static void create_config_file(const char *path) {
if ((fp = fopen(path, "r")) != NULL) { if ((fp = fopen(path, "r")) != NULL) {
fclose(fp); fclose(fp);
} else if ((fp = fopen(path, "a+")) != NULL) { } else if ((fp = fopen(path, "a+")) != NULL) {
fprintf(fp, "%s", fprintf(fp, "%s", 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");
names = mg_get_valid_option_names(); names = mg_get_valid_option_names();
for (i = 0; names[i] != NULL; i += 3) { for (i = 0; names[i] != NULL; i += 3) {
value = mg_get_option(ctx, names[i]); value = mg_get_option(ctx, names[i]);
...@@ -215,30 +233,38 @@ static void process_command_line_arguments(char *argv[], char **options) { ...@@ -215,30 +233,38 @@ static void process_command_line_arguments(char *argv[], char **options) {
// Loop over the lines in config file // Loop over the lines in config file
while (fgets(line, sizeof(line), fp) != NULL) { while (fgets(line, sizeof(line), fp) != NULL) {
line_no++; line_no++;
// Ignore empty lines and comments // Ignore empty lines and comments
for (i = 0; isspace(* (unsigned char *) &line[i]); ) i++; for (i = 0; isspace(* (unsigned char *) &line[i]); ) i++;
if (line[i] == '#' || line[i] == '\0') if (line[i] == '#' || line[i] == '\0') {
continue; continue;
}
if (sscanf(line, "%s %[^\r\n#]", opt, val) != 2) { 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); (void) fclose(fp);
} }
// Handle command line flags. They override config file and default settings. // 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) { for (i = cmd_line_opts_start; argv[i] != NULL; i += 2) {
if (argv[i][0] != '-' || argv[i + 1] == NULL) { if (argv[i][0] != '-' || argv[i + 1] == NULL) {
show_usage_and_exit(); show_usage_and_exit();
} }
set_option(options, &argv[i][1], argv[i + 1]); set_option(options, &argv[i][1], argv[i + 1]);
} }
}
} }
static void init_server_name(void) { static void init_server_name(void) {
...@@ -246,17 +272,14 @@ static void init_server_name(void) { ...@@ -246,17 +272,14 @@ static void init_server_name(void) {
mg_version()); mg_version());
} }
static void *mongoose_callback(enum mg_event ev, struct mg_connection *conn) { static int log_message(const struct mg_connection *conn, const char *message) {
if (ev == MG_EVENT_LOG) { (void) conn;
printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data); printf("%s\n", message);
} return 0;
// Returning NULL marks request as not handled, signalling mongoose to
// proceed with handling it.
return NULL;
} }
static void start_mongoose(int argc, char *argv[]) { static void start_mongoose(int argc, char *argv[]) {
struct mg_callbacks callbacks;
char *options[MAX_OPTIONS]; char *options[MAX_OPTIONS];
int i; int i;
...@@ -282,7 +305,9 @@ static void start_mongoose(int argc, char *argv[]) { ...@@ -282,7 +305,9 @@ static void start_mongoose(int argc, char *argv[]) {
signal(SIGINT, signal_handler); signal(SIGINT, signal_handler);
/* Start Mongoose */ /* 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++) { for (i = 0; options[i] != NULL; i++) {
free(options[i]); free(options[i]);
} }
...@@ -293,9 +318,25 @@ static void start_mongoose(int argc, char *argv[]) { ...@@ -293,9 +318,25 @@ static void start_mongoose(int argc, char *argv[]) {
} }
#ifdef _WIN32 #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 ss;
static SERVICE_STATUS_HANDLE hStatus; static SERVICE_STATUS_HANDLE hStatus;
static const char *service_magic_argument = "--"; static const char *service_magic_argument = "--";
static NOTIFYICONDATA TrayIcon;
static void WINAPI ControlHandler(DWORD code) { static void WINAPI ControlHandler(DWORD code) {
if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) { if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) {
...@@ -323,21 +364,6 @@ static void WINAPI ServiceMain(void) { ...@@ -323,21 +364,6 @@ static void WINAPI ServiceMain(void) {
SetServiceStatus(hStatus, &ss); 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) { static void show_error(void) {
char buf[256]; char buf[256];
...@@ -348,6 +374,272 @@ static void show_error(void) { ...@@ -348,6 +374,272 @@ static void show_error(void) {
MessageBox(NULL, buf, "Error", MB_OK); 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 int manage_service(int action) {
static const char *service_name = "Mongoose"; static const char *service_name = "Mongoose";
SC_HANDLE hSCM = NULL, hService = NULL; SC_HANDLE hSCM = NULL, hService = NULL;
...@@ -417,13 +709,18 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, ...@@ -417,13 +709,18 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
Shell_NotifyIcon(NIM_DELETE, &TrayIcon); Shell_NotifyIcon(NIM_DELETE, &TrayIcon);
PostQuitMessage(0); PostQuitMessage(0);
return 0; return 0;
case ID_EDIT_CONFIG: case ID_SETTINGS:
edit_config_file(); show_settings_dialog();
break; break;
case ID_INSTALL_SERVICE: case ID_INSTALL_SERVICE:
case ID_REMOVE_SERVICE: case ID_REMOVE_SERVICE:
manage_service(LOWORD(wParam)); manage_service(LOWORD(wParam));
break; 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; break;
case WM_USER: case WM_USER:
...@@ -443,7 +740,9 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, ...@@ -443,7 +740,9 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
AppendMenu(hMenu, MF_STRING | (!service_installed ? MF_GRAYED : 0), AppendMenu(hMenu, MF_STRING | (!service_installed ? MF_GRAYED : 0),
ID_REMOVE_SERVICE, "Deinstall service"); ID_REMOVE_SERVICE, "Deinstall service");
AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, ""); 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"); AppendMenu(hMenu, MF_STRING, ID_QUIT, "Exit");
GetCursorPos(&pt); GetCursorPos(&pt);
SetForegroundWindow(hWnd); SetForegroundWindow(hWnd);
...@@ -480,9 +779,10 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) { ...@@ -480,9 +779,10 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) {
ShowWindow(hWnd, SW_HIDE); ShowWindow(hWnd, SW_HIDE);
TrayIcon.cbSize = sizeof(TrayIcon); TrayIcon.cbSize = sizeof(TrayIcon);
TrayIcon.uID = ID_TRAYICON; TrayIcon.uID = ID_ICON;
TrayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; TrayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
TrayIcon.hIcon = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(ID_ICON), TrayIcon.hIcon = hIcon = LoadImage(GetModuleHandle(NULL),
MAKEINTRESOURCE(ID_ICON),
IMAGE_ICON, 16, 16, 0); IMAGE_ICON, 16, 16, 0);
TrayIcon.hWnd = hWnd; TrayIcon.hWnd = hWnd;
snprintf(TrayIcon.szTip, sizeof(TrayIcon.szTip), "%s", server_name); snprintf(TrayIcon.szTip, sizeof(TrayIcon.szTip), "%s", server_name);
...@@ -509,7 +809,7 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) { ...@@ -509,7 +809,7 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) {
- (void) openBrowser { - (void) openBrowser {
[[NSWorkspace sharedWorkspace] [[NSWorkspace sharedWorkspace]
openURL:[NSURL URLWithString: openURL:[NSURL URLWithString:
[NSString stringWithUTF8String:"http://www.yahoo.com"]]]; [NSString stringWithUTF8String:get_url_to_first_open_port(ctx)]]];
} }
- (void) editConfig { - (void) editConfig {
create_config_file(config_file); create_config_file(config_file);
......
.\" 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 .
...@@ -100,7 +100,7 @@ typedef long off_t; ...@@ -100,7 +100,7 @@ typedef long off_t;
#if defined(_MSC_VER) && _MSC_VER < 1300 #if defined(_MSC_VER) && _MSC_VER < 1300
#define STRX(x) #x #define STRX(x) #x
#define STR(x) STRX(x) #define STR(x) STRX(x)
#define __func__ "line " STR(__LINE__) #define __func__ __FILE__ ":" STR(__LINE__)
#define strtoull(x, y, z) strtoul(x, y, z) #define strtoull(x, y, z) strtoul(x, y, z)
#define strtoll(x, y, z) strtol(x, y, z) #define strtoll(x, y, z) strtol(x, y, z)
#else #else
...@@ -139,6 +139,7 @@ typedef long off_t; ...@@ -139,6 +139,7 @@ typedef long off_t;
#define flockfile(x) EnterCriticalSection(&global_log_file_lock) #define flockfile(x) EnterCriticalSection(&global_log_file_lock)
#define funlockfile(x) LeaveCriticalSection(&global_log_file_lock) #define funlockfile(x) LeaveCriticalSection(&global_log_file_lock)
#define sleep(x) Sleep((x) * 1000) #define sleep(x) Sleep((x) * 1000)
#define va_copy(x, y) x = y
#if !defined(fileno) #if !defined(fileno)
#define fileno(x) _fileno(x) #define fileno(x) _fileno(x)
...@@ -176,13 +177,23 @@ typedef struct DIR { ...@@ -176,13 +177,23 @@ typedef struct DIR {
struct dirent result; struct dirent result;
} DIR; } DIR;
#ifndef HAS_POLL
struct pollfd {
int fd;
short events;
short revents;
};
#define POLLIN 1
#endif
// Mark required libraries // Mark required libraries
#pragma comment(lib, "Ws2_32.lib") #pragma comment(lib, "Ws2_32.lib")
#else // UNIX specific #else // UNIX specific
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/select.h> #include <sys/poll.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <sys/time.h> #include <sys/time.h>
...@@ -231,7 +242,7 @@ typedef int SOCKET; ...@@ -231,7 +242,7 @@ typedef int SOCKET;
#include <lauxlib.h> #include <lauxlib.h>
#endif #endif
#define MONGOOSE_VERSION "3.6" #define MONGOOSE_VERSION "3.8"
#define PASSWORDS_FILE_NAME ".htpasswd" #define PASSWORDS_FILE_NAME ".htpasswd"
#define CGI_ENVIRONMENT_SIZE 4096 #define CGI_ENVIRONMENT_SIZE 4096
#define MAX_CGI_ENVIR_VARS 64 #define MAX_CGI_ENVIR_VARS 64
...@@ -286,45 +297,15 @@ typedef int socklen_t; ...@@ -286,45 +297,15 @@ typedef int socklen_t;
static const char *http_500_error = "Internal Server Error"; static const char *http_500_error = "Internal Server Error";
// Snatched from OpenSSL includes. I put the prototypes here to be independent #if defined(NO_SSL_DL)
// from the OpenSSL source installation. Having this, mongoose + SSL can be #include <openssl/ssl.h>
// built on any system with binary SSL libraries installed. #else
// SSL loaded dynamically from DLL.
// I put the prototypes here to be independent from OpenSSL source installation.
typedef struct ssl_st SSL; typedef struct ssl_st SSL;
typedef struct ssl_method_st SSL_METHOD; typedef struct ssl_method_st SSL_METHOD;
typedef struct ssl_ctx_st SSL_CTX; typedef struct ssl_ctx_st SSL_CTX;
#define SSL_ERROR_WANT_READ 2
#define SSL_ERROR_WANT_WRITE 3
#define SSL_FILETYPE_PEM 1
#define CRYPTO_LOCK 1
#if defined(NO_SSL_DL)
extern void SSL_free(SSL *);
extern int SSL_accept(SSL *);
extern int SSL_connect(SSL *);
extern int SSL_read(SSL *, void *, int);
extern int SSL_write(SSL *, const void *, int);
extern int SSL_get_error(const SSL *, int);
extern int SSL_set_fd(SSL *, int);
extern int SSL_pending(SSL *);
extern SSL *SSL_new(SSL_CTX *);
extern SSL_CTX *SSL_CTX_new(SSL_METHOD *);
extern SSL_METHOD *SSLv23_server_method(void);
extern SSL_METHOD *SSLv23_client_method(void);
extern int SSL_library_init(void);
extern void SSL_load_error_strings(void);
extern int SSL_CTX_use_PrivateKey_file(SSL_CTX *, const char *, int);
extern int SSL_CTX_use_certificate_file(SSL_CTX *, const char *, int);
extern int SSL_CTX_use_certificate_chain_file(SSL_CTX *, const char *);
extern void SSL_CTX_set_default_passwd_cb(SSL_CTX *, mg_callback_t);
extern void SSL_CTX_free(SSL_CTX *);
extern unsigned long ERR_get_error(void);
extern char *ERR_error_string(unsigned long, char *);
extern int CRYPTO_num_locks(void);
extern void CRYPTO_set_locking_callback(void (*)(int, int, const char *, int));
extern void CRYPTO_set_id_callback(unsigned long (*)(void));
#else
// Dynamically loaded SSL functionality
struct ssl_func { struct ssl_func {
const char *name; // SSL function name const char *name; // SSL function name
void (*ptr)(void); // Function pointer void (*ptr)(void); // Function pointer
...@@ -353,6 +334,7 @@ struct ssl_func { ...@@ -353,6 +334,7 @@ struct ssl_func {
(* (int (*)(SSL_CTX *, const char *)) ssl_sw[16].ptr) (* (int (*)(SSL_CTX *, const char *)) ssl_sw[16].ptr)
#define SSLv23_client_method (* (SSL_METHOD * (*)(void)) ssl_sw[17].ptr) #define SSLv23_client_method (* (SSL_METHOD * (*)(void)) ssl_sw[17].ptr)
#define SSL_pending (* (int (*)(SSL *)) ssl_sw[18].ptr) #define SSL_pending (* (int (*)(SSL *)) ssl_sw[18].ptr)
#define SSL_CTX_set_verify (* (void (*)(SSL_CTX *, int, int)) ssl_sw[19].ptr)
#define CRYPTO_num_locks (* (int (*)(void)) crypto_sw[0].ptr) #define CRYPTO_num_locks (* (int (*)(void)) crypto_sw[0].ptr)
#define CRYPTO_set_locking_callback \ #define CRYPTO_set_locking_callback \
...@@ -386,6 +368,7 @@ static struct ssl_func ssl_sw[] = { ...@@ -386,6 +368,7 @@ static struct ssl_func ssl_sw[] = {
{"SSL_CTX_use_certificate_chain_file", NULL}, {"SSL_CTX_use_certificate_chain_file", NULL},
{"SSLv23_client_method", NULL}, {"SSLv23_client_method", NULL},
{"SSL_pending", NULL}, {"SSL_pending", NULL},
{"SSL_CTX_set_verify", NULL},
{NULL, NULL} {NULL, NULL}
}; };
...@@ -435,11 +418,11 @@ struct file { ...@@ -435,11 +418,11 @@ struct file {
// Describes listening socket, or socket which was accept()-ed by the master // Describes listening socket, or socket which was accept()-ed by the master
// thread and queued for future handling by the worker thread. // thread and queued for future handling by the worker thread.
struct socket { struct socket {
struct socket *next; // Linkage
SOCKET sock; // Listening socket SOCKET sock; // Listening socket
union usa lsa; // Local socket address union usa lsa; // Local socket address
union usa rsa; // Remote socket address union usa rsa; // Remote socket address
int is_ssl; // Is socket SSL-ed unsigned is_ssl:1; // Is port SSL-ed
unsigned ssl_redir:1; // Is port supposed to redirect everything to SSL port
}; };
// NOTE(lsm): this enum shoulds be in sync with the config_options below. // NOTE(lsm): this enum shoulds be in sync with the config_options below.
...@@ -449,14 +432,14 @@ enum { ...@@ -449,14 +432,14 @@ enum {
ACCESS_LOG_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE, ACCESS_LOG_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE,
GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST, GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST,
EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE, EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE,
NUM_THREADS, RUN_AS_USER, REWRITE, HIDE_FILES, NUM_THREADS, RUN_AS_USER, REWRITE, HIDE_FILES, REQUEST_TIMEOUT,
NUM_OPTIONS NUM_OPTIONS
}; };
static const char *config_options[] = { static const char *config_options[] = {
"C", "cgi_pattern", "**.cgi$|**.pl$|**.php$", "C", "cgi_pattern", "**.cgi$|**.pl$|**.php$",
"E", "cgi_environment", NULL, "E", "cgi_environment", NULL,
"G", "put_delete_passwords_file", NULL, "G", "put_delete_auth_file", NULL,
"I", "cgi_interpreter", NULL, "I", "cgi_interpreter", NULL,
"P", "protect_uri", NULL, "P", "protect_uri", NULL,
"R", "authentication_domain", "mydomain.com", "R", "authentication_domain", "mydomain.com",
...@@ -465,18 +448,20 @@ static const char *config_options[] = { ...@@ -465,18 +448,20 @@ static const char *config_options[] = {
"a", "access_log_file", NULL, "a", "access_log_file", NULL,
"d", "enable_directory_listing", "yes", "d", "enable_directory_listing", "yes",
"e", "error_log_file", NULL, "e", "error_log_file", NULL,
"g", "global_passwords_file", NULL, "g", "global_auth_file", NULL,
"i", "index_files", "index.html,index.htm,index.cgi,index.shtml,index.php", "i", "index_files",
"index.html,index.htm,index.cgi,index.shtml,index.php,index.lp",
"k", "enable_keep_alive", "no", "k", "enable_keep_alive", "no",
"l", "access_control_list", NULL, "l", "access_control_list", NULL,
"m", "extra_mime_types", NULL, "m", "extra_mime_types", NULL,
"p", "listening_ports", "8080", "p", "listening_ports", "8080",
"r", "document_root", ".", "r", "document_root", ".",
"s", "ssl_certificate", NULL, "s", "ssl_certificate", NULL,
"t", "num_threads", "20", "t", "num_threads", "50",
"u", "run_as_user", NULL, "u", "run_as_user", NULL,
"w", "url_rewrite_patterns", NULL, "w", "url_rewrite_patterns", NULL,
"x", "hide_files_patterns", NULL, "x", "hide_files_patterns", NULL,
"z", "request_timeout_ms", "30000",
NULL NULL
}; };
#define ENTRIES_PER_CONFIG_OPTION 3 #define ENTRIES_PER_CONFIG_OPTION 3
...@@ -484,12 +469,12 @@ static const char *config_options[] = { ...@@ -484,12 +469,12 @@ static const char *config_options[] = {
struct mg_context { struct mg_context {
volatile int stop_flag; // Should we stop event loop volatile int stop_flag; // Should we stop event loop
SSL_CTX *ssl_ctx; // SSL context SSL_CTX *ssl_ctx; // SSL context
SSL_CTX *client_ssl_ctx; // Client SSL context
char *config[NUM_OPTIONS]; // Mongoose configuration parameters char *config[NUM_OPTIONS]; // Mongoose configuration parameters
mg_callback_t user_callback; // User-defined callback function struct mg_callbacks callbacks; // User-defined callback function
void *user_data; // User-defined data void *user_data; // User-defined data
struct socket *listening_sockets; struct socket *listening_sockets;
int num_listening_sockets;
volatile int num_threads; // Number of threads volatile int num_threads; // Number of threads
pthread_mutex_t mutex; // Protects (max|num)_threads pthread_mutex_t mutex; // Protects (max|num)_threads
...@@ -506,6 +491,7 @@ struct mg_connection { ...@@ -506,6 +491,7 @@ struct mg_connection {
struct mg_request_info request_info; struct mg_request_info request_info;
struct mg_context *ctx; struct mg_context *ctx;
SSL *ssl; // SSL descriptor SSL *ssl; // SSL descriptor
SSL_CTX *client_ssl_ctx; // SSL context for client connections
struct socket client; // Connected client struct socket client; // Connected client
time_t birth_time; // Time when request was received time_t birth_time; // Time when request was received
int64_t num_bytes_sent; // Total bytes sent to client int64_t num_bytes_sent; // Total bytes sent to client
...@@ -527,19 +513,14 @@ const char **mg_get_valid_option_names(void) { ...@@ -527,19 +513,14 @@ const char **mg_get_valid_option_names(void) {
return config_options; return config_options;
} }
static void *call_user(struct mg_connection *conn, enum mg_event event) {
if (conn != NULL && conn->ctx != NULL) {
conn->request_info.user_data = conn->ctx->user_data;
}
return conn == NULL || conn->ctx == NULL || conn->ctx->user_callback == NULL ?
NULL : conn->ctx->user_callback(event, conn);
}
static int is_file_in_memory(struct mg_connection *conn, const char *path, static int is_file_in_memory(struct mg_connection *conn, const char *path,
struct file *filep) { struct file *filep) {
conn->request_info.ev_data = (void *) path; size_t size = 0;
if ((filep->membuf = call_user(conn, MG_OPEN_FILE)) != NULL) { if ((filep->membuf = conn->ctx->callbacks.open_file == NULL ? NULL :
filep->size = (long) conn->request_info.ev_data; conn->ctx->callbacks.open_file(conn, path, &size)) != NULL) {
// NOTE: override filep->size only on success. Otherwise, it might break
// constructs like if (!mg_stat() || !mg_fopen()) ...
filep->size = size;
} }
return filep->membuf != NULL; return filep->membuf != NULL;
} }
...@@ -625,8 +606,8 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) { ...@@ -625,8 +606,8 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) {
// Do not lock when getting the callback value, here and below. // Do not lock when getting the callback value, here and below.
// I suppose this is fine, since function cannot disappear in the // I suppose this is fine, since function cannot disappear in the
// same way string option can. // same way string option can.
conn->request_info.ev_data = buf; if (conn->ctx->callbacks.log_message == NULL ||
if (call_user(conn, MG_EVENT_LOG) == NULL) { conn->ctx->callbacks.log_message(conn, buf) == 0) {
fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL : fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
fopen(conn->ctx->config[ERROR_LOG_FILE], "a+"); fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
...@@ -649,7 +630,6 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) { ...@@ -649,7 +630,6 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) {
fclose(fp); fclose(fp);
} }
} }
conn->request_info.ev_data = NULL;
} }
// Return fake connection structure. Used for logging, if connection // Return fake connection structure. Used for logging, if connection
...@@ -932,13 +912,10 @@ static void send_http_error(struct mg_connection *conn, int status, ...@@ -932,13 +912,10 @@ static void send_http_error(struct mg_connection *conn, int status,
const char *reason, const char *fmt, ...) { const char *reason, const char *fmt, ...) {
char buf[MG_BUF_LEN]; char buf[MG_BUF_LEN];
va_list ap; va_list ap;
int len; int len = 0;
conn->status_code = status; conn->status_code = status;
conn->request_info.ev_data = (void *) (long) status;
if (call_user(conn, MG_HTTP_ERROR) == NULL) {
buf[0] = '\0'; buf[0] = '\0';
len = 0;
// Errors 1xx, 204 and 304 MUST NOT send a body // Errors 1xx, 204 and 304 MUST NOT send a body
if (status > 199 && status != 204 && status != 304) { if (status > 199 && status != 204 && status != 304) {
...@@ -956,7 +933,6 @@ static void send_http_error(struct mg_connection *conn, int status, ...@@ -956,7 +933,6 @@ static void send_http_error(struct mg_connection *conn, int status,
"Connection: %s\r\n\r\n", status, reason, len, "Connection: %s\r\n\r\n", status, reason, len,
suggest_connection_header(conn)); suggest_connection_header(conn));
conn->num_bytes_sent += mg_printf(conn, "%s", buf); conn->num_bytes_sent += mg_printf(conn, "%s", buf);
}
} }
#if defined(_WIN32) && !defined(__SYMBIAN32__) #if defined(_WIN32) && !defined(__SYMBIAN32__)
...@@ -1158,7 +1134,7 @@ static int mg_mkdir(const char *path, int mode) { ...@@ -1158,7 +1134,7 @@ static int mg_mkdir(const char *path, int mode) {
mg_strlcpy(buf, path, sizeof(buf)); mg_strlcpy(buf, path, sizeof(buf));
change_slashes_to_backslashes(buf); change_slashes_to_backslashes(buf);
(void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, ARRAY_SIZE(wbuf));
return CreateDirectoryW(wbuf, NULL) ? 0 : -1; return CreateDirectoryW(wbuf, NULL) ? 0 : -1;
} }
...@@ -1231,6 +1207,33 @@ static struct dirent *readdir(DIR *dir) { ...@@ -1231,6 +1207,33 @@ static struct dirent *readdir(DIR *dir) {
return result; return result;
} }
#ifndef HAVE_POLL
static int poll(struct pollfd *pfd, int n, int milliseconds) {
struct timeval tv;
fd_set set;
int i, result;
tv.tv_sec = milliseconds / 1000;
tv.tv_usec = (milliseconds % 1000) * 1000;
FD_ZERO(&set);
for (i = 0; i < n; i++) {
FD_SET((SOCKET) pfd[i].fd, &set);
pfd[i].revents = 0;
}
if ((result = select(0, &set, NULL, NULL, &tv)) > 0) {
for (i = 0; i < n; i++) {
if (FD_ISSET(pfd[i].fd, &set)) {
pfd[i].revents = POLLIN;
}
}
}
return result;
}
#endif // HAVE_POLL
#define set_close_on_exec(x) // No FD_CLOEXEC on Windows #define set_close_on_exec(x) // No FD_CLOEXEC on Windows
int mg_start_thread(mg_thread_func_t f, void *p) { int mg_start_thread(mg_thread_func_t f, void *p) {
...@@ -1265,7 +1268,7 @@ static pid_t spawn_process(struct mg_connection *conn, const char *prog, ...@@ -1265,7 +1268,7 @@ static pid_t spawn_process(struct mg_connection *conn, const char *prog,
HANDLE me; HANDLE me;
char *p, *interp, full_interp[PATH_MAX], full_dir[PATH_MAX], char *p, *interp, full_interp[PATH_MAX], full_dir[PATH_MAX],
cmdline[PATH_MAX], buf[PATH_MAX]; cmdline[PATH_MAX], buf[PATH_MAX];
struct file file; struct file file = STRUCT_FILE_INITIALIZER;
STARTUPINFOA si = { sizeof(si) }; STARTUPINFOA si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 }; PROCESS_INFORMATION pi = { 0 };
...@@ -1376,7 +1379,7 @@ static pid_t spawn_process(struct mg_connection *conn, const char *prog, ...@@ -1376,7 +1379,7 @@ static pid_t spawn_process(struct mg_connection *conn, const char *prog,
pid_t pid; pid_t pid;
const char *interp; const char *interp;
envblk = NULL; // Unused (void) envblk;
if ((pid = fork()) == -1) { if ((pid = fork()) == -1) {
// Parent // Parent
...@@ -1444,9 +1447,12 @@ static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf, ...@@ -1444,9 +1447,12 @@ static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
// How many bytes we send in this iteration // How many bytes we send in this iteration
k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent); k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
#ifndef NO_SSL
if (ssl != NULL) { if (ssl != NULL) {
n = SSL_write(ssl, buf + sent, k); n = SSL_write(ssl, buf + sent, k);
} else if (fp != NULL) { } else
#endif
if (fp != NULL) {
n = (int) fwrite(buf + sent, 1, (size_t) k, fp); n = (int) fwrite(buf + sent, 1, (size_t) k, fp);
if (ferror(fp)) if (ferror(fp))
n = -1; n = -1;
...@@ -1454,7 +1460,7 @@ static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf, ...@@ -1454,7 +1460,7 @@ static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL); n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
} }
if (n < 0) if (n <= 0)
break; break;
sent += n; sent += n;
...@@ -1463,31 +1469,6 @@ static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf, ...@@ -1463,31 +1469,6 @@ static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
return sent; return sent;
} }
// This function is needed to prevent Mongoose to be stuck in a blocking
// socket read when user requested exit. To do that, we sleep in select
// with a timeout, and when returned, check the context for the stop flag.
// If it is set, we return 0, and this means that we must not continue
// reading, must give up and close the connection and exit serving thread.
static int wait_until_socket_is_readable(struct mg_connection *conn) {
int result;
struct timeval tv;
fd_set set;
do {
tv.tv_sec = 0;
tv.tv_usec = 300 * 1000;
FD_ZERO(&set);
FD_SET(conn->client.sock, &set);
result = select(conn->client.sock + 1, &set, NULL, NULL, &tv);
if(result == 0 && conn->ssl != NULL) {
result = SSL_pending(conn->ssl);
}
} while ((result == 0 || (result < 0 && ERRNO == EINTR)) &&
conn->ctx->stop_flag == 0);
return conn->ctx->stop_flag || result < 0 ? 0 : 1;
}
// Read from IO channel - opened file descriptor, socket, or SSL descriptor. // Read from IO channel - opened file descriptor, socket, or SSL descriptor.
// Return negative value on error, or number of bytes read on success. // Return negative value on error, or number of bytes read on success.
static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) { static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
...@@ -1498,10 +1479,10 @@ static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) { ...@@ -1498,10 +1479,10 @@ static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
// pipe, fread() may block until IO buffer is filled up. We cannot afford // pipe, fread() may block until IO buffer is filled up. We cannot afford
// to block and must pass all read bytes immediately to the client. // to block and must pass all read bytes immediately to the client.
nread = read(fileno(fp), buf, (size_t) len); nread = read(fileno(fp), buf, (size_t) len);
} else if (!conn->must_close && !wait_until_socket_is_readable(conn)) { #ifndef NO_SSL
nread = -1;
} else if (conn->ssl != NULL) { } else if (conn->ssl != NULL) {
nread = SSL_read(conn->ssl, buf, len); nread = SSL_read(conn->ssl, buf, len);
#endif
} else { } else {
nread = recv(conn->client.sock, buf, (size_t) len, 0); nread = recv(conn->client.sock, buf, (size_t) len, 0);
} }
...@@ -1592,43 +1573,53 @@ int mg_write(struct mg_connection *conn, const void *buf, size_t len) { ...@@ -1592,43 +1573,53 @@ int mg_write(struct mg_connection *conn, const void *buf, size_t len) {
return (int) total; return (int) total;
} }
int mg_printf(struct mg_connection *conn, const char *fmt, ...) { // Print message to buffer. If buffer is large enough to hold the message,
char mem[MG_BUF_LEN], *buf = mem; // return buffer. If buffer is to small, allocate large enough buffer on heap,
// and return allocated buffer.
static int alloc_vprintf(char **buf, size_t size, const char *fmt, va_list ap) {
va_list ap_copy;
int len; int len;
va_list ap;
// Print in a local buffer first, hoping that it is large enough to // Windows is not standard-compliant, and vsnprintf() returns -1 if
// hold the whole message // buffer is too small. Also, older versions of msvcrt.dll do not have
va_start(ap, fmt); // _vscprintf(). However, if size is 0, vsnprintf() behaves correctly.
len = vsnprintf(mem, sizeof(mem), fmt, ap); // Therefore, we make two passes: on first pass, get required message length.
va_end(ap); // On second pass, actually print the message.
va_copy(ap_copy, ap);
len = vsnprintf(NULL, 0, fmt, ap_copy);
if (len > (int) size &&
(size = len + 1) > 0 &&
(*buf = (char *) malloc(size)) == NULL) {
len = -1; // Allocation failed, mark failure
} else {
va_copy(ap_copy, ap);
vsnprintf(*buf, size, fmt, ap_copy);
}
if (len == 0) { return len;
// Do nothing. mg_printf(conn, "%s", "") was called. }
} else if (len < 0) {
// vsnprintf() error, give up int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) {
len = -1; char mem[MG_BUF_LEN], *buf = mem;
cry(conn, "%s(%s, ...): vsnprintf() error", __func__, fmt); int len;
} else if (len > (int) sizeof(mem) && (buf = (char *) malloc(len + 1)) != NULL) {
// Local buffer is not large enough, allocate big buffer on heap if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
va_start(ap, fmt);
vsnprintf(buf, len + 1, fmt, ap);
va_end(ap);
len = mg_write(conn, buf, (size_t) len); len = mg_write(conn, buf, (size_t) len);
}
if (buf != mem && buf != NULL) {
free(buf); free(buf);
} else if (len > (int) sizeof(mem)) {
// Failed to allocate large enough buffer, give up
cry(conn, "%s(%s, ...): Can't allocate %d bytes, not printing anything",
__func__, fmt, len);
len = -1;
} else {
// Copy to the local buffer succeeded
len = mg_write(conn, buf, (size_t) len);
} }
return len; return len;
} }
int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
return mg_vprintf(conn, fmt, ap);
}
// URL-decode input buffer into destination buffer. // URL-decode input buffer into destination buffer.
// 0-terminate the destination buffer. Return the length of decoded data. // 0-terminate the destination buffer. Return the length of decoded data.
// form-url-encoded data differs from URI encoding in a way that it // form-url-encoded data differs from URI encoding in a way that it
...@@ -1713,7 +1704,8 @@ int mg_get_cookie(const struct mg_connection *conn, const char *cookie_name, ...@@ -1713,7 +1704,8 @@ int mg_get_cookie(const struct mg_connection *conn, const char *cookie_name,
if (dst == NULL || dst_size == 0) { if (dst == NULL || dst_size == 0) {
len = -2; len = -2;
} else if (cookie_name == NULL || (s = mg_get_header(conn, "Cookie")) == NULL) { } else if (cookie_name == NULL ||
(s = mg_get_header(conn, "Cookie")) == NULL) {
len = -1; len = -1;
dst[0] = '\0'; dst[0] = '\0';
} else { } else {
...@@ -1791,12 +1783,6 @@ static void convert_uri_to_file_name(struct mg_connection *conn, char *buf, ...@@ -1791,12 +1783,6 @@ static void convert_uri_to_file_name(struct mg_connection *conn, char *buf,
} }
} }
static int sslize(struct mg_connection *conn, SSL_CTX *s, int (*func)(SSL *)) {
return (conn->ssl = SSL_new(s)) != NULL &&
SSL_set_fd(conn->ssl, conn->client.sock) == 1 &&
func(conn->ssl) == 1;
}
// Check whether full request is buffered. Return: // Check whether full request is buffered. Return:
// -1 if request is malformed // -1 if request is malformed
// 0 if request is not yet fully buffered // 0 if request is not yet fully buffered
...@@ -1810,7 +1796,8 @@ static int get_request_len(const char *buf, int buflen) { ...@@ -1810,7 +1796,8 @@ static int get_request_len(const char *buf, int buflen) {
if (!isprint(* (const unsigned char *) s) && *s != '\r' && if (!isprint(* (const unsigned char *) s) && *s != '\r' &&
*s != '\n' && * (const unsigned char *) s < 128) { *s != '\n' && * (const unsigned char *) s < 128) {
len = -1; len = -1;
break; // [i_a] abort scan as soon as one malformed character is found; don't let subsequent \r\n\r\n win us over anyhow break; // [i_a] abort scan as soon as one malformed character is found;
// don't let subsequent \r\n\r\n win us over anyhow
} else if (s[0] == '\n' && s[1] == '\n') { } else if (s[0] == '\n' && s[1] == '\n') {
len = (int) (s - buf) + 2; len = (int) (s - buf) + 2;
} else if (s[0] == '\n' && &s[1] < e && } else if (s[0] == '\n' && &s[1] < e &&
...@@ -2614,7 +2601,7 @@ static int scan_directory(struct mg_connection *conn, const char *dir, ...@@ -2614,7 +2601,7 @@ static int scan_directory(struct mg_connection *conn, const char *dir,
// print_dir_entry(). memset is required only if mg_stat() // print_dir_entry(). memset is required only if mg_stat()
// fails. For more details, see // fails. For more details, see
// http://code.google.com/p/mongoose/issues/detail?id=79 // http://code.google.com/p/mongoose/issues/detail?id=79
// mg_stat will memset the whole struct file with zeroes. memset(&de.file, 0, sizeof(de.file));
mg_stat(conn, path, &de.file); mg_stat(conn, path, &de.file);
de.file_name = dp->d_name; de.file_name = dp->d_name;
...@@ -2821,7 +2808,7 @@ static void handle_file_request(struct mg_connection *conn, const char *path, ...@@ -2821,7 +2808,7 @@ static void handle_file_request(struct mg_connection *conn, const char *path,
} }
void mg_send_file(struct mg_connection *conn, const char *path) { void mg_send_file(struct mg_connection *conn, const char *path) {
struct file file; struct file file = STRUCT_FILE_INITIALIZER;
if (mg_stat(conn, path, &file)) { if (mg_stat(conn, path, &file)) {
handle_file_request(conn, path, &file); handle_file_request(conn, path, &file);
} else { } else {
...@@ -2855,7 +2842,7 @@ static int is_valid_http_method(const char *method) { ...@@ -2855,7 +2842,7 @@ static int is_valid_http_method(const char *method) {
// This function modifies the buffer by NUL-terminating // This function modifies the buffer by NUL-terminating
// HTTP request components, header names and header values. // HTTP request components, header names and header values.
static int parse_http_message(char *buf, int len, struct mg_request_info *ri) { static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
int request_length = get_request_len(buf, len); int is_request, request_length = get_request_len(buf, len);
if (request_length > 0) { if (request_length > 0) {
// Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port // Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL; ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL;
...@@ -2870,26 +2857,18 @@ static int parse_http_message(char *buf, int len, struct mg_request_info *ri) { ...@@ -2870,26 +2857,18 @@ static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
ri->request_method = skip(&buf, " "); ri->request_method = skip(&buf, " ");
ri->uri = skip(&buf, " "); ri->uri = skip(&buf, " ");
ri->http_version = skip(&buf, "\r\n"); ri->http_version = skip(&buf, "\r\n");
if (((is_request = is_valid_http_method(ri->request_method)) &&
memcmp(ri->http_version, "HTTP/", 5) != 0) ||
(!is_request && memcmp(ri->request_method, "HTTP/", 5)) != 0) {
request_length = -1;
} else {
if (is_request) {
ri->http_version += 5;
}
parse_http_headers(&buf, ri); parse_http_headers(&buf, ri);
} }
return request_length;
}
static int parse_http_request(char *buf, int len, struct mg_request_info *ri) {
int result = parse_http_message(buf, len, ri);
if (result > 0 &&
is_valid_http_method(ri->request_method) &&
!strncmp(ri->http_version, "HTTP/", 5)) {
ri->http_version += 5; // Skip "HTTP/"
} else {
result = -1;
} }
return result; return request_length;
}
static int parse_http_response(char *buf, int len, struct mg_request_info *ri) {
int result = parse_http_message(buf, len, ri);
return result > 0 && !strncmp(ri->request_method, "HTTP/", 5) ? result : -1;
} }
// Keep reading the input (either opened file descriptor fd, or socket sock, // Keep reading the input (either opened file descriptor fd, or socket sock,
...@@ -2899,22 +2878,17 @@ static int parse_http_response(char *buf, int len, struct mg_request_info *ri) { ...@@ -2899,22 +2878,17 @@ static int parse_http_response(char *buf, int len, struct mg_request_info *ri) {
// Upon every read operation, increase nread by the number of bytes read. // Upon every read operation, increase nread by the number of bytes read.
static int read_request(FILE *fp, struct mg_connection *conn, static int read_request(FILE *fp, struct mg_connection *conn,
char *buf, int bufsiz, int *nread) { char *buf, int bufsiz, int *nread) {
int request_len, n = 1; int request_len, n = 0;
request_len = get_request_len(buf, *nread); request_len = get_request_len(buf, *nread);
while (*nread < bufsiz && request_len == 0 && n > 0) { while (*nread < bufsiz && request_len == 0 &&
n = pull(fp, conn, buf + *nread, bufsiz - *nread); (n = pull(fp, conn, buf + *nread, bufsiz - *nread)) > 0) {
if (n > 0) {
*nread += n; *nread += n;
assert(*nread <= bufsiz);
request_len = get_request_len(buf, *nread); request_len = get_request_len(buf, *nread);
} }
}
if (n < 0) { return request_len <= 0 && n <= 0 ? -1 : request_len;
// recv() error -> propagate error; do not process a b0rked-with-very-high-probability request
return -1;
}
return request_len;
} }
// For given directory path, substitute it to valid index file. // For given directory path, substitute it to valid index file.
...@@ -3345,7 +3319,7 @@ done: ...@@ -3345,7 +3319,7 @@ done:
static int put_dir(struct mg_connection *conn, const char *path) { static int put_dir(struct mg_connection *conn, const char *path) {
char buf[PATH_MAX]; char buf[PATH_MAX];
const char *s, *p; const char *s, *p;
struct file file; struct file file = STRUCT_FILE_INITIALIZER;
int len, res = 1; int len, res = 1;
for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) { for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) {
...@@ -3374,7 +3348,7 @@ static int put_dir(struct mg_connection *conn, const char *path) { ...@@ -3374,7 +3348,7 @@ static int put_dir(struct mg_connection *conn, const char *path) {
} }
static void put_file(struct mg_connection *conn, const char *path) { static void put_file(struct mg_connection *conn, const char *path) {
struct file file; struct file file = STRUCT_FILE_INITIALIZER;
const char *range; const char *range;
int64_t r1, r2; int64_t r1, r2;
int rc; int rc;
...@@ -3411,7 +3385,7 @@ static void send_ssi_file(struct mg_connection *, const char *, ...@@ -3411,7 +3385,7 @@ static void send_ssi_file(struct mg_connection *, const char *,
static void do_ssi_include(struct mg_connection *conn, const char *ssi, static void do_ssi_include(struct mg_connection *conn, const char *ssi,
char *tag, int include_level) { char *tag, int include_level) {
char file_name[MG_BUF_LEN], path[PATH_MAX], *p; char file_name[MG_BUF_LEN], path[PATH_MAX], *p;
struct file file; struct file file = STRUCT_FILE_INITIALIZER;
// sscanf() is safe here, since send_ssi_file() also uses buffer // sscanf() is safe here, since send_ssi_file() also uses buffer
// of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN. // of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN.
...@@ -3542,7 +3516,7 @@ static void send_ssi_file(struct mg_connection *conn, const char *path, ...@@ -3542,7 +3516,7 @@ static void send_ssi_file(struct mg_connection *conn, const char *path,
static void handle_ssi_file_request(struct mg_connection *conn, static void handle_ssi_file_request(struct mg_connection *conn,
const char *path) { const char *path) {
struct file file; struct file file = STRUCT_FILE_INITIALIZER;
if (!mg_fopen(conn, path, "rb", &file)) { if (!mg_fopen(conn, path, "rb", &file)) {
send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path, send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path,
...@@ -3797,7 +3771,7 @@ static void send_websocket_handshake(struct mg_connection *conn) { ...@@ -3797,7 +3771,7 @@ static void send_websocket_handshake(struct mg_connection *conn) {
} }
static void read_websocket(struct mg_connection *conn) { static void read_websocket(struct mg_connection *conn) {
unsigned char *mask, *buf = (unsigned char *) conn->buf + conn->request_len; unsigned char *buf = (unsigned char *) conn->buf + conn->request_len;
int n, len, mask_len, body_len, discard_len; int n, len, mask_len, body_len, discard_len;
for (;;) { for (;;) {
...@@ -3806,20 +3780,18 @@ static void read_websocket(struct mg_connection *conn) { ...@@ -3806,20 +3780,18 @@ static void read_websocket(struct mg_connection *conn) {
mask_len = buf[1] & 128 ? 4 : 0; mask_len = buf[1] & 128 ? 4 : 0;
if (len < 126) { if (len < 126) {
conn->content_len = 2 + mask_len + len; conn->content_len = 2 + mask_len + len;
mask = buf + 2;
} else if (len == 126 && body_len >= 4) { } else if (len == 126 && body_len >= 4) {
conn->content_len = 4 + mask_len + ((((int) buf[2]) << 8) + buf[3]); conn->content_len = 4 + mask_len + ((((int) buf[2]) << 8) + buf[3]);
mask = buf + 4;
} else if (body_len >= 10) { } else if (body_len >= 10) {
conn->content_len = 10 + mask_len + conn->content_len = 10 + mask_len +
(((uint64_t) htonl(* (uint32_t *) &buf[2])) << 32) | (((uint64_t) htonl(* (uint32_t *) &buf[2])) << 32) +
htonl(* (uint32_t *) &buf[6]); htonl(* (uint32_t *) &buf[6]);
mask = buf + 10;
} }
} }
if (conn->content_len > 0) { if (conn->content_len > 0) {
if (call_user(conn, MG_WEBSOCKET_MESSAGE) != NULL) { if (conn->ctx->callbacks.websocket_data != NULL &&
conn->ctx->callbacks.websocket_data(conn) == 0) {
break; // Callback signalled to exit break; // Callback signalled to exit
} }
discard_len = conn->content_len > body_len ? discard_len = conn->content_len > body_len ?
...@@ -3828,9 +3800,6 @@ static void read_websocket(struct mg_connection *conn) { ...@@ -3828,9 +3800,6 @@ static void read_websocket(struct mg_connection *conn) {
conn->data_len -= discard_len; conn->data_len -= discard_len;
conn->content_len = conn->consumed_content = 0; conn->content_len = conn->consumed_content = 0;
} else { } else {
if (wait_until_socket_is_readable(conn) == 0) {
break;
}
n = pull(NULL, conn, conn->buf + conn->data_len, n = pull(NULL, conn, conn->buf + conn->data_len,
conn->buf_size - conn->data_len); conn->buf_size - conn->data_len);
if (n <= 0) { if (n <= 0) {
...@@ -3844,13 +3813,15 @@ static void read_websocket(struct mg_connection *conn) { ...@@ -3844,13 +3813,15 @@ static void read_websocket(struct mg_connection *conn) {
static void handle_websocket_request(struct mg_connection *conn) { static void handle_websocket_request(struct mg_connection *conn) {
if (strcmp(mg_get_header(conn, "Sec-WebSocket-Version"), "13") != 0) { if (strcmp(mg_get_header(conn, "Sec-WebSocket-Version"), "13") != 0) {
send_http_error(conn, 426, "Upgrade Required", "%s", "Upgrade Required"); send_http_error(conn, 426, "Upgrade Required", "%s", "Upgrade Required");
} else if (call_user(conn, MG_WEBSOCKET_CONNECT) != NULL) { } else if (conn->ctx->callbacks.websocket_connect != NULL &&
// Callback has returned non-NULL, do not proceed with handshake conn->ctx->callbacks.websocket_connect(conn) != 0) {
// Callback has returned non-zero, do not proceed with handshake
} else { } else {
send_websocket_handshake(conn); send_websocket_handshake(conn);
call_user(conn, MG_WEBSOCKET_READY); if (conn->ctx->callbacks.websocket_ready != NULL) {
conn->ctx->callbacks.websocket_ready(conn);
}
read_websocket(conn); read_websocket(conn);
call_user(conn, MG_WEBSOCKET_CLOSE);
} }
} }
...@@ -4012,6 +3983,9 @@ static void prepare_lua_environment(struct mg_connection *conn, lua_State *L) { ...@@ -4012,6 +3983,9 @@ static void prepare_lua_environment(struct mg_connection *conn, lua_State *L) {
int i; int i;
luaL_openlibs(L); luaL_openlibs(L);
#ifdef USE_LUA_SQLITE3
{ extern int luaopen_lsqlite3(lua_State *); luaopen_lsqlite3(L); }
#endif
// Register "print" function which calls mg_write() // Register "print" function which calls mg_write()
lua_pushlightuserdata(L, conn); lua_pushlightuserdata(L, conn);
...@@ -4046,20 +4020,21 @@ static void handle_lsp_request(struct mg_connection *conn, const char *path, ...@@ -4046,20 +4020,21 @@ static void handle_lsp_request(struct mg_connection *conn, const char *path,
void *p = NULL; void *p = NULL;
lua_State *L = NULL; lua_State *L = NULL;
if (!mg_fopen(conn, path, "r", filep)) { if (!mg_stat(conn, path, filep) || !mg_fopen(conn, path, "r", filep)) {
send_http_error(conn, 404, "Not Found", "%s", "File not found"); send_http_error(conn, 404, "Not Found", "%s", "File not found");
} else if (filep->membuf == NULL && } else if (filep->membuf == NULL &&
(p = mmap(NULL, filep->size, PROT_READ, MAP_PRIVATE, (p = mmap(NULL, (size_t) filep->size, PROT_READ, MAP_PRIVATE,
fileno(filep->fp), 0)) == MAP_FAILED) { fileno(filep->fp), 0)) == MAP_FAILED) {
send_http_error(conn, 500, http_500_error, "%s", "x"); send_http_error(conn, 500, http_500_error, "mmap(%s, %zu, %d): %s", path,
(size_t) filep->size, fileno(filep->fp), strerror(errno));
} else if ((L = luaL_newstate()) == NULL) { } else if ((L = luaL_newstate()) == NULL) {
send_http_error(conn, 500, http_500_error, "%s", "y"); send_http_error(conn, 500, http_500_error, "%s", "luaL_newstate failed");
} else { } else {
mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n" // We're not sending HTTP headers here, Lua page must do it.
"Content-Type: text/html\r\nConnection: close\r\n\r\n");
prepare_lua_environment(conn, L); prepare_lua_environment(conn, L);
conn->request_info.ev_data = L; if (conn->ctx->callbacks.init_lua != NULL) {
call_user(conn, MG_INIT_LUA); conn->ctx->callbacks.init_lua(conn, L);
}
lsp(conn, filep->membuf == NULL ? p : filep->membuf, filep->size, L); lsp(conn, filep->membuf == NULL ? p : filep->membuf, filep->size, L);
} }
...@@ -4071,7 +4046,7 @@ static void handle_lsp_request(struct mg_connection *conn, const char *path, ...@@ -4071,7 +4046,7 @@ static void handle_lsp_request(struct mg_connection *conn, const char *path,
int mg_upload(struct mg_connection *conn, const char *destination_dir) { int mg_upload(struct mg_connection *conn, const char *destination_dir) {
const char *content_type_header, *boundary_start; const char *content_type_header, *boundary_start;
char buf[8192], path[PATH_MAX], fname[1024], boundary[100], *s; char buf[MG_BUF_LEN], path[PATH_MAX], fname[1024], boundary[100], *s;
FILE *fp; FILE *fp;
int bl, n, i, j, headers_len, boundary_len, len = 0, num_uploaded_files = 0; int bl, n, i, j, headers_len, boundary_len, len = 0, num_uploaded_files = 0;
...@@ -4141,9 +4116,9 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) { ...@@ -4141,9 +4116,9 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) {
if ((s = strrchr(fname, '/')) == NULL) { if ((s = strrchr(fname, '/')) == NULL) {
s = fname; s = fname;
} }
// Open file in binary mode with exclusive lock set // Open file in binary mode. TODO: set an exclusive lock.
snprintf(path, sizeof(path), "%s/%s", destination_dir, s); snprintf(path, sizeof(path), "%s/%s", destination_dir, s);
if ((fp = fopen(path, "wbx")) == NULL) { if ((fp = fopen(path, "wb")) == NULL) {
break; break;
} }
...@@ -4155,10 +4130,12 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) { ...@@ -4155,10 +4130,12 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) {
if (!memcmp(&buf[i], "\r\n--", 4) && if (!memcmp(&buf[i], "\r\n--", 4) &&
!memcmp(&buf[i + 4], boundary, boundary_len)) { !memcmp(&buf[i + 4], boundary, boundary_len)) {
// Found boundary, that's the end of file data. // Found boundary, that's the end of file data.
(void) fwrite(buf, 1, i, fp); fwrite(buf, 1, i, fp);
fflush(fp);
num_uploaded_files++; num_uploaded_files++;
conn->request_info.ev_data = (void *) path; if (conn->ctx->callbacks.upload != NULL) {
call_user(conn, MG_UPLOAD); conn->ctx->callbacks.upload(conn, path);
}
memmove(buf, &buf[i + bl], len - (i + bl)); memmove(buf, &buf[i + bl], len - (i + bl));
len -= i + bl; len -= i + bl;
break; break;
...@@ -4166,7 +4143,7 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) { ...@@ -4166,7 +4143,7 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) {
} }
if (len > bl) { if (len > bl) {
fwrite(buf, 1, len - bl, fp); fwrite(buf, 1, len - bl, fp);
memmove(buf, &buf[len - bl], len - bl); memmove(buf, &buf[len - bl], bl);
len = bl; len = bl;
} }
} while ((n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0); } while ((n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0);
...@@ -4181,6 +4158,29 @@ static int is_put_or_delete_request(const struct mg_connection *conn) { ...@@ -4181,6 +4158,29 @@ static int is_put_or_delete_request(const struct mg_connection *conn) {
return s != NULL && (!strcmp(s, "PUT") || !strcmp(s, "DELETE")); return s != NULL && (!strcmp(s, "PUT") || !strcmp(s, "DELETE"));
} }
static int get_first_ssl_listener_index(const struct mg_context *ctx) {
int i, index = -1;
for (i = 0; index == -1 && i < ctx->num_listening_sockets; i++) {
index = ctx->listening_sockets[i].is_ssl ? i : -1;
}
return index;
}
static void redirect_to_https_port(struct mg_connection *conn, int ssl_index) {
char host[1025];
const char *host_header;
if ((host_header = mg_get_header(conn, "Host")) == NULL ||
sscanf(host_header, "%1024[^:]", host) == 0) {
// Cannot get host from the Host: header. Fallback to our IP address.
sockaddr_to_string(host, sizeof(host), &conn->client.lsa);
}
mg_printf(conn, "HTTP/1.1 302 Found\r\nLocation: https://%s:%d%s\r\n\r\n",
host, (int) ntohs(conn->ctx->listening_sockets[ssl_index].
lsa.sin.sin_port), conn->request_info.uri);
}
// This is the heart of the Mongoose's logic. // This is the heart of the Mongoose's logic.
// This function is called when the request is read, parsed and validated, // This function is called when the request is read, parsed and validated,
// and Mongoose must decide what action to take: serve a file, or // and Mongoose must decide what action to take: serve a file, or
...@@ -4188,7 +4188,7 @@ static int is_put_or_delete_request(const struct mg_connection *conn) { ...@@ -4188,7 +4188,7 @@ static int is_put_or_delete_request(const struct mg_connection *conn) {
static void handle_request(struct mg_connection *conn) { static void handle_request(struct mg_connection *conn) {
struct mg_request_info *ri = &conn->request_info; struct mg_request_info *ri = &conn->request_info;
char path[PATH_MAX]; char path[PATH_MAX];
int uri_len; int uri_len, ssl_index;
struct file file = STRUCT_FILE_INITIALIZER; struct file file = STRUCT_FILE_INITIALIZER;
if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) { if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {
...@@ -4202,14 +4202,21 @@ static void handle_request(struct mg_connection *conn) { ...@@ -4202,14 +4202,21 @@ static void handle_request(struct mg_connection *conn) {
get_remote_ip(conn), ri->uri); get_remote_ip(conn), ri->uri);
DEBUG_TRACE(("%s", ri->uri)); DEBUG_TRACE(("%s", ri->uri));
if (!is_put_or_delete_request(conn) && !check_authorization(conn, path)) { // Perform redirect and auth checks before calling begin_request() handler.
// Otherwise, begin_request() would need to perform auth checks and redirects.
if (!conn->client.is_ssl && conn->client.ssl_redir &&
(ssl_index = get_first_ssl_listener_index(conn->ctx)) > -1) {
redirect_to_https_port(conn, ssl_index);
} else if (!is_put_or_delete_request(conn) &&
!check_authorization(conn, path)) {
send_authorization_request(conn); send_authorization_request(conn);
} else if (conn->ctx->callbacks.begin_request != NULL &&
conn->ctx->callbacks.begin_request(conn)) {
// Do nothing, callback has served the request
#if defined(USE_WEBSOCKET) #if defined(USE_WEBSOCKET)
} else if (is_websocket_request(conn)) { } else if (is_websocket_request(conn)) {
handle_websocket_request(conn); handle_websocket_request(conn);
#endif #endif
} else if (call_user(conn, MG_NEW_REQUEST) != NULL) {
// Do nothing, callback has served the request
} else if (!strcmp(ri->request_method, "OPTIONS")) { } else if (!strcmp(ri->request_method, "OPTIONS")) {
send_options(conn); send_options(conn);
} else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) { } else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) {
...@@ -4272,12 +4279,11 @@ static void handle_request(struct mg_connection *conn) { ...@@ -4272,12 +4279,11 @@ static void handle_request(struct mg_connection *conn) {
} }
static void close_all_listening_sockets(struct mg_context *ctx) { static void close_all_listening_sockets(struct mg_context *ctx) {
struct socket *sp, *tmp; int i;
for (sp = ctx->listening_sockets; sp != NULL; sp = tmp) { for (i = 0; i < ctx->num_listening_sockets; i++) {
tmp = sp->next; closesocket(ctx->listening_sockets[i].sock);
(void) closesocket(sp->sock);
free(sp);
} }
free(ctx->listening_sockets);
} }
// Valid listening port specification is: [ip_address:]port[s] // Valid listening port specification is: [ip_address:]port[s]
...@@ -4297,11 +4303,13 @@ static int parse_port_string(const struct vec *vec, struct socket *so) { ...@@ -4297,11 +4303,13 @@ static int parse_port_string(const struct vec *vec, struct socket *so) {
} else if (sscanf(vec->ptr, "%d%n", &port, &len) != 1 || } else if (sscanf(vec->ptr, "%d%n", &port, &len) != 1 ||
len <= 0 || len <= 0 ||
len > (int) vec->len || len > (int) vec->len ||
(vec->ptr[len] && vec->ptr[len] != 's' && vec->ptr[len] != ',')) { (vec->ptr[len] && vec->ptr[len] != 's' &&
vec->ptr[len] != 'r' && vec->ptr[len] != ',')) {
return 0; return 0;
} }
so->is_ssl = vec->ptr[len] == 's'; so->is_ssl = vec->ptr[len] == 's';
so->ssl_redir = vec->ptr[len] == 'r';
#if defined(USE_IPV6) #if defined(USE_IPV6)
so->lsa.sin6.sin6_family = AF_INET6; so->lsa.sin6.sin6_family = AF_INET6;
so->lsa.sin6.sin6_port = htons((uint16_t) port); so->lsa.sin6.sin6_port = htons((uint16_t) port);
...@@ -4316,53 +4324,37 @@ static int parse_port_string(const struct vec *vec, struct socket *so) { ...@@ -4316,53 +4324,37 @@ static int parse_port_string(const struct vec *vec, struct socket *so) {
static int set_ports_option(struct mg_context *ctx) { static int set_ports_option(struct mg_context *ctx) {
const char *list = ctx->config[LISTENING_PORTS]; const char *list = ctx->config[LISTENING_PORTS];
int on = 1, success = 1; int on = 1, success = 1;
SOCKET sock;
struct vec vec; struct vec vec;
struct socket so, *listener; struct socket so;
while (success && (list = next_option(list, &vec, NULL)) != NULL) { while (success && (list = next_option(list, &vec, NULL)) != NULL) {
if (!parse_port_string(&vec, &so)) { if (!parse_port_string(&vec, &so)) {
cry(fc(ctx), "%s: %.*s: invalid port spec. Expecting list of: %s", cry(fc(ctx), "%s: %.*s: invalid port spec. Expecting list of: %s",
__func__, (int) vec.len, vec.ptr, "[IP_ADDRESS:]PORT[s|p]"); __func__, (int) vec.len, vec.ptr, "[IP_ADDRESS:]PORT[s|p]");
success = 0; success = 0;
} else if (so.is_ssl && } else if (so.is_ssl && ctx->ssl_ctx == NULL) {
(ctx->ssl_ctx == NULL || ctx->config[SSL_CERTIFICATE] == NULL)) {
cry(fc(ctx), "Cannot add SSL socket, is -ssl_certificate option set?"); cry(fc(ctx), "Cannot add SSL socket, is -ssl_certificate option set?");
success = 0; success = 0;
} else if ((sock = socket(so.lsa.sa.sa_family, SOCK_STREAM, 6)) == } else if ((so.sock = socket(so.lsa.sa.sa_family, SOCK_STREAM, 6)) ==
INVALID_SOCKET || INVALID_SOCKET ||
// On Windows, SO_REUSEADDR is recommended only for // On Windows, SO_REUSEADDR is recommended only for
// broadcast UDP sockets // broadcast UDP sockets
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *) &on, setsockopt(so.sock, SOL_SOCKET, SO_REUSEADDR,
sizeof(on)) != 0 || (void *) &on, sizeof(on)) != 0 ||
// Set TCP keep-alive. This is needed because if HTTP-level bind(so.sock, &so.lsa.sa, sizeof(so.lsa)) != 0 ||
// keep-alive is enabled, and client resets the connection, listen(so.sock, SOMAXCONN) != 0) {
// server won't get TCP FIN or RST and will keep the connection
// open forever. With TCP keep-alive, next keep-alive
// handshake will figure out that the client is down and
// will close the server end.
// Thanks to Igor Klopov who suggested the patch.
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *) &on,
sizeof(on)) != 0 ||
bind(sock, &so.lsa.sa, sizeof(so.lsa)) != 0 ||
listen(sock, SOMAXCONN) != 0) {
closesocket(sock);
cry(fc(ctx), "%s: cannot bind to %.*s: %s", __func__, cry(fc(ctx), "%s: cannot bind to %.*s: %s", __func__,
(int) vec.len, vec.ptr, strerror(ERRNO)); (int) vec.len, vec.ptr, strerror(ERRNO));
success = 0; closesocket(so.sock);
} else if ((listener = (struct socket *)
calloc(1, sizeof(*listener))) == NULL) {
// NOTE(lsm): order is important: call cry before closesocket(),
// cause closesocket() alters the errno.
cry(fc(ctx), "%s: %s", __func__, strerror(ERRNO));
closesocket(sock);
success = 0; success = 0;
} else { } else {
*listener = so; set_close_on_exec(so.sock);
listener->sock = sock; // TODO: handle realloc failure
set_close_on_exec(listener->sock); ctx->listening_sockets = realloc(ctx->listening_sockets,
listener->next = ctx->listening_sockets; (ctx->num_listening_sockets + 1) *
ctx->listening_sockets = listener; sizeof(ctx->listening_sockets[0]));
ctx->listening_sockets[ctx->num_listening_sockets] = so;
ctx->num_listening_sockets++;
} }
} }
...@@ -4443,13 +4435,6 @@ static int check_acl(struct mg_context *ctx, uint32_t remote_ip) { ...@@ -4443,13 +4435,6 @@ static int check_acl(struct mg_context *ctx, uint32_t remote_ip) {
return allowed == '+'; return allowed == '+';
} }
static void add_to_set(SOCKET fd, fd_set *set, int *max_fd) {
FD_SET(fd, set);
if (fd > (SOCKET) *max_fd) {
*max_fd = (int) fd;
}
}
#if !defined(_WIN32) #if !defined(_WIN32)
static int set_uid_option(struct mg_context *ctx) { static int set_uid_option(struct mg_context *ctx) {
struct passwd *pw; struct passwd *pw;
...@@ -4477,6 +4462,12 @@ static int set_uid_option(struct mg_context *ctx) { ...@@ -4477,6 +4462,12 @@ static int set_uid_option(struct mg_context *ctx) {
#if !defined(NO_SSL) #if !defined(NO_SSL)
static pthread_mutex_t *ssl_mutexes; static pthread_mutex_t *ssl_mutexes;
static int sslize(struct mg_connection *conn, SSL_CTX *s, int (*func)(SSL *)) {
return (conn->ssl = SSL_new(s)) != NULL &&
SSL_set_fd(conn->ssl, conn->client.sock) == 1 &&
func(conn->ssl) == 1;
}
// Return OpenSSL error message // Return OpenSSL error message
static const char *ssl_error(void) { static const char *ssl_error(void) {
unsigned long err; unsigned long err;
...@@ -4486,10 +4477,10 @@ static const char *ssl_error(void) { ...@@ -4486,10 +4477,10 @@ static const char *ssl_error(void) {
static void ssl_locking_callback(int mode, int mutex_num, const char *file, static void ssl_locking_callback(int mode, int mutex_num, const char *file,
int line) { int line) {
line = 0; // Unused (void) line;
file = NULL; // Unused (void) file;
if (mode & CRYPTO_LOCK) { if (mode & 1) { // 1 is CRYPTO_LOCK
(void) pthread_mutex_lock(&ssl_mutexes[mutex_num]); (void) pthread_mutex_lock(&ssl_mutexes[mutex_num]);
} else { } else {
(void) pthread_mutex_unlock(&ssl_mutexes[mutex_num]); (void) pthread_mutex_unlock(&ssl_mutexes[mutex_num]);
...@@ -4535,7 +4526,6 @@ static int load_dll(struct mg_context *ctx, const char *dll_name, ...@@ -4535,7 +4526,6 @@ static int load_dll(struct mg_context *ctx, const char *dll_name,
// Dynamically load SSL library. Set up ctx->ssl_ctx pointer. // Dynamically load SSL library. Set up ctx->ssl_ctx pointer.
static int set_ssl_option(struct mg_context *ctx) { static int set_ssl_option(struct mg_context *ctx) {
struct mg_connection *conn;
int i, size; int i, size;
const char *pem; const char *pem;
...@@ -4551,14 +4541,10 @@ static int set_ssl_option(struct mg_context *ctx) { ...@@ -4551,14 +4541,10 @@ static int set_ssl_option(struct mg_context *ctx) {
} }
#endif // NO_SSL_DL #endif // NO_SSL_DL
// Initialize SSL crap // Initialize SSL library
SSL_library_init(); SSL_library_init();
SSL_load_error_strings(); SSL_load_error_strings();
if ((ctx->client_ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
cry(fc(ctx), "SSL_CTX_new (client) error: %s", ssl_error());
}
if ((ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) { if ((ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) {
cry(fc(ctx), "SSL_CTX_new (server) error: %s", ssl_error()); cry(fc(ctx), "SSL_CTX_new (server) error: %s", ssl_error());
return 0; return 0;
...@@ -4566,11 +4552,10 @@ static int set_ssl_option(struct mg_context *ctx) { ...@@ -4566,11 +4552,10 @@ static int set_ssl_option(struct mg_context *ctx) {
// If user callback returned non-NULL, that means that user callback has // If user callback returned non-NULL, that means that user callback has
// set up certificate itself. In this case, skip sertificate setting. // set up certificate itself. In this case, skip sertificate setting.
conn = fc(ctx); if ((ctx->callbacks.init_ssl == NULL ||
conn->request_info.ev_data = ctx->ssl_ctx; !ctx->callbacks.init_ssl(ctx->ssl_ctx)) &&
if (call_user(conn, MG_INIT_SSL) == NULL && (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0 ||
(SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, SSL_FILETYPE_PEM) == 0 || SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0)) {
SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, SSL_FILETYPE_PEM) == 0)) {
cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error()); cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error());
return 0; return 0;
} }
...@@ -4611,7 +4596,7 @@ static void uninitialize_ssl(struct mg_context *ctx) { ...@@ -4611,7 +4596,7 @@ static void uninitialize_ssl(struct mg_context *ctx) {
#endif // !NO_SSL #endif // !NO_SSL
static int set_gpass_option(struct mg_context *ctx) { static int set_gpass_option(struct mg_context *ctx) {
struct file file; struct file file = STRUCT_FILE_INITIALIZER;
const char *path = ctx->config[GLOBAL_PASSWORDS_FILE]; const char *path = ctx->config[GLOBAL_PASSWORDS_FILE];
if (path != NULL && !mg_stat(fc(ctx), path, &file)) { if (path != NULL && !mg_stat(fc(ctx), path, &file)) {
cry(fc(ctx), "Cannot open %s: %s", path, strerror(ERRNO)); cry(fc(ctx), "Cannot open %s: %s", path, strerror(ERRNO));
...@@ -4625,7 +4610,7 @@ static int set_acl_option(struct mg_context *ctx) { ...@@ -4625,7 +4610,7 @@ static int set_acl_option(struct mg_context *ctx) {
} }
static void reset_per_request_attributes(struct mg_connection *conn) { static void reset_per_request_attributes(struct mg_connection *conn) {
conn->path_info = conn->request_info.ev_data = NULL; conn->path_info = NULL;
conn->num_bytes_sent = conn->consumed_content = 0; conn->num_bytes_sent = conn->consumed_content = 0;
conn->status_code = -1; conn->status_code = -1;
conn->must_close = conn->request_len = conn->throttle = 0; conn->must_close = conn->request_len = conn->throttle = 0;
...@@ -4637,17 +4622,17 @@ static void close_socket_gracefully(struct mg_connection *conn) { ...@@ -4637,17 +4622,17 @@ static void close_socket_gracefully(struct mg_connection *conn) {
int n; int n;
#endif #endif
struct linger linger; struct linger linger;
int sock = conn->client.sock;
// Set linger option to avoid socket hanging out after close. This prevent // Set linger option to avoid socket hanging out after close. This prevent
// ephemeral port exhaust problem under high QPS. // ephemeral port exhaust problem under high QPS.
linger.l_onoff = 1; linger.l_onoff = 1;
linger.l_linger = 1; linger.l_linger = 1;
setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof(linger)); setsockopt(conn->client.sock, SOL_SOCKET, SO_LINGER,
(char *) &linger, sizeof(linger));
// Send FIN to the client // Send FIN to the client
(void) shutdown(sock, SHUT_WR); shutdown(conn->client.sock, SHUT_WR);
set_non_blocking_mode(sock); set_non_blocking_mode(conn->client.sock);
#if defined(_WIN32) #if defined(_WIN32)
// Read and discard pending incoming data. If we do not do that and close the // Read and discard pending incoming data. If we do not do that and close the
...@@ -4661,130 +4646,152 @@ static void close_socket_gracefully(struct mg_connection *conn) { ...@@ -4661,130 +4646,152 @@ static void close_socket_gracefully(struct mg_connection *conn) {
#endif #endif
// Now we know that our FIN is ACK-ed, safe to close // Now we know that our FIN is ACK-ed, safe to close
(void) closesocket(sock); closesocket(conn->client.sock);
} }
static void close_connection(struct mg_connection *conn) { static void close_connection(struct mg_connection *conn) {
conn->must_close = 1; conn->must_close = 1;
if (conn->ssl) {
SSL_free(conn->ssl);
conn->ssl = NULL;
}
if (conn->client.sock != INVALID_SOCKET) { if (conn->client.sock != INVALID_SOCKET) {
close_socket_gracefully(conn); close_socket_gracefully(conn);
} }
#ifndef NO_SSL
// Must be done AFTER socket is closed
if (conn->ssl != NULL) {
SSL_free(conn->ssl);
}
#endif
} }
void mg_close_connection(struct mg_connection *conn) { void mg_close_connection(struct mg_connection *conn) {
#ifndef NO_SSL
if (conn->client_ssl_ctx != NULL) {
SSL_CTX_free((SSL_CTX *) conn->client_ssl_ctx);
}
#endif
close_connection(conn); close_connection(conn);
free(conn); free(conn);
} }
struct mg_connection *mg_connect(struct mg_context *ctx, struct mg_connection *mg_connect(const char *host, int port, int use_ssl,
const char *host, int port, int use_ssl) { char *ebuf, size_t ebuf_len) {
struct mg_connection *newconn = NULL; static struct mg_context fake_ctx;
struct mg_connection *conn = NULL;
struct sockaddr_in sin; struct sockaddr_in sin;
struct hostent *he; struct hostent *he;
int sock; int sock;
if (use_ssl && (ctx == NULL || ctx->client_ssl_ctx == NULL)) { if (host == NULL) {
cry(fc(ctx), "%s: SSL is not initialized", __func__); snprintf(ebuf, ebuf_len, "%s", "NULL host");
} else if (use_ssl && SSLv23_client_method == NULL) {
snprintf(ebuf, ebuf_len, "%s", "SSL is not initialized");
} else if ((he = gethostbyname(host)) == NULL) { } else if ((he = gethostbyname(host)) == NULL) {
cry(fc(ctx), "%s: gethostbyname(%s): %s", __func__, host, strerror(ERRNO)); snprintf(ebuf, ebuf_len, "gethostbyname(%s): %s", host, strerror(ERRNO));
} else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { } else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
cry(fc(ctx), "%s: socket: %s", __func__, strerror(ERRNO)); snprintf(ebuf, ebuf_len, "socket(): %s", strerror(ERRNO));
} else { } else {
sin.sin_family = AF_INET; sin.sin_family = AF_INET;
sin.sin_port = htons((uint16_t) port); sin.sin_port = htons((uint16_t) port);
sin.sin_addr = * (struct in_addr *) he->h_addr_list[0]; sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) { if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
cry(fc(ctx), "%s: connect(%s:%d): %s", __func__, host, port, snprintf(ebuf, ebuf_len, "connect(%s:%d): %s",
strerror(ERRNO)); host, port, strerror(ERRNO));
closesocket(sock); closesocket(sock);
} else if ((newconn = (struct mg_connection *) } else if ((conn = (struct mg_connection *)
calloc(1, sizeof(*newconn))) == NULL) { calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE)) == NULL) {
cry(fc(ctx), "%s: calloc: %s", __func__, strerror(ERRNO)); snprintf(ebuf, ebuf_len, "calloc(): %s", strerror(ERRNO));
closesocket(sock); closesocket(sock);
#ifndef NO_SSL
} else if (use_ssl && (conn->client_ssl_ctx =
SSL_CTX_new(SSLv23_client_method())) == NULL) {
snprintf(ebuf, ebuf_len, "SSL_CTX_new error");
closesocket(sock);
free(conn);
conn = NULL;
#endif // NO_SSL
} else { } else {
newconn->ctx = ctx; conn->buf_size = MAX_REQUEST_SIZE;
newconn->client.sock = sock; conn->buf = (char *) (conn + 1);
newconn->client.rsa.sin = sin; conn->ctx = &fake_ctx;
newconn->client.is_ssl = use_ssl; conn->client.sock = sock;
conn->client.rsa.sin = sin;
conn->client.is_ssl = use_ssl;
#ifndef NO_SSL
if (use_ssl) { if (use_ssl) {
sslize(newconn, ctx->client_ssl_ctx, SSL_connect); // SSL_CTX_set_verify call is needed to switch off server certificate
// checking, which is off by default in OpenSSL and on in yaSSL.
SSL_CTX_set_verify(conn->client_ssl_ctx, 0, 0);
sslize(conn, conn->client_ssl_ctx, SSL_connect);
} }
#endif
} }
} }
return newconn; return conn;
} }
FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path, static int is_valid_uri(const char *uri) {
char *buf, size_t buf_len, struct mg_request_info *ri) { // Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
struct mg_connection *newconn; // URI can be an asterisk (*) or should start with slash.
int n, req_length, data_length, port; return uri[0] == '/' || (uri[0] == '*' && uri[1] == '\0');
char host[1025], proto[10], buf2[MG_BUF_LEN]; }
FILE *fp = NULL;
if (sscanf(url, "%9[htps]://%1024[^:]:%d/%n", proto, host, &port, &n) == 3) { static int getreq(struct mg_connection *conn, char *ebuf, size_t ebuf_len) {
} else if (sscanf(url, "%9[htps]://%1024[^/]/%n", proto, host, &n) == 2) { const char *cl;
port = mg_strcasecmp(proto, "https") == 0 ? 443 : 80;
} else {
cry(fc(ctx), "%s: invalid URL: [%s]", __func__, url);
return NULL;
}
if ((newconn = mg_connect(ctx, host, port, ebuf[0] = '\0';
!strcmp(proto, "https"))) == NULL) { reset_per_request_attributes(conn);
cry(fc(ctx), "%s: mg_connect(%s): %s", __func__, url, strerror(ERRNO)); conn->request_len = read_request(NULL, conn, conn->buf, conn->buf_size,
&conn->data_len);
assert(conn->request_len < 0 || conn->data_len >= conn->request_len);
if (conn->request_len == 0 && conn->data_len == conn->buf_size) {
snprintf(ebuf, ebuf_len, "%s", "Request Too Large");
} if (conn->request_len <= 0) {
snprintf(ebuf, ebuf_len, "%s", "Client closed connection");
} else if (parse_http_message(conn->buf, conn->buf_size,
&conn->request_info) <= 0) {
snprintf(ebuf, ebuf_len, "Bad request: [%.*s]", conn->data_len, conn->buf);
} else { } else {
mg_printf(newconn, "GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n", url + n, host); // Request is valid
data_length = 0; if ((cl = get_header(&conn->request_info, "Content-Length")) != NULL) {
req_length = read_request(NULL, newconn, buf, buf_len, &data_length); conn->content_len = strtoll(cl, NULL, 10);
if (req_length <= 0) { } else if (!mg_strcasecmp(conn->request_info.request_method, "POST") ||
cry(fc(ctx), "%s(%s): invalid HTTP reply", __func__, url); !mg_strcasecmp(conn->request_info.request_method, "PUT")) {
} else if (parse_http_response(buf, req_length, ri) <= 0) { conn->content_len = -1;
cry(fc(ctx), "%s(%s): cannot parse HTTP headers", __func__, url);
} else if ((fp = fopen(path, "w+b")) == NULL) {
cry(fc(ctx), "%s: fopen(%s): %s", __func__, path, strerror(ERRNO));
} else { } else {
// Write chunk of data that may be in the user's buffer conn->content_len = 0;
data_length -= req_length;
if (data_length > 0 &&
fwrite(buf + req_length, 1, data_length, fp) != (size_t) data_length) {
cry(fc(ctx), "%s: fwrite(%s): %s", __func__, path, strerror(ERRNO));
fclose(fp);
fp = NULL;
}
// Read the rest of the response and write it to the file. Do not use
// mg_read() cause we didn't set newconn->content_len properly.
while (fp && (data_length = pull(0, newconn, buf2, sizeof(buf2))) > 0) {
if (fwrite(buf2, 1, data_length, fp) != (size_t) data_length) {
cry(fc(ctx), "%s: fwrite(%s): %s", __func__, path, strerror(ERRNO));
fclose(fp);
fp = NULL;
break;
} }
conn->birth_time = time(NULL);
} }
return ebuf[0] == '\0';
}
struct mg_connection *mg_download(const char *host, int port, int use_ssl,
char *ebuf, size_t ebuf_len,
const char *fmt, ...) {
struct mg_connection *conn;
va_list ap;
va_start(ap, fmt);
ebuf[0] = '\0';
if ((conn = mg_connect(host, port, use_ssl, ebuf, ebuf_len)) == NULL) {
} else if (mg_vprintf(conn, fmt, ap) <= 0) {
snprintf(ebuf, ebuf_len, "%s", "Error sending request");
} else {
getreq(conn, ebuf, ebuf_len);
} }
mg_close_connection(newconn); if (ebuf[0] != '\0' && conn != NULL) {
mg_close_connection(conn);
conn = NULL;
} }
return fp; return conn;
}
static int is_valid_uri(const char *uri) {
// Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
// URI can be an asterisk (*) or should start with slash.
return uri[0] == '/' || (uri[0] == '*' && uri[1] == '\0');
} }
static void process_new_connection(struct mg_connection *conn) { static void process_new_connection(struct mg_connection *conn) {
struct mg_request_info *ri = &conn->request_info; struct mg_request_info *ri = &conn->request_info;
int keep_alive_enabled, keep_alive, discard_len; int keep_alive_enabled, keep_alive, discard_len;
const char *cl; char ebuf[100];
keep_alive_enabled = !strcmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes"); keep_alive_enabled = !strcmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes");
keep_alive = 0; keep_alive = 0;
...@@ -4793,41 +4800,22 @@ static void process_new_connection(struct mg_connection *conn) { ...@@ -4793,41 +4800,22 @@ static void process_new_connection(struct mg_connection *conn) {
// to crule42. // to crule42.
conn->data_len = 0; conn->data_len = 0;
do { do {
reset_per_request_attributes(conn); if (!getreq(conn, ebuf, sizeof(ebuf))) {
conn->request_len = read_request(NULL, conn, conn->buf, conn->buf_size, send_http_error(conn, 500, "Server Error", "%s", ebuf);
&conn->data_len); } else if (!is_valid_uri(conn->request_info.uri)) {
assert(conn->request_len < 0 || conn->data_len >= conn->request_len); snprintf(ebuf, sizeof(ebuf), "Invalid URI: [%s]", ri->uri);
if (conn->request_len == 0 && conn->data_len == conn->buf_size) { send_http_error(conn, 400, "Bad Request", "%s", ebuf);
send_http_error(conn, 413, "Request Too Large", "%s", "");
return;
} if (conn->request_len <= 0) {
return; // Remote end closed the connection
}
if (parse_http_request(conn->buf, conn->buf_size, ri) <= 0 ||
!is_valid_uri(ri->uri)) {
// Do not put garbage in the access log, just send it back to the client
send_http_error(conn, 400, "Bad Request",
"Cannot parse HTTP request: [%.*s]", conn->data_len, conn->buf);
conn->must_close = 1;
} else if (strcmp(ri->http_version, "1.0") && } else if (strcmp(ri->http_version, "1.0") &&
strcmp(ri->http_version, "1.1")) { strcmp(ri->http_version, "1.1")) {
// Request seems valid, but HTTP version is strange snprintf(ebuf, sizeof(ebuf), "Bad HTTP version: [%s]", ri->http_version);
send_http_error(conn, 505, "HTTP version not supported", "%s", ""); send_http_error(conn, 505, "Bad HTTP version", "%s", ebuf);
log_access(conn);
} else {
// Request is valid, handle it
if ((cl = get_header(ri, "Content-Length")) != NULL) {
conn->content_len = strtoll(cl, NULL, 10);
} else if (!mg_strcasecmp(ri->request_method, "POST") ||
!mg_strcasecmp(ri->request_method, "PUT")) {
conn->content_len = -1;
} else {
conn->content_len = 0;
} }
conn->birth_time = time(NULL);
if (ebuf[0] == '\0') {
handle_request(conn); handle_request(conn);
conn->request_info.ev_data = (void *) conn->status_code; if (conn->ctx->callbacks.end_request != NULL) {
call_user(conn, MG_REQUEST_COMPLETE); conn->ctx->callbacks.end_request(conn, conn->status_code);
}
log_access(conn); log_access(conn);
} }
if (ri->remote_user != NULL) { if (ri->remote_user != NULL) {
...@@ -4838,21 +4826,19 @@ static void process_new_connection(struct mg_connection *conn) { ...@@ -4838,21 +4826,19 @@ static void process_new_connection(struct mg_connection *conn) {
// is using parsed request, which will be invalid after memmove's below. // is using parsed request, which will be invalid after memmove's below.
// Therefore, memorize should_keep_alive() result now for later use // Therefore, memorize should_keep_alive() result now for later use
// in loop exit condition. // in loop exit condition.
keep_alive = should_keep_alive(conn); keep_alive = conn->ctx->stop_flag == 0 && keep_alive_enabled &&
conn->content_len >= 0 && should_keep_alive(conn);
// Discard all buffered data for this request // Discard all buffered data for this request
discard_len = conn->content_len >= 0 && discard_len = conn->content_len >= 0 && conn->request_len > 0 &&
conn->request_len + conn->content_len < (int64_t) conn->data_len ? conn->request_len + conn->content_len < (int64_t) conn->data_len ?
(int) (conn->request_len + conn->content_len) : conn->data_len; (int) (conn->request_len + conn->content_len) : conn->data_len;
assert(discard_len >= 0);
memmove(conn->buf, conn->buf + discard_len, conn->data_len - discard_len); memmove(conn->buf, conn->buf + discard_len, conn->data_len - discard_len);
conn->data_len -= discard_len; conn->data_len -= discard_len;
assert(conn->data_len >= 0); assert(conn->data_len >= 0);
assert(conn->data_len <= conn->buf_size); assert(conn->data_len <= conn->buf_size);
} while (keep_alive);
} while (conn->ctx->stop_flag == 0 &&
keep_alive_enabled &&
conn->content_len >= 0 &&
keep_alive);
} }
// Worker threads take accepted socket from the queue // Worker threads take accepted socket from the queue
...@@ -4885,7 +4871,8 @@ static int consume_socket(struct mg_context *ctx, struct socket *sp) { ...@@ -4885,7 +4871,8 @@ static int consume_socket(struct mg_context *ctx, struct socket *sp) {
return !ctx->stop_flag; return !ctx->stop_flag;
} }
static void worker_thread(struct mg_context *ctx) { static void *worker_thread(void *thread_func_param) {
struct mg_context *ctx = thread_func_param;
struct mg_connection *conn; struct mg_connection *conn;
conn = (struct mg_connection *) calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE); conn = (struct mg_connection *) calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE);
...@@ -4894,12 +4881,13 @@ static void worker_thread(struct mg_context *ctx) { ...@@ -4894,12 +4881,13 @@ static void worker_thread(struct mg_context *ctx) {
} else { } else {
conn->buf_size = MAX_REQUEST_SIZE; conn->buf_size = MAX_REQUEST_SIZE;
conn->buf = (char *) (conn + 1); conn->buf = (char *) (conn + 1);
conn->ctx = ctx;
conn->request_info.user_data = ctx->user_data;
// Call consume_socket() even when ctx->stop_flag > 0, to let it signal // Call consume_socket() even when ctx->stop_flag > 0, to let it signal
// sq_empty condvar to wake up the master waiting in produce_socket() // sq_empty condvar to wake up the master waiting in produce_socket()
while (consume_socket(ctx, &conn->client)) { while (consume_socket(ctx, &conn->client)) {
conn->birth_time = time(NULL); conn->birth_time = time(NULL);
conn->ctx = ctx;
// Fill in IP, port info early so even if SSL setup below fails, // Fill in IP, port info early so even if SSL setup below fails,
// error handler would have the corresponding info. // error handler would have the corresponding info.
...@@ -4911,9 +4899,11 @@ static void worker_thread(struct mg_context *ctx) { ...@@ -4911,9 +4899,11 @@ static void worker_thread(struct mg_context *ctx) {
conn->request_info.remote_ip = ntohl(conn->request_info.remote_ip); conn->request_info.remote_ip = ntohl(conn->request_info.remote_ip);
conn->request_info.is_ssl = conn->client.is_ssl; conn->request_info.is_ssl = conn->client.is_ssl;
if (!conn->client.is_ssl || if (!conn->client.is_ssl
(conn->client.is_ssl && #ifndef NO_SSL
sslize(conn, conn->ctx->ssl_ctx, SSL_accept))) { || sslize(conn, conn->ctx->ssl_ctx, SSL_accept)
#endif
) {
process_new_connection(conn); process_new_connection(conn);
} }
...@@ -4930,6 +4920,7 @@ static void worker_thread(struct mg_context *ctx) { ...@@ -4930,6 +4920,7 @@ static void worker_thread(struct mg_context *ctx) {
(void) pthread_mutex_unlock(&ctx->mutex); (void) pthread_mutex_unlock(&ctx->mutex);
DEBUG_TRACE(("exiting")); DEBUG_TRACE(("exiting"));
return NULL;
} }
// Master thread adds accepted socket to a queue // Master thread adds accepted socket to a queue
...@@ -4953,36 +4944,52 @@ static void produce_socket(struct mg_context *ctx, const struct socket *sp) { ...@@ -4953,36 +4944,52 @@ static void produce_socket(struct mg_context *ctx, const struct socket *sp) {
(void) pthread_mutex_unlock(&ctx->mutex); (void) pthread_mutex_unlock(&ctx->mutex);
} }
static int set_sock_timeout(SOCKET sock, int milliseconds) {
#ifdef _WIN32
DWORD t = milliseconds;
#else
struct timeval t;
t.tv_sec = milliseconds / 1000;
t.tv_usec = (milliseconds * 1000) % 1000000;
#endif
return setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (void *) &t, sizeof(t)) ||
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (void *) &t, sizeof(t));
}
static void accept_new_connection(const struct socket *listener, static void accept_new_connection(const struct socket *listener,
struct mg_context *ctx) { struct mg_context *ctx) {
struct socket accepted; struct socket so;
char src_addr[20]; char src_addr[20];
socklen_t len; socklen_t len = sizeof(so.rsa);
int allowed; int on = 1;
len = sizeof(accepted.rsa); if ((so.sock = accept(listener->sock, &so.rsa.sa, &len)) == INVALID_SOCKET) {
accepted.lsa = listener->lsa; } else if (!check_acl(ctx, ntohl(* (uint32_t *) &so.rsa.sin.sin_addr))) {
accepted.sock = accept(listener->sock, &accepted.rsa.sa, &len); sockaddr_to_string(src_addr, sizeof(src_addr), &so.rsa);
if (accepted.sock != INVALID_SOCKET) {
allowed = check_acl(ctx, ntohl(* (uint32_t *) &accepted.rsa.sin.sin_addr));
if (allowed) {
// Put accepted socket structure into the queue
DEBUG_TRACE(("accepted socket %d", accepted.sock));
accepted.is_ssl = listener->is_ssl;
produce_socket(ctx, &accepted);
} else {
sockaddr_to_string(src_addr, sizeof(src_addr), &accepted.rsa);
cry(fc(ctx), "%s: %s is not allowed to connect", __func__, src_addr); cry(fc(ctx), "%s: %s is not allowed to connect", __func__, src_addr);
(void) closesocket(accepted.sock); closesocket(so.sock);
} } else {
// Put so socket structure into the queue
DEBUG_TRACE(("Accepted socket %d", (int) so.sock));
so.is_ssl = listener->is_ssl;
so.ssl_redir = listener->ssl_redir;
getsockname(so.sock, &so.lsa.sa, &len);
// Set TCP keep-alive. This is needed because if HTTP-level keep-alive
// is enabled, and client resets the connection, server won't get
// TCP FIN or RST and will keep the connection open forever. With TCP
// keep-alive, next keep-alive handshake will figure out that the client
// is down and will close the server end.
// Thanks to Igor Klopov who suggested the patch.
setsockopt(so.sock, SOL_SOCKET, SO_KEEPALIVE, (void *) &on, sizeof(on));
set_sock_timeout(so.sock, atoi(ctx->config[REQUEST_TIMEOUT]));
produce_socket(ctx, &so);
} }
} }
static void master_thread(struct mg_context *ctx) { static void *master_thread(void *thread_func_param) {
fd_set read_set; struct mg_context *ctx = thread_func_param;
struct timeval tv; struct pollfd *pfd;
struct socket *sp; int i;
int max_fd;
// Increase priority of the master thread // Increase priority of the master thread
#if defined(_WIN32) #if defined(_WIN32)
...@@ -4995,33 +5002,22 @@ static void master_thread(struct mg_context *ctx) { ...@@ -4995,33 +5002,22 @@ static void master_thread(struct mg_context *ctx) {
pthread_setschedparam(pthread_self(), SCHED_RR, &sched_param); pthread_setschedparam(pthread_self(), SCHED_RR, &sched_param);
#endif #endif
pfd = calloc(ctx->num_listening_sockets, sizeof(pfd[0]));
while (ctx->stop_flag == 0) { while (ctx->stop_flag == 0) {
FD_ZERO(&read_set); for (i = 0; i < ctx->num_listening_sockets; i++) {
max_fd = -1; pfd[i].fd = ctx->listening_sockets[i].sock;
pfd[i].events = POLLIN;
// Add listening sockets to the read set
for (sp = ctx->listening_sockets; sp != NULL; sp = sp->next) {
add_to_set(sp->sock, &read_set, &max_fd);
} }
tv.tv_sec = 0; if (poll(pfd, ctx->num_listening_sockets, 200) > 0) {
tv.tv_usec = 200 * 1000; for (i = 0; i < ctx->num_listening_sockets; i++) {
if (ctx->stop_flag == 0 && pfd[i].revents == POLLIN) {
if (select(max_fd + 1, &read_set, NULL, NULL, &tv) < 0) { accept_new_connection(&ctx->listening_sockets[i], ctx);
#ifdef _WIN32
// On windows, if read_set and write_set are empty,
// select() returns "Invalid parameter" error
// (at least on my Windows XP Pro). So in this case, we sleep here.
mg_sleep(1000);
#endif // _WIN32
} else {
for (sp = ctx->listening_sockets; sp != NULL; sp = sp->next) {
if (ctx->stop_flag == 0 && FD_ISSET(sp->sock, &read_set)) {
accept_new_connection(sp, ctx);
} }
} }
} }
} }
free(pfd);
DEBUG_TRACE(("stopping workers")); DEBUG_TRACE(("stopping workers"));
// Stop signal received: somebody called mg_stop. Quit. // Stop signal received: somebody called mg_stop. Quit.
...@@ -5052,6 +5048,7 @@ static void master_thread(struct mg_context *ctx) { ...@@ -5052,6 +5048,7 @@ static void master_thread(struct mg_context *ctx) {
// WARNING: This must be the very last thing this // WARNING: This must be the very last thing this
// thread does, as ctx becomes invalid after this line. // thread does, as ctx becomes invalid after this line.
ctx->stop_flag = 2; ctx->stop_flag = 2;
return NULL;
} }
static void free_context(struct mg_context *ctx) { static void free_context(struct mg_context *ctx) {
...@@ -5063,14 +5060,11 @@ static void free_context(struct mg_context *ctx) { ...@@ -5063,14 +5060,11 @@ static void free_context(struct mg_context *ctx) {
free(ctx->config[i]); free(ctx->config[i]);
} }
#ifndef NO_SSL
// Deallocate SSL context // Deallocate SSL context
if (ctx->ssl_ctx != NULL) { if (ctx->ssl_ctx != NULL) {
SSL_CTX_free(ctx->ssl_ctx); SSL_CTX_free(ctx->ssl_ctx);
} }
if (ctx->client_ssl_ctx != NULL) {
SSL_CTX_free(ctx->client_ssl_ctx);
}
#ifndef NO_SSL
if (ssl_mutexes != NULL) { if (ssl_mutexes != NULL) {
free(ssl_mutexes); free(ssl_mutexes);
ssl_mutexes = NULL; ssl_mutexes = NULL;
...@@ -5095,7 +5089,8 @@ void mg_stop(struct mg_context *ctx) { ...@@ -5095,7 +5089,8 @@ void mg_stop(struct mg_context *ctx) {
#endif // _WIN32 #endif // _WIN32
} }
struct mg_context *mg_start(mg_callback_t user_callback, void *user_data, struct mg_context *mg_start(const struct mg_callbacks *callbacks,
void *user_data,
const char **options) { const char **options) {
struct mg_context *ctx; struct mg_context *ctx;
const char *name, *value, *default_value; const char *name, *value, *default_value;
...@@ -5112,7 +5107,7 @@ struct mg_context *mg_start(mg_callback_t user_callback, void *user_data, ...@@ -5112,7 +5107,7 @@ struct mg_context *mg_start(mg_callback_t user_callback, void *user_data,
if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) { if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) {
return NULL; return NULL;
} }
ctx->user_callback = user_callback; ctx->callbacks = *callbacks;
ctx->user_data = user_data; ctx->user_data = user_data;
while (options && (name = *options++) != NULL) { while (options && (name = *options++) != NULL) {
...@@ -5173,11 +5168,11 @@ struct mg_context *mg_start(mg_callback_t user_callback, void *user_data, ...@@ -5173,11 +5168,11 @@ struct mg_context *mg_start(mg_callback_t user_callback, void *user_data,
(void) pthread_cond_init(&ctx->sq_full, NULL); (void) pthread_cond_init(&ctx->sq_full, NULL);
// Start master (listening) thread // Start master (listening) thread
mg_start_thread((mg_thread_func_t) master_thread, ctx); mg_start_thread(master_thread, ctx);
// Start worker threads // Start worker threads
for (i = 0; i < atoi(ctx->config[NUM_THREADS]); i++) { for (i = 0; i < atoi(ctx->config[NUM_THREADS]); i++) {
if (mg_start_thread((mg_thread_func_t) worker_thread, ctx) != 0) { if (mg_start_thread(worker_thread, ctx) != 0) {
cry(fc(ctx), "Cannot start worker thread: %d", ERRNO); cry(fc(ctx), "Cannot start worker thread: %d", ERRNO);
} else { } else {
ctx->num_threads++; ctx->num_threads++;
......
...@@ -42,122 +42,37 @@ struct mg_request_info { ...@@ -42,122 +42,37 @@ struct mg_request_info {
long remote_ip; // Client's IP address long remote_ip; // Client's IP address
int remote_port; // Client's port int remote_port; // Client's port
int is_ssl; // 1 if SSL-ed, 0 if not 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 { struct mg_header {
const char *name; // HTTP header name const char *name; // HTTP header name
const char *value; // HTTP header value const char *value; // HTTP header value
} http_headers[64]; // Maximum 64 headers } 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. // This structure needs to be passed to mg_start(), to let mongoose know
enum mg_event { // which callbacks to invoke. For detailed description, see
// New HTTP request has arrived from the client. // https://github.com/valenok/mongoose/blob/master/UserManual.md
// If callback returns non-NULL, Mongoose stops handling current request. struct mg_callbacks {
// ev_data contains NULL. int (*begin_request)(struct mg_connection *);
MG_NEW_REQUEST, void (*end_request)(const struct mg_connection *, int reply_status_code);
int (*log_message)(const struct mg_connection *, const char *message);
// Mongoose has finished handling the request. int (*init_ssl)(void *ssl_context);
// Callback return value is ignored. int (*websocket_connect)(const struct mg_connection *);
// ev_data contains integer HTTP status code: void (*websocket_ready)(struct mg_connection *);
// int http_reply_status_code = (long) request_info->ev_data; int (*websocket_data)(struct mg_connection *);
MG_REQUEST_COMPLETE, const char * (*open_file)(const struct mg_connection *,
const char *path, size_t *data_len);
// HTTP error must be returned to the client. void (*init_lua)(struct mg_connection *, void *lua_context);
// If callback returns non-NULL, Mongoose stops handling error. void (*upload)(struct mg_connection *, const char *file_name);
// 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,
}; };
// 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. // Start web server.
// //
// Parameters: // 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 // options: NULL terminated list of option_name, option_value pairs that
// specify Mongoose configuration parameters. // specify Mongoose configuration parameters.
// //
...@@ -179,8 +94,9 @@ typedef void *(*mg_callback_t)(enum mg_event event, struct mg_connection *conn); ...@@ -179,8 +94,9 @@ typedef void *(*mg_callback_t)(enum mg_event event, struct mg_connection *conn);
// //
// Return: // Return:
// web server context, or NULL on error. // web server context, or NULL on error.
struct mg_context *mg_start(mg_callback_t callback, void *user_data, struct mg_context *mg_start(const struct mg_callbacks *callbacks,
const char **options); void *user_data,
const char **configuration_options);
// Stop the web server. // Stop the web server.
...@@ -236,12 +152,6 @@ struct mg_request_info *mg_get_request_info(struct mg_connection *); ...@@ -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); 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 #undef PRINTF_FORMAT_STRING
#if _MSC_VER >= 1400 #if _MSC_VER >= 1400
#include <sal.h> #include <sal.h>
...@@ -260,6 +170,11 @@ int mg_write(struct mg_connection *, const void *buf, size_t len); ...@@ -260,6 +170,11 @@ int mg_write(struct mg_connection *, const void *buf, size_t len);
#define PRINTF_ARGS(x, y) #define PRINTF_ARGS(x, y)
#endif #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 *, int mg_printf(struct mg_connection *,
PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3); PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
...@@ -316,32 +231,30 @@ int mg_get_cookie(const struct mg_connection *, ...@@ -316,32 +231,30 @@ int mg_get_cookie(const struct mg_connection *,
const char *cookie_name, char *buf, size_t buf_len); 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: // Return:
// On success, valid pointer to the new connection // On success, valid pointer to the new connection, suitable for mg_read().
// On error, NULL // On error, NULL. error_buffer contains error message.
struct mg_connection *mg_connect(struct mg_context *ctx, // Example:
const char *host, int port, int use_ssl); // 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); 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 upload functionality. Each uploaded file gets saved into a temporary
// file and MG_UPLOAD event is sent. // file and MG_UPLOAD event is sent.
// Return number of uploaded files. // Return number of uploaded files.
......
<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')
function is_prime(n) ?><html><body>
if n <= 0 then return false end
if n <= 2 then return true end <p>This is an example Lua server page served by
if (n % 2 == 0) then return false end <a href="http://code.google.com/p/mongoose">Mongoose web server</a>.
for i = 3, n / 2, 2 do Mongoose has Lua, Sqlite, and other functionality built in the binary.
if (n % i == 0) then return false end This example page stores the request in the Sqlite database, and shows
end all requests done previously.</p>
return true
end <pre>
<?
for i = 1, 100 do -- Open database
if is_prime(i) then print('<span>' .. i .. '</span>&nbsp;') end local db = sqlite3.open('requests.db')
end
-- 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> -- Create a table if it is not created already
<form method="POST"><input type="text" name="t1"/><input type="submit"></form> db:exec([[
CREATE TABLE IF NOT EXISTS requests (
<pre> id INTEGER PRIMARY KEY AUTOINCREMENT,
POST data: [<? post_data = read() print(post_data) ?>] timestamp NOT NULL,
request method: [<? print(request_info.request_method) ?>] method NOT NULL,
IP/port: [<? print(request_info.remote_ip, ':', request_info.remote_port) ?>] uri NOT NULL,
URI: [<? print(request_info.uri) ?>] user_agent
HTTP version [<? print(request_info.http_version) ?>] );
HEADERS: ]])
<?
for name, value in pairs(request_info.http_headers) do -- Add entry about this request
print(name, ':', value, '\n') 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 end
?>
</pre>
</html> -- Close database
db:close()
?>
</pre></body></html>
...@@ -146,11 +146,6 @@ if ($^O =~ /darwin|bsd|linux/) { ...@@ -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') { if (scalar(@ARGV) > 0 and $ARGV[0] eq 'unit') {
do_unit_test(); do_unit_test();
exit 0; exit 0;
...@@ -158,20 +153,21 @@ if (scalar(@ARGV) > 0 and $ARGV[0] eq 'unit') { ...@@ -158,20 +153,21 @@ if (scalar(@ARGV) > 0 and $ARGV[0] eq 'unit') {
# Make sure we load config file if no options are given. # Make sure we load config file if no options are given.
# Command line options override config files settings # Command line options override config files settings
write_file($config, "access_log_file access.log\nlistening_ports 12345\n"); write_file($config, "access_log_file access.log\n" .
spawn("$exe -p $port"); "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'); o("GET /test/hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'Loading config file');
unlink $config; unlink $config;
kill_spawned_child(); kill_spawned_child();
# Spawn the server on port $port # Spawn the server on port $port
my $cmd = "$exe ". my $cmd = "$exe ".
"-listening_ports $port ". "-listening_ports 127.0.0.1:$port ".
"-access_log_file access.log ". "-access_log_file access.log ".
"-error_log_file debug.log ". "-error_log_file debug.log ".
"-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " . "-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
"-extra_mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " . "-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 ' . '-access_control_list -0.0.0.0/0,+127.0.0.1 ' .
"-document_root $root ". "-document_root $root ".
"-hide_files_patterns **exploit.pl ". "-hide_files_patterns **exploit.pl ".
...@@ -219,11 +215,10 @@ write_file("$root/a+.txt", ''); ...@@ -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'); o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in URI');
# Test HTTP version parsing # Test HTTP version parsing
o("GET / HTTPX/1.0\r\n\r\n", '400 Bad Request', 'Bad HTTP Version', 0); 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", '505 HTTP', 'Bad HTTP maj Version'); 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", '505 HTTP', 'Bad HTTP min Version'); 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", '505 HTTP version not supported', o("GET / HTTP/02.0\r\n\r\n", '^HTTP/1.1 505', 'HTTP Version >1.1', 0);
'HTTP Version >1.1');
# File with leading single dot # File with leading single dot
o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1'); o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1');
...@@ -426,7 +421,6 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") { ...@@ -426,7 +421,6 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
do_PUT_test(); do_PUT_test();
kill_spawned_child(); kill_spawned_child();
do_unit_test(); do_unit_test();
do_embedded_test();
} }
sub do_PUT_test { sub do_PUT_test {
...@@ -456,79 +450,8 @@ sub do_PUT_test { ...@@ -456,79 +450,8 @@ sub do_PUT_test {
} }
sub do_unit_test { sub do_unit_test {
my $cmd = "cc -g -W -Wall -o $unit_test_exe $root/unit_test.c -I. ". my $target = on_windows() ? 'w' : 'u';
"-pthread -DNO_SSL "; system("make $target") == 0 or fail("Unit test failed!");
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();
} }
print "SUCCESS! All tests passed.\n"; print "SUCCESS! All tests passed.\n";
// 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_WEBSOCKET
#define USE_LUA
#include "mongoose.c" #include "mongoose.c"
#define FATAL(str, line) do { \ #define FATAL(str, line) do { \
...@@ -7,24 +30,43 @@ ...@@ -7,24 +30,43 @@
} while (0) } while (0)
#define ASSERT(expr) do { if (!(expr)) FATAL(#expr, __LINE__); } 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
#ifndef _WIN32
#define __cdecl
#endif
static void test_parse_http_request() { static void test_parse_http_message() {
struct mg_request_info ri; struct mg_request_info ri;
char req1[] = "GET / HTTP/1.1\r\n\r\n"; char req1[] = "GET / HTTP/1.1\r\n\r\n";
char req2[] = "BLAH / 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 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 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(strcmp(ri.http_version, "1.1") == 0);
ASSERT(ri.num_headers == 0); ASSERT(ri.num_headers == 0);
ASSERT(parse_http_request(req2, sizeof(req2), &ri) == -1); ASSERT(parse_http_message(req2, sizeof(req2), &ri) == -1);
ASSERT(parse_http_request(req3, sizeof(req3), &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. // 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(ri.num_headers == 3);
ASSERT(strcmp(ri.http_headers[0].name, "A") == 0); ASSERT(strcmp(ri.http_headers[0].name, "A") == 0);
ASSERT(strcmp(ri.http_headers[0].value, "foo bar") == 0); ASSERT(strcmp(ri.http_headers[0].value, "foo bar") == 0);
...@@ -33,7 +75,9 @@ static void test_parse_http_request() { ...@@ -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].name, "baz\r\n\r") == 0);
ASSERT(strcmp(ri.http_headers[2].value, "") == 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) { static void test_should_keep_alive(void) {
...@@ -46,7 +90,8 @@ static void test_should_keep_alive(void) { ...@@ -46,7 +90,8 @@ static void test_should_keep_alive(void) {
memset(&conn, 0, sizeof(conn)); memset(&conn, 0, sizeof(conn));
conn.ctx = &ctx; 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"; ctx.config[ENABLE_KEEP_ALIVE] = "no";
ASSERT(should_keep_alive(&conn) == 0); ASSERT(should_keep_alive(&conn) == 0);
...@@ -58,13 +103,13 @@ static void test_should_keep_alive(void) { ...@@ -58,13 +103,13 @@ static void test_should_keep_alive(void) {
ASSERT(should_keep_alive(&conn) == 0); ASSERT(should_keep_alive(&conn) == 0);
conn.must_close = 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); 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); 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); ASSERT(should_keep_alive(&conn) == 1);
conn.status_code = 401; conn.status_code = 401;
...@@ -121,106 +166,205 @@ static void test_remove_double_dots() { ...@@ -121,106 +166,205 @@ static void test_remove_double_dots() {
size_t i; size_t i;
for (i = 0; i < ARRAY_SIZE(data); 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); remove_double_dots_and_double_slashes(data[i].before);
ASSERT(strcmp(data[i].before, data[i].after) == 0); 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 *fetch_data = "hello world!\n";
static const char *inmemory_file_data = "hi there"; static const char *inmemory_file_data = "hi there";
static void *event_handler(enum mg_event event, struct mg_connection *conn) { static const char *upload_filename = "upload_test.txt";
const struct mg_request_info *request_info = mg_get_request_info(conn); static const char *upload_ok_message = "upload successful";
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 (event == MG_NEW_REQUEST && !strcmp(request_info->uri, "/data")) { if (!strcmp(ri->uri, "/data")) {
mg_printf(conn, "HTTP/1.1 200 OK\r\n" mg_printf(conn, "HTTP/1.1 200 OK\r\n"
"Content-Length: %d\r\n" "Content-Length: %d\r\n"
"Content-Type: text/plain\r\n\r\n" "Content-Type: text/plain\r\n\r\n"
"%s", (int) strlen(fetch_data), fetch_data); "%s", (int) strlen(fetch_data), fetch_data);
return ""; return 1;
} 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); if (!strcmp(ri->uri, "/upload")) {
ASSERT(mg_upload(conn, ".") == 1);
} }
return NULL; return 0;
} }
static void test_mg_fetch(void) { static int log_message_cb(const struct mg_connection *conn, const char *msg) {
static const char *options[] = { (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", ".", "document_root", ".",
"listening_ports", UNUSED_PORT, "listening_ports", LISTENING_ADDR,
"enable_keep_alive", "yes",
"ssl_certificate", "build/ssl_cert.pem",
NULL, NULL,
}; };
char buf[2000], buf2[2000];
int n, length; 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_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_download(NULL, port, 0, ebuf, sizeof(ebuf), "%s", "") == NULL);
ASSERT(mg_fetch(ctx, "localhost", tmp_file, buf, sizeof(buf), &ri) == NULL); ASSERT(mg_download("localhost", 0, 0, ebuf, sizeof(ebuf), "%s", "") == NULL);
ASSERT(mg_fetch(ctx, "localhost:" UNUSED_PORT, tmp_file, ASSERT(mg_download("localhost", port, 1, ebuf, sizeof(ebuf),
buf, sizeof(buf), &ri) == NULL); "%s", "") == NULL);
ASSERT(mg_fetch(ctx, "http://$$$.$$$", tmp_file,
buf, sizeof(buf), &ri) == NULL); // Fetch nonexistent file, should see 404
ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
// Failed fetch, pass invalid file name "GET /gimbec HTTP/1.0\r\n\r\n")) != NULL);
ASSERT(mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/data", ASSERT(strcmp(conn->request_info.uri, "404") == 0);
"/this/file/must/not/exist/ever", mg_close_connection(conn);
buf, sizeof(buf), &ri) == NULL);
ASSERT((conn = mg_download("google.com", 443, 1, ebuf, sizeof(ebuf), "%s",
// Successful fetch "GET / HTTP/1.0\r\n\r\n")) != NULL);
ASSERT((fp = mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/data", mg_close_connection(conn);
tmp_file, buf, sizeof(buf), &ri)) != NULL);
ASSERT(ri.num_headers == 2); // Fetch mongoose.c, should succeed
ASSERT(!strcmp(ri.request_method, "HTTP/1.1")); ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
ASSERT(!strcmp(ri.uri, "200")); "GET /mongoose.c HTTP/1.0\r\n\r\n")) != NULL);
ASSERT(!strcmp(ri.http_version, "OK")); ASSERT(!strcmp(conn->request_info.uri, "200"));
ASSERT((length = ftell(fp)) == (int) strlen(fetch_data)); ASSERT((p1 = read_conn(conn, &len1)) != NULL);
fseek(fp, 0, SEEK_SET); ASSERT((p2 = read_file("mongoose.c", &len2)) != NULL);
ASSERT(fread(buf2, 1, length, fp) == (size_t) length); ASSERT(len1 == len2);
ASSERT(memcmp(buf2, fetch_data, length) == 0); ASSERT(memcmp(p1, p2, len1) == 0);
fclose(fp); free(p1), free(p2);
mg_close_connection(conn);
// 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);
// 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);
// 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);
// Fetch big file, mongoose.c mg_stop(ctx);
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 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 existing inmemory file static int alloc_printf(char **buf, size_t size, char *fmt, ...) {
ASSERT((fp = mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/blah", va_list ap;
tmp_file, buf, sizeof(buf), &ri)) != NULL); va_start(ap, fmt);
ASSERT(!mg_strcasecmp(ri.uri, "200")); return alloc_vprintf(buf, size, fmt, ap);
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);
remove(tmp_file); 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); mg_stop(ctx);
} }
...@@ -232,7 +376,6 @@ static void test_base64_encode(void) { ...@@ -232,7 +376,6 @@ static void test_base64_encode(void) {
for (i = 0; in[i] != NULL; i++) { for (i = 0; in[i] != NULL; i++) {
base64_encode((unsigned char *) in[i], strlen(in[i]), buf); base64_encode((unsigned char *) in[i], strlen(in[i]), buf);
printf("[%s] [%s]\n", out[i], buf);
ASSERT(!strcmp(buf, out[i])); ASSERT(!strcmp(buf, out[i]));
} }
} }
...@@ -297,10 +440,9 @@ static void check_lua_expr(lua_State *L, const char *expr, const char *value) { ...@@ -297,10 +440,9 @@ static void check_lua_expr(lua_State *L, const char *expr, const char *value) {
char buf[100]; char buf[100];
snprintf(buf, sizeof(buf), "%s = %s", var_name, expr); snprintf(buf, sizeof(buf), "%s = %s", var_name, expr);
luaL_dostring(L, buf); (void) luaL_dostring(L, buf);
lua_getglobal(L, var_name); lua_getglobal(L, var_name);
v = lua_tostring(L, -1); v = lua_tostring(L, -1);
printf("%s: %s: [%s] [%s]\n", __func__, expr, v == NULL ? "null" : v, value);
ASSERT((value == NULL && v == NULL) || ASSERT((value == NULL && v == NULL) ||
(value != NULL && v != NULL && !strcmp(value, v))); (value != NULL && v != NULL && !strcmp(value, v)));
} }
...@@ -312,13 +454,12 @@ static void test_lua(void) { ...@@ -312,13 +454,12 @@ static void test_lua(void) {
char http_request[] = "POST /foo/bar HTTP/1.1\r\n" char http_request[] = "POST /foo/bar HTTP/1.1\r\n"
"Content-Length: 12\r\n" "Content-Length: 12\r\n"
"Connection: close\r\n\r\nhello world!"; "Connection: close\r\n\r\nhello world!";
const char *page = "<? print('hi') ?>";
lua_State *L = luaL_newstate(); lua_State *L = luaL_newstate();
conn.ctx = &ctx; conn.ctx = &ctx;
conn.buf = http_request; conn.buf = http_request;
conn.buf_size = conn.data_len = strlen(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.request_info);
conn.content_len = conn.data_len - conn.request_len; conn.content_len = conn.data_len - conn.request_len;
...@@ -332,33 +473,16 @@ static void test_lua(void) { ...@@ -332,33 +473,16 @@ static void test_lua(void) {
check_lua_expr(L, "request_info.remote_ip", "0"); 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['Content-Length']", "12");
check_lua_expr(L, "request_info.http_headers['Connection']", "close"); 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", "12");
check_lua_expr(L, "post", "hello world!"); check_lua_expr(L, "post", "hello world!");
lua_close(L); lua_close(L);
} }
#endif #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 void test_mg_stat(void) {
static struct mg_context ctx; static struct mg_context ctx;
struct file file; struct file file = STRUCT_FILE_INITIALIZER;
memset(&file, 'A', sizeof(file));
ASSERT(!mg_stat(fc(&ctx), " does not exist ", &file)); ASSERT(!mg_stat(fc(&ctx), " does not exist ", &file));
} }
...@@ -380,21 +504,99 @@ static void test_skip_quoted(void) { ...@@ -380,21 +504,99 @@ static void test_skip_quoted(void) {
#endif #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_base64_encode();
test_match_prefix(); test_match_prefix();
test_remove_double_dots(); test_remove_double_dots();
test_should_keep_alive(); test_should_keep_alive();
test_parse_http_request(); test_parse_http_message();
test_mg_fetch(); test_mg_download();
test_mg_get_var(); test_mg_get_var();
test_set_throttle(); test_set_throttle();
test_next_option(); test_next_option();
test_user_data();
test_mg_stat(); test_mg_stat();
test_skip_quoted();
test_mg_upload();
test_request_replies();
test_api_calls();
#ifdef USE_LUA #ifdef USE_LUA
test_lua(); test_lua();
#endif #endif
test_skip_quoted();
printf("%s\n", "PASSED");
return 0; return 0;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment