summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorArno Töll <arno@debian.org>2012-11-21 23:03:34 +0100
committerArno Töll <arno@debian.org>2012-11-21 23:03:34 +0100
commiteb45c46b906e492f063f1469486190e93ff340ff (patch)
tree85d615969fa7bf8056a05b59006f77bc63e85892 /src
parent6426b37107707a1d95ffd03f68620cbda8bdb942 (diff)
downloadlighttpd-eb45c46b906e492f063f1469486190e93ff340ff.tar.gz
Imported Upstream version 1.4.10upstream/1.4.10
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am264
-rw-r--r--src/Makefile.in1512
-rw-r--r--src/array.c369
-rw-r--r--src/array.h150
-rw-r--r--src/base.h606
-rw-r--r--src/bitset.c67
-rw-r--r--src/bitset.h19
-rw-r--r--src/buffer.c1023
-rw-r--r--src/buffer.h130
-rw-r--r--src/chunk.c385
-rw-r--r--src/chunk.h69
-rw-r--r--src/configfile-glue.c445
-rw-r--r--src/configfile.c1194
-rw-r--r--src/configfile.h24
-rw-r--r--src/configparser.c1568
-rw-r--r--src/configparser.h25
-rw-r--r--src/configparser.y544
-rw-r--r--src/connections-glue.c44
-rw-r--r--src/connections.c1708
-rw-r--r--src/connections.h19
-rw-r--r--src/crc32.c82
-rw-r--r--src/crc32.h18
-rw-r--r--src/data_array.c65
-rw-r--r--src/data_config.c138
-rw-r--r--src/data_count.c68
-rw-r--r--src/data_fastcgi.c69
-rw-r--r--src/data_integer.c65
-rw-r--r--src/data_string.c104
-rw-r--r--src/etag.c32
-rw-r--r--src/etag.h15
-rw-r--r--src/fastcgi.h136
-rw-r--r--src/fdevent.c202
-rw-r--r--src/fdevent.h222
-rw-r--r--src/fdevent_freebsd_kqueue.c207
-rw-r--r--src/fdevent_linux_rtsig.c260
-rw-r--r--src/fdevent_linux_sysepoll.c160
-rw-r--r--src/fdevent_poll.c182
-rw-r--r--src/fdevent_select.c131
-rw-r--r--src/fdevent_solaris_devpoll.c158
-rw-r--r--src/http-header-glue.c319
-rw-r--r--src/http_auth.c1007
-rw-r--r--src/http_auth.h68
-rw-r--r--src/http_auth_digest.c19
-rw-r--r--src/http_auth_digest.h24
-rw-r--r--src/http_chunk.c133
-rw-r--r--src/http_chunk.h12
-rw-r--r--src/inet_ntop_cache.c53
-rw-r--r--src/inet_ntop_cache.h7
-rw-r--r--src/joblist.c63
-rw-r--r--src/joblist.h13
-rw-r--r--src/keyvalue.c384
-rw-r--r--src/keyvalue.h104
-rw-r--r--src/lemon.c4396
-rw-r--r--src/lempar.c687
-rw-r--r--src/log.c256
-rw-r--r--src/log.h13
-rw-r--r--src/md5.c355
-rw-r--r--src/md5.h47
-rw-r--r--src/mod_access.c167
-rw-r--r--src/mod_accesslog.c850
-rw-r--r--src/mod_alias.c199
-rw-r--r--src/mod_auth.c630
-rw-r--r--src/mod_auth.h0
-rw-r--r--src/mod_cgi.c1305
-rw-r--r--src/mod_cml.c325
-rw-r--r--src/mod_cml.h43
-rw-r--r--src/mod_cml_funcs.c301
-rw-r--r--src/mod_cml_funcs.h21
-rw-r--r--src/mod_cml_lua.c468
-rw-r--r--src/mod_compress.c701
-rw-r--r--src/mod_dirlisting.c888
-rw-r--r--src/mod_evasive.c178
-rw-r--r--src/mod_evhost.c334
-rw-r--r--src/mod_expire.c361
-rw-r--r--src/mod_fastcgi.c3874
-rw-r--r--src/mod_indexfile.c219
-rw-r--r--src/mod_mysql_vhost.c431
-rw-r--r--src/mod_proxy.c1324
-rw-r--r--src/mod_redirect.c277
-rw-r--r--src/mod_rewrite.c450
-rw-r--r--src/mod_rrdtool.c449
-rw-r--r--src/mod_scgi.c3089
-rw-r--r--src/mod_secure_download.c305
-rw-r--r--src/mod_setenv.c247
-rw-r--r--src/mod_simple_vhost.c281
-rw-r--r--src/mod_skeleton.c210
-rw-r--r--src/mod_ssi.c1086
-rw-r--r--src/mod_ssi.h43
-rw-r--r--src/mod_ssi_expr.c324
-rw-r--r--src/mod_ssi_expr.h31
-rw-r--r--src/mod_ssi_exprparser.c951
-rw-r--r--src/mod_ssi_exprparser.h12
-rw-r--r--src/mod_ssi_exprparser.y121
-rw-r--r--src/mod_staticfile.c492
-rw-r--r--src/mod_status.c841
-rw-r--r--src/mod_trigger_b4_dl.c586
-rw-r--r--src/mod_userdir.c294
-rw-r--r--src/mod_usertrack.c270
-rw-r--r--src/mod_webdav.c1854
-rw-r--r--src/network.c643
-rw-r--r--src/network.h13
-rw-r--r--src/network_backends.h58
-rw-r--r--src/network_freebsd_sendfile.c207
-rw-r--r--src/network_linux_sendfile.c245
-rw-r--r--src/network_openssl.c273
-rw-r--r--src/network_solaris_sendfilev.c213
-rw-r--r--src/network_write.c168
-rw-r--r--src/network_writev.c343
-rw-r--r--src/plugin.c451
-rw-r--r--src/plugin.h92
-rw-r--r--src/proc_open.c386
-rw-r--r--src/proc_open.h25
-rw-r--r--src/request.c1083
-rw-r--r--src/request.h9
-rw-r--r--src/response.c591
-rw-r--r--src/response.h19
-rw-r--r--src/server.c1336
-rw-r--r--src/server.h17
-rw-r--r--src/settings.h44
-rw-r--r--src/spawn-fcgi.c431
-rw-r--r--src/splaytree.c210
-rw-r--r--src/splaytree.h24
-rw-r--r--src/stat_cache.c668
-rw-r--r--src/stat_cache.h13
-rw-r--r--src/stream.c106
-rw-r--r--src/stream.h14
-rw-r--r--src/sys-mmap.h24
-rw-r--r--src/sys-socket.h24
128 files changed, 53001 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..7e9fc9e
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,264 @@
+noinst_PROGRAMS=proc_open lemon # simple-fcgi #graphic evalo bench ajp ssl error_test adserver gen-license
+sbin_PROGRAMS=lighttpd
+bin_PROGRAMS=spawn-fcgi
+LEMON=$(top_builddir)/src/lemon
+
+lemon_SOURCES=lemon.c
+
+#simple_fcgi_SOURCES=simple-fcgi.c
+#simple_fcgi_LDADD=-lfcgi
+
+if CROSS_COMPILING
+configparser.c configparser.h:
+mod_ssi_exprparser.c mod_ssi_exprparser.h:
+else
+configparser.y: lemon
+mod_ssi_exprparser.y: lemon
+
+configparser.c configparser.h: configparser.y
+ rm -f configparser.h
+ $(LEMON) -q $(srcdir)/configparser.y $(srcdir)/lempar.c
+
+mod_ssi_exprparser.c mod_ssi_exprparser.h: mod_ssi_exprparser.y
+ rm -f mod_ssi_exprparser.h
+ $(LEMON) -q $(srcdir)/mod_ssi_exprparser.y $(srcdir)/lempar.c
+endif
+
+configfile.c: configparser.h
+mod_ssi_expr.c: mod_ssi_exprparser.h
+
+common_src=buffer.c log.c \
+ keyvalue.c chunk.c \
+ http_chunk.c stream.c fdevent.c \
+ stat_cache.c plugin.c joblist.c etag.c array.c \
+ data_string.c data_count.c data_array.c \
+ data_integer.c md5.c data_fastcgi.c \
+ fdevent_select.c fdevent_linux_rtsig.c \
+ fdevent_poll.c fdevent_linux_sysepoll.c \
+ fdevent_solaris_devpoll.c fdevent_freebsd_kqueue.c \
+ data_config.c bitset.c \
+ inet_ntop_cache.c crc32.c \
+ connections-glue.c \
+ configfile-glue.c \
+ http-header-glue.c \
+ network_write.c network_linux_sendfile.c \
+ network_freebsd_sendfile.c network_writev.c \
+ network_solaris_sendfilev.c network_openssl.c \
+ splaytree.c
+
+src = server.c response.c connections.c network.c \
+ configfile.c configparser.c request.c proc_open.c
+
+spawn_fcgi_SOURCES=spawn-fcgi.c
+
+lib_LTLIBRARIES =
+
+if NO_RDYNAMIC
+# if the linker doesn't allow referencing symbols of the binary
+# we have to put everything into a shared-lib and link it into
+# everything
+lib_LTLIBRARIES += liblightcomp.la
+liblightcomp_la_SOURCES=$(common_src)
+liblightcomp_la_CFLAGS=$(AM_CFLAGS) $(FAM_CFLAGS)
+liblightcomp_la_LDFLAGS = -avoid-version -no-undefined
+liblightcomp_la_LIBADD = $(PCRE_LIB) $(SSL_LIB) $(FAM_LIBS)
+common_libadd = liblightcomp.la
+else
+src += $(common_src)
+common_libadd =
+endif
+
+lib_LTLIBRARIES += mod_evasive.la
+mod_evasive_la_SOURCES = mod_evasive.c
+mod_evasive_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_evasive_la_LIBADD = $(common_libadd)
+
+
+lib_LTLIBRARIES += mod_webdav.la
+mod_webdav_la_SOURCES = mod_webdav.c
+mod_webdav_la_CFLAGS = $(XML_CFLAGS)
+mod_webdav_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_webdav_la_LIBADD = $(common_libadd) $(XML_LIBS) $(SQLITE_LIBS)
+
+lib_LTLIBRARIES += mod_cml.la
+mod_cml_la_SOURCES = mod_cml.c mod_cml_lua.c mod_cml_funcs.c
+mod_cml_la_CFLAGS = $(AM_CFLAGS) $(LUA_CFLAGS)
+mod_cml_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_cml_la_LIBADD = $(MEMCACHE_LIB) $(common_libadd) $(LUA_LIBS) -lm
+
+lib_LTLIBRARIES += mod_trigger_b4_dl.la
+mod_trigger_b4_dl_la_SOURCES = mod_trigger_b4_dl.c
+mod_trigger_b4_dl_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_trigger_b4_dl_la_LIBADD = $(GDBM_LIB) $(MEMCACHE_LIB) $(PCRE_LIB) $(common_libadd)
+
+lib_LTLIBRARIES += mod_mysql_vhost.la
+mod_mysql_vhost_la_SOURCES = mod_mysql_vhost.c
+mod_mysql_vhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_mysql_vhost_la_LIBADD = $(MYSQL_LIBS) $(common_libadd)
+mod_mysql_vhost_la_CPPFLAGS = $(MYSQL_INCLUDE)
+
+lib_LTLIBRARIES += mod_cgi.la
+mod_cgi_la_SOURCES = mod_cgi.c
+mod_cgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_cgi_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_scgi.la
+mod_scgi_la_SOURCES = mod_scgi.c
+mod_scgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_scgi_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_staticfile.la
+mod_staticfile_la_SOURCES = mod_staticfile.c
+mod_staticfile_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_staticfile_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_dirlisting.la
+mod_dirlisting_la_SOURCES = mod_dirlisting.c
+mod_dirlisting_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_dirlisting_la_LIBADD = $(common_libadd) $(PCRE_LIB)
+
+lib_LTLIBRARIES += mod_indexfile.la
+mod_indexfile_la_SOURCES = mod_indexfile.c
+mod_indexfile_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_indexfile_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_setenv.la
+mod_setenv_la_SOURCES = mod_setenv.c
+mod_setenv_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_setenv_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_alias.la
+mod_alias_la_SOURCES = mod_alias.c
+mod_alias_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_alias_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_userdir.la
+mod_userdir_la_SOURCES = mod_userdir.c
+mod_userdir_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_userdir_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_rrdtool.la
+mod_rrdtool_la_SOURCES = mod_rrdtool.c
+mod_rrdtool_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_rrdtool_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_usertrack.la
+mod_usertrack_la_SOURCES = mod_usertrack.c
+mod_usertrack_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_usertrack_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_proxy.la
+mod_proxy_la_SOURCES = mod_proxy.c
+mod_proxy_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_proxy_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_ssi.la
+mod_ssi_la_SOURCES = mod_ssi_exprparser.c mod_ssi_expr.c mod_ssi.c
+mod_ssi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_ssi_la_LIBADD = $(common_libadd) $(PCRE_LIB)
+
+lib_LTLIBRARIES += mod_secdownload.la
+mod_secdownload_la_SOURCES = mod_secure_download.c
+mod_secdownload_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_secdownload_la_LIBADD = $(common_libadd)
+
+#lib_LTLIBRARIES += mod_httptls.la
+#mod_httptls_la_SOURCES = mod_httptls.c
+#mod_httptls_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+#mod_httptls_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_expire.la
+mod_expire_la_SOURCES = mod_expire.c
+mod_expire_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_expire_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_evhost.la
+mod_evhost_la_SOURCES = mod_evhost.c
+mod_evhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_evhost_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_simple_vhost.la
+mod_simple_vhost_la_SOURCES = mod_simple_vhost.c
+mod_simple_vhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_simple_vhost_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_fastcgi.la
+mod_fastcgi_la_SOURCES = mod_fastcgi.c
+mod_fastcgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_fastcgi_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_access.la
+mod_access_la_SOURCES = mod_access.c
+mod_access_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_access_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_compress.la
+mod_compress_la_SOURCES = mod_compress.c
+mod_compress_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_compress_la_LIBADD = $(Z_LIB) $(BZ_LIB) $(common_libadd)
+
+lib_LTLIBRARIES += mod_auth.la
+mod_auth_la_SOURCES = mod_auth.c http_auth_digest.c http_auth.c
+mod_auth_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_auth_la_LIBADD = $(CRYPT_LIB) $(LDAP_LIB) $(LBER_LIB) $(common_libadd)
+
+lib_LTLIBRARIES += mod_rewrite.la
+mod_rewrite_la_SOURCES = mod_rewrite.c
+mod_rewrite_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_rewrite_la_LIBADD = $(PCRE_LIB) $(common_libadd)
+
+lib_LTLIBRARIES += mod_redirect.la
+mod_redirect_la_SOURCES = mod_redirect.c
+mod_redirect_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_redirect_la_LIBADD = $(PCRE_LIB) $(common_libadd)
+
+lib_LTLIBRARIES += mod_status.la
+mod_status_la_SOURCES = mod_status.c
+mod_status_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_status_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_accesslog.la
+mod_accesslog_la_SOURCES = mod_accesslog.c
+mod_accesslog_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_accesslog_la_LIBADD = $(common_libadd)
+
+
+hdr = server.h buffer.h network.h log.h keyvalue.h \
+ response.h request.h fastcgi.h chunk.h \
+ settings.h http_chunk.h http_auth_digest.h \
+ md5.h http_auth.h stream.h \
+ fdevent.h connections.h base.h stat_cache.h \
+ plugin.h mod_auth.h \
+ etag.h joblist.h array.h crc32.h \
+ network_backends.h configfile.h bitset.h \
+ mod_ssi.h mod_ssi_expr.h inet_ntop_cache.h \
+ configparser.h mod_ssi_exprparser.h \
+ sys-mmap.h sys-socket.h mod_cml.h mod_cml_funcs.h \
+ splaytree.h proc_open.h
+
+DEFS= @DEFS@ -DLIBRARY_DIR="\"$(libdir)\""
+
+lighttpd_SOURCES = $(src)
+lighttpd_LDADD = $(PCRE_LIB) $(DL_LIB) $(SENDFILE_LIB) $(ATTR_LIB) $(common_libadd) $(SSL_LIB) $(FAM_LIBS)
+lighttpd_LDFLAGS = -export-dynamic
+lighttpd_CCPFLAGS = $(FAM_CFLAGS)
+
+proc_open_SOURCES = proc_open.c buffer.c
+proc_open_CPPFLAGS= -DDEBUG_PROC_OPEN
+
+#gen_license_SOURCES = license.c md5.c buffer.c gen_license.c
+
+#ssl_SOURCES = ssl.c
+
+
+#adserver_SOURCES = buffer.c iframe.c
+#adserver_LDADD = -lfcgi -lmysqlclient
+
+#error_test_SOURCES = error_test.c
+
+#evalo_SOURCES = buffer.c eval.c
+#bench_SOURCES = buffer.c bench.c
+#ajp_SOURCES = ajp.c
+
+noinst_HEADERS = $(hdr)
+EXTRA_DIST = mod_skeleton.c configparser.y mod_ssi_exprparser.y lempar.c
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..cc6fab9
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,1512 @@
+# Makefile.in generated by automake 1.9.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005 Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+SOURCES = $(liblightcomp_la_SOURCES) $(mod_access_la_SOURCES) $(mod_accesslog_la_SOURCES) $(mod_alias_la_SOURCES) $(mod_auth_la_SOURCES) $(mod_cgi_la_SOURCES) $(mod_cml_la_SOURCES) $(mod_compress_la_SOURCES) $(mod_dirlisting_la_SOURCES) $(mod_evasive_la_SOURCES) $(mod_evhost_la_SOURCES) $(mod_expire_la_SOURCES) $(mod_fastcgi_la_SOURCES) $(mod_indexfile_la_SOURCES) $(mod_mysql_vhost_la_SOURCES) $(mod_proxy_la_SOURCES) $(mod_redirect_la_SOURCES) $(mod_rewrite_la_SOURCES) $(mod_rrdtool_la_SOURCES) $(mod_scgi_la_SOURCES) $(mod_secdownload_la_SOURCES) $(mod_setenv_la_SOURCES) $(mod_simple_vhost_la_SOURCES) $(mod_ssi_la_SOURCES) $(mod_staticfile_la_SOURCES) $(mod_status_la_SOURCES) $(mod_trigger_b4_dl_la_SOURCES) $(mod_userdir_la_SOURCES) $(mod_usertrack_la_SOURCES) $(mod_webdav_la_SOURCES) $(lemon_SOURCES) $(lighttpd_SOURCES) $(proc_open_SOURCES) $(spawn_fcgi_SOURCES)
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+top_builddir = ..
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+INSTALL = @INSTALL@
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+noinst_PROGRAMS = proc_open$(EXEEXT) lemon$(EXEEXT)
+sbin_PROGRAMS = lighttpd$(EXEEXT)
+bin_PROGRAMS = spawn-fcgi$(EXEEXT)
+
+# if the linker doesn't allow referencing symbols of the binary
+# we have to put everything into a shared-lib and link it into
+# everything
+@NO_RDYNAMIC_TRUE@am__append_1 = liblightcomp.la
+@NO_RDYNAMIC_FALSE@am__append_2 = $(common_src)
+subdir = src
+DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \
+ $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = `echo $$p | sed -e 's|^.*/||'`;
+am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" \
+ "$(DESTDIR)$(sbindir)"
+libLTLIBRARIES_INSTALL = $(INSTALL)
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@NO_RDYNAMIC_TRUE@liblightcomp_la_DEPENDENCIES = \
+@NO_RDYNAMIC_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+@NO_RDYNAMIC_TRUE@ $(am__DEPENDENCIES_1)
+am__liblightcomp_la_SOURCES_DIST = buffer.c log.c keyvalue.c chunk.c \
+ http_chunk.c stream.c fdevent.c stat_cache.c plugin.c \
+ joblist.c etag.c array.c data_string.c data_count.c \
+ data_array.c data_integer.c md5.c data_fastcgi.c \
+ fdevent_select.c fdevent_linux_rtsig.c fdevent_poll.c \
+ fdevent_linux_sysepoll.c fdevent_solaris_devpoll.c \
+ fdevent_freebsd_kqueue.c data_config.c bitset.c \
+ inet_ntop_cache.c crc32.c connections-glue.c configfile-glue.c \
+ http-header-glue.c network_write.c network_linux_sendfile.c \
+ network_freebsd_sendfile.c network_writev.c \
+ network_solaris_sendfilev.c network_openssl.c splaytree.c
+am__objects_1 = liblightcomp_la-buffer.lo liblightcomp_la-log.lo \
+ liblightcomp_la-keyvalue.lo liblightcomp_la-chunk.lo \
+ liblightcomp_la-http_chunk.lo liblightcomp_la-stream.lo \
+ liblightcomp_la-fdevent.lo liblightcomp_la-stat_cache.lo \
+ liblightcomp_la-plugin.lo liblightcomp_la-joblist.lo \
+ liblightcomp_la-etag.lo liblightcomp_la-array.lo \
+ liblightcomp_la-data_string.lo liblightcomp_la-data_count.lo \
+ liblightcomp_la-data_array.lo liblightcomp_la-data_integer.lo \
+ liblightcomp_la-md5.lo liblightcomp_la-data_fastcgi.lo \
+ liblightcomp_la-fdevent_select.lo \
+ liblightcomp_la-fdevent_linux_rtsig.lo \
+ liblightcomp_la-fdevent_poll.lo \
+ liblightcomp_la-fdevent_linux_sysepoll.lo \
+ liblightcomp_la-fdevent_solaris_devpoll.lo \
+ liblightcomp_la-fdevent_freebsd_kqueue.lo \
+ liblightcomp_la-data_config.lo liblightcomp_la-bitset.lo \
+ liblightcomp_la-inet_ntop_cache.lo liblightcomp_la-crc32.lo \
+ liblightcomp_la-connections-glue.lo \
+ liblightcomp_la-configfile-glue.lo \
+ liblightcomp_la-http-header-glue.lo \
+ liblightcomp_la-network_write.lo \
+ liblightcomp_la-network_linux_sendfile.lo \
+ liblightcomp_la-network_freebsd_sendfile.lo \
+ liblightcomp_la-network_writev.lo \
+ liblightcomp_la-network_solaris_sendfilev.lo \
+ liblightcomp_la-network_openssl.lo \
+ liblightcomp_la-splaytree.lo
+@NO_RDYNAMIC_TRUE@am_liblightcomp_la_OBJECTS = $(am__objects_1)
+liblightcomp_la_OBJECTS = $(am_liblightcomp_la_OBJECTS)
+@NO_RDYNAMIC_TRUE@am_liblightcomp_la_rpath = -rpath $(libdir)
+@NO_RDYNAMIC_TRUE@am__DEPENDENCIES_2 = liblightcomp.la
+mod_access_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_access_la_OBJECTS = mod_access.lo
+mod_access_la_OBJECTS = $(am_mod_access_la_OBJECTS)
+mod_accesslog_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_accesslog_la_OBJECTS = mod_accesslog.lo
+mod_accesslog_la_OBJECTS = $(am_mod_accesslog_la_OBJECTS)
+mod_alias_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_alias_la_OBJECTS = mod_alias.lo
+mod_alias_la_OBJECTS = $(am_mod_alias_la_OBJECTS)
+mod_auth_la_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2)
+am_mod_auth_la_OBJECTS = mod_auth.lo http_auth_digest.lo http_auth.lo
+mod_auth_la_OBJECTS = $(am_mod_auth_la_OBJECTS)
+mod_cgi_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_cgi_la_OBJECTS = mod_cgi.lo
+mod_cgi_la_OBJECTS = $(am_mod_cgi_la_OBJECTS)
+mod_cml_la_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) \
+ $(am__DEPENDENCIES_1)
+am_mod_cml_la_OBJECTS = mod_cml_la-mod_cml.lo \
+ mod_cml_la-mod_cml_lua.lo mod_cml_la-mod_cml_funcs.lo
+mod_cml_la_OBJECTS = $(am_mod_cml_la_OBJECTS)
+mod_compress_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2)
+am_mod_compress_la_OBJECTS = mod_compress.lo
+mod_compress_la_OBJECTS = $(am_mod_compress_la_OBJECTS)
+mod_dirlisting_la_DEPENDENCIES = $(am__DEPENDENCIES_2) \
+ $(am__DEPENDENCIES_1)
+am_mod_dirlisting_la_OBJECTS = mod_dirlisting.lo
+mod_dirlisting_la_OBJECTS = $(am_mod_dirlisting_la_OBJECTS)
+mod_evasive_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_evasive_la_OBJECTS = mod_evasive.lo
+mod_evasive_la_OBJECTS = $(am_mod_evasive_la_OBJECTS)
+mod_evhost_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_evhost_la_OBJECTS = mod_evhost.lo
+mod_evhost_la_OBJECTS = $(am_mod_evhost_la_OBJECTS)
+mod_expire_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_expire_la_OBJECTS = mod_expire.lo
+mod_expire_la_OBJECTS = $(am_mod_expire_la_OBJECTS)
+mod_fastcgi_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_fastcgi_la_OBJECTS = mod_fastcgi.lo
+mod_fastcgi_la_OBJECTS = $(am_mod_fastcgi_la_OBJECTS)
+mod_indexfile_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_indexfile_la_OBJECTS = mod_indexfile.lo
+mod_indexfile_la_OBJECTS = $(am_mod_indexfile_la_OBJECTS)
+mod_mysql_vhost_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_2)
+am_mod_mysql_vhost_la_OBJECTS = mod_mysql_vhost_la-mod_mysql_vhost.lo
+mod_mysql_vhost_la_OBJECTS = $(am_mod_mysql_vhost_la_OBJECTS)
+mod_proxy_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_proxy_la_OBJECTS = mod_proxy.lo
+mod_proxy_la_OBJECTS = $(am_mod_proxy_la_OBJECTS)
+mod_redirect_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_2)
+am_mod_redirect_la_OBJECTS = mod_redirect.lo
+mod_redirect_la_OBJECTS = $(am_mod_redirect_la_OBJECTS)
+mod_rewrite_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_2)
+am_mod_rewrite_la_OBJECTS = mod_rewrite.lo
+mod_rewrite_la_OBJECTS = $(am_mod_rewrite_la_OBJECTS)
+mod_rrdtool_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_rrdtool_la_OBJECTS = mod_rrdtool.lo
+mod_rrdtool_la_OBJECTS = $(am_mod_rrdtool_la_OBJECTS)
+mod_scgi_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_scgi_la_OBJECTS = mod_scgi.lo
+mod_scgi_la_OBJECTS = $(am_mod_scgi_la_OBJECTS)
+mod_secdownload_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_secdownload_la_OBJECTS = mod_secure_download.lo
+mod_secdownload_la_OBJECTS = $(am_mod_secdownload_la_OBJECTS)
+mod_setenv_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_setenv_la_OBJECTS = mod_setenv.lo
+mod_setenv_la_OBJECTS = $(am_mod_setenv_la_OBJECTS)
+mod_simple_vhost_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_simple_vhost_la_OBJECTS = mod_simple_vhost.lo
+mod_simple_vhost_la_OBJECTS = $(am_mod_simple_vhost_la_OBJECTS)
+mod_ssi_la_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1)
+am_mod_ssi_la_OBJECTS = mod_ssi_exprparser.lo mod_ssi_expr.lo \
+ mod_ssi.lo
+mod_ssi_la_OBJECTS = $(am_mod_ssi_la_OBJECTS)
+mod_staticfile_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_staticfile_la_OBJECTS = mod_staticfile.lo
+mod_staticfile_la_OBJECTS = $(am_mod_staticfile_la_OBJECTS)
+mod_status_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_status_la_OBJECTS = mod_status.lo
+mod_status_la_OBJECTS = $(am_mod_status_la_OBJECTS)
+mod_trigger_b4_dl_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_2)
+am_mod_trigger_b4_dl_la_OBJECTS = mod_trigger_b4_dl.lo
+mod_trigger_b4_dl_la_OBJECTS = $(am_mod_trigger_b4_dl_la_OBJECTS)
+mod_userdir_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_userdir_la_OBJECTS = mod_userdir.lo
+mod_userdir_la_OBJECTS = $(am_mod_userdir_la_OBJECTS)
+mod_usertrack_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_mod_usertrack_la_OBJECTS = mod_usertrack.lo
+mod_usertrack_la_OBJECTS = $(am_mod_usertrack_la_OBJECTS)
+mod_webdav_la_DEPENDENCIES = $(am__DEPENDENCIES_2) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_mod_webdav_la_OBJECTS = mod_webdav_la-mod_webdav.lo
+mod_webdav_la_OBJECTS = $(am_mod_webdav_la_OBJECTS)
+binPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
+sbinPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
+PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS) $(sbin_PROGRAMS)
+am_lemon_OBJECTS = lemon.$(OBJEXT)
+lemon_OBJECTS = $(am_lemon_OBJECTS)
+lemon_LDADD = $(LDADD)
+am__lighttpd_SOURCES_DIST = server.c response.c connections.c \
+ network.c configfile.c configparser.c request.c proc_open.c \
+ buffer.c log.c keyvalue.c chunk.c http_chunk.c stream.c \
+ fdevent.c stat_cache.c plugin.c joblist.c etag.c array.c \
+ data_string.c data_count.c data_array.c data_integer.c md5.c \
+ data_fastcgi.c fdevent_select.c fdevent_linux_rtsig.c \
+ fdevent_poll.c fdevent_linux_sysepoll.c \
+ fdevent_solaris_devpoll.c fdevent_freebsd_kqueue.c \
+ data_config.c bitset.c inet_ntop_cache.c crc32.c \
+ connections-glue.c configfile-glue.c http-header-glue.c \
+ network_write.c network_linux_sendfile.c \
+ network_freebsd_sendfile.c network_writev.c \
+ network_solaris_sendfilev.c network_openssl.c splaytree.c
+am__objects_2 = buffer.$(OBJEXT) log.$(OBJEXT) keyvalue.$(OBJEXT) \
+ chunk.$(OBJEXT) http_chunk.$(OBJEXT) stream.$(OBJEXT) \
+ fdevent.$(OBJEXT) stat_cache.$(OBJEXT) plugin.$(OBJEXT) \
+ joblist.$(OBJEXT) etag.$(OBJEXT) array.$(OBJEXT) \
+ data_string.$(OBJEXT) data_count.$(OBJEXT) \
+ data_array.$(OBJEXT) data_integer.$(OBJEXT) md5.$(OBJEXT) \
+ data_fastcgi.$(OBJEXT) fdevent_select.$(OBJEXT) \
+ fdevent_linux_rtsig.$(OBJEXT) fdevent_poll.$(OBJEXT) \
+ fdevent_linux_sysepoll.$(OBJEXT) \
+ fdevent_solaris_devpoll.$(OBJEXT) \
+ fdevent_freebsd_kqueue.$(OBJEXT) data_config.$(OBJEXT) \
+ bitset.$(OBJEXT) inet_ntop_cache.$(OBJEXT) crc32.$(OBJEXT) \
+ connections-glue.$(OBJEXT) configfile-glue.$(OBJEXT) \
+ http-header-glue.$(OBJEXT) network_write.$(OBJEXT) \
+ network_linux_sendfile.$(OBJEXT) \
+ network_freebsd_sendfile.$(OBJEXT) network_writev.$(OBJEXT) \
+ network_solaris_sendfilev.$(OBJEXT) network_openssl.$(OBJEXT) \
+ splaytree.$(OBJEXT)
+@NO_RDYNAMIC_FALSE@am__objects_3 = $(am__objects_2)
+am__objects_4 = server.$(OBJEXT) response.$(OBJEXT) \
+ connections.$(OBJEXT) network.$(OBJEXT) configfile.$(OBJEXT) \
+ configparser.$(OBJEXT) request.$(OBJEXT) proc_open.$(OBJEXT) \
+ $(am__objects_3)
+am_lighttpd_OBJECTS = $(am__objects_4)
+lighttpd_OBJECTS = $(am_lighttpd_OBJECTS)
+lighttpd_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_proc_open_OBJECTS = proc_open-proc_open.$(OBJEXT) \
+ proc_open-buffer.$(OBJEXT)
+proc_open_OBJECTS = $(am_proc_open_OBJECTS)
+proc_open_LDADD = $(LDADD)
+am_spawn_fcgi_OBJECTS = spawn-fcgi.$(OBJEXT)
+spawn_fcgi_OBJECTS = $(am_spawn_fcgi_OBJECTS)
+spawn_fcgi_LDADD = $(LDADD)
+DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+SOURCES = $(liblightcomp_la_SOURCES) $(mod_access_la_SOURCES) \
+ $(mod_accesslog_la_SOURCES) $(mod_alias_la_SOURCES) \
+ $(mod_auth_la_SOURCES) $(mod_cgi_la_SOURCES) \
+ $(mod_cml_la_SOURCES) $(mod_compress_la_SOURCES) \
+ $(mod_dirlisting_la_SOURCES) $(mod_evasive_la_SOURCES) \
+ $(mod_evhost_la_SOURCES) $(mod_expire_la_SOURCES) \
+ $(mod_fastcgi_la_SOURCES) $(mod_indexfile_la_SOURCES) \
+ $(mod_mysql_vhost_la_SOURCES) $(mod_proxy_la_SOURCES) \
+ $(mod_redirect_la_SOURCES) $(mod_rewrite_la_SOURCES) \
+ $(mod_rrdtool_la_SOURCES) $(mod_scgi_la_SOURCES) \
+ $(mod_secdownload_la_SOURCES) $(mod_setenv_la_SOURCES) \
+ $(mod_simple_vhost_la_SOURCES) $(mod_ssi_la_SOURCES) \
+ $(mod_staticfile_la_SOURCES) $(mod_status_la_SOURCES) \
+ $(mod_trigger_b4_dl_la_SOURCES) $(mod_userdir_la_SOURCES) \
+ $(mod_usertrack_la_SOURCES) $(mod_webdav_la_SOURCES) \
+ $(lemon_SOURCES) $(lighttpd_SOURCES) $(proc_open_SOURCES) \
+ $(spawn_fcgi_SOURCES)
+DIST_SOURCES = $(am__liblightcomp_la_SOURCES_DIST) \
+ $(mod_access_la_SOURCES) $(mod_accesslog_la_SOURCES) \
+ $(mod_alias_la_SOURCES) $(mod_auth_la_SOURCES) \
+ $(mod_cgi_la_SOURCES) $(mod_cml_la_SOURCES) \
+ $(mod_compress_la_SOURCES) $(mod_dirlisting_la_SOURCES) \
+ $(mod_evasive_la_SOURCES) $(mod_evhost_la_SOURCES) \
+ $(mod_expire_la_SOURCES) $(mod_fastcgi_la_SOURCES) \
+ $(mod_indexfile_la_SOURCES) $(mod_mysql_vhost_la_SOURCES) \
+ $(mod_proxy_la_SOURCES) $(mod_redirect_la_SOURCES) \
+ $(mod_rewrite_la_SOURCES) $(mod_rrdtool_la_SOURCES) \
+ $(mod_scgi_la_SOURCES) $(mod_secdownload_la_SOURCES) \
+ $(mod_setenv_la_SOURCES) $(mod_simple_vhost_la_SOURCES) \
+ $(mod_ssi_la_SOURCES) $(mod_staticfile_la_SOURCES) \
+ $(mod_status_la_SOURCES) $(mod_trigger_b4_dl_la_SOURCES) \
+ $(mod_userdir_la_SOURCES) $(mod_usertrack_la_SOURCES) \
+ $(mod_webdav_la_SOURCES) $(lemon_SOURCES) \
+ $(am__lighttpd_SOURCES_DIST) $(proc_open_SOURCES) \
+ $(spawn_fcgi_SOURCES)
+HEADERS = $(noinst_HEADERS)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMDEP_FALSE = @AMDEP_FALSE@
+AMDEP_TRUE = @AMDEP_TRUE@
+AMTAR = @AMTAR@
+AR = @AR@
+ATTR_LIB = @ATTR_LIB@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BZ_LIB = @BZ_LIB@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_WITH_FASTCGI_FALSE = @CHECK_WITH_FASTCGI_FALSE@
+CHECK_WITH_FASTCGI_TRUE = @CHECK_WITH_FASTCGI_TRUE@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CROSS_COMPILING_FALSE = @CROSS_COMPILING_FALSE@
+CROSS_COMPILING_TRUE = @CROSS_COMPILING_TRUE@
+CRYPT_LIB = @CRYPT_LIB@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@ -DLIBRARY_DIR="\"$(libdir)\""
+DEPDIR = @DEPDIR@
+DL_LIB = @DL_LIB@
+ECHO = @ECHO@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+F77 = @F77@
+FAM_CFLAGS = @FAM_CFLAGS@
+FAM_LIBS = @FAM_LIBS@
+FFLAGS = @FFLAGS@
+GDBM_LIB = @GDBM_LIB@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LBER_LIB = @LBER_LIB@
+LDAP_LIB = @LDAP_LIB@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LUACONFIG = @LUACONFIG@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAINTAINER_MODE_FALSE = @MAINTAINER_MODE_FALSE@
+MAINTAINER_MODE_TRUE = @MAINTAINER_MODE_TRUE@
+MAKEINFO = @MAKEINFO@
+MEMCACHE_LIB = @MEMCACHE_LIB@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_INCLUDE = @MYSQL_INCLUDE@
+MYSQL_LIBS = @MYSQL_LIBS@
+NO_RDYNAMIC_FALSE = @NO_RDYNAMIC_FALSE@
+NO_RDYNAMIC_TRUE = @NO_RDYNAMIC_TRUE@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRECONFIG = @PCRECONFIG@
+PCRE_LIB = @PCRE_LIB@
+PKG_CONFIG = @PKG_CONFIG@
+RANLIB = @RANLIB@
+SENDFILE_LIB = @SENDFILE_LIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SSL_LIB = @SSL_LIB@
+STRIP = @STRIP@
+U = @U@
+VERSION = @VERSION@
+XML_CFLAGS = @XML_CFLAGS@
+XML_LIBS = @XML_LIBS@
+Z_LIB = @Z_LIB@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_F77 = @ac_ct_F77@
+ac_ct_RANLIB = @ac_ct_RANLIB@
+ac_ct_STRIP = @ac_ct_STRIP@
+am__fastdepCC_FALSE = @am__fastdepCC_FALSE@
+am__fastdepCC_TRUE = @am__fastdepCC_TRUE@
+am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@
+am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+datadir = @datadir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+LEMON = $(top_builddir)/src/lemon
+lemon_SOURCES = lemon.c
+common_src = buffer.c log.c \
+ keyvalue.c chunk.c \
+ http_chunk.c stream.c fdevent.c \
+ stat_cache.c plugin.c joblist.c etag.c array.c \
+ data_string.c data_count.c data_array.c \
+ data_integer.c md5.c data_fastcgi.c \
+ fdevent_select.c fdevent_linux_rtsig.c \
+ fdevent_poll.c fdevent_linux_sysepoll.c \
+ fdevent_solaris_devpoll.c fdevent_freebsd_kqueue.c \
+ data_config.c bitset.c \
+ inet_ntop_cache.c crc32.c \
+ connections-glue.c \
+ configfile-glue.c \
+ http-header-glue.c \
+ network_write.c network_linux_sendfile.c \
+ network_freebsd_sendfile.c network_writev.c \
+ network_solaris_sendfilev.c network_openssl.c \
+ splaytree.c
+
+src = server.c response.c connections.c network.c configfile.c \
+ configparser.c request.c proc_open.c $(am__append_2)
+spawn_fcgi_SOURCES = spawn-fcgi.c
+
+#lib_LTLIBRARIES += mod_httptls.la
+#mod_httptls_la_SOURCES = mod_httptls.c
+#mod_httptls_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+#mod_httptls_la_LIBADD = $(common_libadd)
+lib_LTLIBRARIES = $(am__append_1) mod_evasive.la mod_webdav.la \
+ mod_cml.la mod_trigger_b4_dl.la mod_mysql_vhost.la mod_cgi.la \
+ mod_scgi.la mod_staticfile.la mod_dirlisting.la \
+ mod_indexfile.la mod_setenv.la mod_alias.la mod_userdir.la \
+ mod_rrdtool.la mod_usertrack.la mod_proxy.la mod_ssi.la \
+ mod_secdownload.la mod_expire.la mod_evhost.la \
+ mod_simple_vhost.la mod_fastcgi.la mod_access.la \
+ mod_compress.la mod_auth.la mod_rewrite.la mod_redirect.la \
+ mod_status.la mod_accesslog.la
+@NO_RDYNAMIC_TRUE@liblightcomp_la_SOURCES = $(common_src)
+@NO_RDYNAMIC_TRUE@liblightcomp_la_CFLAGS = $(AM_CFLAGS) $(FAM_CFLAGS)
+@NO_RDYNAMIC_TRUE@liblightcomp_la_LDFLAGS = -avoid-version -no-undefined
+@NO_RDYNAMIC_TRUE@liblightcomp_la_LIBADD = $(PCRE_LIB) $(SSL_LIB) $(FAM_LIBS)
+@NO_RDYNAMIC_FALSE@common_libadd =
+@NO_RDYNAMIC_TRUE@common_libadd = liblightcomp.la
+mod_evasive_la_SOURCES = mod_evasive.c
+mod_evasive_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_evasive_la_LIBADD = $(common_libadd)
+mod_webdav_la_SOURCES = mod_webdav.c
+mod_webdav_la_CFLAGS = $(XML_CFLAGS)
+mod_webdav_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_webdav_la_LIBADD = $(common_libadd) $(XML_LIBS) $(SQLITE_LIBS)
+mod_cml_la_SOURCES = mod_cml.c mod_cml_lua.c mod_cml_funcs.c
+mod_cml_la_CFLAGS = $(AM_CFLAGS) $(LUA_CFLAGS)
+mod_cml_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_cml_la_LIBADD = $(MEMCACHE_LIB) $(common_libadd) $(LUA_LIBS) -lm
+mod_trigger_b4_dl_la_SOURCES = mod_trigger_b4_dl.c
+mod_trigger_b4_dl_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_trigger_b4_dl_la_LIBADD = $(GDBM_LIB) $(MEMCACHE_LIB) $(PCRE_LIB) $(common_libadd)
+mod_mysql_vhost_la_SOURCES = mod_mysql_vhost.c
+mod_mysql_vhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_mysql_vhost_la_LIBADD = $(MYSQL_LIBS) $(common_libadd)
+mod_mysql_vhost_la_CPPFLAGS = $(MYSQL_INCLUDE)
+mod_cgi_la_SOURCES = mod_cgi.c
+mod_cgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_cgi_la_LIBADD = $(common_libadd)
+mod_scgi_la_SOURCES = mod_scgi.c
+mod_scgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_scgi_la_LIBADD = $(common_libadd)
+mod_staticfile_la_SOURCES = mod_staticfile.c
+mod_staticfile_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_staticfile_la_LIBADD = $(common_libadd)
+mod_dirlisting_la_SOURCES = mod_dirlisting.c
+mod_dirlisting_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_dirlisting_la_LIBADD = $(common_libadd) $(PCRE_LIB)
+mod_indexfile_la_SOURCES = mod_indexfile.c
+mod_indexfile_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_indexfile_la_LIBADD = $(common_libadd)
+mod_setenv_la_SOURCES = mod_setenv.c
+mod_setenv_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_setenv_la_LIBADD = $(common_libadd)
+mod_alias_la_SOURCES = mod_alias.c
+mod_alias_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_alias_la_LIBADD = $(common_libadd)
+mod_userdir_la_SOURCES = mod_userdir.c
+mod_userdir_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_userdir_la_LIBADD = $(common_libadd)
+mod_rrdtool_la_SOURCES = mod_rrdtool.c
+mod_rrdtool_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_rrdtool_la_LIBADD = $(common_libadd)
+mod_usertrack_la_SOURCES = mod_usertrack.c
+mod_usertrack_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_usertrack_la_LIBADD = $(common_libadd)
+mod_proxy_la_SOURCES = mod_proxy.c
+mod_proxy_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_proxy_la_LIBADD = $(common_libadd)
+mod_ssi_la_SOURCES = mod_ssi_exprparser.c mod_ssi_expr.c mod_ssi.c
+mod_ssi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_ssi_la_LIBADD = $(common_libadd) $(PCRE_LIB)
+mod_secdownload_la_SOURCES = mod_secure_download.c
+mod_secdownload_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_secdownload_la_LIBADD = $(common_libadd)
+mod_expire_la_SOURCES = mod_expire.c
+mod_expire_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_expire_la_LIBADD = $(common_libadd)
+mod_evhost_la_SOURCES = mod_evhost.c
+mod_evhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_evhost_la_LIBADD = $(common_libadd)
+mod_simple_vhost_la_SOURCES = mod_simple_vhost.c
+mod_simple_vhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_simple_vhost_la_LIBADD = $(common_libadd)
+mod_fastcgi_la_SOURCES = mod_fastcgi.c
+mod_fastcgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_fastcgi_la_LIBADD = $(common_libadd)
+mod_access_la_SOURCES = mod_access.c
+mod_access_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_access_la_LIBADD = $(common_libadd)
+mod_compress_la_SOURCES = mod_compress.c
+mod_compress_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_compress_la_LIBADD = $(Z_LIB) $(BZ_LIB) $(common_libadd)
+mod_auth_la_SOURCES = mod_auth.c http_auth_digest.c http_auth.c
+mod_auth_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_auth_la_LIBADD = $(CRYPT_LIB) $(LDAP_LIB) $(LBER_LIB) $(common_libadd)
+mod_rewrite_la_SOURCES = mod_rewrite.c
+mod_rewrite_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_rewrite_la_LIBADD = $(PCRE_LIB) $(common_libadd)
+mod_redirect_la_SOURCES = mod_redirect.c
+mod_redirect_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_redirect_la_LIBADD = $(PCRE_LIB) $(common_libadd)
+mod_status_la_SOURCES = mod_status.c
+mod_status_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_status_la_LIBADD = $(common_libadd)
+mod_accesslog_la_SOURCES = mod_accesslog.c
+mod_accesslog_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_accesslog_la_LIBADD = $(common_libadd)
+hdr = server.h buffer.h network.h log.h keyvalue.h \
+ response.h request.h fastcgi.h chunk.h \
+ settings.h http_chunk.h http_auth_digest.h \
+ md5.h http_auth.h stream.h \
+ fdevent.h connections.h base.h stat_cache.h \
+ plugin.h mod_auth.h \
+ etag.h joblist.h array.h crc32.h \
+ network_backends.h configfile.h bitset.h \
+ mod_ssi.h mod_ssi_expr.h inet_ntop_cache.h \
+ configparser.h mod_ssi_exprparser.h \
+ sys-mmap.h sys-socket.h mod_cml.h mod_cml_funcs.h \
+ splaytree.h proc_open.h
+
+lighttpd_SOURCES = $(src)
+lighttpd_LDADD = $(PCRE_LIB) $(DL_LIB) $(SENDFILE_LIB) $(ATTR_LIB) $(common_libadd) $(SSL_LIB) $(FAM_LIBS)
+lighttpd_LDFLAGS = -export-dynamic
+lighttpd_CCPFLAGS = $(FAM_CFLAGS)
+proc_open_SOURCES = proc_open.c buffer.c
+proc_open_CPPFLAGS = -DDEBUG_PROC_OPEN
+
+#gen_license_SOURCES = license.c md5.c buffer.c gen_license.c
+
+#ssl_SOURCES = ssl.c
+
+#adserver_SOURCES = buffer.c iframe.c
+#adserver_LDADD = -lfcgi -lmysqlclient
+
+#error_test_SOURCES = error_test.c
+
+#evalo_SOURCES = buffer.c eval.c
+#bench_SOURCES = buffer.c bench.c
+#ajp_SOURCES = ajp.c
+noinst_HEADERS = $(hdr)
+EXTRA_DIST = mod_skeleton.c configparser.y mod_ssi_exprparser.y lempar.c
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \
+ cd $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ test -z "$(libdir)" || $(mkdir_p) "$(DESTDIR)$(libdir)"
+ @list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+ if test -f $$p; then \
+ f=$(am__strip_dir) \
+ echo " $(LIBTOOL) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) '$$p' '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) "$$p" "$(DESTDIR)$(libdir)/$$f"; \
+ else :; fi; \
+ done
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @set -x; list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+ p=$(am__strip_dir) \
+ echo " $(LIBTOOL) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$p'"; \
+ $(LIBTOOL) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$p"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+ dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \
+ test "$$dir" != "$$p" || dir=.; \
+ echo "rm -f \"$${dir}/so_locations\""; \
+ rm -f "$${dir}/so_locations"; \
+ done
+liblightcomp.la: $(liblightcomp_la_OBJECTS) $(liblightcomp_la_DEPENDENCIES)
+ $(LINK) $(am_liblightcomp_la_rpath) $(liblightcomp_la_LDFLAGS) $(liblightcomp_la_OBJECTS) $(liblightcomp_la_LIBADD) $(LIBS)
+mod_access.la: $(mod_access_la_OBJECTS) $(mod_access_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_access_la_LDFLAGS) $(mod_access_la_OBJECTS) $(mod_access_la_LIBADD) $(LIBS)
+mod_accesslog.la: $(mod_accesslog_la_OBJECTS) $(mod_accesslog_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_accesslog_la_LDFLAGS) $(mod_accesslog_la_OBJECTS) $(mod_accesslog_la_LIBADD) $(LIBS)
+mod_alias.la: $(mod_alias_la_OBJECTS) $(mod_alias_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_alias_la_LDFLAGS) $(mod_alias_la_OBJECTS) $(mod_alias_la_LIBADD) $(LIBS)
+mod_auth.la: $(mod_auth_la_OBJECTS) $(mod_auth_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_auth_la_LDFLAGS) $(mod_auth_la_OBJECTS) $(mod_auth_la_LIBADD) $(LIBS)
+mod_cgi.la: $(mod_cgi_la_OBJECTS) $(mod_cgi_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_cgi_la_LDFLAGS) $(mod_cgi_la_OBJECTS) $(mod_cgi_la_LIBADD) $(LIBS)
+mod_cml.la: $(mod_cml_la_OBJECTS) $(mod_cml_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_cml_la_LDFLAGS) $(mod_cml_la_OBJECTS) $(mod_cml_la_LIBADD) $(LIBS)
+mod_compress.la: $(mod_compress_la_OBJECTS) $(mod_compress_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_compress_la_LDFLAGS) $(mod_compress_la_OBJECTS) $(mod_compress_la_LIBADD) $(LIBS)
+mod_dirlisting.la: $(mod_dirlisting_la_OBJECTS) $(mod_dirlisting_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_dirlisting_la_LDFLAGS) $(mod_dirlisting_la_OBJECTS) $(mod_dirlisting_la_LIBADD) $(LIBS)
+mod_evasive.la: $(mod_evasive_la_OBJECTS) $(mod_evasive_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_evasive_la_LDFLAGS) $(mod_evasive_la_OBJECTS) $(mod_evasive_la_LIBADD) $(LIBS)
+mod_evhost.la: $(mod_evhost_la_OBJECTS) $(mod_evhost_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_evhost_la_LDFLAGS) $(mod_evhost_la_OBJECTS) $(mod_evhost_la_LIBADD) $(LIBS)
+mod_expire.la: $(mod_expire_la_OBJECTS) $(mod_expire_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_expire_la_LDFLAGS) $(mod_expire_la_OBJECTS) $(mod_expire_la_LIBADD) $(LIBS)
+mod_fastcgi.la: $(mod_fastcgi_la_OBJECTS) $(mod_fastcgi_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_fastcgi_la_LDFLAGS) $(mod_fastcgi_la_OBJECTS) $(mod_fastcgi_la_LIBADD) $(LIBS)
+mod_indexfile.la: $(mod_indexfile_la_OBJECTS) $(mod_indexfile_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_indexfile_la_LDFLAGS) $(mod_indexfile_la_OBJECTS) $(mod_indexfile_la_LIBADD) $(LIBS)
+mod_mysql_vhost.la: $(mod_mysql_vhost_la_OBJECTS) $(mod_mysql_vhost_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_mysql_vhost_la_LDFLAGS) $(mod_mysql_vhost_la_OBJECTS) $(mod_mysql_vhost_la_LIBADD) $(LIBS)
+mod_proxy.la: $(mod_proxy_la_OBJECTS) $(mod_proxy_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_proxy_la_LDFLAGS) $(mod_proxy_la_OBJECTS) $(mod_proxy_la_LIBADD) $(LIBS)
+mod_redirect.la: $(mod_redirect_la_OBJECTS) $(mod_redirect_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_redirect_la_LDFLAGS) $(mod_redirect_la_OBJECTS) $(mod_redirect_la_LIBADD) $(LIBS)
+mod_rewrite.la: $(mod_rewrite_la_OBJECTS) $(mod_rewrite_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_rewrite_la_LDFLAGS) $(mod_rewrite_la_OBJECTS) $(mod_rewrite_la_LIBADD) $(LIBS)
+mod_rrdtool.la: $(mod_rrdtool_la_OBJECTS) $(mod_rrdtool_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_rrdtool_la_LDFLAGS) $(mod_rrdtool_la_OBJECTS) $(mod_rrdtool_la_LIBADD) $(LIBS)
+mod_scgi.la: $(mod_scgi_la_OBJECTS) $(mod_scgi_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_scgi_la_LDFLAGS) $(mod_scgi_la_OBJECTS) $(mod_scgi_la_LIBADD) $(LIBS)
+mod_secdownload.la: $(mod_secdownload_la_OBJECTS) $(mod_secdownload_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_secdownload_la_LDFLAGS) $(mod_secdownload_la_OBJECTS) $(mod_secdownload_la_LIBADD) $(LIBS)
+mod_setenv.la: $(mod_setenv_la_OBJECTS) $(mod_setenv_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_setenv_la_LDFLAGS) $(mod_setenv_la_OBJECTS) $(mod_setenv_la_LIBADD) $(LIBS)
+mod_simple_vhost.la: $(mod_simple_vhost_la_OBJECTS) $(mod_simple_vhost_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_simple_vhost_la_LDFLAGS) $(mod_simple_vhost_la_OBJECTS) $(mod_simple_vhost_la_LIBADD) $(LIBS)
+mod_ssi.la: $(mod_ssi_la_OBJECTS) $(mod_ssi_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_ssi_la_LDFLAGS) $(mod_ssi_la_OBJECTS) $(mod_ssi_la_LIBADD) $(LIBS)
+mod_staticfile.la: $(mod_staticfile_la_OBJECTS) $(mod_staticfile_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_staticfile_la_LDFLAGS) $(mod_staticfile_la_OBJECTS) $(mod_staticfile_la_LIBADD) $(LIBS)
+mod_status.la: $(mod_status_la_OBJECTS) $(mod_status_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_status_la_LDFLAGS) $(mod_status_la_OBJECTS) $(mod_status_la_LIBADD) $(LIBS)
+mod_trigger_b4_dl.la: $(mod_trigger_b4_dl_la_OBJECTS) $(mod_trigger_b4_dl_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_trigger_b4_dl_la_LDFLAGS) $(mod_trigger_b4_dl_la_OBJECTS) $(mod_trigger_b4_dl_la_LIBADD) $(LIBS)
+mod_userdir.la: $(mod_userdir_la_OBJECTS) $(mod_userdir_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_userdir_la_LDFLAGS) $(mod_userdir_la_OBJECTS) $(mod_userdir_la_LIBADD) $(LIBS)
+mod_usertrack.la: $(mod_usertrack_la_OBJECTS) $(mod_usertrack_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_usertrack_la_LDFLAGS) $(mod_usertrack_la_OBJECTS) $(mod_usertrack_la_LIBADD) $(LIBS)
+mod_webdav.la: $(mod_webdav_la_OBJECTS) $(mod_webdav_la_DEPENDENCIES)
+ $(LINK) -rpath $(libdir) $(mod_webdav_la_LDFLAGS) $(mod_webdav_la_OBJECTS) $(mod_webdav_la_LIBADD) $(LIBS)
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ test -z "$(bindir)" || $(mkdir_p) "$(DESTDIR)$(bindir)"
+ @list='$(bin_PROGRAMS)'; for p in $$list; do \
+ p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+ if test -f $$p \
+ || test -f $$p1 \
+ ; then \
+ f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \
+ else :; fi; \
+ done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; for p in $$list; do \
+ f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+ echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \
+ rm -f "$(DESTDIR)$(bindir)/$$f"; \
+ done
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; for p in $$list; do \
+ f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f $$p $$f"; \
+ rm -f $$p $$f ; \
+ done
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; for p in $$list; do \
+ f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f $$p $$f"; \
+ rm -f $$p $$f ; \
+ done
+install-sbinPROGRAMS: $(sbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ test -z "$(sbindir)" || $(mkdir_p) "$(DESTDIR)$(sbindir)"
+ @list='$(sbin_PROGRAMS)'; for p in $$list; do \
+ p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+ if test -f $$p \
+ || test -f $$p1 \
+ ; then \
+ f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(sbinPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(sbindir)/$$f'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(sbinPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(sbindir)/$$f" || exit 1; \
+ else :; fi; \
+ done
+
+uninstall-sbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_PROGRAMS)'; for p in $$list; do \
+ f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+ echo " rm -f '$(DESTDIR)$(sbindir)/$$f'"; \
+ rm -f "$(DESTDIR)$(sbindir)/$$f"; \
+ done
+
+clean-sbinPROGRAMS:
+ @list='$(sbin_PROGRAMS)'; for p in $$list; do \
+ f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f $$p $$f"; \
+ rm -f $$p $$f ; \
+ done
+lemon$(EXEEXT): $(lemon_OBJECTS) $(lemon_DEPENDENCIES)
+ @rm -f lemon$(EXEEXT)
+ $(LINK) $(lemon_LDFLAGS) $(lemon_OBJECTS) $(lemon_LDADD) $(LIBS)
+lighttpd$(EXEEXT): $(lighttpd_OBJECTS) $(lighttpd_DEPENDENCIES)
+ @rm -f lighttpd$(EXEEXT)
+ $(LINK) $(lighttpd_LDFLAGS) $(lighttpd_OBJECTS) $(lighttpd_LDADD) $(LIBS)
+proc_open$(EXEEXT): $(proc_open_OBJECTS) $(proc_open_DEPENDENCIES)
+ @rm -f proc_open$(EXEEXT)
+ $(LINK) $(proc_open_LDFLAGS) $(proc_open_OBJECTS) $(proc_open_LDADD) $(LIBS)
+spawn-fcgi$(EXEEXT): $(spawn_fcgi_OBJECTS) $(spawn_fcgi_DEPENDENCIES)
+ @rm -f spawn-fcgi$(EXEEXT)
+ $(LINK) $(spawn_fcgi_LDFLAGS) $(spawn_fcgi_OBJECTS) $(spawn_fcgi_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/array.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bitset.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buffer.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chunk.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/configfile-glue.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/configfile.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/configparser.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connections-glue.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connections.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc32.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data_array.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data_config.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data_count.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data_fastcgi.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data_integer.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data_string.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/etag.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent_freebsd_kqueue.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent_linux_rtsig.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent_linux_sysepoll.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent_poll.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent_select.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent_solaris_devpoll.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http-header-glue.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http_auth.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http_auth_digest.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http_chunk.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/inet_ntop_cache.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/joblist.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyvalue.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lemon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-array.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-bitset.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-buffer.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-chunk.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-configfile-glue.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-connections-glue.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-crc32.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-data_array.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-data_config.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-data_count.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-data_fastcgi.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-data_integer.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-data_string.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-etag.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent_freebsd_kqueue.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent_linux_rtsig.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent_linux_sysepoll.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent_poll.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent_select.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent_solaris_devpoll.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-http-header-glue.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-http_chunk.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-inet_ntop_cache.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-joblist.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-keyvalue.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-log.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-md5.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-network_freebsd_sendfile.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-network_linux_sendfile.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-network_openssl.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-network_solaris_sendfilev.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-network_write.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-network_writev.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-plugin.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-splaytree.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-stat_cache.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-stream.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_access.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_accesslog.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_alias.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_auth.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_cgi.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_cml_la-mod_cml.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_cml_la-mod_cml_funcs.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_cml_la-mod_cml_lua.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_compress.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_dirlisting.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_evasive.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_evhost.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_expire.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_fastcgi.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_indexfile.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_mysql_vhost_la-mod_mysql_vhost.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_proxy.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_redirect.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_rewrite.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_rrdtool.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_scgi.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_secure_download.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_setenv.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_simple_vhost.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_ssi.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_ssi_expr.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_ssi_exprparser.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_staticfile.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_status.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_trigger_b4_dl.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_userdir.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_usertrack.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_webdav_la-mod_webdav.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_freebsd_sendfile.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_linux_sendfile.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_openssl.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_solaris_sendfilev.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_write.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_writev.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/plugin.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_open-buffer.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_open-proc_open.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_open.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/request.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/response.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/spawn-fcgi.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/splaytree.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stat_cache.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stream.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ if $(LTCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Plo"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+liblightcomp_la-buffer.lo: buffer.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-buffer.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-buffer.Tpo" -c -o liblightcomp_la-buffer.lo `test -f 'buffer.c' || echo '$(srcdir)/'`buffer.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-buffer.Tpo" "$(DEPDIR)/liblightcomp_la-buffer.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-buffer.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='buffer.c' object='liblightcomp_la-buffer.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-buffer.lo `test -f 'buffer.c' || echo '$(srcdir)/'`buffer.c
+
+liblightcomp_la-log.lo: log.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-log.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-log.Tpo" -c -o liblightcomp_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-log.Tpo" "$(DEPDIR)/liblightcomp_la-log.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-log.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='log.c' object='liblightcomp_la-log.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c
+
+liblightcomp_la-keyvalue.lo: keyvalue.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-keyvalue.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-keyvalue.Tpo" -c -o liblightcomp_la-keyvalue.lo `test -f 'keyvalue.c' || echo '$(srcdir)/'`keyvalue.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-keyvalue.Tpo" "$(DEPDIR)/liblightcomp_la-keyvalue.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-keyvalue.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='keyvalue.c' object='liblightcomp_la-keyvalue.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-keyvalue.lo `test -f 'keyvalue.c' || echo '$(srcdir)/'`keyvalue.c
+
+liblightcomp_la-chunk.lo: chunk.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-chunk.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-chunk.Tpo" -c -o liblightcomp_la-chunk.lo `test -f 'chunk.c' || echo '$(srcdir)/'`chunk.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-chunk.Tpo" "$(DEPDIR)/liblightcomp_la-chunk.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-chunk.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='chunk.c' object='liblightcomp_la-chunk.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-chunk.lo `test -f 'chunk.c' || echo '$(srcdir)/'`chunk.c
+
+liblightcomp_la-http_chunk.lo: http_chunk.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-http_chunk.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-http_chunk.Tpo" -c -o liblightcomp_la-http_chunk.lo `test -f 'http_chunk.c' || echo '$(srcdir)/'`http_chunk.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-http_chunk.Tpo" "$(DEPDIR)/liblightcomp_la-http_chunk.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-http_chunk.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='http_chunk.c' object='liblightcomp_la-http_chunk.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-http_chunk.lo `test -f 'http_chunk.c' || echo '$(srcdir)/'`http_chunk.c
+
+liblightcomp_la-stream.lo: stream.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-stream.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-stream.Tpo" -c -o liblightcomp_la-stream.lo `test -f 'stream.c' || echo '$(srcdir)/'`stream.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-stream.Tpo" "$(DEPDIR)/liblightcomp_la-stream.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-stream.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='stream.c' object='liblightcomp_la-stream.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-stream.lo `test -f 'stream.c' || echo '$(srcdir)/'`stream.c
+
+liblightcomp_la-fdevent.lo: fdevent.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent.Tpo" -c -o liblightcomp_la-fdevent.lo `test -f 'fdevent.c' || echo '$(srcdir)/'`fdevent.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent.c' object='liblightcomp_la-fdevent.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent.lo `test -f 'fdevent.c' || echo '$(srcdir)/'`fdevent.c
+
+liblightcomp_la-stat_cache.lo: stat_cache.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-stat_cache.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-stat_cache.Tpo" -c -o liblightcomp_la-stat_cache.lo `test -f 'stat_cache.c' || echo '$(srcdir)/'`stat_cache.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-stat_cache.Tpo" "$(DEPDIR)/liblightcomp_la-stat_cache.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-stat_cache.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='stat_cache.c' object='liblightcomp_la-stat_cache.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-stat_cache.lo `test -f 'stat_cache.c' || echo '$(srcdir)/'`stat_cache.c
+
+liblightcomp_la-plugin.lo: plugin.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-plugin.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-plugin.Tpo" -c -o liblightcomp_la-plugin.lo `test -f 'plugin.c' || echo '$(srcdir)/'`plugin.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-plugin.Tpo" "$(DEPDIR)/liblightcomp_la-plugin.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-plugin.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='plugin.c' object='liblightcomp_la-plugin.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-plugin.lo `test -f 'plugin.c' || echo '$(srcdir)/'`plugin.c
+
+liblightcomp_la-joblist.lo: joblist.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-joblist.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-joblist.Tpo" -c -o liblightcomp_la-joblist.lo `test -f 'joblist.c' || echo '$(srcdir)/'`joblist.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-joblist.Tpo" "$(DEPDIR)/liblightcomp_la-joblist.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-joblist.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='joblist.c' object='liblightcomp_la-joblist.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-joblist.lo `test -f 'joblist.c' || echo '$(srcdir)/'`joblist.c
+
+liblightcomp_la-etag.lo: etag.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-etag.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-etag.Tpo" -c -o liblightcomp_la-etag.lo `test -f 'etag.c' || echo '$(srcdir)/'`etag.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-etag.Tpo" "$(DEPDIR)/liblightcomp_la-etag.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-etag.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='etag.c' object='liblightcomp_la-etag.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-etag.lo `test -f 'etag.c' || echo '$(srcdir)/'`etag.c
+
+liblightcomp_la-array.lo: array.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-array.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-array.Tpo" -c -o liblightcomp_la-array.lo `test -f 'array.c' || echo '$(srcdir)/'`array.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-array.Tpo" "$(DEPDIR)/liblightcomp_la-array.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-array.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='array.c' object='liblightcomp_la-array.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-array.lo `test -f 'array.c' || echo '$(srcdir)/'`array.c
+
+liblightcomp_la-data_string.lo: data_string.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-data_string.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-data_string.Tpo" -c -o liblightcomp_la-data_string.lo `test -f 'data_string.c' || echo '$(srcdir)/'`data_string.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-data_string.Tpo" "$(DEPDIR)/liblightcomp_la-data_string.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-data_string.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='data_string.c' object='liblightcomp_la-data_string.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-data_string.lo `test -f 'data_string.c' || echo '$(srcdir)/'`data_string.c
+
+liblightcomp_la-data_count.lo: data_count.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-data_count.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-data_count.Tpo" -c -o liblightcomp_la-data_count.lo `test -f 'data_count.c' || echo '$(srcdir)/'`data_count.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-data_count.Tpo" "$(DEPDIR)/liblightcomp_la-data_count.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-data_count.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='data_count.c' object='liblightcomp_la-data_count.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-data_count.lo `test -f 'data_count.c' || echo '$(srcdir)/'`data_count.c
+
+liblightcomp_la-data_array.lo: data_array.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-data_array.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-data_array.Tpo" -c -o liblightcomp_la-data_array.lo `test -f 'data_array.c' || echo '$(srcdir)/'`data_array.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-data_array.Tpo" "$(DEPDIR)/liblightcomp_la-data_array.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-data_array.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='data_array.c' object='liblightcomp_la-data_array.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-data_array.lo `test -f 'data_array.c' || echo '$(srcdir)/'`data_array.c
+
+liblightcomp_la-data_integer.lo: data_integer.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-data_integer.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-data_integer.Tpo" -c -o liblightcomp_la-data_integer.lo `test -f 'data_integer.c' || echo '$(srcdir)/'`data_integer.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-data_integer.Tpo" "$(DEPDIR)/liblightcomp_la-data_integer.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-data_integer.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='data_integer.c' object='liblightcomp_la-data_integer.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-data_integer.lo `test -f 'data_integer.c' || echo '$(srcdir)/'`data_integer.c
+
+liblightcomp_la-md5.lo: md5.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-md5.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-md5.Tpo" -c -o liblightcomp_la-md5.lo `test -f 'md5.c' || echo '$(srcdir)/'`md5.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-md5.Tpo" "$(DEPDIR)/liblightcomp_la-md5.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-md5.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5.c' object='liblightcomp_la-md5.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-md5.lo `test -f 'md5.c' || echo '$(srcdir)/'`md5.c
+
+liblightcomp_la-data_fastcgi.lo: data_fastcgi.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-data_fastcgi.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-data_fastcgi.Tpo" -c -o liblightcomp_la-data_fastcgi.lo `test -f 'data_fastcgi.c' || echo '$(srcdir)/'`data_fastcgi.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-data_fastcgi.Tpo" "$(DEPDIR)/liblightcomp_la-data_fastcgi.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-data_fastcgi.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='data_fastcgi.c' object='liblightcomp_la-data_fastcgi.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-data_fastcgi.lo `test -f 'data_fastcgi.c' || echo '$(srcdir)/'`data_fastcgi.c
+
+liblightcomp_la-fdevent_select.lo: fdevent_select.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent_select.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent_select.Tpo" -c -o liblightcomp_la-fdevent_select.lo `test -f 'fdevent_select.c' || echo '$(srcdir)/'`fdevent_select.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent_select.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent_select.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent_select.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent_select.c' object='liblightcomp_la-fdevent_select.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent_select.lo `test -f 'fdevent_select.c' || echo '$(srcdir)/'`fdevent_select.c
+
+liblightcomp_la-fdevent_linux_rtsig.lo: fdevent_linux_rtsig.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent_linux_rtsig.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent_linux_rtsig.Tpo" -c -o liblightcomp_la-fdevent_linux_rtsig.lo `test -f 'fdevent_linux_rtsig.c' || echo '$(srcdir)/'`fdevent_linux_rtsig.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent_linux_rtsig.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent_linux_rtsig.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent_linux_rtsig.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent_linux_rtsig.c' object='liblightcomp_la-fdevent_linux_rtsig.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent_linux_rtsig.lo `test -f 'fdevent_linux_rtsig.c' || echo '$(srcdir)/'`fdevent_linux_rtsig.c
+
+liblightcomp_la-fdevent_poll.lo: fdevent_poll.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent_poll.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent_poll.Tpo" -c -o liblightcomp_la-fdevent_poll.lo `test -f 'fdevent_poll.c' || echo '$(srcdir)/'`fdevent_poll.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent_poll.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent_poll.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent_poll.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent_poll.c' object='liblightcomp_la-fdevent_poll.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent_poll.lo `test -f 'fdevent_poll.c' || echo '$(srcdir)/'`fdevent_poll.c
+
+liblightcomp_la-fdevent_linux_sysepoll.lo: fdevent_linux_sysepoll.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent_linux_sysepoll.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent_linux_sysepoll.Tpo" -c -o liblightcomp_la-fdevent_linux_sysepoll.lo `test -f 'fdevent_linux_sysepoll.c' || echo '$(srcdir)/'`fdevent_linux_sysepoll.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent_linux_sysepoll.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent_linux_sysepoll.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent_linux_sysepoll.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent_linux_sysepoll.c' object='liblightcomp_la-fdevent_linux_sysepoll.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent_linux_sysepoll.lo `test -f 'fdevent_linux_sysepoll.c' || echo '$(srcdir)/'`fdevent_linux_sysepoll.c
+
+liblightcomp_la-fdevent_solaris_devpoll.lo: fdevent_solaris_devpoll.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent_solaris_devpoll.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent_solaris_devpoll.Tpo" -c -o liblightcomp_la-fdevent_solaris_devpoll.lo `test -f 'fdevent_solaris_devpoll.c' || echo '$(srcdir)/'`fdevent_solaris_devpoll.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent_solaris_devpoll.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent_solaris_devpoll.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent_solaris_devpoll.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent_solaris_devpoll.c' object='liblightcomp_la-fdevent_solaris_devpoll.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent_solaris_devpoll.lo `test -f 'fdevent_solaris_devpoll.c' || echo '$(srcdir)/'`fdevent_solaris_devpoll.c
+
+liblightcomp_la-fdevent_freebsd_kqueue.lo: fdevent_freebsd_kqueue.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent_freebsd_kqueue.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent_freebsd_kqueue.Tpo" -c -o liblightcomp_la-fdevent_freebsd_kqueue.lo `test -f 'fdevent_freebsd_kqueue.c' || echo '$(srcdir)/'`fdevent_freebsd_kqueue.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent_freebsd_kqueue.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent_freebsd_kqueue.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent_freebsd_kqueue.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent_freebsd_kqueue.c' object='liblightcomp_la-fdevent_freebsd_kqueue.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent_freebsd_kqueue.lo `test -f 'fdevent_freebsd_kqueue.c' || echo '$(srcdir)/'`fdevent_freebsd_kqueue.c
+
+liblightcomp_la-data_config.lo: data_config.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-data_config.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-data_config.Tpo" -c -o liblightcomp_la-data_config.lo `test -f 'data_config.c' || echo '$(srcdir)/'`data_config.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-data_config.Tpo" "$(DEPDIR)/liblightcomp_la-data_config.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-data_config.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='data_config.c' object='liblightcomp_la-data_config.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-data_config.lo `test -f 'data_config.c' || echo '$(srcdir)/'`data_config.c
+
+liblightcomp_la-bitset.lo: bitset.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-bitset.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-bitset.Tpo" -c -o liblightcomp_la-bitset.lo `test -f 'bitset.c' || echo '$(srcdir)/'`bitset.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-bitset.Tpo" "$(DEPDIR)/liblightcomp_la-bitset.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-bitset.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='bitset.c' object='liblightcomp_la-bitset.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-bitset.lo `test -f 'bitset.c' || echo '$(srcdir)/'`bitset.c
+
+liblightcomp_la-inet_ntop_cache.lo: inet_ntop_cache.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-inet_ntop_cache.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-inet_ntop_cache.Tpo" -c -o liblightcomp_la-inet_ntop_cache.lo `test -f 'inet_ntop_cache.c' || echo '$(srcdir)/'`inet_ntop_cache.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-inet_ntop_cache.Tpo" "$(DEPDIR)/liblightcomp_la-inet_ntop_cache.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-inet_ntop_cache.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='inet_ntop_cache.c' object='liblightcomp_la-inet_ntop_cache.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-inet_ntop_cache.lo `test -f 'inet_ntop_cache.c' || echo '$(srcdir)/'`inet_ntop_cache.c
+
+liblightcomp_la-crc32.lo: crc32.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-crc32.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-crc32.Tpo" -c -o liblightcomp_la-crc32.lo `test -f 'crc32.c' || echo '$(srcdir)/'`crc32.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-crc32.Tpo" "$(DEPDIR)/liblightcomp_la-crc32.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-crc32.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='crc32.c' object='liblightcomp_la-crc32.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-crc32.lo `test -f 'crc32.c' || echo '$(srcdir)/'`crc32.c
+
+liblightcomp_la-connections-glue.lo: connections-glue.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-connections-glue.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-connections-glue.Tpo" -c -o liblightcomp_la-connections-glue.lo `test -f 'connections-glue.c' || echo '$(srcdir)/'`connections-glue.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-connections-glue.Tpo" "$(DEPDIR)/liblightcomp_la-connections-glue.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-connections-glue.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='connections-glue.c' object='liblightcomp_la-connections-glue.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-connections-glue.lo `test -f 'connections-glue.c' || echo '$(srcdir)/'`connections-glue.c
+
+liblightcomp_la-configfile-glue.lo: configfile-glue.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-configfile-glue.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-configfile-glue.Tpo" -c -o liblightcomp_la-configfile-glue.lo `test -f 'configfile-glue.c' || echo '$(srcdir)/'`configfile-glue.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-configfile-glue.Tpo" "$(DEPDIR)/liblightcomp_la-configfile-glue.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-configfile-glue.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='configfile-glue.c' object='liblightcomp_la-configfile-glue.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-configfile-glue.lo `test -f 'configfile-glue.c' || echo '$(srcdir)/'`configfile-glue.c
+
+liblightcomp_la-http-header-glue.lo: http-header-glue.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-http-header-glue.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-http-header-glue.Tpo" -c -o liblightcomp_la-http-header-glue.lo `test -f 'http-header-glue.c' || echo '$(srcdir)/'`http-header-glue.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-http-header-glue.Tpo" "$(DEPDIR)/liblightcomp_la-http-header-glue.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-http-header-glue.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='http-header-glue.c' object='liblightcomp_la-http-header-glue.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-http-header-glue.lo `test -f 'http-header-glue.c' || echo '$(srcdir)/'`http-header-glue.c
+
+liblightcomp_la-network_write.lo: network_write.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-network_write.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-network_write.Tpo" -c -o liblightcomp_la-network_write.lo `test -f 'network_write.c' || echo '$(srcdir)/'`network_write.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-network_write.Tpo" "$(DEPDIR)/liblightcomp_la-network_write.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-network_write.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='network_write.c' object='liblightcomp_la-network_write.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-network_write.lo `test -f 'network_write.c' || echo '$(srcdir)/'`network_write.c
+
+liblightcomp_la-network_linux_sendfile.lo: network_linux_sendfile.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-network_linux_sendfile.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-network_linux_sendfile.Tpo" -c -o liblightcomp_la-network_linux_sendfile.lo `test -f 'network_linux_sendfile.c' || echo '$(srcdir)/'`network_linux_sendfile.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-network_linux_sendfile.Tpo" "$(DEPDIR)/liblightcomp_la-network_linux_sendfile.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-network_linux_sendfile.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='network_linux_sendfile.c' object='liblightcomp_la-network_linux_sendfile.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-network_linux_sendfile.lo `test -f 'network_linux_sendfile.c' || echo '$(srcdir)/'`network_linux_sendfile.c
+
+liblightcomp_la-network_freebsd_sendfile.lo: network_freebsd_sendfile.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-network_freebsd_sendfile.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-network_freebsd_sendfile.Tpo" -c -o liblightcomp_la-network_freebsd_sendfile.lo `test -f 'network_freebsd_sendfile.c' || echo '$(srcdir)/'`network_freebsd_sendfile.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-network_freebsd_sendfile.Tpo" "$(DEPDIR)/liblightcomp_la-network_freebsd_sendfile.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-network_freebsd_sendfile.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='network_freebsd_sendfile.c' object='liblightcomp_la-network_freebsd_sendfile.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-network_freebsd_sendfile.lo `test -f 'network_freebsd_sendfile.c' || echo '$(srcdir)/'`network_freebsd_sendfile.c
+
+liblightcomp_la-network_writev.lo: network_writev.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-network_writev.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-network_writev.Tpo" -c -o liblightcomp_la-network_writev.lo `test -f 'network_writev.c' || echo '$(srcdir)/'`network_writev.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-network_writev.Tpo" "$(DEPDIR)/liblightcomp_la-network_writev.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-network_writev.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='network_writev.c' object='liblightcomp_la-network_writev.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-network_writev.lo `test -f 'network_writev.c' || echo '$(srcdir)/'`network_writev.c
+
+liblightcomp_la-network_solaris_sendfilev.lo: network_solaris_sendfilev.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-network_solaris_sendfilev.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-network_solaris_sendfilev.Tpo" -c -o liblightcomp_la-network_solaris_sendfilev.lo `test -f 'network_solaris_sendfilev.c' || echo '$(srcdir)/'`network_solaris_sendfilev.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-network_solaris_sendfilev.Tpo" "$(DEPDIR)/liblightcomp_la-network_solaris_sendfilev.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-network_solaris_sendfilev.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='network_solaris_sendfilev.c' object='liblightcomp_la-network_solaris_sendfilev.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-network_solaris_sendfilev.lo `test -f 'network_solaris_sendfilev.c' || echo '$(srcdir)/'`network_solaris_sendfilev.c
+
+liblightcomp_la-network_openssl.lo: network_openssl.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-network_openssl.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-network_openssl.Tpo" -c -o liblightcomp_la-network_openssl.lo `test -f 'network_openssl.c' || echo '$(srcdir)/'`network_openssl.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-network_openssl.Tpo" "$(DEPDIR)/liblightcomp_la-network_openssl.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-network_openssl.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='network_openssl.c' object='liblightcomp_la-network_openssl.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-network_openssl.lo `test -f 'network_openssl.c' || echo '$(srcdir)/'`network_openssl.c
+
+liblightcomp_la-splaytree.lo: splaytree.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-splaytree.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-splaytree.Tpo" -c -o liblightcomp_la-splaytree.lo `test -f 'splaytree.c' || echo '$(srcdir)/'`splaytree.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-splaytree.Tpo" "$(DEPDIR)/liblightcomp_la-splaytree.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-splaytree.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='splaytree.c' object='liblightcomp_la-splaytree.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-splaytree.lo `test -f 'splaytree.c' || echo '$(srcdir)/'`splaytree.c
+
+mod_cml_la-mod_cml.lo: mod_cml.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_cml_la_CFLAGS) $(CFLAGS) -MT mod_cml_la-mod_cml.lo -MD -MP -MF "$(DEPDIR)/mod_cml_la-mod_cml.Tpo" -c -o mod_cml_la-mod_cml.lo `test -f 'mod_cml.c' || echo '$(srcdir)/'`mod_cml.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/mod_cml_la-mod_cml.Tpo" "$(DEPDIR)/mod_cml_la-mod_cml.Plo"; else rm -f "$(DEPDIR)/mod_cml_la-mod_cml.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='mod_cml.c' object='mod_cml_la-mod_cml.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_cml_la_CFLAGS) $(CFLAGS) -c -o mod_cml_la-mod_cml.lo `test -f 'mod_cml.c' || echo '$(srcdir)/'`mod_cml.c
+
+mod_cml_la-mod_cml_lua.lo: mod_cml_lua.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_cml_la_CFLAGS) $(CFLAGS) -MT mod_cml_la-mod_cml_lua.lo -MD -MP -MF "$(DEPDIR)/mod_cml_la-mod_cml_lua.Tpo" -c -o mod_cml_la-mod_cml_lua.lo `test -f 'mod_cml_lua.c' || echo '$(srcdir)/'`mod_cml_lua.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/mod_cml_la-mod_cml_lua.Tpo" "$(DEPDIR)/mod_cml_la-mod_cml_lua.Plo"; else rm -f "$(DEPDIR)/mod_cml_la-mod_cml_lua.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='mod_cml_lua.c' object='mod_cml_la-mod_cml_lua.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_cml_la_CFLAGS) $(CFLAGS) -c -o mod_cml_la-mod_cml_lua.lo `test -f 'mod_cml_lua.c' || echo '$(srcdir)/'`mod_cml_lua.c
+
+mod_cml_la-mod_cml_funcs.lo: mod_cml_funcs.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_cml_la_CFLAGS) $(CFLAGS) -MT mod_cml_la-mod_cml_funcs.lo -MD -MP -MF "$(DEPDIR)/mod_cml_la-mod_cml_funcs.Tpo" -c -o mod_cml_la-mod_cml_funcs.lo `test -f 'mod_cml_funcs.c' || echo '$(srcdir)/'`mod_cml_funcs.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/mod_cml_la-mod_cml_funcs.Tpo" "$(DEPDIR)/mod_cml_la-mod_cml_funcs.Plo"; else rm -f "$(DEPDIR)/mod_cml_la-mod_cml_funcs.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='mod_cml_funcs.c' object='mod_cml_la-mod_cml_funcs.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_cml_la_CFLAGS) $(CFLAGS) -c -o mod_cml_la-mod_cml_funcs.lo `test -f 'mod_cml_funcs.c' || echo '$(srcdir)/'`mod_cml_funcs.c
+
+mod_mysql_vhost_la-mod_mysql_vhost.lo: mod_mysql_vhost.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(mod_mysql_vhost_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT mod_mysql_vhost_la-mod_mysql_vhost.lo -MD -MP -MF "$(DEPDIR)/mod_mysql_vhost_la-mod_mysql_vhost.Tpo" -c -o mod_mysql_vhost_la-mod_mysql_vhost.lo `test -f 'mod_mysql_vhost.c' || echo '$(srcdir)/'`mod_mysql_vhost.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/mod_mysql_vhost_la-mod_mysql_vhost.Tpo" "$(DEPDIR)/mod_mysql_vhost_la-mod_mysql_vhost.Plo"; else rm -f "$(DEPDIR)/mod_mysql_vhost_la-mod_mysql_vhost.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='mod_mysql_vhost.c' object='mod_mysql_vhost_la-mod_mysql_vhost.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(mod_mysql_vhost_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o mod_mysql_vhost_la-mod_mysql_vhost.lo `test -f 'mod_mysql_vhost.c' || echo '$(srcdir)/'`mod_mysql_vhost.c
+
+mod_webdav_la-mod_webdav.lo: mod_webdav.c
+@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_webdav_la_CFLAGS) $(CFLAGS) -MT mod_webdav_la-mod_webdav.lo -MD -MP -MF "$(DEPDIR)/mod_webdav_la-mod_webdav.Tpo" -c -o mod_webdav_la-mod_webdav.lo `test -f 'mod_webdav.c' || echo '$(srcdir)/'`mod_webdav.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/mod_webdav_la-mod_webdav.Tpo" "$(DEPDIR)/mod_webdav_la-mod_webdav.Plo"; else rm -f "$(DEPDIR)/mod_webdav_la-mod_webdav.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='mod_webdav.c' object='mod_webdav_la-mod_webdav.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_webdav_la_CFLAGS) $(CFLAGS) -c -o mod_webdav_la-mod_webdav.lo `test -f 'mod_webdav.c' || echo '$(srcdir)/'`mod_webdav.c
+
+proc_open-proc_open.o: proc_open.c
+@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT proc_open-proc_open.o -MD -MP -MF "$(DEPDIR)/proc_open-proc_open.Tpo" -c -o proc_open-proc_open.o `test -f 'proc_open.c' || echo '$(srcdir)/'`proc_open.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/proc_open-proc_open.Tpo" "$(DEPDIR)/proc_open-proc_open.Po"; else rm -f "$(DEPDIR)/proc_open-proc_open.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='proc_open.c' object='proc_open-proc_open.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o proc_open-proc_open.o `test -f 'proc_open.c' || echo '$(srcdir)/'`proc_open.c
+
+proc_open-proc_open.obj: proc_open.c
+@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT proc_open-proc_open.obj -MD -MP -MF "$(DEPDIR)/proc_open-proc_open.Tpo" -c -o proc_open-proc_open.obj `if test -f 'proc_open.c'; then $(CYGPATH_W) 'proc_open.c'; else $(CYGPATH_W) '$(srcdir)/proc_open.c'; fi`; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/proc_open-proc_open.Tpo" "$(DEPDIR)/proc_open-proc_open.Po"; else rm -f "$(DEPDIR)/proc_open-proc_open.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='proc_open.c' object='proc_open-proc_open.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o proc_open-proc_open.obj `if test -f 'proc_open.c'; then $(CYGPATH_W) 'proc_open.c'; else $(CYGPATH_W) '$(srcdir)/proc_open.c'; fi`
+
+proc_open-buffer.o: buffer.c
+@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT proc_open-buffer.o -MD -MP -MF "$(DEPDIR)/proc_open-buffer.Tpo" -c -o proc_open-buffer.o `test -f 'buffer.c' || echo '$(srcdir)/'`buffer.c; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/proc_open-buffer.Tpo" "$(DEPDIR)/proc_open-buffer.Po"; else rm -f "$(DEPDIR)/proc_open-buffer.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='buffer.c' object='proc_open-buffer.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o proc_open-buffer.o `test -f 'buffer.c' || echo '$(srcdir)/'`buffer.c
+
+proc_open-buffer.obj: buffer.c
+@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT proc_open-buffer.obj -MD -MP -MF "$(DEPDIR)/proc_open-buffer.Tpo" -c -o proc_open-buffer.obj `if test -f 'buffer.c'; then $(CYGPATH_W) 'buffer.c'; else $(CYGPATH_W) '$(srcdir)/buffer.c'; fi`; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/proc_open-buffer.Tpo" "$(DEPDIR)/proc_open-buffer.Po"; else rm -f "$(DEPDIR)/proc_open-buffer.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='buffer.c' object='proc_open-buffer.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o proc_open-buffer.obj `if test -f 'buffer.c'; then $(CYGPATH_W) 'buffer.c'; else $(CYGPATH_W) '$(srcdir)/buffer.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+distclean-libtool:
+ -rm -f libtool
+uninstall-info-am:
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$tags $$unique; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ test -z "$(CTAGS_ARGS)$$tags$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$tags $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && cd $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) $$here
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \
+ list='$(DISTFILES)'; for file in $$list; do \
+ case $$file in \
+ $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \
+ $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \
+ esac; \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test "$$dir" != "$$file" && test "$$dir" != "."; then \
+ dir="/$$dir"; \
+ $(mkdir_p) "$(distdir)$$dir"; \
+ else \
+ dir=''; \
+ fi; \
+ if test -d $$d/$$file; then \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+ else \
+ test -f $(distdir)/$$file \
+ || cp -p $$d/$$file $(distdir)/$$file \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(PROGRAMS) $(HEADERS)
+install-binPROGRAMS: install-libLTLIBRARIES
+
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" "$(DESTDIR)$(sbindir)"; do \
+ test -z "$$dir" || $(mkdir_p) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libLTLIBRARIES \
+ clean-libtool clean-noinstPROGRAMS clean-sbinPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-libtool distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-exec-am: install-binPROGRAMS install-libLTLIBRARIES \
+ install-sbinPROGRAMS
+
+install-info: install-info-am
+
+install-man:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS uninstall-info-am \
+ uninstall-libLTLIBRARIES uninstall-sbinPROGRAMS
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+ clean-generic clean-libLTLIBRARIES clean-libtool \
+ clean-noinstPROGRAMS clean-sbinPROGRAMS ctags distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-binPROGRAMS install-data \
+ install-data-am install-exec install-exec-am install-info \
+ install-info-am install-libLTLIBRARIES install-man \
+ install-sbinPROGRAMS install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags uninstall uninstall-am uninstall-binPROGRAMS \
+ uninstall-info-am uninstall-libLTLIBRARIES \
+ uninstall-sbinPROGRAMS
+
+
+#simple_fcgi_SOURCES=simple-fcgi.c
+#simple_fcgi_LDADD=-lfcgi
+
+@CROSS_COMPILING_TRUE@configparser.c configparser.h:
+@CROSS_COMPILING_TRUE@mod_ssi_exprparser.c mod_ssi_exprparser.h:
+@CROSS_COMPILING_FALSE@configparser.y: lemon
+@CROSS_COMPILING_FALSE@mod_ssi_exprparser.y: lemon
+
+@CROSS_COMPILING_FALSE@configparser.c configparser.h: configparser.y
+@CROSS_COMPILING_FALSE@ rm -f configparser.h
+@CROSS_COMPILING_FALSE@ $(LEMON) -q $(srcdir)/configparser.y $(srcdir)/lempar.c
+
+@CROSS_COMPILING_FALSE@mod_ssi_exprparser.c mod_ssi_exprparser.h: mod_ssi_exprparser.y
+@CROSS_COMPILING_FALSE@ rm -f mod_ssi_exprparser.h
+@CROSS_COMPILING_FALSE@ $(LEMON) -q $(srcdir)/mod_ssi_exprparser.y $(srcdir)/lempar.c
+
+configfile.c: configparser.h
+mod_ssi_expr.c: mod_ssi_exprparser.h
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/array.c b/src/array.c
new file mode 100644
index 0000000..14afa28
--- /dev/null
+++ b/src/array.c
@@ -0,0 +1,369 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include <errno.h>
+#include <assert.h>
+
+#include "array.h"
+#include "buffer.h"
+
+array *array_init(void) {
+ array *a;
+
+ a = calloc(1, sizeof(*a));
+ assert(a);
+
+ a->next_power_of_2 = 1;
+
+ return a;
+}
+
+array *array_init_array(array *src) {
+ size_t i;
+ array *a = array_init();
+
+ a->used = src->used;
+ a->size = src->size;
+ a->next_power_of_2 = src->next_power_of_2;
+ a->unique_ndx = src->unique_ndx;
+
+ a->data = malloc(sizeof(*src->data) * src->size);
+ for (i = 0; i < src->size; i++) {
+ if (src->data[i]) a->data[i] = src->data[i]->copy(src->data[i]);
+ else a->data[i] = NULL;
+ }
+
+ a->sorted = malloc(sizeof(*src->sorted) * src->size);
+ memcpy(a->sorted, src->sorted, sizeof(*src->sorted) * src->size);
+ return a;
+}
+
+void array_free(array *a) {
+ size_t i;
+ if (!a) return;
+
+ if (!a->is_weakref) {
+ for (i = 0; i < a->size; i++) {
+ if (a->data[i]) a->data[i]->free(a->data[i]);
+ }
+ }
+
+ if (a->data) free(a->data);
+ if (a->sorted) free(a->sorted);
+
+ free(a);
+}
+
+void array_reset(array *a) {
+ size_t i;
+ if (!a) return;
+
+ if (!a->is_weakref) {
+ for (i = 0; i < a->used; i++) {
+ a->data[i]->reset(a->data[i]);
+ }
+ }
+
+ a->used = 0;
+}
+
+data_unset *array_pop(array *a) {
+ data_unset *du;
+
+ assert(a->used != 0);
+
+ a->used --;
+ du = a->data[a->used];
+ a->data[a->used] = NULL;
+
+ return du;
+}
+
+static int array_get_index(array *a, const char *key, size_t keylen, int *rndx) {
+ int ndx = -1;
+ int i, pos = 0;
+
+ if (key == NULL) return -1;
+
+ /* try to find the string */
+ for (i = pos = a->next_power_of_2 / 2; ; i >>= 1) {
+ int cmp;
+
+ if (pos < 0) {
+ pos += i;
+ } else if (pos >= (int)a->used) {
+ pos -= i;
+ } else {
+ cmp = buffer_caseless_compare(key, keylen, a->data[a->sorted[pos]]->key->ptr, a->data[a->sorted[pos]]->key->used);
+
+ if (cmp == 0) {
+ /* found */
+ ndx = a->sorted[pos];
+ break;
+ } else if (cmp < 0) {
+ pos -= i;
+ } else {
+ pos += i;
+ }
+ }
+ if (i == 0) break;
+ }
+
+ if (rndx) *rndx = pos;
+
+ return ndx;
+}
+
+data_unset *array_get_element(array *a, const char *key) {
+ int ndx;
+
+ if (-1 != (ndx = array_get_index(a, key, strlen(key) + 1, NULL))) {
+ /* found, leave here */
+
+ return a->data[ndx];
+ }
+
+ return NULL;
+}
+
+data_unset *array_get_unused_element(array *a, data_type_t t) {
+ data_unset *ds = NULL;
+
+ UNUSED(t);
+
+ if (a->size == 0) return NULL;
+
+ if (a->used == a->size) return NULL;
+
+ if (a->data[a->used]) {
+ ds = a->data[a->used];
+
+ a->data[a->used] = NULL;
+ }
+
+ return ds;
+}
+
+/* replace or insert data, return the old one with the same key */
+data_unset *array_replace(array *a, data_unset *du) {
+ int ndx;
+
+ if (-1 == (ndx = array_get_index(a, du->key->ptr, du->key->used, NULL))) {
+ array_insert_unique(a, du);
+ return NULL;
+ } else {
+ data_unset *old = a->data[ndx];
+ a->data[ndx] = du;
+ return old;
+ }
+}
+
+int array_insert_unique(array *a, data_unset *str) {
+ int ndx = -1;
+ int pos = 0;
+ size_t j;
+
+ /* generate unique index if neccesary */
+ if (str->key->used == 0 || str->is_index_key) {
+ buffer_copy_long(str->key, a->unique_ndx++);
+ str->is_index_key = 1;
+ }
+
+ /* try to find the string */
+ if (-1 != (ndx = array_get_index(a, str->key->ptr, str->key->used, &pos))) {
+ /* found, leave here */
+ if (a->data[ndx]->type == str->type) {
+ str->insert_dup(a->data[ndx], str);
+ } else {
+ fprintf(stderr, "a\n");
+ }
+ return 0;
+ }
+
+ /* insert */
+
+ if (a->used+1 > INT_MAX) {
+ /* we can't handle more then INT_MAX entries: see array_get_index() */
+ return -1;
+ }
+
+ if (a->size == 0) {
+ a->size = 16;
+ a->data = malloc(sizeof(*a->data) * a->size);
+ a->sorted = malloc(sizeof(*a->sorted) * a->size);
+ assert(a->data);
+ assert(a->sorted);
+ for (j = a->used; j < a->size; j++) a->data[j] = NULL;
+ } else if (a->size == a->used) {
+ a->size += 16;
+ a->data = realloc(a->data, sizeof(*a->data) * a->size);
+ a->sorted = realloc(a->sorted, sizeof(*a->sorted) * a->size);
+ assert(a->data);
+ assert(a->sorted);
+ for (j = a->used; j < a->size; j++) a->data[j] = NULL;
+ }
+
+ ndx = (int) a->used;
+
+ a->data[a->used++] = str;
+
+ if (pos != ndx &&
+ ((pos < 0) ||
+ buffer_caseless_compare(str->key->ptr, str->key->used, a->data[a->sorted[pos]]->key->ptr, a->data[a->sorted[pos]]->key->used) > 0)) {
+ pos++;
+ }
+
+ /* move everything on step to the right */
+ if (pos != ndx) {
+ memmove(a->sorted + (pos + 1), a->sorted + (pos), (ndx - pos) * sizeof(*a->sorted));
+ }
+
+ /* insert */
+ a->sorted[pos] = ndx;
+
+ if (a->next_power_of_2 == (size_t)ndx) a->next_power_of_2 <<= 1;
+
+ return 0;
+}
+
+void array_print_indent(int depth) {
+ int i;
+ for (i = 0; i < depth; i ++) {
+ fprintf(stderr, " ");
+ }
+}
+
+size_t array_get_max_key_length(array *a) {
+ size_t maxlen, i;
+
+ maxlen = 0;
+ for (i = 0; i < a->used; i ++) {
+ data_unset *du = a->data[i];
+ size_t len = strlen(du->key->ptr);
+
+ if (len > maxlen) {
+ maxlen = len;
+ }
+ }
+ return maxlen;
+}
+
+int array_print(array *a, int depth) {
+ size_t i;
+ size_t maxlen;
+ int oneline = 1;
+
+ if (a->used > 5) {
+ oneline = 0;
+ }
+ for (i = 0; i < a->used && oneline; i++) {
+ data_unset *du = a->data[i];
+ if (!du->is_index_key) {
+ oneline = 0;
+ break;
+ }
+ switch (du->type) {
+ case TYPE_INTEGER:
+ case TYPE_STRING:
+ case TYPE_COUNT:
+ break;
+ default:
+ oneline = 0;
+ break;
+ }
+ }
+ if (oneline) {
+ fprintf(stderr, "(");
+ for (i = 0; i < a->used; i++) {
+ data_unset *du = a->data[i];
+ if (i != 0) {
+ fprintf(stderr, ", ");
+ }
+ du->print(du, depth + 1);
+ }
+ fprintf(stderr, ")");
+ return 0;
+ }
+
+ maxlen = array_get_max_key_length(a);
+ fprintf(stderr, "(\n");
+ for (i = 0; i < a->used; i++) {
+ data_unset *du = a->data[i];
+ array_print_indent(depth + 1);
+ if (!du->is_index_key) {
+ int j;
+
+ if (i && (i % 5) == 0) {
+ fprintf(stderr, "# %zd\n", i);
+ array_print_indent(depth + 1);
+ }
+ fprintf(stderr, "\"%s\"", du->key->ptr);
+ for (j = maxlen - strlen(du->key->ptr); j > 0; j --) {
+ fprintf(stderr, " ");
+ }
+ fprintf(stderr, " => ");
+ }
+ du->print(du, depth + 1);
+ fprintf(stderr, ",\n");
+ }
+ if (!(i && (i - 1 % 5) == 0)) {
+ array_print_indent(depth + 1);
+ fprintf(stderr, "# %zd\n", i);
+ }
+ array_print_indent(depth);
+ fprintf(stderr, ")");
+
+ return 0;
+}
+
+#ifdef DEBUG_ARRAY
+int main (int argc, char **argv) {
+ array *a;
+ data_string *ds;
+ data_count *dc;
+
+ UNUSED(argc);
+ UNUSED(argv);
+
+ a = array_init();
+
+ ds = data_string_init();
+ buffer_copy_string(ds->key, "abc");
+ buffer_copy_string(ds->value, "alfrag");
+
+ array_insert_unique(a, (data_unset *)ds);
+
+ ds = data_string_init();
+ buffer_copy_string(ds->key, "abc");
+ buffer_copy_string(ds->value, "hameplman");
+
+ array_insert_unique(a, (data_unset *)ds);
+
+ ds = data_string_init();
+ buffer_copy_string(ds->key, "123");
+ buffer_copy_string(ds->value, "alfrag");
+
+ array_insert_unique(a, (data_unset *)ds);
+
+ dc = data_count_init();
+ buffer_copy_string(dc->key, "def");
+
+ array_insert_unique(a, (data_unset *)dc);
+
+ dc = data_count_init();
+ buffer_copy_string(dc->key, "def");
+
+ array_insert_unique(a, (data_unset *)dc);
+
+ array_print(a, 0);
+
+ array_free(a);
+
+ fprintf(stderr, "%d\n",
+ buffer_caseless_compare(CONST_STR_LEN("Content-Type"), CONST_STR_LEN("Content-type")));
+
+ return 0;
+}
+#endif
diff --git a/src/array.h b/src/array.h
new file mode 100644
index 0000000..27f27a4
--- /dev/null
+++ b/src/array.h
@@ -0,0 +1,150 @@
+#ifndef ARRAY_H
+#define ARRAY_H
+
+#include <stdlib.h>
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#ifdef HAVE_PCRE_H
+# include <pcre.h>
+#endif
+#include "buffer.h"
+
+#define DATA_IS_STRING(x) (x->type == TYPE_STRING)
+
+typedef enum { TYPE_UNSET, TYPE_STRING, TYPE_COUNT, TYPE_ARRAY, TYPE_INTEGER, TYPE_FASTCGI, TYPE_CONFIG } data_type_t;
+#define DATA_UNSET \
+ data_type_t type; \
+ buffer *key; \
+ int is_index_key; /* 1 if key is a array index (autogenerated keys) */ \
+ struct data_unset *(*copy)(const struct data_unset *src); \
+ void (* free)(struct data_unset *p); \
+ void (* reset)(struct data_unset *p); \
+ int (*insert_dup)(struct data_unset *dst, struct data_unset *src); \
+ void (*print)(const struct data_unset *p, int depth)
+
+typedef struct data_unset {
+ DATA_UNSET;
+} data_unset;
+
+typedef struct {
+ data_unset **data;
+
+ size_t *sorted;
+
+ size_t used;
+ size_t size;
+
+ size_t unique_ndx;
+
+ size_t next_power_of_2;
+ int is_weakref; /* data is weakref, don't bother the data */
+} array;
+
+typedef struct {
+ DATA_UNSET;
+
+ int count;
+} data_count;
+
+data_count *data_count_init(void);
+
+typedef struct {
+ DATA_UNSET;
+
+ buffer *value;
+} data_string;
+
+data_string *data_string_init(void);
+data_string *data_response_init(void);
+
+typedef struct {
+ DATA_UNSET;
+
+ array *value;
+} data_array;
+
+data_array *data_array_init(void);
+
+typedef enum { CONFIG_COND_UNSET, CONFIG_COND_EQ, CONFIG_COND_MATCH, CONFIG_COND_NE, CONFIG_COND_NOMATCH } config_cond_t;
+
+#define PATCHES NULL, "SERVERsocket", "HTTPurl", "HTTPhost", "HTTPreferer", "HTTPuseragent", "HTTPcookie", "HTTPremoteip"
+typedef enum {
+ COMP_UNSET,
+ COMP_SERVER_SOCKET, COMP_HTTP_URL, COMP_HTTP_HOST, COMP_HTTP_REFERER, COMP_HTTP_USERAGENT, COMP_HTTP_COOKIE, COMP_HTTP_REMOTEIP
+} comp_key_t;
+
+/* $HTTP["host"] == "incremental.home.kneschke.de" { ... }
+ * for print: comp_key op string
+ * for compare: comp cond string/regex
+ */
+
+typedef struct _data_config data_config;
+struct _data_config {
+ DATA_UNSET;
+
+ array *value;
+
+ buffer *comp_key;
+ comp_key_t comp;
+
+ config_cond_t cond;
+ buffer *op;
+
+ int context_ndx; /* more or less like an id */
+ array *childs;
+ /* nested */
+ data_config *parent;
+ /* for chaining only */
+ data_config *prev;
+ data_config *next;
+
+ buffer *string;
+#ifdef HAVE_PCRE_H
+ pcre *regex;
+ pcre_extra *regex_study;
+#endif
+};
+
+data_config *data_config_init(void);
+
+typedef struct {
+ DATA_UNSET;
+
+ int value;
+} data_integer;
+
+data_integer *data_integer_init(void);
+
+typedef struct {
+ DATA_UNSET;
+
+ buffer *host;
+
+ unsigned short port;
+
+ time_t disable_ts;
+ int is_disabled;
+ size_t balance;
+
+ int usage; /* fair-balancing needs the no. of connections active on this host */
+ int last_used_ndx; /* round robin */
+} data_fastcgi;
+
+data_fastcgi *data_fastcgi_init(void);
+
+array *array_init(void);
+array *array_init_array(array *a);
+void array_free(array *a);
+void array_reset(array *a);
+int array_insert_unique(array *a, data_unset *str);
+data_unset *array_pop(array *a);
+int array_print(array *a, int depth);
+data_unset *array_get_unused_element(array *a, data_type_t t);
+data_unset *array_get_element(array *a, const char *key);
+data_unset *array_replace(array *a, data_unset *du);
+int array_strcasecmp(const char *a, size_t a_len, const char *b, size_t b_len);
+void array_print_indent(int depth);
+size_t array_get_max_key_length(array *a);
+
+#endif
diff --git a/src/base.h b/src/base.h
new file mode 100644
index 0000000..98e23b8
--- /dev/null
+++ b/src/base.h
@@ -0,0 +1,606 @@
+#ifndef _BASE_H_
+#define _BASE_H_
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <limits.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+
+#include "buffer.h"
+#include "array.h"
+#include "chunk.h"
+#include "keyvalue.h"
+#include "settings.h"
+#include "fdevent.h"
+#include "sys-socket.h"
+#include "splaytree.h"
+
+
+#if defined HAVE_LIBSSL && defined HAVE_OPENSSL_SSL_H
+# define USE_OPENSSL
+# include <openssl/ssl.h>
+#endif
+
+#ifdef HAVE_FAM_H
+# include <fam.h>
+#endif
+
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+#ifndef O_LARGEFILE
+# define O_LARGEFILE 0
+#endif
+
+#ifndef SIZE_MAX
+# ifdef SIZE_T_MAX
+# define SIZE_MAX SIZE_T_MAX
+# else
+# define SIZE_MAX ((size_t)~0)
+# endif
+#endif
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX ((size_t)~0 >> 1)
+#endif
+
+#ifdef __APPLE__
+#include <crt_externs.h>
+#define environ (* _NSGetEnviron())
+#else
+extern char **environ;
+#endif
+
+/* for solaris 2.5 and NetBSD 1.3.x */
+#ifndef HAVE_SOCKLEN_T
+typedef int socklen_t;
+#endif
+
+/* solaris and NetBSD 1.3.x again */
+#if (!defined(HAVE_STDINT_H)) && (!defined(HAVE_INTTYPES_H)) && (!defined(uint32_t))
+# define uint32_t u_int32_t
+#endif
+
+
+#ifndef SHUT_WR
+# define SHUT_WR 1
+#endif
+
+#include "settings.h"
+
+typedef enum { T_CONFIG_UNSET,
+ T_CONFIG_STRING,
+ T_CONFIG_SHORT,
+ T_CONFIG_BOOLEAN,
+ T_CONFIG_ARRAY,
+ T_CONFIG_LOCAL,
+ T_CONFIG_DEPRECATED
+} config_values_type_t;
+
+typedef enum { T_CONFIG_SCOPE_UNSET,
+ T_CONFIG_SCOPE_SERVER,
+ T_CONFIG_SCOPE_CONNECTION
+} config_scope_type_t;
+
+typedef struct {
+ const char *key;
+ void *destination;
+
+ config_values_type_t type;
+ config_scope_type_t scope;
+} config_values_t;
+
+typedef enum { DIRECT, EXTERNAL } connection_type;
+
+typedef struct {
+ char *key;
+ connection_type type;
+ char *value;
+} request_handler;
+
+typedef struct {
+ char *key;
+ char *host;
+ unsigned short port;
+ int used;
+ short factor;
+} fcgi_connections;
+
+
+typedef union {
+#ifdef HAVE_IPV6
+ struct sockaddr_in6 ipv6;
+#endif
+ struct sockaddr_in ipv4;
+#ifdef HAVE_SYS_UN_H
+ struct sockaddr_un un;
+#endif
+ struct sockaddr plain;
+} sock_addr;
+
+/* fcgi_response_header contains ... */
+#define HTTP_STATUS BV(0)
+#define HTTP_CONNECTION BV(1)
+#define HTTP_CONTENT_LENGTH BV(2)
+#define HTTP_DATE BV(3)
+#define HTTP_LOCATION BV(4)
+
+typedef struct {
+ /** HEADER */
+ /* the request-line */
+ buffer *request;
+ buffer *uri;
+
+ buffer *orig_uri;
+
+ http_method_t http_method;
+ http_version_t http_version;
+
+ buffer *request_line;
+
+ /* strings to the header */
+ buffer *http_host; /* not alloced */
+ const char *http_range;
+ const char *http_content_type;
+ const char *http_if_modified_since;
+ const char *http_if_none_match;
+
+ array *headers;
+
+ /* CONTENT */
+ size_t content_length; /* returned by strtoul() */
+
+ /* internal representation */
+ int accept_encoding;
+
+ /* internal */
+ buffer *pathinfo;
+} request;
+
+typedef struct {
+ off_t content_length;
+ int keep_alive; /* used by the subrequests in proxy, cgi and fcgi to say the subrequest was keep-alive or not */
+
+ array *headers;
+
+ enum {
+ HTTP_TRANSFER_ENCODING_IDENTITY, HTTP_TRANSFER_ENCODING_CHUNKED
+ } transfer_encoding;
+} response;
+
+typedef struct {
+ buffer *scheme;
+ buffer *authority;
+ buffer *path;
+ buffer *path_raw;
+ buffer *query;
+} request_uri;
+
+typedef struct {
+ buffer *path;
+ buffer *basedir; /* path = "(basedir)(.*)" */
+
+ buffer *doc_root; /* path = doc_root + rel_path */
+ buffer *rel_path;
+
+ buffer *etag;
+} physical;
+
+typedef struct {
+ buffer *name;
+ buffer *etag;
+
+ struct stat st;
+
+ time_t stat_ts;
+
+#ifdef HAVE_FAM_H
+ int dir_version;
+ int dir_ndx;
+#endif
+
+ buffer *content_type;
+} stat_cache_entry;
+
+typedef struct {
+ splay_tree *files; /* the nodes of the tree are stat_cache_entry's */
+
+ buffer *dir_name; /* for building the dirname from the filename */
+#ifdef HAVE_FAM_H
+ splay_tree *dirs; /* the nodes of the tree are fam_dir_entry */
+
+ FAMConnection *fam;
+ int fam_fcce_ndx;
+#endif
+} stat_cache;
+
+typedef struct {
+ array *mimetypes;
+
+ /* virtual-servers */
+ buffer *document_root;
+ buffer *server_name;
+ buffer *error_handler;
+ buffer *server_tag;
+ buffer *dirlist_encoding;
+ buffer *errorfile_prefix;
+
+ unsigned short max_keep_alive_requests;
+ unsigned short max_keep_alive_idle;
+ unsigned short max_read_idle;
+ unsigned short max_write_idle;
+ unsigned short use_xattr;
+ unsigned short follow_symlink;
+ unsigned short range_requests;
+
+ /* debug */
+
+ unsigned short log_file_not_found;
+ unsigned short log_request_header;
+ unsigned short log_request_handling;
+ unsigned short log_response_header;
+ unsigned short log_condition_handling;
+
+
+ /* server wide */
+ buffer *ssl_pemfile;
+ buffer *ssl_ca_file;
+ unsigned short use_ipv6;
+ unsigned short is_ssl;
+ unsigned short allow_http11;
+ unsigned short force_lowercase_filenames; /* if the FS is case-insensitive, force all files to lower-case */
+ unsigned short max_request_size;
+
+ unsigned short kbytes_per_second; /* connection kb/s limit */
+
+ /* configside */
+ unsigned short global_kbytes_per_second; /* */
+
+ off_t global_bytes_per_second_cnt;
+ /* server-wide traffic-shaper
+ *
+ * each context has the counter which is inited once
+ * a second by the global_kbytes_per_second config-var
+ *
+ * as soon as global_kbytes_per_second gets below 0
+ * the connected conns are "offline" a little bit
+ *
+ * the problem:
+ * we somehow have to loose our "we are writable" signal
+ * on the way.
+ *
+ */
+ off_t *global_bytes_per_second_cnt_ptr; /* */
+
+#ifdef USE_OPENSSL
+ SSL_CTX *ssl_ctx;
+#endif
+} specific_config;
+
+/* the order of the items should be the same as they are processed
+ * read before write as we use this later */
+typedef enum {
+ CON_STATE_CONNECT,
+ CON_STATE_REQUEST_START,
+ CON_STATE_READ,
+ CON_STATE_REQUEST_END,
+ CON_STATE_READ_POST,
+ CON_STATE_HANDLE_REQUEST,
+ CON_STATE_RESPONSE_START,
+ CON_STATE_WRITE,
+ CON_STATE_RESPONSE_END,
+ CON_STATE_ERROR,
+ CON_STATE_CLOSE
+} connection_state_t;
+
+typedef enum { COND_RESULT_UNSET, COND_RESULT_FALSE, COND_RESULT_TRUE } cond_result_t;
+typedef struct {
+ cond_result_t result;
+ int patterncount;
+ int matches[3 * 10];
+ buffer *comp_value; /* just a pointer */
+} cond_cache_t;
+
+typedef struct {
+ connection_state_t state;
+
+ /* timestamps */
+ time_t read_idle_ts;
+ time_t close_timeout_ts;
+ time_t write_request_ts;
+
+ time_t connection_start;
+ time_t request_start;
+
+ struct timeval start_tv;
+
+ size_t request_count; /* number of requests handled in this connection */
+ size_t loops_per_request; /* to catch endless loops in a single request
+ *
+ * used by mod_rewrite, mod_fastcgi, ... and others
+ * this is self-protection
+ */
+
+ int fd; /* the FD for this connection */
+ int fde_ndx; /* index for the fdevent-handler */
+ int ndx; /* reverse mapping to server->connection[ndx] */
+
+ /* fd states */
+ int is_readable;
+ int is_writable;
+
+ int keep_alive; /* only request.c can enable it, all other just disable */
+
+ int file_started;
+ int file_finished;
+
+ chunkqueue *write_queue; /* a large queue for low-level write ( HTTP response ) [ file, mem ] */
+ chunkqueue *read_queue; /* a small queue for low-level read ( HTTP request ) [ mem ] */
+ chunkqueue *request_content_queue; /* takes request-content into tempfile if necessary [ tempfile, mem ]*/
+
+ int traffic_limit_reached;
+
+ off_t bytes_written; /* used by mod_accesslog, mod_rrd */
+ off_t bytes_written_cur_second; /* used by mod_accesslog, mod_rrd */
+ off_t bytes_read; /* used by mod_accesslog, mod_rrd */
+ off_t bytes_header;
+
+ int http_status;
+
+ sock_addr dst_addr;
+ buffer *dst_addr_buf;
+
+ /* request */
+ buffer *parse_request;
+ unsigned int parsed_response; /* bitfield which contains the important header-fields of the parsed response header */
+
+ request request;
+ request_uri uri;
+ physical physical;
+ response response;
+
+ size_t header_len;
+
+ buffer *authed_user;
+ array *environment; /* used to pass lighttpd internal stuff to the FastCGI/CGI apps, setenv does that */
+
+ /* response */
+ int got_response;
+
+ int in_joblist;
+
+ connection_type mode;
+
+ void **plugin_ctx; /* plugin connection specific config */
+
+ specific_config conf; /* global connection specific config */
+ cond_cache_t *cond_cache;
+
+ buffer *server_name;
+
+ /* error-handler */
+ buffer *error_handler;
+ int error_handler_saved_status;
+ int in_error_handler;
+
+ void *srv_socket; /* reference to the server-socket (typecast to server_socket) */
+
+#ifdef USE_OPENSSL
+ SSL *ssl;
+#endif
+} connection;
+
+typedef struct {
+ connection **ptr;
+ size_t size;
+ size_t used;
+} connections;
+
+
+#ifdef HAVE_IPV6
+typedef struct {
+ int family;
+ union {
+ struct in6_addr ipv6;
+ struct in_addr ipv4;
+ } addr;
+ char b2[INET6_ADDRSTRLEN + 1];
+ time_t ts;
+} inet_ntop_cache_type;
+#endif
+
+
+typedef struct {
+ buffer *uri;
+ time_t mtime;
+ int http_status;
+} realpath_cache_type;
+
+typedef struct {
+ time_t mtime; /* the key */
+ buffer *str; /* a buffer for the string represenation */
+} mtime_cache_type;
+
+typedef struct {
+ void *ptr;
+ size_t used;
+ size_t size;
+} buffer_plugin;
+
+typedef struct {
+ unsigned short port;
+ buffer *bindhost;
+
+ buffer *errorlog_file;
+ unsigned short errorlog_use_syslog;
+
+ unsigned short dont_daemonize;
+ buffer *changeroot;
+ buffer *username;
+ buffer *groupname;
+
+ buffer *pid_file;
+
+ buffer *event_handler;
+
+ buffer *modules_dir;
+ buffer *network_backend;
+ array *modules;
+ array *upload_tempdirs;
+
+ unsigned short max_worker;
+ unsigned short max_fds;
+ unsigned short max_conns;
+ unsigned short max_request_size;
+
+ unsigned short log_request_header_on_error;
+ unsigned short log_state_handling;
+
+ enum { STAT_CACHE_ENGINE_UNSET,
+ STAT_CACHE_ENGINE_NONE,
+ STAT_CACHE_ENGINE_SIMPLE,
+ STAT_CACHE_ENGINE_FAM
+ } stat_cache_engine;
+ unsigned short enable_cores;
+} server_config;
+
+typedef struct {
+ sock_addr addr;
+ int fd;
+ int fde_ndx;
+
+ buffer *ssl_pemfile;
+ buffer *ssl_ca_file;
+ unsigned short use_ipv6;
+ unsigned short is_ssl;
+
+ buffer *srv_token;
+
+#ifdef USE_OPENSSL
+ SSL_CTX *ssl_ctx;
+#endif
+} server_socket;
+
+typedef struct {
+ server_socket **ptr;
+
+ size_t size;
+ size_t used;
+} server_socket_array;
+
+typedef struct server {
+ server_socket_array srv_sockets;
+
+ /* the errorlog */
+ int errorlog_fd;
+ enum { ERRORLOG_STDERR, ERRORLOG_FILE, ERRORLOG_SYSLOG } errorlog_mode;
+ buffer *errorlog_buf;
+
+ fdevents *ev, *ev_ins;
+
+ buffer_plugin plugins;
+ void *plugin_slots;
+
+ /* counters */
+ int con_opened;
+ int con_read;
+ int con_written;
+ int con_closed;
+
+ int ssl_is_init;
+
+ int max_fds; /* max possible fds */
+ int cur_fds; /* currently used fds */
+ int want_fds; /* waiting fds */
+ int sockets_disabled;
+
+ size_t max_conns;
+
+ /* buffers */
+ buffer *parse_full_path;
+ buffer *response_header;
+ buffer *response_range;
+ buffer *tmp_buf;
+
+ buffer *tmp_chunk_len;
+
+ buffer *empty_string; /* is necessary for cond_match */
+
+ buffer *cond_check_buf;
+
+ /* caches */
+#ifdef HAVE_IPV6
+ inet_ntop_cache_type inet_ntop_cache[INET_NTOP_CACHE_MAX];
+#endif
+ mtime_cache_type mtime_cache[FILE_CACHE_MAX];
+
+ array *split_vals;
+
+ /* Timestamps */
+ time_t cur_ts;
+ time_t last_generated_date_ts;
+ time_t last_generated_debug_ts;
+ time_t startup_ts;
+
+ buffer *ts_debug_str;
+ buffer *ts_date_str;
+
+ /* config-file */
+ array *config;
+ array *config_touched;
+
+ array *config_context;
+ specific_config **config_storage;
+
+ server_config srvconf;
+
+ int config_deprecated;
+
+ connections *conns;
+ connections *joblist;
+ connections *fdwaitqueue;
+
+ stat_cache *stat_cache;
+
+ /**
+ * The status array can carry all the status information you want
+ * the key to the array is <module-prefix>.<name>
+ * and the values are counters
+ *
+ * example:
+ * fastcgi.backends = 10
+ * fastcgi.active-backends = 6
+ * fastcgi.backend.<key>.load = 24
+ * fastcgi.backend.<key>....
+ *
+ * fastcgi.backend.<key>.disconnects = ...
+ */
+ array *status;
+
+ fdevent_handler_t event_handler;
+
+ int (* network_backend_write)(struct server *srv, connection *con, int fd, chunkqueue *cq);
+ int (* network_backend_read)(struct server *srv, connection *con, int fd, chunkqueue *cq);
+#ifdef USE_OPENSSL
+ int (* network_ssl_backend_write)(struct server *srv, connection *con, SSL *ssl, chunkqueue *cq);
+ int (* network_ssl_backend_read)(struct server *srv, connection *con, SSL *ssl, chunkqueue *cq);
+#endif
+
+ uid_t uid;
+ gid_t gid;
+} server;
+
+
+#endif
diff --git a/src/bitset.c b/src/bitset.c
new file mode 100644
index 0000000..7fe5662
--- /dev/null
+++ b/src/bitset.c
@@ -0,0 +1,67 @@
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include "bitset.h"
+#include "buffer.h"
+
+#define BITSET_BITS \
+ ( CHAR_BIT * sizeof(size_t) )
+
+#define BITSET_MASK(pos) \
+ ( ((size_t)1) << ((pos) % BITSET_BITS) )
+
+#define BITSET_WORD(set, pos) \
+ ( (set)->bits[(pos) / BITSET_BITS] )
+
+#define BITSET_USED(nbits) \
+ ( ((nbits) + (BITSET_BITS - 1)) / BITSET_BITS )
+
+bitset *bitset_init(size_t nbits) {
+ bitset *set;
+
+ set = malloc(sizeof(*set));
+ assert(set);
+
+ set->bits = calloc(BITSET_USED(nbits), sizeof(*set->bits));
+ set->nbits = nbits;
+
+ assert(set->bits);
+
+ return set;
+}
+
+void bitset_reset(bitset *set) {
+ memset(set->bits, 0, BITSET_USED(set->nbits) * sizeof(*set->bits));
+}
+
+void bitset_free(bitset *set) {
+ free(set->bits);
+ free(set);
+}
+
+void bitset_clear_bit(bitset *set, size_t pos) {
+ if (pos >= set->nbits) {
+ SEGFAULT();
+ }
+
+ BITSET_WORD(set, pos) &= ~BITSET_MASK(pos);
+}
+
+void bitset_set_bit(bitset *set, size_t pos) {
+ if (pos >= set->nbits) {
+ SEGFAULT();
+ }
+
+ BITSET_WORD(set, pos) |= BITSET_MASK(pos);
+}
+
+int bitset_test_bit(bitset *set, size_t pos) {
+ if (pos >= set->nbits) {
+ SEGFAULT();
+ }
+
+ return (BITSET_WORD(set, pos) & BITSET_MASK(pos)) != 0;
+}
diff --git a/src/bitset.h b/src/bitset.h
new file mode 100644
index 0000000..467e13f
--- /dev/null
+++ b/src/bitset.h
@@ -0,0 +1,19 @@
+#ifndef _BITSET_H_
+#define _BITSET_H_
+
+#include <stddef.h>
+
+typedef struct {
+ size_t *bits;
+ size_t nbits;
+} bitset;
+
+bitset *bitset_init(size_t nbits);
+void bitset_reset(bitset *set);
+void bitset_free(bitset *set);
+
+void bitset_clear_bit(bitset *set, size_t pos);
+void bitset_set_bit(bitset *set, size_t pos);
+int bitset_test_bit(bitset *set, size_t pos);
+
+#endif
diff --git a/src/buffer.c b/src/buffer.c
new file mode 100644
index 0000000..40b8cb9
--- /dev/null
+++ b/src/buffer.c
@@ -0,0 +1,1023 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include <stdio.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "buffer.h"
+
+
+static const char hex_chars[] = "0123456789abcdef";
+
+
+/**
+ * init the buffer
+ *
+ */
+
+buffer* buffer_init(void) {
+ buffer *b;
+
+ b = malloc(sizeof(*b));
+ assert(b);
+
+ b->ptr = NULL;
+ b->size = 0;
+ b->used = 0;
+
+ return b;
+}
+
+buffer *buffer_init_buffer(buffer *src) {
+ buffer *b = buffer_init();
+ buffer_copy_string_buffer(b, src);
+ return b;
+}
+
+/**
+ * free the buffer
+ *
+ */
+
+void buffer_free(buffer *b) {
+ if (!b) return;
+
+ free(b->ptr);
+ free(b);
+}
+
+void buffer_reset(buffer *b) {
+ if (!b) return;
+
+ /* limit don't reuse buffer larger than ... bytes */
+ if (b->size > BUFFER_MAX_REUSE_SIZE) {
+ free(b->ptr);
+ b->ptr = NULL;
+ b->size = 0;
+ }
+
+ b->used = 0;
+}
+
+
+/**
+ *
+ * allocate (if neccessary) enough space for 'size' bytes and
+ * set the 'used' counter to 0
+ *
+ */
+
+#define BUFFER_PIECE_SIZE 64
+
+int buffer_prepare_copy(buffer *b, size_t size) {
+ if (!b) return -1;
+
+ if ((0 == b->size) ||
+ (size > b->size)) {
+ if (b->size) free(b->ptr);
+
+ b->size = size;
+
+ /* always allocate a multiply of BUFFER_PIECE_SIZE */
+ b->size += BUFFER_PIECE_SIZE - (b->size % BUFFER_PIECE_SIZE);
+
+ b->ptr = malloc(b->size);
+ assert(b->ptr);
+ }
+ b->used = 0;
+ return 0;
+}
+
+/**
+ *
+ * increase the internal buffer (if neccessary) to append another 'size' byte
+ * ->used isn't changed
+ *
+ */
+
+int buffer_prepare_append(buffer *b, size_t size) {
+ if (!b) return -1;
+
+ if (0 == b->size) {
+ b->size = size;
+
+ /* always allocate a multiply of BUFFER_PIECE_SIZE */
+ b->size += BUFFER_PIECE_SIZE - (b->size % BUFFER_PIECE_SIZE);
+
+ b->ptr = malloc(b->size);
+ b->used = 0;
+ assert(b->ptr);
+ } else if (b->used + size > b->size) {
+ b->size += size;
+
+ /* always allocate a multiply of BUFFER_PIECE_SIZE */
+ b->size += BUFFER_PIECE_SIZE - (b->size % BUFFER_PIECE_SIZE);
+
+ b->ptr = realloc(b->ptr, b->size);
+ assert(b->ptr);
+ }
+ return 0;
+}
+
+int buffer_copy_string(buffer *b, const char *s) {
+ size_t s_len;
+
+ if (!s || !b) return -1;
+
+ s_len = strlen(s) + 1;
+ buffer_prepare_copy(b, s_len);
+
+ memcpy(b->ptr, s, s_len);
+ b->used = s_len;
+
+ return 0;
+}
+
+int buffer_copy_string_len(buffer *b, const char *s, size_t s_len) {
+ if (!s || !b) return -1;
+#if 0
+ /* removed optimization as we have to keep the empty string
+ * in some cases for the config handling
+ *
+ * url.access-deny = ( "" )
+ */
+ if (s_len == 0) return 0;
+#endif
+ buffer_prepare_copy(b, s_len + 1);
+
+ memcpy(b->ptr, s, s_len);
+ b->ptr[s_len] = '\0';
+ b->used = s_len + 1;
+
+ return 0;
+}
+
+int buffer_copy_string_buffer(buffer *b, const buffer *src) {
+ if (!src) return -1;
+
+ if (src->used == 0) {
+ b->used = 0;
+ return 0;
+ }
+ return buffer_copy_string_len(b, src->ptr, src->used - 1);
+}
+
+int buffer_append_string(buffer *b, const char *s) {
+ size_t s_len;
+
+ if (!s || !b) return -1;
+
+ s_len = strlen(s);
+ buffer_prepare_append(b, s_len + 1);
+ if (b->used == 0)
+ b->used++;
+
+ memcpy(b->ptr + b->used - 1, s, s_len + 1);
+ b->used += s_len;
+
+ return 0;
+}
+
+int buffer_append_string_rfill(buffer *b, const char *s, size_t maxlen) {
+ size_t s_len;
+
+ if (!s || !b) return -1;
+
+ s_len = strlen(s);
+ buffer_prepare_append(b, maxlen + 1);
+ if (b->used == 0)
+ b->used++;
+
+ memcpy(b->ptr + b->used - 1, s, s_len);
+ if (maxlen > s_len) {
+ memset(b->ptr + b->used - 1 + s_len, ' ', maxlen - s_len);
+ }
+
+ b->used += maxlen;
+ b->ptr[b->used - 1] = '\0';
+ return 0;
+}
+
+/**
+ * append a string to the end of the buffer
+ *
+ * the resulting buffer is terminated with a '\0'
+ * s is treated as a un-terminated string (a \0 is handled a normal character)
+ *
+ * @param b a buffer
+ * @param s the string
+ * @param s_len size of the string (without the terminating \0)
+ */
+
+int buffer_append_string_len(buffer *b, const char *s, size_t s_len) {
+ if (!s || !b) return -1;
+ if (s_len == 0) return 0;
+
+ buffer_prepare_append(b, s_len + 1);
+ if (b->used == 0)
+ b->used++;
+
+ memcpy(b->ptr + b->used - 1, s, s_len);
+ b->used += s_len;
+ b->ptr[b->used - 1] = '\0';
+
+ return 0;
+}
+
+int buffer_append_string_buffer(buffer *b, const buffer *src) {
+ if (!src) return -1;
+ if (src->used == 0) return 0;
+
+ return buffer_append_string_len(b, src->ptr, src->used - 1);
+}
+
+int buffer_append_memory(buffer *b, const char *s, size_t s_len) {
+ if (!s || !b) return -1;
+ if (s_len == 0) return 0;
+
+ buffer_prepare_append(b, s_len);
+ memcpy(b->ptr + b->used, s, s_len);
+ b->used += s_len;
+
+ return 0;
+}
+
+int buffer_copy_memory(buffer *b, const char *s, size_t s_len) {
+ if (!s || !b) return -1;
+
+ b->used = 0;
+
+ return buffer_append_memory(b, s, s_len);
+}
+
+int buffer_append_long_hex(buffer *b, unsigned long value) {
+ char *buf;
+ int shift = 0;
+ unsigned long copy = value;
+
+ while (copy) {
+ copy >>= 4;
+ shift++;
+ }
+ if (shift == 0)
+ shift++;
+ if (shift & 0x01)
+ shift++;
+
+ buffer_prepare_append(b, shift + 1);
+ if (b->used == 0)
+ b->used++;
+ buf = b->ptr + (b->used - 1);
+ b->used += shift;
+
+ shift <<= 2;
+ while (shift > 0) {
+ shift -= 4;
+ *(buf++) = hex_chars[(value >> shift) & 0x0F];
+ }
+ *buf = '\0';
+
+ return 0;
+}
+
+int ltostr(char *buf, long val) {
+ char swap;
+ char *end;
+ int len = 1;
+
+ if (val < 0) {
+ len++;
+ *(buf++) = '-';
+ val = -val;
+ }
+
+ end = buf;
+ while (val > 9) {
+ *(end++) = '0' + (val % 10);
+ val = val / 10;
+ }
+ *(end) = '0' + val;
+ *(end + 1) = '\0';
+ len += end - buf;
+
+ while (buf < end) {
+ swap = *end;
+ *end = *buf;
+ *buf = swap;
+
+ buf++;
+ end--;
+ }
+
+ return len;
+}
+
+int buffer_append_long(buffer *b, long val) {
+ if (!b) return -1;
+
+ buffer_prepare_append(b, 32);
+ if (b->used == 0)
+ b->used++;
+
+ b->used += ltostr(b->ptr + (b->used - 1), val);
+ return 0;
+}
+
+int buffer_copy_long(buffer *b, long val) {
+ if (!b) return -1;
+
+ b->used = 0;
+ return buffer_append_long(b, val);
+}
+
+#if !defined(SIZEOF_LONG) || (SIZEOF_LONG != SIZEOF_OFF_T)
+int buffer_append_off_t(buffer *b, off_t val) {
+ char swap;
+ char *end;
+ char *start;
+ int len = 1;
+
+ if (!b) return -1;
+
+ buffer_prepare_append(b, 32);
+ if (b->used == 0)
+ b->used++;
+
+ start = b->ptr + (b->used - 1);
+ if (val < 0) {
+ len++;
+ *(start++) = '-';
+ val = -val;
+ }
+
+ end = start;
+ while (val > 9) {
+ *(end++) = '0' + (val % 10);
+ val = val / 10;
+ }
+ *(end) = '0' + val;
+ *(end + 1) = '\0';
+ len += end - start;
+
+ while (start < end) {
+ swap = *end;
+ *end = *start;
+ *start = swap;
+
+ start++;
+ end--;
+ }
+
+ b->used += len;
+ return 0;
+}
+
+int buffer_copy_off_t(buffer *b, off_t val) {
+ if (!b) return -1;
+
+ b->used = 0;
+ return buffer_append_off_t(b, val);
+}
+#endif /* !defined(SIZEOF_LONG) || (SIZEOF_LONG != SIZEOF_OFF_T) */
+
+char int2hex(char c) {
+ return hex_chars[(c & 0x0F)];
+}
+
+/* converts hex char (0-9, A-Z, a-z) to decimal.
+ * returns 0xFF on invalid input.
+ */
+char hex2int(unsigned char hex) {
+ hex = hex - '0';
+ if (hex > 9) {
+ hex = (hex + '0' - 1) | 0x20;
+ hex = hex - 'a' + 11;
+ }
+ if (hex > 15)
+ hex = 0xFF;
+
+ return hex;
+}
+
+
+/**
+ * init the buffer
+ *
+ */
+
+buffer_array* buffer_array_init(void) {
+ buffer_array *b;
+
+ b = malloc(sizeof(*b));
+
+ assert(b);
+ b->ptr = NULL;
+ b->size = 0;
+ b->used = 0;
+
+ return b;
+}
+
+void buffer_array_reset(buffer_array *b) {
+ size_t i;
+
+ if (!b) return;
+
+ /* if they are too large, reduce them */
+ for (i = 0; i < b->used; i++) {
+ buffer_reset(b->ptr[i]);
+ }
+
+ b->used = 0;
+}
+
+
+/**
+ * free the buffer_array
+ *
+ */
+
+void buffer_array_free(buffer_array *b) {
+ size_t i;
+ if (!b) return;
+
+ for (i = 0; i < b->size; i++) {
+ if (b->ptr[i]) buffer_free(b->ptr[i]);
+ }
+ free(b->ptr);
+ free(b);
+}
+
+buffer *buffer_array_append_get_buffer(buffer_array *b) {
+ size_t i;
+
+ if (b->size == 0) {
+ b->size = 16;
+ b->ptr = malloc(sizeof(*b->ptr) * b->size);
+ assert(b->ptr);
+ for (i = 0; i < b->size; i++) {
+ b->ptr[i] = NULL;
+ }
+ } else if (b->size == b->used) {
+ b->size += 16;
+ b->ptr = realloc(b->ptr, sizeof(*b->ptr) * b->size);
+ assert(b->ptr);
+ for (i = b->used; i < b->size; i++) {
+ b->ptr[i] = NULL;
+ }
+ }
+
+ if (b->ptr[b->used] == NULL) {
+ b->ptr[b->used] = buffer_init();
+ }
+
+ b->ptr[b->used]->used = 0;
+
+ return b->ptr[b->used++];
+}
+
+
+char * buffer_search_string_len(buffer *b, const char *needle, size_t len) {
+ size_t i;
+ if (len == 0) return NULL;
+ if (needle == NULL) return NULL;
+
+ if (b->used < len) return NULL;
+
+ for(i = 0; i < b->used - len; i++) {
+ if (0 == memcmp(b->ptr + i, needle, len)) {
+ return b->ptr + i;
+ }
+ }
+
+ return NULL;
+}
+
+buffer *buffer_init_string(const char *str) {
+ buffer *b = buffer_init();
+
+ buffer_copy_string(b, str);
+
+ return b;
+}
+
+int buffer_is_empty(buffer *b) {
+ return (b->used == 0);
+}
+
+/**
+ * check if two buffer contain the same data
+ *
+ * HISTORY: this function was pretty much optimized, but didn't handled
+ * alignment properly.
+ */
+
+int buffer_is_equal(buffer *a, buffer *b) {
+ if (a->used != b->used) return 0;
+ if (a->used == 0) return 1;
+
+ return (0 == strcmp(a->ptr, b->ptr));
+}
+
+int buffer_is_equal_string(buffer *a, const char *s, size_t b_len) {
+ buffer b;
+
+ b.ptr = (char *)s;
+ b.used = b_len + 1;
+
+ return buffer_is_equal(a, &b);
+}
+
+/* simple-assumption:
+ *
+ * most parts are equal and doing a case conversion needs time
+ *
+ */
+int buffer_caseless_compare(const char *a, size_t a_len, const char *b, size_t b_len) {
+ size_t ndx = 0, max_ndx;
+ size_t *al, *bl;
+ size_t mask = sizeof(*al) - 1;
+
+ al = (size_t *)a;
+ bl = (size_t *)b;
+
+ /* is the alignment correct ? */
+ if ( ((size_t)al & mask) == 0 &&
+ ((size_t)bl & mask) == 0 ) {
+
+ max_ndx = ((a_len < b_len) ? a_len : b_len) & ~mask;
+
+ for (; ndx < max_ndx; ndx += sizeof(*al)) {
+ if (*al != *bl) break;
+ al++; bl++;
+
+ }
+
+ }
+
+ a = (char *)al;
+ b = (char *)bl;
+
+ max_ndx = ((a_len < b_len) ? a_len : b_len);
+
+ for (; ndx < max_ndx; ndx++) {
+ char a1 = *a++, b1 = *b++;
+
+ if (a1 != b1) {
+ if ((a1 >= 'A' && a1 <= 'Z') && (b1 >= 'a' && b1 <= 'z'))
+ a1 |= 32;
+ else if ((a1 >= 'a' && a1 <= 'z') && (b1 >= 'A' && b1 <= 'Z'))
+ b1 |= 32;
+ if ((a1 - b1) != 0) return (a1 - b1);
+
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * check if the rightmost bytes of the string are equal.
+ *
+ *
+ */
+
+int buffer_is_equal_right_len(buffer *b1, buffer *b2, size_t len) {
+ /* no, len -> equal */
+ if (len == 0) return 1;
+
+ /* len > 0, but empty buffers -> not equal */
+ if (b1->used == 0 || b2->used == 0) return 0;
+
+ /* buffers too small -> not equal */
+ if (b1->used - 1 < len || b1->used - 1 < len) return 0;
+
+ if (0 == strncmp(b1->ptr + b1->used - 1 - len,
+ b2->ptr + b2->used - 1 - len, len)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+int buffer_copy_string_hex(buffer *b, const char *in, size_t in_len) {
+ size_t i;
+
+ /* BO protection */
+ if (in_len * 2 < in_len) return -1;
+
+ buffer_prepare_copy(b, in_len * 2 + 1);
+
+ for (i = 0; i < in_len; i++) {
+ b->ptr[b->used++] = hex_chars[(in[i] >> 4) & 0x0F];
+ b->ptr[b->used++] = hex_chars[in[i] & 0x0F];
+ }
+ b->ptr[b->used++] = '\0';
+
+ return 0;
+}
+
+const char encoded_chars_rel_uri_part[] = {
+ /*
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 00 - 0F control chars */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 10 - 1F */
+ 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, /* 20 - 2F space " # $ % & ' + , / */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, /* 30 - 3F : ; = ? @ < > */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40 - 4F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50 - 5F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* 70 - 7F DEL */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80 - 8F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 90 - 9F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* A0 - AF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* B0 - BF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* C0 - CF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* D0 - DF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* E0 - EF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* F0 - FF */
+};
+
+const char encoded_chars_rel_uri[] = {
+ /*
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 00 - 0F control chars */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 10 - 1F */
+ 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, /* 20 - 2F space " # $ % & ' + , / */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, /* 30 - 3F : ; = ? @ < > */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40 - 4F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50 - 5F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* 70 - 7F DEL */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80 - 8F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 90 - 9F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* A0 - AF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* B0 - BF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* C0 - CF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* D0 - DF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* E0 - EF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* F0 - FF */
+};
+
+const char encoded_chars_html[] = {
+ /*
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 00 - 0F control chars */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 10 - 1F */
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 - 2F & */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, /* 30 - 3F < > */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40 - 4F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50 - 5F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* 70 - 7F DEL */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80 - 8F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 90 - 9F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* A0 - AF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* B0 - BF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* C0 - CF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* D0 - DF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* E0 - EF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* F0 - FF */
+};
+
+const char encoded_chars_minimal_xml[] = {
+ /*
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 00 - 0F control chars */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 10 - 1F */
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 - 2F & */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, /* 30 - 3F < > */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40 - 4F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50 - 5F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* 70 - 7F DEL */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 - 8F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 - 9F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* A0 - AF */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* B0 - BF */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* C0 - CF */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* D0 - DF */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* E0 - EF */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* F0 - FF */
+};
+
+const char encoded_chars_hex[] = {
+ /*
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 00 - 0F control chars */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 10 - 1F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 20 - 2F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 30 - 3F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 40 - 4F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 50 - 5F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 60 - 6F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 70 - 7F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80 - 8F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 90 - 9F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* A0 - AF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* B0 - BF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* C0 - CF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* D0 - DF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* E0 - EF */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* F0 - FF */
+};
+
+
+int buffer_append_string_encoded(buffer *b, const char *s, size_t s_len, buffer_encoding_t encoding) {
+ unsigned char *ds, *d;
+ size_t d_len, ndx;
+ const char *map = NULL;
+
+ if (!s || !b) return -1;
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+ if (s_len == 0) return 0;
+
+ switch(encoding) {
+ case ENCODING_REL_URI:
+ map = encoded_chars_rel_uri;
+ break;
+ case ENCODING_REL_URI_PART:
+ map = encoded_chars_rel_uri_part;
+ break;
+ case ENCODING_HTML:
+ map = encoded_chars_html;
+ break;
+ case ENCODING_MINIMAL_XML:
+ map = encoded_chars_minimal_xml;
+ break;
+ case ENCODING_HEX:
+ map = encoded_chars_hex;
+ break;
+ case ENCODING_UNSET:
+ break;
+ }
+
+ assert(map != NULL);
+
+ /* count to-be-encoded-characters */
+ for (ds = (unsigned char *)s, d_len = 0, ndx = 0; ndx < s_len; ds++, ndx++) {
+ if (map[*ds]) {
+ switch(encoding) {
+ case ENCODING_REL_URI:
+ case ENCODING_REL_URI_PART:
+ d_len += 3;
+ break;
+ case ENCODING_HTML:
+ case ENCODING_MINIMAL_XML:
+ d_len += 6;
+ break;
+ case ENCODING_HEX:
+ d_len += 2;
+ break;
+ case ENCODING_UNSET:
+ break;
+ }
+ } else {
+ d_len ++;
+ }
+ }
+
+ buffer_prepare_append(b, d_len);
+
+ for (ds = (unsigned char *)s, d = (unsigned char *)b->ptr + b->used - 1, d_len = 0, ndx = 0; ndx < s_len; ds++, ndx++) {
+ if (map[*ds]) {
+ switch(encoding) {
+ case ENCODING_REL_URI:
+ case ENCODING_REL_URI_PART:
+ d[d_len++] = '%';
+ d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
+ d[d_len++] = hex_chars[(*ds) & 0x0F];
+ break;
+ case ENCODING_HTML:
+ case ENCODING_MINIMAL_XML:
+ d[d_len++] = '&';
+ d[d_len++] = '#';
+ d[d_len++] = 'x';
+ d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
+ d[d_len++] = hex_chars[(*ds) & 0x0F];
+ d[d_len++] = ';';
+ break;
+ case ENCODING_HEX:
+ d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
+ d[d_len++] = hex_chars[(*ds) & 0x0F];
+ break;
+ case ENCODING_UNSET:
+ break;
+ }
+ } else {
+ d[d_len++] = *ds;
+ }
+ }
+
+ /* terminate buffer and calculate new length */
+ b->ptr[b->used + d_len - 1] = '\0';
+
+ b->used += d_len;
+
+ return 0;
+}
+
+
+/* decodes url-special-chars inplace.
+ * replaces non-printable characters with '_'
+ */
+
+static int buffer_urldecode_internal(buffer *url, int is_query) {
+ unsigned char high, low;
+ const char *src;
+ char *dst;
+
+ if (!url || !url->ptr) return -1;
+
+ src = (const char*) url->ptr;
+ dst = (char*) url->ptr;
+
+ while ((*src) != '\0') {
+ if (is_query && *src == '+') {
+ *dst = ' ';
+ } else if (*src == '%') {
+ *dst = '%';
+
+ high = hex2int(*(src + 1));
+ if (high != 0xFF) {
+ low = hex2int(*(src + 2));
+ if (low != 0xFF) {
+ high = (high << 4) | low;
+
+ /* map control-characters out */
+ if (high < 32 || high == 127) high = '_';
+
+ *dst = high;
+ src += 2;
+ }
+ }
+ } else {
+ *dst = *src;
+ }
+
+ dst++;
+ src++;
+ }
+
+ *dst = '\0';
+ url->used = (dst - url->ptr) + 1;
+
+ return 0;
+}
+
+int buffer_urldecode_path(buffer *url) {
+ return buffer_urldecode_internal(url, 0);
+}
+
+int buffer_urldecode_query(buffer *url) {
+ return buffer_urldecode_internal(url, 1);
+}
+
+/* Remove "/../", "//", "/./" parts from path.
+ *
+ * /blah/.. gets /
+ * /blah/../foo gets /foo
+ * /abc/./xyz gets /abc/xyz
+ * /abc//xyz gets /abc/xyz
+ *
+ * NOTE: src and dest can point to the same buffer, in which case,
+ * the operation is performed in-place.
+ */
+
+int buffer_path_simplify(buffer *dest, buffer *src)
+{
+ int toklen;
+ char c, pre1;
+ char *start, *slash, *walk, *out;
+ unsigned short pre;
+
+ if (src == NULL || src->ptr == NULL || dest == NULL)
+ return -1;
+
+ if (src == dest)
+ buffer_prepare_append(dest, 1);
+ else
+ buffer_prepare_copy(dest, src->used + 1);
+
+ walk = src->ptr;
+ start = dest->ptr;
+ out = dest->ptr;
+ slash = dest->ptr;
+ while (*walk == ' ') {
+ walk++;
+ }
+
+ pre1 = *(walk++);
+ c = *(walk++);
+ pre = pre1;
+ if (pre1 != '/') {
+ pre = ('/' << 8) | pre1;
+ *(out++) = '/';
+ }
+ *(out++) = pre1;
+
+ if (pre1 == '\0') {
+ dest->used = (out - start) + 1;
+ return 0;
+ }
+
+ while (1) {
+ if (c == '/' || c == '\0') {
+ toklen = out - slash;
+ if (toklen == 3 && pre == (('.' << 8) | '.')) {
+ out = slash;
+ if (out > start) {
+ out--;
+ while (out > start && *out != '/') {
+ out--;
+ }
+ }
+
+ if (c == '\0')
+ out++;
+ } else if (toklen == 1 || pre == (('/' << 8) | '.')) {
+ out = slash;
+ if (c == '\0')
+ out++;
+ }
+
+ slash = out;
+ }
+
+ if (c == '\0')
+ break;
+
+ pre1 = c;
+ pre = (pre << 8) | pre1;
+ c = *walk;
+ *out = pre1;
+
+ out++;
+ walk++;
+ }
+
+ *out = '\0';
+ dest->used = (out - start) + 1;
+
+ return 0;
+}
+
+int light_isdigit(int c) {
+ return (c >= '0' && c <= '9');
+}
+
+int light_isxdigit(int c) {
+ if (light_isdigit(c)) return 1;
+
+ c |= 32;
+ return (c >= 'a' && c <= 'f');
+}
+
+int light_isalpha(int c) {
+ c |= 32;
+ return (c >= 'a' && c <= 'z');
+}
+
+int light_isalnum(int c) {
+ return light_isdigit(c) || light_isalpha(c);
+}
+
+int buffer_to_lower(buffer *b) {
+ char *c;
+
+ if (b->used == 0) return 0;
+
+ for (c = b->ptr; *c; c++) {
+ if (*c >= 'A' && *c <= 'Z') {
+ *c |= 32;
+ }
+ }
+
+ return 0;
+}
+
+
+int buffer_to_upper(buffer *b) {
+ char *c;
+
+ if (b->used == 0) return 0;
+
+ for (c = b->ptr; *c; c++) {
+ if (*c >= 'a' && *c <= 'z') {
+ *c &= ~32;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/buffer.h b/src/buffer.h
new file mode 100644
index 0000000..3ca22e5
--- /dev/null
+++ b/src/buffer.h
@@ -0,0 +1,130 @@
+#ifndef _BUFFER_H_
+#define _BUFFER_H_
+
+#include <stdlib.h>
+#include <sys/types.h>
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "settings.h"
+
+typedef struct {
+ char *ptr;
+
+ size_t used;
+ size_t size;
+} buffer;
+
+typedef struct {
+ buffer **ptr;
+
+ size_t used;
+ size_t size;
+} buffer_array;
+
+typedef struct {
+ char *ptr;
+
+ size_t offset; /* input-pointer */
+
+ size_t used; /* output-pointer */
+ size_t size;
+} read_buffer;
+
+buffer_array* buffer_array_init(void);
+void buffer_array_free(buffer_array *b);
+void buffer_array_reset(buffer_array *b);
+buffer *buffer_array_append_get_buffer(buffer_array *b);
+
+buffer* buffer_init(void);
+buffer* buffer_init_buffer(buffer *b);
+buffer* buffer_init_string(const char *str);
+void buffer_free(buffer *b);
+void buffer_reset(buffer *b);
+
+int buffer_prepare_copy(buffer *b, size_t size);
+int buffer_prepare_append(buffer *b, size_t size);
+
+int buffer_copy_string(buffer *b, const char *s);
+int buffer_copy_string_len(buffer *b, const char *s, size_t s_len);
+int buffer_copy_string_buffer(buffer *b, const buffer *src);
+int buffer_copy_string_hex(buffer *b, const char *in, size_t in_len);
+
+int buffer_copy_long(buffer *b, long val);
+
+int buffer_copy_memory(buffer *b, const char *s, size_t s_len);
+
+int buffer_append_string(buffer *b, const char *s);
+int buffer_append_string_len(buffer *b, const char *s, size_t s_len);
+int buffer_append_string_buffer(buffer *b, const buffer *src);
+int buffer_append_string_lfill(buffer *b, const char *s, size_t maxlen);
+int buffer_append_string_rfill(buffer *b, const char *s, size_t maxlen);
+
+int buffer_append_long_hex(buffer *b, unsigned long len);
+int buffer_append_long(buffer *b, long val);
+
+#if defined(SIZEOF_LONG) && (SIZEOF_LONG == SIZEOF_OFF_T)
+#define buffer_copy_off_t(x, y) buffer_copy_long(x, y)
+#define buffer_append_off_t(x, y) buffer_append_long(x, y)
+#else
+int buffer_copy_off_t(buffer *b, off_t val);
+int buffer_append_off_t(buffer *b, off_t val);
+#endif
+
+int buffer_append_memory(buffer *b, const char *s, size_t s_len);
+
+char * buffer_search_string_len(buffer *b, const char *needle, size_t len);
+
+int buffer_is_empty(buffer *b);
+int buffer_is_equal(buffer *a, buffer *b);
+int buffer_is_equal_right_len(buffer *a, buffer *b, size_t len);
+int buffer_is_equal_string(buffer *a, const char *s, size_t b_len);
+int buffer_caseless_compare(const char *a, size_t a_len, const char *b, size_t b_len);
+
+typedef enum {
+ ENCODING_UNSET,
+ ENCODING_REL_URI, /* for coding a rel-uri (/with space/and%percent) nicely as part of a href */
+ ENCODING_REL_URI_PART, /* same as ENC_REL_URL plus coding / too as %2F */
+ ENCODING_HTML, /* & becomes &amp; and so on */
+ ENCODING_MINIMAL_XML, /* minimal encoding for xml */
+ ENCODING_HEX /* encode string as hex */
+} buffer_encoding_t;
+
+int buffer_append_string_encoded(buffer *b, const char *s, size_t s_len, buffer_encoding_t encoding);
+
+int buffer_urldecode_path(buffer *url);
+int buffer_urldecode_query(buffer *url);
+int buffer_path_simplify(buffer *dest, buffer *src);
+
+int buffer_to_lower(buffer *b);
+int buffer_to_upper(buffer *b);
+
+/** deprecated */
+int ltostr(char *buf, long val);
+char hex2int(unsigned char c);
+char int2hex(char i);
+
+int light_isdigit(int c);
+int light_isxdigit(int c);
+int light_isalpha(int c);
+int light_isalnum(int c);
+
+#define BUFFER_APPEND_STRING_CONST(x, y) \
+ buffer_append_string_len(x, y, sizeof(y) - 1)
+
+#define BUFFER_COPY_STRING_CONST(x, y) \
+ buffer_copy_string_len(x, y, sizeof(y) - 1)
+
+#define BUFFER_APPEND_SLASH(x) \
+ if (x->used > 1 && x->ptr[x->used - 2] != '/') { BUFFER_APPEND_STRING_CONST(x, "/"); }
+
+#define CONST_STR_LEN(x) x, x ? sizeof(x) - 1 : 0
+#define CONST_BUF_LEN(x) x->ptr, x->used ? x->used - 1 : 0
+
+
+#define SEGFAULT() do { fprintf(stderr, "%s.%d: aborted\n", __FILE__, __LINE__); abort(); } while(0)
+#define UNUSED(x) ( (void)(x) )
+
+#endif
diff --git a/src/chunk.c b/src/chunk.c
new file mode 100644
index 0000000..3903428
--- /dev/null
+++ b/src/chunk.c
@@ -0,0 +1,385 @@
+/**
+ * the network chunk-API
+ *
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include "chunk.h"
+
+chunkqueue *chunkqueue_init(void) {
+ chunkqueue *cq;
+
+ cq = calloc(1, sizeof(*cq));
+
+ cq->first = NULL;
+ cq->last = NULL;
+
+ cq->unused = NULL;
+
+ return cq;
+}
+
+static chunk *chunk_init(void) {
+ chunk *c;
+
+ c = calloc(1, sizeof(*c));
+
+ c->mem = buffer_init();
+ c->file.name = buffer_init();
+ c->file.fd = -1;
+ c->file.mmap.start = MAP_FAILED;
+ c->next = NULL;
+
+ return c;
+}
+
+static void chunk_free(chunk *c) {
+ if (!c) return;
+
+ buffer_free(c->mem);
+ buffer_free(c->file.name);
+
+ free(c);
+}
+
+static void chunk_reset(chunk *c) {
+ if (!c) return;
+
+ buffer_reset(c->mem);
+
+ if (c->file.is_temp && !buffer_is_empty(c->file.name)) {
+ unlink(c->file.name->ptr);
+ }
+
+ buffer_reset(c->file.name);
+
+ if (c->file.fd != -1) {
+ close(c->file.fd);
+ c->file.fd = -1;
+ }
+ if (MAP_FAILED != c->file.mmap.start) {
+ munmap(c->file.mmap.start, c->file.mmap.length);
+ c->file.mmap.start = MAP_FAILED;
+ }
+}
+
+
+void chunkqueue_free(chunkqueue *cq) {
+ chunk *c, *pc;
+
+ if (!cq) return;
+
+ for (c = cq->first; c; ) {
+ pc = c;
+ c = c->next;
+ chunk_free(pc);
+ }
+
+ for (c = cq->unused; c; ) {
+ pc = c;
+ c = c->next;
+ chunk_free(pc);
+ }
+
+ free(cq);
+}
+
+static chunk *chunkqueue_get_unused_chunk(chunkqueue *cq) {
+ chunk *c;
+
+ /* check if we have a unused chunk */
+ if (!cq->unused) {
+ c = chunk_init();
+ } else {
+ /* take the first element from the list (a stack) */
+ c = cq->unused;
+ cq->unused = c->next;
+ c->next = NULL;
+ cq->unused_chunks--;
+ }
+
+ return c;
+}
+
+static int chunkqueue_prepend_chunk(chunkqueue *cq, chunk *c) {
+ c->next = cq->first;
+ cq->first = c;
+
+ if (cq->last == NULL) {
+ cq->last = c;
+ }
+
+ return 0;
+}
+
+static int chunkqueue_append_chunk(chunkqueue *cq, chunk *c) {
+ if (cq->last) {
+ cq->last->next = c;
+ }
+ cq->last = c;
+
+ if (cq->first == NULL) {
+ cq->first = c;
+ }
+
+ return 0;
+}
+
+void chunkqueue_reset(chunkqueue *cq) {
+ chunk *c;
+ /* move everything to the unused queue */
+
+ /* mark all read written */
+ for (c = cq->first; c; c = c->next) {
+ switch(c->type) {
+ case MEM_CHUNK:
+ c->offset = c->mem->used - 1;
+ break;
+ case FILE_CHUNK:
+ c->offset = c->file.length;
+ break;
+ default:
+ break;
+ }
+ }
+
+ chunkqueue_remove_finished_chunks(cq);
+ cq->bytes_in = 0;
+ cq->bytes_out = 0;
+}
+
+int chunkqueue_append_file(chunkqueue *cq, buffer *fn, off_t offset, off_t len) {
+ chunk *c;
+
+ if (len == 0) return 0;
+
+ c = chunkqueue_get_unused_chunk(cq);
+
+ c->type = FILE_CHUNK;
+
+ buffer_copy_string_buffer(c->file.name, fn);
+ c->file.start = offset;
+ c->file.length = len;
+ c->offset = 0;
+
+ chunkqueue_append_chunk(cq, c);
+
+ return 0;
+}
+
+int chunkqueue_append_buffer(chunkqueue *cq, buffer *mem) {
+ chunk *c;
+
+ if (mem->used == 0) return 0;
+
+ c = chunkqueue_get_unused_chunk(cq);
+ c->type = MEM_CHUNK;
+ c->offset = 0;
+ buffer_copy_string_buffer(c->mem, mem);
+
+ chunkqueue_append_chunk(cq, c);
+
+ return 0;
+}
+
+int chunkqueue_prepend_buffer(chunkqueue *cq, buffer *mem) {
+ chunk *c;
+
+ if (mem->used == 0) return 0;
+
+ c = chunkqueue_get_unused_chunk(cq);
+ c->type = MEM_CHUNK;
+ c->offset = 0;
+ buffer_copy_string_buffer(c->mem, mem);
+
+ chunkqueue_prepend_chunk(cq, c);
+
+ return 0;
+}
+
+int chunkqueue_append_mem(chunkqueue *cq, const char * mem, size_t len) {
+ chunk *c;
+
+ if (len == 0) return 0;
+
+ c = chunkqueue_get_unused_chunk(cq);
+ c->type = MEM_CHUNK;
+ c->offset = 0;
+ buffer_copy_string_len(c->mem, mem, len - 1);
+
+ chunkqueue_append_chunk(cq, c);
+
+ return 0;
+}
+
+buffer * chunkqueue_get_prepend_buffer(chunkqueue *cq) {
+ chunk *c;
+
+ c = chunkqueue_get_unused_chunk(cq);
+
+ c->type = MEM_CHUNK;
+ c->offset = 0;
+ buffer_reset(c->mem);
+
+ chunkqueue_prepend_chunk(cq, c);
+
+ return c->mem;
+}
+
+buffer *chunkqueue_get_append_buffer(chunkqueue *cq) {
+ chunk *c;
+
+ c = chunkqueue_get_unused_chunk(cq);
+
+ c->type = MEM_CHUNK;
+ c->offset = 0;
+ buffer_reset(c->mem);
+
+ chunkqueue_append_chunk(cq, c);
+
+ return c->mem;
+}
+
+int chunkqueue_set_tempdirs(chunkqueue *cq, array *tempdirs) {
+ if (!cq) return -1;
+
+ cq->tempdirs = tempdirs;
+
+ return 0;
+}
+
+chunk *chunkqueue_get_append_tempfile(chunkqueue *cq) {
+ chunk *c;
+ buffer *template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX");
+
+ c = chunkqueue_get_unused_chunk(cq);
+
+ c->type = FILE_CHUNK;
+ c->offset = 0;
+
+ if (cq->tempdirs && cq->tempdirs->used) {
+ size_t i;
+
+ /* we have several tempdirs, only if all of them fail we jump out */
+
+ for (i = 0; i < cq->tempdirs->used; i++) {
+ data_string *ds = (data_string *)cq->tempdirs->data[i];
+
+ buffer_copy_string_buffer(template, ds->value);
+ BUFFER_APPEND_SLASH(template);
+ BUFFER_APPEND_STRING_CONST(template, "lighttpd-upload-XXXXXX");
+
+ if (-1 != (c->file.fd = mkstemp(template->ptr))) {
+ /* only trigger the unlink if we created the temp-file successfully */
+ c->file.is_temp = 1;
+ break;
+ }
+ }
+ } else {
+ if (-1 != (c->file.fd = mkstemp(template->ptr))) {
+ /* only trigger the unlink if we created the temp-file successfully */
+ c->file.is_temp = 1;
+ }
+ }
+
+ buffer_copy_string_buffer(c->file.name, template);
+ c->file.length = 0;
+
+ chunkqueue_append_chunk(cq, c);
+
+ buffer_free(template);
+
+ return c;
+}
+
+
+off_t chunkqueue_length(chunkqueue *cq) {
+ off_t len = 0;
+ chunk *c;
+
+ for (c = cq->first; c; c = c->next) {
+ switch (c->type) {
+ case MEM_CHUNK:
+ len += c->mem->used ? c->mem->used - 1 : 0;
+ break;
+ case FILE_CHUNK:
+ len += c->file.length;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return len;
+}
+
+off_t chunkqueue_written(chunkqueue *cq) {
+ off_t len = 0;
+ chunk *c;
+
+ for (c = cq->first; c; c = c->next) {
+ switch (c->type) {
+ case MEM_CHUNK:
+ case FILE_CHUNK:
+ len += c->offset;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return len;
+}
+
+int chunkqueue_is_empty(chunkqueue *cq) {
+ return cq->first ? 0 : 1;
+}
+
+int chunkqueue_remove_finished_chunks(chunkqueue *cq) {
+ chunk *c;
+
+ for (c = cq->first; c; c = cq->first) {
+ int is_finished = 0;
+
+ switch (c->type) {
+ case MEM_CHUNK:
+ if (c->offset == (off_t)c->mem->used - 1) is_finished = 1;
+ break;
+ case FILE_CHUNK:
+ if (c->offset == c->file.length) is_finished = 1;
+ break;
+ default:
+ break;
+ }
+
+ if (!is_finished) break;
+
+ chunk_reset(c);
+
+ cq->first = c->next;
+ if (c == cq->last) cq->last = NULL;
+
+ /* keep at max 4 chunks in the 'unused'-cache */
+ if (cq->unused_chunks > 4) {
+ chunk_free(c);
+ } else {
+ c->next = cq->unused;
+ cq->unused = c;
+ cq->unused_chunks++;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/chunk.h b/src/chunk.h
new file mode 100644
index 0000000..ddc8617
--- /dev/null
+++ b/src/chunk.h
@@ -0,0 +1,69 @@
+#ifndef _CHUNK_H_
+#define _CHUNK_H_
+
+#include "buffer.h"
+#include "array.h"
+
+typedef struct chunk {
+ enum { UNUSED_CHUNK, MEM_CHUNK, FILE_CHUNK } type;
+
+ buffer *mem; /* either the storage of the mem-chunk or the read-ahead buffer */
+
+ struct {
+ /* filechunk */
+ buffer *name; /* name of the file */
+ off_t start; /* starting offset in the file */
+ off_t length; /* octets to send from the starting offset */
+
+ int fd;
+ struct {
+ char *start; /* the start pointer of the mmap'ed area */
+ size_t length; /* size of the mmap'ed area */
+ off_t offset; /* start is <n> octet away from the start of the file */
+ } mmap;
+
+ int is_temp; /* file is temporary and will be deleted if on cleanup */
+ } file;
+
+ off_t offset; /* octets sent from this chunk
+ the size of the chunk is either
+ - mem-chunk: mem->used - 1
+ - file-chunk: file.length
+ */
+
+ struct chunk *next;
+} chunk;
+
+typedef struct {
+ chunk *first;
+ chunk *last;
+
+ chunk *unused;
+ size_t unused_chunks;
+
+ array *tempdirs;
+
+ off_t bytes_in, bytes_out;
+} chunkqueue;
+
+chunkqueue *chunkqueue_init(void);
+int chunkqueue_set_tempdirs(chunkqueue *c, array *tempdirs);
+int chunkqueue_append_file(chunkqueue *c, buffer *fn, off_t offset, off_t len);
+int chunkqueue_append_mem(chunkqueue *c, const char *mem, size_t len);
+int chunkqueue_append_buffer(chunkqueue *c, buffer *mem);
+int chunkqueue_prepend_buffer(chunkqueue *c, buffer *mem);
+
+buffer * chunkqueue_get_append_buffer(chunkqueue *c);
+buffer * chunkqueue_get_prepend_buffer(chunkqueue *c);
+chunk * chunkqueue_get_append_tempfile(chunkqueue *cq);
+
+int chunkqueue_remove_finished_chunks(chunkqueue *cq);
+
+off_t chunkqueue_length(chunkqueue *c);
+off_t chunkqueue_written(chunkqueue *c);
+void chunkqueue_free(chunkqueue *c);
+void chunkqueue_reset(chunkqueue *c);
+
+int chunkqueue_is_empty(chunkqueue *c);
+
+#endif
diff --git a/src/configfile-glue.c b/src/configfile-glue.c
new file mode 100644
index 0000000..5c3c68b
--- /dev/null
+++ b/src/configfile-glue.c
@@ -0,0 +1,445 @@
+#include <string.h>
+
+#include "base.h"
+#include "buffer.h"
+#include "array.h"
+#include "log.h"
+#include "plugin.h"
+
+/**
+ * like all glue code this file contains functions which
+ * are the external interface of lighttpd. The functions
+ * are used by the server itself and the plugins.
+ *
+ * The main-goal is to have a small library in the end
+ * which is linked against both and which will define
+ * the interface itself in the end.
+ *
+ */
+
+
+/* handle global options */
+
+/* parse config array */
+int config_insert_values_internal(server *srv, array *ca, const config_values_t cv[]) {
+ size_t i;
+ data_unset *du;
+
+ for (i = 0; cv[i].key; i++) {
+
+ if (NULL == (du = array_get_element(ca, cv[i].key))) {
+ /* no found */
+
+ continue;
+ }
+
+ switch (cv[i].type) {
+ case T_CONFIG_ARRAY:
+ if (du->type == TYPE_ARRAY) {
+ size_t j;
+ data_array *da = (data_array *)du;
+
+ for (j = 0; j < da->value->used; j++) {
+ if (da->value->data[j]->type == TYPE_STRING) {
+ data_string *ds = data_string_init();
+
+ buffer_copy_string_buffer(ds->value, ((data_string *)(da->value->data[j]))->value);
+ if (!da->is_index_key) {
+ /* the id's were generated automaticly, as we copy now we might have to renumber them
+ * this is used to prepend server.modules by mod_indexfiles as it has to be loaded
+ * before mod_fastcgi and friends */
+ buffer_copy_string_buffer(ds->key, ((data_string *)(da->value->data[j]))->key);
+ }
+
+ array_insert_unique(cv[i].destination, (data_unset *)ds);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sssd",
+ "the key of and array can only be a string or a integer, variable:",
+ cv[i].key, "type:", da->value->data[j]->type);
+
+ return -1;
+ }
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss", "unexpected type for key: ", cv[i].key, "array of strings");
+
+ return -1;
+ }
+ break;
+ case T_CONFIG_STRING:
+ if (du->type == TYPE_STRING) {
+ data_string *ds = (data_string *)du;
+
+ buffer_copy_string_buffer(cv[i].destination, ds->value);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssss", "unexpected type for key: ", cv[i].key, "(string)", "\"...\"");
+
+ return -1;
+ }
+ break;
+ case T_CONFIG_SHORT:
+ switch(du->type) {
+ case TYPE_INTEGER: {
+ data_integer *di = (data_integer *)du;
+
+ *((unsigned short *)(cv[i].destination)) = di->value;
+ break;
+ }
+ case TYPE_STRING: {
+ data_string *ds = (data_string *)du;
+
+ log_error_write(srv, __FILE__, __LINE__, "ssb", "get a string but expected a short:", cv[i].key, ds->value);
+
+ return -1;
+ }
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssds", "unexpected type for key:", cv[i].key, du->type, "expected a integer, range 0 ... 65535");
+ return -1;
+ }
+ break;
+ case T_CONFIG_BOOLEAN:
+ if (du->type == TYPE_STRING) {
+ data_string *ds = (data_string *)du;
+
+ if (buffer_is_equal_string(ds->value, CONST_STR_LEN("enable"))) {
+ *((unsigned short *)(cv[i].destination)) = 1;
+ } else if (buffer_is_equal_string(ds->value, CONST_STR_LEN("disable"))) {
+ *((unsigned short *)(cv[i].destination)) = 0;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssbs", "ERROR: unexpected value for key:", cv[i].key, ds->value, "(enable|disable)");
+
+ return -1;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssss", "ERROR: unexpected type for key:", cv[i].key, "(string)", "\"(enable|disable)\"");
+
+ return -1;
+ }
+ break;
+ case T_CONFIG_LOCAL:
+ case T_CONFIG_UNSET:
+ break;
+ case T_CONFIG_DEPRECATED:
+ log_error_write(srv, __FILE__, __LINE__, "ssss", "ERROR: found deprecated key:", cv[i].key, "-", (char *)(cv[i].destination));
+
+ srv->config_deprecated = 1;
+
+ break;
+ }
+ }
+ return 0;
+}
+
+int config_insert_values_global(server *srv, array *ca, const config_values_t cv[]) {
+ size_t i;
+ data_unset *du;
+
+ for (i = 0; cv[i].key; i++) {
+ data_string *touched;
+
+ if (NULL == (du = array_get_element(ca, cv[i].key))) {
+ /* no found */
+
+ continue;
+ }
+
+ /* touched */
+ touched = data_string_init();
+
+ buffer_copy_string(touched->value, "");
+ buffer_copy_string_buffer(touched->key, du->key);
+
+ array_insert_unique(srv->config_touched, (data_unset *)touched);
+ }
+
+ return config_insert_values_internal(srv, ca, cv);
+}
+
+unsigned short sock_addr_get_port(sock_addr *addr) {
+#ifdef HAVE_IPV6
+ return ntohs(addr->plain.sa_family ? addr->ipv6.sin6_port : addr->ipv4.sin_port);
+#else
+ return ntohs(addr->ipv4.sin_port);
+#endif
+}
+
+static cond_result_t config_check_cond_cached(server *srv, connection *con, data_config *dc);
+
+static cond_result_t config_check_cond_nocache(server *srv, connection *con, data_config *dc) {
+ buffer *l;
+ server_socket *srv_sock = con->srv_socket;
+ /* check parent first */
+ if (dc->parent && dc->parent->context_ndx) {
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "go parent", dc->parent->key);
+ }
+ if (config_check_cond_cached(srv, con, dc->parent) == COND_RESULT_FALSE) {
+ return COND_RESULT_FALSE;
+ }
+ }
+
+ if (dc->prev) {
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "go prev", dc->prev->key);
+ }
+ /* make sure prev is checked first */
+ config_check_cond_cached(srv, con, dc->prev);
+ /* one of prev set me to FALSE */
+ if (COND_RESULT_FALSE == con->cond_cache[dc->context_ndx].result) {
+ return COND_RESULT_FALSE;
+ }
+ }
+
+ /* pass the rules */
+
+ switch (dc->comp) {
+ case COMP_HTTP_HOST: {
+ char *ck_colon = NULL, *val_colon = NULL;
+
+ if (!buffer_is_empty(con->uri.authority)) {
+
+ /*
+ * append server-port to the HTTP_POST if necessary
+ */
+
+ l = con->uri.authority;
+
+ switch(dc->cond) {
+ case CONFIG_COND_NE:
+ case CONFIG_COND_EQ:
+ ck_colon = strchr(dc->string->ptr, ':');
+ val_colon = strchr(l->ptr, ':');
+
+ if (ck_colon == val_colon) {
+ /* nothing to do with it */
+ break;
+ }
+ if (ck_colon) {
+ /* condition "host:port" but client send "host" */
+ buffer_copy_string_buffer(srv->cond_check_buf, l);
+ BUFFER_APPEND_STRING_CONST(srv->cond_check_buf, ":");
+ buffer_append_long(srv->cond_check_buf, sock_addr_get_port(&(srv_sock->addr)));
+ l = srv->cond_check_buf;
+ } else if (!ck_colon) {
+ /* condition "host" but client send "host:port" */
+ buffer_copy_string_len(srv->cond_check_buf, l->ptr, val_colon - l->ptr);
+ l = srv->cond_check_buf;
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ l = NULL;
+ }
+ break;
+ }
+ case COMP_HTTP_REMOTEIP: {
+ char *nm_slash;
+ /* handle remoteip limitations
+ *
+ * "10.0.0.1" is provided for all comparisions
+ *
+ * only for == and != we support
+ *
+ * "10.0.0.1/24"
+ */
+
+ if ((dc->cond == CONFIG_COND_EQ ||
+ dc->cond == CONFIG_COND_NE) &&
+ (con->dst_addr.plain.sa_family == AF_INET) &&
+ (NULL != (nm_slash = strchr(dc->string->ptr, '/')))) {
+ int nm_bits;
+ long nm;
+ char *err;
+ struct in_addr val_inp;
+
+ if (*(nm_slash+1) == '\0') {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: no number after / ", dc->string);
+
+ return COND_RESULT_FALSE;
+ }
+
+ nm_bits = strtol(nm_slash + 1, &err, 10);
+
+ if (*err) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "ERROR: non-digit found in netmask:", dc->string, *err);
+
+ return COND_RESULT_FALSE;
+ }
+
+ /* take IP convert to the native */
+ buffer_copy_string_len(srv->cond_check_buf, dc->string->ptr, nm_slash - dc->string->ptr);
+#ifdef __WIN32
+ if (INADDR_NONE == (val_inp.s_addr = inet_addr(srv->cond_check_buf->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: ip addr is invalid:", srv->cond_check_buf);
+
+ return COND_RESULT_FALSE;
+ }
+
+#else
+ if (0 == inet_aton(srv->cond_check_buf->ptr, &val_inp)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: ip addr is invalid:", srv->cond_check_buf);
+
+ return COND_RESULT_FALSE;
+ }
+#endif
+
+ /* build netmask */
+ nm = htonl(~((1 << (32 - nm_bits)) - 1));
+
+ if ((val_inp.s_addr & nm) == (con->dst_addr.ipv4.sin_addr.s_addr & nm)) {
+ return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_TRUE : COND_RESULT_FALSE;
+ } else {
+ return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_FALSE : COND_RESULT_TRUE;
+ }
+ } else {
+ l = con->dst_addr_buf;
+ }
+ break;
+ }
+ case COMP_HTTP_URL:
+ l = con->uri.path;
+ break;
+
+ case COMP_SERVER_SOCKET:
+ l = srv_sock->srv_token;
+ break;
+
+ case COMP_HTTP_REFERER: {
+ data_string *ds;
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Referer"))) {
+ l = ds->value;
+ } else {
+ l = srv->empty_string;
+ }
+ break;
+ }
+ case COMP_HTTP_COOKIE: {
+ data_string *ds;
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Cookie"))) {
+ l = ds->value;
+ } else {
+ l = srv->empty_string;
+ }
+ break;
+ }
+ case COMP_HTTP_USERAGENT: {
+ data_string *ds;
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "User-Agent"))) {
+ l = ds->value;
+ } else {
+ l = srv->empty_string;
+ }
+ break;
+ }
+
+ default:
+ return COND_RESULT_FALSE;
+ }
+
+ if (NULL == l) {
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "bsbs", dc->comp_key,
+ "(", l, ") compare to NULL");
+ }
+ return COND_RESULT_FALSE;
+ }
+
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "bsbsb", dc->comp_key,
+ "(", l, ") compare to ", dc->string);
+ }
+ switch(dc->cond) {
+ case CONFIG_COND_NE:
+ case CONFIG_COND_EQ:
+ if (buffer_is_equal(l, dc->string)) {
+ return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_TRUE : COND_RESULT_FALSE;
+ } else {
+ return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_FALSE : COND_RESULT_TRUE;
+ }
+ break;
+#ifdef HAVE_PCRE_H
+ case CONFIG_COND_NOMATCH:
+ case CONFIG_COND_MATCH: {
+ cond_cache_t *cache = &con->cond_cache[dc->context_ndx];
+ int n;
+
+#ifndef elementsof
+#define elementsof(x) (sizeof(x) / sizeof(x[0]))
+#endif
+ n = pcre_exec(dc->regex, dc->regex_study, l->ptr, l->used - 1, 0, 0,
+ cache->matches, elementsof(cache->matches));
+
+ if (n > 0) {
+ cache->patterncount = n;
+ cache->comp_value = l;
+ return (dc->cond == CONFIG_COND_MATCH) ? COND_RESULT_TRUE : COND_RESULT_FALSE;
+ } else {
+ /* cache is already cleared */
+ return (dc->cond == CONFIG_COND_MATCH) ? COND_RESULT_FALSE : COND_RESULT_TRUE;
+ }
+ break;
+ }
+#endif
+ default:
+ /* no way */
+ break;
+ }
+
+ return COND_RESULT_FALSE;
+}
+
+static cond_result_t config_check_cond_cached(server *srv, connection *con, data_config *dc) {
+ cond_cache_t *caches = con->cond_cache;
+
+ if (COND_RESULT_UNSET == caches[dc->context_ndx].result) {
+ if (COND_RESULT_TRUE == (caches[dc->context_ndx].result = config_check_cond_nocache(srv, con, dc))) {
+ if (dc->next) {
+ data_config *c;
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "setting remains of chaining to false");
+ }
+ for (c = dc->next; c; c = c->next) {
+ caches[c->context_ndx].result = COND_RESULT_FALSE;
+ }
+ }
+ }
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "dss", dc->context_ndx,
+ "(uncached) result:",
+ caches[dc->context_ndx].result == COND_RESULT_TRUE ? "true" : "false");
+ }
+ } else {
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "dss", dc->context_ndx,
+ "(cached) result:",
+ caches[dc->context_ndx].result == COND_RESULT_TRUE ? "true" : "false");
+ }
+ }
+ return caches[dc->context_ndx].result;
+}
+
+int config_check_cond(server *srv, connection *con, data_config *dc) {
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "=== start of condition block ===");
+ }
+ return (config_check_cond_cached(srv, con, dc) == COND_RESULT_TRUE);
+}
+
+int config_append_cond_match_buffer(connection *con, data_config *dc, buffer *buf, int n)
+{
+ cond_cache_t *cache = &con->cond_cache[dc->context_ndx];
+ if (n > cache->patterncount) {
+ return 0;
+ }
+
+ n <<= 1; /* n *= 2 */
+ buffer_append_string_len(buf,
+ cache->comp_value->ptr + cache->matches[n],
+ cache->matches[n + 1] - cache->matches[n]);
+ return 1;
+}
+
diff --git a/src/configfile.c b/src/configfile.c
new file mode 100644
index 0000000..acb1e0a
--- /dev/null
+++ b/src/configfile.c
@@ -0,0 +1,1194 @@
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "server.h"
+#include "log.h"
+#include "stream.h"
+#include "plugin.h"
+#ifdef USE_LICENSE
+#include "license.h"
+#endif
+
+#include "configparser.h"
+#include "configfile.h"
+#include "proc_open.h"
+
+
+static int config_insert(server *srv) {
+ size_t i;
+ int ret = 0;
+ buffer *stat_cache_string;
+
+ config_values_t cv[] = {
+ { "server.bind", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 0 */
+ { "server.errorlog", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 1 */
+ { "server.errorfile-prefix", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 2 */
+ { "server.chroot", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 3 */
+ { "server.username", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 4 */
+ { "server.groupname", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 5 */
+ { "server.port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, /* 6 */
+ { "server.tag", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
+ { "server.use-ipv6", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
+ { "server.modules", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_SERVER }, /* 9 */
+
+ { "server.event-handler", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 10 */
+ { "server.pid-file", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 11 */
+ { "server.max-request-size", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
+ { "server.max-worker", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, /* 13 */
+ { "server.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 14 */
+ { "server.force-lowercase-filenames", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 15 */
+ { "debug.log-condition-handling", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 16 */
+ { "server.max-keep-alive-requests", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 17 */
+ { "server.name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 18 */
+ { "server.max-keep-alive-idle", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 19 */
+
+ { "server.max-read-idle", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 20 */
+ { "server.max-write-idle", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 21 */
+ { "server.error-handler-404", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 22 */
+ { "server.max-fds", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, /* 23 */
+ { "server.follow-symlink", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 24 */
+ { "server.kbytes-per-second", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 25 */
+ { "connection.kbytes-per-second", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 26 */
+ { "mimetype.use-xattr", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 27 */
+ { "mimetype.assign", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 28 */
+ { "ssl.pemfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 29 */
+
+ { "ssl.engine", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 30 */
+
+ { "debug.log-file-not-found", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 31 */
+ { "debug.log-request-handling", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 32 */
+ { "debug.log-response-header", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 33 */
+ { "debug.log-request-header", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 34 */
+
+ { "server.protocol-http11", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 35 */
+ { "debug.log-request-header-on-error", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 36 */
+ { "debug.log-state-handling", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 37 */
+ { "ssl.ca-file", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 38 */
+
+ { "server.errorlog-use-syslog", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 39 */
+ { "server.range-requests", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 40 */
+ { "server.stat-cache-engine", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 41 */
+ { "server.max-connections", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, /* 42 */
+ { "server.network-backend", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 43 */
+ { "server.upload-dirs", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 44 */
+ { "server.core-files", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 45 */
+
+ { "server.host", "use server.bind instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.docroot", "use server.document-root instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.virtual-root", "load mod_simple_vhost and use simple-vhost.server-root instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.virtual-default-host", "load mod_simple_vhost and use simple-vhost.default-host instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.virtual-docroot", "load mod_simple_vhost and use simple-vhost.document-root instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.userid", "use server.username instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.groupid", "use server.groupname instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.use-keep-alive", "use server.max-keep-alive-requests = 0 instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.force-lower-case-files", "use server.force-lowercase-filenames instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+
+ /* 0 */
+ cv[0].destination = srv->srvconf.bindhost;
+ cv[1].destination = srv->srvconf.errorlog_file;
+ cv[3].destination = srv->srvconf.changeroot;
+ cv[4].destination = srv->srvconf.username;
+ cv[5].destination = srv->srvconf.groupname;
+ cv[6].destination = &(srv->srvconf.port);
+
+ cv[9].destination = srv->srvconf.modules;
+ cv[10].destination = srv->srvconf.event_handler;
+ cv[11].destination = srv->srvconf.pid_file;
+
+ cv[13].destination = &(srv->srvconf.max_worker);
+ cv[23].destination = &(srv->srvconf.max_fds);
+ cv[36].destination = &(srv->srvconf.log_request_header_on_error);
+ cv[37].destination = &(srv->srvconf.log_state_handling);
+
+ cv[39].destination = &(srv->srvconf.errorlog_use_syslog);
+
+ stat_cache_string = buffer_init();
+ cv[41].destination = stat_cache_string;
+ cv[43].destination = srv->srvconf.network_backend;
+ cv[44].destination = srv->srvconf.upload_tempdirs;
+ cv[45].destination = &(srv->srvconf.enable_cores);
+
+ cv[42].destination = &(srv->srvconf.max_conns);
+ cv[12].destination = &(srv->srvconf.max_request_size);
+ srv->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ assert(srv->config_storage);
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ specific_config *s;
+
+ s = calloc(1, sizeof(specific_config));
+ assert(s);
+ s->document_root = buffer_init();
+ s->mimetypes = array_init();
+ s->server_name = buffer_init();
+ s->ssl_pemfile = buffer_init();
+ s->ssl_ca_file = buffer_init();
+ s->error_handler = buffer_init();
+ s->server_tag = buffer_init();
+ s->errorfile_prefix = buffer_init();
+ s->max_keep_alive_requests = 16;
+ s->max_keep_alive_idle = 5;
+ s->max_read_idle = 60;
+ s->max_write_idle = 360;
+ s->use_xattr = 0;
+ s->is_ssl = 0;
+ s->use_ipv6 = 0;
+ s->follow_symlink = 1;
+ s->kbytes_per_second = 0;
+ s->allow_http11 = 1;
+ s->range_requests = 1;
+ s->force_lowercase_filenames = 0;
+ s->global_kbytes_per_second = 0;
+ s->global_bytes_per_second_cnt = 0;
+ s->global_bytes_per_second_cnt_ptr = &s->global_bytes_per_second_cnt;
+
+ cv[2].destination = s->errorfile_prefix;
+
+ cv[7].destination = s->server_tag;
+ cv[8].destination = &(s->use_ipv6);
+
+
+ /* 13 max-worker */
+ cv[14].destination = s->document_root;
+ cv[15].destination = &(s->force_lowercase_filenames);
+ cv[16].destination = &(s->log_condition_handling);
+ cv[17].destination = &(s->max_keep_alive_requests);
+ cv[18].destination = s->server_name;
+ cv[19].destination = &(s->max_keep_alive_idle);
+ cv[20].destination = &(s->max_read_idle);
+ cv[21].destination = &(s->max_write_idle);
+ cv[22].destination = s->error_handler;
+ cv[24].destination = &(s->follow_symlink);
+ /* 23 -> max-fds */
+ cv[25].destination = &(s->global_kbytes_per_second);
+ cv[26].destination = &(s->kbytes_per_second);
+ cv[27].destination = &(s->use_xattr);
+ cv[28].destination = s->mimetypes;
+ cv[29].destination = s->ssl_pemfile;
+ cv[30].destination = &(s->is_ssl);
+
+ cv[31].destination = &(s->log_file_not_found);
+ cv[32].destination = &(s->log_request_handling);
+ cv[33].destination = &(s->log_response_header);
+ cv[34].destination = &(s->log_request_header);
+
+ cv[35].destination = &(s->allow_http11);
+ cv[38].destination = s->ssl_ca_file;
+ cv[40].destination = &(s->range_requests);
+
+ srv->config_storage[i] = s;
+
+ if (0 != (ret = config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv))) {
+ break;
+ }
+ }
+
+ if (buffer_is_empty(stat_cache_string)) {
+ srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE;
+ } else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("simple"))) {
+ srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE;
+ } else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("fam"))) {
+ srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_FAM;
+ } else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("disable"))) {
+ srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_NONE;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "server.stat-cache-engine can be one of \"disable\", \"simple\", \"fam\", but not:", stat_cache_string);
+ ret = HANDLER_ERROR;
+ }
+
+ buffer_free(stat_cache_string);
+
+ return ret;
+
+}
+
+
+#define PATCH(x) con->conf.x = s->x
+int config_setup_connection(server *srv, connection *con) {
+ specific_config *s = srv->config_storage[0];
+
+ PATCH(allow_http11);
+ PATCH(mimetypes);
+ PATCH(document_root);
+ PATCH(max_keep_alive_requests);
+ PATCH(max_keep_alive_idle);
+ PATCH(max_read_idle);
+ PATCH(max_write_idle);
+ PATCH(use_xattr);
+ PATCH(error_handler);
+ PATCH(errorfile_prefix);
+ PATCH(follow_symlink);
+ PATCH(server_tag);
+ PATCH(kbytes_per_second);
+ PATCH(global_kbytes_per_second);
+ PATCH(global_bytes_per_second_cnt);
+
+ con->conf.global_bytes_per_second_cnt_ptr = &s->global_bytes_per_second_cnt;
+ buffer_copy_string_buffer(con->server_name, s->server_name);
+
+ PATCH(log_request_header);
+ PATCH(log_response_header);
+ PATCH(log_request_handling);
+ PATCH(log_condition_handling);
+ PATCH(log_file_not_found);
+
+ PATCH(range_requests);
+ PATCH(force_lowercase_filenames);
+ PATCH(is_ssl);
+
+ PATCH(ssl_pemfile);
+ PATCH(ssl_ca_file);
+ return 0;
+}
+
+int config_patch_connection(server *srv, connection *con, comp_key_t comp) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ specific_config *s = srv->config_storage[i];
+
+ /* not our stage */
+ if (comp != dc->comp) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.document-root"))) {
+ PATCH(document_root);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.range-requests"))) {
+ PATCH(range_requests);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.error-handler-404"))) {
+ PATCH(error_handler);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.errorfile-prefix"))) {
+ PATCH(errorfile_prefix);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("mimetype.assign"))) {
+ PATCH(mimetypes);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-keep-alive-requests"))) {
+ PATCH(max_keep_alive_requests);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-keep-alive-idle"))) {
+ PATCH(max_keep_alive_idle);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-write-idle"))) {
+ PATCH(max_write_idle);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-read-idle"))) {
+ PATCH(max_read_idle);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("mimetype.use-xattr"))) {
+ PATCH(use_xattr);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssl.pemfile"))) {
+ PATCH(ssl_pemfile);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssl.ca-file"))) {
+ PATCH(ssl_ca_file);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssl.engine"))) {
+ PATCH(is_ssl);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.follow-symlink"))) {
+ PATCH(follow_symlink);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.name"))) {
+ buffer_copy_string_buffer(con->server_name, s->server_name);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.tag"))) {
+ PATCH(server_tag);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("connection.kbytes-per-second"))) {
+ PATCH(kbytes_per_second);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-request-handling"))) {
+ PATCH(log_request_handling);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-request-header"))) {
+ PATCH(log_request_header);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-response-header"))) {
+ PATCH(log_response_header);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-condition-handling"))) {
+ PATCH(log_condition_handling);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-file-not-found"))) {
+ PATCH(log_file_not_found);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.protocol-http11"))) {
+ PATCH(allow_http11);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.force-lowercase-filenames"))) {
+ PATCH(force_lowercase_filenames);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.kbytes-per-second"))) {
+ PATCH(global_kbytes_per_second);
+ PATCH(global_bytes_per_second_cnt);
+ con->conf.global_bytes_per_second_cnt_ptr = &s->global_bytes_per_second_cnt;
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+typedef struct {
+ int foo;
+ int bar;
+
+ const buffer *source;
+ const char *input;
+ size_t offset;
+ size_t size;
+
+ int line_pos;
+ int line;
+
+ int in_key;
+ int in_brace;
+ int in_cond;
+} tokenizer_t;
+
+#if 0
+static int tokenizer_open(server *srv, tokenizer_t *t, buffer *basedir, const char *fn) {
+ if (buffer_is_empty(basedir) &&
+ (fn[0] == '/' || fn[0] == '\\') &&
+ (fn[0] == '.' && (fn[1] == '/' || fn[1] == '\\'))) {
+ t->file = buffer_init_string(fn);
+ } else {
+ t->file = buffer_init_buffer(basedir);
+ buffer_append_string(t->file, fn);
+ }
+
+ if (0 != stream_open(&(t->s), t->file)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "opening configfile ", t->file, "failed:", strerror(errno));
+ buffer_free(t->file);
+ return -1;
+ }
+
+ t->input = t->s.start;
+ t->offset = 0;
+ t->size = t->s.size;
+ t->line = 1;
+ t->line_pos = 1;
+
+ t->in_key = 1;
+ t->in_brace = 0;
+ t->in_cond = 0;
+ return 0;
+}
+
+static int tokenizer_close(server *srv, tokenizer_t *t) {
+ UNUSED(srv);
+
+ buffer_free(t->file);
+ return stream_close(&(t->s));
+}
+#endif
+static int config_skip_newline(tokenizer_t *t) {
+ int skipped = 1;
+ assert(t->input[t->offset] == '\r' || t->input[t->offset] == '\n');
+ if (t->input[t->offset] == '\r' && t->input[t->offset + 1] == '\n') {
+ skipped ++;
+ t->offset ++;
+ }
+ t->offset ++;
+ return skipped;
+}
+
+static int config_skip_comment(tokenizer_t *t) {
+ int i;
+ assert(t->input[t->offset] == '#');
+ for (i = 1; t->input[t->offset + i] &&
+ (t->input[t->offset + i] != '\n' && t->input[t->offset + i] != '\r');
+ i++);
+ t->offset += i;
+ return i;
+}
+
+static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer *token) {
+ int tid = 0;
+ size_t i;
+
+ for (tid = 0; tid == 0 && t->offset < t->size && t->input[t->offset] ; ) {
+ char c = t->input[t->offset];
+ const char *start = NULL;
+
+ switch (c) {
+ case '=':
+ if (t->in_brace) {
+ if (t->input[t->offset + 1] == '>') {
+ t->offset += 2;
+
+ buffer_copy_string(token, "=>");
+
+ tid = TK_ARRAY_ASSIGN;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbsdsds",
+ "source:", t->source,
+ "line:", t->line, "pos:", t->line_pos,
+ "use => for assignments in arrays");
+ return -1;
+ }
+ } else if (t->in_cond) {
+ if (t->input[t->offset + 1] == '=') {
+ t->offset += 2;
+
+ buffer_copy_string(token, "==");
+
+ tid = TK_EQ;
+ } else if (t->input[t->offset + 1] == '~') {
+ t->offset += 2;
+
+ buffer_copy_string(token, "=~");
+
+ tid = TK_MATCH;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbsdsds",
+ "source:", t->source,
+ "line:", t->line, "pos:", t->line_pos,
+ "only =~ and == are allowed in the condition");
+ return -1;
+ }
+ t->in_key = 1;
+ t->in_cond = 0;
+ } else if (t->in_key) {
+ tid = TK_ASSIGN;
+
+ buffer_copy_string_len(token, t->input + t->offset, 1);
+
+ t->offset++;
+ t->line_pos++;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbsdsds",
+ "source:", t->source,
+ "line:", t->line, "pos:", t->line_pos,
+ "unexpected equal-sign: =");
+ return -1;
+ }
+
+ break;
+ case '!':
+ if (t->in_cond) {
+ if (t->input[t->offset + 1] == '=') {
+ t->offset += 2;
+
+ buffer_copy_string(token, "!=");
+
+ tid = TK_NE;
+ } else if (t->input[t->offset + 1] == '~') {
+ t->offset += 2;
+
+ buffer_copy_string(token, "!~");
+
+ tid = TK_NOMATCH;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbsdsds",
+ "source:", t->source,
+ "line:", t->line, "pos:", t->line_pos,
+ "only !~ and != are allowed in the condition");
+ return -1;
+ }
+ t->in_key = 1;
+ t->in_cond = 0;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbsdsds",
+ "source:", t->source,
+ "line:", t->line, "pos:", t->line_pos,
+ "unexpected exclamation-marks: !");
+ return -1;
+ }
+
+ break;
+ case '\t':
+ case ' ':
+ t->offset++;
+ t->line_pos++;
+ break;
+ case '\n':
+ case '\r':
+ if (t->in_brace == 0) {
+ int done = 0;
+ while (!done && t->offset < t->size) {
+ switch (t->input[t->offset]) {
+ case '\r':
+ case '\n':
+ config_skip_newline(t);
+ t->line_pos = 1;
+ t->line++;
+ break;
+
+ case '#':
+ t->line_pos += config_skip_comment(t);
+ break;
+
+ case '\t':
+ case ' ':
+ t->offset++;
+ t->line_pos++;
+ break;
+
+ default:
+ done = 1;
+ }
+ }
+ t->in_key = 1;
+ tid = TK_EOL;
+ buffer_copy_string(token, "(EOL)");
+ } else {
+ config_skip_newline(t);
+ t->line_pos = 1;
+ t->line++;
+ }
+ break;
+ case ',':
+ if (t->in_brace > 0) {
+ tid = TK_COMMA;
+
+ buffer_copy_string(token, "(COMMA)");
+ }
+
+ t->offset++;
+ t->line_pos++;
+ break;
+ case '"':
+ /* search for the terminating " */
+ start = t->input + t->offset + 1;
+ buffer_copy_string(token, "");
+
+ for (i = 1; t->input[t->offset + i]; i++) {
+ if (t->input[t->offset + i] == '\\' &&
+ t->input[t->offset + i + 1] == '"') {
+
+ buffer_append_string_len(token, start, t->input + t->offset + i - start);
+
+ start = t->input + t->offset + i + 1;
+
+ /* skip the " */
+ i++;
+ continue;
+ }
+
+
+ if (t->input[t->offset + i] == '"') {
+ tid = TK_STRING;
+
+ buffer_append_string_len(token, start, t->input + t->offset + i - start);
+
+ break;
+ }
+ }
+
+ if (t->input[t->offset + i] == '\0') {
+ /* ERROR */
+
+ log_error_write(srv, __FILE__, __LINE__, "sbsdsds",
+ "source:", t->source,
+ "line:", t->line, "pos:", t->line_pos,
+ "missing closing quote");
+
+ return -1;
+ }
+
+ t->offset += i + 1;
+ t->line_pos += i + 1;
+
+ break;
+ case '(':
+ t->offset++;
+ t->in_brace++;
+
+ tid = TK_LPARAN;
+
+ buffer_copy_string(token, "(");
+ break;
+ case ')':
+ t->offset++;
+ t->in_brace--;
+
+ tid = TK_RPARAN;
+
+ buffer_copy_string(token, ")");
+ break;
+ case '$':
+ t->offset++;
+
+ tid = TK_DOLLAR;
+ t->in_cond = 1;
+ t->in_key = 0;
+
+ buffer_copy_string(token, "$");
+
+ break;
+
+ case '+':
+ if (t->input[t->offset + 1] == '=') {
+ t->offset += 2;
+ buffer_copy_string(token, "+=");
+ tid = TK_APPEND;
+ } else {
+ t->offset++;
+ tid = TK_PLUS;
+ buffer_copy_string(token, "+");
+ }
+ break;
+
+ case '{':
+ t->offset++;
+
+ tid = TK_LCURLY;
+
+ buffer_copy_string(token, "{");
+
+ break;
+
+ case '}':
+ t->offset++;
+
+ tid = TK_RCURLY;
+
+ buffer_copy_string(token, "}");
+
+ break;
+
+ case '[':
+ t->offset++;
+
+ tid = TK_LBRACKET;
+
+ buffer_copy_string(token, "[");
+
+ break;
+
+ case ']':
+ t->offset++;
+
+ tid = TK_RBRACKET;
+
+ buffer_copy_string(token, "]");
+
+ break;
+ case '#':
+ t->line_pos += config_skip_comment(t);
+
+ break;
+ default:
+ if (t->in_cond) {
+ for (i = 0; t->input[t->offset + i] &&
+ (isalpha((unsigned char)t->input[t->offset + i])
+ ); i++);
+
+ if (i && t->input[t->offset + i]) {
+ tid = TK_SRVVARNAME;
+ buffer_copy_string_len(token, t->input + t->offset, i);
+
+ t->offset += i;
+ t->line_pos += i;
+ } else {
+ /* ERROR */
+ log_error_write(srv, __FILE__, __LINE__, "sbsdsds",
+ "source:", t->source,
+ "line:", t->line, "pos:", t->line_pos,
+ "invalid character in condition");
+ return -1;
+ }
+ } else if (isdigit((unsigned char)c)) {
+ /* take all digits */
+ for (i = 0; t->input[t->offset + i] && isdigit((unsigned char)t->input[t->offset + i]); i++);
+
+ /* was there it least a digit ? */
+ if (i && t->input[t->offset + i]) {
+ tid = TK_INTEGER;
+
+ buffer_copy_string_len(token, t->input + t->offset, i);
+
+ t->offset += i;
+ t->line_pos += i;
+ } else {
+ /* ERROR */
+ log_error_write(srv, __FILE__, __LINE__, "sbsdsds",
+ "source:", t->source,
+ "line:", t->line, "pos:", t->line_pos,
+ "unexpected EOF");
+
+ return -1;
+ }
+ } else {
+ /* the key might consist of [-.0-9a-z] */
+ for (i = 0; t->input[t->offset + i] &&
+ (isalnum((unsigned char)t->input[t->offset + i]) ||
+ t->input[t->offset + i] == '.' ||
+ t->input[t->offset + i] == '_' || /* for env.* */
+ t->input[t->offset + i] == '-'
+ ); i++);
+
+ if (i && t->input[t->offset + i]) {
+ buffer_copy_string_len(token, t->input + t->offset, i);
+
+ if (strcmp(token->ptr, "include") == 0) {
+ tid = TK_INCLUDE;
+ } else if (strcmp(token->ptr, "include_shell") == 0) {
+ tid = TK_INCLUDE_SHELL;
+ } else if (strcmp(token->ptr, "global") == 0) {
+ tid = TK_GLOBAL;
+ } else if (strcmp(token->ptr, "else") == 0) {
+ tid = TK_ELSE;
+ } else {
+ tid = TK_LKEY;
+ }
+
+ t->offset += i;
+ t->line_pos += i;
+ } else {
+ /* ERROR */
+ log_error_write(srv, __FILE__, __LINE__, "sbsdsds",
+ "source:", t->source,
+ "line:", t->line, "pos:", t->line_pos,
+ "invalid character in variable name");
+ return -1;
+ }
+ }
+ break;
+ }
+ }
+
+ if (tid) {
+ *token_id = tid;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sbsdsdbdd",
+ "source:", t->source,
+ "line:", t->line, "pos:", t->line_pos,
+ token, token->used - 1, tid);
+#endif
+
+ return 1;
+ } else if (t->offset < t->size) {
+ fprintf(stderr, "%s.%d: %d, %s\n",
+ __FILE__, __LINE__,
+ tid, token->ptr);
+ }
+ return 0;
+}
+
+static int config_parse(server *srv, config_t *context, tokenizer_t *t) {
+ void *pParser;
+ int token_id;
+ buffer *token, *lasttoken;
+ int ret;
+
+ pParser = configparserAlloc( malloc );
+ lasttoken = buffer_init();
+ token = buffer_init();
+ while((1 == (ret = config_tokenizer(srv, t, &token_id, token))) && context->ok) {
+ buffer_copy_string_buffer(lasttoken, token);
+ configparser(pParser, token_id, token, context);
+
+ token = buffer_init();
+ }
+ buffer_free(token);
+
+ if (ret != -1 && context->ok) {
+ /* add an EOL at EOF, better than say sorry */
+ configparser(pParser, TK_EOL, buffer_init_string("(EOL)"), context);
+ if (context->ok) {
+ configparser(pParser, 0, NULL, context);
+ }
+ }
+ configparserFree(pParser, free);
+
+ if (ret == -1) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "configfile parser failed:", lasttoken);
+ } else if (context->ok == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsdsdsb",
+ "source:", t->source,
+ "line:", t->line, "pos:", t->line_pos,
+ "parser failed somehow near here:", lasttoken);
+ ret = -1;
+ }
+ buffer_free(lasttoken);
+
+ return ret == -1 ? -1 : 0;
+}
+
+static int tokenizer_init(tokenizer_t *t, const buffer *source, const char *input, size_t size) {
+
+ t->source = source;
+ t->input = input;
+ t->size = size;
+ t->offset = 0;
+ t->line = 1;
+ t->line_pos = 1;
+
+ t->in_key = 1;
+ t->in_brace = 0;
+ t->in_cond = 0;
+ return 0;
+}
+
+int config_parse_file(server *srv, config_t *context, const char *fn) {
+ tokenizer_t t;
+ stream s;
+ int ret;
+ buffer *filename;
+
+ if (buffer_is_empty(context->basedir) &&
+ (fn[0] == '/' || fn[0] == '\\') &&
+ (fn[0] == '.' && (fn[1] == '/' || fn[1] == '\\'))) {
+ filename = buffer_init_string(fn);
+ } else {
+ filename = buffer_init_buffer(context->basedir);
+ buffer_append_string(filename, fn);
+ }
+
+ if (0 != stream_open(&s, filename)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "opening configfile ", filename, "failed:", strerror(errno));
+ ret = -1;
+ } else {
+ tokenizer_init(&t, filename, s.start, s.size);
+ ret = config_parse(srv, context, &t);
+ }
+
+ stream_close(&s);
+ buffer_free(filename);
+ return ret;
+}
+
+int config_parse_cmd(server *srv, config_t *context, const char *cmd) {
+ proc_handler_t proc;
+ tokenizer_t t;
+ int ret;
+ buffer *source;
+ buffer *out;
+ char oldpwd[PATH_MAX];
+
+ if (NULL == getcwd(oldpwd, sizeof(oldpwd))) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "cannot get cwd", strerror(errno));
+ return -1;
+ }
+
+ source = buffer_init_string(cmd);
+ out = buffer_init();
+
+ if (!buffer_is_empty(context->basedir)) {
+ chdir(context->basedir->ptr);
+ }
+
+ if (0 != proc_open_buffer(&proc, cmd, NULL, out, NULL)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "opening", source, "failed:", strerror(errno));
+ ret = -1;
+ } else {
+ tokenizer_init(&t, source, out->ptr, out->used);
+ ret = config_parse(srv, context, &t);
+ }
+
+ buffer_free(source);
+ buffer_free(out);
+ chdir(oldpwd);
+ return ret;
+}
+
+static void context_init(server *srv, config_t *context) {
+ context->srv = srv;
+ context->ok = 1;
+ context->configs_stack = array_init();
+ context->configs_stack->is_weakref = 1;
+ context->basedir = buffer_init();
+}
+
+static void context_free(config_t *context) {
+ array_free(context->configs_stack);
+ buffer_free(context->basedir);
+}
+
+int config_read(server *srv, const char *fn) {
+ config_t context;
+ data_config *dc;
+ data_integer *dpid;
+ data_string *dcwd;
+ int ret;
+ char *pos;
+ data_array *modules;
+
+ context_init(srv, &context);
+ context.all_configs = srv->config_context;
+
+ pos = strrchr(fn,
+#ifdef __WIN32
+ '\\'
+#else
+ '/'
+#endif
+ );
+ if (pos) {
+ buffer_copy_string_len(context.basedir, fn, pos - fn + 1);
+ fn = pos + 1;
+ }
+
+ dc = data_config_init();
+ buffer_copy_string(dc->key, "global");
+
+ assert(context.all_configs->used == 0);
+ dc->context_ndx = context.all_configs->used;
+ array_insert_unique(context.all_configs, (data_unset *)dc);
+ context.current = dc;
+
+ /* default context */
+ srv->config = dc->value;
+ dpid = data_integer_init();
+ dpid->value = getpid();
+ buffer_copy_string(dpid->key, "var.PID");
+ array_insert_unique(srv->config, (data_unset *)dpid);
+
+ dcwd = data_string_init();
+ buffer_prepare_copy(dcwd->value, 1024);
+ if (NULL != getcwd(dcwd->value->ptr, dcwd->value->size - 1)) {
+ dcwd->value->used = strlen(dcwd->value->ptr) + 1;
+ buffer_copy_string(dcwd->key, "var.CWD");
+ array_insert_unique(srv->config, (data_unset *)dcwd);
+ }
+
+ ret = config_parse_file(srv, &context, fn);
+
+ /* remains nothing if parser is ok */
+ assert(!(0 == ret && context.ok && 0 != context.configs_stack->used));
+ context_free(&context);
+
+ if (0 != ret) {
+ return ret;
+ }
+
+ if (NULL != (dc = (data_config *)array_get_element(srv->config_context, "global"))) {
+ srv->config = dc->value;
+ } else {
+ return -1;
+ }
+
+ if (NULL != (modules = (data_array *)array_get_element(srv->config, "server.modules"))) {
+ data_string *ds;
+ data_array *prepends;
+
+ if (modules->type != TYPE_ARRAY) {
+ fprintf(stderr, "server.modules must be an array");
+ return -1;
+ }
+
+ prepends = data_array_init();
+
+ /* prepend default modules */
+ if (NULL == array_get_element(modules->value, "mod_indexfile")) {
+ ds = data_string_init();
+ buffer_copy_string(ds->value, "mod_indexfile");
+ array_insert_unique(prepends->value, (data_unset *)ds);
+ }
+
+ prepends = (data_array *)configparser_merge_data((data_unset *)prepends, (data_unset *)modules);
+ buffer_copy_string_buffer(prepends->key, modules->key);
+ array_replace(srv->config, (data_unset *)prepends);
+ modules->free((data_unset *)modules);
+ modules = prepends;
+
+ /* append default modules */
+ if (NULL == array_get_element(modules->value, "mod_dirlisting")) {
+ ds = data_string_init();
+ buffer_copy_string(ds->value, "mod_dirlisting");
+ array_insert_unique(modules->value, (data_unset *)ds);
+ }
+
+ if (NULL == array_get_element(modules->value, "mod_staticfile")) {
+ ds = data_string_init();
+ buffer_copy_string(ds->value, "mod_staticfile");
+ array_insert_unique(modules->value, (data_unset *)ds);
+ }
+ } else {
+ data_string *ds;
+
+ modules = data_array_init();
+
+ /* server.modules is not set */
+ ds = data_string_init();
+ buffer_copy_string(ds->value, "mod_indexfile");
+ array_insert_unique(modules->value, (data_unset *)ds);
+
+ ds = data_string_init();
+ buffer_copy_string(ds->value, "mod_dirlisting");
+ array_insert_unique(modules->value, (data_unset *)ds);
+
+ ds = data_string_init();
+ buffer_copy_string(ds->value, "mod_staticfile");
+ array_insert_unique(modules->value, (data_unset *)ds);
+
+ buffer_copy_string(modules->key, "server.modules");
+ array_insert_unique(srv->config, (data_unset *)modules);
+ }
+
+
+ if (0 != config_insert(srv)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int config_set_defaults(server *srv) {
+ size_t i;
+ specific_config *s = srv->config_storage[0];
+ struct stat st1, st2;
+
+ struct ev_map { fdevent_handler_t et; const char *name; } event_handlers[] =
+ {
+ /* - poll is most reliable
+ * - select works everywhere
+ * - linux-* are experimental
+ */
+#ifdef USE_POLL
+ { FDEVENT_HANDLER_POLL, "poll" },
+#endif
+#ifdef USE_SELECT
+ { FDEVENT_HANDLER_SELECT, "select" },
+#endif
+#ifdef USE_LINUX_EPOLL
+ { FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll" },
+#endif
+#ifdef USE_LINUX_SIGIO
+ { FDEVENT_HANDLER_LINUX_RTSIG, "linux-rtsig" },
+#endif
+#ifdef USE_SOLARIS_DEVPOLL
+ { FDEVENT_HANDLER_SOLARIS_DEVPOLL,"solaris-devpoll" },
+#endif
+#ifdef USE_FREEBSD_KQUEUE
+ { FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue" },
+ { FDEVENT_HANDLER_FREEBSD_KQUEUE, "kqueue" },
+#endif
+ { FDEVENT_HANDLER_UNSET, NULL }
+ };
+
+
+ if (buffer_is_empty(s->document_root)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "a default document-root has to be set");
+
+ return -1;
+ }
+
+ if (buffer_is_empty(srv->srvconf.changeroot)) {
+ if (-1 == stat(s->document_root->ptr, &st1)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "base-docroot doesn't exist:",
+ s->document_root);
+ return -1;
+ }
+
+ } else {
+ buffer_copy_string_buffer(srv->tmp_buf, srv->srvconf.changeroot);
+ buffer_append_string_buffer(srv->tmp_buf, s->document_root);
+
+ if (-1 == stat(srv->tmp_buf->ptr, &st1)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "base-docroot doesn't exist:",
+ srv->tmp_buf);
+ return -1;
+ }
+
+ }
+
+ buffer_copy_string_buffer(srv->tmp_buf, s->document_root);
+
+ buffer_to_lower(srv->tmp_buf);
+
+ if (0 == stat(srv->tmp_buf->ptr, &st1)) {
+ int is_lower = 0;
+
+ is_lower = buffer_is_equal(srv->tmp_buf, s->document_root);
+
+ /* lower-case existed, check upper-case */
+ buffer_copy_string_buffer(srv->tmp_buf, s->document_root);
+
+ buffer_to_upper(srv->tmp_buf);
+
+ /* we have to handle the special case that upper and lower-casing results in the same filename
+ * as in server.document-root = "/" or "/12345/" */
+
+ if (is_lower && buffer_is_equal(srv->tmp_buf, s->document_root)) {
+ /* lower-casing and upper-casing didn't result in
+ * an other filename, no need to stat(),
+ * just assume it is case-sensitive. */
+
+ s->force_lowercase_filenames = 0;
+ } else if (0 == stat(srv->tmp_buf->ptr, &st2)) {
+
+ /* upper case exists too, doesn't the FS handle this ? */
+
+ /* upper and lower have the same inode -> case-insensitve FS */
+
+ if (st1.st_ino == st2.st_ino) {
+ /* upper and lower have the same inode -> case-insensitve FS */
+
+ s->force_lowercase_filenames = 1;
+ }
+ }
+ }
+
+ if (srv->srvconf.port == 0) {
+ srv->srvconf.port = s->is_ssl ? 443 : 80;
+ }
+
+ if (srv->srvconf.event_handler->used == 0) {
+ /* choose a good default
+ *
+ * the event_handler list is sorted by 'goodness'
+ * taking the first available should be the best solution
+ */
+ srv->event_handler = event_handlers[0].et;
+
+ if (FDEVENT_HANDLER_UNSET == srv->event_handler) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "sorry, there is no event handler for this system");
+
+ return -1;
+ }
+ } else {
+ /*
+ * User override
+ */
+
+ for (i = 0; event_handlers[i].name; i++) {
+ if (0 == strcmp(event_handlers[i].name, srv->srvconf.event_handler->ptr)) {
+ srv->event_handler = event_handlers[i].et;
+ break;
+ }
+ }
+
+ if (FDEVENT_HANDLER_UNSET == srv->event_handler) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "the selected event-handler in unknown or not supported:",
+ srv->srvconf.event_handler );
+
+ return -1;
+ }
+ }
+
+ if (s->is_ssl) {
+ if (buffer_is_empty(s->ssl_pemfile)) {
+ /* PEM file is require */
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ssl.pemfile has to be set");
+ return -1;
+ }
+
+#ifndef USE_OPENSSL
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ssl support is missing, recompile with --with-openssl");
+
+ return -1;
+#endif
+ }
+
+ return 0;
+}
diff --git a/src/configfile.h b/src/configfile.h
new file mode 100644
index 0000000..600297f
--- /dev/null
+++ b/src/configfile.h
@@ -0,0 +1,24 @@
+#ifndef _CONFIG_PARSER_H_
+#define _CONFIG_PARSER_H_
+
+#include "array.h"
+#include "buffer.h"
+#include "server.h"
+
+typedef struct {
+ server *srv;
+ int ok;
+ array *all_configs;
+ array *configs_stack; /* to parse nested block */
+ data_config *current; /* current started with { */
+ buffer *basedir;
+} config_t;
+
+void *configparserAlloc(void *(*mallocProc)(size_t));
+void configparserFree(void *p, void (*freeProc)(void*));
+void configparser(void *yyp, int yymajor, buffer *yyminor, config_t *ctx);
+int config_parse_file(server *srv, config_t *context, const char *fn);
+int config_parse_cmd(server *srv, config_t *context, const char *cmd);
+data_unset *configparser_merge_data(data_unset *op1, const data_unset *op2);
+
+#endif
diff --git a/src/configparser.c b/src/configparser.c
new file mode 100644
index 0000000..c2a3960
--- /dev/null
+++ b/src/configparser.c
@@ -0,0 +1,1568 @@
+/* Driver template for the LEMON parser generator.
+** The author disclaims copyright to this source code.
+*/
+/* First off, code is include which follows the "include" declaration
+** in the input file. */
+#include <stdio.h>
+#line 5 "./configparser.y"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "configfile.h"
+#include "buffer.h"
+#include "array.h"
+
+static void configparser_push(config_t *ctx, data_config *dc, int isnew) {
+ if (isnew) {
+ dc->context_ndx = ctx->all_configs->used;
+ assert(dc->context_ndx > ctx->current->context_ndx);
+ array_insert_unique(ctx->all_configs, (data_unset *)dc);
+ dc->parent = ctx->current;
+ array_insert_unique(dc->parent->childs, (data_unset *)dc);
+ }
+ array_insert_unique(ctx->configs_stack, (data_unset *)ctx->current);
+ ctx->current = dc;
+}
+
+static data_config *configparser_pop(config_t *ctx) {
+ data_config *old = ctx->current;
+ ctx->current = (data_config *) array_pop(ctx->configs_stack);
+ return old;
+}
+
+/* return a copied variable */
+static data_unset *configparser_get_variable(config_t *ctx, const buffer *key) {
+ if (strncmp(key->ptr, "env.", sizeof("env.") - 1) == 0) {
+ char *env;
+
+ if (NULL != (env = getenv(key->ptr + 4))) {
+ data_string *ds;
+ ds = data_string_init();
+ buffer_append_string(ds->value, env);
+ return (data_unset *)ds;
+ }
+
+ fprintf(stderr, "Undefined env variable: %s\n", key->ptr + 4);
+ ctx->ok = 0;
+
+ return NULL;
+ } else {
+ data_unset *du;
+ data_config *dc;
+
+#if 0
+ fprintf(stderr, "get var %s\n", key->ptr);
+#endif
+ for (dc = ctx->current; dc; dc = dc->parent) {
+#if 0
+ fprintf(stderr, "get var on block: %s\n", dc->key->ptr);
+ array_print(dc->value, 0);
+#endif
+ if (NULL != (du = array_get_element(dc->value, key->ptr))) {
+ return du->copy(du);
+ }
+ }
+ fprintf(stderr, "Undefined config variable: %s\n", key->ptr);
+ ctx->ok = 0;
+ return NULL;
+ }
+}
+
+/* op1 is to be eat/return by this function, op1->key is not cared
+ op2 is left untouch, unreferenced
+ */
+data_unset *configparser_merge_data(data_unset *op1, const data_unset *op2) {
+ /* type mismatch */
+ if (op1->type != op2->type) {
+ if (op1->type == TYPE_STRING && op2->type == TYPE_INTEGER) {
+ data_string *ds = (data_string *)op1;
+ buffer_append_long(ds->value, ((data_integer*)op2)->value);
+ return op1;
+ } else if (op1->type == TYPE_INTEGER && op2->type == TYPE_STRING) {
+ data_string *ds = data_string_init();
+ buffer_append_long(ds->value, ((data_integer*)op1)->value);
+ buffer_append_string_buffer(ds->value, ((data_string*)op2)->value);
+ op1->free(op1);
+ return (data_unset *)ds;
+ } else {
+ fprintf(stderr, "data type mismatch, cannot be merge\n");
+ op1->free(op1);
+ return NULL;
+ }
+ }
+
+ switch (op1->type) {
+ case TYPE_STRING:
+ buffer_append_string_buffer(((data_string *)op1)->value, ((data_string *)op2)->value);
+ break;
+ case TYPE_INTEGER:
+ ((data_integer *)op1)->value += ((data_integer *)op2)->value;
+ break;
+ case TYPE_ARRAY: {
+ array *dst = ((data_array *)op1)->value;
+ array *src = ((data_array *)op2)->value;
+ data_unset *du;
+ size_t i;
+
+ for (i = 0; i < src->used; i ++) {
+ du = (data_unset *)src->data[i];
+ if (du) {
+ array_insert_unique(dst, du->copy(du));
+ }
+ }
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ }
+ return op1;
+}
+
+
+#line 128 "configparser.c"
+/* Next is all token values, in a form suitable for use by makeheaders.
+** This section will be null unless lemon is run with the -m switch.
+*/
+/*
+** These constants (all generated automatically by the parser generator)
+** specify the various kinds of tokens (terminals) that the parser
+** understands.
+**
+** Each symbol here is a terminal symbol in the grammar.
+*/
+/* Make sure the INTERFACE macro is defined.
+*/
+#ifndef INTERFACE
+# define INTERFACE 1
+#endif
+/* The next thing included is series of defines which control
+** various aspects of the generated parser.
+** YYCODETYPE is the data type used for storing terminal
+** and nonterminal numbers. "unsigned char" is
+** used if there are fewer than 250 terminals
+** and nonterminals. "int" is used otherwise.
+** YYNOCODE is a number of type YYCODETYPE which corresponds
+** to no legal terminal or nonterminal number. This
+** number is used to fill in empty slots of the hash
+** table.
+** YYFALLBACK If defined, this indicates that one or more tokens
+** have fall-back values which should be used if the
+** original value of the token will not parse.
+** YYACTIONTYPE is the data type used for storing terminal
+** and nonterminal numbers. "unsigned char" is
+** used if there are fewer than 250 rules and
+** states combined. "int" is used otherwise.
+** configparserTOKENTYPE is the data type used for minor tokens given
+** directly to the parser from the tokenizer.
+** YYMINORTYPE is the data type used for all minor tokens.
+** This is typically a union of many types, one of
+** which is configparserTOKENTYPE. The entry in the union
+** for base tokens is called "yy0".
+** YYSTACKDEPTH is the maximum depth of the parser's stack.
+** configparserARG_SDECL A static variable declaration for the %extra_argument
+** configparserARG_PDECL A parameter declaration for the %extra_argument
+** configparserARG_STORE Code to store %extra_argument into yypParser
+** configparserARG_FETCH Code to extract %extra_argument from yypParser
+** YYNSTATE the combined number of states.
+** YYNRULE the number of rules in the grammar
+** YYERRORSYMBOL is the code number of the error symbol. If not
+** defined, then do no error processing.
+*/
+/*  */
+#define YYCODETYPE unsigned char
+#define YYNOCODE 48
+#define YYACTIONTYPE unsigned char
+#define configparserTOKENTYPE buffer *
+typedef union {
+ configparserTOKENTYPE yy0;
+ config_cond_t yy27;
+ array * yy40;
+ data_unset * yy41;
+ buffer * yy43;
+ data_config * yy78;
+ int yy95;
+} YYMINORTYPE;
+#define YYSTACKDEPTH 100
+#define configparserARG_SDECL config_t *ctx;
+#define configparserARG_PDECL ,config_t *ctx
+#define configparserARG_FETCH config_t *ctx = yypParser->ctx
+#define configparserARG_STORE yypParser->ctx = ctx
+#define YYNSTATE 62
+#define YYNRULE 39
+#define YYERRORSYMBOL 26
+#define YYERRSYMDT yy95
+#define YY_NO_ACTION (YYNSTATE+YYNRULE+2)
+#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1)
+#define YY_ERROR_ACTION (YYNSTATE+YYNRULE)
+
+/* Next are that tables used to determine what action to take based on the
+** current state and lookahead token. These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.
+**
+** Suppose the action integer is N. Then the action is determined as
+** follows
+**
+** 0 <= N < YYNSTATE Shift N. That is, push the lookahead
+** token onto the stack and goto state N.
+**
+** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE.
+**
+** N == YYNSTATE+YYNRULE A syntax error has occurred.
+**
+** N == YYNSTATE+YYNRULE+1 The parser accepts its input.
+**
+** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused
+** slots in the yy_action[] table.
+**
+** The action table is constructed as a single large table named yy_action[].
+** Given state S and lookahead X, the action is computed as
+**
+** yy_action[ yy_shift_ofst[S] + X ]
+**
+** If the index value yy_shift_ofst[S]+X is out of range or if the value
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S]
+** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table
+** and that yy_default[S] should be used instead.
+**
+** The formula above is for computing the action when the lookahead is
+** a terminal symbol. If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the yy_reduce_ofst[] array is used in place of
+** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of
+** YY_SHIFT_USE_DFLT.
+**
+** The following are the tables generated in this section:
+**
+** yy_action[] A single table containing all actions.
+** yy_lookahead[] A table containing the lookahead for each entry in
+** yy_action. Used to detect hash collisions.
+** yy_shift_ofst[] For each state, the offset into yy_action for
+** shifting terminals.
+** yy_reduce_ofst[] For each state, the offset into yy_action for
+** shifting non-terminals after a reduce.
+** yy_default[] Default action for each state.
+*/
+static YYACTIONTYPE yy_action[] = {
+ /* 0 */ 2, 3, 4, 5, 13, 14, 62, 15, 7, 44,
+ /* 10 */ 20, 86, 16, 45, 28, 48, 40, 10, 39, 25,
+ /* 20 */ 22, 49, 45, 8, 15, 102, 1, 20, 28, 18,
+ /* 30 */ 57, 59, 19, 25, 22, 39, 19, 61, 98, 45,
+ /* 40 */ 20, 6, 23, 24, 26, 28, 35, 57, 59, 12,
+ /* 50 */ 25, 22, 28, 27, 36, 87, 29, 25, 22, 33,
+ /* 60 */ 15, 30, 31, 20, 28, 38, 9, 17, 37, 25,
+ /* 70 */ 22, 39, 42, 43, 10, 45, 11, 53, 54, 55,
+ /* 80 */ 56, 28, 52, 57, 59, 34, 25, 22, 28, 27,
+ /* 90 */ 32, 88, 41, 25, 22, 33, 28, 48, 46, 28,
+ /* 100 */ 48, 25, 22, 58, 25, 22, 60, 21, 19, 47,
+ /* 110 */ 51, 50, 25, 22, 88, 88, 93,
+};
+static YYCODETYPE yy_lookahead[] = {
+ /* 0 */ 29, 30, 31, 32, 33, 34, 0, 1, 44, 38,
+ /* 10 */ 4, 15, 41, 16, 35, 36, 45, 46, 12, 40,
+ /* 20 */ 41, 42, 16, 15, 1, 27, 28, 4, 35, 36,
+ /* 30 */ 24, 25, 5, 40, 41, 12, 5, 14, 11, 16,
+ /* 40 */ 4, 1, 6, 7, 8, 35, 36, 24, 25, 28,
+ /* 50 */ 40, 41, 35, 36, 37, 15, 39, 40, 41, 42,
+ /* 60 */ 1, 9, 10, 4, 35, 36, 38, 2, 3, 40,
+ /* 70 */ 41, 12, 28, 14, 46, 16, 13, 20, 21, 22,
+ /* 80 */ 23, 35, 36, 24, 25, 11, 40, 41, 35, 36,
+ /* 90 */ 37, 13, 13, 40, 41, 42, 35, 36, 17, 35,
+ /* 100 */ 36, 40, 41, 42, 40, 41, 42, 35, 5, 18,
+ /* 110 */ 43, 19, 40, 41, 47, 47, 13,
+};
+#define YY_SHIFT_USE_DFLT (-5)
+static signed char yy_shift_ofst[] = {
+ /* 0 */ -5, 6, -5, -5, -5, 40, -4, 8, -3, -5,
+ /* 10 */ 63, -5, 23, -5, -5, -5, 65, 36, 31, 36,
+ /* 20 */ -5, -5, -5, -5, -5, -5, 36, 27, -5, 52,
+ /* 30 */ -5, 36, -5, 74, 36, 31, -5, 36, 31, 78,
+ /* 40 */ 79, -5, 59, -5, -5, 81, 91, 36, 31, 92,
+ /* 50 */ 57, 36, 103, -5, -5, -5, -5, 36, -5, 36,
+ /* 60 */ -5, -5,
+};
+#define YY_REDUCE_USE_DFLT (-37)
+static signed char yy_reduce_ofst[] = {
+ /* 0 */ -2, -29, -37, -37, -37, -36, -37, -37, 28, -37,
+ /* 10 */ -37, 21, -29, -37, -37, -37, -37, -7, -37, 72,
+ /* 20 */ -37, -37, -37, -37, -37, -37, 17, -37, -37, -37,
+ /* 30 */ -37, 53, -37, -37, 10, -37, -37, 29, -37, -37,
+ /* 40 */ -37, 44, -29, -37, -37, -37, -37, -21, -37, -37,
+ /* 50 */ 67, 46, -37, -37, -37, -37, -37, 61, -37, 64,
+ /* 60 */ -37, -37,
+};
+static YYACTIONTYPE yy_default[] = {
+ /* 0 */ 64, 101, 63, 65, 66, 101, 67, 101, 101, 90,
+ /* 10 */ 101, 64, 101, 68, 69, 70, 101, 101, 71, 101,
+ /* 20 */ 73, 74, 76, 77, 78, 79, 101, 84, 75, 101,
+ /* 30 */ 80, 82, 81, 101, 101, 85, 83, 101, 72, 101,
+ /* 40 */ 101, 64, 101, 89, 91, 101, 101, 101, 98, 101,
+ /* 50 */ 101, 101, 101, 94, 95, 96, 97, 101, 99, 101,
+ /* 60 */ 100, 92,
+};
+#define YY_SZ_ACTTAB (sizeof(yy_action)/sizeof(yy_action[0]))
+
+/* The next table maps tokens into fallback tokens. If a construct
+** like the following:
+**
+** %fallback ID X Y Z.
+**
+** appears in the grammer, then ID becomes a fallback token for X, Y,
+** and Z. Whenever one of the tokens X, Y, or Z is input to the parser
+** but it does not parse, the type of the token is changed to ID and
+** the parse is retried before an error is thrown.
+*/
+#ifdef YYFALLBACK
+static const YYCODETYPE yyFallback[] = {
+};
+#endif /* YYFALLBACK */
+
+/* The following structure represents a single element of the
+** parser's stack. Information stored includes:
+**
+** + The state number for the parser at this level of the stack.
+**
+** + The value of the token stored at this level of the stack.
+** (In other words, the "major" token.)
+**
+** + The semantic value stored at this level of the stack. This is
+** the information used by the action routines in the grammar.
+** It is sometimes called the "minor" token.
+*/
+struct yyStackEntry {
+ int stateno; /* The state-number */
+ int major; /* The major token value. This is the code
+ ** number for the token at this stack level */
+ YYMINORTYPE minor; /* The user-supplied minor token value. This
+ ** is the value of the token */
+};
+typedef struct yyStackEntry yyStackEntry;
+
+/* The state of the parser is completely contained in an instance of
+** the following structure */
+struct yyParser {
+ int yyidx; /* Index of top element in stack */
+ int yyerrcnt; /* Shifts left before out of the error */
+ configparserARG_SDECL /* A place to hold %extra_argument */
+ yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */
+};
+typedef struct yyParser yyParser;
+
+#ifndef NDEBUG
+#include <stdio.h>
+static FILE *yyTraceFILE = 0;
+static char *yyTracePrompt = 0;
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/*
+** Turn parser tracing on by giving a stream to which to write the trace
+** and a prompt to preface each trace message. Tracing is turned off
+** by making either argument NULL
+**
+** Inputs:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+** If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+** line of trace output. If NULL, then tracing is
+** turned off.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void configparserTrace(FILE *TraceFILE, char *zTracePrompt){
+ yyTraceFILE = TraceFILE;
+ yyTracePrompt = zTracePrompt;
+ if( yyTraceFILE==0 ) yyTracePrompt = 0;
+ else if( yyTracePrompt==0 ) yyTraceFILE = 0;
+}
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing shifts, the names of all terminals and nonterminals
+** are required. The following table supplies these names */
+static const char *yyTokenName[] = {
+ "$", "EOL", "ASSIGN", "APPEND",
+ "LKEY", "PLUS", "STRING", "INTEGER",
+ "LPARAN", "RPARAN", "COMMA", "ARRAY_ASSIGN",
+ "GLOBAL", "LCURLY", "RCURLY", "ELSE",
+ "DOLLAR", "SRVVARNAME", "LBRACKET", "RBRACKET",
+ "EQ", "MATCH", "NE", "NOMATCH",
+ "INCLUDE", "INCLUDE_SHELL", "error", "input",
+ "metalines", "metaline", "varline", "global",
+ "condlines", "include", "include_shell", "value",
+ "expression", "aelement", "condline", "aelements",
+ "array", "key", "stringop", "cond",
+ "eols", "globalstart", "context",
+};
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *yyRuleName[] = {
+ /* 0 */ "input ::= metalines",
+ /* 1 */ "metalines ::= metalines metaline",
+ /* 2 */ "metalines ::=",
+ /* 3 */ "metaline ::= varline",
+ /* 4 */ "metaline ::= global",
+ /* 5 */ "metaline ::= condlines EOL",
+ /* 6 */ "metaline ::= include",
+ /* 7 */ "metaline ::= include_shell",
+ /* 8 */ "metaline ::= EOL",
+ /* 9 */ "varline ::= key ASSIGN expression",
+ /* 10 */ "varline ::= key APPEND expression",
+ /* 11 */ "key ::= LKEY",
+ /* 12 */ "expression ::= expression PLUS value",
+ /* 13 */ "expression ::= value",
+ /* 14 */ "value ::= key",
+ /* 15 */ "value ::= STRING",
+ /* 16 */ "value ::= INTEGER",
+ /* 17 */ "value ::= array",
+ /* 18 */ "array ::= LPARAN aelements RPARAN",
+ /* 19 */ "aelements ::= aelements COMMA aelement",
+ /* 20 */ "aelements ::= aelements COMMA",
+ /* 21 */ "aelements ::= aelement",
+ /* 22 */ "aelement ::= expression",
+ /* 23 */ "aelement ::= stringop ARRAY_ASSIGN expression",
+ /* 24 */ "eols ::= EOL",
+ /* 25 */ "eols ::=",
+ /* 26 */ "globalstart ::= GLOBAL",
+ /* 27 */ "global ::= globalstart LCURLY metalines RCURLY",
+ /* 28 */ "condlines ::= condlines eols ELSE condline",
+ /* 29 */ "condlines ::= condline",
+ /* 30 */ "condline ::= context LCURLY metalines RCURLY",
+ /* 31 */ "context ::= DOLLAR SRVVARNAME LBRACKET stringop RBRACKET cond expression",
+ /* 32 */ "cond ::= EQ",
+ /* 33 */ "cond ::= MATCH",
+ /* 34 */ "cond ::= NE",
+ /* 35 */ "cond ::= NOMATCH",
+ /* 36 */ "stringop ::= expression",
+ /* 37 */ "include ::= INCLUDE stringop",
+ /* 38 */ "include_shell ::= INCLUDE_SHELL stringop",
+};
+#endif /* NDEBUG */
+
+/*
+** This function returns the symbolic name associated with a token
+** value.
+*/
+const char *configparserTokenName(int tokenType){
+#ifndef NDEBUG
+ if( tokenType>0 && tokenType<(sizeof(yyTokenName)/sizeof(yyTokenName[0])) ){
+ return yyTokenName[tokenType];
+ }else{
+ return "Unknown";
+ }
+#else
+ return "";
+#endif
+}
+
+/*
+** This function allocates a new parser.
+** The only argument is a pointer to a function which works like
+** malloc.
+**
+** Inputs:
+** A pointer to the function used to allocate memory.
+**
+** Outputs:
+** A pointer to a parser. This pointer is used in subsequent calls
+** to configparser and configparserFree.
+*/
+void *configparserAlloc(void *(*mallocProc)(size_t)){
+ yyParser *pParser;
+ pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) );
+ if( pParser ){
+ pParser->yyidx = -1;
+ }
+ return pParser;
+}
+
+/* The following function deletes the value associated with a
+** symbol. The symbol can be either a terminal or nonterminal.
+** "yymajor" is the symbol code, and "yypminor" is a pointer to
+** the value.
+*/
+static void yy_destructor(YYCODETYPE yymajor, YYMINORTYPE *yypminor){
+ switch( yymajor ){
+ /* Here is inserted the actions which take place when a
+ ** terminal or non-terminal is destroyed. This can happen
+ ** when the symbol is popped from the stack during a
+ ** reduce or during error processing or when a parser is
+ ** being destroyed before it is finished parsing.
+ **
+ ** Note: during a reduce, the only symbols destroyed are those
+ ** which appear on the RHS of the rule, but which are not used
+ ** inside the C code.
+ */
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ case 17:
+ case 18:
+ case 19:
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ case 24:
+ case 25:
+#line 160 "./configparser.y"
+{ buffer_free((yypminor->yy0)); }
+#line 533 "configparser.c"
+ break;
+ case 35:
+#line 151 "./configparser.y"
+{ (yypminor->yy41)->free((yypminor->yy41)); }
+#line 538 "configparser.c"
+ break;
+ case 36:
+#line 152 "./configparser.y"
+{ (yypminor->yy41)->free((yypminor->yy41)); }
+#line 543 "configparser.c"
+ break;
+ case 37:
+#line 153 "./configparser.y"
+{ (yypminor->yy41)->free((yypminor->yy41)); }
+#line 548 "configparser.c"
+ break;
+ case 39:
+#line 154 "./configparser.y"
+{ array_free((yypminor->yy40)); }
+#line 553 "configparser.c"
+ break;
+ case 40:
+#line 155 "./configparser.y"
+{ array_free((yypminor->yy40)); }
+#line 558 "configparser.c"
+ break;
+ case 41:
+#line 156 "./configparser.y"
+{ buffer_free((yypminor->yy43)); }
+#line 563 "configparser.c"
+ break;
+ case 42:
+#line 157 "./configparser.y"
+{ buffer_free((yypminor->yy43)); }
+#line 568 "configparser.c"
+ break;
+ default: break; /* If no destructor action specified: do nothing */
+ }
+}
+
+/*
+** Pop the parser's stack once.
+**
+** If there is a destructor routine associated with the token which
+** is popped from the stack, then call it.
+**
+** Return the major token number for the symbol popped.
+*/
+static int yy_pop_parser_stack(yyParser *pParser){
+ YYCODETYPE yymajor;
+ yyStackEntry *yytos = &pParser->yystack[pParser->yyidx];
+
+ if( pParser->yyidx<0 ) return 0;
+#ifndef NDEBUG
+ if( yyTraceFILE && pParser->yyidx>=0 ){
+ fprintf(yyTraceFILE,"%sPopping %s\n",
+ yyTracePrompt,
+ yyTokenName[yytos->major]);
+ }
+#endif
+ yymajor = yytos->major;
+ yy_destructor( yymajor, &yytos->minor);
+ pParser->yyidx--;
+ return yymajor;
+}
+
+/*
+** Deallocate and destroy a parser. Destructors are all called for
+** all stack elements before shutting the parser down.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser. This should be a pointer
+** obtained from configparserAlloc.
+** <li> A pointer to a function used to reclaim memory obtained
+** from malloc.
+** </ul>
+*/
+void configparserFree(
+ void *p, /* The parser to be deleted */
+ void (*freeProc)(void*) /* Function used to reclaim memory */
+){
+ yyParser *pParser = (yyParser*)p;
+ if( pParser==0 ) return;
+ while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser);
+ (*freeProc)((void*)pParser);
+}
+
+/*
+** Find the appropriate action for a parser given the terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead. If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_shift_action(
+ yyParser *pParser, /* The parser */
+ int iLookAhead /* The look-ahead token */
+){
+ int i;
+ int stateno = pParser->yystack[pParser->yyidx].stateno;
+
+ /* if( pParser->yyidx<0 ) return YY_NO_ACTION; */
+ i = yy_shift_ofst[stateno];
+ if( i==YY_SHIFT_USE_DFLT ){
+ return yy_default[stateno];
+ }
+ if( iLookAhead==YYNOCODE ){
+ return YY_NO_ACTION;
+ }
+ i += iLookAhead;
+ if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+#ifdef YYFALLBACK
+ int iFallback; /* Fallback token */
+ if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
+ && (iFallback = yyFallback[iLookAhead])!=0 ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
+ yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
+ }
+#endif
+ return yy_find_shift_action(pParser, iFallback);
+ }
+#endif
+ return yy_default[stateno];
+ }else{
+ return yy_action[i];
+ }
+}
+
+/*
+** Find the appropriate action for a parser given the non-terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead. If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_reduce_action(
+ yyParser *pParser, /* The parser */
+ int iLookAhead /* The look-ahead token */
+){
+ int i;
+ int stateno = pParser->yystack[pParser->yyidx].stateno;
+
+ i = yy_reduce_ofst[stateno];
+ if( i==YY_REDUCE_USE_DFLT ){
+ return yy_default[stateno];
+ }
+ if( iLookAhead==YYNOCODE ){
+ return YY_NO_ACTION;
+ }
+ i += iLookAhead;
+ if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+ return yy_default[stateno];
+ }else{
+ return yy_action[i];
+ }
+}
+
+/*
+** Perform a shift action.
+*/
+static void yy_shift(
+ yyParser *yypParser, /* The parser to be shifted */
+ int yyNewState, /* The new state to shift in */
+ int yyMajor, /* The major token to shift in */
+ YYMINORTYPE *yypMinor /* Pointer ot the minor token to shift in */
+){
+ yyStackEntry *yytos;
+ yypParser->yyidx++;
+ if( yypParser->yyidx>=YYSTACKDEPTH ){
+ configparserARG_FETCH;
+ yypParser->yyidx--;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will execute if the parser
+ ** stack every overflows */
+ configparserARG_STORE; /* Suppress warning about unused %extra_argument var */
+ return;
+ }
+ yytos = &yypParser->yystack[yypParser->yyidx];
+ yytos->stateno = yyNewState;
+ yytos->major = yyMajor;
+ yytos->minor = *yypMinor;
+#ifndef NDEBUG
+ if( yyTraceFILE && yypParser->yyidx>0 ){
+ int i;
+ fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState);
+ fprintf(yyTraceFILE,"%sStack:",yyTracePrompt);
+ for(i=1; i<=yypParser->yyidx; i++)
+ fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]);
+ fprintf(yyTraceFILE,"\n");
+ }
+#endif
+}
+
+/* The following table contains information about every rule that
+** is used during the reduce.
+*/
+static struct {
+ YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */
+ unsigned char nrhs; /* Number of right-hand side symbols in the rule */
+} yyRuleInfo[] = {
+ { 27, 1 },
+ { 28, 2 },
+ { 28, 0 },
+ { 29, 1 },
+ { 29, 1 },
+ { 29, 2 },
+ { 29, 1 },
+ { 29, 1 },
+ { 29, 1 },
+ { 30, 3 },
+ { 30, 3 },
+ { 41, 1 },
+ { 36, 3 },
+ { 36, 1 },
+ { 35, 1 },
+ { 35, 1 },
+ { 35, 1 },
+ { 35, 1 },
+ { 40, 3 },
+ { 39, 3 },
+ { 39, 2 },
+ { 39, 1 },
+ { 37, 1 },
+ { 37, 3 },
+ { 44, 1 },
+ { 44, 0 },
+ { 45, 1 },
+ { 31, 4 },
+ { 32, 4 },
+ { 32, 1 },
+ { 38, 4 },
+ { 46, 7 },
+ { 43, 1 },
+ { 43, 1 },
+ { 43, 1 },
+ { 43, 1 },
+ { 42, 1 },
+ { 33, 2 },
+ { 34, 2 },
+};
+
+static void yy_accept(yyParser*); /* Forward Declaration */
+
+/*
+** Perform a reduce action and the shift that must immediately
+** follow the reduce.
+*/
+static void yy_reduce(
+ yyParser *yypParser, /* The parser */
+ int yyruleno /* Number of the rule by which to reduce */
+){
+ int yygoto; /* The next state */
+ int yyact; /* The next action */
+ YYMINORTYPE yygotominor; /* The LHS of the rule reduced */
+ yyStackEntry *yymsp; /* The top of the parser's stack */
+ int yysize; /* Amount to pop the stack */
+ configparserARG_FETCH;
+ yymsp = &yypParser->yystack[yypParser->yyidx];
+#ifndef NDEBUG
+ if( yyTraceFILE && yyruleno>=0
+ && yyruleno<sizeof(yyRuleName)/sizeof(yyRuleName[0]) ){
+ fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt,
+ yyRuleName[yyruleno]);
+ }
+#endif /* NDEBUG */
+
+ switch( yyruleno ){
+ /* Beginning here are the reduction cases. A typical example
+ ** follows:
+ ** case 0:
+ ** #line <lineno> <grammarfile>
+ ** { ... } // User supplied code
+ ** #line <lineno> <thisfile>
+ ** break;
+ */
+ case 0:
+ /* No destructor defined for metalines */
+ break;
+ case 1:
+ /* No destructor defined for metalines */
+ /* No destructor defined for metaline */
+ break;
+ case 2:
+ break;
+ case 3:
+ /* No destructor defined for varline */
+ break;
+ case 4:
+ /* No destructor defined for global */
+ break;
+ case 5:
+#line 134 "./configparser.y"
+{ yymsp[-1].minor.yy78 = NULL; }
+#line 837 "configparser.c"
+ yy_destructor(1,&yymsp[0].minor);
+ break;
+ case 6:
+ /* No destructor defined for include */
+ break;
+ case 7:
+ /* No destructor defined for include_shell */
+ break;
+ case 8:
+ yy_destructor(1,&yymsp[0].minor);
+ break;
+ case 9:
+#line 162 "./configparser.y"
+{
+ buffer_copy_string_buffer(yymsp[0].minor.yy41->key, yymsp[-2].minor.yy43);
+ if (NULL == array_get_element(ctx->current->value, yymsp[0].minor.yy41->key->ptr)) {
+ array_insert_unique(ctx->current->value, yymsp[0].minor.yy41);
+ yymsp[0].minor.yy41 = NULL;
+ } else {
+ fprintf(stderr, "Duplicate config variable in conditional %d %s: %s\n",
+ ctx->current->context_ndx,
+ ctx->current->key->ptr, yymsp[0].minor.yy41->key->ptr);
+ ctx->ok = 0;
+ yymsp[0].minor.yy41->free(yymsp[0].minor.yy41);
+ yymsp[0].minor.yy41 = NULL;
+ }
+ buffer_free(yymsp[-2].minor.yy43);
+ yymsp[-2].minor.yy43 = NULL;
+}
+#line 867 "configparser.c"
+ yy_destructor(2,&yymsp[-1].minor);
+ break;
+ case 10:
+#line 179 "./configparser.y"
+{
+ array *vars = ctx->current->value;
+ data_unset *du;
+
+ if (NULL != (du = array_get_element(vars, yymsp[-2].minor.yy43->ptr))) {
+ /* exists in current block */
+ du = configparser_merge_data(du, yymsp[0].minor.yy41);
+ if (NULL == du) {
+ ctx->ok = 0;
+ }
+ else {
+ buffer_copy_string_buffer(du->key, yymsp[-2].minor.yy43);
+ array_replace(vars, du);
+ }
+ } else if (NULL != (du = configparser_get_variable(ctx, yymsp[-2].minor.yy43))) {
+ du = configparser_merge_data(du, yymsp[0].minor.yy41);
+ if (NULL == du) {
+ ctx->ok = 0;
+ }
+ else {
+ buffer_copy_string_buffer(du->key, yymsp[-2].minor.yy43);
+ array_insert_unique(ctx->current->value, du);
+ }
+ } else {
+ fprintf(stderr, "Undefined config variable in conditional %d %s: %s\n",
+ ctx->current->context_ndx,
+ ctx->current->key->ptr, yymsp[-2].minor.yy43->ptr);
+ ctx->ok = 0;
+ }
+ buffer_free(yymsp[-2].minor.yy43);
+ yymsp[-2].minor.yy43 = NULL;
+ yymsp[0].minor.yy41->free(yymsp[0].minor.yy41);
+ yymsp[0].minor.yy41 = NULL;
+}
+#line 906 "configparser.c"
+ yy_destructor(3,&yymsp[-1].minor);
+ break;
+ case 11:
+#line 214 "./configparser.y"
+{
+ if (strchr(yymsp[0].minor.yy0->ptr, '.') == NULL) {
+ yygotominor.yy43 = buffer_init_string("var.");
+ buffer_append_string_buffer(yygotominor.yy43, yymsp[0].minor.yy0);
+ buffer_free(yymsp[0].minor.yy0);
+ yymsp[0].minor.yy0 = NULL;
+ } else {
+ yygotominor.yy43 = yymsp[0].minor.yy0;
+ yymsp[0].minor.yy0 = NULL;
+ }
+}
+#line 922 "configparser.c"
+ break;
+ case 12:
+#line 226 "./configparser.y"
+{
+ yygotominor.yy41 = configparser_merge_data(yymsp[-2].minor.yy41, yymsp[0].minor.yy41);
+ if (NULL == yygotominor.yy41) {
+ ctx->ok = 0;
+ }
+ yymsp[-2].minor.yy41 = NULL;
+ yymsp[0].minor.yy41->free(yymsp[0].minor.yy41);
+ yymsp[0].minor.yy41 = NULL;
+}
+#line 935 "configparser.c"
+ yy_destructor(5,&yymsp[-1].minor);
+ break;
+ case 13:
+#line 236 "./configparser.y"
+{
+ yygotominor.yy41 = yymsp[0].minor.yy41;
+ yymsp[0].minor.yy41 = NULL;
+}
+#line 944 "configparser.c"
+ break;
+ case 14:
+#line 241 "./configparser.y"
+{
+ yygotominor.yy41 = configparser_get_variable(ctx, yymsp[0].minor.yy43);
+ if (!yygotominor.yy41) {
+ /* make a dummy so it won't crash */
+ yygotominor.yy41 = (data_unset *)data_string_init();
+ }
+ buffer_free(yymsp[0].minor.yy43);
+ yymsp[0].minor.yy43 = NULL;
+}
+#line 957 "configparser.c"
+ break;
+ case 15:
+#line 251 "./configparser.y"
+{
+ yygotominor.yy41 = (data_unset *)data_string_init();
+ buffer_copy_string_buffer(((data_string *)(yygotominor.yy41))->value, yymsp[0].minor.yy0);
+ buffer_free(yymsp[0].minor.yy0);
+ yymsp[0].minor.yy0 = NULL;
+}
+#line 967 "configparser.c"
+ break;
+ case 16:
+#line 258 "./configparser.y"
+{
+ yygotominor.yy41 = (data_unset *)data_integer_init();
+ ((data_integer *)(yygotominor.yy41))->value = strtol(yymsp[0].minor.yy0->ptr, NULL, 10);
+ buffer_free(yymsp[0].minor.yy0);
+ yymsp[0].minor.yy0 = NULL;
+}
+#line 977 "configparser.c"
+ break;
+ case 17:
+#line 264 "./configparser.y"
+{
+ yygotominor.yy41 = (data_unset *)data_array_init();
+ array_free(((data_array *)(yygotominor.yy41))->value);
+ ((data_array *)(yygotominor.yy41))->value = yymsp[0].minor.yy40;
+ yymsp[0].minor.yy40 = NULL;
+}
+#line 987 "configparser.c"
+ break;
+ case 18:
+#line 270 "./configparser.y"
+{
+ yygotominor.yy40 = yymsp[-1].minor.yy40;
+ yymsp[-1].minor.yy40 = NULL;
+}
+#line 995 "configparser.c"
+ yy_destructor(8,&yymsp[-2].minor);
+ yy_destructor(9,&yymsp[0].minor);
+ break;
+ case 19:
+#line 275 "./configparser.y"
+{
+ if (buffer_is_empty(yymsp[0].minor.yy41->key) ||
+ NULL == array_get_element(yymsp[-2].minor.yy40, yymsp[0].minor.yy41->key->ptr)) {
+ array_insert_unique(yymsp[-2].minor.yy40, yymsp[0].minor.yy41);
+ yymsp[0].minor.yy41 = NULL;
+ } else {
+ fprintf(stderr, "Duplicate array-key: %s\n",
+ yymsp[0].minor.yy41->key->ptr);
+ ctx->ok = 0;
+ yymsp[0].minor.yy41->free(yymsp[0].minor.yy41);
+ yymsp[0].minor.yy41 = NULL;
+ }
+
+ yygotominor.yy40 = yymsp[-2].minor.yy40;
+ yymsp[-2].minor.yy40 = NULL;
+}
+#line 1017 "configparser.c"
+ yy_destructor(10,&yymsp[-1].minor);
+ break;
+ case 20:
+#line 292 "./configparser.y"
+{
+ yygotominor.yy40 = yymsp[-1].minor.yy40;
+ yymsp[-1].minor.yy40 = NULL;
+}
+#line 1026 "configparser.c"
+ yy_destructor(10,&yymsp[0].minor);
+ break;
+ case 21:
+#line 297 "./configparser.y"
+{
+ yygotominor.yy40 = array_init();
+ array_insert_unique(yygotominor.yy40, yymsp[0].minor.yy41);
+ yymsp[0].minor.yy41 = NULL;
+}
+#line 1036 "configparser.c"
+ break;
+ case 22:
+#line 303 "./configparser.y"
+{
+ yygotominor.yy41 = yymsp[0].minor.yy41;
+ yymsp[0].minor.yy41 = NULL;
+}
+#line 1044 "configparser.c"
+ break;
+ case 23:
+#line 307 "./configparser.y"
+{
+ buffer_copy_string_buffer(yymsp[0].minor.yy41->key, yymsp[-2].minor.yy43);
+ buffer_free(yymsp[-2].minor.yy43);
+ yymsp[-2].minor.yy43 = NULL;
+
+ yygotominor.yy41 = yymsp[0].minor.yy41;
+ yymsp[0].minor.yy41 = NULL;
+}
+#line 1056 "configparser.c"
+ yy_destructor(11,&yymsp[-1].minor);
+ break;
+ case 24:
+ yy_destructor(1,&yymsp[0].minor);
+ break;
+ case 25:
+ break;
+ case 26:
+#line 319 "./configparser.y"
+{
+ data_config *dc;
+ dc = (data_config *)array_get_element(ctx->srv->config_context, "global");
+ assert(dc);
+ configparser_push(ctx, dc, 0);
+}
+#line 1072 "configparser.c"
+ yy_destructor(12,&yymsp[0].minor);
+ break;
+ case 27:
+#line 326 "./configparser.y"
+{
+ data_config *cur;
+
+ cur = ctx->current;
+ configparser_pop(ctx);
+
+ assert(cur && ctx->current);
+
+ yygotominor.yy0 = cur;
+}
+#line 1087 "configparser.c"
+ /* No destructor defined for globalstart */
+ yy_destructor(13,&yymsp[-2].minor);
+ /* No destructor defined for metalines */
+ yy_destructor(14,&yymsp[0].minor);
+ break;
+ case 28:
+#line 337 "./configparser.y"
+{
+ assert(yymsp[-3].minor.yy78->context_ndx < yymsp[0].minor.yy78->context_ndx);
+ yymsp[0].minor.yy78->prev = yymsp[-3].minor.yy78;
+ yymsp[-3].minor.yy78->next = yymsp[0].minor.yy78;
+ yygotominor.yy78 = yymsp[0].minor.yy78;
+ yymsp[-3].minor.yy78 = NULL;
+ yymsp[0].minor.yy78 = NULL;
+}
+#line 1103 "configparser.c"
+ /* No destructor defined for eols */
+ yy_destructor(15,&yymsp[-1].minor);
+ break;
+ case 29:
+#line 346 "./configparser.y"
+{
+ yygotominor.yy78 = yymsp[0].minor.yy78;
+ yymsp[0].minor.yy78 = NULL;
+}
+#line 1113 "configparser.c"
+ break;
+ case 30:
+#line 351 "./configparser.y"
+{
+ data_config *cur;
+
+ cur = ctx->current;
+ configparser_pop(ctx);
+
+ assert(cur && ctx->current);
+
+ yygotominor.yy78 = cur;
+}
+#line 1127 "configparser.c"
+ /* No destructor defined for context */
+ yy_destructor(13,&yymsp[-2].minor);
+ /* No destructor defined for metalines */
+ yy_destructor(14,&yymsp[0].minor);
+ break;
+ case 31:
+#line 362 "./configparser.y"
+{
+ data_config *dc;
+ buffer *b, *rvalue, *op;
+
+ if (ctx->ok && yymsp[0].minor.yy41->type != TYPE_STRING) {
+ fprintf(stderr, "rvalue must be string");
+ ctx->ok = 0;
+ }
+
+ switch(yymsp[-1].minor.yy27) {
+ case CONFIG_COND_NE:
+ op = buffer_init_string("!=");
+ break;
+ case CONFIG_COND_EQ:
+ op = buffer_init_string("==");
+ break;
+ case CONFIG_COND_NOMATCH:
+ op = buffer_init_string("!~");
+ break;
+ case CONFIG_COND_MATCH:
+ op = buffer_init_string("=~");
+ break;
+ default:
+ assert(0);
+ return;
+ }
+
+ b = buffer_init();
+ buffer_copy_string_buffer(b, ctx->current->key);
+ buffer_append_string(b, "/");
+ buffer_append_string_buffer(b, yymsp[-5].minor.yy0);
+ buffer_append_string_buffer(b, yymsp[-3].minor.yy43);
+ buffer_append_string_buffer(b, op);
+ rvalue = ((data_string*)yymsp[0].minor.yy41)->value;
+ buffer_append_string_buffer(b, rvalue);
+
+ if (NULL != (dc = (data_config *)array_get_element(ctx->all_configs, b->ptr))) {
+ configparser_push(ctx, dc, 0);
+ } else {
+ struct {
+ comp_key_t comp;
+ char *comp_key;
+ size_t len;
+ } comps[] = {
+ { COMP_SERVER_SOCKET, CONST_STR_LEN("SERVER[\"socket\"]" ) },
+ { COMP_HTTP_URL, CONST_STR_LEN("HTTP[\"url\"]" ) },
+ { COMP_HTTP_HOST, CONST_STR_LEN("HTTP[\"host\"]" ) },
+ { COMP_HTTP_REFERER, CONST_STR_LEN("HTTP[\"referer\"]" ) },
+ { COMP_HTTP_USERAGENT, CONST_STR_LEN("HTTP[\"useragent\"]" ) },
+ { COMP_HTTP_COOKIE, CONST_STR_LEN("HTTP[\"cookie\"]" ) },
+ { COMP_HTTP_REMOTEIP, CONST_STR_LEN("HTTP[\"remoteip\"]" ) },
+ { COMP_UNSET, NULL, 0 },
+ };
+ size_t i;
+
+ dc = data_config_init();
+
+ buffer_copy_string_buffer(dc->key, b);
+ buffer_copy_string_buffer(dc->op, op);
+ buffer_copy_string_buffer(dc->comp_key, yymsp[-5].minor.yy0);
+ buffer_append_string_len(dc->comp_key, CONST_STR_LEN("[\""));
+ buffer_append_string_buffer(dc->comp_key, yymsp[-3].minor.yy43);
+ buffer_append_string_len(dc->comp_key, CONST_STR_LEN("\"]"));
+ dc->cond = yymsp[-1].minor.yy27;
+
+ for (i = 0; comps[i].comp_key; i ++) {
+ if (buffer_is_equal_string(
+ dc->comp_key, comps[i].comp_key, comps[i].len)) {
+ dc->comp = comps[i].comp;
+ break;
+ }
+ }
+ if (COMP_UNSET == dc->comp) {
+ fprintf(stderr, "error comp_key %s", dc->comp_key->ptr);
+ ctx->ok = 0;
+ }
+
+ switch(yymsp[-1].minor.yy27) {
+ case CONFIG_COND_NE:
+ case CONFIG_COND_EQ:
+ dc->string = buffer_init_buffer(rvalue);
+ break;
+ case CONFIG_COND_NOMATCH:
+ case CONFIG_COND_MATCH: {
+#ifdef HAVE_PCRE_H
+ const char *errptr;
+ int erroff;
+
+ if (NULL == (dc->regex =
+ pcre_compile(rvalue->ptr, 0, &errptr, &erroff, NULL))) {
+ dc->string = buffer_init_string(errptr);
+ dc->cond = CONFIG_COND_UNSET;
+
+ fprintf(stderr, "parsing regex failed: %s -> %s at offset %d\n",
+ rvalue->ptr, errptr, erroff);
+
+ ctx->ok = 0;
+ } else if (NULL == (dc->regex_study =
+ pcre_study(dc->regex, 0, &errptr)) &&
+ errptr != NULL) {
+ fprintf(stderr, "studying regex failed: %s -> %s\n",
+ rvalue->ptr, errptr);
+ ctx->ok = 0;
+ } else {
+ dc->string = buffer_init_buffer(rvalue);
+ }
+#else
+ fprintf(stderr, "can't handle '$%s[%s] =~ ...' as you compiled without pcre support. \n"
+ "(perhaps just a missing pcre-devel package ?) \n",
+ yymsp[-5].minor.yy0->ptr, yymsp[-3].minor.yy43->ptr);
+ ctx->ok = 0;
+#endif
+ break;
+ }
+
+ default:
+ fprintf(stderr, "unknown condition for $%s[%s]\n",
+ yymsp[-5].minor.yy0->ptr, yymsp[-3].minor.yy43->ptr);
+ ctx->ok = 0;
+ break;
+ }
+
+ configparser_push(ctx, dc, 1);
+ }
+
+ buffer_free(b);
+ buffer_free(op);
+ buffer_free(yymsp[-5].minor.yy0);
+ yymsp[-5].minor.yy0 = NULL;
+ buffer_free(yymsp[-3].minor.yy43);
+ yymsp[-3].minor.yy43 = NULL;
+ yymsp[0].minor.yy41->free(yymsp[0].minor.yy41);
+ yymsp[0].minor.yy41 = NULL;
+}
+#line 1269 "configparser.c"
+ yy_destructor(16,&yymsp[-6].minor);
+ yy_destructor(18,&yymsp[-4].minor);
+ yy_destructor(19,&yymsp[-2].minor);
+ break;
+ case 32:
+#line 496 "./configparser.y"
+{
+ yygotominor.yy27 = CONFIG_COND_EQ;
+}
+#line 1279 "configparser.c"
+ yy_destructor(20,&yymsp[0].minor);
+ break;
+ case 33:
+#line 499 "./configparser.y"
+{
+ yygotominor.yy27 = CONFIG_COND_MATCH;
+}
+#line 1287 "configparser.c"
+ yy_destructor(21,&yymsp[0].minor);
+ break;
+ case 34:
+#line 502 "./configparser.y"
+{
+ yygotominor.yy27 = CONFIG_COND_NE;
+}
+#line 1295 "configparser.c"
+ yy_destructor(22,&yymsp[0].minor);
+ break;
+ case 35:
+#line 505 "./configparser.y"
+{
+ yygotominor.yy27 = CONFIG_COND_NOMATCH;
+}
+#line 1303 "configparser.c"
+ yy_destructor(23,&yymsp[0].minor);
+ break;
+ case 36:
+#line 509 "./configparser.y"
+{
+ yygotominor.yy43 = NULL;
+ if (ctx->ok) {
+ if (yymsp[0].minor.yy41->type == TYPE_STRING) {
+ yygotominor.yy43 = buffer_init_buffer(((data_string*)yymsp[0].minor.yy41)->value);
+ } else if (yymsp[0].minor.yy41->type == TYPE_INTEGER) {
+ yygotominor.yy43 = buffer_init();
+ buffer_copy_long(yygotominor.yy43, ((data_integer *)yymsp[0].minor.yy41)->value);
+ } else {
+ fprintf(stderr, "operand must be string");
+ ctx->ok = 0;
+ }
+ }
+ yymsp[0].minor.yy41->free(yymsp[0].minor.yy41);
+ yymsp[0].minor.yy41 = NULL;
+}
+#line 1324 "configparser.c"
+ break;
+ case 37:
+#line 526 "./configparser.y"
+{
+ if (ctx->ok) {
+ if (0 != config_parse_file(ctx->srv, ctx, yymsp[0].minor.yy43->ptr)) {
+ ctx->ok = 0;
+ }
+ buffer_free(yymsp[0].minor.yy43);
+ yymsp[0].minor.yy43 = NULL;
+ }
+}
+#line 1337 "configparser.c"
+ yy_destructor(24,&yymsp[-1].minor);
+ break;
+ case 38:
+#line 536 "./configparser.y"
+{
+ if (ctx->ok) {
+ if (0 != config_parse_cmd(ctx->srv, ctx, yymsp[0].minor.yy43->ptr)) {
+ ctx->ok = 0;
+ }
+ buffer_free(yymsp[0].minor.yy43);
+ yymsp[0].minor.yy43 = NULL;
+ }
+}
+#line 1351 "configparser.c"
+ yy_destructor(25,&yymsp[-1].minor);
+ break;
+ };
+ yygoto = yyRuleInfo[yyruleno].lhs;
+ yysize = yyRuleInfo[yyruleno].nrhs;
+ yypParser->yyidx -= yysize;
+ yyact = yy_find_reduce_action(yypParser,yygoto);
+ if( yyact < YYNSTATE ){
+ yy_shift(yypParser,yyact,yygoto,&yygotominor);
+ }else if( yyact == YYNSTATE + YYNRULE + 1 ){
+ yy_accept(yypParser);
+ }
+}
+
+/*
+** The following code executes when the parse fails
+*/
+static void yy_parse_failed(
+ yyParser *yypParser /* The parser */
+){
+ configparserARG_FETCH;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser fails */
+#line 125 "./configparser.y"
+
+ ctx->ok = 0;
+
+#line 1385 "configparser.c"
+ configparserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following code executes when a syntax error first occurs.
+*/
+static void yy_syntax_error(
+ yyParser *yypParser, /* The parser */
+ int yymajor, /* The major type of the error token */
+ YYMINORTYPE yyminor /* The minor type of the error token */
+){
+ configparserARG_FETCH;
+#define TOKEN (yyminor.yy0)
+ configparserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+ yyParser *yypParser /* The parser */
+){
+ configparserARG_FETCH;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser accepts */
+ configparserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "configparserAlloc" which describes the current state of the parser.
+** The second argument is the major token number. The third is
+** the minor token. The fourth optional argument is whatever the
+** user wants (and specified in the grammar) and is available for
+** use by the action routines.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void configparser(
+ void *yyp, /* The parser */
+ int yymajor, /* The major token code number */
+ configparserTOKENTYPE yyminor /* The value for the token */
+ configparserARG_PDECL /* Optional %extra_argument parameter */
+){
+ YYMINORTYPE yyminorunion;
+ int yyact; /* The parser action. */
+ int yyendofinput; /* True if we are at the end of input */
+ int yyerrorhit = 0; /* True if yymajor has invoked an error */
+ yyParser *yypParser; /* The parser */
+
+ /* (re)initialize the parser, if necessary */
+ yypParser = (yyParser*)yyp;
+ if( yypParser->yyidx<0 ){
+ if( yymajor==0 ) return;
+ yypParser->yyidx = 0;
+ yypParser->yyerrcnt = -1;
+ yypParser->yystack[0].stateno = 0;
+ yypParser->yystack[0].major = 0;
+ }
+ yyminorunion.yy0 = yyminor;
+ yyendofinput = (yymajor==0);
+ configparserARG_STORE;
+
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+
+ do{
+ yyact = yy_find_shift_action(yypParser,yymajor);
+ if( yyact<YYNSTATE ){
+ yy_shift(yypParser,yyact,yymajor,&yyminorunion);
+ yypParser->yyerrcnt--;
+ if( yyendofinput && yypParser->yyidx>=0 ){
+ yymajor = 0;
+ }else{
+ yymajor = YYNOCODE;
+ }
+ }else if( yyact < YYNSTATE + YYNRULE ){
+ yy_reduce(yypParser,yyact-YYNSTATE);
+ }else if( yyact == YY_ERROR_ACTION ){
+ int yymx;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
+ }
+#endif
+#ifdef YYERRORSYMBOL
+ /* A syntax error has occurred.
+ ** The response to an error depends upon whether or not the
+ ** grammar defines an error token "ERROR".
+ **
+ ** This is what we do if the grammar does define ERROR:
+ **
+ ** * Call the %syntax_error function.
+ **
+ ** * Begin popping the stack until we enter a state where
+ ** it is legal to shift the error symbol, then shift
+ ** the error symbol.
+ **
+ ** * Set the error count to three.
+ **
+ ** * Begin accepting and shifting new tokens. No new error
+ ** processing will occur until three tokens have been
+ ** shifted successfully.
+ **
+ */
+ if( yypParser->yyerrcnt<0 ){
+ yy_syntax_error(yypParser,yymajor,yyminorunion);
+ }
+ yymx = yypParser->yystack[yypParser->yyidx].major;
+ if( yymx==YYERRORSYMBOL || yyerrorhit ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sDiscard input token %s\n",
+ yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+ yy_destructor(yymajor,&yyminorunion);
+ yymajor = YYNOCODE;
+ }else{
+ while(
+ yypParser->yyidx >= 0 &&
+ yymx != YYERRORSYMBOL &&
+ (yyact = yy_find_shift_action(yypParser,YYERRORSYMBOL)) >= YYNSTATE
+ ){
+ yy_pop_parser_stack(yypParser);
+ }
+ if( yypParser->yyidx < 0 || yymajor==0 ){
+ yy_destructor(yymajor,&yyminorunion);
+ yy_parse_failed(yypParser);
+ yymajor = YYNOCODE;
+ }else if( yymx!=YYERRORSYMBOL ){
+ YYMINORTYPE u2;
+ u2.YYERRSYMDT = 0;
+ yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2);
+ }
+ }
+ yypParser->yyerrcnt = 3;
+ yyerrorhit = 1;
+#else /* YYERRORSYMBOL is not defined */
+ /* This is what we do if the grammar does not define ERROR:
+ **
+ ** * Report an error message, and throw away the input token.
+ **
+ ** * If the input token is $, then fail the parse.
+ **
+ ** As before, subsequent error messages are suppressed until
+ ** three input tokens have been successfully shifted.
+ */
+ if( yypParser->yyerrcnt<=0 ){
+ yy_syntax_error(yypParser,yymajor,yyminorunion);
+ }
+ yypParser->yyerrcnt = 3;
+ yy_destructor(yymajor,&yyminorunion);
+ if( yyendofinput ){
+ yy_parse_failed(yypParser);
+ }
+ yymajor = YYNOCODE;
+#endif
+ }else{
+ yy_accept(yypParser);
+ yymajor = YYNOCODE;
+ }
+ }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 );
+ return;
+}
diff --git a/src/configparser.h b/src/configparser.h
new file mode 100644
index 0000000..1610621
--- /dev/null
+++ b/src/configparser.h
@@ -0,0 +1,25 @@
+#define TK_EOL 1
+#define TK_ASSIGN 2
+#define TK_APPEND 3
+#define TK_LKEY 4
+#define TK_PLUS 5
+#define TK_STRING 6
+#define TK_INTEGER 7
+#define TK_LPARAN 8
+#define TK_RPARAN 9
+#define TK_COMMA 10
+#define TK_ARRAY_ASSIGN 11
+#define TK_GLOBAL 12
+#define TK_LCURLY 13
+#define TK_RCURLY 14
+#define TK_ELSE 15
+#define TK_DOLLAR 16
+#define TK_SRVVARNAME 17
+#define TK_LBRACKET 18
+#define TK_RBRACKET 19
+#define TK_EQ 20
+#define TK_MATCH 21
+#define TK_NE 22
+#define TK_NOMATCH 23
+#define TK_INCLUDE 24
+#define TK_INCLUDE_SHELL 25
diff --git a/src/configparser.y b/src/configparser.y
new file mode 100644
index 0000000..ef65061
--- /dev/null
+++ b/src/configparser.y
@@ -0,0 +1,544 @@
+%token_prefix TK_
+%extra_argument {config_t *ctx}
+%name configparser
+
+%include {
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "configfile.h"
+#include "buffer.h"
+#include "array.h"
+
+static void configparser_push(config_t *ctx, data_config *dc, int isnew) {
+ if (isnew) {
+ dc->context_ndx = ctx->all_configs->used;
+ assert(dc->context_ndx > ctx->current->context_ndx);
+ array_insert_unique(ctx->all_configs, (data_unset *)dc);
+ dc->parent = ctx->current;
+ array_insert_unique(dc->parent->childs, (data_unset *)dc);
+ }
+ array_insert_unique(ctx->configs_stack, (data_unset *)ctx->current);
+ ctx->current = dc;
+}
+
+static data_config *configparser_pop(config_t *ctx) {
+ data_config *old = ctx->current;
+ ctx->current = (data_config *) array_pop(ctx->configs_stack);
+ return old;
+}
+
+/* return a copied variable */
+static data_unset *configparser_get_variable(config_t *ctx, const buffer *key) {
+ if (strncmp(key->ptr, "env.", sizeof("env.") - 1) == 0) {
+ char *env;
+
+ if (NULL != (env = getenv(key->ptr + 4))) {
+ data_string *ds;
+ ds = data_string_init();
+ buffer_append_string(ds->value, env);
+ return (data_unset *)ds;
+ }
+
+ fprintf(stderr, "Undefined env variable: %s\n", key->ptr + 4);
+ ctx->ok = 0;
+
+ return NULL;
+ } else {
+ data_unset *du;
+ data_config *dc;
+
+#if 0
+ fprintf(stderr, "get var %s\n", key->ptr);
+#endif
+ for (dc = ctx->current; dc; dc = dc->parent) {
+#if 0
+ fprintf(stderr, "get var on block: %s\n", dc->key->ptr);
+ array_print(dc->value, 0);
+#endif
+ if (NULL != (du = array_get_element(dc->value, key->ptr))) {
+ return du->copy(du);
+ }
+ }
+ fprintf(stderr, "Undefined config variable: %s\n", key->ptr);
+ ctx->ok = 0;
+ return NULL;
+ }
+}
+
+/* op1 is to be eat/return by this function, op1->key is not cared
+ op2 is left untouch, unreferenced
+ */
+data_unset *configparser_merge_data(data_unset *op1, const data_unset *op2) {
+ /* type mismatch */
+ if (op1->type != op2->type) {
+ if (op1->type == TYPE_STRING && op2->type == TYPE_INTEGER) {
+ data_string *ds = (data_string *)op1;
+ buffer_append_long(ds->value, ((data_integer*)op2)->value);
+ return op1;
+ } else if (op1->type == TYPE_INTEGER && op2->type == TYPE_STRING) {
+ data_string *ds = data_string_init();
+ buffer_append_long(ds->value, ((data_integer*)op1)->value);
+ buffer_append_string_buffer(ds->value, ((data_string*)op2)->value);
+ op1->free(op1);
+ return (data_unset *)ds;
+ } else {
+ fprintf(stderr, "data type mismatch, cannot be merge\n");
+ op1->free(op1);
+ return NULL;
+ }
+ }
+
+ switch (op1->type) {
+ case TYPE_STRING:
+ buffer_append_string_buffer(((data_string *)op1)->value, ((data_string *)op2)->value);
+ break;
+ case TYPE_INTEGER:
+ ((data_integer *)op1)->value += ((data_integer *)op2)->value;
+ break;
+ case TYPE_ARRAY: {
+ array *dst = ((data_array *)op1)->value;
+ array *src = ((data_array *)op2)->value;
+ data_unset *du;
+ size_t i;
+
+ for (i = 0; i < src->used; i ++) {
+ du = (data_unset *)src->data[i];
+ if (du) {
+ array_insert_unique(dst, du->copy(du));
+ }
+ }
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ }
+ return op1;
+}
+
+}
+
+%parse_failure {
+ ctx->ok = 0;
+}
+
+input ::= metalines.
+metalines ::= metalines metaline.
+metalines ::= .
+metaline ::= varline.
+metaline ::= global.
+metaline ::= condlines(A) EOL. { A = NULL; }
+metaline ::= include.
+metaline ::= include_shell.
+metaline ::= EOL.
+
+%type value {data_unset *}
+%type expression {data_unset *}
+%type aelement {data_unset *}
+%type condline {data_config *}
+%type condlines {data_config *}
+%type aelements {array *}
+%type array {array *}
+%type key {buffer *}
+%type stringop {buffer *}
+
+%type cond {config_cond_t }
+
+%destructor value { $$->free($$); }
+%destructor expression { $$->free($$); }
+%destructor aelement { $$->free($$); }
+%destructor aelements { array_free($$); }
+%destructor array { array_free($$); }
+%destructor key { buffer_free($$); }
+%destructor stringop { buffer_free($$); }
+
+%token_type {buffer *}
+%token_destructor { buffer_free($$); }
+
+varline ::= key(A) ASSIGN expression(B). {
+ buffer_copy_string_buffer(B->key, A);
+ if (NULL == array_get_element(ctx->current->value, B->key->ptr)) {
+ array_insert_unique(ctx->current->value, B);
+ B = NULL;
+ } else {
+ fprintf(stderr, "Duplicate config variable in conditional %d %s: %s\n",
+ ctx->current->context_ndx,
+ ctx->current->key->ptr, B->key->ptr);
+ ctx->ok = 0;
+ B->free(B);
+ B = NULL;
+ }
+ buffer_free(A);
+ A = NULL;
+}
+
+varline ::= key(A) APPEND expression(B). {
+ array *vars = ctx->current->value;
+ data_unset *du;
+
+ if (NULL != (du = array_get_element(vars, A->ptr))) {
+ /* exists in current block */
+ du = configparser_merge_data(du, B);
+ if (NULL == du) {
+ ctx->ok = 0;
+ }
+ else {
+ buffer_copy_string_buffer(du->key, A);
+ array_replace(vars, du);
+ }
+ } else if (NULL != (du = configparser_get_variable(ctx, A))) {
+ du = configparser_merge_data(du, B);
+ if (NULL == du) {
+ ctx->ok = 0;
+ }
+ else {
+ buffer_copy_string_buffer(du->key, A);
+ array_insert_unique(ctx->current->value, du);
+ }
+ } else {
+ fprintf(stderr, "Undefined config variable in conditional %d %s: %s\n",
+ ctx->current->context_ndx,
+ ctx->current->key->ptr, A->ptr);
+ ctx->ok = 0;
+ }
+ buffer_free(A);
+ A = NULL;
+ B->free(B);
+ B = NULL;
+}
+
+key(A) ::= LKEY(B). {
+ if (strchr(B->ptr, '.') == NULL) {
+ A = buffer_init_string("var.");
+ buffer_append_string_buffer(A, B);
+ buffer_free(B);
+ B = NULL;
+ } else {
+ A = B;
+ B = NULL;
+ }
+}
+
+expression(A) ::= expression(B) PLUS value(C). {
+ A = configparser_merge_data(B, C);
+ if (NULL == A) {
+ ctx->ok = 0;
+ }
+ B = NULL;
+ C->free(C);
+ C = NULL;
+}
+
+expression(A) ::= value(B). {
+ A = B;
+ B = NULL;
+}
+
+value(A) ::= key(B). {
+ A = configparser_get_variable(ctx, B);
+ if (!A) {
+ /* make a dummy so it won't crash */
+ A = (data_unset *)data_string_init();
+ }
+ buffer_free(B);
+ B = NULL;
+}
+
+value(A) ::= STRING(B). {
+ A = (data_unset *)data_string_init();
+ buffer_copy_string_buffer(((data_string *)(A))->value, B);
+ buffer_free(B);
+ B = NULL;
+}
+
+value(A) ::= INTEGER(B). {
+ A = (data_unset *)data_integer_init();
+ ((data_integer *)(A))->value = strtol(B->ptr, NULL, 10);
+ buffer_free(B);
+ B = NULL;
+}
+value(A) ::= array(B). {
+ A = (data_unset *)data_array_init();
+ array_free(((data_array *)(A))->value);
+ ((data_array *)(A))->value = B;
+ B = NULL;
+}
+array(A) ::= LPARAN aelements(B) RPARAN. {
+ A = B;
+ B = NULL;
+}
+
+aelements(A) ::= aelements(C) COMMA aelement(B). {
+ if (buffer_is_empty(B->key) ||
+ NULL == array_get_element(C, B->key->ptr)) {
+ array_insert_unique(C, B);
+ B = NULL;
+ } else {
+ fprintf(stderr, "Duplicate array-key: %s\n",
+ B->key->ptr);
+ ctx->ok = 0;
+ B->free(B);
+ B = NULL;
+ }
+
+ A = C;
+ C = NULL;
+}
+
+aelements(A) ::= aelements(C) COMMA. {
+ A = C;
+ C = NULL;
+}
+
+aelements(A) ::= aelement(B). {
+ A = array_init();
+ array_insert_unique(A, B);
+ B = NULL;
+}
+
+aelement(A) ::= expression(B). {
+ A = B;
+ B = NULL;
+}
+aelement(A) ::= stringop(B) ARRAY_ASSIGN expression(C). {
+ buffer_copy_string_buffer(C->key, B);
+ buffer_free(B);
+ B = NULL;
+
+ A = C;
+ C = NULL;
+}
+
+eols ::= EOL.
+eols ::= .
+
+globalstart ::= GLOBAL. {
+ data_config *dc;
+ dc = (data_config *)array_get_element(ctx->srv->config_context, "global");
+ assert(dc);
+ configparser_push(ctx, dc, 0);
+}
+
+global(A) ::= globalstart LCURLY metalines RCURLY. {
+ data_config *cur;
+
+ cur = ctx->current;
+ configparser_pop(ctx);
+
+ assert(cur && ctx->current);
+
+ A = cur;
+}
+
+condlines(A) ::= condlines(B) eols ELSE condline(C). {
+ assert(B->context_ndx < C->context_ndx);
+ C->prev = B;
+ B->next = C;
+ A = C;
+ B = NULL;
+ C = NULL;
+}
+
+condlines(A) ::= condline(B). {
+ A = B;
+ B = NULL;
+}
+
+condline(A) ::= context LCURLY metalines RCURLY. {
+ data_config *cur;
+
+ cur = ctx->current;
+ configparser_pop(ctx);
+
+ assert(cur && ctx->current);
+
+ A = cur;
+}
+
+context ::= DOLLAR SRVVARNAME(B) LBRACKET stringop(C) RBRACKET cond(E) expression(D). {
+ data_config *dc;
+ buffer *b, *rvalue, *op;
+
+ if (ctx->ok && D->type != TYPE_STRING) {
+ fprintf(stderr, "rvalue must be string");
+ ctx->ok = 0;
+ }
+
+ switch(E) {
+ case CONFIG_COND_NE:
+ op = buffer_init_string("!=");
+ break;
+ case CONFIG_COND_EQ:
+ op = buffer_init_string("==");
+ break;
+ case CONFIG_COND_NOMATCH:
+ op = buffer_init_string("!~");
+ break;
+ case CONFIG_COND_MATCH:
+ op = buffer_init_string("=~");
+ break;
+ default:
+ assert(0);
+ return;
+ }
+
+ b = buffer_init();
+ buffer_copy_string_buffer(b, ctx->current->key);
+ buffer_append_string(b, "/");
+ buffer_append_string_buffer(b, B);
+ buffer_append_string_buffer(b, C);
+ buffer_append_string_buffer(b, op);
+ rvalue = ((data_string*)D)->value;
+ buffer_append_string_buffer(b, rvalue);
+
+ if (NULL != (dc = (data_config *)array_get_element(ctx->all_configs, b->ptr))) {
+ configparser_push(ctx, dc, 0);
+ } else {
+ struct {
+ comp_key_t comp;
+ char *comp_key;
+ size_t len;
+ } comps[] = {
+ { COMP_SERVER_SOCKET, CONST_STR_LEN("SERVER[\"socket\"]" ) },
+ { COMP_HTTP_URL, CONST_STR_LEN("HTTP[\"url\"]" ) },
+ { COMP_HTTP_HOST, CONST_STR_LEN("HTTP[\"host\"]" ) },
+ { COMP_HTTP_REFERER, CONST_STR_LEN("HTTP[\"referer\"]" ) },
+ { COMP_HTTP_USERAGENT, CONST_STR_LEN("HTTP[\"useragent\"]" ) },
+ { COMP_HTTP_COOKIE, CONST_STR_LEN("HTTP[\"cookie\"]" ) },
+ { COMP_HTTP_REMOTEIP, CONST_STR_LEN("HTTP[\"remoteip\"]" ) },
+ { COMP_UNSET, NULL, 0 },
+ };
+ size_t i;
+
+ dc = data_config_init();
+
+ buffer_copy_string_buffer(dc->key, b);
+ buffer_copy_string_buffer(dc->op, op);
+ buffer_copy_string_buffer(dc->comp_key, B);
+ buffer_append_string_len(dc->comp_key, CONST_STR_LEN("[\""));
+ buffer_append_string_buffer(dc->comp_key, C);
+ buffer_append_string_len(dc->comp_key, CONST_STR_LEN("\"]"));
+ dc->cond = E;
+
+ for (i = 0; comps[i].comp_key; i ++) {
+ if (buffer_is_equal_string(
+ dc->comp_key, comps[i].comp_key, comps[i].len)) {
+ dc->comp = comps[i].comp;
+ break;
+ }
+ }
+ if (COMP_UNSET == dc->comp) {
+ fprintf(stderr, "error comp_key %s", dc->comp_key->ptr);
+ ctx->ok = 0;
+ }
+
+ switch(E) {
+ case CONFIG_COND_NE:
+ case CONFIG_COND_EQ:
+ dc->string = buffer_init_buffer(rvalue);
+ break;
+ case CONFIG_COND_NOMATCH:
+ case CONFIG_COND_MATCH: {
+#ifdef HAVE_PCRE_H
+ const char *errptr;
+ int erroff;
+
+ if (NULL == (dc->regex =
+ pcre_compile(rvalue->ptr, 0, &errptr, &erroff, NULL))) {
+ dc->string = buffer_init_string(errptr);
+ dc->cond = CONFIG_COND_UNSET;
+
+ fprintf(stderr, "parsing regex failed: %s -> %s at offset %d\n",
+ rvalue->ptr, errptr, erroff);
+
+ ctx->ok = 0;
+ } else if (NULL == (dc->regex_study =
+ pcre_study(dc->regex, 0, &errptr)) &&
+ errptr != NULL) {
+ fprintf(stderr, "studying regex failed: %s -> %s\n",
+ rvalue->ptr, errptr);
+ ctx->ok = 0;
+ } else {
+ dc->string = buffer_init_buffer(rvalue);
+ }
+#else
+ fprintf(stderr, "can't handle '$%s[%s] =~ ...' as you compiled without pcre support. \n"
+ "(perhaps just a missing pcre-devel package ?) \n",
+ B->ptr, C->ptr);
+ ctx->ok = 0;
+#endif
+ break;
+ }
+
+ default:
+ fprintf(stderr, "unknown condition for $%s[%s]\n",
+ B->ptr, C->ptr);
+ ctx->ok = 0;
+ break;
+ }
+
+ configparser_push(ctx, dc, 1);
+ }
+
+ buffer_free(b);
+ buffer_free(op);
+ buffer_free(B);
+ B = NULL;
+ buffer_free(C);
+ C = NULL;
+ D->free(D);
+ D = NULL;
+}
+cond(A) ::= EQ. {
+ A = CONFIG_COND_EQ;
+}
+cond(A) ::= MATCH. {
+ A = CONFIG_COND_MATCH;
+}
+cond(A) ::= NE. {
+ A = CONFIG_COND_NE;
+}
+cond(A) ::= NOMATCH. {
+ A = CONFIG_COND_NOMATCH;
+}
+
+stringop(A) ::= expression(B). {
+ A = NULL;
+ if (ctx->ok) {
+ if (B->type == TYPE_STRING) {
+ A = buffer_init_buffer(((data_string*)B)->value);
+ } else if (B->type == TYPE_INTEGER) {
+ A = buffer_init();
+ buffer_copy_long(A, ((data_integer *)B)->value);
+ } else {
+ fprintf(stderr, "operand must be string");
+ ctx->ok = 0;
+ }
+ }
+ B->free(B);
+ B = NULL;
+}
+
+include ::= INCLUDE stringop(A). {
+ if (ctx->ok) {
+ if (0 != config_parse_file(ctx->srv, ctx, A->ptr)) {
+ ctx->ok = 0;
+ }
+ buffer_free(A);
+ A = NULL;
+ }
+}
+
+include_shell ::= INCLUDE_SHELL stringop(A). {
+ if (ctx->ok) {
+ if (0 != config_parse_cmd(ctx->srv, ctx, A->ptr)) {
+ ctx->ok = 0;
+ }
+ buffer_free(A);
+ A = NULL;
+ }
+}
diff --git a/src/connections-glue.c b/src/connections-glue.c
new file mode 100644
index 0000000..ac6d267
--- /dev/null
+++ b/src/connections-glue.c
@@ -0,0 +1,44 @@
+#include "base.h"
+
+const char *connection_get_state(connection_state_t state) {
+ switch (state) {
+ case CON_STATE_CONNECT: return "connect";
+ case CON_STATE_READ: return "read";
+ case CON_STATE_READ_POST: return "readpost";
+ case CON_STATE_WRITE: return "write";
+ case CON_STATE_CLOSE: return "close";
+ case CON_STATE_ERROR: return "error";
+ case CON_STATE_HANDLE_REQUEST: return "handle-req";
+ case CON_STATE_REQUEST_START: return "req-start";
+ case CON_STATE_REQUEST_END: return "req-end";
+ case CON_STATE_RESPONSE_START: return "resp-start";
+ case CON_STATE_RESPONSE_END: return "resp-end";
+ default: return "(unknown)";
+ }
+}
+
+const char *connection_get_short_state(connection_state_t state) {
+ switch (state) {
+ case CON_STATE_CONNECT: return ".";
+ case CON_STATE_READ: return "r";
+ case CON_STATE_READ_POST: return "R";
+ case CON_STATE_WRITE: return "W";
+ case CON_STATE_CLOSE: return "C";
+ case CON_STATE_ERROR: return "E";
+ case CON_STATE_HANDLE_REQUEST: return "h";
+ case CON_STATE_REQUEST_START: return "q";
+ case CON_STATE_REQUEST_END: return "Q";
+ case CON_STATE_RESPONSE_START: return "s";
+ case CON_STATE_RESPONSE_END: return "S";
+ default: return "x";
+ }
+}
+
+int connection_set_state(server *srv, connection *con, connection_state_t state) {
+ UNUSED(srv);
+
+ con->state = state;
+
+ return 0;
+}
+
diff --git a/src/connections.c b/src/connections.c
new file mode 100644
index 0000000..e4db410
--- /dev/null
+++ b/src/connections.c
@@ -0,0 +1,1708 @@
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "buffer.h"
+#include "server.h"
+#include "log.h"
+#include "connections.h"
+#include "fdevent.h"
+
+#include "request.h"
+#include "response.h"
+#include "network.h"
+#include "http_chunk.h"
+#include "stat_cache.h"
+#include "joblist.h"
+
+#include "plugin.h"
+
+#include "inet_ntop_cache.h"
+
+#ifdef USE_OPENSSL
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#endif
+
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#include "sys-socket.h"
+
+typedef struct {
+ PLUGIN_DATA;
+} plugin_data;
+
+static connection *connections_get_new_connection(server *srv) {
+ connections *conns = srv->conns;
+ size_t i;
+
+ if (conns->size == 0) {
+ conns->size = 128;
+ conns->ptr = NULL;
+ conns->ptr = malloc(sizeof(*conns->ptr) * conns->size);
+ for (i = 0; i < conns->size; i++) {
+ conns->ptr[i] = connection_init(srv);
+ }
+ } else if (conns->size == conns->used) {
+ conns->size += 128;
+ conns->ptr = realloc(conns->ptr, sizeof(*conns->ptr) * conns->size);
+
+ for (i = conns->used; i < conns->size; i++) {
+ conns->ptr[i] = connection_init(srv);
+ }
+ }
+
+ connection_reset(srv, conns->ptr[conns->used]);
+#if 0
+ fprintf(stderr, "%s.%d: add: ", __FILE__, __LINE__);
+ for (i = 0; i < conns->used + 1; i++) {
+ fprintf(stderr, "%d ", conns->ptr[i]->fd);
+ }
+ fprintf(stderr, "\n");
+#endif
+
+ conns->ptr[conns->used]->ndx = conns->used;
+ return conns->ptr[conns->used++];
+}
+
+static int connection_del(server *srv, connection *con) {
+ size_t i;
+ connections *conns = srv->conns;
+ connection *temp;
+
+ if (con == NULL) return -1;
+
+ if (-1 == con->ndx) return -1;
+
+ i = con->ndx;
+
+ /* not last element */
+
+ if (i != conns->used - 1) {
+ temp = conns->ptr[i];
+ conns->ptr[i] = conns->ptr[conns->used - 1];
+ conns->ptr[conns->used - 1] = temp;
+
+ conns->ptr[i]->ndx = i;
+ conns->ptr[conns->used - 1]->ndx = -1;
+ }
+
+ conns->used--;
+
+ con->ndx = -1;
+#if 0
+ fprintf(stderr, "%s.%d: del: (%d)", __FILE__, __LINE__, conns->used);
+ for (i = 0; i < conns->used; i++) {
+ fprintf(stderr, "%d ", conns->ptr[i]->fd);
+ }
+ fprintf(stderr, "\n");
+#endif
+ return 0;
+}
+
+int connection_close(server *srv, connection *con) {
+#ifdef USE_OPENSSL
+ server_socket *srv_sock = con->srv_socket;
+#endif
+
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ if (con->ssl) SSL_free(con->ssl);
+ con->ssl = NULL;
+ }
+#endif
+
+ fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
+ fdevent_unregister(srv->ev, con->fd);
+#ifdef __WIN32
+ if (closesocket(con->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "(warning) close:", con->fd, strerror(errno));
+ }
+#else
+ if (close(con->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "(warning) close:", con->fd, strerror(errno));
+ }
+#endif
+
+ srv->cur_fds--;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "closed()", con->fd);
+#endif
+
+ connection_del(srv, con);
+ connection_set_state(srv, con, CON_STATE_CONNECT);
+
+ return 0;
+}
+
+#if 0
+static void dump_packet(const unsigned char *data, size_t len) {
+ size_t i, j;
+
+ if (len == 0) return;
+
+ for (i = 0; i < len; i++) {
+ if (i % 16 == 0) fprintf(stderr, " ");
+
+ fprintf(stderr, "%02x ", data[i]);
+
+ if ((i + 1) % 16 == 0) {
+ fprintf(stderr, " ");
+ for (j = 0; j <= i % 16; j++) {
+ unsigned char c;
+
+ if (i-15+j >= len) break;
+
+ c = data[i-15+j];
+
+ fprintf(stderr, "%c", c > 32 && c < 128 ? c : '.');
+ }
+
+ fprintf(stderr, "\n");
+ }
+ }
+
+ if (len % 16 != 0) {
+ for (j = i % 16; j < 16; j++) {
+ fprintf(stderr, " ");
+ }
+
+ fprintf(stderr, " ");
+ for (j = i & ~0xf; j < len; j++) {
+ unsigned char c;
+
+ c = data[j];
+ fprintf(stderr, "%c", c > 32 && c < 128 ? c : '.');
+ }
+ fprintf(stderr, "\n");
+ }
+}
+#endif
+
+static int connection_handle_read(server *srv, connection *con) {
+ int len;
+ buffer *b;
+ int toread;
+#ifdef USE_OPENSSL
+ server_socket *srv_sock = con->srv_socket;
+#endif
+
+ b = chunkqueue_get_append_buffer(con->read_queue);
+ buffer_prepare_copy(b, 4096);
+
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ len = SSL_read(con->ssl, b->ptr, b->size - 1);
+ } else {
+ if (ioctl(con->fd, FIONREAD, &toread)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "unexpected end-of-file:",
+ con->fd);
+ return -1;
+ }
+ buffer_prepare_copy(b, toread);
+
+ len = read(con->fd, b->ptr, b->size - 1);
+ }
+#elif defined(__WIN32)
+ len = recv(con->fd, b->ptr, b->size - 1, 0);
+#else
+ if (ioctl(con->fd, FIONREAD, &toread)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "unexpected end-of-file:",
+ con->fd);
+ return -1;
+ }
+ buffer_prepare_copy(b, toread);
+
+ len = read(con->fd, b->ptr, b->size - 1);
+#endif
+
+ if (len < 0) {
+ con->is_readable = 0;
+
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ int r, ssl_err;
+
+ switch ((r = SSL_get_error(con->ssl, len))) {
+ case SSL_ERROR_WANT_READ:
+ return 0;
+ case SSL_ERROR_SYSCALL:
+ /**
+ * man SSL_get_error()
+ *
+ * SSL_ERROR_SYSCALL
+ * Some I/O error occurred. The OpenSSL error queue may contain more
+ * information on the error. If the error queue is empty (i.e.
+ * ERR_get_error() returns 0), ret can be used to find out more about
+ * the error: If ret == 0, an EOF was observed that violates the
+ * protocol. If ret == -1, the underlying BIO reported an I/O error
+ * (for socket I/O on Unix systems, consult errno for details).
+ *
+ */
+ while((ssl_err = ERR_get_error())) {
+ /* get all errors from the error-queue */
+ log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:",
+ r, ERR_error_string(ssl_err, NULL));
+ }
+
+ switch(errno) {
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL:",
+ len, r, errno,
+ strerror(errno));
+ break;
+ }
+
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ /* clean shutdown on the remote side */
+
+ if (r == 0) {
+ /* FIXME: later */
+ }
+
+ /* fall thourgh */
+ default:
+ while((ssl_err = ERR_get_error())) {
+ /* get all errors from the error-queue */
+ log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:",
+ r, ERR_error_string(ssl_err, NULL));
+ }
+ break;
+ }
+ } else {
+ if (errno == EAGAIN) return 0;
+ if (errno == EINTR) {
+ /* we have been interrupted before we could read */
+ con->is_readable = 1;
+ return 0;
+ }
+
+ if (errno != ECONNRESET) {
+ /* expected for keep-alive */
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno);
+ }
+ }
+#else
+ if (errno == EAGAIN) return 0;
+ if (errno == EINTR) {
+ /* we have been interrupted before we could read */
+ con->is_readable = 1;
+ return 0;
+ }
+
+ if (errno != ECONNRESET) {
+ /* expected for keep-alive */
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno);
+ }
+#endif
+ connection_set_state(srv, con, CON_STATE_ERROR);
+
+ return -1;
+ } else if (len == 0) {
+ con->is_readable = 0;
+ /* the other end close the connection -> KEEP-ALIVE */
+
+ /* pipelining */
+
+ return -2;
+ } else if ((size_t)len < b->size - 1) {
+ /* we got less then expected, wait for the next fd-event */
+
+ con->is_readable = 0;
+ }
+
+ b->used = len;
+ b->ptr[b->used++] = '\0';
+
+ con->bytes_read += len;
+#if 0
+ dump_packet(b->ptr, len);
+#endif
+
+ return 0;
+}
+
+static int connection_handle_write_prepare(server *srv, connection *con) {
+ if (con->mode == DIRECT) {
+ /* static files */
+ switch(con->request.http_method) {
+ case HTTP_METHOD_GET:
+ case HTTP_METHOD_POST:
+ case HTTP_METHOD_HEAD:
+ case HTTP_METHOD_PUT:
+ case HTTP_METHOD_MKCOL:
+ case HTTP_METHOD_DELETE:
+ case HTTP_METHOD_COPY:
+ case HTTP_METHOD_MOVE:
+ case HTTP_METHOD_PROPFIND:
+ case HTTP_METHOD_PROPPATCH:
+ break;
+ case HTTP_METHOD_OPTIONS:
+ /*
+ * 400 is coming from the request-parser BEFORE uri.path is set
+ * 403 is from the response handler when noone else catched it
+ *
+ * */
+ if (con->uri.path->used &&
+ con->uri.path->ptr[0] != '*') {
+ response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("OPTIONS, GET, HEAD, POST"));
+
+ con->http_status = 200;
+ con->file_finished = 1;
+
+ chunkqueue_reset(con->write_queue);
+ }
+ break;
+ default:
+ switch(con->http_status) {
+ case 400: /* bad request */
+ case 414: /* overload request header */
+ case 505: /* unknown protocol */
+ case 207: /* this was webdav */
+ break;
+ default:
+ con->http_status = 501;
+ break;
+ }
+ break;
+ }
+ }
+
+ if (con->http_status == 0) {
+ con->http_status = 403;
+ }
+
+ switch(con->http_status) {
+ case 400: /* class: header + custom body */
+ case 401:
+ case 403:
+ case 404:
+ case 408:
+ case 411:
+ case 416:
+ case 500:
+ case 501:
+ case 503:
+ case 505:
+ if (con->mode != DIRECT) break;
+
+ con->file_finished = 0;
+
+ buffer_reset(con->physical.path);
+
+ /* try to send static errorfile */
+ if (!buffer_is_empty(con->conf.errorfile_prefix)) {
+ stat_cache_entry *sce = NULL;
+
+ buffer_copy_string_buffer(con->physical.path, con->conf.errorfile_prefix);
+ buffer_append_string(con->physical.path, get_http_status_body_name(con->http_status));
+
+ if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
+ con->file_finished = 1;
+
+ http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size);
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
+ }
+ }
+
+ if (!con->file_finished) {
+ buffer *b;
+
+ buffer_reset(con->physical.path);
+
+ con->file_finished = 1;
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ /* build default error-page */
+ buffer_copy_string(b,
+ "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
+ " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
+ " <head>\n"
+ " <title>");
+ buffer_append_long(b, con->http_status);
+ buffer_append_string(b, " - ");
+ buffer_append_string(b, get_http_status_name(con->http_status));
+
+ buffer_append_string(b,
+ "</title>\n"
+ " </head>\n"
+ " <body>\n"
+ " <h1>");
+ buffer_append_long(b, con->http_status);
+ buffer_append_string(b, " - ");
+ buffer_append_string(b, get_http_status_name(con->http_status));
+
+ buffer_append_string(b,"</h1>\n"
+ " </body>\n"
+ "</html>\n"
+ );
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+ }
+ /* fall through */
+ case 207:
+ case 200: /* class: header + body */
+ case 301:
+ case 302:
+ break;
+
+ case 206: /* write_queue is already prepared */
+ con->file_finished = 1;
+
+ break;
+ case 205: /* class: header only */
+ case 304:
+ default:
+ /* disable chunked encoding again as we have no body */
+ con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
+ chunkqueue_reset(con->write_queue);
+
+ con->file_finished = 1;
+ break;
+ }
+
+
+ if (con->file_finished) {
+ /* we have all the content and chunked encoding is not used, set a content-length */
+
+ if ((!(con->parsed_response & HTTP_CONTENT_LENGTH)) &&
+ (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0) {
+ buffer_copy_off_t(srv->tmp_buf, chunkqueue_length(con->write_queue));
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Length"), CONST_BUF_LEN(srv->tmp_buf));
+ }
+ } else {
+ /* disable keep-alive if size-info for the body is missing */
+ if ((con->parsed_response & HTTP_CONTENT_LENGTH) &&
+ ((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0)) {
+ con->keep_alive = 0;
+ }
+
+ if (0 == (con->parsed_response & HTTP_CONNECTION)) {
+ /* (f)cgi did'nt send Connection: header
+ *
+ * shall we ?
+ */
+ if (((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0) &&
+ (con->parsed_response & HTTP_CONTENT_LENGTH) == 0) {
+ /* without content_length, no keep-alive */
+
+ con->keep_alive = 0;
+ }
+ } else {
+ /* a subrequest disable keep-alive although the client wanted it */
+ if (con->keep_alive && !con->response.keep_alive) {
+ con->keep_alive = 0;
+
+ /* FIXME: we have to drop the Connection: Header from the subrequest */
+ }
+ }
+ }
+
+ if (con->request.http_method == HTTP_METHOD_HEAD) {
+ chunkqueue_reset(con->write_queue);
+ }
+
+ http_response_write_header(srv, con);
+
+ return 0;
+}
+
+static int connection_handle_write(server *srv, connection *con) {
+ switch(network_write_chunkqueue(srv, con, con->write_queue)) {
+ case 0:
+ if (con->file_finished) {
+ connection_set_state(srv, con, CON_STATE_RESPONSE_END);
+ joblist_append(srv, con);
+ }
+ break;
+ case -1: /* error on our side */
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed: write failed on fd", con->fd);
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ joblist_append(srv, con);
+ break;
+ case -2: /* remote close */
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ joblist_append(srv, con);
+ break;
+ case 1:
+ con->is_writable = 0;
+
+ /* not finished yet -> WRITE */
+ break;
+ }
+
+ return 0;
+}
+
+
+
+connection *connection_init(server *srv) {
+ connection *con;
+
+ UNUSED(srv);
+
+ con = calloc(1, sizeof(*con));
+
+ con->fd = 0;
+ con->ndx = -1;
+ con->fde_ndx = -1;
+ con->bytes_written = 0;
+ con->bytes_read = 0;
+ con->bytes_header = 0;
+ con->loops_per_request = 0;
+
+#define CLEAN(x) \
+ con->x = buffer_init();
+
+ CLEAN(request.uri);
+ CLEAN(request.request_line);
+ CLEAN(request.request);
+ CLEAN(request.pathinfo);
+
+ CLEAN(request.orig_uri);
+
+ CLEAN(uri.scheme);
+ CLEAN(uri.authority);
+ CLEAN(uri.path);
+ CLEAN(uri.path_raw);
+ CLEAN(uri.query);
+
+ CLEAN(physical.doc_root);
+ CLEAN(physical.path);
+ CLEAN(physical.basedir);
+ CLEAN(physical.rel_path);
+ CLEAN(physical.etag);
+ CLEAN(parse_request);
+
+ CLEAN(authed_user);
+ CLEAN(server_name);
+ CLEAN(error_handler);
+ CLEAN(dst_addr_buf);
+
+#undef CLEAN
+ con->write_queue = chunkqueue_init();
+ con->read_queue = chunkqueue_init();
+ con->request_content_queue = chunkqueue_init();
+ chunkqueue_set_tempdirs(con->request_content_queue, srv->srvconf.upload_tempdirs);
+
+ con->request.headers = array_init();
+ con->response.headers = array_init();
+ con->environment = array_init();
+
+ /* init plugin specific connection structures */
+
+ con->plugin_ctx = calloc(1, (srv->plugins.used + 1) * sizeof(void *));
+
+ con->cond_cache = calloc(srv->config_context->used, sizeof(cond_cache_t));
+ config_setup_connection(srv, con);
+
+ return con;
+}
+
+void connections_free(server *srv) {
+ connections *conns = srv->conns;
+ size_t i;
+
+ for (i = 0; i < conns->size; i++) {
+ connection *con = conns->ptr[i];
+
+ connection_reset(srv, con);
+
+ chunkqueue_free(con->write_queue);
+ chunkqueue_free(con->read_queue);
+ chunkqueue_free(con->request_content_queue);
+ array_free(con->request.headers);
+ array_free(con->response.headers);
+ array_free(con->environment);
+
+#define CLEAN(x) \
+ buffer_free(con->x);
+
+ CLEAN(request.uri);
+ CLEAN(request.request_line);
+ CLEAN(request.request);
+ CLEAN(request.pathinfo);
+
+ CLEAN(request.orig_uri);
+
+ CLEAN(uri.scheme);
+ CLEAN(uri.authority);
+ CLEAN(uri.path);
+ CLEAN(uri.path_raw);
+ CLEAN(uri.query);
+
+ CLEAN(physical.doc_root);
+ CLEAN(physical.path);
+ CLEAN(physical.basedir);
+ CLEAN(physical.etag);
+ CLEAN(physical.rel_path);
+ CLEAN(parse_request);
+
+ CLEAN(authed_user);
+ CLEAN(server_name);
+ CLEAN(error_handler);
+ CLEAN(dst_addr_buf);
+#undef CLEAN
+ free(con->plugin_ctx);
+ free(con->cond_cache);
+
+ free(con);
+ }
+
+ free(conns->ptr);
+}
+
+
+int connection_reset(server *srv, connection *con) {
+ size_t i;
+
+ plugins_call_connection_reset(srv, con);
+
+ con->is_readable = 1;
+ con->is_writable = 1;
+ con->http_status = 0;
+ con->file_finished = 0;
+ con->file_started = 0;
+ con->got_response = 0;
+
+ con->parsed_response = 0;
+
+ con->bytes_written = 0;
+ con->bytes_written_cur_second = 0;
+ con->bytes_read = 0;
+ con->bytes_header = 0;
+ con->loops_per_request = 0;
+
+ con->request.http_method = HTTP_METHOD_UNSET;
+ con->request.http_version = HTTP_VERSION_UNSET;
+
+ con->request.http_if_modified_since = NULL;
+ con->request.http_if_none_match = NULL;
+
+ con->response.keep_alive = 0;
+ con->response.content_length = -1;
+ con->response.transfer_encoding = 0;
+
+ con->mode = DIRECT;
+
+#define CLEAN(x) \
+ if (con->x) buffer_reset(con->x);
+
+ CLEAN(request.uri);
+ CLEAN(request.request_line);
+ CLEAN(request.pathinfo);
+ CLEAN(request.request);
+
+ CLEAN(request.orig_uri);
+
+ CLEAN(uri.scheme);
+ CLEAN(uri.authority);
+ CLEAN(uri.path);
+ CLEAN(uri.path_raw);
+ CLEAN(uri.query);
+
+ CLEAN(physical.doc_root);
+ CLEAN(physical.path);
+ CLEAN(physical.basedir);
+ CLEAN(physical.rel_path);
+ CLEAN(physical.etag);
+
+ CLEAN(parse_request);
+
+ CLEAN(authed_user);
+ CLEAN(server_name);
+ CLEAN(error_handler);
+#undef CLEAN
+
+#define CLEAN(x) \
+ if (con->x) con->x->used = 0;
+
+#undef CLEAN
+
+#define CLEAN(x) \
+ con->request.x = NULL;
+
+ CLEAN(http_host);
+ CLEAN(http_range);
+ CLEAN(http_content_type);
+#undef CLEAN
+ con->request.content_length = 0;
+
+ array_reset(con->request.headers);
+ array_reset(con->response.headers);
+ array_reset(con->environment);
+
+ chunkqueue_reset(con->write_queue);
+ chunkqueue_reset(con->request_content_queue);
+
+ /* the plugins should cleanup themself */
+ for (i = 0; i < srv->plugins.used; i++) {
+ plugin *p = ((plugin **)(srv->plugins.ptr))[i];
+ plugin_data *pd = p->data;
+
+ if (!pd) continue;
+
+ if (con->plugin_ctx[pd->id] != NULL) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "missing cleanup in", p->name);
+ }
+
+ con->plugin_ctx[pd->id] = NULL;
+ }
+
+#if COND_RESULT_UNSET
+ for (i = srv->config_context->used - 1; i >= 0; i --) {
+ con->cond_cache[i].result = COND_RESULT_UNSET;
+ con->cond_cache[i].patterncount = 0;
+ }
+#else
+ memset(con->cond_cache, 0, sizeof(cond_cache_t) * srv->config_context->used);
+#endif
+
+ con->header_len = 0;
+ con->in_error_handler = 0;
+
+ config_setup_connection(srv, con);
+
+ return 0;
+}
+
+/**
+ *
+ * search for \r\n\r\n
+ *
+ * this is a special 32bit version which is using a sliding window for
+ * the comparisions
+ *
+ * how it works:
+ *
+ * b: 'abcdefg'
+ * rnrn: 'cdef'
+ *
+ * cmpbuf: abcd != cdef
+ * cmpbuf: bcde != cdef
+ * cmpbuf: cdef == cdef -> return &c
+ *
+ * cmpbuf and rnrn are treated as 32bit uint and bit-ops are used to
+ * maintain cmpbuf and rnrn
+ *
+ */
+
+char *buffer_search_rnrn(buffer *b) {
+ uint32_t cmpbuf, rnrn;
+ char *cp;
+ size_t i;
+
+ if (b->used < 4) return NULL;
+
+ rnrn = ('\r' << 24) | ('\n' << 16) |
+ ('\r' << 8) | ('\n' << 0);
+
+ cmpbuf = (b->ptr[0] << 24) | (b->ptr[1] << 16) |
+ (b->ptr[2] << 8) | (b->ptr[3] << 0);
+
+ cp = b->ptr + 4;
+ for (i = 0; i < b->used - 4; i++) {
+ if (cmpbuf == rnrn) return cp - 4;
+
+ cmpbuf = (cmpbuf << 8 | *(cp++)) & 0xffffffff;
+ }
+
+ return NULL;
+}
+/**
+ * handle all header and content read
+ *
+ * we get called by the state-engine and by the fdevent-handler
+ */
+int connection_handle_read_state(server *srv, connection *con) {
+ int ostate = con->state;
+ char *h_term = NULL;
+ chunk *c;
+ chunkqueue *cq = con->read_queue;
+ chunkqueue *dst_cq = con->request_content_queue;
+
+ if (con->is_readable) {
+ con->read_idle_ts = srv->cur_ts;
+
+ switch(connection_handle_read(srv, con)) {
+ case -1:
+ return -1;
+ case -2:
+ /* remote side closed the connection
+ * if we still have content, handle it, if not leave here */
+
+ if (cq->first == cq->last &&
+ cq->first->mem->used == 0) {
+
+ /* conn-closed, leave here */
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ }
+ default:
+ break;
+ }
+ }
+
+ /* the last chunk might be empty */
+ for (c = cq->first; c;) {
+ if (cq->first == c && c->mem->used == 0) {
+ /* the first node is empty */
+ /* ... and it is empty, move it to unused */
+
+ cq->first = c->next;
+ if (cq->first == NULL) cq->last = NULL;
+
+ c->next = cq->unused;
+ cq->unused = c;
+ cq->unused_chunks++;
+
+ c = cq->first;
+ } else if (c->next && c->next->mem->used == 0) {
+ chunk *fc;
+ /* next node is the last one */
+ /* ... and it is empty, move it to unused */
+
+ fc = c->next;
+ c->next = fc->next;
+
+ fc->next = cq->unused;
+ cq->unused = fc;
+ cq->unused_chunks++;
+
+ /* the last node was empty */
+ if (c->next == NULL) {
+ cq->last = c;
+ }
+
+ c = c->next;
+ } else {
+ c = c->next;
+ }
+ }
+
+ /* nothing to handle */
+ if (cq->first == NULL) return 0;
+
+ switch(ostate) {
+ case CON_STATE_READ:
+ /* prepare con->request.request */
+ c = cq->first;
+
+ /* check if we need the full package */
+ if (con->request.request->used == 0) {
+ buffer b;
+
+ b.ptr = c->mem->ptr + c->offset;
+ b.used = c->mem->used - c->offset;
+
+ if (NULL != (h_term = buffer_search_rnrn(&b))) {
+ /* \r\n\r\n found
+ * - copy everything incl. the terminator to request.request
+ */
+
+ buffer_copy_string_len(con->request.request,
+ b.ptr,
+ h_term - b.ptr + 4);
+
+ /* the buffer has been read up to the terminator */
+ c->offset += h_term - b.ptr + 4;
+ } else {
+ /* not found, copy everything */
+ buffer_copy_string_len(con->request.request, c->mem->ptr + c->offset, c->mem->used - c->offset - 1);
+ c->offset = c->mem->used - 1;
+ }
+ } else {
+ /* have to take care of overlapping header terminators */
+
+ size_t l = con->request.request->used - 2;
+ char *s = con->request.request->ptr;
+ buffer b;
+
+ b.ptr = c->mem->ptr + c->offset;
+ b.used = c->mem->used - c->offset;
+
+ if (con->request.request->used - 1 > 3 &&
+ c->mem->used > 1 &&
+ s[l-2] == '\r' &&
+ s[l-1] == '\n' &&
+ s[l-0] == '\r' &&
+ c->mem->ptr[0] == '\n') {
+ buffer_append_string_len(con->request.request, c->mem->ptr + c->offset, 1);
+ c->offset += 1;
+
+ h_term = con->request.request->ptr;
+ } else if (con->request.request->used - 1 > 2 &&
+ c->mem->used > 2 &&
+ s[l-1] == '\r' &&
+ s[l-0] == '\n' &&
+ c->mem->ptr[0] == '\r' &&
+ c->mem->ptr[1] == '\n') {
+ buffer_append_string_len(con->request.request, c->mem->ptr + c->offset, 2);
+ c->offset += 2;
+
+ h_term = con->request.request->ptr;
+ } else if (con->request.request->used - 1 > 1 &&
+ c->mem->used > 3 &&
+ s[l-0] == '\r' &&
+ c->mem->ptr[0] == '\n' &&
+ c->mem->ptr[1] == '\r' &&
+ c->mem->ptr[2] == '\n') {
+ buffer_append_string_len(con->request.request, c->mem->ptr + c->offset, 3);
+ c->offset += 3;
+
+ h_term = con->request.request->ptr;
+ } else if (NULL != (h_term = buffer_search_string_len(&b, "\r\n\r\n", 4))) {
+ /* \r\n\r\n found
+ * - copy everything incl. the terminator to request.request
+ */
+
+ buffer_append_string_len(con->request.request,
+ c->mem->ptr + c->offset,
+ c->offset + h_term - b.ptr + 4);
+
+ /* the buffer has been read up to the terminator */
+ c->offset += h_term - b.ptr + 4;
+ } else {
+ /* not found, copy everything */
+ buffer_append_string_len(con->request.request, c->mem->ptr + c->offset, c->mem->used - c->offset - 1);
+ c->offset = c->mem->used - 1;
+ }
+ }
+
+ /* con->request.request is setup up */
+ if (h_term) {
+ connection_set_state(srv, con, CON_STATE_REQUEST_END);
+ } else if (con->request.request->used > 64 * 1024) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "oversized request-header -> sending Status 414");
+
+ con->http_status = 414; /* Request-URI too large */
+ con->keep_alive = 0;
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ }
+ break;
+ case CON_STATE_READ_POST:
+ for (c = cq->first; c && (dst_cq->bytes_in != (off_t)con->request.content_length); c = c->next) {
+ off_t weWant, weHave, toRead;
+
+ weWant = con->request.content_length - dst_cq->bytes_in;
+
+ assert(c->mem->used);
+
+ weHave = c->mem->used - c->offset - 1;
+
+ toRead = weHave > weWant ? weWant : weHave;
+
+ /* the new way, copy everything into a chunkqueue whcih might use tempfiles */
+ if (con->request.content_length > 64 * 1024) {
+ chunk *dst_c = NULL;
+ /* copy everything to max 1Mb sized tempfiles */
+
+ /*
+ * if the last chunk is
+ * - smaller than 1Mb (size < 1Mb)
+ * - not read yet (offset == 0)
+ * -> append to it
+ * otherwise
+ * -> create a new chunk
+ *
+ * */
+
+ if (dst_cq->last &&
+ dst_cq->last->type == FILE_CHUNK &&
+ dst_cq->last->file.is_temp &&
+ dst_cq->last->offset == 0) {
+ /* ok, take the last chunk for our job */
+
+ if (dst_cq->last->file.length < 1 * 1024 * 1024) {
+ dst_c = dst_cq->last;
+
+ if (dst_c->file.fd == -1) {
+ /* this should not happen as we cache the fd, but you never know */
+ dst_c->file.fd = open(dst_c->file.name->ptr, O_WRONLY | O_APPEND);
+ }
+ } else {
+ /* the chunk is too large now, close it */
+ dst_c = dst_cq->last;
+
+ if (dst_c->file.fd != -1) {
+ close(dst_c->file.fd);
+ dst_c->file.fd = -1;
+ }
+ dst_c = chunkqueue_get_append_tempfile(dst_cq);
+ }
+ } else {
+ dst_c = chunkqueue_get_append_tempfile(dst_cq);
+ }
+
+ /* we have a chunk, let's write to it */
+
+ if (dst_c->file.fd == -1) {
+ /* we don't have file to write to,
+ * EACCES might be one reason.
+ *
+ * Instead of sending 500 we send 413 and say the request is too large
+ * */
+
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "denying upload as opening to temp-file for upload failed:",
+ dst_c->file.name, strerror(errno));
+
+ con->http_status = 413; /* Request-Entity too large */
+ con->keep_alive = 0;
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+
+ break;
+ }
+
+ if (toRead != write(dst_c->file.fd, c->mem->ptr + c->offset, toRead)) {
+ /* write failed for some reason ... disk full ? */
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "denying upload as writing to file failed:",
+ dst_c->file.name, strerror(errno));
+
+ con->http_status = 413; /* Request-Entity too large */
+ con->keep_alive = 0;
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+
+ close(dst_c->file.fd);
+ dst_c->file.fd = -1;
+
+ break;
+ }
+
+ dst_c->file.length += toRead;
+
+ if (dst_cq->bytes_in + toRead == (off_t)con->request.content_length) {
+ /* we read everything, close the chunk */
+ close(dst_c->file.fd);
+ dst_c->file.fd = -1;
+ }
+ } else {
+ buffer *b;
+
+ b = chunkqueue_get_append_buffer(dst_cq);
+ buffer_copy_string_len(b, c->mem->ptr + c->offset, toRead);
+ }
+
+ c->offset += toRead;
+ dst_cq->bytes_in += toRead;
+ }
+
+ /* Content is ready */
+ if (dst_cq->bytes_in == (off_t)con->request.content_length) {
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ }
+
+ break;
+ }
+
+ chunkqueue_remove_finished_chunks(cq);
+
+ return 0;
+}
+
+handler_t connection_handle_fdevent(void *s, void *context, int revents) {
+ server *srv = (server *)s;
+ connection *con = context;
+
+ joblist_append(srv, con);
+
+ if (revents & FDEVENT_IN) {
+ con->is_readable = 1;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "read-wait - done", con->fd);
+#endif
+ }
+ if (revents & FDEVENT_OUT) {
+ con->is_writable = 1;
+ /* we don't need the event twice */
+ }
+
+
+ if (revents & ~(FDEVENT_IN | FDEVENT_OUT)) {
+ /* looks like an error */
+
+ /* FIXME: revents = 0x19 still means that we should read from the queue */
+ if (revents & FDEVENT_HUP) {
+ if (con->state == CON_STATE_CLOSE) {
+ con->close_timeout_ts = 0;
+ } else {
+ /* sigio reports the wrong event here
+ *
+ * there was no HUP at all
+ */
+#ifdef USE_LINUX_SIGIO
+ if (srv->ev->in_sigio == 1) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed: poll() -> HUP", con->fd);
+ } else {
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ }
+#else
+ connection_set_state(srv, con, CON_STATE_ERROR);
+#endif
+
+ }
+ } else if (revents & FDEVENT_ERR) {
+#ifndef USE_LINUX_SIGIO
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed: poll() -> ERR", con->fd);
+#endif
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed: poll() -> ???", revents);
+ }
+ }
+
+ if (con->state == CON_STATE_READ ||
+ con->state == CON_STATE_READ_POST) {
+ connection_handle_read_state(srv, con);
+ }
+
+ if (con->state == CON_STATE_WRITE &&
+ !chunkqueue_is_empty(con->write_queue) &&
+ con->is_writable) {
+
+ if (-1 == connection_handle_write(srv, con)) {
+ connection_set_state(srv, con, CON_STATE_ERROR);
+
+ log_error_write(srv, __FILE__, __LINE__, "ds",
+ con->fd,
+ "handle write failed.");
+ } else if (con->state == CON_STATE_WRITE) {
+ con->write_request_ts = srv->cur_ts;
+ }
+ }
+
+ if (con->state == CON_STATE_CLOSE) {
+ /* flush the read buffers */
+ int b;
+
+ if (ioctl(con->fd, FIONREAD, &b)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "ioctl() failed", strerror(errno));
+ }
+
+ if (b > 0) {
+ char buf[1024];
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "CLOSE-read()", con->fd, b);
+
+ /* */
+ read(con->fd, buf, sizeof(buf));
+ } else {
+ /* nothing to read */
+
+ con->close_timeout_ts = 0;
+ }
+ }
+
+ return HANDLER_FINISHED;
+}
+
+
+connection *connection_accept(server *srv, server_socket *srv_socket) {
+ /* accept everything */
+
+ /* search an empty place */
+ int cnt;
+ sock_addr cnt_addr;
+ socklen_t cnt_len;
+ /* accept it and register the fd */
+
+ cnt_len = sizeof(cnt_addr);
+
+ if (-1 == (cnt = accept(srv_socket->fd, (struct sockaddr *) &cnt_addr, &cnt_len))) {
+ if ((errno != EAGAIN) &&
+ (errno != EINTR)) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "accept failed:", strerror(errno), errno);
+ }
+ return NULL;
+ } else {
+ connection *con;
+
+ srv->cur_fds++;
+
+ /* ok, we have the connection, register it */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "appected()", cnt);
+#endif
+ srv->con_opened++;
+
+ con = connections_get_new_connection(srv);
+
+ con->fd = cnt;
+ con->fde_ndx = -1;
+#if 0
+ gettimeofday(&(con->start_tv), NULL);
+#endif
+ fdevent_register(srv->ev, con->fd, connection_handle_fdevent, con);
+
+ connection_set_state(srv, con, CON_STATE_REQUEST_START);
+
+ con->connection_start = srv->cur_ts;
+ con->dst_addr = cnt_addr;
+ buffer_copy_string(con->dst_addr_buf, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
+ con->srv_socket = srv_socket;
+
+ if (-1 == (fdevent_fcntl_set(srv->ev, con->fd))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
+ return NULL;
+ }
+#ifdef USE_OPENSSL
+ /* connect FD to SSL */
+ if (srv_socket->is_ssl) {
+ if (NULL == (con->ssl = SSL_new(srv_socket->ssl_ctx))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ return NULL;
+ }
+
+ SSL_set_accept_state(con->ssl);
+ con->conf.is_ssl=1;
+
+ if (1 != (SSL_set_fd(con->ssl, cnt))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL));
+ return NULL;
+ }
+ }
+#endif
+ return con;
+ }
+}
+
+
+int connection_state_machine(server *srv, connection *con) {
+ int done = 0, r;
+#ifdef USE_OPENSSL
+ server_socket *srv_sock = con->srv_socket;
+#endif
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state at start",
+ con->fd,
+ connection_get_state(con->state));
+ }
+
+ while (done == 0) {
+ size_t ostate = con->state;
+ int b;
+
+ switch (con->state) {
+ case CON_STATE_REQUEST_START: /* transient */
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ con->request_start = srv->cur_ts;
+ con->read_idle_ts = srv->cur_ts;
+
+ con->request_count++;
+ con->loops_per_request = 0;
+
+ connection_set_state(srv, con, CON_STATE_READ);
+
+ break;
+ case CON_STATE_REQUEST_END: /* transient */
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ if (http_request_parse(srv, con)) {
+ /* we have to read some data from the POST request */
+
+ connection_set_state(srv, con, CON_STATE_READ_POST);
+
+ break;
+ }
+
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+
+ break;
+ case CON_STATE_HANDLE_REQUEST:
+ /*
+ * the request is parsed
+ *
+ * decided what to do with the request
+ * -
+ *
+ *
+ */
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ switch (r = http_response_prepare(srv, con)) {
+ case HANDLER_FINISHED:
+ if (con->http_status == 404 ||
+ con->http_status == 403) {
+ /* 404 error-handler */
+
+ if (con->in_error_handler == 0 &&
+ (!buffer_is_empty(con->conf.error_handler) ||
+ !buffer_is_empty(con->error_handler))) {
+ /* call error-handler */
+
+ con->error_handler_saved_status = con->http_status;
+ con->http_status = 0;
+
+ if (buffer_is_empty(con->error_handler)) {
+ buffer_copy_string_buffer(con->request.uri, con->conf.error_handler);
+ } else {
+ buffer_copy_string_buffer(con->request.uri, con->error_handler);
+ }
+ buffer_reset(con->physical.path);
+
+ con->in_error_handler = 1;
+
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+
+ done = -1;
+ break;
+ } else if (con->in_error_handler) {
+ /* error-handler is a 404 */
+
+ /* continue as normal, status is the same */
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "Warning: Either the error-handler returned status 404 or the error-handler itself was not found:", con->request.uri);
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "returning the original status", con->error_handler_saved_status);
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "If this is a rails app: check your production.log");
+ con->http_status = con->error_handler_saved_status;
+ }
+ } else if (con->in_error_handler) {
+ /* error-handler is back and has generated content */
+ /* if Status: was set, take it otherwise use 200 */
+ }
+
+ if (con->http_status == 0) con->http_status = 200;
+
+ /* we have something to send, go on */
+ connection_set_state(srv, con, CON_STATE_RESPONSE_START);
+ break;
+ case HANDLER_WAIT_FOR_FD:
+ srv->want_fds++;
+
+ fdwaitqueue_append(srv, con);
+
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+
+ break;
+ case HANDLER_COMEBACK:
+ done = -1;
+ case HANDLER_WAIT_FOR_EVENT:
+ /* come back here */
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+
+ break;
+ case HANDLER_ERROR:
+ /* something went wrong */
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sdd", "unknown ret-value: ", con->fd, r);
+ break;
+ }
+
+ break;
+ case CON_STATE_RESPONSE_START:
+ /*
+ * the decision is done
+ * - create the HTTP-Response-Header
+ *
+ */
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ if (-1 == connection_handle_write_prepare(srv, con)) {
+ connection_set_state(srv, con, CON_STATE_ERROR);
+
+ break;
+ }
+
+ connection_set_state(srv, con, CON_STATE_WRITE);
+ break;
+ case CON_STATE_RESPONSE_END: /* transient */
+ /* log the request */
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ plugins_call_handle_request_done(srv, con);
+
+ srv->con_written++;
+
+ if (con->keep_alive) {
+ connection_set_state(srv, con, CON_STATE_REQUEST_START);
+
+#if 0
+ con->request_start = srv->cur_ts;
+ con->read_idle_ts = srv->cur_ts;
+#endif
+ } else {
+ switch(r = plugins_call_handle_connection_close(srv, con)) {
+ case HANDLER_GO_ON:
+ case HANDLER_FINISHED:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "unhandling return value", r);
+ break;
+ }
+
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ switch (SSL_shutdown(con->ssl)) {
+ case 1:
+ /* done */
+ break;
+ case 0:
+ /* wait for fd-event
+ *
+ * FIXME: wait for fdevent and call SSL_shutdown again
+ *
+ */
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+ }
+#endif
+ connection_close(srv, con);
+
+ srv->con_closed++;
+ }
+
+ connection_reset(srv, con);
+
+ break;
+ case CON_STATE_CONNECT:
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ chunkqueue_reset(con->read_queue);
+
+ con->request_count = 0;
+
+ break;
+ case CON_STATE_CLOSE:
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ if (con->keep_alive) {
+ if (ioctl(con->fd, FIONREAD, &b)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "ioctl() failed", strerror(errno));
+ }
+ if (b > 0) {
+ char buf[1024];
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "CLOSE-read()", con->fd, b);
+
+ /* */
+ read(con->fd, buf, sizeof(buf));
+ } else {
+ /* nothing to read */
+
+ con->close_timeout_ts = 0;
+ }
+ } else {
+ con->close_timeout_ts = 0;
+ }
+
+ if (srv->cur_ts - con->close_timeout_ts > 1) {
+ connection_close(srv, con);
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed for fd", con->fd);
+ }
+ }
+
+ break;
+ case CON_STATE_READ_POST:
+ case CON_STATE_READ:
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ connection_handle_read_state(srv, con);
+ break;
+ case CON_STATE_WRITE:
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ /* only try to write if we have something in the queue */
+ if (!chunkqueue_is_empty(con->write_queue)) {
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "dsd",
+ con->fd,
+ "packets to write:",
+ con->write_queue->used);
+#endif
+ }
+ if (!chunkqueue_is_empty(con->write_queue) && con->is_writable) {
+ if (-1 == connection_handle_write(srv, con)) {
+ log_error_write(srv, __FILE__, __LINE__, "ds",
+ con->fd,
+ "handle write failed.");
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ } else if (con->state == CON_STATE_WRITE) {
+ con->write_request_ts = srv->cur_ts;
+ }
+ }
+
+ break;
+ case CON_STATE_ERROR: /* transient */
+
+ /* even if the connection was drop we still have to write it to the access log */
+ if (con->http_status) {
+ plugins_call_handle_request_done(srv, con);
+ }
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ int ret;
+ switch ((ret = SSL_shutdown(con->ssl))) {
+ case 1:
+ /* ok */
+ break;
+ case 0:
+ SSL_shutdown(con->ssl);
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:",
+ SSL_get_error(con->ssl, ret),
+ ERR_error_string(ERR_get_error(), NULL));
+ return -1;
+ }
+ }
+#endif
+
+ switch(con->mode) {
+ case DIRECT:
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "emergency exit: direct",
+ con->fd);
+#endif
+ break;
+ default:
+ switch(r = plugins_call_handle_connection_close(srv, con)) {
+ case HANDLER_GO_ON:
+ case HANDLER_FINISHED:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "");
+ break;
+ }
+ break;
+ }
+
+ connection_reset(srv, con);
+
+ /* close the connection */
+ if ((con->keep_alive == 1) &&
+ (0 == shutdown(con->fd, SHUT_WR))) {
+ con->close_timeout_ts = srv->cur_ts;
+ connection_set_state(srv, con, CON_STATE_CLOSE);
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "shutdown for fd", con->fd);
+ }
+ } else {
+ connection_close(srv, con);
+ }
+
+ con->keep_alive = 0;
+
+ srv->con_closed++;
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "unknown state:", con->fd, con->state);
+
+ break;
+ }
+
+ if (done == -1) {
+ done = 0;
+ } else if (ostate == con->state) {
+ done = 1;
+ }
+ }
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state at exit:",
+ con->fd,
+ connection_get_state(con->state));
+ }
+
+ switch(con->state) {
+ case CON_STATE_READ_POST:
+ case CON_STATE_READ:
+ case CON_STATE_CLOSE:
+ fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_IN);
+ break;
+ case CON_STATE_WRITE:
+ /* request write-fdevent only if we really need it
+ * - if we have data to write
+ * - if the socket is not writable yet
+ */
+ if (!chunkqueue_is_empty(con->write_queue) &&
+ (con->is_writable == 0) &&
+ (con->traffic_limit_reached == 0)) {
+ fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_OUT);
+ } else {
+ fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
+ }
+ break;
+ default:
+ fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
+ break;
+ }
+
+ return 0;
+}
diff --git a/src/connections.h b/src/connections.h
new file mode 100644
index 0000000..1fcfc36
--- /dev/null
+++ b/src/connections.h
@@ -0,0 +1,19 @@
+#ifndef _CONNECTIONS_H_
+#define _CONNECTIONS_H_
+
+#include "server.h"
+#include "fdevent.h"
+
+connection *connection_init(server *srv);
+int connection_reset(server *srv, connection *con);
+void connections_free(server *srv);
+
+connection * connection_accept(server *srv, server_socket *srv_sock);
+int connection_close(server *srv, connection *con);
+
+int connection_set_state(server *srv, connection *con, connection_state_t state);
+const char * connection_get_state(connection_state_t state);
+const char * connection_get_short_state(connection_state_t state);
+int connection_state_machine(server *srv, connection *con);
+
+#endif
diff --git a/src/crc32.c b/src/crc32.c
new file mode 100644
index 0000000..cdad7bc
--- /dev/null
+++ b/src/crc32.c
@@ -0,0 +1,82 @@
+#include "crc32.h"
+
+#define CRC32C(c,d) (c=(c>>8)^crc_c[(c^(d))&0xFF])
+
+static const unsigned int crc_c[256] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
+ 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+ 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
+ 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
+ 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+ 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
+ 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
+ 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
+ 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+ 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
+ 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+ 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+ 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
+ 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
+ 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
+ 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
+ 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+ 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
+ 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+ 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
+ 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
+ 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
+};
+
+
+uint32_t generate_crc32c(char *buffer, size_t length) {
+ size_t i;
+ uint32_t crc32 = ~0L;
+
+ for (i = 0; i < length; i++){
+ CRC32C(crc32, (unsigned char)buffer[i]);
+ }
+ return ~crc32;
+}
+
diff --git a/src/crc32.h b/src/crc32.h
new file mode 100644
index 0000000..d671c53
--- /dev/null
+++ b/src/crc32.h
@@ -0,0 +1,18 @@
+#ifndef __crc32cr_table_h__
+#define __crc32cr_table_h__
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#if defined HAVE_STDINT_H
+#include <stdint.h>
+#elif defined HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+
+uint32_t generate_crc32c(char *string, size_t length);
+
+#endif
diff --git a/src/data_array.c b/src/data_array.c
new file mode 100644
index 0000000..9dfa6fd
--- /dev/null
+++ b/src/data_array.c
@@ -0,0 +1,65 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "array.h"
+
+static data_unset *data_array_copy(const data_unset *s) {
+ data_array *src = (data_array *)s;
+ data_array *ds = data_array_init();
+
+ buffer_copy_string_buffer(ds->key, src->key);
+ array_free(ds->value);
+ ds->value = array_init_array(src->value);
+ ds->is_index_key = src->is_index_key;
+ return (data_unset *)ds;
+}
+
+static void data_array_free(data_unset *d) {
+ data_array *ds = (data_array *)d;
+
+ buffer_free(ds->key);
+ array_free(ds->value);
+
+ free(d);
+}
+
+static void data_array_reset(data_unset *d) {
+ data_array *ds = (data_array *)d;
+
+ /* reused array elements */
+ buffer_reset(ds->key);
+ array_reset(ds->value);
+}
+
+static int data_array_insert_dup(data_unset *dst, data_unset *src) {
+ UNUSED(dst);
+
+ src->free(src);
+
+ return 0;
+}
+
+static void data_array_print(const data_unset *d, int depth) {
+ data_array *ds = (data_array *)d;
+
+ array_print(ds->value, depth);
+}
+
+data_array *data_array_init(void) {
+ data_array *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->value = array_init();
+
+ ds->copy = data_array_copy;
+ ds->free = data_array_free;
+ ds->reset = data_array_reset;
+ ds->insert_dup = data_array_insert_dup;
+ ds->print = data_array_print;
+ ds->type = TYPE_ARRAY;
+
+ return ds;
+}
diff --git a/src/data_config.c b/src/data_config.c
new file mode 100644
index 0000000..8b3735e
--- /dev/null
+++ b/src/data_config.c
@@ -0,0 +1,138 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "array.h"
+
+static data_unset *data_config_copy(const data_unset *s) {
+ data_config *src = (data_config *)s;
+ data_config *ds = data_config_init();
+
+ buffer_copy_string_buffer(ds->key, src->key);
+ buffer_copy_string_buffer(ds->comp_key, src->comp_key);
+ array_free(ds->value);
+ ds->value = array_init_array(src->value);
+ return (data_unset *)ds;
+}
+
+static void data_config_free(data_unset *d) {
+ data_config *ds = (data_config *)d;
+
+ buffer_free(ds->key);
+ buffer_free(ds->op);
+ buffer_free(ds->comp_key);
+
+ array_free(ds->value);
+ array_free(ds->childs);
+
+ if (ds->string) buffer_free(ds->string);
+#ifdef HAVE_PCRE_H
+ if (ds->regex) pcre_free(ds->regex);
+ if (ds->regex_study) pcre_free(ds->regex_study);
+#endif
+
+ free(d);
+}
+
+static void data_config_reset(data_unset *d) {
+ data_config *ds = (data_config *)d;
+
+ /* reused array elements */
+ buffer_reset(ds->key);
+ buffer_reset(ds->comp_key);
+ array_reset(ds->value);
+}
+
+static int data_config_insert_dup(data_unset *dst, data_unset *src) {
+ UNUSED(dst);
+
+ src->free(src);
+
+ return 0;
+}
+
+static void data_config_print(const data_unset *d, int depth) {
+ data_config *ds = (data_config *)d;
+ array *a = (array *)ds->value;
+ size_t i;
+ size_t maxlen;
+
+ if (0 == ds->context_ndx) {
+ fprintf(stderr, "config {\n");
+ }
+ else {
+ fprintf(stderr, "$%s %s \"%s\" {\n",
+ ds->comp_key->ptr, ds->op->ptr, ds->string->ptr);
+ array_print_indent(depth + 1);
+ fprintf(stderr, "# block %d\n", ds->context_ndx);
+ }
+ depth ++;
+
+ maxlen = array_get_max_key_length(a);
+ for (i = 0; i < a->used; i ++) {
+ data_unset *du = a->data[i];
+ size_t len = strlen(du->key->ptr);
+ size_t j;
+
+ array_print_indent(depth);
+ fprintf(stderr, "%s", du->key->ptr);
+ for (j = maxlen - len; j > 0; j --) {
+ fprintf(stderr, " ");
+ }
+ fprintf(stderr, " = ");
+ du->print(du, depth);
+ fprintf(stderr, "\n");
+ }
+
+ if (ds->childs) {
+ fprintf(stderr, "\n");
+ for (i = 0; i < ds->childs->used; i ++) {
+ data_unset *du = ds->childs->data[i];
+
+ /* only the 1st block of chaining */
+ if (NULL == ((data_config *)du)->prev) {
+ fprintf(stderr, "\n");
+ array_print_indent(depth);
+ du->print(du, depth);
+ fprintf(stderr, "\n");
+ }
+ }
+ }
+
+ depth --;
+ array_print_indent(depth);
+ fprintf(stderr, "}");
+ if (0 != ds->context_ndx) {
+ fprintf(stderr, " # end of $%s %s \"%s\"",
+ ds->comp_key->ptr, ds->op->ptr, ds->string->ptr);
+ }
+
+ if (ds->next) {
+ fprintf(stderr, "\n");
+ array_print_indent(depth);
+ fprintf(stderr, "else ");
+ ds->next->print((data_unset *)ds->next, depth);
+ }
+}
+
+data_config *data_config_init(void) {
+ data_config *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->op = buffer_init();
+ ds->comp_key = buffer_init();
+ ds->value = array_init();
+ ds->childs = array_init();
+ ds->childs->is_weakref = 1;
+
+ ds->copy = data_config_copy;
+ ds->free = data_config_free;
+ ds->reset = data_config_reset;
+ ds->insert_dup = data_config_insert_dup;
+ ds->print = data_config_print;
+ ds->type = TYPE_CONFIG;
+
+ return ds;
+}
diff --git a/src/data_count.c b/src/data_count.c
new file mode 100644
index 0000000..ca51f67
--- /dev/null
+++ b/src/data_count.c
@@ -0,0 +1,68 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "array.h"
+
+static data_unset *data_count_copy(const data_unset *s) {
+ data_count *src = (data_count *)s;
+ data_count *ds = data_count_init();
+
+ buffer_copy_string_buffer(ds->key, src->key);
+ ds->count = src->count;
+ ds->is_index_key = src->is_index_key;
+ return (data_unset *)ds;
+}
+
+static void data_count_free(data_unset *d) {
+ data_count *ds = (data_count *)d;
+
+ buffer_free(ds->key);
+
+ free(d);
+}
+
+static void data_count_reset(data_unset *d) {
+ data_count *ds = (data_count *)d;
+
+ buffer_reset(ds->key);
+
+ ds->count = 0;
+}
+
+static int data_count_insert_dup(data_unset *dst, data_unset *src) {
+ data_count *ds_dst = (data_count *)dst;
+ data_count *ds_src = (data_count *)src;
+
+ ds_dst->count += ds_src->count;
+
+ src->free(src);
+
+ return 0;
+}
+
+static void data_count_print(const data_unset *d, int depth) {
+ data_count *ds = (data_count *)d;
+ UNUSED(depth);
+
+ fprintf(stderr, "count(%d)", ds->count);
+}
+
+
+data_count *data_count_init(void) {
+ data_count *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->count = 1;
+
+ ds->copy = data_count_copy;
+ ds->free = data_count_free;
+ ds->reset = data_count_reset;
+ ds->insert_dup = data_count_insert_dup;
+ ds->print = data_count_print;
+ ds->type = TYPE_COUNT;
+
+ return ds;
+}
diff --git a/src/data_fastcgi.c b/src/data_fastcgi.c
new file mode 100644
index 0000000..714b290
--- /dev/null
+++ b/src/data_fastcgi.c
@@ -0,0 +1,69 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "array.h"
+#include "fastcgi.h"
+
+static data_unset *data_fastcgi_copy(const data_unset *s) {
+ data_fastcgi *src = (data_fastcgi *)s;
+ data_fastcgi *ds = data_fastcgi_init();
+
+ buffer_copy_string_buffer(ds->key, src->key);
+ buffer_copy_string_buffer(ds->host, src->host);
+ ds->is_index_key = src->is_index_key;
+ return (data_unset *)ds;
+}
+
+static void data_fastcgi_free(data_unset *d) {
+ data_fastcgi *ds = (data_fastcgi *)d;
+
+ buffer_free(ds->key);
+ buffer_free(ds->host);
+
+ free(d);
+}
+
+static void data_fastcgi_reset(data_unset *d) {
+ data_fastcgi *ds = (data_fastcgi *)d;
+
+ buffer_reset(ds->key);
+ buffer_reset(ds->host);
+
+}
+
+static int data_fastcgi_insert_dup(data_unset *dst, data_unset *src) {
+ UNUSED(dst);
+
+ src->free(src);
+
+ return 0;
+}
+
+static void data_fastcgi_print(const data_unset *d, int depth) {
+ data_fastcgi *ds = (data_fastcgi *)d;
+ UNUSED(depth);
+
+ fprintf(stderr, "fastcgi(%s)", ds->host->ptr);
+}
+
+
+data_fastcgi *data_fastcgi_init(void) {
+ data_fastcgi *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->host = buffer_init();
+ ds->port = 0;
+ ds->is_disabled = 0;
+
+ ds->copy = data_fastcgi_copy;
+ ds->free = data_fastcgi_free;
+ ds->reset = data_fastcgi_reset;
+ ds->insert_dup = data_fastcgi_insert_dup;
+ ds->print = data_fastcgi_print;
+ ds->type = TYPE_FASTCGI;
+
+ return ds;
+}
diff --git a/src/data_integer.c b/src/data_integer.c
new file mode 100644
index 0000000..96d1d0a
--- /dev/null
+++ b/src/data_integer.c
@@ -0,0 +1,65 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "array.h"
+
+static data_unset *data_integer_copy(const data_unset *s) {
+ data_integer *src = (data_integer *)s;
+ data_integer *ds = data_integer_init();
+
+ buffer_copy_string_buffer(ds->key, src->key);
+ ds->is_index_key = src->is_index_key;
+ ds->value = src->value;
+ return (data_unset *)ds;
+}
+
+static void data_integer_free(data_unset *d) {
+ data_integer *ds = (data_integer *)d;
+
+ buffer_free(ds->key);
+
+ free(d);
+}
+
+static void data_integer_reset(data_unset *d) {
+ data_integer *ds = (data_integer *)d;
+
+ /* reused integer elements */
+ buffer_reset(ds->key);
+ ds->value = 0;
+}
+
+static int data_integer_insert_dup(data_unset *dst, data_unset *src) {
+ UNUSED(dst);
+
+ src->free(src);
+
+ return 0;
+}
+
+static void data_integer_print(const data_unset *d, int depth) {
+ data_integer *ds = (data_integer *)d;
+ UNUSED(depth);
+
+ fprintf(stderr, "%d", ds->value);
+}
+
+
+data_integer *data_integer_init(void) {
+ data_integer *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->value = 0;
+
+ ds->copy = data_integer_copy;
+ ds->free = data_integer_free;
+ ds->reset = data_integer_reset;
+ ds->insert_dup = data_integer_insert_dup;
+ ds->print = data_integer_print;
+ ds->type = TYPE_INTEGER;
+
+ return ds;
+}
diff --git a/src/data_string.c b/src/data_string.c
new file mode 100644
index 0000000..d9325da
--- /dev/null
+++ b/src/data_string.c
@@ -0,0 +1,104 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "array.h"
+
+static data_unset *data_string_copy(const data_unset *s) {
+ data_string *src = (data_string *)s;
+ data_string *ds = data_string_init();
+
+ buffer_copy_string_buffer(ds->key, src->key);
+ buffer_copy_string_buffer(ds->value, src->value);
+ ds->is_index_key = src->is_index_key;
+ return (data_unset *)ds;
+}
+
+static void data_string_free(data_unset *d) {
+ data_string *ds = (data_string *)d;
+
+ buffer_free(ds->key);
+ buffer_free(ds->value);
+
+ free(d);
+}
+
+static void data_string_reset(data_unset *d) {
+ data_string *ds = (data_string *)d;
+
+ /* reused array elements */
+ buffer_reset(ds->key);
+ buffer_reset(ds->value);
+}
+
+static int data_string_insert_dup(data_unset *dst, data_unset *src) {
+ data_string *ds_dst = (data_string *)dst;
+ data_string *ds_src = (data_string *)src;
+
+ if (ds_dst->value->used) {
+ buffer_append_string(ds_dst->value, ", ");
+ buffer_append_string_buffer(ds_dst->value, ds_src->value);
+ } else {
+ buffer_copy_string_buffer(ds_dst->value, ds_src->value);
+ }
+
+ src->free(src);
+
+ return 0;
+}
+
+static int data_response_insert_dup(data_unset *dst, data_unset *src) {
+ data_string *ds_dst = (data_string *)dst;
+ data_string *ds_src = (data_string *)src;
+
+ if (ds_dst->value->used) {
+ buffer_append_string(ds_dst->value, "\r\n");
+ buffer_append_string_buffer(ds_dst->value, ds_dst->key);
+ buffer_append_string(ds_dst->value, ": ");
+ buffer_append_string_buffer(ds_dst->value, ds_src->value);
+ } else {
+ buffer_copy_string_buffer(ds_dst->value, ds_src->value);
+ }
+
+ src->free(src);
+
+ return 0;
+}
+
+
+static void data_string_print(const data_unset *d, int depth) {
+ data_string *ds = (data_string *)d;
+ UNUSED(depth);
+
+ fprintf(stderr, "\"%s\"", ds->value->used ? ds->value->ptr : "");
+}
+
+
+data_string *data_string_init(void) {
+ data_string *ds;
+
+ ds = calloc(1, sizeof(*ds));
+ assert(ds);
+
+ ds->key = buffer_init();
+ ds->value = buffer_init();
+
+ ds->copy = data_string_copy;
+ ds->free = data_string_free;
+ ds->reset = data_string_reset;
+ ds->insert_dup = data_string_insert_dup;
+ ds->print = data_string_print;
+ ds->type = TYPE_STRING;
+
+ return ds;
+}
+
+data_string *data_response_init(void) {
+ data_string *ds;
+
+ ds = data_string_init();
+ ds->insert_dup = data_response_insert_dup;
+
+ return ds;
+}
diff --git a/src/etag.c b/src/etag.c
new file mode 100644
index 0000000..89dfb9c
--- /dev/null
+++ b/src/etag.c
@@ -0,0 +1,32 @@
+#include <string.h>
+
+#include "buffer.h"
+#include "etag.h"
+
+int etag_is_equal(buffer *etag, const char *matches) {
+ if (0 == strcmp(etag->ptr, matches)) return 1;
+ return 0;
+}
+
+int etag_create(buffer *etag, struct stat *st) {
+ buffer_copy_off_t(etag, st->st_ino);
+ buffer_append_string_len(etag, CONST_STR_LEN("-"));
+ buffer_append_off_t(etag, st->st_size);
+ buffer_append_string_len(etag, CONST_STR_LEN("-"));
+ buffer_append_long(etag, st->st_mtime);
+
+ return 0;
+}
+
+int etag_mutate(buffer *mut, buffer *etag) {
+ size_t h, i;
+
+ for (h=0, i=0; i < etag->used; ++i) h = (h<<5)^(h>>27)^(etag->ptr[i]);
+
+ buffer_reset(mut);
+ buffer_copy_string_len(mut, CONST_STR_LEN("\""));
+ buffer_append_long(mut, h);
+ buffer_append_string_len(mut, CONST_STR_LEN("\""));
+
+ return 0;
+}
diff --git a/src/etag.h b/src/etag.h
new file mode 100644
index 0000000..53fae00
--- /dev/null
+++ b/src/etag.h
@@ -0,0 +1,15 @@
+#ifndef ETAG_H
+#define ETAG_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "buffer.h"
+
+int etag_is_equal(buffer *etag, const char *matches);
+int etag_create(buffer *etag, struct stat *st);
+int etag_mutate(buffer *mut, buffer *etag);
+
+
+#endif
diff --git a/src/fastcgi.h b/src/fastcgi.h
new file mode 100644
index 0000000..15f1dea
--- /dev/null
+++ b/src/fastcgi.h
@@ -0,0 +1,136 @@
+/*
+ * fastcgi.h --
+ *
+ * Defines for the FastCGI protocol.
+ *
+ *
+ * Copyright (c) 1995-1996 Open Market, Inc.
+ *
+ * See the file "LICENSE.TERMS" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * $Id: fastcgi.h,v 1.1.1.1 2003/10/18 09:54:10 weigon Exp $
+ */
+
+#ifndef _FASTCGI_H
+#define _FASTCGI_H
+
+/*
+ * Listening socket file number
+ */
+#define FCGI_LISTENSOCK_FILENO 0
+
+typedef struct {
+ unsigned char version;
+ unsigned char type;
+ unsigned char requestIdB1;
+ unsigned char requestIdB0;
+ unsigned char contentLengthB1;
+ unsigned char contentLengthB0;
+ unsigned char paddingLength;
+ unsigned char reserved;
+} FCGI_Header;
+
+#define FCGI_MAX_LENGTH 0xffff
+
+/*
+ * Number of bytes in a FCGI_Header. Future versions of the protocol
+ * will not reduce this number.
+ */
+#define FCGI_HEADER_LEN 8
+
+/*
+ * Value for version component of FCGI_Header
+ */
+#define FCGI_VERSION_1 1
+
+/*
+ * Values for type component of FCGI_Header
+ */
+#define FCGI_BEGIN_REQUEST 1
+#define FCGI_ABORT_REQUEST 2
+#define FCGI_END_REQUEST 3
+#define FCGI_PARAMS 4
+#define FCGI_STDIN 5
+#define FCGI_STDOUT 6
+#define FCGI_STDERR 7
+#define FCGI_DATA 8
+#define FCGI_GET_VALUES 9
+#define FCGI_GET_VALUES_RESULT 10
+#define FCGI_UNKNOWN_TYPE 11
+#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
+
+/*
+ * Value for requestId component of FCGI_Header
+ */
+#define FCGI_NULL_REQUEST_ID 0
+
+
+typedef struct {
+ unsigned char roleB1;
+ unsigned char roleB0;
+ unsigned char flags;
+ unsigned char reserved[5];
+} FCGI_BeginRequestBody;
+
+typedef struct {
+ FCGI_Header header;
+ FCGI_BeginRequestBody body;
+} FCGI_BeginRequestRecord;
+
+/*
+ * Mask for flags component of FCGI_BeginRequestBody
+ */
+#define FCGI_KEEP_CONN 1
+
+/*
+ * Values for role component of FCGI_BeginRequestBody
+ */
+#define FCGI_RESPONDER 1
+#define FCGI_AUTHORIZER 2
+#define FCGI_FILTER 3
+
+
+typedef struct {
+ unsigned char appStatusB3;
+ unsigned char appStatusB2;
+ unsigned char appStatusB1;
+ unsigned char appStatusB0;
+ unsigned char protocolStatus;
+ unsigned char reserved[3];
+} FCGI_EndRequestBody;
+
+typedef struct {
+ FCGI_Header header;
+ FCGI_EndRequestBody body;
+} FCGI_EndRequestRecord;
+
+/*
+ * Values for protocolStatus component of FCGI_EndRequestBody
+ */
+#define FCGI_REQUEST_COMPLETE 0
+#define FCGI_CANT_MPX_CONN 1
+#define FCGI_OVERLOADED 2
+#define FCGI_UNKNOWN_ROLE 3
+
+
+/*
+ * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
+ */
+#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
+#define FCGI_MAX_REQS "FCGI_MAX_REQS"
+#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
+
+
+typedef struct {
+ unsigned char type;
+ unsigned char reserved[7];
+} FCGI_UnknownTypeBody;
+
+typedef struct {
+ FCGI_Header header;
+ FCGI_UnknownTypeBody body;
+} FCGI_UnknownTypeRecord;
+
+#endif /* _FASTCGI_H */
+
diff --git a/src/fdevent.c b/src/fdevent.c
new file mode 100644
index 0000000..fdf834f
--- /dev/null
+++ b/src/fdevent.c
@@ -0,0 +1,202 @@
+#include <sys/types.h>
+
+#include "settings.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "buffer.h"
+
+fdevents *fdevent_init(size_t maxfds, fdevent_handler_t type) {
+ fdevents *ev;
+
+ ev = calloc(1, sizeof(*ev));
+ ev->fdarray = calloc(maxfds, sizeof(*ev->fdarray));
+ ev->maxfds = maxfds;
+
+ switch(type) {
+ case FDEVENT_HANDLER_POLL:
+ if (0 != fdevent_poll_init(ev)) {
+ fprintf(stderr, "%s.%d: event-handler poll failed\n",
+ __FILE__, __LINE__);
+
+ return NULL;
+ }
+ break;
+ case FDEVENT_HANDLER_SELECT:
+ if (0 != fdevent_select_init(ev)) {
+ fprintf(stderr, "%s.%d: event-handler select failed\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+ break;
+ case FDEVENT_HANDLER_LINUX_RTSIG:
+ if (0 != fdevent_linux_rtsig_init(ev)) {
+ fprintf(stderr, "%s.%d: event-handler linux-rtsig failed, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+ break;
+ case FDEVENT_HANDLER_LINUX_SYSEPOLL:
+ if (0 != fdevent_linux_sysepoll_init(ev)) {
+ fprintf(stderr, "%s.%d: event-handler linux-sysepoll failed, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+ break;
+ case FDEVENT_HANDLER_SOLARIS_DEVPOLL:
+ if (0 != fdevent_solaris_devpoll_init(ev)) {
+ fprintf(stderr, "%s.%d: event-handler solaris-devpoll failed, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+ break;
+ case FDEVENT_HANDLER_FREEBSD_KQUEUE:
+ if (0 != fdevent_freebsd_kqueue_init(ev)) {
+ fprintf(stderr, "%s.%d: event-handler freebsd-kqueue failed, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+ break;
+ default:
+ fprintf(stderr, "%s.%d: event-handler is unknown, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+
+ return ev;
+}
+
+void fdevent_free(fdevents *ev) {
+ size_t i;
+ if (!ev) return;
+
+ if (ev->free) ev->free(ev);
+
+ for (i = 0; i < ev->maxfds; i++) {
+ if (ev->fdarray[i]) free(ev->fdarray[i]);
+ }
+
+ free(ev->fdarray);
+ free(ev);
+}
+
+int fdevent_reset(fdevents *ev) {
+ if (ev->reset) return ev->reset(ev);
+
+ return 0;
+}
+
+fdnode *fdnode_init() {
+ fdnode *fdn;
+
+ fdn = calloc(1, sizeof(*fdn));
+ fdn->fd = -1;
+ return fdn;
+}
+
+void fdnode_free(fdnode *fdn) {
+ free(fdn);
+}
+
+int fdevent_register(fdevents *ev, int fd, fdevent_handler handler, void *ctx) {
+ fdnode *fdn;
+
+ fdn = fdnode_init();
+ fdn->handler = handler;
+ fdn->fd = fd;
+ fdn->ctx = ctx;
+
+ ev->fdarray[fd] = fdn;
+
+ return 0;
+}
+
+int fdevent_unregister(fdevents *ev, int fd) {
+ fdnode *fdn;
+ if (!ev) return 0;
+ fdn = ev->fdarray[fd];
+
+ fdnode_free(fdn);
+
+ ev->fdarray[fd] = NULL;
+
+ return 0;
+}
+
+int fdevent_event_del(fdevents *ev, int *fde_ndx, int fd) {
+ int fde = fde_ndx ? *fde_ndx : -1;
+
+ if (ev->event_del) fde = ev->event_del(ev, fde, fd);
+
+ if (fde_ndx) *fde_ndx = fde;
+
+ return 0;
+}
+
+int fdevent_event_add(fdevents *ev, int *fde_ndx, int fd, int events) {
+ int fde = fde_ndx ? *fde_ndx : -1;
+
+ if (ev->event_add) fde = ev->event_add(ev, fde, fd, events);
+
+ if (fde_ndx) *fde_ndx = fde;
+
+ return 0;
+}
+
+int fdevent_poll(fdevents *ev, int timeout_ms) {
+ if (ev->poll == NULL) SEGFAULT();
+ return ev->poll(ev, timeout_ms);
+}
+
+int fdevent_event_get_revent(fdevents *ev, size_t ndx) {
+ if (ev->event_get_revent == NULL) SEGFAULT();
+
+ return ev->event_get_revent(ev, ndx);
+}
+
+int fdevent_event_get_fd(fdevents *ev, size_t ndx) {
+ if (ev->event_get_fd == NULL) SEGFAULT();
+
+ return ev->event_get_fd(ev, ndx);
+}
+
+fdevent_handler fdevent_get_handler(fdevents *ev, int fd) {
+ if (ev->fdarray[fd] == NULL) SEGFAULT();
+ if (ev->fdarray[fd]->fd != fd) SEGFAULT();
+
+ return ev->fdarray[fd]->handler;
+}
+
+void * fdevent_get_context(fdevents *ev, int fd) {
+ if (ev->fdarray[fd] == NULL) SEGFAULT();
+ if (ev->fdarray[fd]->fd != fd) SEGFAULT();
+
+ return ev->fdarray[fd]->ctx;
+}
+
+int fdevent_fcntl_set(fdevents *ev, int fd) {
+#ifdef FD_CLOEXEC
+ /* close fd on exec (cgi) */
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+#endif
+ if ((ev) && (ev->fcntl_set)) return ev->fcntl_set(ev, fd);
+#ifdef O_NONBLOCK
+ return fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR);
+#else
+ return 0;
+#endif
+}
+
+
+int fdevent_event_next_fdndx(fdevents *ev, int ndx) {
+ if (ev->event_next_fdndx) return ev->event_next_fdndx(ev, ndx);
+
+ return -1;
+}
+
diff --git a/src/fdevent.h b/src/fdevent.h
new file mode 100644
index 0000000..0bc05ca
--- /dev/null
+++ b/src/fdevent.h
@@ -0,0 +1,222 @@
+#ifndef _FDEVENT_H_
+#define _FDEVENT_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "settings.h"
+#include "bitset.h"
+
+/* select event-system */
+
+#if defined(HAVE_EPOLL_CTL) && defined(HAVE_SYS_EPOLL_H)
+# if defined HAVE_STDINT_H
+# include <stdint.h>
+# endif
+# define USE_LINUX_EPOLL
+# include <sys/epoll.h>
+#endif
+
+/* MacOS 10.3.x has poll.h under /usr/include/, all other unixes
+ * under /usr/include/sys/ */
+#if defined HAVE_POLL && (defined(HAVE_SYS_POLL_H) || defined(HAVE_POLL_H))
+# define USE_POLL
+# ifdef HAVE_POLL_H
+# include <poll.h>
+# else
+# include <sys/poll.h>
+# endif
+# if defined HAVE_SIGTIMEDWAIT && defined(__linux__)
+# define USE_LINUX_SIGIO
+# include <signal.h>
+# endif
+#endif
+
+#if defined HAVE_SELECT
+# ifdef __WIN32
+# include <winsock2.h>
+# endif
+# define USE_SELECT
+# ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+# endif
+#endif
+
+#if defined HAVE_SYS_DEVPOLL_H && defined(__sun)
+# define USE_SOLARIS_DEVPOLL
+# include <sys/devpoll.h>
+#endif
+
+#if defined HAVE_SYS_EVENT_H && defined HAVE_KQUEUE
+# define USE_FREEBSD_KQUEUE
+# include <sys/event.h>
+#endif
+
+#if defined HAVE_SYS_PORT_H && defined HAVE_PORT_CREATE
+# define USE_SOLARIS_PORT
+# include <sys/port.h>
+#endif
+
+
+typedef handler_t (*fdevent_handler)(void *srv, void *ctx, int revents);
+
+#define FDEVENT_IN BV(0)
+#define FDEVENT_PRI BV(1)
+#define FDEVENT_OUT BV(2)
+#define FDEVENT_ERR BV(3)
+#define FDEVENT_HUP BV(4)
+#define FDEVENT_NVAL BV(5)
+
+typedef enum { FD_EVENT_TYPE_UNSET = -1,
+ FD_EVENT_TYPE_CONNECTION,
+ FD_EVENT_TYPE_FCGI_CONNECTION,
+ FD_EVENT_TYPE_DIRWATCH,
+ FD_EVENT_TYPE_CGI_CONNECTION
+} fd_event_t;
+
+typedef enum { FDEVENT_HANDLER_UNSET,
+ FDEVENT_HANDLER_SELECT,
+ FDEVENT_HANDLER_POLL,
+ FDEVENT_HANDLER_LINUX_RTSIG,
+ FDEVENT_HANDLER_LINUX_SYSEPOLL,
+ FDEVENT_HANDLER_SOLARIS_DEVPOLL,
+ FDEVENT_HANDLER_FREEBSD_KQUEUE,
+ FDEVENT_HANDLER_SOLARIS_PORT
+} fdevent_handler_t;
+
+/**
+ * a mapping from fd to connection structure
+ *
+ */
+typedef struct {
+ int fd; /**< the fd */
+ void *conn; /**< a reference the corresponding data-structure */
+ fd_event_t fd_type; /**< type of the fd */
+ int events; /**< registered events */
+ int revents;
+} fd_conn;
+
+typedef struct {
+ fd_conn *ptr;
+
+ size_t size;
+ size_t used;
+} fd_conn_buffer;
+
+/**
+ * array of unused fd's
+ *
+ */
+
+typedef struct _fdnode {
+ fdevent_handler handler;
+ void *ctx;
+ int fd;
+
+ struct _fdnode *prev, *next;
+} fdnode;
+
+typedef struct {
+ int *ptr;
+
+ size_t used;
+ size_t size;
+} buffer_int;
+
+/**
+ * fd-event handler for select(), poll() and rt-signals on Linux 2.4
+ *
+ */
+typedef struct fdevents {
+ fdevent_handler_t type;
+
+ fdnode **fdarray;
+ size_t maxfds;
+
+#ifdef USE_LINUX_SIGIO
+ int in_sigio;
+ int signum;
+ sigset_t sigset;
+ siginfo_t siginfo;
+ bitset *sigbset;
+#endif
+#ifdef USE_LINUX_EPOLL
+ int epoll_fd;
+ struct epoll_event *epoll_events;
+#endif
+#ifdef USE_POLL
+ struct pollfd *pollfds;
+
+ size_t size;
+ size_t used;
+
+ buffer_int unused;
+#endif
+#ifdef USE_SELECT
+ fd_set select_read;
+ fd_set select_write;
+ fd_set select_error;
+
+ fd_set select_set_read;
+ fd_set select_set_write;
+ fd_set select_set_error;
+
+ int select_max_fd;
+#endif
+#ifdef USE_SOLARIS_DEVPOLL
+ int devpoll_fd;
+ struct pollfd *devpollfds;
+#endif
+#ifdef USE_FREEBSD_KQUEUE
+ int kq_fd;
+ struct kevent *kq_results;
+ bitset *kq_bevents;
+#endif
+#ifdef USE_SOLARIS_PORT
+ int port_fd;
+#endif
+ int (*reset)(struct fdevents *ev);
+ void (*free)(struct fdevents *ev);
+
+ int (*event_add)(struct fdevents *ev, int fde_ndx, int fd, int events);
+ int (*event_del)(struct fdevents *ev, int fde_ndx, int fd);
+ int (*event_get_revent)(struct fdevents *ev, size_t ndx);
+ int (*event_get_fd)(struct fdevents *ev, size_t ndx);
+
+ int (*event_next_fdndx)(struct fdevents *ev, int ndx);
+
+ int (*poll)(struct fdevents *ev, int timeout_ms);
+
+ int (*fcntl_set)(struct fdevents *ev, int fd);
+} fdevents;
+
+fdevents *fdevent_init(size_t maxfds, fdevent_handler_t type);
+int fdevent_reset(fdevents *ev);
+void fdevent_free(fdevents *ev);
+
+int fdevent_event_add(fdevents *ev, int *fde_ndx, int fd, int events);
+int fdevent_event_del(fdevents *ev, int *fde_ndx, int fd);
+int fdevent_event_get_revent(fdevents *ev, size_t ndx);
+int fdevent_event_get_fd(fdevents *ev, size_t ndx);
+fdevent_handler fdevent_get_handler(fdevents *ev, int fd);
+void * fdevent_get_context(fdevents *ev, int fd);
+
+int fdevent_event_next_fdndx(fdevents *ev, int ndx);
+
+int fdevent_poll(fdevents *ev, int timeout_ms);
+
+int fdevent_register(fdevents *ev, int fd, fdevent_handler handler, void *ctx);
+int fdevent_unregister(fdevents *ev, int fd);
+
+int fdevent_fcntl_set(fdevents *ev, int fd);
+
+int fdevent_select_init(fdevents *ev);
+int fdevent_poll_init(fdevents *ev);
+int fdevent_linux_rtsig_init(fdevents *ev);
+int fdevent_linux_sysepoll_init(fdevents *ev);
+int fdevent_solaris_devpoll_init(fdevents *ev);
+int fdevent_freebsd_kqueue_init(fdevents *ev);
+
+#endif
+
+
diff --git a/src/fdevent_freebsd_kqueue.c b/src/fdevent_freebsd_kqueue.c
new file mode 100644
index 0000000..b955726
--- /dev/null
+++ b/src/fdevent_freebsd_kqueue.c
@@ -0,0 +1,207 @@
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "settings.h"
+#include "buffer.h"
+#include "server.h"
+
+#ifdef USE_FREEBSD_KQUEUE
+#include <sys/event.h>
+#include <sys/time.h>
+
+static void fdevent_freebsd_kqueue_free(fdevents *ev) {
+ close(ev->kq_fd);
+ free(ev->kq_results);
+ bitset_free(ev->kq_bevents);
+}
+
+static int fdevent_freebsd_kqueue_event_del(fdevents *ev, int fde_ndx, int fd) {
+ int filter, ret;
+ struct kevent kev;
+ struct timespec ts;
+
+ if (fde_ndx < 0) return -1;
+
+ filter = bitset_test_bit(ev->kq_bevents, fd) ? EVFILT_READ : EVFILT_WRITE;
+
+ EV_SET(&kev, fd, filter, EV_DELETE, 0, 0, NULL);
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ ret = kevent(ev->kq_fd,
+ &kev, 1,
+ NULL, 0,
+ &ts);
+
+ if (ret == -1) {
+ fprintf(stderr, "%s.%d: kqueue failed polling: %s\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ return -1;
+}
+
+static int fdevent_freebsd_kqueue_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
+ int filter, ret;
+ struct kevent kev;
+ struct timespec ts;
+
+ UNUSED(fde_ndx);
+
+ filter = (events & FDEVENT_IN) ? EVFILT_READ : EVFILT_WRITE;
+
+ EV_SET(&kev, fd, filter, EV_ADD|EV_CLEAR, 0, 0, NULL);
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ ret = kevent(ev->kq_fd,
+ &kev, 1,
+ NULL, 0,
+ &ts);
+
+ if (ret == -1) {
+ fprintf(stderr, "%s.%d: kqueue failed polling: %s\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ if (filter == EVFILT_READ) {
+ bitset_set_bit(ev->kq_bevents, fd);
+ } else {
+ bitset_clear_bit(ev->kq_bevents, fd);
+ }
+
+ return fd;
+}
+
+static int fdevent_freebsd_kqueue_poll(fdevents *ev, int timeout_ms) {
+ int ret;
+ struct timespec ts;
+
+ ts.tv_sec = timeout_ms / 1000;
+ ts.tv_nsec = (timeout_ms % 1000) * 1000000;
+
+ ret = kevent(ev->kq_fd,
+ NULL, 0,
+ ev->kq_results, ev->maxfds,
+ &ts);
+
+ if (ret == -1) {
+ switch(errno) {
+ case EINTR:
+ /* we got interrupted, perhaps just a SIGCHLD of a CGI script */
+ return 0;
+ default:
+ fprintf(stderr, "%s.%d: kqueue failed polling: %s\n",
+ __FILE__, __LINE__, strerror(errno));
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int fdevent_freebsd_kqueue_event_get_revent(fdevents *ev, size_t ndx) {
+ int events = 0, e;
+
+ e = ev->kq_results[ndx].filter;
+
+ if (e == EVFILT_READ) {
+ events |= FDEVENT_IN;
+ } else if (e == EVFILT_WRITE) {
+ events |= FDEVENT_OUT;
+ }
+
+ e = ev->kq_results[ndx].flags;
+
+ if (e & EV_EOF) {
+ events |= FDEVENT_HUP;
+ }
+
+ if (e & EV_ERROR) {
+ events |= FDEVENT_ERR;
+ }
+
+ return events;
+}
+
+static int fdevent_freebsd_kqueue_event_get_fd(fdevents *ev, size_t ndx) {
+ return ev->kq_results[ndx].ident;
+}
+
+static int fdevent_freebsd_kqueue_event_next_fdndx(fdevents *ev, int ndx) {
+ UNUSED(ev);
+
+ return (ndx < 0) ? 0 : ndx + 1;
+}
+
+static int fdevent_freebsd_kqueue_reset(fdevents *ev) {
+ if (-1 == (ev->kq_fd = kqueue())) {
+ fprintf(stderr, "%s.%d: kqueue failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int fdevent_freebsd_kqueue_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_FREEBSD_KQUEUE;
+#define SET(x) \
+ ev->x = fdevent_freebsd_kqueue_##x;
+
+ SET(free);
+ SET(poll);
+ SET(reset);
+
+ SET(event_del);
+ SET(event_add);
+
+ SET(event_next_fdndx);
+ SET(event_get_fd);
+ SET(event_get_revent);
+
+ ev->kq_fd = -1;
+
+ ev->kq_results = calloc(ev->maxfds, sizeof(*ev->kq_results));
+ ev->kq_bevents = bitset_init(ev->maxfds);
+
+ /* check that kqueue works */
+
+ if (-1 == (ev->kq_fd = kqueue())) {
+ fprintf(stderr, "%s.%d: kqueue failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ close(ev->kq_fd);
+ ev->kq_fd = -1;
+
+ return 0;
+}
+#else
+int fdevent_freebsd_kqueue_init(fdevents *ev) {
+ UNUSED(ev);
+
+ fprintf(stderr, "%s.%d: kqueue not available, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+
+ return -1;
+}
+#endif
diff --git a/src/fdevent_linux_rtsig.c b/src/fdevent_linux_rtsig.c
new file mode 100644
index 0000000..dcefff8
--- /dev/null
+++ b/src/fdevent_linux_rtsig.c
@@ -0,0 +1,260 @@
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <limits.h>
+
+#define __USE_GNU
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "settings.h"
+#include "buffer.h"
+
+#ifdef USE_LINUX_SIGIO
+static void fdevent_linux_rtsig_free(fdevents *ev) {
+ free(ev->pollfds);
+ if (ev->unused.ptr) free(ev->unused.ptr);
+
+ bitset_free(ev->sigbset);
+}
+
+
+static int fdevent_linux_rtsig_event_del(fdevents *ev, int fde_ndx, int fd) {
+ if (fde_ndx < 0) return -1;
+
+ if ((size_t)fde_ndx >= ev->used) {
+ fprintf(stderr, "%s.%d: del! out of range %d %zu\n", __FILE__, __LINE__, fde_ndx, ev->used);
+ SEGFAULT();
+ }
+
+ if (ev->pollfds[fde_ndx].fd == fd) {
+ size_t k = fde_ndx;
+
+ ev->pollfds[k].fd = -1;
+
+ bitset_clear_bit(ev->sigbset, fd);
+
+ if (ev->unused.size == 0) {
+ ev->unused.size = 16;
+ ev->unused.ptr = malloc(sizeof(*(ev->unused.ptr)) * ev->unused.size);
+ } else if (ev->unused.size == ev->unused.used) {
+ ev->unused.size += 16;
+ ev->unused.ptr = realloc(ev->unused.ptr, sizeof(*(ev->unused.ptr)) * ev->unused.size);
+ }
+
+ ev->unused.ptr[ev->unused.used++] = k;
+ } else {
+ fprintf(stderr, "%s.%d: del! %d %d\n", __FILE__, __LINE__, ev->pollfds[fde_ndx].fd, fd);
+
+ SEGFAULT();
+ }
+
+ return -1;
+}
+
+#if 0
+static int fdevent_linux_rtsig_event_compress(fdevents *ev) {
+ size_t j;
+
+ if (ev->used == 0) return 0;
+ if (ev->unused.used != 0) return 0;
+
+ for (j = ev->used - 1; j + 1 > 0; j--) {
+ if (ev->pollfds[j].fd == -1) ev->used--;
+ }
+
+
+ return 0;
+}
+#endif
+
+static int fdevent_linux_rtsig_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
+ /* known index */
+ if (fde_ndx != -1) {
+ if (ev->pollfds[fde_ndx].fd == fd) {
+ ev->pollfds[fde_ndx].events = events;
+
+ return fde_ndx;
+ }
+ fprintf(stderr, "%s.%d: add: (%d, %d)\n", __FILE__, __LINE__, fde_ndx, ev->pollfds[fde_ndx].fd);
+ SEGFAULT();
+ }
+
+ if (ev->unused.used > 0) {
+ int k = ev->unused.ptr[--ev->unused.used];
+
+ ev->pollfds[k].fd = fd;
+ ev->pollfds[k].events = events;
+
+ bitset_set_bit(ev->sigbset, fd);
+
+ return k;
+ } else {
+ if (ev->size == 0) {
+ ev->size = 16;
+ ev->pollfds = malloc(sizeof(*ev->pollfds) * ev->size);
+ } else if (ev->size == ev->used) {
+ ev->size += 16;
+ ev->pollfds = realloc(ev->pollfds, sizeof(*ev->pollfds) * ev->size);
+ }
+
+ ev->pollfds[ev->used].fd = fd;
+ ev->pollfds[ev->used].events = events;
+
+ bitset_set_bit(ev->sigbset, fd);
+
+ return ev->used++;
+ }
+}
+
+static int fdevent_linux_rtsig_poll(fdevents *ev, int timeout_ms) {
+ struct timespec ts;
+ int r;
+
+#if 0
+ fdevent_linux_rtsig_event_compress(ev);
+#endif
+
+ ev->in_sigio = 1;
+
+ ts.tv_sec = timeout_ms / 1000;
+ ts.tv_nsec = (timeout_ms % 1000) * 1000000;
+ r = sigtimedwait(&(ev->sigset), &(ev->siginfo), &(ts));
+
+ if (r == -1) {
+ if (errno == EAGAIN) return 0;
+ return r;
+ } else if (r == SIGIO) {
+ struct sigaction act;
+
+ /* flush the signal queue */
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = SIG_IGN;
+ sigaction(ev->signum, &act, NULL);
+
+ /* re-enable the signal queue */
+ act.sa_handler = SIG_DFL;
+ sigaction(ev->signum, &act, NULL);
+
+ ev->in_sigio = 0;
+ r = poll(ev->pollfds, ev->used, timeout_ms);
+
+ return r;
+ } else if (r == ev->signum) {
+# if 0
+ fprintf(stderr, "event: %d %02lx\n", ev->siginfo.si_fd, ev->siginfo.si_band);
+# endif
+ return bitset_test_bit(ev->sigbset, ev->siginfo.si_fd);
+ } else {
+ /* ? */
+ return -1;
+ }
+}
+
+static int fdevent_linux_rtsig_event_get_revent(fdevents *ev, size_t ndx) {
+ if (ev->in_sigio == 1) {
+# if 0
+ if (ev->siginfo.si_band == POLLERR) {
+ fprintf(stderr, "event: %d %02lx %02x %s\n", ev->siginfo.si_fd, ev->siginfo.si_band, errno, strerror(errno));
+ }
+# endif
+ if (ndx != 0) {
+ fprintf(stderr, "+\n");
+ return 0;
+ }
+
+ return ev->siginfo.si_band & 0x3f;
+ } else {
+ if (ndx >= ev->used) {
+ fprintf(stderr, "%s.%d: event: %zu %zu\n", __FILE__, __LINE__, ndx, ev->used);
+ return 0;
+ }
+ return ev->pollfds[ndx].revents;
+ }
+}
+
+static int fdevent_linux_rtsig_event_get_fd(fdevents *ev, size_t ndx) {
+ if (ev->in_sigio == 1) {
+ return ev->siginfo.si_fd;
+ } else {
+ return ev->pollfds[ndx].fd;
+ }
+}
+
+static int fdevent_linux_rtsig_fcntl_set(fdevents *ev, int fd) {
+ static pid_t pid = 0;
+
+ if (pid == 0) pid = getpid();
+
+ if (-1 == fcntl(fd, F_SETSIG, ev->signum)) return -1;
+
+ if (-1 == fcntl(fd, F_SETOWN, (int) pid)) return -1;
+
+ return fcntl(fd, F_SETFL, O_ASYNC | O_NONBLOCK | O_RDWR);
+}
+
+
+static int fdevent_linux_rtsig_event_next_fdndx(fdevents *ev, int ndx) {
+ if (ev->in_sigio == 1) {
+ if (ndx < 0) return 0;
+ return -1;
+ } else {
+ size_t i;
+
+ i = (ndx < 0) ? 0 : ndx + 1;
+ for (; i < ev->used; i++) {
+ if (ev->pollfds[i].revents) break;
+ }
+
+ return i;
+ }
+}
+
+int fdevent_linux_rtsig_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_LINUX_RTSIG;
+#define SET(x) \
+ ev->x = fdevent_linux_rtsig_##x;
+
+ SET(free);
+ SET(poll);
+
+ SET(event_del);
+ SET(event_add);
+
+ SET(event_next_fdndx);
+ SET(fcntl_set);
+ SET(event_get_fd);
+ SET(event_get_revent);
+
+ ev->signum = SIGRTMIN + 1;
+
+ sigemptyset(&(ev->sigset));
+ sigaddset(&(ev->sigset), ev->signum);
+ sigaddset(&(ev->sigset), SIGIO);
+ if (-1 == sigprocmask(SIG_BLOCK, &(ev->sigset), NULL)) {
+ fprintf(stderr, "%s.%d: sigprocmask failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ ev->in_sigio = 1;
+
+ ev->sigbset = bitset_init(ev->maxfds);
+
+ return 0;
+}
+#else
+int fdevent_linux_rtsig_init(fdevents *ev) {
+ UNUSED(ev);
+
+ fprintf(stderr, "%s.%d: linux-rtsig not supported, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+ return -1;
+}
+#endif
diff --git a/src/fdevent_linux_sysepoll.c b/src/fdevent_linux_sysepoll.c
new file mode 100644
index 0000000..31caabd
--- /dev/null
+++ b/src/fdevent_linux_sysepoll.c
@@ -0,0 +1,160 @@
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "settings.h"
+#include "buffer.h"
+
+#ifdef USE_LINUX_EPOLL
+static void fdevent_linux_sysepoll_free(fdevents *ev) {
+ close(ev->epoll_fd);
+ free(ev->epoll_events);
+}
+
+static int fdevent_linux_sysepoll_event_del(fdevents *ev, int fde_ndx, int fd) {
+ struct epoll_event ep;
+
+ if (fde_ndx < 0) return -1;
+
+ memset(&ep, 0, sizeof(ep));
+
+ ep.data.fd = fd;
+ ep.data.ptr = NULL;
+
+ if (0 != epoll_ctl(ev->epoll_fd, EPOLL_CTL_DEL, fd, &ep)) {
+ fprintf(stderr, "%s.%d: epoll_ctl failed: %s, dying\n", __FILE__, __LINE__, strerror(errno));
+
+ SEGFAULT();
+
+ return 0;
+ }
+
+
+ return -1;
+}
+
+static int fdevent_linux_sysepoll_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
+ struct epoll_event ep;
+ int add = 0;
+
+ if (fde_ndx == -1) add = 1;
+
+ memset(&ep, 0, sizeof(ep));
+
+ ep.events = 0;
+
+ if (events & FDEVENT_IN) ep.events |= EPOLLIN;
+ if (events & FDEVENT_OUT) ep.events |= EPOLLOUT;
+
+ /**
+ *
+ * with EPOLLET we don't get a FDEVENT_HUP
+ * if the close is delay after everything has
+ * sent.
+ *
+ */
+
+ ep.events |= EPOLLERR | EPOLLHUP /* | EPOLLET */;
+
+ ep.data.ptr = NULL;
+ ep.data.fd = fd;
+
+ if (0 != epoll_ctl(ev->epoll_fd, add ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, fd, &ep)) {
+ fprintf(stderr, "%s.%d: epoll_ctl failed: %s, dying\n", __FILE__, __LINE__, strerror(errno));
+
+ SEGFAULT();
+
+ return 0;
+ }
+
+ return fd;
+}
+
+static int fdevent_linux_sysepoll_poll(fdevents *ev, int timeout_ms) {
+ return epoll_wait(ev->epoll_fd, ev->epoll_events, ev->maxfds, timeout_ms);
+}
+
+static int fdevent_linux_sysepoll_event_get_revent(fdevents *ev, size_t ndx) {
+ int events = 0, e;
+
+ e = ev->epoll_events[ndx].events;
+ if (e & EPOLLIN) events |= FDEVENT_IN;
+ if (e & EPOLLOUT) events |= FDEVENT_OUT;
+ if (e & EPOLLERR) events |= FDEVENT_ERR;
+ if (e & EPOLLHUP) events |= FDEVENT_HUP;
+ if (e & EPOLLPRI) events |= FDEVENT_PRI;
+
+ return e;
+}
+
+static int fdevent_linux_sysepoll_event_get_fd(fdevents *ev, size_t ndx) {
+# if 0
+ fprintf(stderr, "%s.%d: %d, %d\n", __FILE__, __LINE__, ndx, ev->epoll_events[ndx].data.fd);
+# endif
+
+ return ev->epoll_events[ndx].data.fd;
+}
+
+static int fdevent_linux_sysepoll_event_next_fdndx(fdevents *ev, int ndx) {
+ size_t i;
+
+ UNUSED(ev);
+
+ i = (ndx < 0) ? 0 : ndx + 1;
+
+ return i;
+}
+
+int fdevent_linux_sysepoll_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_LINUX_SYSEPOLL;
+#define SET(x) \
+ ev->x = fdevent_linux_sysepoll_##x;
+
+ SET(free);
+ SET(poll);
+
+ SET(event_del);
+ SET(event_add);
+
+ SET(event_next_fdndx);
+ SET(event_get_fd);
+ SET(event_get_revent);
+
+ if (-1 == (ev->epoll_fd = epoll_create(ev->maxfds))) {
+ fprintf(stderr, "%s.%d: epoll_create failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ if (-1 == fcntl(ev->epoll_fd, F_SETFD, FD_CLOEXEC)) {
+ fprintf(stderr, "%s.%d: epoll_create failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ close(ev->epoll_fd);
+
+ return -1;
+ }
+
+ ev->epoll_events = malloc(ev->maxfds * sizeof(*ev->epoll_events));
+
+ return 0;
+}
+
+#else
+int fdevent_linux_sysepoll_init(fdevents *ev) {
+ UNUSED(ev);
+
+ fprintf(stderr, "%s.%d: linux-sysepoll not supported, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+
+ return -1;
+}
+#endif
diff --git a/src/fdevent_poll.c b/src/fdevent_poll.c
new file mode 100644
index 0000000..7d8017a
--- /dev/null
+++ b/src/fdevent_poll.c
@@ -0,0 +1,182 @@
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "settings.h"
+#include "buffer.h"
+
+#ifdef USE_POLL
+static void fdevent_poll_free(fdevents *ev) {
+ free(ev->pollfds);
+ if (ev->unused.ptr) free(ev->unused.ptr);
+}
+
+static int fdevent_poll_event_del(fdevents *ev, int fde_ndx, int fd) {
+ if (fde_ndx < 0) return -1;
+
+ if ((size_t)fde_ndx >= ev->used) {
+ fprintf(stderr, "%s.%d: del! out of range %d %zd\n", __FILE__, __LINE__, fde_ndx, ev->used);
+ SEGFAULT();
+ }
+
+ if (ev->pollfds[fde_ndx].fd == fd) {
+ size_t k = fde_ndx;
+
+ ev->pollfds[k].fd = -1;
+ /* ev->pollfds[k].events = 0; */
+ /* ev->pollfds[k].revents = 0; */
+
+ if (ev->unused.size == 0) {
+ ev->unused.size = 16;
+ ev->unused.ptr = malloc(sizeof(*(ev->unused.ptr)) * ev->unused.size);
+ } else if (ev->unused.size == ev->unused.used) {
+ ev->unused.size += 16;
+ ev->unused.ptr = realloc(ev->unused.ptr, sizeof(*(ev->unused.ptr)) * ev->unused.size);
+ }
+
+ ev->unused.ptr[ev->unused.used++] = k;
+ } else {
+ SEGFAULT();
+ }
+
+ return -1;
+}
+
+#if 0
+static int fdevent_poll_event_compress(fdevents *ev) {
+ size_t j;
+
+ if (ev->used == 0) return 0;
+ if (ev->unused.used != 0) return 0;
+
+ for (j = ev->used - 1; j + 1 > 0 && ev->pollfds[j].fd == -1; j--) ev->used--;
+
+ return 0;
+}
+#endif
+
+static int fdevent_poll_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
+ /* known index */
+
+ if (fde_ndx != -1) {
+ if (ev->pollfds[fde_ndx].fd == fd) {
+ ev->pollfds[fde_ndx].events = events;
+
+ return fde_ndx;
+ }
+ fprintf(stderr, "%s.%d: add: (%d, %d)\n", __FILE__, __LINE__, fde_ndx, ev->pollfds[fde_ndx].fd);
+ SEGFAULT();
+ }
+
+ if (ev->unused.used > 0) {
+ int k = ev->unused.ptr[--ev->unused.used];
+
+ ev->pollfds[k].fd = fd;
+ ev->pollfds[k].events = events;
+
+ return k;
+ } else {
+ if (ev->size == 0) {
+ ev->size = 16;
+ ev->pollfds = malloc(sizeof(*ev->pollfds) * ev->size);
+ } else if (ev->size == ev->used) {
+ ev->size += 16;
+ ev->pollfds = realloc(ev->pollfds, sizeof(*ev->pollfds) * ev->size);
+ }
+
+ ev->pollfds[ev->used].fd = fd;
+ ev->pollfds[ev->used].events = events;
+
+ return ev->used++;
+ }
+}
+
+static int fdevent_poll_poll(fdevents *ev, int timeout_ms) {
+#if 0
+ fdevent_poll_event_compress(ev);
+#endif
+ return poll(ev->pollfds, ev->used, timeout_ms);
+}
+
+static int fdevent_poll_event_get_revent(fdevents *ev, size_t ndx) {
+ int r, poll_r;
+ if (ndx >= ev->used) {
+ fprintf(stderr, "%s.%d: dying because: event: %zd >= %zd\n", __FILE__, __LINE__, ndx, ev->used);
+
+ SEGFAULT();
+
+ return 0;
+ }
+
+ if (ev->pollfds[ndx].revents & POLLNVAL) {
+ /* should never happen */
+ SEGFAULT();
+ }
+
+ r = 0;
+ poll_r = ev->pollfds[ndx].revents;
+
+ /* map POLL* to FDEVEN_* */
+
+ if (poll_r & POLLIN) r |= FDEVENT_IN;
+ if (poll_r & POLLOUT) r |= FDEVENT_OUT;
+ if (poll_r & POLLERR) r |= FDEVENT_ERR;
+ if (poll_r & POLLHUP) r |= FDEVENT_HUP;
+ if (poll_r & POLLNVAL) r |= FDEVENT_NVAL;
+ if (poll_r & POLLPRI) r |= FDEVENT_PRI;
+
+ return ev->pollfds[ndx].revents;
+}
+
+static int fdevent_poll_event_get_fd(fdevents *ev, size_t ndx) {
+ return ev->pollfds[ndx].fd;
+}
+
+static int fdevent_poll_event_next_fdndx(fdevents *ev, int ndx) {
+ size_t i;
+
+ i = (ndx < 0) ? 0 : ndx + 1;
+ for (; i < ev->used; i++) {
+ if (ev->pollfds[i].revents) break;
+ }
+
+ return i;
+}
+
+int fdevent_poll_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_POLL;
+#define SET(x) \
+ ev->x = fdevent_poll_##x;
+
+ SET(free);
+ SET(poll);
+
+ SET(event_del);
+ SET(event_add);
+
+ SET(event_next_fdndx);
+ SET(event_get_fd);
+ SET(event_get_revent);
+
+ return 0;
+}
+
+
+
+
+#else
+int fdevent_poll_init(fdevents *ev) {
+ UNUSED(ev);
+
+ fprintf(stderr, "%s.%d: poll is not supported, try to set server.event-handler = \"select\"\n",
+ __FILE__, __LINE__);
+ return -1;
+}
+#endif
diff --git a/src/fdevent_select.c b/src/fdevent_select.c
new file mode 100644
index 0000000..3eb10f3
--- /dev/null
+++ b/src/fdevent_select.c
@@ -0,0 +1,131 @@
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "fdevent.h"
+#include "settings.h"
+#include "buffer.h"
+
+#ifdef USE_SELECT
+
+static int fdevent_select_reset(fdevents *ev) {
+ FD_ZERO(&(ev->select_set_read));
+ FD_ZERO(&(ev->select_set_write));
+ FD_ZERO(&(ev->select_set_error));
+ ev->select_max_fd = -1;
+
+ return 0;
+}
+
+static int fdevent_select_event_del(fdevents *ev, int fde_ndx, int fd) {
+ if (fde_ndx < 0) return -1;
+
+ FD_CLR(fd, &(ev->select_set_read));
+ FD_CLR(fd, &(ev->select_set_write));
+ FD_CLR(fd, &(ev->select_set_error));
+
+ return -1;
+}
+
+static int fdevent_select_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
+ UNUSED(fde_ndx);
+
+ /* we should be protected by max-fds, but you never know */
+ assert(fd < FD_SETSIZE);
+
+ if (events & FDEVENT_IN) {
+ FD_SET(fd, &(ev->select_set_read));
+ FD_CLR(fd, &(ev->select_set_write));
+ }
+ if (events & FDEVENT_OUT) {
+ FD_CLR(fd, &(ev->select_set_read));
+ FD_SET(fd, &(ev->select_set_write));
+ }
+ FD_SET(fd, &(ev->select_set_error));
+
+ if (fd > ev->select_max_fd) ev->select_max_fd = fd;
+
+ return fd;
+}
+
+static int fdevent_select_poll(fdevents *ev, int timeout_ms) {
+ struct timeval tv;
+
+ tv.tv_sec = timeout_ms / 1000;
+ tv.tv_usec = (timeout_ms % 1000) * 1000;
+
+ ev->select_read = ev->select_set_read;
+ ev->select_write = ev->select_set_write;
+ ev->select_error = ev->select_set_error;
+
+ return select(ev->select_max_fd + 1, &(ev->select_read), &(ev->select_write), &(ev->select_error), &tv);
+}
+
+static int fdevent_select_event_get_revent(fdevents *ev, size_t ndx) {
+ int revents = 0;
+
+ if (FD_ISSET(ndx, &(ev->select_read))) {
+ revents |= FDEVENT_IN;
+ }
+ if (FD_ISSET(ndx, &(ev->select_write))) {
+ revents |= FDEVENT_OUT;
+ }
+ if (FD_ISSET(ndx, &(ev->select_error))) {
+ revents |= FDEVENT_ERR;
+ }
+
+ return revents;
+}
+
+static int fdevent_select_event_get_fd(fdevents *ev, size_t ndx) {
+ UNUSED(ev);
+
+ return ndx;
+}
+
+static int fdevent_select_event_next_fdndx(fdevents *ev, int ndx) {
+ int i;
+
+ i = (ndx < 0) ? 0 : ndx + 1;
+
+ for (; i < ev->select_max_fd + 1; i++) {
+ if (FD_ISSET(i, &(ev->select_read))) break;
+ if (FD_ISSET(i, &(ev->select_write))) break;
+ if (FD_ISSET(i, &(ev->select_error))) break;
+ }
+
+ return i;
+}
+
+int fdevent_select_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_SELECT;
+#define SET(x) \
+ ev->x = fdevent_select_##x;
+
+ SET(reset);
+ SET(poll);
+
+ SET(event_del);
+ SET(event_add);
+
+ SET(event_next_fdndx);
+ SET(event_get_fd);
+ SET(event_get_revent);
+
+ return 0;
+}
+
+#else
+int fdevent_select_init(fdevents *ev) {
+ UNUSED(ev);
+
+ return -1;
+}
+#endif
diff --git a/src/fdevent_solaris_devpoll.c b/src/fdevent_solaris_devpoll.c
new file mode 100644
index 0000000..91238b0
--- /dev/null
+++ b/src/fdevent_solaris_devpoll.c
@@ -0,0 +1,158 @@
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "settings.h"
+#include "buffer.h"
+
+#ifdef USE_SOLARIS_DEVPOLL
+
+static void fdevent_solaris_devpoll_free(fdevents *ev) {
+ free(ev->devpollfds);
+ close(ev->devpoll_fd);
+}
+
+/* return -1 is fine here */
+
+static int fdevent_solaris_devpoll_event_del(fdevents *ev, int fde_ndx, int fd) {
+ struct pollfd pfd;
+
+ if (fde_ndx < 0) return -1;
+
+ pfd.fd = fd;
+ pfd.events = POLLREMOVE;
+ pfd.revents = 0;
+
+ if (-1 == write(ev->devpoll_fd, &pfd, sizeof(pfd))) {
+ fprintf(stderr, "%s.%d: (del) write failed: (%d, %s)\n",
+ __FILE__, __LINE__,
+ fd, strerror(errno));
+
+ return -1;
+ }
+
+ return -1;
+}
+
+static int fdevent_solaris_devpoll_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
+ struct pollfd pfd;
+ int add = 0;
+
+ if (fde_ndx == -1) add = 1;
+
+ pfd.fd = fd;
+ pfd.events = events;
+ pfd.revents = 0;
+
+ if (-1 == write(ev->devpoll_fd, &pfd, sizeof(pfd))) {
+ fprintf(stderr, "%s.%d: (del) write failed: (%d, %s)\n",
+ __FILE__, __LINE__,
+ fd, strerror(errno));
+
+ return -1;
+ }
+
+ return fd;
+}
+
+static int fdevent_solaris_devpoll_poll(fdevents *ev, int timeout_ms) {
+ struct dvpoll dopoll;
+ int ret;
+
+ dopoll.dp_timeout = timeout_ms;
+ dopoll.dp_nfds = ev->maxfds;
+ dopoll.dp_fds = ev->devpollfds;
+
+ ret = ioctl(ev->devpoll_fd, DP_POLL, &dopoll);
+
+ return ret;
+}
+
+static int fdevent_solaris_devpoll_event_get_revent(fdevents *ev, size_t ndx) {
+ return ev->devpollfds[ndx].revents;
+}
+
+static int fdevent_solaris_devpoll_event_get_fd(fdevents *ev, size_t ndx) {
+ return ev->devpollfds[ndx].fd;
+}
+
+static int fdevent_solaris_devpoll_event_next_fdndx(fdevents *ev, int last_ndx) {
+ size_t i;
+
+ UNUSED(ev);
+
+ i = (last_ndx < 0) ? 0 : last_ndx + 1;
+
+ return i;
+}
+
+int fdevent_solaris_devpoll_reset(fdevents *ev) {
+ /* a forked process does only inherit the filedescriptor,
+ * but every operation on the device will lead to a EACCES */
+ if ((ev->devpoll_fd = open("/dev/poll", O_RDWR)) < 0) {
+ fprintf(stderr, "%s.%d: opening /dev/poll failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ if (fcntl(ev->devpoll_fd, F_SETFD, FD_CLOEXEC) < 0) {
+ fprintf(stderr, "%s.%d: opening /dev/poll failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ close(ev->devpoll_fd);
+
+ return -1;
+ }
+ return 0;
+}
+int fdevent_solaris_devpoll_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_SOLARIS_DEVPOLL;
+#define SET(x) \
+ ev->x = fdevent_solaris_devpoll_##x;
+
+ SET(free);
+ SET(poll);
+ SET(reset);
+
+ SET(event_del);
+ SET(event_add);
+
+ SET(event_next_fdndx);
+ SET(event_get_fd);
+ SET(event_get_revent);
+
+ ev->devpollfds = malloc(sizeof(*ev->devpollfds) * ev->maxfds);
+
+ if ((ev->devpoll_fd = open("/dev/poll", O_RDWR)) < 0) {
+ fprintf(stderr, "%s.%d: opening /dev/poll failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ /* we just wanted to check if it works */
+ close(ev->devpoll_fd);
+
+ ev->devpoll_fd = -1;
+
+ return 0;
+}
+
+#else
+int fdevent_solaris_devpoll_init(fdevents *ev) {
+ UNUSED(ev);
+
+ fprintf(stderr, "%s.%d: solaris-devpoll not supported, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+
+ return -1;
+}
+#endif
diff --git a/src/http-header-glue.c b/src/http-header-glue.c
new file mode 100644
index 0000000..4771a5e
--- /dev/null
+++ b/src/http-header-glue.c
@@ -0,0 +1,319 @@
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+
+#include "base.h"
+#include "array.h"
+#include "buffer.h"
+#include "log.h"
+#include "etag.h"
+
+/*
+ * This was 'borrowed' from tcpdump.
+ *
+ *
+ * This is fun.
+ *
+ * In older BSD systems, socket addresses were fixed-length, and
+ * "sizeof (struct sockaddr)" gave the size of the structure.
+ * All addresses fit within a "struct sockaddr".
+ *
+ * In newer BSD systems, the socket address is variable-length, and
+ * there's an "sa_len" field giving the length of the structure;
+ * this allows socket addresses to be longer than 2 bytes of family
+ * and 14 bytes of data.
+ *
+ * Some commercial UNIXes use the old BSD scheme, some use the RFC 2553
+ * variant of the old BSD scheme (with "struct sockaddr_storage" rather
+ * than "struct sockaddr"), and some use the new BSD scheme.
+ *
+ * Some versions of GNU libc use neither scheme, but has an "SA_LEN()"
+ * macro that determines the size based on the address family. Other
+ * versions don't have "SA_LEN()" (as it was in drafts of RFC 2553
+ * but not in the final version). On the latter systems, we explicitly
+ * check the AF_ type to determine the length; we assume that on
+ * all those systems we have "struct sockaddr_storage".
+ */
+
+#ifdef HAVE_IPV6
+# ifndef SA_LEN
+# ifdef HAVE_SOCKADDR_SA_LEN
+# define SA_LEN(addr) ((addr)->sa_len)
+# else /* HAVE_SOCKADDR_SA_LEN */
+# ifdef HAVE_STRUCT_SOCKADDR_STORAGE
+static size_t get_sa_len(const struct sockaddr *addr) {
+ switch (addr->sa_family) {
+
+# ifdef AF_INET
+ case AF_INET:
+ return (sizeof (struct sockaddr_in));
+# endif
+
+# ifdef AF_INET6
+ case AF_INET6:
+ return (sizeof (struct sockaddr_in6));
+# endif
+
+ default:
+ return (sizeof (struct sockaddr));
+
+ }
+}
+# define SA_LEN(addr) (get_sa_len(addr))
+# else /* HAVE_SOCKADDR_STORAGE */
+# define SA_LEN(addr) (sizeof (struct sockaddr))
+# endif /* HAVE_SOCKADDR_STORAGE */
+# endif /* HAVE_SOCKADDR_SA_LEN */
+# endif /* SA_LEN */
+#endif
+
+
+
+
+int response_header_insert(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen) {
+ data_string *ds;
+
+ UNUSED(srv);
+
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
+ ds = data_response_init();
+ }
+ buffer_copy_string_len(ds->key, key, keylen);
+ buffer_copy_string_len(ds->value, value, vallen);
+
+ array_insert_unique(con->response.headers, (data_unset *)ds);
+
+ return 0;
+}
+
+int response_header_overwrite(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen) {
+ data_string *ds;
+
+ UNUSED(srv);
+
+ /* if there already is a key by this name overwrite the value */
+ if (NULL != (ds = (data_string *)array_get_element(con->response.headers, key))) {
+ buffer_copy_string(ds->value, value);
+
+ return 0;
+ }
+
+ return response_header_insert(srv, con, key, keylen, value, vallen);
+}
+
+int http_response_redirect_to_directory(server *srv, connection *con) {
+ buffer *o;
+
+ o = buffer_init();
+
+ if (con->conf.is_ssl) {
+ buffer_copy_string(o, "https://");
+ } else {
+ buffer_copy_string(o, "http://");
+ }
+ if (con->uri.authority->used) {
+ buffer_append_string_buffer(o, con->uri.authority);
+ } else {
+ /* get the name of the currently connected socket */
+ struct hostent *he;
+#ifdef HAVE_IPV6
+ char hbuf[256];
+#endif
+ sock_addr our_addr;
+ socklen_t our_addr_len;
+
+ our_addr_len = sizeof(our_addr);
+
+ if (-1 == getsockname(con->fd, &(our_addr.plain), &our_addr_len)) {
+ con->http_status = 500;
+
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "can't get sockname", strerror(errno));
+
+ buffer_free(o);
+ return 0;
+ }
+
+
+ /* Lookup name: secondly try to get hostname for bind address */
+ switch(our_addr.plain.sa_family) {
+#ifdef HAVE_IPV6
+ case AF_INET6:
+ if (0 != getnameinfo((const struct sockaddr *)(&our_addr.ipv6),
+ SA_LEN((const struct sockaddr *)&our_addr.ipv6),
+ hbuf, sizeof(hbuf), NULL, 0, 0)) {
+
+ char dst[INET6_ADDRSTRLEN];
+
+ log_error_write(srv, __FILE__, __LINE__,
+ "SSSS", "NOTICE: getnameinfo failed: ",
+ strerror(errno), ", using ip-address instead");
+
+ buffer_append_string(o,
+ inet_ntop(AF_INET6, (char *)&our_addr.ipv6.sin6_addr,
+ dst, sizeof(dst)));
+ } else {
+ buffer_append_string(o, hbuf);
+ }
+ break;
+#endif
+ case AF_INET:
+ if (NULL == (he = gethostbyaddr((char *)&our_addr.ipv4.sin_addr, sizeof(struct in_addr), AF_INET))) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "SdSS", "NOTICE: gethostbyaddr failed: ",
+ h_errno, ", using ip-address instead");
+
+ buffer_append_string(o, inet_ntoa(our_addr.ipv4.sin_addr));
+ } else {
+ buffer_append_string(o, he->h_name);
+ }
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__,
+ "S", "ERROR: unsupported address-type");
+
+ buffer_free(o);
+ return -1;
+ }
+
+ if (!((con->conf.is_ssl == 0 && srv->srvconf.port == 80) ||
+ (con->conf.is_ssl == 1 && srv->srvconf.port == 443))) {
+ buffer_append_string(o, ":");
+ buffer_append_long(o, srv->srvconf.port);
+ }
+ }
+ buffer_append_string_buffer(o, con->uri.path);
+ buffer_append_string(o, "/");
+ if (!buffer_is_empty(con->uri.query)) {
+ buffer_append_string(o, "?");
+ buffer_append_string_buffer(o, con->uri.query);
+ }
+
+ response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(o));
+
+ con->http_status = 301;
+ con->file_finished = 1;
+
+ buffer_free(o);
+
+ return 0;
+}
+
+buffer * strftime_cache_get(server *srv, time_t last_mod) {
+ struct tm *tm;
+ size_t i;
+
+ for (i = 0; i < FILE_CACHE_MAX; i++) {
+ /* found cache-entry */
+ if (srv->mtime_cache[i].mtime == last_mod) return srv->mtime_cache[i].str;
+
+ /* found empty slot */
+ if (srv->mtime_cache[i].mtime == 0) break;
+ }
+
+ if (i == FILE_CACHE_MAX) {
+ i = 0;
+ }
+
+ srv->mtime_cache[i].mtime = last_mod;
+ buffer_prepare_copy(srv->mtime_cache[i].str, 1024);
+ tm = gmtime(&(srv->mtime_cache[i].mtime));
+ srv->mtime_cache[i].str->used = strftime(srv->mtime_cache[i].str->ptr,
+ srv->mtime_cache[i].str->size - 1,
+ "%a, %d %b %Y %H:%M:%S GMT", tm);
+ srv->mtime_cache[i].str->used++;
+
+ return srv->mtime_cache[i].str;
+}
+
+
+int http_response_handle_cachable(server *srv, connection *con, buffer *mtime) {
+ /*
+ * 14.26 If-None-Match
+ * [...]
+ * If none of the entity tags match, then the server MAY perform the
+ * requested method as if the If-None-Match header field did not exist,
+ * but MUST also ignore any If-Modified-Since header field(s) in the
+ * request. That is, if no entity tags match, then the server MUST NOT
+ * return a 304 (Not Modified) response.
+ */
+
+ /* last-modified handling */
+ if (con->request.http_if_none_match) {
+ if (etag_is_equal(con->physical.etag, con->request.http_if_none_match)) {
+ if (con->request.http_method == HTTP_METHOD_GET ||
+ con->request.http_method == HTTP_METHOD_HEAD) {
+
+ /* check if etag + last-modified */
+ if (con->request.http_if_modified_since) {
+ size_t used_len;
+ char *semicolon;
+
+ if (NULL == (semicolon = strchr(con->request.http_if_modified_since, ';'))) {
+ used_len = strlen(con->request.http_if_modified_since);
+ } else {
+ used_len = semicolon - con->request.http_if_modified_since;
+ }
+
+ if (0 == strncmp(con->request.http_if_modified_since, mtime->ptr, used_len)) {
+ con->http_status = 304;
+ return HANDLER_FINISHED;
+ } else {
+ char buf[sizeof("Sat, 23 Jul 2005 21:20:01 GMT")];
+
+ /* convert to timestamp */
+ if (used_len < sizeof(buf)) {
+ time_t t_header, t_file;
+ struct tm tm;
+
+ strncpy(buf, con->request.http_if_modified_since, used_len);
+ buf[used_len] = '\0';
+
+ strptime(buf, "%a, %d %b %Y %H:%M:%S GMT", &tm);
+ t_header = mktime(&tm);
+
+ strptime(mtime->ptr, "%a, %d %b %Y %H:%M:%S GMT", &tm);
+ t_file = mktime(&tm);
+
+ if (t_file > t_header) {
+ con->http_status = 304;
+ return HANDLER_FINISHED;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssdd",
+ "DEBUG: Last-Modified check failed as the received timestamp was too long:",
+ con->request.http_if_modified_since, used_len, sizeof(buf) - 1);
+
+ con->http_status = 412;
+ return HANDLER_FINISHED;
+ }
+ }
+ } else {
+ con->http_status = 304;
+ return HANDLER_FINISHED;
+ }
+ } else {
+ con->http_status = 412;
+ return HANDLER_FINISHED;
+ }
+ }
+ } else if (con->request.http_if_modified_since) {
+ size_t used_len;
+ char *semicolon;
+
+ if (NULL == (semicolon = strchr(con->request.http_if_modified_since, ';'))) {
+ used_len = strlen(con->request.http_if_modified_since);
+ } else {
+ used_len = semicolon - con->request.http_if_modified_since;
+ }
+
+ if (0 == strncmp(con->request.http_if_modified_since, mtime->ptr, used_len)) {
+ con->http_status = 304;
+ return HANDLER_FINISHED;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
diff --git a/src/http_auth.c b/src/http_auth.c
new file mode 100644
index 0000000..d4de212
--- /dev/null
+++ b/src/http_auth.c
@@ -0,0 +1,1007 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_CRYPT_H
+# include <crypt.h>
+#elif defined(__linux__)
+/* linux needs _XOPEN_SOURCE */
+# define _XOPEN_SOURCE
+#endif
+
+#ifdef HAVE_LIBCRYPT
+# define HAVE_CRYPT
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "server.h"
+#include "log.h"
+#include "http_auth.h"
+#include "http_auth_digest.h"
+#include "stream.h"
+
+#ifdef USE_OPENSSL
+# include <openssl/md5.h>
+#else
+# include "md5.h"
+#endif
+
+
+#ifdef USE_PAM
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+
+static struct pam_conv conv = {
+ misc_conv,
+ NULL
+};
+#endif
+
+handler_t auth_ldap_init(server *srv, mod_auth_plugin_config *s);
+
+static const char base64_pad = '=';
+
+static const short base64_reverse_table[256] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+
+static unsigned char * base64_decode(buffer *out, const char *in) {
+ unsigned char *result;
+ int ch, j = 0, k;
+ size_t i;
+
+ size_t in_len = strlen(in);
+
+ buffer_prepare_copy(out, in_len);
+
+ result = (unsigned char *)out->ptr;
+
+ ch = in[0];
+ /* run through the whole string, converting as we go */
+ for (i = 0; i < in_len; i++) {
+ ch = in[i];
+
+ if (ch == '\0') break;
+
+ if (ch == base64_pad) break;
+
+ ch = base64_reverse_table[ch];
+ if (ch < 0) continue;
+
+ switch(i % 4) {
+ case 0:
+ result[j] = ch << 2;
+ break;
+ case 1:
+ result[j++] |= ch >> 4;
+ result[j] = (ch & 0x0f) << 4;
+ break;
+ case 2:
+ result[j++] |= ch >>2;
+ result[j] = (ch & 0x03) << 6;
+ break;
+ case 3:
+ result[j++] |= ch;
+ break;
+ }
+ }
+ k = j;
+ /* mop things up if we ended on a boundary */
+ if (ch == base64_pad) {
+ switch(i % 4) {
+ case 0:
+ case 1:
+ return NULL;
+ case 2:
+ k++;
+ case 3:
+ result[k++] = 0;
+ }
+ }
+ result[k] = '\0';
+
+ out->used = k;
+
+ return result;
+}
+
+static int http_auth_get_password(server *srv, mod_auth_plugin_data *p, buffer *username, buffer *realm, buffer *password) {
+ int ret = -1;
+
+ if (!username->used|| !realm->used) return -1;
+
+ if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
+ stream f;
+ char * f_line;
+
+ if (buffer_is_empty(p->conf.auth_htdigest_userfile)) return -1;
+
+ if (0 != stream_open(&f, p->conf.auth_htdigest_userfile)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "opening digest-userfile", p->conf.auth_htdigest_userfile, "failed:", strerror(errno));
+
+ return -1;
+ }
+
+ f_line = f.start;
+
+ while (f_line - f.start != f.size) {
+ char *f_user, *f_pwd, *e, *f_realm;
+ size_t u_len, pwd_len, r_len;
+
+ f_user = f_line;
+
+ /*
+ * htdigest format
+ *
+ * user:realm:md5(user:realm:password)
+ */
+
+ if (NULL == (f_realm = memchr(f_user, ':', f.size - (f_user - f.start) ))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "parsed error in", p->conf.auth_htdigest_userfile,
+ "expected 'username:realm:hashed password'");
+
+ stream_close(&f);
+
+ return -1;
+ }
+
+ if (NULL == (f_pwd = memchr(f_realm + 1, ':', f.size - (f_realm + 1 - f.start)))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "parsed error in", p->conf.auth_plain_userfile,
+ "expected 'username:realm:hashed password'");
+
+ stream_close(&f);
+
+ return -1;
+ }
+
+ /* get pointers to the fields */
+ u_len = f_realm - f_user;
+ f_realm++;
+ r_len = f_pwd - f_realm;
+ f_pwd++;
+
+ if (NULL != (e = memchr(f_pwd, '\n', f.size - (f_pwd - f.start)))) {
+ pwd_len = e - f_pwd;
+ } else {
+ pwd_len = f.size - (f_pwd - f.start);
+ }
+
+ if (username->used - 1 == u_len &&
+ (realm->used - 1 == r_len) &&
+ (0 == strncmp(username->ptr, f_user, u_len)) &&
+ (0 == strncmp(realm->ptr, f_realm, r_len))) {
+ /* found */
+
+ buffer_copy_string_len(password, f_pwd, pwd_len);
+
+ ret = 0;
+ break;
+ }
+
+ /* EOL */
+ if (!e) break;
+
+ f_line = e + 1;
+ }
+
+ stream_close(&f);
+ } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD ||
+ p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
+ stream f;
+ char * f_line;
+ buffer *auth_fn;
+
+ auth_fn = (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) ? p->conf.auth_htpasswd_userfile : p->conf.auth_plain_userfile;
+
+ if (buffer_is_empty(auth_fn)) return -1;
+
+ if (0 != stream_open(&f, auth_fn)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "opening plain-userfile", auth_fn, "failed:", strerror(errno));
+
+ return -1;
+ }
+
+ f_line = f.start;
+
+ while (f_line - f.start != f.size) {
+ char *f_user, *f_pwd, *e;
+ size_t u_len, pwd_len;
+
+ f_user = f_line;
+
+ /*
+ * htpasswd format
+ *
+ * user:crypted passwd
+ */
+
+ if (NULL == (f_pwd = memchr(f_user, ':', f.size - (f_user - f.start) ))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "parsed error in", auth_fn,
+ "expected 'username:hashed password'");
+
+ stream_close(&f);
+
+ return -1;
+ }
+
+ /* get pointers to the fields */
+ u_len = f_pwd - f_user;
+ f_pwd++;
+
+ if (NULL != (e = memchr(f_pwd, '\n', f.size - (f_pwd - f.start)))) {
+ pwd_len = e - f_pwd;
+ } else {
+ pwd_len = f.size - (f_pwd - f.start);
+ }
+
+ if (username->used - 1 == u_len &&
+ (0 == strncmp(username->ptr, f_user, u_len))) {
+ /* found */
+
+ buffer_copy_string_len(password, f_pwd, pwd_len);
+
+ ret = 0;
+ break;
+ }
+
+ /* EOL */
+ if (!e) break;
+
+ f_line = e + 1;
+ }
+
+ stream_close(&f);
+ } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
+ ret = 0;
+ } else {
+ return -1;
+ }
+
+ return ret;
+}
+
+static int http_auth_match_rules(server *srv, mod_auth_plugin_data *p, const char *url, const char *username, const char *group, const char *host) {
+ const char *r = NULL, *rules = NULL;
+ size_t i;
+ int username_len;
+ data_string *require;
+ array *req;
+
+ UNUSED(group);
+ UNUSED(host);
+
+ /* check what has to be match to fullfil the request */
+ /* search auth-directives for path */
+ for (i = 0; i < p->conf.auth_require->used; i++) {
+ if (p->conf.auth_require->data[i]->key->used == 0) continue;
+
+ if (0 == strncmp(url, p->conf.auth_require->data[i]->key->ptr, p->conf.auth_require->data[i]->key->used - 1)) {
+ break;
+ }
+ }
+
+ if (i == p->conf.auth_require->used) {
+ return -1;
+ }
+
+ req = ((data_array *)(p->conf.auth_require->data[i]))->value;
+
+ require = (data_string *)array_get_element(req, "require");
+
+ /* if we get here, the user we got a authed user */
+ if (0 == strcmp(require->value->ptr, "valid-user")) {
+ return 0;
+ }
+
+ /* user=name1|group=name3|host=name4 */
+
+ /* seperate the string by | */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sb", "rules", require->value);
+#endif
+
+ username_len = username ? strlen(username) : 0;
+
+ r = rules = require->value->ptr;
+
+ while (1) {
+ const char *eq;
+ const char *k, *v, *e;
+ int k_len, v_len, r_len;
+
+ e = strchr(r, '|');
+
+ if (e) {
+ r_len = e - r;
+ } else {
+ r_len = strlen(rules) - (r - rules);
+ }
+
+ /* from r to r + r_len is a rule */
+
+ if (0 == strncmp(r, "valid-user", r_len)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "parsing the 'require' section in 'auth.require' failed: valid-user cannot be combined with other require rules",
+ require->value);
+ return -1;
+ }
+
+ /* search for = in the rules */
+ if (NULL == (eq = strchr(r, '='))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "parsing the 'require' section in 'auth.require' failed: a = is missing",
+ require->value);
+ return -1;
+ }
+
+ /* = out of range */
+ if (eq > r + r_len) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "parsing the 'require' section in 'auth.require' failed: = out of range",
+ require->value);
+
+ return -1;
+ }
+
+ /* the part before the = is user|group|host */
+
+ k = r;
+ k_len = eq - r;
+ v = eq + 1;
+ v_len = r_len - k_len - 1;
+
+ if (k_len == 4) {
+ if (0 == strncmp(k, "user", k_len)) {
+ if (username &&
+ username_len == v_len &&
+ 0 == strncmp(username, v, v_len)) {
+ return 0;
+ }
+ } else if (0 == strncmp(k, "host", k_len)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "host ... (not implemented)");
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown key");
+ return -1;
+ }
+ } else if (k_len == 5) {
+ if (0 == strncmp(k, "group", k_len)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "group ... (not implemented)");
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "unknown key", k);
+ return -1;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown key");
+ return -1;
+ }
+
+ if (!e) break;
+ r = e + 1;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "nothing matched");
+
+ return -1;
+}
+
+/**
+ *
+ *
+ * @param password password-string from the auth-backend
+ * @param pw password-string from the client
+ */
+
+static int http_auth_basic_password_compare(server *srv, mod_auth_plugin_data *p, array *req, buffer *username, buffer *realm, buffer *password, const char *pw) {
+ UNUSED(srv);
+ UNUSED(req);
+
+ if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
+ /*
+ * htdigest format
+ *
+ * user:realm:md5(user:realm:password)
+ */
+
+ MD5_CTX Md5Ctx;
+ HASH HA1;
+ char a1[256];
+
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)username->ptr, username->used - 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)realm->ptr, realm->used - 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pw, strlen(pw));
+ MD5_Final(HA1, &Md5Ctx);
+
+ CvtHex(HA1, a1);
+
+ if (0 == strcmp(password->ptr, a1)) {
+ return 0;
+ }
+ } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) {
+#ifdef HAVE_CRYPT
+ char salt[32];
+ char *crypted;
+ size_t salt_len = 0;
+ /*
+ * htpasswd format
+ *
+ * user:crypted password
+ */
+
+ /*
+ * Algorithm Salt
+ * CRYPT_STD_DES 2-character (Default)
+ * CRYPT_EXT_DES 9-character
+ * CRYPT_MD5 12-character beginning with $1$
+ * CRYPT_BLOWFISH 16-character beginning with $2$
+ */
+
+ if (password->used < 13 + 1) {
+ fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
+ return -1;
+ }
+
+ if (password->used == 13 + 1) {
+ /* a simple DES password is 2 + 11 characters */
+ salt_len = 2;
+ } else if (password->ptr[0] == '$' && password->ptr[2] == '$') {
+ char *dollar = NULL;
+
+ if (NULL == (dollar = strchr(password->ptr + 3, '$'))) {
+ fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
+ return -1;
+ }
+
+ salt_len = dollar - password->ptr;
+ }
+
+ if (salt_len > sizeof(salt) - 1) {
+ fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
+ return -1;
+ }
+
+ strncpy(salt, password->ptr, salt_len);
+
+ salt[salt_len] = '\0';
+
+ crypted = crypt(pw, salt);
+
+ if (0 == strcmp(password->ptr, crypted)) {
+ return 0;
+ } else {
+ fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
+ }
+
+#endif
+ } else if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
+ if (0 == strcmp(password->ptr, pw)) {
+ return 0;
+ }
+ } else if (p->conf.auth_backend == AUTH_BACKEND_PAM) {
+#ifdef USE_PAM
+ pam_handle_t *pamh=NULL;
+ int retval;
+
+ retval = pam_start("lighttpd", username->ptr, &conv, &pamh);
+
+ if (retval == PAM_SUCCESS)
+ retval = pam_authenticate(pamh, 0); /* is user really user? */
+
+ if (retval == PAM_SUCCESS)
+ retval = pam_acct_mgmt(pamh, 0); /* permitted access? */
+
+ /* This is where we have been authorized or not. */
+
+ if (pam_end(pamh,retval) != PAM_SUCCESS) { /* close Linux-PAM */
+ pamh = NULL;
+ log_error_write(srv, __FILE__, __LINE__, "s", "failed to release authenticator");
+ }
+
+ if (retval == PAM_SUCCESS) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "Authenticated");
+ return 0;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "Not Authenticated");
+ }
+#endif
+ } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
+#ifdef USE_LDAP
+ LDAP *ldap;
+ LDAPMessage *lm, *first;
+ char *dn;
+ int ret;
+ char *attrs[] = { LDAP_NO_ATTRS, NULL };
+ size_t i;
+
+ /* for now we stay synchronous */
+
+ /*
+ * 1. connect anonymously (done in plugin init)
+ * 2. get DN for uid = username
+ * 3. auth against ldap server
+ * 4. (optional) check a field
+ * 5. disconnect
+ *
+ */
+
+ /* check username
+ *
+ * we have to protect us againt username which modifies out filter in
+ * a unpleasant way
+ */
+
+ for (i = 0; i < username->used - 1; i++) {
+ char c = username->ptr[i];
+
+ if (!isalpha(c) &&
+ !isdigit(c)) {
+
+ log_error_write(srv, __FILE__, __LINE__, "sbd",
+ "ldap: invalid character (a-zA-Z0-9 allowed) in username:", username, i);
+
+ return -1;
+ }
+ }
+
+
+
+ /* build filter */
+ buffer_copy_string_buffer(p->ldap_filter, p->conf.ldap_filter_pre);
+ buffer_append_string_buffer(p->ldap_filter, username);
+ buffer_append_string_buffer(p->ldap_filter, p->conf.ldap_filter_post);
+
+
+ /* 2. */
+ if (p->conf.ldap == NULL ||
+ LDAP_SUCCESS != (ret = ldap_search_s(p->conf.ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) {
+ if (auth_ldap_init(srv, &p->conf) != HANDLER_GO_ON)
+ return -1;
+ if (LDAP_SUCCESS != (ret = ldap_search_s(p->conf.ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) {
+
+ log_error_write(srv, __FILE__, __LINE__, "sssb",
+ "ldap:", ldap_err2string(ret), "filter:", p->ldap_filter);
+
+ return -1;
+ }
+ }
+
+ if (NULL == (first = ldap_first_entry(p->conf.ldap, lm))) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "ldap ...");
+
+ ldap_msgfree(lm);
+
+ return -1;
+ }
+
+ if (NULL == (dn = ldap_get_dn(p->conf.ldap, first))) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "ldap ...");
+
+ ldap_msgfree(lm);
+
+ return -1;
+ }
+
+ ldap_msgfree(lm);
+
+
+ /* 3. */
+ if (NULL == (ldap = ldap_init(p->conf.auth_ldap_hostname->ptr, LDAP_PORT))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap ...", strerror(errno));
+ return -1;
+ }
+
+ ret = LDAP_VERSION3;
+ if (LDAP_OPT_SUCCESS != (ret = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ret))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
+
+ ldap_unbind_s(ldap);
+
+ return -1;
+ }
+
+ if (p->conf.auth_ldap_starttls == 1) {
+ if (LDAP_OPT_SUCCESS != (ret = ldap_start_tls_s(ldap, NULL, NULL))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap startTLS failed:", ldap_err2string(ret));
+
+ ldap_unbind_s(ldap);
+
+ return -1;
+ }
+ }
+
+
+ if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(ldap, dn, pw))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
+
+ ldap_unbind_s(ldap);
+
+ return -1;
+ }
+
+ /* 5. */
+ ldap_unbind_s(ldap);
+
+ /* everything worked, good, access granted */
+
+ return 0;
+#endif
+ }
+ return -1;
+}
+
+int http_auth_basic_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str) {
+ buffer *username, *password;
+ char *pw;
+
+ data_string *realm;
+
+ realm = (data_string *)array_get_element(req, "realm");
+
+ username = buffer_init();
+ password = buffer_init();
+
+ base64_decode(username, realm_str);
+
+ /* r2 == user:password */
+ if (NULL == (pw = strchr(username->ptr, ':'))) {
+ buffer_free(username);
+
+ log_error_write(srv, __FILE__, __LINE__, "sb", ": is missing in", username);
+
+ return 0;
+ }
+
+ *pw++ = '\0';
+
+ username->used = pw - username->ptr;
+
+ /* copy password to r1 */
+ if (http_auth_get_password(srv, p, username, realm->value, password)) {
+ buffer_free(username);
+ buffer_free(password);
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "get_password failed");
+
+ return 0;
+ }
+
+ /* password doesn't match */
+ if (http_auth_basic_password_compare(srv, p, req, username, realm->value, password, pw)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbb", "password doesn't match for", con->uri.path, username);
+
+ buffer_free(username);
+ buffer_free(password);
+
+ return 0;
+ }
+
+ /* value is our allow-rules */
+ if (http_auth_match_rules(srv, p, url->ptr, username->ptr, NULL, NULL)) {
+ buffer_free(username);
+ buffer_free(password);
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "rules didn't match");
+
+ return 0;
+ }
+
+ /* remember the username */
+ buffer_copy_string_buffer(p->auth_user, username);
+
+ buffer_free(username);
+ buffer_free(password);
+
+ return 1;
+}
+
+typedef struct {
+ const char *key;
+ int key_len;
+ char **ptr;
+} digest_kv;
+
+int http_auth_digest_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str) {
+ char a1[256];
+ char a2[256];
+
+ char *username;
+ char *realm;
+ char *nonce;
+ char *uri;
+ char *algorithm;
+ char *qop;
+ char *cnonce;
+ char *nc;
+ char *respons;
+
+ char *e, *c;
+ const char *m = NULL;
+ int i;
+ buffer *password, *b, *username_buf, *realm_buf;
+
+ MD5_CTX Md5Ctx;
+ HASH HA1;
+ HASH HA2;
+ HASH RespHash;
+ HASHHEX HA2Hex;
+
+
+ /* init pointers */
+#define S(x) \
+ x, sizeof(x)-1, NULL
+ digest_kv dkv[10] = {
+ { S("username=") },
+ { S("realm=") },
+ { S("nonce=") },
+ { S("uri=") },
+ { S("algorithm=") },
+ { S("qop=") },
+ { S("cnonce=") },
+ { S("nc=") },
+ { S("response=") },
+
+ { NULL, 0, NULL }
+ };
+#undef S
+
+ dkv[0].ptr = &username;
+ dkv[1].ptr = &realm;
+ dkv[2].ptr = &nonce;
+ dkv[3].ptr = &uri;
+ dkv[4].ptr = &algorithm;
+ dkv[5].ptr = &qop;
+ dkv[6].ptr = &cnonce;
+ dkv[7].ptr = &nc;
+ dkv[8].ptr = &respons;
+ dkv[9].ptr = NULL;
+
+ UNUSED(req);
+
+ for (i = 0; dkv[i].key; i++) {
+ *(dkv[i].ptr) = NULL;
+ }
+
+
+ if (p->conf.auth_backend != AUTH_BACKEND_HTDIGEST &&
+ p->conf.auth_backend != AUTH_BACKEND_PLAIN) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "digest: unsupported backend (only htdigest or plain)");
+
+ return -1;
+ }
+
+ b = buffer_init_string(realm_str);
+
+ /* parse credentials from client */
+ for (c = b->ptr; *c; c++) {
+ /* skip whitespaces */
+ while (*c == ' ' || *c == '\t') c++;
+ if (!c) break;
+
+ for (i = 0; dkv[i].key; i++) {
+ if ((0 == strncmp(c, dkv[i].key, dkv[i].key_len))) {
+ if ((c[dkv[i].key_len] == '"') &&
+ (NULL != (e = strchr(c + dkv[i].key_len + 1, '"')))) {
+ /* value with "..." */
+ *(dkv[i].ptr) = c + dkv[i].key_len + 1;
+ c = e;
+
+ *e = '\0';
+ } else if (NULL != (e = strchr(c + dkv[i].key_len, ','))) {
+ /* value without "...", terminated by ',' */
+ *(dkv[i].ptr) = c + dkv[i].key_len;
+ c = e;
+
+ *e = '\0';
+ } else {
+ /* value without "...", terminated by EOL */
+ *(dkv[i].ptr) = c + dkv[i].key_len;
+ c += strlen(c) - 1;
+ }
+ }
+ }
+ }
+
+ if (p->conf.auth_debug > 1) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "username", username);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "realm", realm);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "nonce", nonce);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "uri", uri);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "algorigthm", algorithm);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "qop", qop);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "cnonce", cnonce);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "nc", nc);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "response", respons);
+ }
+
+ /* check if everything is transmitted */
+ if (!username ||
+ !realm ||
+ !nonce ||
+ !uri ||
+ (qop && (!nc || !cnonce)) ||
+ !respons ) {
+ /* missing field */
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "digest: missing field");
+ return -1;
+ }
+
+ m = get_http_method_name(con->request.http_method);
+
+ /* password-string == HA1 */
+ password = buffer_init();
+ username_buf = buffer_init_string(username);
+ realm_buf = buffer_init_string(realm);
+ if (http_auth_get_password(srv, p, username_buf, realm_buf, password)) {
+ buffer_free(password);
+ buffer_free(b);
+ buffer_free(username_buf);
+ buffer_free(realm_buf);
+ return 0;
+ }
+
+ buffer_free(username_buf);
+ buffer_free(realm_buf);
+
+ if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
+ /* generate password from plain-text */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)username, strlen(username));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)realm, strlen(realm));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)password->ptr, password->used - 1);
+ MD5_Final(HA1, &Md5Ctx);
+ } else if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
+ /* HA1 */
+ /* transform the 32-byte-hex-md5 to a 16-byte-md5 */
+ for (i = 0; i < HASHLEN; i++) {
+ HA1[i] = hex2int(password->ptr[i*2]) << 4;
+ HA1[i] |= hex2int(password->ptr[i*2+1]);
+ }
+ } else {
+ /* we already check that above */
+ SEGFAULT();
+ }
+
+ buffer_free(password);
+
+ if (algorithm &&
+ strcasecmp(algorithm, "md5-sess") == 0) {
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)HA1, 16);
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
+ MD5_Final(HA1, &Md5Ctx);
+ }
+
+ CvtHex(HA1, a1);
+
+ /* calculate H(A2) */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)m, strlen(m));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)uri, strlen(uri));
+ if (qop && strcasecmp(qop, "auth-int") == 0) {
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)"", HASHHEXLEN);
+ }
+ MD5_Final(HA2, &Md5Ctx);
+ CvtHex(HA2, HA2Hex);
+
+ /* calculate response */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ if (qop && *qop) {
+ MD5_Update(&Md5Ctx, (unsigned char *)nc, strlen(nc));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)qop, strlen(qop));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ };
+ MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN);
+ MD5_Final(RespHash, &Md5Ctx);
+ CvtHex(RespHash, a2);
+
+ if (0 != strcmp(a2, respons)) {
+ /* digest not ok */
+
+ if (p->conf.auth_debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "digest: digest mismatch", a2, respons);
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "digest: auth failed for", username, "wrong password");
+
+ buffer_free(b);
+ return 0;
+ }
+
+ /* value is our allow-rules */
+ if (http_auth_match_rules(srv, p, url->ptr, username, NULL, NULL)) {
+ buffer_free(b);
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "digest: rules did match");
+
+ return 0;
+ }
+
+ /* remember the username */
+ buffer_copy_string(p->auth_user, username);
+
+ buffer_free(b);
+
+ if (p->conf.auth_debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "digest: auth ok");
+ }
+ return 1;
+}
+
+
+int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char out[33]) {
+ HASH h;
+ MD5_CTX Md5Ctx;
+ char hh[32];
+
+ UNUSED(p);
+
+ /* generate shared-secret */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)fn->ptr, fn->used - 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)"+", 1);
+
+ /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
+ ltostr(hh, srv->cur_ts);
+ MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
+ ltostr(hh, rand());
+ MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
+
+ MD5_Final(h, &Md5Ctx);
+
+ CvtHex(h, out);
+
+ return 0;
+}
diff --git a/src/http_auth.h b/src/http_auth.h
new file mode 100644
index 0000000..0b664fa
--- /dev/null
+++ b/src/http_auth.h
@@ -0,0 +1,68 @@
+#ifndef _HTTP_AUTH_H_
+#define _HTTP_AUTH_H_
+
+#include "server.h"
+#include "plugin.h"
+
+#if defined(HAVE_LDAP_H) && defined(HAVE_LBER_H) && defined(HAVE_LIBLDAP) && defined(HAVE_LIBLBER)
+# define USE_LDAP
+# include <ldap.h>
+#endif
+
+typedef enum { AUTH_BACKEND_UNSET, AUTH_BACKEND_PLAIN,
+ AUTH_BACKEND_LDAP, AUTH_BACKEND_HTPASSWD,
+ AUTH_BACKEND_HTDIGEST, AUTH_BACKEND_PAM } auth_backend_t;
+
+typedef struct {
+ /* auth */
+ array *auth_require;
+
+ buffer *auth_plain_groupfile;
+ buffer *auth_plain_userfile;
+
+ buffer *auth_htdigest_userfile;
+ buffer *auth_htpasswd_userfile;
+
+ buffer *auth_backend_conf;
+
+ buffer *auth_ldap_hostname;
+ buffer *auth_ldap_basedn;
+ buffer *auth_ldap_binddn;
+ buffer *auth_ldap_bindpw;
+ buffer *auth_ldap_filter;
+ buffer *auth_ldap_cafile;
+ unsigned short auth_ldap_starttls;
+
+ unsigned short auth_debug;
+
+ /* generated */
+ auth_backend_t auth_backend;
+
+#ifdef USE_LDAP
+ LDAP *ldap;
+
+ buffer *ldap_filter_pre;
+ buffer *ldap_filter_post;
+#endif
+} mod_auth_plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer *tmp_buf;
+
+ buffer *auth_user;
+
+#ifdef USE_LDAP
+ buffer *ldap_filter;
+#endif
+
+ mod_auth_plugin_config **config_storage;
+
+ mod_auth_plugin_config conf; /* this is only used as long as no handler_ctx is setup */
+} mod_auth_plugin_data;
+
+int http_auth_basic_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str);
+int http_auth_digest_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str);
+int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char hh[33]);
+
+#endif
diff --git a/src/http_auth_digest.c b/src/http_auth_digest.c
new file mode 100644
index 0000000..e440430
--- /dev/null
+++ b/src/http_auth_digest.c
@@ -0,0 +1,19 @@
+#include <string.h>
+#include "http_auth_digest.h"
+
+#include "buffer.h"
+
+#ifndef USE_OPENSSL
+# include "md5.h"
+#endif
+
+void CvtHex(IN HASH Bin, OUT HASHHEX Hex) {
+ unsigned short i;
+
+ for (i = 0; i < HASHLEN; i++) {
+ Hex[i*2] = int2hex((Bin[i] >> 4) & 0xf);
+ Hex[i*2+1] = int2hex(Bin[i] & 0xf);
+ }
+ Hex[HASHHEXLEN] = '\0';
+}
+
diff --git a/src/http_auth_digest.h b/src/http_auth_digest.h
new file mode 100644
index 0000000..8bffce4
--- /dev/null
+++ b/src/http_auth_digest.h
@@ -0,0 +1,24 @@
+#ifndef _DIGCALC_H_
+#define _DIGCALC_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define HASHLEN 16
+typedef unsigned char HASH[HASHLEN];
+#define HASHHEXLEN 32
+typedef char HASHHEX[HASHHEXLEN+1];
+#ifdef USE_OPENSSL
+#define IN const
+#else
+#define IN
+#endif
+#define OUT
+
+void CvtHex(
+ IN HASH Bin,
+ OUT HASHHEX Hex
+ );
+
+#endif
diff --git a/src/http_chunk.c b/src/http_chunk.c
new file mode 100644
index 0000000..c128bf1
--- /dev/null
+++ b/src/http_chunk.c
@@ -0,0 +1,133 @@
+/**
+ * the HTTP chunk-API
+ *
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include "server.h"
+#include "chunk.h"
+#include "http_chunk.h"
+#include "log.h"
+
+static int http_chunk_append_len(server *srv, connection *con, size_t len) {
+ size_t i, olen = len, j;
+ buffer *b;
+
+ b = srv->tmp_chunk_len;
+
+ if (len == 0) {
+ buffer_copy_string(b, "0");
+ } else {
+ for (i = 0; i < 8 && len; i++) {
+ len >>= 4;
+ }
+
+ /* i is the number of hex digits we have */
+ buffer_prepare_copy(b, i + 1);
+
+ for (j = i-1, len = olen; j+1 > 0; j--) {
+ b->ptr[j] = (len & 0xf) + (((len & 0xf) <= 9) ? '0' : 'a' - 10);
+ len >>= 4;
+ }
+ b->used = i;
+ b->ptr[b->used++] = '\0';
+ }
+
+ buffer_append_string(b, "\r\n");
+ chunkqueue_append_buffer(con->write_queue, b);
+
+ return 0;
+}
+
+
+int http_chunk_append_file(server *srv, connection *con, buffer *fn, off_t offset, off_t len) {
+ chunkqueue *cq;
+
+ if (!con) return -1;
+
+ cq = con->write_queue;
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ http_chunk_append_len(srv, con, len);
+ }
+
+ chunkqueue_append_file(cq, fn, offset, len);
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED && len > 0) {
+ chunkqueue_append_mem(cq, "\r\n", 2 + 1);
+ }
+
+ return 0;
+}
+
+int http_chunk_append_buffer(server *srv, connection *con, buffer *mem) {
+ chunkqueue *cq;
+
+ if (!con) return -1;
+
+ cq = con->write_queue;
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ http_chunk_append_len(srv, con, mem->used - 1);
+ }
+
+ chunkqueue_append_buffer(cq, mem);
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED && mem->used > 0) {
+ chunkqueue_append_mem(cq, "\r\n", 2 + 1);
+ }
+
+ return 0;
+}
+
+int http_chunk_append_mem(server *srv, connection *con, const char * mem, size_t len) {
+ chunkqueue *cq;
+
+ if (!con) return -1;
+
+ cq = con->write_queue;
+
+ if (len == 0) {
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ http_chunk_append_len(srv, con, 0);
+ chunkqueue_append_mem(cq, "\r\n", 2 + 1);
+ } else {
+ chunkqueue_append_mem(cq, "", 1);
+ }
+ return 0;
+ }
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ http_chunk_append_len(srv, con, len - 1);
+ }
+
+ chunkqueue_append_mem(cq, mem, len);
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ chunkqueue_append_mem(cq, "\r\n", 2 + 1);
+ }
+
+ return 0;
+}
+
+
+off_t http_chunkqueue_length(server *srv, connection *con) {
+ if (!con) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "connection is NULL!!");
+
+ return 0;
+ }
+
+ return chunkqueue_length(con->write_queue);
+}
diff --git a/src/http_chunk.h b/src/http_chunk.h
new file mode 100644
index 0000000..4ba24a2
--- /dev/null
+++ b/src/http_chunk.h
@@ -0,0 +1,12 @@
+#ifndef _HTTP_CHUNK_H_
+#define _HTTP_CHUNK_H_
+
+#include "server.h"
+#include <sys/types.h>
+
+int http_chunk_append_mem(server *srv, connection *con, const char * mem, size_t len);
+int http_chunk_append_buffer(server *srv, connection *con, buffer *mem);
+int http_chunk_append_file(server *srv, connection *con, buffer *fn, off_t offset, off_t len);
+off_t http_chunkqueue_length(server *srv, connection *con);
+
+#endif
diff --git a/src/inet_ntop_cache.c b/src/inet_ntop_cache.c
new file mode 100644
index 0000000..c0b3aa1
--- /dev/null
+++ b/src/inet_ntop_cache.c
@@ -0,0 +1,53 @@
+#include <sys/types.h>
+
+#include <string.h>
+
+
+#include "base.h"
+#include "inet_ntop_cache.h"
+#include "sys-socket.h"
+
+const char * inet_ntop_cache_get_ip(server *srv, sock_addr *addr) {
+#ifdef HAVE_IPV6
+ size_t ndx = 0, i;
+ for (i = 0; i < INET_NTOP_CACHE_MAX; i++) {
+ if (srv->inet_ntop_cache[i].ts != 0) {
+ if (srv->inet_ntop_cache[i].family == AF_INET6 &&
+ 0 == memcmp(srv->inet_ntop_cache[i].addr.ipv6.s6_addr, addr->ipv6.sin6_addr.s6_addr, 16)) {
+ /* IPv6 found in cache */
+ break;
+ } else if (srv->inet_ntop_cache[i].family == AF_INET &&
+ srv->inet_ntop_cache[i].addr.ipv4.s_addr == addr->ipv4.sin_addr.s_addr) {
+ /* IPv4 found in cache */
+ break;
+
+ }
+ }
+ }
+
+ if (i == INET_NTOP_CACHE_MAX) {
+ /* not found in cache */
+
+ i = ndx;
+ inet_ntop(addr->plain.sa_family,
+ addr->plain.sa_family == AF_INET6 ?
+ (const void *) &(addr->ipv6.sin6_addr) :
+ (const void *) &(addr->ipv4.sin_addr),
+ srv->inet_ntop_cache[i].b2, INET6_ADDRSTRLEN);
+
+ srv->inet_ntop_cache[i].ts = srv->cur_ts;
+ srv->inet_ntop_cache[i].family = addr->plain.sa_family;
+
+ if (srv->inet_ntop_cache[i].family == AF_INET) {
+ srv->inet_ntop_cache[i].addr.ipv4.s_addr = addr->ipv4.sin_addr.s_addr;
+ } else if (srv->inet_ntop_cache[i].family == AF_INET6) {
+ memcpy(srv->inet_ntop_cache[i].addr.ipv6.s6_addr, addr->ipv6.sin6_addr.s6_addr, 16);
+ }
+ }
+
+ return srv->inet_ntop_cache[i].b2;
+#else
+ UNUSED(srv);
+ return inet_ntoa(addr->ipv4.sin_addr);
+#endif
+}
diff --git a/src/inet_ntop_cache.h b/src/inet_ntop_cache.h
new file mode 100644
index 0000000..fd3c281
--- /dev/null
+++ b/src/inet_ntop_cache.h
@@ -0,0 +1,7 @@
+#ifndef _INET_NTOP_CACHE_H_
+#define _INET_NTOP_CACHE_H_
+
+#include "base.h"
+const char * inet_ntop_cache_get_ip(server *srv, sock_addr *addr);
+
+#endif
diff --git a/src/joblist.c b/src/joblist.c
new file mode 100644
index 0000000..dcab955
--- /dev/null
+++ b/src/joblist.c
@@ -0,0 +1,63 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "joblist.h"
+#include "log.h"
+
+int joblist_append(server *srv, connection *con) {
+ if (con->in_joblist) return 0;
+
+ if (srv->joblist->size == 0) {
+ srv->joblist->size = 16;
+ srv->joblist->ptr = malloc(sizeof(*srv->joblist->ptr) * srv->joblist->size);
+ } else if (srv->joblist->used == srv->joblist->size) {
+ srv->joblist->size += 16;
+ srv->joblist->ptr = realloc(srv->joblist->ptr, sizeof(*srv->joblist->ptr) * srv->joblist->size);
+ }
+
+ srv->joblist->ptr[srv->joblist->used++] = con;
+
+ return 0;
+}
+
+void joblist_free(server *srv, connections *joblist) {
+ UNUSED(srv);
+
+ free(joblist->ptr);
+ free(joblist);
+}
+
+connection *fdwaitqueue_unshift(server *srv, connections *fdwaitqueue) {
+ connection *con;
+ UNUSED(srv);
+
+
+ if (fdwaitqueue->used == 0) return NULL;
+
+ con = fdwaitqueue->ptr[0];
+
+ memmove(fdwaitqueue->ptr, &(fdwaitqueue->ptr[1]), --fdwaitqueue->used * sizeof(*(fdwaitqueue->ptr)));
+
+ return con;
+}
+
+int fdwaitqueue_append(server *srv, connection *con) {
+ if (srv->fdwaitqueue->size == 0) {
+ srv->fdwaitqueue->size = 16;
+ srv->fdwaitqueue->ptr = malloc(sizeof(*(srv->fdwaitqueue->ptr)) * srv->fdwaitqueue->size);
+ } else if (srv->fdwaitqueue->used == srv->fdwaitqueue->size) {
+ srv->fdwaitqueue->size += 16;
+ srv->fdwaitqueue->ptr = realloc(srv->fdwaitqueue->ptr, sizeof(*(srv->fdwaitqueue->ptr)) * srv->fdwaitqueue->size);
+ }
+
+ srv->fdwaitqueue->ptr[srv->fdwaitqueue->used++] = con;
+
+ return 0;
+}
+
+void fdwaitqueue_free(server *srv, connections *fdwaitqueue) {
+ UNUSED(srv);
+ free(fdwaitqueue->ptr);
+ free(fdwaitqueue);
+}
diff --git a/src/joblist.h b/src/joblist.h
new file mode 100644
index 0000000..3701e09
--- /dev/null
+++ b/src/joblist.h
@@ -0,0 +1,13 @@
+#ifndef _JOB_LIST_H_
+#define _JOB_LIST_H_
+
+#include "base.h"
+
+int joblist_append(server *srv, connection *con);
+void joblist_free(server *srv, connections *joblist);
+
+int fdwaitqueue_append(server *srv, connection *con);
+void fdwaitqueue_free(server *srv, connections *fdwaitqueue);
+connection *fdwaitqueue_unshift(server *srv, connections *fdwaitqueue);
+
+#endif
diff --git a/src/keyvalue.c b/src/keyvalue.c
new file mode 100644
index 0000000..b26588f
--- /dev/null
+++ b/src/keyvalue.c
@@ -0,0 +1,384 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "server.h"
+#include "keyvalue.h"
+
+static keyvalue http_versions[] = {
+ { HTTP_VERSION_1_1, "HTTP/1.1" },
+ { HTTP_VERSION_1_0, "HTTP/1.0" },
+ { HTTP_VERSION_UNSET, NULL }
+};
+
+static keyvalue http_methods[] = {
+ { HTTP_METHOD_GET, "GET" },
+ { HTTP_METHOD_POST, "POST" },
+ { HTTP_METHOD_HEAD, "HEAD" },
+ { HTTP_METHOD_PROPFIND, "PROPFIND" },
+ { HTTP_METHOD_PROPPATCH, "PROPPATCH" },
+ { HTTP_METHOD_REPORT, "REPORT" },
+ { HTTP_METHOD_OPTIONS, "OPTIONS" },
+ { HTTP_METHOD_MKCOL, "MKCOL" },
+ { HTTP_METHOD_PUT, "PUT" },
+ { HTTP_METHOD_DELETE, "DELETE" },
+ { HTTP_METHOD_COPY, "COPY" },
+ { HTTP_METHOD_MOVE, "MOVE" },
+ { HTTP_METHOD_LABEL, "LABEL" },
+ { HTTP_METHOD_CHECKOUT, "CHECKOUT" },
+ { HTTP_METHOD_CHECKIN, "CHECKIN" },
+ { HTTP_METHOD_UNCHECKOUT, "UNCHECKOUT" },
+ { HTTP_METHOD_VERSION_CONTROL, "VERSION-CONTROL" },
+ { HTTP_METHOD_CONNECT, "CONNECT" },
+
+ { HTTP_METHOD_UNSET, NULL }
+};
+
+static keyvalue http_status[] = {
+ { 100, "Continue" },
+ { 101, "Switching Protocols" },
+ { 102, "Processing" }, /* WebDAV */
+ { 200, "OK" },
+ { 201, "Created" },
+ { 202, "Accepted" },
+ { 203, "Non-Authoritative Information" },
+ { 204, "No Content" },
+ { 205, "Reset Content" },
+ { 206, "Partial Content" },
+ { 207, "Multi-status" }, /* WebDAV */
+ { 300, "Multiple Choices" },
+ { 301, "Moved Permanently" },
+ { 302, "Found" },
+ { 303, "See Other" },
+ { 304, "Not Modified" },
+ { 305, "Use Proxy" },
+ { 306, "(Unused)" },
+ { 307, "Temporary Redirect" },
+ { 400, "Bad Request" },
+ { 401, "Unauthorized" },
+ { 402, "Payment Required" },
+ { 403, "Forbidden" },
+ { 404, "Not Found" },
+ { 405, "Method Not Allowed" },
+ { 406, "Not Acceptable" },
+ { 407, "Proxy Authentication Required" },
+ { 408, "Request Timeout" },
+ { 409, "Conflict" },
+ { 410, "Gone" },
+ { 411, "Length Required" },
+ { 412, "Precondition Failed" },
+ { 413, "Request Entity Too Large" },
+ { 414, "Request-URI Too Long" },
+ { 415, "Unsupported Media Type" },
+ { 416, "Requested Range Not Satisfiable" },
+ { 417, "Expectation Failed" },
+ { 422, "Unprocessable Entity" }, /* WebDAV */
+ { 423, "Locked" }, /* WebDAV */
+ { 424, "Failed Dependency" }, /* WebDAV */
+ { 426, "Upgrade Required" }, /* TLS */
+ { 500, "Internal Server Error" },
+ { 501, "Not Implemented" },
+ { 502, "Bad Gateway" },
+ { 503, "Service Not Available" },
+ { 504, "Gateway Timeout" },
+ { 505, "HTTP Version Not Supported" },
+ { 507, "Insufficient Storage" }, /* WebDAV */
+
+ { -1, NULL }
+};
+
+static keyvalue http_status_body[] = {
+ { 400, "400.html" },
+ { 401, "401.html" },
+ { 403, "403.html" },
+ { 404, "404.html" },
+ { 411, "411.html" },
+ { 416, "416.html" },
+ { 500, "500.html" },
+ { 501, "501.html" },
+ { 503, "503.html" },
+ { 505, "505.html" },
+
+ { -1, NULL }
+};
+
+
+const char *keyvalue_get_value(keyvalue *kv, int k) {
+ int i;
+ for (i = 0; kv[i].value; i++) {
+ if (kv[i].key == k) return kv[i].value;
+ }
+ return NULL;
+}
+
+int keyvalue_get_key(keyvalue *kv, const char *s) {
+ int i;
+ for (i = 0; kv[i].value; i++) {
+ if (0 == strcmp(kv[i].value, s)) return kv[i].key;
+ }
+ return -1;
+}
+
+keyvalue_buffer *keyvalue_buffer_init(void) {
+ keyvalue_buffer *kvb;
+
+ kvb = calloc(1, sizeof(*kvb));
+
+ return kvb;
+}
+
+int keyvalue_buffer_append(keyvalue_buffer *kvb, int key, const char *value) {
+ size_t i;
+ if (kvb->size == 0) {
+ kvb->size = 4;
+
+ kvb->kv = malloc(kvb->size * sizeof(*kvb->kv));
+
+ for(i = 0; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ } else if (kvb->used == kvb->size) {
+ kvb->size += 4;
+
+ kvb->kv = realloc(kvb->kv, kvb->size * sizeof(*kvb->kv));
+
+ for(i = kvb->used; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ }
+
+ kvb->kv[kvb->used]->key = key;
+ kvb->kv[kvb->used]->value = strdup(value);
+
+ kvb->used++;
+
+ return 0;
+}
+
+void keyvalue_buffer_free(keyvalue_buffer *kvb) {
+ size_t i;
+
+ for (i = 0; i < kvb->size; i++) {
+ if (kvb->kv[i]->value) free(kvb->kv[i]->value);
+ free(kvb->kv[i]);
+ }
+
+ if (kvb->kv) free(kvb->kv);
+
+ free(kvb);
+}
+
+
+s_keyvalue_buffer *s_keyvalue_buffer_init(void) {
+ s_keyvalue_buffer *kvb;
+
+ kvb = calloc(1, sizeof(*kvb));
+
+ return kvb;
+}
+
+int s_keyvalue_buffer_append(s_keyvalue_buffer *kvb, const char *key, const char *value) {
+ size_t i;
+ if (kvb->size == 0) {
+ kvb->size = 4;
+ kvb->used = 0;
+
+ kvb->kv = malloc(kvb->size * sizeof(*kvb->kv));
+
+ for(i = 0; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ } else if (kvb->used == kvb->size) {
+ kvb->size += 4;
+
+ kvb->kv = realloc(kvb->kv, kvb->size * sizeof(*kvb->kv));
+
+ for(i = kvb->used; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ }
+
+ kvb->kv[kvb->used]->key = key ? strdup(key) : NULL;
+ kvb->kv[kvb->used]->value = strdup(value);
+
+ kvb->used++;
+
+ return 0;
+}
+
+void s_keyvalue_buffer_free(s_keyvalue_buffer *kvb) {
+ size_t i;
+
+ for (i = 0; i < kvb->size; i++) {
+ if (kvb->kv[i]->key) free(kvb->kv[i]->key);
+ if (kvb->kv[i]->value) free(kvb->kv[i]->value);
+ free(kvb->kv[i]);
+ }
+
+ if (kvb->kv) free(kvb->kv);
+
+ free(kvb);
+}
+
+
+httpauth_keyvalue_buffer *httpauth_keyvalue_buffer_init(void) {
+ httpauth_keyvalue_buffer *kvb;
+
+ kvb = calloc(1, sizeof(*kvb));
+
+ return kvb;
+}
+
+int httpauth_keyvalue_buffer_append(httpauth_keyvalue_buffer *kvb, const char *key, const char *realm, httpauth_type type) {
+ size_t i;
+ if (kvb->size == 0) {
+ kvb->size = 4;
+
+ kvb->kv = malloc(kvb->size * sizeof(*kvb->kv));
+
+ for(i = 0; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ } else if (kvb->used == kvb->size) {
+ kvb->size += 4;
+
+ kvb->kv = realloc(kvb->kv, kvb->size * sizeof(*kvb->kv));
+
+ for(i = kvb->used; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ }
+
+ kvb->kv[kvb->used]->key = strdup(key);
+ kvb->kv[kvb->used]->realm = strdup(realm);
+ kvb->kv[kvb->used]->type = type;
+
+ kvb->used++;
+
+ return 0;
+}
+
+void httpauth_keyvalue_buffer_free(httpauth_keyvalue_buffer *kvb) {
+ size_t i;
+
+ for (i = 0; i < kvb->size; i++) {
+ if (kvb->kv[i]->key) free(kvb->kv[i]->key);
+ if (kvb->kv[i]->realm) free(kvb->kv[i]->realm);
+ free(kvb->kv[i]);
+ }
+
+ if (kvb->kv) free(kvb->kv);
+
+ free(kvb);
+}
+
+
+const char *get_http_version_name(int i) {
+ return keyvalue_get_value(http_versions, i);
+}
+
+const char *get_http_status_name(int i) {
+ return keyvalue_get_value(http_status, i);
+}
+
+const char *get_http_method_name(http_method_t i) {
+ return keyvalue_get_value(http_methods, i);
+}
+
+const char *get_http_status_body_name(int i) {
+ return keyvalue_get_value(http_status_body, i);
+}
+
+int get_http_version_key(const char *s) {
+ return keyvalue_get_key(http_versions, s);
+}
+
+http_method_t get_http_method_key(const char *s) {
+ return (http_method_t)keyvalue_get_key(http_methods, s);
+}
+
+
+
+
+pcre_keyvalue_buffer *pcre_keyvalue_buffer_init(void) {
+ pcre_keyvalue_buffer *kvb;
+
+ kvb = calloc(1, sizeof(*kvb));
+
+ return kvb;
+}
+
+int pcre_keyvalue_buffer_append(pcre_keyvalue_buffer *kvb, const char *key, const char *value) {
+#ifdef HAVE_PCRE_H
+ size_t i;
+ const char *errptr;
+ int erroff;
+ pcre_keyvalue *kv;
+#endif
+
+ if (!key) return -1;
+
+#ifdef HAVE_PCRE_H
+ if (kvb->size == 0) {
+ kvb->size = 4;
+ kvb->used = 0;
+
+ kvb->kv = malloc(kvb->size * sizeof(*kvb->kv));
+
+ for(i = 0; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ } else if (kvb->used == kvb->size) {
+ kvb->size += 4;
+
+ kvb->kv = realloc(kvb->kv, kvb->size * sizeof(*kvb->kv));
+
+ for(i = kvb->used; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ }
+
+ kv = kvb->kv[kvb->used];
+ if (NULL == (kv->key = pcre_compile(key,
+ 0, &errptr, &erroff, NULL))) {
+
+ fprintf(stderr, "%s.%d: rexexp compilation error at %s\n", __FILE__, __LINE__, errptr);
+ return -1;
+ }
+
+ if (NULL == (kv->key_extra = pcre_study(kv->key, 0, &errptr)) &&
+ errptr != NULL) {
+ return -1;
+ }
+
+ kv->value = buffer_init_string(value);
+
+ kvb->used++;
+
+ return 0;
+#else
+ UNUSED(kvb);
+ UNUSED(value);
+
+ return -1;
+#endif
+}
+
+void pcre_keyvalue_buffer_free(pcre_keyvalue_buffer *kvb) {
+#ifdef HAVE_PCRE_H
+ size_t i;
+ pcre_keyvalue *kv;
+
+ for (i = 0; i < kvb->size; i++) {
+ kv = kvb->kv[i];
+ if (kv->key) pcre_free(kv->key);
+ if (kv->key_extra) pcre_free(kv->key_extra);
+ if (kv->value) buffer_free(kv->value);
+ free(kv);
+ }
+
+ if (kvb->kv) free(kvb->kv);
+#endif
+
+ free(kvb);
+}
diff --git a/src/keyvalue.h b/src/keyvalue.h
new file mode 100644
index 0000000..e1c940f
--- /dev/null
+++ b/src/keyvalue.h
@@ -0,0 +1,104 @@
+#ifndef _KEY_VALUE_H_
+#define _KEY_VALUE_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_PCRE_H
+# include <pcre.h>
+#endif
+
+typedef enum {
+ HTTP_METHOD_UNSET = -1,
+ HTTP_METHOD_GET,
+ HTTP_METHOD_POST,
+ HTTP_METHOD_HEAD,
+ HTTP_METHOD_OPTIONS,
+ HTTP_METHOD_PROPFIND, /* WebDAV */
+ HTTP_METHOD_MKCOL,
+ HTTP_METHOD_PUT,
+ HTTP_METHOD_DELETE,
+ HTTP_METHOD_COPY,
+ HTTP_METHOD_MOVE,
+ HTTP_METHOD_PROPPATCH,
+ HTTP_METHOD_REPORT, /* DeltaV */
+ HTTP_METHOD_CHECKOUT,
+ HTTP_METHOD_CHECKIN,
+ HTTP_METHOD_VERSION_CONTROL,
+ HTTP_METHOD_UNCHECKOUT,
+ HTTP_METHOD_LABEL,
+ HTTP_METHOD_CONNECT
+} http_method_t;
+
+typedef enum { HTTP_VERSION_UNSET = -1, HTTP_VERSION_1_0, HTTP_VERSION_1_1 } http_version_t;
+
+typedef struct {
+ int key;
+
+ char *value;
+} keyvalue;
+
+typedef struct {
+ char *key;
+
+ char *value;
+} s_keyvalue;
+
+typedef struct {
+#ifdef HAVE_PCRE_H
+ pcre *key;
+ pcre_extra *key_extra;
+#endif
+
+ buffer *value;
+} pcre_keyvalue;
+
+typedef enum { HTTP_AUTH_BASIC, HTTP_AUTH_DIGEST } httpauth_type;
+
+typedef struct {
+ char *key;
+
+ char *realm;
+ httpauth_type type;
+} httpauth_keyvalue;
+
+#define KVB(x) \
+typedef struct {\
+ x **kv; \
+ size_t used;\
+ size_t size;\
+} x ## _buffer
+
+KVB(keyvalue);
+KVB(s_keyvalue);
+KVB(httpauth_keyvalue);
+KVB(pcre_keyvalue);
+
+const char *get_http_status_name(int i);
+const char *get_http_version_name(int i);
+const char *get_http_method_name(http_method_t i);
+const char *get_http_status_body_name(int i);
+int get_http_version_key(const char *s);
+http_method_t get_http_method_key(const char *s);
+
+const char *keyvalue_get_value(keyvalue *kv, int k);
+int keyvalue_get_key(keyvalue *kv, const char *s);
+
+keyvalue_buffer *keyvalue_buffer_init(void);
+int keyvalue_buffer_append(keyvalue_buffer *kvb, int k, const char *value);
+void keyvalue_buffer_free(keyvalue_buffer *kvb);
+
+s_keyvalue_buffer *s_keyvalue_buffer_init(void);
+int s_keyvalue_buffer_append(s_keyvalue_buffer *kvb, const char *key, const char *value);
+void s_keyvalue_buffer_free(s_keyvalue_buffer *kvb);
+
+httpauth_keyvalue_buffer *httpauth_keyvalue_buffer_init(void);
+int httpauth_keyvalue_buffer_append(httpauth_keyvalue_buffer *kvb, const char *key, const char *realm, httpauth_type type);
+void httpauth_keyvalue_buffer_free(httpauth_keyvalue_buffer *kvb);
+
+pcre_keyvalue_buffer *pcre_keyvalue_buffer_init(void);
+int pcre_keyvalue_buffer_append(pcre_keyvalue_buffer *kvb, const char *key, const char *value);
+void pcre_keyvalue_buffer_free(pcre_keyvalue_buffer *kvb);
+
+#endif
diff --git a/src/lemon.c b/src/lemon.c
new file mode 100644
index 0000000..dd87cdf
--- /dev/null
+++ b/src/lemon.c
@@ -0,0 +1,4396 @@
+/*
+** This file contains all sources (including headers) to the LEMON
+** LALR(1) parser generator. The sources have been combined into a
+** single file to make it easy to include LEMON in the source tree
+** and Makefile of another program.
+**
+** The author of this program disclaims copyright.
+*/
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+extern void qsort();
+extern double strtod();
+extern long strtol();
+extern void free();
+extern int access();
+extern int atoi();
+
+#ifndef __WIN32__
+# if defined(_WIN32) || defined(WIN32)
+# define __WIN32__
+# endif
+#endif
+
+/* #define PRIVATE static */
+#define PRIVATE
+
+#ifdef TEST
+#define MAXRHS 5 /* Set low to exercise exception code */
+#else
+#define MAXRHS 1000
+#endif
+
+char *msort();
+extern void *malloc();
+
+/******** From the file "action.h" *************************************/
+struct action *Action_new();
+struct action *Action_sort();
+void Action_add();
+
+/********* From the file "assert.h" ************************************/
+void myassert();
+#ifndef NDEBUG
+# define assert(X) if(!(X))myassert(__FILE__,__LINE__)
+#else
+# define assert(X)
+#endif
+
+/********** From the file "build.h" ************************************/
+void FindRulePrecedences();
+void FindFirstSets();
+void FindStates();
+void FindLinks();
+void FindFollowSets();
+void FindActions();
+
+/********* From the file "configlist.h" *********************************/
+void Configlist_init(/* void */);
+struct config *Configlist_add(/* struct rule *, int */);
+struct config *Configlist_addbasis(/* struct rule *, int */);
+void Configlist_closure(/* void */);
+void Configlist_sort(/* void */);
+void Configlist_sortbasis(/* void */);
+struct config *Configlist_return(/* void */);
+struct config *Configlist_basis(/* void */);
+void Configlist_eat(/* struct config * */);
+void Configlist_reset(/* void */);
+
+/********* From the file "error.h" ***************************************/
+void ErrorMsg(const char *, int,const char *, ...);
+
+/****** From the file "option.h" ******************************************/
+struct s_options {
+ enum { OPT_FLAG=1, OPT_INT, OPT_DBL, OPT_STR,
+ OPT_FFLAG, OPT_FINT, OPT_FDBL, OPT_FSTR} type;
+ char *label;
+ char *arg;
+ char *message;
+};
+int OptInit(/* char**,struct s_options*,FILE* */);
+int OptNArgs(/* void */);
+char *OptArg(/* int */);
+void OptErr(/* int */);
+void OptPrint(/* void */);
+
+/******** From the file "parse.h" *****************************************/
+void Parse(/* struct lemon *lemp */);
+
+/********* From the file "plink.h" ***************************************/
+struct plink *Plink_new(/* void */);
+void Plink_add(/* struct plink **, struct config * */);
+void Plink_copy(/* struct plink **, struct plink * */);
+void Plink_delete(/* struct plink * */);
+
+/********** From the file "report.h" *************************************/
+void Reprint(/* struct lemon * */);
+void ReportOutput(/* struct lemon * */);
+void ReportTable(/* struct lemon * */);
+void ReportHeader(/* struct lemon * */);
+void CompressTables(/* struct lemon * */);
+
+/********** From the file "set.h" ****************************************/
+void SetSize(/* int N */); /* All sets will be of size N */
+char *SetNew(/* void */); /* A new set for element 0..N */
+void SetFree(/* char* */); /* Deallocate a set */
+
+int SetAdd(/* char*,int */); /* Add element to a set */
+int SetUnion(/* char *A,char *B */); /* A <- A U B, thru element N */
+
+#define SetFind(X,Y) (X[Y]) /* True if Y is in set X */
+
+/********** From the file "struct.h" *************************************/
+/*
+** Principal data structures for the LEMON parser generator.
+*/
+
+typedef enum {Bo_FALSE=0, Bo_TRUE} Boolean;
+
+/* Symbols (terminals and nonterminals) of the grammar are stored
+** in the following: */
+struct symbol {
+ char *name; /* Name of the symbol */
+ int index; /* Index number for this symbol */
+ enum {
+ TERMINAL,
+ NONTERMINAL
+ } type; /* Symbols are all either TERMINALS or NTs */
+ struct rule *rule; /* Linked list of rules of this (if an NT) */
+ struct symbol *fallback; /* fallback token in case this token doesn't parse */
+ int prec; /* Precedence if defined (-1 otherwise) */
+ enum e_assoc {
+ LEFT,
+ RIGHT,
+ NONE,
+ UNK
+ } assoc; /* Associativity if predecence is defined */
+ char *firstset; /* First-set for all rules of this symbol */
+ Boolean lambda; /* True if NT and can generate an empty string */
+ char *destructor; /* Code which executes whenever this symbol is
+ ** popped from the stack during error processing */
+ int destructorln; /* Line number of destructor code */
+ char *datatype; /* The data type of information held by this
+ ** object. Only used if type==NONTERMINAL */
+ int dtnum; /* The data type number. In the parser, the value
+ ** stack is a union. The .yy%d element of this
+ ** union is the correct data type for this object */
+};
+
+/* Each production rule in the grammar is stored in the following
+** structure. */
+struct rule {
+ struct symbol *lhs; /* Left-hand side of the rule */
+ char *lhsalias; /* Alias for the LHS (NULL if none) */
+ int ruleline; /* Line number for the rule */
+ int nrhs; /* Number of RHS symbols */
+ struct symbol **rhs; /* The RHS symbols */
+ char **rhsalias; /* An alias for each RHS symbol (NULL if none) */
+ int line; /* Line number at which code begins */
+ char *code; /* The code executed when this rule is reduced */
+ struct symbol *precsym; /* Precedence symbol for this rule */
+ int index; /* An index number for this rule */
+ Boolean canReduce; /* True if this rule is ever reduced */
+ struct rule *nextlhs; /* Next rule with the same LHS */
+ struct rule *next; /* Next rule in the global list */
+};
+
+/* A configuration is a production rule of the grammar together with
+** a mark (dot) showing how much of that rule has been processed so far.
+** Configurations also contain a follow-set which is a list of terminal
+** symbols which are allowed to immediately follow the end of the rule.
+** Every configuration is recorded as an instance of the following: */
+struct config {
+ struct rule *rp; /* The rule upon which the configuration is based */
+ int dot; /* The parse point */
+ char *fws; /* Follow-set for this configuration only */
+ struct plink *fplp; /* Follow-set forward propagation links */
+ struct plink *bplp; /* Follow-set backwards propagation links */
+ struct state *stp; /* Pointer to state which contains this */
+ enum {
+ COMPLETE, /* The status is used during followset and */
+ INCOMPLETE /* shift computations */
+ } status;
+ struct config *next; /* Next configuration in the state */
+ struct config *bp; /* The next basis configuration */
+};
+
+/* Every shift or reduce operation is stored as one of the following */
+struct action {
+ struct symbol *sp; /* The look-ahead symbol */
+ enum e_action {
+ SHIFT,
+ ACCEPT,
+ REDUCE,
+ ERROR,
+ CONFLICT, /* Was a reduce, but part of a conflict */
+ SH_RESOLVED, /* Was a shift. Precedence resolved conflict */
+ RD_RESOLVED, /* Was reduce. Precedence resolved conflict */
+ NOT_USED /* Deleted by compression */
+ } type;
+ union {
+ struct state *stp; /* The new state, if a shift */
+ struct rule *rp; /* The rule, if a reduce */
+ } x;
+ struct action *next; /* Next action for this state */
+ struct action *collide; /* Next action with the same hash */
+};
+
+/* Each state of the generated parser's finite state machine
+** is encoded as an instance of the following structure. */
+struct state {
+ struct config *bp; /* The basis configurations for this state */
+ struct config *cfp; /* All configurations in this set */
+ int index; /* Sequencial number for this state */
+ struct action *ap; /* Array of actions for this state */
+ int nTknAct, nNtAct; /* Number of actions on terminals and nonterminals */
+ int iTknOfst, iNtOfst; /* yy_action[] offset for terminals and nonterms */
+ int iDflt; /* Default action */
+};
+#define NO_OFFSET (-2147483647)
+
+/* A followset propagation link indicates that the contents of one
+** configuration followset should be propagated to another whenever
+** the first changes. */
+struct plink {
+ struct config *cfp; /* The configuration to which linked */
+ struct plink *next; /* The next propagate link */
+};
+
+/* The state vector for the entire parser generator is recorded as
+** follows. (LEMON uses no global variables and makes little use of
+** static variables. Fields in the following structure can be thought
+** of as begin global variables in the program.) */
+struct lemon {
+ struct state **sorted; /* Table of states sorted by state number */
+ struct rule *rule; /* List of all rules */
+ int nstate; /* Number of states */
+ int nrule; /* Number of rules */
+ int nsymbol; /* Number of terminal and nonterminal symbols */
+ int nterminal; /* Number of terminal symbols */
+ struct symbol **symbols; /* Sorted array of pointers to symbols */
+ int errorcnt; /* Number of errors */
+ struct symbol *errsym; /* The error symbol */
+ char *name; /* Name of the generated parser */
+ char *arg; /* Declaration of the 3th argument to parser */
+ char *tokentype; /* Type of terminal symbols in the parser stack */
+ char *vartype; /* The default type of non-terminal symbols */
+ char *start; /* Name of the start symbol for the grammar */
+ char *stacksize; /* Size of the parser stack */
+ char *include; /* Code to put at the start of the C file */
+ int includeln; /* Line number for start of include code */
+ char *error; /* Code to execute when an error is seen */
+ int errorln; /* Line number for start of error code */
+ char *overflow; /* Code to execute on a stack overflow */
+ int overflowln; /* Line number for start of overflow code */
+ char *failure; /* Code to execute on parser failure */
+ int failureln; /* Line number for start of failure code */
+ char *accept; /* Code to execute when the parser excepts */
+ int acceptln; /* Line number for the start of accept code */
+ char *extracode; /* Code appended to the generated file */
+ int extracodeln; /* Line number for the start of the extra code */
+ char *tokendest; /* Code to execute to destroy token data */
+ int tokendestln; /* Line number for token destroyer code */
+ char *vardest; /* Code for the default non-terminal destructor */
+ int vardestln; /* Line number for default non-term destructor code*/
+ char *filename; /* Name of the input file */
+ char *tmplname; /* Name of the template file */
+ char *outname; /* Name of the current output file */
+ char *tokenprefix; /* A prefix added to token names in the .h file */
+ int nconflict; /* Number of parsing conflicts */
+ int tablesize; /* Size of the parse tables */
+ int basisflag; /* Print only basis configurations */
+ int has_fallback; /* True if any %fallback is seen in the grammer */
+ char *argv0; /* Name of the program */
+};
+
+#define MemoryCheck(X) if((X)==0){ \
+ extern void memory_error(); \
+ memory_error(); \
+}
+
+/**************** From the file "table.h" *********************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+** "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file! Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+
+/* Routines for handling a strings */
+
+char *Strsafe();
+
+void Strsafe_init(/* void */);
+int Strsafe_insert(/* char * */);
+char *Strsafe_find(/* char * */);
+
+/* Routines for handling symbols of the grammar */
+
+struct symbol *Symbol_new();
+int Symbolcmpp(/* struct symbol **, struct symbol ** */);
+void Symbol_init(/* void */);
+int Symbol_insert(/* struct symbol *, char * */);
+struct symbol *Symbol_find(/* char * */);
+struct symbol *Symbol_Nth(/* int */);
+int Symbol_count(/* */);
+struct symbol **Symbol_arrayof(/* */);
+
+/* Routines to manage the state table */
+
+int Configcmp(/* struct config *, struct config * */);
+struct state *State_new();
+void State_init(/* void */);
+int State_insert(/* struct state *, struct config * */);
+struct state *State_find(/* struct config * */);
+struct state **State_arrayof(/* */);
+
+/* Routines used for efficiency in Configlist_add */
+
+void Configtable_init(/* void */);
+int Configtable_insert(/* struct config * */);
+struct config *Configtable_find(/* struct config * */);
+void Configtable_clear(/* int(*)(struct config *) */);
+/****************** From the file "action.c" *******************************/
+/*
+** Routines processing parser actions in the LEMON parser generator.
+*/
+
+/* Allocate a new parser action */
+struct action *Action_new(){
+ static struct action *freelist = 0;
+ struct action *new;
+
+ if( freelist==0 ){
+ int i;
+ int amt = 100;
+ freelist = (struct action *)malloc( sizeof(struct action)*amt );
+ if( freelist==0 ){
+ fprintf(stderr,"Unable to allocate memory for a new parser action.");
+ exit(1);
+ }
+ for(i=0; i<amt-1; i++) freelist[i].next = &freelist[i+1];
+ freelist[amt-1].next = 0;
+ }
+ new = freelist;
+ freelist = freelist->next;
+ return new;
+}
+
+/* Compare two actions */
+static int actioncmp(ap1,ap2)
+struct action *ap1;
+struct action *ap2;
+{
+ int rc;
+ rc = ap1->sp->index - ap2->sp->index;
+ if( rc==0 ) rc = (int)ap1->type - (int)ap2->type;
+ if( rc==0 ){
+ assert( ap1->type==REDUCE || ap1->type==RD_RESOLVED || ap1->type==CONFLICT);
+ assert( ap2->type==REDUCE || ap2->type==RD_RESOLVED || ap2->type==CONFLICT);
+ rc = ap1->x.rp->index - ap2->x.rp->index;
+ }
+ return rc;
+}
+
+/* Sort parser actions */
+struct action *Action_sort(ap)
+struct action *ap;
+{
+ ap = (struct action *)msort(ap,&ap->next,actioncmp);
+ return ap;
+}
+
+void Action_add(app,type,sp,arg)
+struct action **app;
+enum e_action type;
+struct symbol *sp;
+char *arg;
+{
+ struct action *new;
+ new = Action_new();
+ new->next = *app;
+ *app = new;
+ new->type = type;
+ new->sp = sp;
+ if( type==SHIFT ){
+ new->x.stp = (struct state *)arg;
+ }else{
+ new->x.rp = (struct rule *)arg;
+ }
+}
+/********************** New code to implement the "acttab" module ***********/
+/*
+** This module implements routines use to construct the yy_action[] table.
+*/
+
+/*
+** The state of the yy_action table under construction is an instance of
+** the following structure
+*/
+typedef struct acttab acttab;
+struct acttab {
+ int nAction; /* Number of used slots in aAction[] */
+ int nActionAlloc; /* Slots allocated for aAction[] */
+ struct {
+ int lookahead; /* Value of the lookahead token */
+ int action; /* Action to take on the given lookahead */
+ } *aAction, /* The yy_action[] table under construction */
+ *aLookahead; /* A single new transaction set */
+ int mnLookahead; /* Minimum aLookahead[].lookahead */
+ int mnAction; /* Action associated with mnLookahead */
+ int mxLookahead; /* Maximum aLookahead[].lookahead */
+ int nLookahead; /* Used slots in aLookahead[] */
+ int nLookaheadAlloc; /* Slots allocated in aLookahead[] */
+};
+
+/* Return the number of entries in the yy_action table */
+#define acttab_size(X) ((X)->nAction)
+
+/* The value for the N-th entry in yy_action */
+#define acttab_yyaction(X,N) ((X)->aAction[N].action)
+
+/* The value for the N-th entry in yy_lookahead */
+#define acttab_yylookahead(X,N) ((X)->aAction[N].lookahead)
+
+/* Free all memory associated with the given acttab */
+void acttab_free(acttab *p){
+ free( p->aAction );
+ free( p->aLookahead );
+ free( p );
+}
+
+/* Allocate a new acttab structure */
+acttab *acttab_alloc(void){
+ acttab *p = malloc( sizeof(*p) );
+ if( p==0 ){
+ fprintf(stderr,"Unable to allocate memory for a new acttab.");
+ exit(1);
+ }
+ memset(p, 0, sizeof(*p));
+ return p;
+}
+
+/* Add a new action to the current transaction set
+*/
+void acttab_action(acttab *p, int lookahead, int action){
+ if( p->nLookahead>=p->nLookaheadAlloc ){
+ p->nLookaheadAlloc += 25;
+ p->aLookahead = realloc( p->aLookahead,
+ sizeof(p->aLookahead[0])*p->nLookaheadAlloc );
+ if( p->aLookahead==0 ){
+ fprintf(stderr,"malloc failed\n");
+ exit(1);
+ }
+ }
+ if( p->nLookahead==0 ){
+ p->mxLookahead = lookahead;
+ p->mnLookahead = lookahead;
+ p->mnAction = action;
+ }else{
+ if( p->mxLookahead<lookahead ) p->mxLookahead = lookahead;
+ if( p->mnLookahead>lookahead ){
+ p->mnLookahead = lookahead;
+ p->mnAction = action;
+ }
+ }
+ p->aLookahead[p->nLookahead].lookahead = lookahead;
+ p->aLookahead[p->nLookahead].action = action;
+ p->nLookahead++;
+}
+
+/*
+** Add the transaction set built up with prior calls to acttab_action()
+** into the current action table. Then reset the transaction set back
+** to an empty set in preparation for a new round of acttab_action() calls.
+**
+** Return the offset into the action table of the new transaction.
+*/
+int acttab_insert(acttab *p){
+ int i, j, k, n;
+ assert( p->nLookahead>0 );
+
+ /* Make sure we have enough space to hold the expanded action table
+ ** in the worst case. The worst case occurs if the transaction set
+ ** must be appended to the current action table
+ */
+ n = p->mxLookahead + 1;
+ if( p->nAction + n >= p->nActionAlloc ){
+ int oldAlloc = p->nActionAlloc;
+ p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20;
+ p->aAction = realloc( p->aAction,
+ sizeof(p->aAction[0])*p->nActionAlloc);
+ if( p->aAction==0 ){
+ fprintf(stderr,"malloc failed\n");
+ exit(1);
+ }
+ for(i=oldAlloc; i<p->nActionAlloc; i++){
+ p->aAction[i].lookahead = -1;
+ p->aAction[i].action = -1;
+ }
+ }
+
+ /* Scan the existing action table looking for an offset where we can
+ ** insert the current transaction set. Fall out of the loop when that
+ ** offset is found. In the worst case, we fall out of the loop when
+ ** i reaches p->nAction, which means we append the new transaction set.
+ **
+ ** i is the index in p->aAction[] where p->mnLookahead is inserted.
+ */
+ for(i=0; i<p->nAction+p->mnLookahead; i++){
+ if( p->aAction[i].lookahead<0 ){
+ for(j=0; j<p->nLookahead; j++){
+ k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+ if( k<0 ) break;
+ if( p->aAction[k].lookahead>=0 ) break;
+ }
+ if( j<p->nLookahead ) continue;
+ for(j=0; j<p->nAction; j++){
+ if( p->aAction[j].lookahead==j+p->mnLookahead-i ) break;
+ }
+ if( j==p->nAction ){
+ break; /* Fits in empty slots */
+ }
+ }else if( p->aAction[i].lookahead==p->mnLookahead ){
+ if( p->aAction[i].action!=p->mnAction ) continue;
+ for(j=0; j<p->nLookahead; j++){
+ k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+ if( k<0 || k>=p->nAction ) break;
+ if( p->aLookahead[j].lookahead!=p->aAction[k].lookahead ) break;
+ if( p->aLookahead[j].action!=p->aAction[k].action ) break;
+ }
+ if( j<p->nLookahead ) continue;
+ n = 0;
+ for(j=0; j<p->nAction; j++){
+ if( p->aAction[j].lookahead<0 ) continue;
+ if( p->aAction[j].lookahead==j+p->mnLookahead-i ) n++;
+ }
+ if( n==p->nLookahead ){
+ break; /* Same as a prior transaction set */
+ }
+ }
+ }
+ /* Insert transaction set at index i. */
+ for(j=0; j<p->nLookahead; j++){
+ k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+ p->aAction[k] = p->aLookahead[j];
+ if( k>=p->nAction ) p->nAction = k+1;
+ }
+ p->nLookahead = 0;
+
+ /* Return the offset that is added to the lookahead in order to get the
+ ** index into yy_action of the action */
+ return i - p->mnLookahead;
+}
+
+/********************** From the file "assert.c" ****************************/
+/*
+** A more efficient way of handling assertions.
+*/
+void myassert(file,line)
+char *file;
+int line;
+{
+ fprintf(stderr,"Assertion failed on line %d of file \"%s\"\n",line,file);
+ exit(1);
+}
+/********************** From the file "build.c" *****************************/
+/*
+** Routines to construction the finite state machine for the LEMON
+** parser generator.
+*/
+
+/* Find a precedence symbol of every rule in the grammar.
+**
+** Those rules which have a precedence symbol coded in the input
+** grammar using the "[symbol]" construct will already have the
+** rp->precsym field filled. Other rules take as their precedence
+** symbol the first RHS symbol with a defined precedence. If there
+** are not RHS symbols with a defined precedence, the precedence
+** symbol field is left blank.
+*/
+void FindRulePrecedences(xp)
+struct lemon *xp;
+{
+ struct rule *rp;
+ for(rp=xp->rule; rp; rp=rp->next){
+ if( rp->precsym==0 ){
+ int i;
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhs[i]->prec>=0 ){
+ rp->precsym = rp->rhs[i];
+ break;
+ }
+ }
+ }
+ }
+ return;
+}
+
+/* Find all nonterminals which will generate the empty string.
+** Then go back and compute the first sets of every nonterminal.
+** The first set is the set of all terminal symbols which can begin
+** a string generated by that nonterminal.
+*/
+void FindFirstSets(lemp)
+struct lemon *lemp;
+{
+ int i;
+ struct rule *rp;
+ int progress;
+
+ for(i=0; i<lemp->nsymbol; i++){
+ lemp->symbols[i]->lambda = Bo_FALSE;
+ }
+ for(i=lemp->nterminal; i<lemp->nsymbol; i++){
+ lemp->symbols[i]->firstset = SetNew();
+ }
+
+ /* First compute all lambdas */
+ do{
+ progress = 0;
+ for(rp=lemp->rule; rp; rp=rp->next){
+ if( rp->lhs->lambda ) continue;
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhs[i]->lambda==Bo_FALSE ) break;
+ }
+ if( i==rp->nrhs ){
+ rp->lhs->lambda = Bo_TRUE;
+ progress = 1;
+ }
+ }
+ }while( progress );
+
+ /* Now compute all first sets */
+ do{
+ struct symbol *s1, *s2;
+ progress = 0;
+ for(rp=lemp->rule; rp; rp=rp->next){
+ s1 = rp->lhs;
+ for(i=0; i<rp->nrhs; i++){
+ s2 = rp->rhs[i];
+ if( s2->type==TERMINAL ){
+ progress += SetAdd(s1->firstset,s2->index);
+ break;
+ }else if( s1==s2 ){
+ if( s1->lambda==Bo_FALSE ) break;
+ }else{
+ progress += SetUnion(s1->firstset,s2->firstset);
+ if( s2->lambda==Bo_FALSE ) break;
+ }
+ }
+ }
+ }while( progress );
+ return;
+}
+
+/* Compute all LR(0) states for the grammar. Links
+** are added to between some states so that the LR(1) follow sets
+** can be computed later.
+*/
+PRIVATE struct state *getstate(/* struct lemon * */); /* forward reference */
+void FindStates(lemp)
+struct lemon *lemp;
+{
+ struct symbol *sp;
+ struct rule *rp;
+
+ Configlist_init();
+
+ /* Find the start symbol */
+ if( lemp->start ){
+ sp = Symbol_find(lemp->start);
+ if( sp==0 ){
+ ErrorMsg(lemp->filename,0,
+"The specified start symbol \"%s\" is not \
+in a nonterminal of the grammar. \"%s\" will be used as the start \
+symbol instead.",lemp->start,lemp->rule->lhs->name);
+ lemp->errorcnt++;
+ sp = lemp->rule->lhs;
+ }
+ }else{
+ sp = lemp->rule->lhs;
+ }
+
+ /* Make sure the start symbol doesn't occur on the right-hand side of
+ ** any rule. Report an error if it does. (YACC would generate a new
+ ** start symbol in this case.) */
+ for(rp=lemp->rule; rp; rp=rp->next){
+ int i;
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhs[i]==sp ){
+ ErrorMsg(lemp->filename,0,
+"The start symbol \"%s\" occurs on the \
+right-hand side of a rule. This will result in a parser which \
+does not work properly.",sp->name);
+ lemp->errorcnt++;
+ }
+ }
+ }
+
+ /* The basis configuration set for the first state
+ ** is all rules which have the start symbol as their
+ ** left-hand side */
+ for(rp=sp->rule; rp; rp=rp->nextlhs){
+ struct config *newcfp;
+ newcfp = Configlist_addbasis(rp,0);
+ SetAdd(newcfp->fws,0);
+ }
+
+ /* Compute the first state. All other states will be
+ ** computed automatically during the computation of the first one.
+ ** The returned pointer to the first state is not used. */
+ (void)getstate(lemp);
+ return;
+}
+
+/* Return a pointer to a state which is described by the configuration
+** list which has been built from calls to Configlist_add.
+*/
+PRIVATE void buildshifts(/* struct lemon *, struct state * */); /* Forwd ref */
+PRIVATE struct state *getstate(lemp)
+struct lemon *lemp;
+{
+ struct config *cfp, *bp;
+ struct state *stp;
+
+ /* Extract the sorted basis of the new state. The basis was constructed
+ ** by prior calls to "Configlist_addbasis()". */
+ Configlist_sortbasis();
+ bp = Configlist_basis();
+
+ /* Get a state with the same basis */
+ stp = State_find(bp);
+ if( stp ){
+ /* A state with the same basis already exists! Copy all the follow-set
+ ** propagation links from the state under construction into the
+ ** preexisting state, then return a pointer to the preexisting state */
+ struct config *x, *y;
+ for(x=bp, y=stp->bp; x && y; x=x->bp, y=y->bp){
+ Plink_copy(&y->bplp,x->bplp);
+ Plink_delete(x->fplp);
+ x->fplp = x->bplp = 0;
+ }
+ cfp = Configlist_return();
+ Configlist_eat(cfp);
+ }else{
+ /* This really is a new state. Construct all the details */
+ Configlist_closure(lemp); /* Compute the configuration closure */
+ Configlist_sort(); /* Sort the configuration closure */
+ cfp = Configlist_return(); /* Get a pointer to the config list */
+ stp = State_new(); /* A new state structure */
+ MemoryCheck(stp);
+ stp->bp = bp; /* Remember the configuration basis */
+ stp->cfp = cfp; /* Remember the configuration closure */
+ stp->index = lemp->nstate++; /* Every state gets a sequence number */
+ stp->ap = 0; /* No actions, yet. */
+ State_insert(stp,stp->bp); /* Add to the state table */
+ buildshifts(lemp,stp); /* Recursively compute successor states */
+ }
+ return stp;
+}
+
+/* Construct all successor states to the given state. A "successor"
+** state is any state which can be reached by a shift action.
+*/
+PRIVATE void buildshifts(lemp,stp)
+struct lemon *lemp;
+struct state *stp; /* The state from which successors are computed */
+{
+ struct config *cfp; /* For looping thru the config closure of "stp" */
+ struct config *bcfp; /* For the inner loop on config closure of "stp" */
+ struct config *new; /* */
+ struct symbol *sp; /* Symbol following the dot in configuration "cfp" */
+ struct symbol *bsp; /* Symbol following the dot in configuration "bcfp" */
+ struct state *newstp; /* A pointer to a successor state */
+
+ /* Each configuration becomes complete after it contibutes to a successor
+ ** state. Initially, all configurations are incomplete */
+ for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE;
+
+ /* Loop through all configurations of the state "stp" */
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){
+ if( cfp->status==COMPLETE ) continue; /* Already used by inner loop */
+ if( cfp->dot>=cfp->rp->nrhs ) continue; /* Can't shift this config */
+ Configlist_reset(); /* Reset the new config set */
+ sp = cfp->rp->rhs[cfp->dot]; /* Symbol after the dot */
+
+ /* For every configuration in the state "stp" which has the symbol "sp"
+ ** following its dot, add the same configuration to the basis set under
+ ** construction but with the dot shifted one symbol to the right. */
+ for(bcfp=cfp; bcfp; bcfp=bcfp->next){
+ if( bcfp->status==COMPLETE ) continue; /* Already used */
+ if( bcfp->dot>=bcfp->rp->nrhs ) continue; /* Can't shift this one */
+ bsp = bcfp->rp->rhs[bcfp->dot]; /* Get symbol after dot */
+ if( bsp!=sp ) continue; /* Must be same as for "cfp" */
+ bcfp->status = COMPLETE; /* Mark this config as used */
+ new = Configlist_addbasis(bcfp->rp,bcfp->dot+1);
+ Plink_add(&new->bplp,bcfp);
+ }
+
+ /* Get a pointer to the state described by the basis configuration set
+ ** constructed in the preceding loop */
+ newstp = getstate(lemp);
+
+ /* The state "newstp" is reached from the state "stp" by a shift action
+ ** on the symbol "sp" */
+ Action_add(&stp->ap,SHIFT,sp,newstp);
+ }
+}
+
+/*
+** Construct the propagation links
+*/
+void FindLinks(lemp)
+struct lemon *lemp;
+{
+ int i;
+ struct config *cfp, *other;
+ struct state *stp;
+ struct plink *plp;
+
+ /* Housekeeping detail:
+ ** Add to every propagate link a pointer back to the state to
+ ** which the link is attached. */
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){
+ cfp->stp = stp;
+ }
+ }
+
+ /* Convert all backlinks into forward links. Only the forward
+ ** links are used in the follow-set computation. */
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){
+ for(plp=cfp->bplp; plp; plp=plp->next){
+ other = plp->cfp;
+ Plink_add(&other->fplp,cfp);
+ }
+ }
+ }
+}
+
+/* Compute all followsets.
+**
+** A followset is the set of all symbols which can come immediately
+** after a configuration.
+*/
+void FindFollowSets(lemp)
+struct lemon *lemp;
+{
+ int i;
+ struct config *cfp;
+ struct plink *plp;
+ int progress;
+ int change;
+
+ for(i=0; i<lemp->nstate; i++){
+ for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+ cfp->status = INCOMPLETE;
+ }
+ }
+
+ do{
+ progress = 0;
+ for(i=0; i<lemp->nstate; i++){
+ for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+ if( cfp->status==COMPLETE ) continue;
+ for(plp=cfp->fplp; plp; plp=plp->next){
+ change = SetUnion(plp->cfp->fws,cfp->fws);
+ if( change ){
+ plp->cfp->status = INCOMPLETE;
+ progress = 1;
+ }
+ }
+ cfp->status = COMPLETE;
+ }
+ }
+ }while( progress );
+}
+
+static int resolve_conflict();
+
+/* Compute the reduce actions, and resolve conflicts.
+*/
+void FindActions(lemp)
+struct lemon *lemp;
+{
+ int i,j;
+ struct config *cfp;
+ struct symbol *sp;
+ struct rule *rp;
+
+ /* Add all of the reduce actions
+ ** A reduce action is added for each element of the followset of
+ ** a configuration which has its dot at the extreme right.
+ */
+ for(i=0; i<lemp->nstate; i++){ /* Loop over all states */
+ struct state *stp;
+ stp = lemp->sorted[i];
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){ /* Loop over all configurations */
+ if( cfp->rp->nrhs==cfp->dot ){ /* Is dot at extreme right? */
+ for(j=0; j<lemp->nterminal; j++){
+ if( SetFind(cfp->fws,j) ){
+ /* Add a reduce action to the state "stp" which will reduce by the
+ ** rule "cfp->rp" if the lookahead symbol is "lemp->symbols[j]" */
+ Action_add(&stp->ap,REDUCE,lemp->symbols[j],cfp->rp);
+ }
+ }
+ }
+ }
+ }
+
+ /* Add the accepting token */
+ if( lemp->start ){
+ sp = Symbol_find(lemp->start);
+ if( sp==0 ) sp = lemp->rule->lhs;
+ }else{
+ sp = lemp->rule->lhs;
+ }
+ /* Add to the first state (which is always the starting state of the
+ ** finite state machine) an action to ACCEPT if the lookahead is the
+ ** start nonterminal. */
+ Action_add(&lemp->sorted[0]->ap,ACCEPT,sp,0);
+
+ /* Resolve conflicts */
+ for(i=0; i<lemp->nstate; i++){
+ struct action *ap, *nap;
+ struct state *stp;
+ stp = lemp->sorted[i];
+ assert( stp->ap );
+ stp->ap = Action_sort(stp->ap);
+ for(ap=stp->ap; ap && ap->next; ap=ap->next){
+ for(nap=ap->next; nap && nap->sp==ap->sp; nap=nap->next){
+ /* The two actions "ap" and "nap" have the same lookahead.
+ ** Figure out which one should be used */
+ lemp->nconflict += resolve_conflict(ap,nap,lemp->errsym);
+ }
+ }
+ }
+
+ /* Report an error for each rule that can never be reduced. */
+ for(rp=lemp->rule; rp; rp=rp->next) rp->canReduce = Bo_FALSE;
+ for(i=0; i<lemp->nstate; i++){
+ struct action *ap;
+ for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){
+ if( ap->type==REDUCE ) ap->x.rp->canReduce = Bo_TRUE;
+ }
+ }
+ for(rp=lemp->rule; rp; rp=rp->next){
+ if( rp->canReduce ) continue;
+ ErrorMsg(lemp->filename,rp->ruleline,"This rule can not be reduced.\n");
+ lemp->errorcnt++;
+ }
+}
+
+/* Resolve a conflict between the two given actions. If the
+** conflict can't be resolve, return non-zero.
+**
+** NO LONGER TRUE:
+** To resolve a conflict, first look to see if either action
+** is on an error rule. In that case, take the action which
+** is not associated with the error rule. If neither or both
+** actions are associated with an error rule, then try to
+** use precedence to resolve the conflict.
+**
+** If either action is a SHIFT, then it must be apx. This
+** function won't work if apx->type==REDUCE and apy->type==SHIFT.
+*/
+static int resolve_conflict(apx,apy,errsym)
+struct action *apx;
+struct action *apy;
+struct symbol *errsym; /* The error symbol (if defined. NULL otherwise) */
+{
+ struct symbol *spx, *spy;
+ int errcnt = 0;
+ assert( apx->sp==apy->sp ); /* Otherwise there would be no conflict */
+ if( apx->type==SHIFT && apy->type==REDUCE ){
+ spx = apx->sp;
+ spy = apy->x.rp->precsym;
+ if( spy==0 || spx->prec<0 || spy->prec<0 ){
+ /* Not enough precedence information. */
+ apy->type = CONFLICT;
+ errcnt++;
+ }else if( spx->prec>spy->prec ){ /* Lower precedence wins */
+ apy->type = RD_RESOLVED;
+ }else if( spx->prec<spy->prec ){
+ apx->type = SH_RESOLVED;
+ }else if( spx->prec==spy->prec && spx->assoc==RIGHT ){ /* Use operator */
+ apy->type = RD_RESOLVED; /* associativity */
+ }else if( spx->prec==spy->prec && spx->assoc==LEFT ){ /* to break tie */
+ apx->type = SH_RESOLVED;
+ }else{
+ assert( spx->prec==spy->prec && spx->assoc==NONE );
+ apy->type = CONFLICT;
+ errcnt++;
+ }
+ }else if( apx->type==REDUCE && apy->type==REDUCE ){
+ spx = apx->x.rp->precsym;
+ spy = apy->x.rp->precsym;
+ if( spx==0 || spy==0 || spx->prec<0 ||
+ spy->prec<0 || spx->prec==spy->prec ){
+ apy->type = CONFLICT;
+ errcnt++;
+ }else if( spx->prec>spy->prec ){
+ apy->type = RD_RESOLVED;
+ }else if( spx->prec<spy->prec ){
+ apx->type = RD_RESOLVED;
+ }
+ }else{
+ assert(
+ apx->type==SH_RESOLVED ||
+ apx->type==RD_RESOLVED ||
+ apx->type==CONFLICT ||
+ apy->type==SH_RESOLVED ||
+ apy->type==RD_RESOLVED ||
+ apy->type==CONFLICT
+ );
+ /* The REDUCE/SHIFT case cannot happen because SHIFTs come before
+ ** REDUCEs on the list. If we reach this point it must be because
+ ** the parser conflict had already been resolved. */
+ }
+ return errcnt;
+}
+/********************* From the file "configlist.c" *************************/
+/*
+** Routines to processing a configuration list and building a state
+** in the LEMON parser generator.
+*/
+
+static struct config *freelist = 0; /* List of free configurations */
+static struct config *current = 0; /* Top of list of configurations */
+static struct config **currentend = 0; /* Last on list of configs */
+static struct config *basis = 0; /* Top of list of basis configs */
+static struct config **basisend = 0; /* End of list of basis configs */
+
+/* Return a pointer to a new configuration */
+PRIVATE struct config *newconfig(){
+ struct config *new;
+ if( freelist==0 ){
+ int i;
+ int amt = 3;
+ freelist = (struct config *)malloc( sizeof(struct config)*amt );
+ if( freelist==0 ){
+ fprintf(stderr,"Unable to allocate memory for a new configuration.");
+ exit(1);
+ }
+ for(i=0; i<amt-1; i++) freelist[i].next = &freelist[i+1];
+ freelist[amt-1].next = 0;
+ }
+ new = freelist;
+ freelist = freelist->next;
+ return new;
+}
+
+/* The configuration "old" is no longer used */
+PRIVATE void deleteconfig(old)
+struct config *old;
+{
+ old->next = freelist;
+ freelist = old;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_init(){
+ current = 0;
+ currentend = &current;
+ basis = 0;
+ basisend = &basis;
+ Configtable_init();
+ return;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_reset(){
+ current = 0;
+ currentend = &current;
+ basis = 0;
+ basisend = &basis;
+ Configtable_clear(0);
+ return;
+}
+
+/* Add another configuration to the configuration list */
+struct config *Configlist_add(rp,dot)
+struct rule *rp; /* The rule */
+int dot; /* Index into the RHS of the rule where the dot goes */
+{
+ struct config *cfp, model;
+
+ assert( currentend!=0 );
+ model.rp = rp;
+ model.dot = dot;
+ cfp = Configtable_find(&model);
+ if( cfp==0 ){
+ cfp = newconfig();
+ cfp->rp = rp;
+ cfp->dot = dot;
+ cfp->fws = SetNew();
+ cfp->stp = 0;
+ cfp->fplp = cfp->bplp = 0;
+ cfp->next = 0;
+ cfp->bp = 0;
+ *currentend = cfp;
+ currentend = &cfp->next;
+ Configtable_insert(cfp);
+ }
+ return cfp;
+}
+
+/* Add a basis configuration to the configuration list */
+struct config *Configlist_addbasis(rp,dot)
+struct rule *rp;
+int dot;
+{
+ struct config *cfp, model;
+
+ assert( basisend!=0 );
+ assert( currentend!=0 );
+ model.rp = rp;
+ model.dot = dot;
+ cfp = Configtable_find(&model);
+ if( cfp==0 ){
+ cfp = newconfig();
+ cfp->rp = rp;
+ cfp->dot = dot;
+ cfp->fws = SetNew();
+ cfp->stp = 0;
+ cfp->fplp = cfp->bplp = 0;
+ cfp->next = 0;
+ cfp->bp = 0;
+ *currentend = cfp;
+ currentend = &cfp->next;
+ *basisend = cfp;
+ basisend = &cfp->bp;
+ Configtable_insert(cfp);
+ }
+ return cfp;
+}
+
+/* Compute the closure of the configuration list */
+void Configlist_closure(lemp)
+struct lemon *lemp;
+{
+ struct config *cfp, *newcfp;
+ struct rule *rp, *newrp;
+ struct symbol *sp, *xsp;
+ int i, dot;
+
+ assert( currentend!=0 );
+ for(cfp=current; cfp; cfp=cfp->next){
+ rp = cfp->rp;
+ dot = cfp->dot;
+ if( dot>=rp->nrhs ) continue;
+ sp = rp->rhs[dot];
+ if( sp->type==NONTERMINAL ){
+ if( sp->rule==0 && sp!=lemp->errsym ){
+ ErrorMsg(lemp->filename,rp->line,"Nonterminal \"%s\" has no rules.",
+ sp->name);
+ lemp->errorcnt++;
+ }
+ for(newrp=sp->rule; newrp; newrp=newrp->nextlhs){
+ newcfp = Configlist_add(newrp,0);
+ for(i=dot+1; i<rp->nrhs; i++){
+ xsp = rp->rhs[i];
+ if( xsp->type==TERMINAL ){
+ SetAdd(newcfp->fws,xsp->index);
+ break;
+ }else{
+ SetUnion(newcfp->fws,xsp->firstset);
+ if( xsp->lambda==Bo_FALSE ) break;
+ }
+ }
+ if( i==rp->nrhs ) Plink_add(&cfp->fplp,newcfp);
+ }
+ }
+ }
+ return;
+}
+
+/* Sort the configuration list */
+void Configlist_sort(){
+ current = (struct config *)msort(current,&(current->next),Configcmp);
+ currentend = 0;
+ return;
+}
+
+/* Sort the basis configuration list */
+void Configlist_sortbasis(){
+ basis = (struct config *)msort(current,&(current->bp),Configcmp);
+ basisend = 0;
+ return;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_return(){
+ struct config *old;
+ old = current;
+ current = 0;
+ currentend = 0;
+ return old;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_basis(){
+ struct config *old;
+ old = basis;
+ basis = 0;
+ basisend = 0;
+ return old;
+}
+
+/* Free all elements of the given configuration list */
+void Configlist_eat(cfp)
+struct config *cfp;
+{
+ struct config *nextcfp;
+ for(; cfp; cfp=nextcfp){
+ nextcfp = cfp->next;
+ assert( cfp->fplp==0 );
+ assert( cfp->bplp==0 );
+ if( cfp->fws ) SetFree(cfp->fws);
+ deleteconfig(cfp);
+ }
+ return;
+}
+/***************** From the file "error.c" *********************************/
+/*
+** Code for printing error message.
+*/
+
+/* Find a good place to break "msg" so that its length is at least "min"
+** but no more than "max". Make the point as close to max as possible.
+*/
+static int findbreak(msg,min,max)
+char *msg;
+int min;
+int max;
+{
+ int i,spot;
+ char c;
+ for(i=spot=min; i<=max; i++){
+ c = msg[i];
+ if( c=='\t' ) msg[i] = ' ';
+ if( c=='\n' ){ msg[i] = ' '; spot = i; break; }
+ if( c==0 ){ spot = i; break; }
+ if( c=='-' && i<max-1 ) spot = i+1;
+ if( c==' ' ) spot = i;
+ }
+ return spot;
+}
+
+/*
+** The error message is split across multiple lines if necessary. The
+** splits occur at a space, if there is a space available near the end
+** of the line.
+*/
+#define ERRMSGSIZE 10000 /* Hope this is big enough. No way to error check */
+#define LINEWIDTH 79 /* Max width of any output line */
+#define PREFIXLIMIT 30 /* Max width of the prefix on each line */
+void ErrorMsg(const char *filename, int lineno, const char *format, ...){
+ char errmsg[ERRMSGSIZE];
+ char prefix[PREFIXLIMIT+10];
+ int errmsgsize;
+ int prefixsize;
+ int availablewidth;
+ va_list ap;
+ int end, restart, base;
+
+ va_start(ap, format);
+ /* Prepare a prefix to be prepended to every output line */
+ if( lineno>0 ){
+ sprintf(prefix,"%.*s:%d: ",PREFIXLIMIT-10,filename,lineno);
+ }else{
+ sprintf(prefix,"%.*s: ",PREFIXLIMIT-10,filename);
+ }
+ prefixsize = strlen(prefix);
+ availablewidth = LINEWIDTH - prefixsize;
+
+ /* Generate the error message */
+ vsprintf(errmsg,format,ap);
+ va_end(ap);
+ errmsgsize = strlen(errmsg);
+ /* Remove trailing '\n's from the error message. */
+ while( errmsgsize>0 && errmsg[errmsgsize-1]=='\n' ){
+ errmsg[--errmsgsize] = 0;
+ }
+
+ /* Print the error message */
+ base = 0;
+ while( errmsg[base]!=0 ){
+ end = restart = findbreak(&errmsg[base],0,availablewidth);
+ restart += base;
+ while( errmsg[restart]==' ' ) restart++;
+ fprintf(stdout,"%s%.*s\n",prefix,end,&errmsg[base]);
+ base = restart;
+ }
+}
+/**************** From the file "main.c" ************************************/
+/*
+** Main program file for the LEMON parser generator.
+*/
+
+/* Report an out-of-memory condition and abort. This function
+** is used mostly by the "MemoryCheck" macro in struct.h
+*/
+void memory_error(){
+ fprintf(stderr,"Out of memory. Aborting...\n");
+ exit(1);
+}
+
+
+/* The main program. Parse the command line and do it... */
+int main(argc,argv)
+int argc;
+char **argv;
+{
+ static int version = 0;
+ static int rpflag = 0;
+ static int basisflag = 0;
+ static int compress = 0;
+ static int quiet = 0;
+ static int statistics = 0;
+ static int mhflag = 0;
+ static struct s_options options[] = {
+ {OPT_FLAG, "b", (char*)&basisflag, "Print only the basis in report."},
+ {OPT_FLAG, "c", (char*)&compress, "Don't compress the action table."},
+ {OPT_FLAG, "g", (char*)&rpflag, "Print grammar without actions."},
+ {OPT_FLAG, "m", (char*)&mhflag, "Output a makeheaders compatible file"},
+ {OPT_FLAG, "q", (char*)&quiet, "(Quiet) Don't print the report file."},
+ {OPT_FLAG, "s", (char*)&statistics, "Print parser stats to standard output."},
+ {OPT_FLAG, "x", (char*)&version, "Print the version number."},
+ {OPT_FLAG,0,0,0}
+ };
+ int i;
+ struct lemon lem;
+ char *def_tmpl_name = "lempar.c";
+
+ OptInit(argv,options,stderr);
+ if( version ){
+ printf("Lemon version 1.0\n");
+ exit(0);
+ }
+ if( OptNArgs() < 1 ){
+ fprintf(stderr,"Exactly one filename argument is required.\n");
+ exit(1);
+ }
+ lem.errorcnt = 0;
+
+ /* Initialize the machine */
+ Strsafe_init();
+ Symbol_init();
+ State_init();
+ lem.argv0 = argv[0];
+ lem.filename = OptArg(0);
+ lem.tmplname = (OptNArgs() == 2) ? OptArg(1) : def_tmpl_name;
+ lem.basisflag = basisflag;
+ lem.has_fallback = 0;
+ lem.nconflict = 0;
+ lem.name = lem.include = lem.arg = lem.tokentype = lem.start = 0;
+ lem.vartype = 0;
+ lem.stacksize = 0;
+ lem.error = lem.overflow = lem.failure = lem.accept = lem.tokendest =
+ lem.tokenprefix = lem.outname = lem.extracode = 0;
+ lem.vardest = 0;
+ lem.tablesize = 0;
+ Symbol_new("$");
+ lem.errsym = Symbol_new("error");
+
+ /* Parse the input file */
+ Parse(&lem);
+ if( lem.errorcnt ) exit(lem.errorcnt);
+ if( lem.rule==0 ){
+ fprintf(stderr,"Empty grammar.\n");
+ exit(1);
+ }
+
+ /* Count and index the symbols of the grammar */
+ lem.nsymbol = Symbol_count();
+ Symbol_new("{default}");
+ lem.symbols = Symbol_arrayof();
+ for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i;
+ qsort(lem.symbols,lem.nsymbol+1,sizeof(struct symbol*),
+ (int(*)())Symbolcmpp);
+ for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i;
+ for(i=1; isupper(lem.symbols[i]->name[0]); i++);
+ lem.nterminal = i;
+
+ /* Generate a reprint of the grammar, if requested on the command line */
+ if( rpflag ){
+ Reprint(&lem);
+ }else{
+ /* Initialize the size for all follow and first sets */
+ SetSize(lem.nterminal);
+
+ /* Find the precedence for every production rule (that has one) */
+ FindRulePrecedences(&lem);
+
+ /* Compute the lambda-nonterminals and the first-sets for every
+ ** nonterminal */
+ FindFirstSets(&lem);
+
+ /* Compute all LR(0) states. Also record follow-set propagation
+ ** links so that the follow-set can be computed later */
+ lem.nstate = 0;
+ FindStates(&lem);
+ lem.sorted = State_arrayof();
+
+ /* Tie up loose ends on the propagation links */
+ FindLinks(&lem);
+
+ /* Compute the follow set of every reducible configuration */
+ FindFollowSets(&lem);
+
+ /* Compute the action tables */
+ FindActions(&lem);
+
+ /* Compress the action tables */
+ if( compress==0 ) CompressTables(&lem);
+
+ /* Generate a report of the parser generated. (the "y.output" file) */
+ if( !quiet ) ReportOutput(&lem);
+
+ /* Generate the source code for the parser */
+ ReportTable(&lem, mhflag);
+
+ /* Produce a header file for use by the scanner. (This step is
+ ** omitted if the "-m" option is used because makeheaders will
+ ** generate the file for us.) */
+ if( !mhflag ) ReportHeader(&lem);
+ }
+ if( statistics ){
+ printf("Parser statistics: %d terminals, %d nonterminals, %d rules\n",
+ lem.nterminal, lem.nsymbol - lem.nterminal, lem.nrule);
+ printf(" %d states, %d parser table entries, %d conflicts\n",
+ lem.nstate, lem.tablesize, lem.nconflict);
+ }
+ if( lem.nconflict ){
+ fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict);
+ }
+ exit(lem.errorcnt + lem.nconflict);
+}
+/******************** From the file "msort.c" *******************************/
+/*
+** A generic merge-sort program.
+**
+** USAGE:
+** Let "ptr" be a pointer to some structure which is at the head of
+** a null-terminated list. Then to sort the list call:
+**
+** ptr = msort(ptr,&(ptr->next),cmpfnc);
+**
+** In the above, "cmpfnc" is a pointer to a function which compares
+** two instances of the structure and returns an integer, as in
+** strcmp. The second argument is a pointer to the pointer to the
+** second element of the linked list. This address is used to compute
+** the offset to the "next" field within the structure. The offset to
+** the "next" field must be constant for all structures in the list.
+**
+** The function returns a new pointer which is the head of the list
+** after sorting.
+**
+** ALGORITHM:
+** Merge-sort.
+*/
+
+/*
+** Return a pointer to the next structure in the linked list.
+*/
+#define NEXT(A) (*(char**)(((unsigned long)A)+offset))
+
+/*
+** Inputs:
+** a: A sorted, null-terminated linked list. (May be null).
+** b: A sorted, null-terminated linked list. (May be null).
+** cmp: A pointer to the comparison function.
+** offset: Offset in the structure to the "next" field.
+**
+** Return Value:
+** A pointer to the head of a sorted list containing the elements
+** of both a and b.
+**
+** Side effects:
+** The "next" pointers for elements in the lists a and b are
+** changed.
+*/
+static char *merge(a,b,cmp,offset)
+char *a;
+char *b;
+int (*cmp)();
+int offset;
+{
+ char *ptr, *head;
+
+ if( a==0 ){
+ head = b;
+ }else if( b==0 ){
+ head = a;
+ }else{
+ if( (*cmp)(a,b)<0 ){
+ ptr = a;
+ a = NEXT(a);
+ }else{
+ ptr = b;
+ b = NEXT(b);
+ }
+ head = ptr;
+ while( a && b ){
+ if( (*cmp)(a,b)<0 ){
+ NEXT(ptr) = a;
+ ptr = a;
+ a = NEXT(a);
+ }else{
+ NEXT(ptr) = b;
+ ptr = b;
+ b = NEXT(b);
+ }
+ }
+ if( a ) NEXT(ptr) = a;
+ else NEXT(ptr) = b;
+ }
+ return head;
+}
+
+/*
+** Inputs:
+** list: Pointer to a singly-linked list of structures.
+** next: Pointer to pointer to the second element of the list.
+** cmp: A comparison function.
+**
+** Return Value:
+** A pointer to the head of a sorted list containing the elements
+** orginally in list.
+**
+** Side effects:
+** The "next" pointers for elements in list are changed.
+*/
+#define LISTSIZE 30
+char *msort(list,next,cmp)
+char *list;
+char **next;
+int (*cmp)();
+{
+ unsigned long offset;
+ char *ep;
+ char *set[LISTSIZE];
+ int i;
+ offset = (unsigned long)next - (unsigned long)list;
+ for(i=0; i<LISTSIZE; i++) set[i] = 0;
+ while( list ){
+ ep = list;
+ list = NEXT(list);
+ NEXT(ep) = 0;
+ for(i=0; i<LISTSIZE-1 && set[i]!=0; i++){
+ ep = merge(ep,set[i],cmp,offset);
+ set[i] = 0;
+ }
+ set[i] = ep;
+ }
+ ep = 0;
+ for(i=0; i<LISTSIZE; i++) if( set[i] ) ep = merge(ep,set[i],cmp,offset);
+ return ep;
+}
+/************************ From the file "option.c" **************************/
+static char **argv;
+static struct s_options *op;
+static FILE *errstream;
+
+#define ISOPT(X) ((X)[0]=='-'||(X)[0]=='+'||strchr((X),'=')!=0)
+
+/*
+** Print the command line with a carrot pointing to the k-th character
+** of the n-th field.
+*/
+static void errline(n,k,err)
+int n;
+int k;
+FILE *err;
+{
+ int spcnt, i;
+ spcnt = 0;
+ if( argv[0] ) fprintf(err,"%s",argv[0]);
+ spcnt = strlen(argv[0]) + 1;
+ for(i=1; i<n && argv[i]; i++){
+ fprintf(err," %s",argv[i]);
+ spcnt += strlen(argv[i]+1);
+ }
+ spcnt += k;
+ for(; argv[i]; i++) fprintf(err," %s",argv[i]);
+ if( spcnt<20 ){
+ fprintf(err,"\n%*s^-- here\n",spcnt,"");
+ }else{
+ fprintf(err,"\n%*shere --^\n",spcnt-7,"");
+ }
+}
+
+/*
+** Return the index of the N-th non-switch argument. Return -1
+** if N is out of range.
+*/
+static int argindex(n)
+int n;
+{
+ int i;
+ int dashdash = 0;
+ if( argv!=0 && *argv!=0 ){
+ for(i=1; argv[i]; i++){
+ if( dashdash || !ISOPT(argv[i]) ){
+ if( n==0 ) return i;
+ n--;
+ }
+ if( strcmp(argv[i],"--")==0 ) dashdash = 1;
+ }
+ }
+ return -1;
+}
+
+static char emsg[] = "Command line syntax error: ";
+
+/*
+** Process a flag command line argument.
+*/
+static int handleflags(i,err)
+int i;
+FILE *err;
+{
+ int v;
+ int errcnt = 0;
+ int j;
+ for(j=0; op[j].label; j++){
+ if( strcmp(&argv[i][1],op[j].label)==0 ) break;
+ }
+ v = argv[i][0]=='-' ? 1 : 0;
+ if( op[j].label==0 ){
+ if( err ){
+ fprintf(err,"%sundefined option.\n",emsg);
+ errline(i,1,err);
+ }
+ errcnt++;
+ }else if( op[j].type==OPT_FLAG ){
+ *((int*)op[j].arg) = v;
+ }else if( op[j].type==OPT_FFLAG ){
+ (*(void(*)())(op[j].arg))(v);
+ }else{
+ if( err ){
+ fprintf(err,"%smissing argument on switch.\n",emsg);
+ errline(i,1,err);
+ }
+ errcnt++;
+ }
+ return errcnt;
+}
+
+/*
+** Process a command line switch which has an argument.
+*/
+static int handleswitch(i,err)
+int i;
+FILE *err;
+{
+ int lv = 0;
+ double dv = 0.0;
+ char *sv = 0, *end;
+ char *cp;
+ int j;
+ int errcnt = 0;
+ cp = strchr(argv[i],'=');
+ *cp = 0;
+ for(j=0; op[j].label; j++){
+ if( strcmp(argv[i],op[j].label)==0 ) break;
+ }
+ *cp = '=';
+ if( op[j].label==0 ){
+ if( err ){
+ fprintf(err,"%sundefined option.\n",emsg);
+ errline(i,0,err);
+ }
+ errcnt++;
+ }else{
+ cp++;
+ switch( op[j].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ if( err ){
+ fprintf(err,"%soption requires an argument.\n",emsg);
+ errline(i,0,err);
+ }
+ errcnt++;
+ break;
+ case OPT_DBL:
+ case OPT_FDBL:
+ dv = strtod(cp,&end);
+ if( *end ){
+ if( err ){
+ fprintf(err,"%sillegal character in floating-point argument.\n",emsg);
+ errline(i,((unsigned long)end)-(unsigned long)argv[i],err);
+ }
+ errcnt++;
+ }
+ break;
+ case OPT_INT:
+ case OPT_FINT:
+ lv = strtol(cp,&end,0);
+ if( *end ){
+ if( err ){
+ fprintf(err,"%sillegal character in integer argument.\n",emsg);
+ errline(i,((unsigned long)end)-(unsigned long)argv[i],err);
+ }
+ errcnt++;
+ }
+ break;
+ case OPT_STR:
+ case OPT_FSTR:
+ sv = cp;
+ break;
+ }
+ switch( op[j].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ break;
+ case OPT_DBL:
+ *(double*)(op[j].arg) = dv;
+ break;
+ case OPT_FDBL:
+ (*(void(*)())(op[j].arg))(dv);
+ break;
+ case OPT_INT:
+ *(int*)(op[j].arg) = lv;
+ break;
+ case OPT_FINT:
+ (*(void(*)())(op[j].arg))((int)lv);
+ break;
+ case OPT_STR:
+ *(char**)(op[j].arg) = sv;
+ break;
+ case OPT_FSTR:
+ (*(void(*)())(op[j].arg))(sv);
+ break;
+ }
+ }
+ return errcnt;
+}
+
+int OptInit(a,o,err)
+char **a;
+struct s_options *o;
+FILE *err;
+{
+ int errcnt = 0;
+ argv = a;
+ op = o;
+ errstream = err;
+ if( argv && *argv && op ){
+ int i;
+ for(i=1; argv[i]; i++){
+ if( argv[i][0]=='+' || argv[i][0]=='-' ){
+ errcnt += handleflags(i,err);
+ }else if( strchr(argv[i],'=') ){
+ errcnt += handleswitch(i,err);
+ }
+ }
+ }
+ if( errcnt>0 ){
+ fprintf(err,"Valid command line options for \"%s\" are:\n",*a);
+ OptPrint();
+ exit(1);
+ }
+ return 0;
+}
+
+int OptNArgs(){
+ int cnt = 0;
+ int dashdash = 0;
+ int i;
+ if( argv!=0 && argv[0]!=0 ){
+ for(i=1; argv[i]; i++){
+ if( dashdash || !ISOPT(argv[i]) ) cnt++;
+ if( strcmp(argv[i],"--")==0 ) dashdash = 1;
+ }
+ }
+ return cnt;
+}
+
+char *OptArg(n)
+int n;
+{
+ int i;
+ i = argindex(n);
+ return i>=0 ? argv[i] : 0;
+}
+
+void OptErr(n)
+int n;
+{
+ int i;
+ i = argindex(n);
+ if( i>=0 ) errline(i,0,errstream);
+}
+
+void OptPrint(){
+ int i;
+ int max, len;
+ max = 0;
+ for(i=0; op[i].label; i++){
+ len = strlen(op[i].label) + 1;
+ switch( op[i].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ break;
+ case OPT_INT:
+ case OPT_FINT:
+ len += 9; /* length of "<integer>" */
+ break;
+ case OPT_DBL:
+ case OPT_FDBL:
+ len += 6; /* length of "<real>" */
+ break;
+ case OPT_STR:
+ case OPT_FSTR:
+ len += 8; /* length of "<string>" */
+ break;
+ }
+ if( len>max ) max = len;
+ }
+ for(i=0; op[i].label; i++){
+ switch( op[i].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ fprintf(errstream," -%-*s %s\n",max,op[i].label,op[i].message);
+ break;
+ case OPT_INT:
+ case OPT_FINT:
+ fprintf(errstream," %s=<integer>%*s %s\n",op[i].label,
+ (int)(max-strlen(op[i].label)-9),"",op[i].message);
+ break;
+ case OPT_DBL:
+ case OPT_FDBL:
+ fprintf(errstream," %s=<real>%*s %s\n",op[i].label,
+ (int)(max-strlen(op[i].label)-6),"",op[i].message);
+ break;
+ case OPT_STR:
+ case OPT_FSTR:
+ fprintf(errstream," %s=<string>%*s %s\n",op[i].label,
+ (int)(max-strlen(op[i].label)-8),"",op[i].message);
+ break;
+ }
+ }
+}
+/*********************** From the file "parse.c" ****************************/
+/*
+** Input file parser for the LEMON parser generator.
+*/
+
+/* The state of the parser */
+struct pstate {
+ char *filename; /* Name of the input file */
+ int tokenlineno; /* Linenumber at which current token starts */
+ int errorcnt; /* Number of errors so far */
+ char *tokenstart; /* Text of current token */
+ struct lemon *gp; /* Global state vector */
+ enum e_state {
+ INITIALIZE,
+ WAITING_FOR_DECL_OR_RULE,
+ WAITING_FOR_DECL_KEYWORD,
+ WAITING_FOR_DECL_ARG,
+ WAITING_FOR_PRECEDENCE_SYMBOL,
+ WAITING_FOR_ARROW,
+ IN_RHS,
+ LHS_ALIAS_1,
+ LHS_ALIAS_2,
+ LHS_ALIAS_3,
+ RHS_ALIAS_1,
+ RHS_ALIAS_2,
+ PRECEDENCE_MARK_1,
+ PRECEDENCE_MARK_2,
+ RESYNC_AFTER_RULE_ERROR,
+ RESYNC_AFTER_DECL_ERROR,
+ WAITING_FOR_DESTRUCTOR_SYMBOL,
+ WAITING_FOR_DATATYPE_SYMBOL,
+ WAITING_FOR_FALLBACK_ID
+ } state; /* The state of the parser */
+ struct symbol *fallback; /* The fallback token */
+ struct symbol *lhs; /* Left-hand side of current rule */
+ char *lhsalias; /* Alias for the LHS */
+ int nrhs; /* Number of right-hand side symbols seen */
+ struct symbol *rhs[MAXRHS]; /* RHS symbols */
+ char *alias[MAXRHS]; /* Aliases for each RHS symbol (or NULL) */
+ struct rule *prevrule; /* Previous rule parsed */
+ char *declkeyword; /* Keyword of a declaration */
+ char **declargslot; /* Where the declaration argument should be put */
+ int *decllnslot; /* Where the declaration linenumber is put */
+ enum e_assoc declassoc; /* Assign this association to decl arguments */
+ int preccounter; /* Assign this precedence to decl arguments */
+ struct rule *firstrule; /* Pointer to first rule in the grammar */
+ struct rule *lastrule; /* Pointer to the most recently parsed rule */
+};
+
+/* Parse a single token */
+static void parseonetoken(psp)
+struct pstate *psp;
+{
+ char *x;
+ x = Strsafe(psp->tokenstart); /* Save the token permanently */
+#if 0
+ printf("%s:%d: Token=[%s] state=%d\n",psp->filename,psp->tokenlineno,
+ x,psp->state);
+#endif
+ switch( psp->state ){
+ case INITIALIZE:
+ psp->prevrule = 0;
+ psp->preccounter = 0;
+ psp->firstrule = psp->lastrule = 0;
+ psp->gp->nrule = 0;
+ /* Fall thru to next case */
+ case WAITING_FOR_DECL_OR_RULE:
+ if( x[0]=='%' ){
+ psp->state = WAITING_FOR_DECL_KEYWORD;
+ }else if( islower(x[0]) ){
+ psp->lhs = Symbol_new(x);
+ psp->nrhs = 0;
+ psp->lhsalias = 0;
+ psp->state = WAITING_FOR_ARROW;
+ }else if( x[0]=='{' ){
+ if( psp->prevrule==0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+"There is not prior rule opon which to attach the code \
+fragment which begins on this line.");
+ psp->errorcnt++;
+ }else if( psp->prevrule->code!=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+"Code fragment beginning on this line is not the first \
+to follow the previous rule.");
+ psp->errorcnt++;
+ }else{
+ psp->prevrule->line = psp->tokenlineno;
+ psp->prevrule->code = &x[1];
+ }
+ }else if( x[0]=='[' ){
+ psp->state = PRECEDENCE_MARK_1;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Token \"%s\" should be either \"%%\" or a nonterminal name.",
+ x);
+ psp->errorcnt++;
+ }
+ break;
+ case PRECEDENCE_MARK_1:
+ if( !isupper(x[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "The precedence symbol must be a terminal.");
+ psp->errorcnt++;
+ }else if( psp->prevrule==0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "There is no prior rule to assign precedence \"[%s]\".",x);
+ psp->errorcnt++;
+ }else if( psp->prevrule->precsym!=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+"Precedence mark on this line is not the first \
+to follow the previous rule.");
+ psp->errorcnt++;
+ }else{
+ psp->prevrule->precsym = Symbol_new(x);
+ }
+ psp->state = PRECEDENCE_MARK_2;
+ break;
+ case PRECEDENCE_MARK_2:
+ if( x[0]!=']' ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \"]\" on precedence mark.");
+ psp->errorcnt++;
+ }
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ break;
+ case WAITING_FOR_ARROW:
+ if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+ psp->state = IN_RHS;
+ }else if( x[0]=='(' ){
+ psp->state = LHS_ALIAS_1;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Expected to see a \":\" following the LHS symbol \"%s\".",
+ psp->lhs->name);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case LHS_ALIAS_1:
+ if( isalpha(x[0]) ){
+ psp->lhsalias = x;
+ psp->state = LHS_ALIAS_2;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "\"%s\" is not a valid alias for the LHS \"%s\"\n",
+ x,psp->lhs->name);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case LHS_ALIAS_2:
+ if( x[0]==')' ){
+ psp->state = LHS_ALIAS_3;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case LHS_ALIAS_3:
+ if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+ psp->state = IN_RHS;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \"->\" following: \"%s(%s)\".",
+ psp->lhs->name,psp->lhsalias);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case IN_RHS:
+ if( x[0]=='.' ){
+ struct rule *rp;
+ rp = (struct rule *)malloc( sizeof(struct rule) +
+ sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs );
+ if( rp==0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Can't allocate enough memory for this rule.");
+ psp->errorcnt++;
+ psp->prevrule = 0;
+ }else{
+ int i;
+ rp->ruleline = psp->tokenlineno;
+ rp->rhs = (struct symbol**)&rp[1];
+ rp->rhsalias = (char**)&(rp->rhs[psp->nrhs]);
+ for(i=0; i<psp->nrhs; i++){
+ rp->rhs[i] = psp->rhs[i];
+ rp->rhsalias[i] = psp->alias[i];
+ }
+ rp->lhs = psp->lhs;
+ rp->lhsalias = psp->lhsalias;
+ rp->nrhs = psp->nrhs;
+ rp->code = 0;
+ rp->precsym = 0;
+ rp->index = psp->gp->nrule++;
+ rp->nextlhs = rp->lhs->rule;
+ rp->lhs->rule = rp;
+ rp->next = 0;
+ if( psp->firstrule==0 ){
+ psp->firstrule = psp->lastrule = rp;
+ }else{
+ psp->lastrule->next = rp;
+ psp->lastrule = rp;
+ }
+ psp->prevrule = rp;
+ }
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( isalpha(x[0]) ){
+ if( psp->nrhs>=MAXRHS ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Too many symbol on RHS or rule beginning at \"%s\".",
+ x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }else{
+ psp->rhs[psp->nrhs] = Symbol_new(x);
+ psp->alias[psp->nrhs] = 0;
+ psp->nrhs++;
+ }
+ }else if( x[0]=='(' && psp->nrhs>0 ){
+ psp->state = RHS_ALIAS_1;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Illegal character on RHS of rule: \"%s\".",x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case RHS_ALIAS_1:
+ if( isalpha(x[0]) ){
+ psp->alias[psp->nrhs-1] = x;
+ psp->state = RHS_ALIAS_2;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n",
+ x,psp->rhs[psp->nrhs-1]->name);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case RHS_ALIAS_2:
+ if( x[0]==')' ){
+ psp->state = IN_RHS;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case WAITING_FOR_DECL_KEYWORD:
+ if( isalpha(x[0]) ){
+ psp->declkeyword = x;
+ psp->declargslot = 0;
+ psp->decllnslot = 0;
+ psp->state = WAITING_FOR_DECL_ARG;
+ if( strcmp(x,"name")==0 ){
+ psp->declargslot = &(psp->gp->name);
+ }else if( strcmp(x,"include")==0 ){
+ psp->declargslot = &(psp->gp->include);
+ psp->decllnslot = &psp->gp->includeln;
+ }else if( strcmp(x,"code")==0 ){
+ psp->declargslot = &(psp->gp->extracode);
+ psp->decllnslot = &psp->gp->extracodeln;
+ }else if( strcmp(x,"token_destructor")==0 ){
+ psp->declargslot = &psp->gp->tokendest;
+ psp->decllnslot = &psp->gp->tokendestln;
+ }else if( strcmp(x,"default_destructor")==0 ){
+ psp->declargslot = &psp->gp->vardest;
+ psp->decllnslot = &psp->gp->vardestln;
+ }else if( strcmp(x,"token_prefix")==0 ){
+ psp->declargslot = &psp->gp->tokenprefix;
+ }else if( strcmp(x,"syntax_error")==0 ){
+ psp->declargslot = &(psp->gp->error);
+ psp->decllnslot = &psp->gp->errorln;
+ }else if( strcmp(x,"parse_accept")==0 ){
+ psp->declargslot = &(psp->gp->accept);
+ psp->decllnslot = &psp->gp->acceptln;
+ }else if( strcmp(x,"parse_failure")==0 ){
+ psp->declargslot = &(psp->gp->failure);
+ psp->decllnslot = &psp->gp->failureln;
+ }else if( strcmp(x,"stack_overflow")==0 ){
+ psp->declargslot = &(psp->gp->overflow);
+ psp->decllnslot = &psp->gp->overflowln;
+ }else if( strcmp(x,"extra_argument")==0 ){
+ psp->declargslot = &(psp->gp->arg);
+ }else if( strcmp(x,"token_type")==0 ){
+ psp->declargslot = &(psp->gp->tokentype);
+ }else if( strcmp(x,"default_type")==0 ){
+ psp->declargslot = &(psp->gp->vartype);
+ }else if( strcmp(x,"stack_size")==0 ){
+ psp->declargslot = &(psp->gp->stacksize);
+ }else if( strcmp(x,"start_symbol")==0 ){
+ psp->declargslot = &(psp->gp->start);
+ }else if( strcmp(x,"left")==0 ){
+ psp->preccounter++;
+ psp->declassoc = LEFT;
+ psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+ }else if( strcmp(x,"right")==0 ){
+ psp->preccounter++;
+ psp->declassoc = RIGHT;
+ psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+ }else if( strcmp(x,"nonassoc")==0 ){
+ psp->preccounter++;
+ psp->declassoc = NONE;
+ psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+ }else if( strcmp(x,"destructor")==0 ){
+ psp->state = WAITING_FOR_DESTRUCTOR_SYMBOL;
+ }else if( strcmp(x,"type")==0 ){
+ psp->state = WAITING_FOR_DATATYPE_SYMBOL;
+ }else if( strcmp(x,"fallback")==0 ){
+ psp->fallback = 0;
+ psp->state = WAITING_FOR_FALLBACK_ID;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Unknown declaration keyword: \"%%%s\".",x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Illegal declaration keyword: \"%s\".",x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ break;
+ case WAITING_FOR_DESTRUCTOR_SYMBOL:
+ if( !isalpha(x[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol name missing after %destructor keyword");
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ struct symbol *sp = Symbol_new(x);
+ psp->declargslot = &sp->destructor;
+ psp->decllnslot = &sp->destructorln;
+ psp->state = WAITING_FOR_DECL_ARG;
+ }
+ break;
+ case WAITING_FOR_DATATYPE_SYMBOL:
+ if( !isalpha(x[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol name missing after %destructor keyword");
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ struct symbol *sp = Symbol_new(x);
+ psp->declargslot = &sp->datatype;
+ psp->decllnslot = 0;
+ psp->state = WAITING_FOR_DECL_ARG;
+ }
+ break;
+ case WAITING_FOR_PRECEDENCE_SYMBOL:
+ if( x[0]=='.' ){
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( isupper(x[0]) ){
+ struct symbol *sp;
+ sp = Symbol_new(x);
+ if( sp->prec>=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol \"%s\" has already be given a precedence.",x);
+ psp->errorcnt++;
+ }else{
+ sp->prec = psp->preccounter;
+ sp->assoc = psp->declassoc;
+ }
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Can't assign a precedence to \"%s\".",x);
+ psp->errorcnt++;
+ }
+ break;
+ case WAITING_FOR_DECL_ARG:
+ if( (x[0]=='{' || x[0]=='\"' || isalnum(x[0])) ){
+ if( *(psp->declargslot)!=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "The argument \"%s\" to declaration \"%%%s\" is not the first.",
+ x[0]=='\"' ? &x[1] : x,psp->declkeyword);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ *(psp->declargslot) = (x[0]=='\"' || x[0]=='{') ? &x[1] : x;
+ if( psp->decllnslot ) *psp->decllnslot = psp->tokenlineno;
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Illegal argument to %%%s: %s",psp->declkeyword,x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ break;
+ case WAITING_FOR_FALLBACK_ID:
+ if( x[0]=='.' ){
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( !isupper(x[0]) ){
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "%%fallback argument \"%s\" should be a token", x);
+ psp->errorcnt++;
+ }else{
+ struct symbol *sp = Symbol_new(x);
+ if( psp->fallback==0 ){
+ psp->fallback = sp;
+ }else if( sp->fallback ){
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "More than one fallback assigned to token %s", x);
+ psp->errorcnt++;
+ }else{
+ sp->fallback = psp->fallback;
+ psp->gp->has_fallback = 1;
+ }
+ }
+ break;
+ case RESYNC_AFTER_RULE_ERROR:
+/* if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+** break; */
+ case RESYNC_AFTER_DECL_ERROR:
+ if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+ if( x[0]=='%' ) psp->state = WAITING_FOR_DECL_KEYWORD;
+ break;
+ }
+}
+
+/* In spite of its name, this function is really a scanner. It read
+** in the entire input file (all at once) then tokenizes it. Each
+** token is passed to the function "parseonetoken" which builds all
+** the appropriate data structures in the global state vector "gp".
+*/
+void Parse(gp)
+struct lemon *gp;
+{
+ struct pstate ps;
+ FILE *fp;
+ char *filebuf;
+ size_t filesize;
+ int lineno;
+ int c;
+ char *cp, *nextcp;
+ int startline = 0;
+
+ ps.gp = gp;
+ ps.filename = gp->filename;
+ ps.errorcnt = 0;
+ ps.state = INITIALIZE;
+
+ /* Begin by reading the input file */
+ fp = fopen(ps.filename,"rb");
+ if( fp==0 ){
+ ErrorMsg(ps.filename,0,"Can't open this file for reading.");
+ gp->errorcnt++;
+ return;
+ }
+ fseek(fp,0,2);
+ filesize = ftell(fp);
+ rewind(fp);
+ filebuf = (char *)malloc( filesize+1 );
+ if( filebuf==0 ){
+ ErrorMsg(ps.filename,0,"Can't allocate %d of memory to hold this file.",
+ filesize+1);
+ gp->errorcnt++;
+ return;
+ }
+ if( fread(filebuf,1,filesize,fp)!=filesize ){
+ ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.",
+ filesize);
+ free(filebuf);
+ gp->errorcnt++;
+ return;
+ }
+ fclose(fp);
+ filebuf[filesize] = 0;
+
+ /* Now scan the text of the input file */
+ lineno = 1;
+ for(cp=filebuf; (c= *cp)!=0; ){
+ if( c=='\n' ) lineno++; /* Keep track of the line number */
+ if( isspace(c) ){ cp++; continue; } /* Skip all white space */
+ if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments */
+ cp+=2;
+ while( (c= *cp)!=0 && c!='\n' ) cp++;
+ continue;
+ }
+ if( c=='/' && cp[1]=='*' ){ /* Skip C style comments */
+ cp+=2;
+ while( (c= *cp)!=0 && (c!='/' || cp[-1]!='*') ){
+ if( c=='\n' ) lineno++;
+ cp++;
+ }
+ if( c ) cp++;
+ continue;
+ }
+ ps.tokenstart = cp; /* Mark the beginning of the token */
+ ps.tokenlineno = lineno; /* Linenumber on which token begins */
+ if( c=='\"' ){ /* String literals */
+ cp++;
+ while( (c= *cp)!=0 && c!='\"' ){
+ if( c=='\n' ) lineno++;
+ cp++;
+ }
+ if( c==0 ){
+ ErrorMsg(ps.filename,startline,
+"String starting on this line is not terminated before the end of the file.");
+ ps.errorcnt++;
+ nextcp = cp;
+ }else{
+ nextcp = cp+1;
+ }
+ }else if( c=='{' ){ /* A block of C code */
+ int level;
+ cp++;
+ for(level=1; (c= *cp)!=0 && (level>1 || c!='}'); cp++){
+ if( c=='\n' ) lineno++;
+ else if( c=='{' ) level++;
+ else if( c=='}' ) level--;
+ else if( c=='/' && cp[1]=='*' ){ /* Skip comments */
+ int prevc;
+ cp = &cp[2];
+ prevc = 0;
+ while( (c= *cp)!=0 && (c!='/' || prevc!='*') ){
+ if( c=='\n' ) lineno++;
+ prevc = c;
+ cp++;
+ }
+ }else if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments too */
+ cp = &cp[2];
+ while( (c= *cp)!=0 && c!='\n' ) cp++;
+ if( c ) lineno++;
+ }else if( c=='\'' || c=='\"' ){ /* String a character literals */
+ int startchar, prevc;
+ startchar = c;
+ prevc = 0;
+ for(cp++; (c= *cp)!=0 && (c!=startchar || prevc=='\\'); cp++){
+ if( c=='\n' ) lineno++;
+ if( prevc=='\\' ) prevc = 0;
+ else prevc = c;
+ }
+ }
+ }
+ if( c==0 ){
+ ErrorMsg(ps.filename,ps.tokenlineno,
+"C code starting on this line is not terminated before the end of the file.");
+ ps.errorcnt++;
+ nextcp = cp;
+ }else{
+ nextcp = cp+1;
+ }
+ }else if( isalnum(c) ){ /* Identifiers */
+ while( (c= *cp)!=0 && (isalnum(c) || c=='_') ) cp++;
+ nextcp = cp;
+ }else if( c==':' && cp[1]==':' && cp[2]=='=' ){ /* The operator "::=" */
+ cp += 3;
+ nextcp = cp;
+ }else{ /* All other (one character) operators */
+ cp++;
+ nextcp = cp;
+ }
+ c = *cp;
+ *cp = 0; /* Null terminate the token */
+ parseonetoken(&ps); /* Parse the token */
+ *cp = c; /* Restore the buffer */
+ cp = nextcp;
+ }
+ free(filebuf); /* Release the buffer after parsing */
+ gp->rule = ps.firstrule;
+ gp->errorcnt = ps.errorcnt;
+}
+/*************************** From the file "plink.c" *********************/
+/*
+** Routines processing configuration follow-set propagation links
+** in the LEMON parser generator.
+*/
+static struct plink *plink_freelist = 0;
+
+/* Allocate a new plink */
+struct plink *Plink_new(){
+ struct plink *new;
+
+ if( plink_freelist==0 ){
+ int i;
+ int amt = 100;
+ plink_freelist = (struct plink *)malloc( sizeof(struct plink)*amt );
+ if( plink_freelist==0 ){
+ fprintf(stderr,
+ "Unable to allocate memory for a new follow-set propagation link.\n");
+ exit(1);
+ }
+ for(i=0; i<amt-1; i++) plink_freelist[i].next = &plink_freelist[i+1];
+ plink_freelist[amt-1].next = 0;
+ }
+ new = plink_freelist;
+ plink_freelist = plink_freelist->next;
+ return new;
+}
+
+/* Add a plink to a plink list */
+void Plink_add(plpp,cfp)
+struct plink **plpp;
+struct config *cfp;
+{
+ struct plink *new;
+ new = Plink_new();
+ new->next = *plpp;
+ *plpp = new;
+ new->cfp = cfp;
+}
+
+/* Transfer every plink on the list "from" to the list "to" */
+void Plink_copy(to,from)
+struct plink **to;
+struct plink *from;
+{
+ struct plink *nextpl;
+ while( from ){
+ nextpl = from->next;
+ from->next = *to;
+ *to = from;
+ from = nextpl;
+ }
+}
+
+/* Delete every plink on the list */
+void Plink_delete(plp)
+struct plink *plp;
+{
+ struct plink *nextpl;
+
+ while( plp ){
+ nextpl = plp->next;
+ plp->next = plink_freelist;
+ plink_freelist = plp;
+ plp = nextpl;
+ }
+}
+/*********************** From the file "report.c" **************************/
+/*
+** Procedures for generating reports and tables in the LEMON parser generator.
+*/
+
+/* Generate a filename with the given suffix. Space to hold the
+** name comes from malloc() and must be freed by the calling
+** function.
+*/
+PRIVATE char *file_makename(lemp,suffix)
+struct lemon *lemp;
+char *suffix;
+{
+ char *name;
+ char *cp;
+
+ name = malloc( strlen(lemp->filename) + strlen(suffix) + 5 );
+ if( name==0 ){
+ fprintf(stderr,"Can't allocate space for a filename.\n");
+ exit(1);
+ }
+ /* skip directory, JK */
+ if (NULL == (cp = strrchr(lemp->filename, '/'))) {
+ cp = lemp->filename;
+ } else {
+ cp++;
+ }
+ strcpy(name,cp);
+ cp = strrchr(name,'.');
+ if( cp ) *cp = 0;
+ strcat(name,suffix);
+ return name;
+}
+
+/* Open a file with a name based on the name of the input file,
+** but with a different (specified) suffix, and return a pointer
+** to the stream */
+PRIVATE FILE *file_open(lemp,suffix,mode)
+struct lemon *lemp;
+char *suffix;
+char *mode;
+{
+ FILE *fp;
+
+ if( lemp->outname ) free(lemp->outname);
+ lemp->outname = file_makename(lemp, suffix);
+ fp = fopen(lemp->outname,mode);
+ if( fp==0 && *mode=='w' ){
+ fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname);
+ lemp->errorcnt++;
+ return 0;
+ }
+ return fp;
+}
+
+/* Duplicate the input file without comments and without actions
+** on rules */
+void Reprint(lemp)
+struct lemon *lemp;
+{
+ struct rule *rp;
+ struct symbol *sp;
+ int i, j, maxlen, len, ncolumns, skip;
+ printf("// Reprint of input file \"%s\".\n// Symbols:\n",lemp->filename);
+ maxlen = 10;
+ for(i=0; i<lemp->nsymbol; i++){
+ sp = lemp->symbols[i];
+ len = strlen(sp->name);
+ if( len>maxlen ) maxlen = len;
+ }
+ ncolumns = 76/(maxlen+5);
+ if( ncolumns<1 ) ncolumns = 1;
+ skip = (lemp->nsymbol + ncolumns - 1)/ncolumns;
+ for(i=0; i<skip; i++){
+ printf("//");
+ for(j=i; j<lemp->nsymbol; j+=skip){
+ sp = lemp->symbols[j];
+ assert( sp->index==j );
+ printf(" %3d %-*.*s",j,maxlen,maxlen,sp->name);
+ }
+ printf("\n");
+ }
+ for(rp=lemp->rule; rp; rp=rp->next){
+ printf("%s",rp->lhs->name);
+/* if( rp->lhsalias ) printf("(%s)",rp->lhsalias); */
+ printf(" ::=");
+ for(i=0; i<rp->nrhs; i++){
+ printf(" %s",rp->rhs[i]->name);
+/* if( rp->rhsalias[i] ) printf("(%s)",rp->rhsalias[i]); */
+ }
+ printf(".");
+ if( rp->precsym ) printf(" [%s]",rp->precsym->name);
+/* if( rp->code ) printf("\n %s",rp->code); */
+ printf("\n");
+ }
+}
+
+void ConfigPrint(fp,cfp)
+FILE *fp;
+struct config *cfp;
+{
+ struct rule *rp;
+ int i;
+ rp = cfp->rp;
+ fprintf(fp,"%s ::=",rp->lhs->name);
+ for(i=0; i<=rp->nrhs; i++){
+ if( i==cfp->dot ) fprintf(fp," *");
+ if( i==rp->nrhs ) break;
+ fprintf(fp," %s",rp->rhs[i]->name);
+ }
+}
+
+/* #define TEST */
+#ifdef TEST
+/* Print a set */
+PRIVATE void SetPrint(out,set,lemp)
+FILE *out;
+char *set;
+struct lemon *lemp;
+{
+ int i;
+ char *spacer;
+ spacer = "";
+ fprintf(out,"%12s[","");
+ for(i=0; i<lemp->nterminal; i++){
+ if( SetFind(set,i) ){
+ fprintf(out,"%s%s",spacer,lemp->symbols[i]->name);
+ spacer = " ";
+ }
+ }
+ fprintf(out,"]\n");
+}
+
+/* Print a plink chain */
+PRIVATE void PlinkPrint(out,plp,tag)
+FILE *out;
+struct plink *plp;
+char *tag;
+{
+ while( plp ){
+ fprintf(out,"%12s%s (state %2d) ","",tag,plp->cfp->stp->index);
+ ConfigPrint(out,plp->cfp);
+ fprintf(out,"\n");
+ plp = plp->next;
+ }
+}
+#endif
+
+/* Print an action to the given file descriptor. Return FALSE if
+** nothing was actually printed.
+*/
+int PrintAction(struct action *ap, FILE *fp, int indent){
+ int result = 1;
+ switch( ap->type ){
+ case SHIFT:
+ fprintf(fp,"%*s shift %d",indent,ap->sp->name,ap->x.stp->index);
+ break;
+ case REDUCE:
+ fprintf(fp,"%*s reduce %d",indent,ap->sp->name,ap->x.rp->index);
+ break;
+ case ACCEPT:
+ fprintf(fp,"%*s accept",indent,ap->sp->name);
+ break;
+ case ERROR:
+ fprintf(fp,"%*s error",indent,ap->sp->name);
+ break;
+ case CONFLICT:
+ fprintf(fp,"%*s reduce %-3d ** Parsing conflict **",
+ indent,ap->sp->name,ap->x.rp->index);
+ break;
+ case SH_RESOLVED:
+ case RD_RESOLVED:
+ case NOT_USED:
+ result = 0;
+ break;
+ }
+ return result;
+}
+
+/* Generate the "y.output" log file */
+void ReportOutput(lemp)
+struct lemon *lemp;
+{
+ int i;
+ struct state *stp;
+ struct config *cfp;
+ struct action *ap;
+ FILE *fp;
+
+ fp = file_open(lemp,".out","w");
+ if( fp==0 ) return;
+ fprintf(fp," \b");
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ fprintf(fp,"State %d:\n",stp->index);
+ if( lemp->basisflag ) cfp=stp->bp;
+ else cfp=stp->cfp;
+ while( cfp ){
+ char buf[20];
+ if( cfp->dot==cfp->rp->nrhs ){
+ sprintf(buf,"(%d)",cfp->rp->index);
+ fprintf(fp," %5s ",buf);
+ }else{
+ fprintf(fp," ");
+ }
+ ConfigPrint(fp,cfp);
+ fprintf(fp,"\n");
+#ifdef TEST
+ SetPrint(fp,cfp->fws,lemp);
+ PlinkPrint(fp,cfp->fplp,"To ");
+ PlinkPrint(fp,cfp->bplp,"From");
+#endif
+ if( lemp->basisflag ) cfp=cfp->bp;
+ else cfp=cfp->next;
+ }
+ fprintf(fp,"\n");
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( PrintAction(ap,fp,30) ) fprintf(fp,"\n");
+ }
+ fprintf(fp,"\n");
+ }
+ fclose(fp);
+ return;
+}
+
+/* Search for the file "name" which is in the same directory as
+** the exacutable */
+PRIVATE char *pathsearch(argv0,name,modemask)
+char *argv0;
+char *name;
+int modemask;
+{
+ char *pathlist;
+ char *path,*cp;
+ char c;
+ extern int access();
+
+#ifdef __WIN32__
+ cp = strrchr(argv0,'\\');
+#else
+ cp = strrchr(argv0,'/');
+#endif
+ if( cp ){
+ c = *cp;
+ *cp = 0;
+ path = (char *)malloc( strlen(argv0) + strlen(name) + 2 );
+ if( path ) sprintf(path,"%s/%s",argv0,name);
+ *cp = c;
+ }else{
+ extern char *getenv();
+ pathlist = getenv("PATH");
+ if( pathlist==0 ) pathlist = ".:/bin:/usr/bin";
+ path = (char *)malloc( strlen(pathlist)+strlen(name)+2 );
+ if( path!=0 ){
+ while( *pathlist ){
+ cp = strchr(pathlist,':');
+ if( cp==0 ) cp = &pathlist[strlen(pathlist)];
+ c = *cp;
+ *cp = 0;
+ sprintf(path,"%s/%s",pathlist,name);
+ *cp = c;
+ if( c==0 ) pathlist = "";
+ else pathlist = &cp[1];
+ if( access(path,modemask)==0 ) break;
+ }
+ }
+ }
+ return path;
+}
+
+/* Given an action, compute the integer value for that action
+** which is to be put in the action table of the generated machine.
+** Return negative if no action should be generated.
+*/
+PRIVATE int compute_action(lemp,ap)
+struct lemon *lemp;
+struct action *ap;
+{
+ int act;
+ switch( ap->type ){
+ case SHIFT: act = ap->x.stp->index; break;
+ case REDUCE: act = ap->x.rp->index + lemp->nstate; break;
+ case ERROR: act = lemp->nstate + lemp->nrule; break;
+ case ACCEPT: act = lemp->nstate + lemp->nrule + 1; break;
+ default: act = -1; break;
+ }
+ return act;
+}
+
+#define LINESIZE 1000
+/* The next cluster of routines are for reading the template file
+** and writing the results to the generated parser */
+/* The first function transfers data from "in" to "out" until
+** a line is seen which begins with "%%". The line number is
+** tracked.
+**
+** if name!=0, then any word that begin with "Parse" is changed to
+** begin with *name instead.
+*/
+PRIVATE void tplt_xfer(name,in,out,lineno)
+char *name;
+FILE *in;
+FILE *out;
+int *lineno;
+{
+ int i, iStart;
+ char line[LINESIZE];
+ while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){
+ (*lineno)++;
+ iStart = 0;
+ if( name ){
+ for(i=0; line[i]; i++){
+ if( line[i]=='P' && strncmp(&line[i],"Parse",5)==0
+ && (i==0 || !isalpha(line[i-1]))
+ ){
+ if( i>iStart ) fprintf(out,"%.*s",i-iStart,&line[iStart]);
+ fprintf(out,"%s",name);
+ i += 4;
+ iStart = i+1;
+ }
+ }
+ }
+ fprintf(out,"%s",&line[iStart]);
+ }
+}
+
+/* The next function finds the template file and opens it, returning
+** a pointer to the opened file. */
+PRIVATE FILE *tplt_open(lemp)
+struct lemon *lemp;
+{
+
+ char buf[1000];
+ FILE *in;
+ char *tpltname;
+ char *cp;
+
+ cp = strrchr(lemp->filename,'.');
+ if( cp ){
+ sprintf(buf,"%.*s.lt",(int)(cp-lemp->filename),lemp->filename);
+ }else{
+ sprintf(buf,"%s.lt",lemp->filename);
+ }
+ if( access(buf,004)==0 ){
+ tpltname = buf;
+ }else if( access(lemp->tmplname,004)==0 ){
+ tpltname = lemp->tmplname;
+ }else{
+ tpltname = pathsearch(lemp->argv0,lemp->tmplname,0);
+ }
+ if( tpltname==0 ){
+ fprintf(stderr,"Can't find the parser driver template file \"%s\".\n",
+ lemp->tmplname);
+ lemp->errorcnt++;
+ return 0;
+ }
+ in = fopen(tpltname,"r");
+ if( in==0 ){
+ fprintf(stderr,"Can't open the template file \"%s\".\n",lemp->tmplname);
+ lemp->errorcnt++;
+ return 0;
+ }
+ return in;
+}
+
+/* Print a string to the file and keep the linenumber up to date */
+PRIVATE void tplt_print(out,lemp,str,strln,lineno)
+FILE *out;
+struct lemon *lemp;
+char *str;
+int strln;
+int *lineno;
+{
+ if( str==0 ) return;
+ fprintf(out,"#line %d \"%s\"\n",strln,lemp->filename); (*lineno)++;
+ while( *str ){
+ if( *str=='\n' ) (*lineno)++;
+ putc(*str,out);
+ str++;
+ }
+ fprintf(out,"\n#line %d \"%s\"\n",*lineno+2,lemp->outname); (*lineno)+=2;
+ return;
+}
+
+/*
+** The following routine emits code for the destructor for the
+** symbol sp
+*/
+void emit_destructor_code(out,sp,lemp,lineno)
+FILE *out;
+struct symbol *sp;
+struct lemon *lemp;
+int *lineno;
+{
+ char *cp = 0;
+
+ int linecnt = 0;
+ if( sp->type==TERMINAL ){
+ cp = lemp->tokendest;
+ if( cp==0 ) return;
+ fprintf(out,"#line %d \"%s\"\n{",lemp->tokendestln,lemp->filename);
+ }else if( sp->destructor ){
+ cp = sp->destructor;
+ fprintf(out,"#line %d \"%s\"\n{",sp->destructorln,lemp->filename);
+ }else if( lemp->vardest ){
+ cp = lemp->vardest;
+ if( cp==0 ) return;
+ fprintf(out,"#line %d \"%s\"\n{",lemp->vardestln,lemp->filename);
+ }
+ for(; *cp; cp++){
+ if( *cp=='$' && cp[1]=='$' ){
+ fprintf(out,"(yypminor->yy%d)",sp->dtnum);
+ cp++;
+ continue;
+ }
+ if( *cp=='\n' ) linecnt++;
+ fputc(*cp,out);
+ }
+ (*lineno) += 3 + linecnt;
+ fprintf(out,"}\n#line %d \"%s\"\n",*lineno,lemp->outname);
+ return;
+}
+
+/*
+** Return TRUE (non-zero) if the given symbol has a destructor.
+*/
+int has_destructor(sp, lemp)
+struct symbol *sp;
+struct lemon *lemp;
+{
+ int ret;
+ if( sp->type==TERMINAL ){
+ ret = lemp->tokendest!=0;
+ }else{
+ ret = lemp->vardest!=0 || sp->destructor!=0;
+ }
+ return ret;
+}
+
+/*
+** Generate code which executes when the rule "rp" is reduced. Write
+** the code to "out". Make sure lineno stays up-to-date.
+*/
+PRIVATE void emit_code(out,rp,lemp,lineno)
+FILE *out;
+struct rule *rp;
+struct lemon *lemp;
+int *lineno;
+{
+ char *cp, *xp;
+ int linecnt = 0;
+ int i;
+ char lhsused = 0; /* True if the LHS element has been used */
+ char used[MAXRHS]; /* True for each RHS element which is used */
+
+ for(i=0; i<rp->nrhs; i++) used[i] = 0;
+ lhsused = 0;
+
+ /* Generate code to do the reduce action */
+ if( rp->code ){
+ fprintf(out,"#line %d \"%s\"\n{",rp->line,lemp->filename);
+ for(cp=rp->code; *cp; cp++){
+ if( isalpha(*cp) && (cp==rp->code || (!isalnum(cp[-1]) && cp[-1]!='_')) ){
+ char saved;
+ for(xp= &cp[1]; isalnum(*xp) || *xp=='_'; xp++);
+ saved = *xp;
+ *xp = 0;
+ if( rp->lhsalias && strcmp(cp,rp->lhsalias)==0 ){
+ fprintf(out,"yygotominor.yy%d",rp->lhs->dtnum);
+ cp = xp;
+ lhsused = 1;
+ }else{
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhsalias[i] && strcmp(cp,rp->rhsalias[i])==0 ){
+ fprintf(out,"yymsp[%d].minor.yy%d",i-rp->nrhs+1,rp->rhs[i]->dtnum);
+ cp = xp;
+ used[i] = 1;
+ break;
+ }
+ }
+ }
+ *xp = saved;
+ }
+ if( *cp=='\n' ) linecnt++;
+ fputc(*cp,out);
+ } /* End loop */
+ (*lineno) += 3 + linecnt;
+ fprintf(out,"}\n#line %d \"%s\"\n",*lineno,lemp->outname);
+ } /* End if( rp->code ) */
+
+ /* Check to make sure the LHS has been used */
+ if( rp->lhsalias && !lhsused ){
+ ErrorMsg(lemp->filename,rp->ruleline,
+ "Label \"%s\" for \"%s(%s)\" is never used.",
+ rp->lhsalias,rp->lhs->name,rp->lhsalias);
+ lemp->errorcnt++;
+ }
+
+ /* Generate destructor code for RHS symbols which are not used in the
+ ** reduce code */
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhsalias[i] && !used[i] ){
+ ErrorMsg(lemp->filename,rp->ruleline,
+ "Label %s for \"%s(%s)\" is never used.",
+ rp->rhsalias[i],rp->rhs[i]->name,rp->rhsalias[i]);
+ lemp->errorcnt++;
+ }else if( rp->rhsalias[i]==0 ){
+ if( has_destructor(rp->rhs[i],lemp) ){
+ fprintf(out," yy_destructor(%d,&yymsp[%d].minor);\n",
+ rp->rhs[i]->index,i-rp->nrhs+1); (*lineno)++;
+ }else{
+ fprintf(out," /* No destructor defined for %s */\n",
+ rp->rhs[i]->name);
+ (*lineno)++;
+ }
+ }
+ }
+ return;
+}
+
+/*
+** Print the definition of the union used for the parser's data stack.
+** This union contains fields for every possible data type for tokens
+** and nonterminals. In the process of computing and printing this
+** union, also set the ".dtnum" field of every terminal and nonterminal
+** symbol.
+*/
+void print_stack_union(out,lemp,plineno,mhflag)
+FILE *out; /* The output stream */
+struct lemon *lemp; /* The main info structure for this parser */
+int *plineno; /* Pointer to the line number */
+int mhflag; /* True if generating makeheaders output */
+{
+ int lineno = *plineno; /* The line number of the output */
+ char **types; /* A hash table of datatypes */
+ int arraysize; /* Size of the "types" array */
+ int maxdtlength; /* Maximum length of any ".datatype" field. */
+ char *stddt; /* Standardized name for a datatype */
+ int i,j; /* Loop counters */
+ int hash; /* For hashing the name of a type */
+ char *name; /* Name of the parser */
+
+ /* Allocate and initialize types[] and allocate stddt[] */
+ arraysize = lemp->nsymbol * 2;
+ types = (char**)malloc( arraysize * sizeof(char*) );
+ for(i=0; i<arraysize; i++) types[i] = 0;
+ maxdtlength = 0;
+ if( lemp->vartype ){
+ maxdtlength = strlen(lemp->vartype);
+ }
+ for(i=0; i<lemp->nsymbol; i++){
+ int len;
+ struct symbol *sp = lemp->symbols[i];
+ if( sp->datatype==0 ) continue;
+ len = strlen(sp->datatype);
+ if( len>maxdtlength ) maxdtlength = len;
+ }
+ stddt = (char*)malloc( maxdtlength*2 + 1 );
+ if( types==0 || stddt==0 ){
+ fprintf(stderr,"Out of memory.\n");
+ exit(1);
+ }
+
+ /* Build a hash table of datatypes. The ".dtnum" field of each symbol
+ ** is filled in with the hash index plus 1. A ".dtnum" value of 0 is
+ ** used for terminal symbols. If there is no %default_type defined then
+ ** 0 is also used as the .dtnum value for nonterminals which do not specify
+ ** a datatype using the %type directive.
+ */
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ char *cp;
+ if( sp==lemp->errsym ){
+ sp->dtnum = arraysize+1;
+ continue;
+ }
+ if( sp->type!=NONTERMINAL || (sp->datatype==0 && lemp->vartype==0) ){
+ sp->dtnum = 0;
+ continue;
+ }
+ cp = sp->datatype;
+ if( cp==0 ) cp = lemp->vartype;
+ j = 0;
+ while( isspace(*cp) ) cp++;
+ while( *cp ) stddt[j++] = *cp++;
+ while( j>0 && isspace(stddt[j-1]) ) j--;
+ stddt[j] = 0;
+ hash = 0;
+ for(j=0; stddt[j]; j++){
+ hash = hash*53 + stddt[j];
+ }
+ hash = (hash & 0x7fffffff)%arraysize;
+ while( types[hash] ){
+ if( strcmp(types[hash],stddt)==0 ){
+ sp->dtnum = hash + 1;
+ break;
+ }
+ hash++;
+ if( hash>=arraysize ) hash = 0;
+ }
+ if( types[hash]==0 ){
+ sp->dtnum = hash + 1;
+ types[hash] = (char*)malloc( strlen(stddt)+1 );
+ if( types[hash]==0 ){
+ fprintf(stderr,"Out of memory.\n");
+ exit(1);
+ }
+ strcpy(types[hash],stddt);
+ }
+ }
+
+ /* Print out the definition of YYTOKENTYPE and YYMINORTYPE */
+ name = lemp->name ? lemp->name : "Parse";
+ lineno = *plineno;
+ if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; }
+ fprintf(out,"#define %sTOKENTYPE %s\n",name,
+ lemp->tokentype?lemp->tokentype:"void*"); lineno++;
+ if( mhflag ){ fprintf(out,"#endif\n"); lineno++; }
+ fprintf(out,"typedef union {\n"); lineno++;
+ fprintf(out," %sTOKENTYPE yy0;\n",name); lineno++;
+ for(i=0; i<arraysize; i++){
+ if( types[i]==0 ) continue;
+ fprintf(out," %s yy%d;\n",types[i],i+1); lineno++;
+ free(types[i]);
+ }
+ fprintf(out," int yy%d;\n",lemp->errsym->dtnum); lineno++;
+ free(stddt);
+ free(types);
+ fprintf(out,"} YYMINORTYPE;\n"); lineno++;
+ *plineno = lineno;
+}
+
+/*
+** Return the name of a C datatype able to represent values between
+** lwr and upr, inclusive.
+*/
+static const char *minimum_size_type(int lwr, int upr){
+ if( lwr>=0 ){
+ if( upr<=255 ){
+ return "unsigned char";
+ }else if( upr<65535 ){
+ return "unsigned short int";
+ }else{
+ return "unsigned int";
+ }
+ }else if( lwr>=-127 && upr<=127 ){
+ return "signed char";
+ }else if( lwr>=-32767 && upr<32767 ){
+ return "short";
+ }else{
+ return "int";
+ }
+}
+
+/*
+** Each state contains a set of token transaction and a set of
+** nonterminal transactions. Each of these sets makes an instance
+** of the following structure. An array of these structures is used
+** to order the creation of entries in the yy_action[] table.
+*/
+struct axset {
+ struct state *stp; /* A pointer to a state */
+ int isTkn; /* True to use tokens. False for non-terminals */
+ int nAction; /* Number of actions */
+};
+
+/*
+** Compare to axset structures for sorting purposes
+*/
+static int axset_compare(const void *a, const void *b){
+ struct axset *p1 = (struct axset*)a;
+ struct axset *p2 = (struct axset*)b;
+ return p2->nAction - p1->nAction;
+}
+
+/* Generate C source code for the parser */
+void ReportTable(lemp, mhflag)
+struct lemon *lemp;
+int mhflag; /* Output in makeheaders format if true */
+{
+ FILE *out, *in;
+ char line[LINESIZE];
+ int lineno;
+ struct state *stp;
+ struct action *ap;
+ struct rule *rp;
+ struct acttab *pActtab;
+ int i, j, n;
+ int mnTknOfst, mxTknOfst;
+ int mnNtOfst, mxNtOfst;
+ struct axset *ax;
+ char *name;
+
+ in = tplt_open(lemp);
+ if( in==0 ) return;
+ out = file_open(lemp,".c","w");
+ if( out==0 ){
+ fclose(in);
+ return;
+ }
+ lineno = 1;
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the include code, if any */
+ tplt_print(out,lemp,lemp->include,lemp->includeln,&lineno);
+ if( mhflag ){
+ name = file_makename(lemp, ".h");
+ fprintf(out,"#include \"%s\"\n", name); lineno++;
+ free(name);
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate #defines for all tokens */
+ if( mhflag ){
+ char *prefix;
+ fprintf(out,"#if INTERFACE\n"); lineno++;
+ if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+ else prefix = "";
+ for(i=1; i<lemp->nterminal; i++){
+ fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+ lineno++;
+ }
+ fprintf(out,"#endif\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the defines */
+ fprintf(out,"/* \001 */\n");
+ fprintf(out,"#define YYCODETYPE %s\n",
+ minimum_size_type(0, lemp->nsymbol+5)); lineno++;
+ fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1); lineno++;
+ fprintf(out,"#define YYACTIONTYPE %s\n",
+ minimum_size_type(0, lemp->nstate+lemp->nrule+5)); lineno++;
+ print_stack_union(out,lemp,&lineno,mhflag);
+ if( lemp->stacksize ){
+ if( atoi(lemp->stacksize)<=0 ){
+ ErrorMsg(lemp->filename,0,
+"Illegal stack size: [%s]. The stack size should be an integer constant.",
+ lemp->stacksize);
+ lemp->errorcnt++;
+ lemp->stacksize = "100";
+ }
+ fprintf(out,"#define YYSTACKDEPTH %s\n",lemp->stacksize); lineno++;
+ }else{
+ fprintf(out,"#define YYSTACKDEPTH 100\n"); lineno++;
+ }
+ if( mhflag ){
+ fprintf(out,"#if INTERFACE\n"); lineno++;
+ }
+ name = lemp->name ? lemp->name : "Parse";
+ if( lemp->arg && lemp->arg[0] ){
+ i = strlen(lemp->arg);
+ while( i>=1 && isspace(lemp->arg[i-1]) ) i--;
+ while( i>=1 && (isalnum(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--;
+ fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg); lineno++;
+ fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg); lineno++;
+ fprintf(out,"#define %sARG_FETCH %s = yypParser->%s\n",
+ name,lemp->arg,&lemp->arg[i]); lineno++;
+ fprintf(out,"#define %sARG_STORE yypParser->%s = %s\n",
+ name,&lemp->arg[i],&lemp->arg[i]); lineno++;
+ }else{
+ fprintf(out,"#define %sARG_SDECL\n",name); lineno++;
+ fprintf(out,"#define %sARG_PDECL\n",name); lineno++;
+ fprintf(out,"#define %sARG_FETCH\n",name); lineno++;
+ fprintf(out,"#define %sARG_STORE\n",name); lineno++;
+ }
+ if( mhflag ){
+ fprintf(out,"#endif\n"); lineno++;
+ }
+ fprintf(out,"#define YYNSTATE %d\n",lemp->nstate); lineno++;
+ fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++;
+ fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++;
+ fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++;
+ if( lemp->has_fallback ){
+ fprintf(out,"#define YYFALLBACK 1\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the action table and its associates:
+ **
+ ** yy_action[] A single table containing all actions.
+ ** yy_lookahead[] A table containing the lookahead for each entry in
+ ** yy_action. Used to detect hash collisions.
+ ** yy_shift_ofst[] For each state, the offset into yy_action for
+ ** shifting terminals.
+ ** yy_reduce_ofst[] For each state, the offset into yy_action for
+ ** shifting non-terminals after a reduce.
+ ** yy_default[] Default action for each state.
+ */
+
+ /* Compute the actions on all states and count them up */
+ ax = malloc( sizeof(ax[0])*lemp->nstate*2 );
+ if( ax==0 ){
+ fprintf(stderr,"malloc failed\n");
+ exit(1);
+ }
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ stp->nTknAct = stp->nNtAct = 0;
+ stp->iDflt = lemp->nstate + lemp->nrule;
+ stp->iTknOfst = NO_OFFSET;
+ stp->iNtOfst = NO_OFFSET;
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( compute_action(lemp,ap)>=0 ){
+ if( ap->sp->index<lemp->nterminal ){
+ stp->nTknAct++;
+ }else if( ap->sp->index<lemp->nsymbol ){
+ stp->nNtAct++;
+ }else{
+ stp->iDflt = compute_action(lemp, ap);
+ }
+ }
+ }
+ ax[i*2].stp = stp;
+ ax[i*2].isTkn = 1;
+ ax[i*2].nAction = stp->nTknAct;
+ ax[i*2+1].stp = stp;
+ ax[i*2+1].isTkn = 0;
+ ax[i*2+1].nAction = stp->nNtAct;
+ }
+ mxTknOfst = mnTknOfst = 0;
+ mxNtOfst = mnNtOfst = 0;
+
+ /* Compute the action table. In order to try to keep the size of the
+ ** action table to a minimum, the heuristic of placing the largest action
+ ** sets first is used.
+ */
+ qsort(ax, lemp->nstate*2, sizeof(ax[0]), axset_compare);
+ pActtab = acttab_alloc();
+ for(i=0; i<lemp->nstate*2 && ax[i].nAction>0; i++){
+ stp = ax[i].stp;
+ if( ax[i].isTkn ){
+ for(ap=stp->ap; ap; ap=ap->next){
+ int action;
+ if( ap->sp->index>=lemp->nterminal ) continue;
+ action = compute_action(lemp, ap);
+ if( action<0 ) continue;
+ acttab_action(pActtab, ap->sp->index, action);
+ }
+ stp->iTknOfst = acttab_insert(pActtab);
+ if( stp->iTknOfst<mnTknOfst ) mnTknOfst = stp->iTknOfst;
+ if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst;
+ }else{
+ for(ap=stp->ap; ap; ap=ap->next){
+ int action;
+ if( ap->sp->index<lemp->nterminal ) continue;
+ if( ap->sp->index==lemp->nsymbol ) continue;
+ action = compute_action(lemp, ap);
+ if( action<0 ) continue;
+ acttab_action(pActtab, ap->sp->index, action);
+ }
+ stp->iNtOfst = acttab_insert(pActtab);
+ if( stp->iNtOfst<mnNtOfst ) mnNtOfst = stp->iNtOfst;
+ if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst;
+ }
+ }
+ free(ax);
+
+ /* Output the yy_action table */
+ fprintf(out,"static YYACTIONTYPE yy_action[] = {\n"); lineno++;
+ n = acttab_size(pActtab);
+ for(i=j=0; i<n; i++){
+ int action = acttab_yyaction(pActtab, i);
+ if( action<0 ) action = lemp->nsymbol + lemp->nrule + 2;
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", action);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+
+ /* Output the yy_lookahead table */
+ fprintf(out,"static YYCODETYPE yy_lookahead[] = {\n"); lineno++;
+ for(i=j=0; i<n; i++){
+ int la = acttab_yylookahead(pActtab, i);
+ if( la<0 ) la = lemp->nsymbol;
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", la);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+
+ /* Output the yy_shift_ofst[] table */
+ fprintf(out, "#define YY_SHIFT_USE_DFLT (%d)\n", mnTknOfst-1); lineno++;
+ fprintf(out, "static %s yy_shift_ofst[] = {\n",
+ minimum_size_type(mnTknOfst-1, mxTknOfst)); lineno++;
+ n = lemp->nstate;
+ for(i=j=0; i<n; i++){
+ int ofst;
+ stp = lemp->sorted[i];
+ ofst = stp->iTknOfst;
+ if( ofst==NO_OFFSET ) ofst = mnTknOfst - 1;
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", ofst);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+
+ /* Output the yy_reduce_ofst[] table */
+ fprintf(out, "#define YY_REDUCE_USE_DFLT (%d)\n", mnNtOfst-1); lineno++;
+ fprintf(out, "static %s yy_reduce_ofst[] = {\n",
+ minimum_size_type(mnNtOfst-1, mxNtOfst)); lineno++;
+ n = lemp->nstate;
+ for(i=j=0; i<n; i++){
+ int ofst;
+ stp = lemp->sorted[i];
+ ofst = stp->iNtOfst;
+ if( ofst==NO_OFFSET ) ofst = mnNtOfst - 1;
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", ofst);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+
+ /* Output the default action table */
+ fprintf(out, "static YYACTIONTYPE yy_default[] = {\n"); lineno++;
+ n = lemp->nstate;
+ for(i=j=0; i<n; i++){
+ stp = lemp->sorted[i];
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", stp->iDflt);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the table of fallback tokens.
+ */
+ if( lemp->has_fallback ){
+ for(i=0; i<lemp->nterminal; i++){
+ struct symbol *p = lemp->symbols[i];
+ if( p->fallback==0 ){
+ fprintf(out, " 0, /* %10s => nothing */\n", p->name);
+ }else{
+ fprintf(out, " %3d, /* %10s => %s */\n", p->fallback->index,
+ p->name, p->fallback->name);
+ }
+ lineno++;
+ }
+ }
+ tplt_xfer(lemp->name, in, out, &lineno);
+
+ /* Generate a table containing the symbolic name of every symbol
+ */
+ for(i=0; i<lemp->nsymbol; i++){
+ sprintf(line,"\"%s\",",lemp->symbols[i]->name);
+ fprintf(out," %-15s",line);
+ if( (i&3)==3 ){ fprintf(out,"\n"); lineno++; }
+ }
+ if( (i&3)!=0 ){ fprintf(out,"\n"); lineno++; }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate a table containing a text string that describes every
+ ** rule in the rule set of the grammer. This information is used
+ ** when tracing REDUCE actions.
+ */
+ for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+ assert( rp->index==i );
+ fprintf(out," /* %3d */ \"%s ::=", i, rp->lhs->name);
+ for(j=0; j<rp->nrhs; j++) fprintf(out," %s",rp->rhs[j]->name);
+ fprintf(out,"\",\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes every time a symbol is popped from
+ ** the stack while processing errors or while destroying the parser.
+ ** (In other words, generate the %destructor actions)
+ */
+ if( lemp->tokendest ){
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ if( sp==0 || sp->type!=TERMINAL ) continue;
+ fprintf(out," case %d:\n",sp->index); lineno++;
+ }
+ for(i=0; i<lemp->nsymbol && lemp->symbols[i]->type!=TERMINAL; i++);
+ if( i<lemp->nsymbol ){
+ emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ }
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue;
+ fprintf(out," case %d:\n",sp->index); lineno++;
+ emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ if( lemp->vardest ){
+ struct symbol *dflt_sp = 0;
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ if( sp==0 || sp->type==TERMINAL ||
+ sp->index<=0 || sp->destructor!=0 ) continue;
+ fprintf(out," case %d:\n",sp->index); lineno++;
+ dflt_sp = sp;
+ }
+ if( dflt_sp!=0 ){
+ emit_destructor_code(out,dflt_sp,lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes whenever the parser stack overflows */
+ tplt_print(out,lemp,lemp->overflow,lemp->overflowln,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the table of rule information
+ **
+ ** Note: This code depends on the fact that rules are number
+ ** sequentually beginning with 0.
+ */
+ for(rp=lemp->rule; rp; rp=rp->next){
+ fprintf(out," { %d, %d },\n",rp->lhs->index,rp->nrhs); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which execution during each REDUCE action */
+ for(rp=lemp->rule; rp; rp=rp->next){
+ fprintf(out," case %d:\n",rp->index); lineno++;
+ emit_code(out,rp,lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes if a parse fails */
+ tplt_print(out,lemp,lemp->failure,lemp->failureln,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes when a syntax error occurs */
+ tplt_print(out,lemp,lemp->error,lemp->errorln,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes when the parser accepts its input */
+ tplt_print(out,lemp,lemp->accept,lemp->acceptln,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Append any addition code the user desires */
+ tplt_print(out,lemp,lemp->extracode,lemp->extracodeln,&lineno);
+
+ fclose(in);
+ fclose(out);
+ return;
+}
+
+/* Generate a header file for the parser */
+void ReportHeader(lemp)
+struct lemon *lemp;
+{
+ FILE *out, *in;
+ char *prefix;
+ char line[LINESIZE];
+ char pattern[LINESIZE];
+ int i;
+
+ if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+ else prefix = "";
+ in = file_open(lemp,".h","r");
+ if( in ){
+ for(i=1; i<lemp->nterminal && fgets(line,LINESIZE,in); i++){
+ sprintf(pattern,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+ if( strcmp(line,pattern) ) break;
+ }
+ fclose(in);
+ if( i==lemp->nterminal ){
+ /* No change in the file. Don't rewrite it. */
+ return;
+ }
+ }
+ out = file_open(lemp,".h","w");
+ if( out ){
+ for(i=1; i<lemp->nterminal; i++){
+ fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+ }
+ fclose(out);
+ }
+ return;
+}
+
+/* Reduce the size of the action tables, if possible, by making use
+** of defaults.
+**
+** In this version, we take the most frequent REDUCE action and make
+** it the default. Only default a reduce if there are more than one.
+*/
+void CompressTables(lemp)
+struct lemon *lemp;
+{
+ struct state *stp;
+ struct action *ap, *ap2;
+ struct rule *rp, *rp2, *rbest;
+ int nbest, n;
+ int i;
+
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ nbest = 0;
+ rbest = 0;
+
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( ap->type!=REDUCE ) continue;
+ rp = ap->x.rp;
+ if( rp==rbest ) continue;
+ n = 1;
+ for(ap2=ap->next; ap2; ap2=ap2->next){
+ if( ap2->type!=REDUCE ) continue;
+ rp2 = ap2->x.rp;
+ if( rp2==rbest ) continue;
+ if( rp2==rp ) n++;
+ }
+ if( n>nbest ){
+ nbest = n;
+ rbest = rp;
+ }
+ }
+
+ /* Do not make a default if the number of rules to default
+ ** is not at least 2 */
+ if( nbest<2 ) continue;
+
+
+ /* Combine matching REDUCE actions into a single default */
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( ap->type==REDUCE && ap->x.rp==rbest ) break;
+ }
+ assert( ap );
+ ap->sp = Symbol_new("{default}");
+ for(ap=ap->next; ap; ap=ap->next){
+ if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED;
+ }
+ stp->ap = Action_sort(stp->ap);
+ }
+}
+
+/***************** From the file "set.c" ************************************/
+/*
+** Set manipulation routines for the LEMON parser generator.
+*/
+
+static int global_size = 0;
+
+/* Set the set size */
+void SetSize(n)
+int n;
+{
+ global_size = n+1;
+}
+
+/* Allocate a new set */
+char *SetNew(){
+ char *s;
+ int i;
+ s = (char*)malloc( global_size );
+ if( s==0 ){
+ extern void memory_error();
+ memory_error();
+ }
+ for(i=0; i<global_size; i++) s[i] = 0;
+ return s;
+}
+
+/* Deallocate a set */
+void SetFree(s)
+char *s;
+{
+ free(s);
+}
+
+/* Add a new element to the set. Return TRUE if the element was added
+** and FALSE if it was already there. */
+int SetAdd(s,e)
+char *s;
+int e;
+{
+ int rv;
+ rv = s[e];
+ s[e] = 1;
+ return !rv;
+}
+
+/* Add every element of s2 to s1. Return TRUE if s1 changes. */
+int SetUnion(s1,s2)
+char *s1;
+char *s2;
+{
+ int i, progress;
+ progress = 0;
+ for(i=0; i<global_size; i++){
+ if( s2[i]==0 ) continue;
+ if( s1[i]==0 ){
+ progress = 1;
+ s1[i] = 1;
+ }
+ }
+ return progress;
+}
+/********************** From the file "table.c" ****************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+** "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file! Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+
+PRIVATE int strhash(x)
+char *x;
+{
+ int h = 0;
+ while( *x) h = h*13 + *(x++);
+ return h;
+}
+
+/* Works like strdup, sort of. Save a string in malloced memory, but
+** keep strings in a table so that the same string is not in more
+** than one place.
+*/
+char *Strsafe(y)
+char *y;
+{
+ char *z;
+
+ z = Strsafe_find(y);
+ if( z==0 && (z=malloc( strlen(y)+1 ))!=0 ){
+ strcpy(z,y);
+ Strsafe_insert(z);
+ }
+ MemoryCheck(z);
+ return z;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x1".
+*/
+struct s_x1 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x1node *tbl; /* The data stored here */
+ struct s_x1node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x1".
+*/
+typedef struct s_x1node {
+ char *data; /* The data */
+ struct s_x1node *next; /* Next entry with the same hash */
+ struct s_x1node **from; /* Previous link */
+} x1node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x1 *x1a;
+
+/* Allocate a new associative array */
+void Strsafe_init(){
+ if( x1a ) return;
+ x1a = (struct s_x1*)malloc( sizeof(struct s_x1) );
+ if( x1a ){
+ x1a->size = 1024;
+ x1a->count = 0;
+ x1a->tbl = (x1node*)malloc(
+ (sizeof(x1node) + sizeof(x1node*))*1024 );
+ if( x1a->tbl==0 ){
+ free(x1a);
+ x1a = 0;
+ }else{
+ int i;
+ x1a->ht = (x1node**)&(x1a->tbl[1024]);
+ for(i=0; i<1024; i++) x1a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Strsafe_insert(data)
+char *data;
+{
+ x1node *np;
+ int h;
+ int ph;
+
+ if( x1a==0 ) return 0;
+ ph = strhash(data);
+ h = ph & (x1a->size-1);
+ np = x1a->ht[h];
+ while( np ){
+ if( strcmp(np->data,data)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x1a->count>=x1a->size ){
+ /* Need to make the hash table bigger */
+ int i,size;
+ struct s_x1 array;
+ array.size = size = x1a->size*2;
+ array.count = x1a->count;
+ array.tbl = (x1node*)malloc(
+ (sizeof(x1node) + sizeof(x1node*))*size );
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x1node**)&(array.tbl[size]);
+ for(i=0; i<size; i++) array.ht[i] = 0;
+ for(i=0; i<x1a->count; i++){
+ x1node *oldnp, *newnp;
+ oldnp = &(x1a->tbl[i]);
+ h = strhash(oldnp->data) & (size-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x1a->tbl);
+ *x1a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x1a->size-1);
+ np = &(x1a->tbl[x1a->count++]);
+ np->data = data;
+ if( x1a->ht[h] ) x1a->ht[h]->from = &(np->next);
+ np->next = x1a->ht[h];
+ x1a->ht[h] = np;
+ np->from = &(x1a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+char *Strsafe_find(key)
+char *key;
+{
+ int h;
+ x1node *np;
+
+ if( x1a==0 ) return 0;
+ h = strhash(key) & (x1a->size-1);
+ np = x1a->ht[h];
+ while( np ){
+ if( strcmp(np->data,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Return a pointer to the (terminal or nonterminal) symbol "x".
+** Create a new symbol if this is the first time "x" has been seen.
+*/
+struct symbol *Symbol_new(x)
+char *x;
+{
+ struct symbol *sp;
+
+ sp = Symbol_find(x);
+ if( sp==0 ){
+ sp = (struct symbol *)malloc( sizeof(struct symbol) );
+ MemoryCheck(sp);
+ sp->name = Strsafe(x);
+ sp->type = isupper(*x) ? TERMINAL : NONTERMINAL;
+ sp->rule = 0;
+ sp->fallback = 0;
+ sp->prec = -1;
+ sp->assoc = UNK;
+ sp->firstset = 0;
+ sp->lambda = Bo_FALSE;
+ sp->destructor = 0;
+ sp->datatype = 0;
+ Symbol_insert(sp,sp->name);
+ }
+ return sp;
+}
+
+/* Compare two symbols for working purposes
+**
+** Symbols that begin with upper case letters (terminals or tokens)
+** must sort before symbols that begin with lower case letters
+** (non-terminals). Other than that, the order does not matter.
+**
+** We find experimentally that leaving the symbols in their original
+** order (the order they appeared in the grammar file) gives the
+** smallest parser tables in SQLite.
+*/
+int Symbolcmpp(struct symbol **a, struct symbol **b){
+ int i1 = (**a).index + 10000000*((**a).name[0]>'Z');
+ int i2 = (**b).index + 10000000*((**b).name[0]>'Z');
+ return i1-i2;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x2".
+*/
+struct s_x2 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x2node *tbl; /* The data stored here */
+ struct s_x2node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x2".
+*/
+typedef struct s_x2node {
+ struct symbol *data; /* The data */
+ char *key; /* The key */
+ struct s_x2node *next; /* Next entry with the same hash */
+ struct s_x2node **from; /* Previous link */
+} x2node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x2 *x2a;
+
+/* Allocate a new associative array */
+void Symbol_init(){
+ if( x2a ) return;
+ x2a = (struct s_x2*)malloc( sizeof(struct s_x2) );
+ if( x2a ){
+ x2a->size = 128;
+ x2a->count = 0;
+ x2a->tbl = (x2node*)malloc(
+ (sizeof(x2node) + sizeof(x2node*))*128 );
+ if( x2a->tbl==0 ){
+ free(x2a);
+ x2a = 0;
+ }else{
+ int i;
+ x2a->ht = (x2node**)&(x2a->tbl[128]);
+ for(i=0; i<128; i++) x2a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Symbol_insert(data,key)
+struct symbol *data;
+char *key;
+{
+ x2node *np;
+ int h;
+ int ph;
+
+ if( x2a==0 ) return 0;
+ ph = strhash(key);
+ h = ph & (x2a->size-1);
+ np = x2a->ht[h];
+ while( np ){
+ if( strcmp(np->key,key)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x2a->count>=x2a->size ){
+ /* Need to make the hash table bigger */
+ int i,size;
+ struct s_x2 array;
+ array.size = size = x2a->size*2;
+ array.count = x2a->count;
+ array.tbl = (x2node*)malloc(
+ (sizeof(x2node) + sizeof(x2node*))*size );
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x2node**)&(array.tbl[size]);
+ for(i=0; i<size; i++) array.ht[i] = 0;
+ for(i=0; i<x2a->count; i++){
+ x2node *oldnp, *newnp;
+ oldnp = &(x2a->tbl[i]);
+ h = strhash(oldnp->key) & (size-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->key = oldnp->key;
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x2a->tbl);
+ *x2a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x2a->size-1);
+ np = &(x2a->tbl[x2a->count++]);
+ np->key = key;
+ np->data = data;
+ if( x2a->ht[h] ) x2a->ht[h]->from = &(np->next);
+ np->next = x2a->ht[h];
+ x2a->ht[h] = np;
+ np->from = &(x2a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+struct symbol *Symbol_find(key)
+char *key;
+{
+ int h;
+ x2node *np;
+
+ if( x2a==0 ) return 0;
+ h = strhash(key) & (x2a->size-1);
+ np = x2a->ht[h];
+ while( np ){
+ if( strcmp(np->key,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Return the n-th data. Return NULL if n is out of range. */
+struct symbol *Symbol_Nth(n)
+int n;
+{
+ struct symbol *data;
+ if( x2a && n>0 && n<=x2a->count ){
+ data = x2a->tbl[n-1].data;
+ }else{
+ data = 0;
+ }
+ return data;
+}
+
+/* Return the size of the array */
+int Symbol_count()
+{
+ return x2a ? x2a->count : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc. Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct symbol **Symbol_arrayof()
+{
+ struct symbol **array;
+ int i,size;
+ if( x2a==0 ) return 0;
+ size = x2a->count;
+ array = (struct symbol **)malloc( sizeof(struct symbol *)*size );
+ if( array ){
+ for(i=0; i<size; i++) array[i] = x2a->tbl[i].data;
+ }
+ return array;
+}
+
+/* Compare two configurations */
+int Configcmp(a,b)
+struct config *a;
+struct config *b;
+{
+ int x;
+ x = a->rp->index - b->rp->index;
+ if( x==0 ) x = a->dot - b->dot;
+ return x;
+}
+
+/* Compare two states */
+PRIVATE int statecmp(a,b)
+struct config *a;
+struct config *b;
+{
+ int rc;
+ for(rc=0; rc==0 && a && b; a=a->bp, b=b->bp){
+ rc = a->rp->index - b->rp->index;
+ if( rc==0 ) rc = a->dot - b->dot;
+ }
+ if( rc==0 ){
+ if( a ) rc = 1;
+ if( b ) rc = -1;
+ }
+ return rc;
+}
+
+/* Hash a state */
+PRIVATE int statehash(a)
+struct config *a;
+{
+ int h=0;
+ while( a ){
+ h = h*571 + a->rp->index*37 + a->dot;
+ a = a->bp;
+ }
+ return h;
+}
+
+/* Allocate a new state structure */
+struct state *State_new()
+{
+ struct state *new;
+ new = (struct state *)malloc( sizeof(struct state) );
+ MemoryCheck(new);
+ return new;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x3".
+*/
+struct s_x3 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x3node *tbl; /* The data stored here */
+ struct s_x3node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x3".
+*/
+typedef struct s_x3node {
+ struct state *data; /* The data */
+ struct config *key; /* The key */
+ struct s_x3node *next; /* Next entry with the same hash */
+ struct s_x3node **from; /* Previous link */
+} x3node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x3 *x3a;
+
+/* Allocate a new associative array */
+void State_init(){
+ if( x3a ) return;
+ x3a = (struct s_x3*)malloc( sizeof(struct s_x3) );
+ if( x3a ){
+ x3a->size = 128;
+ x3a->count = 0;
+ x3a->tbl = (x3node*)malloc(
+ (sizeof(x3node) + sizeof(x3node*))*128 );
+ if( x3a->tbl==0 ){
+ free(x3a);
+ x3a = 0;
+ }else{
+ int i;
+ x3a->ht = (x3node**)&(x3a->tbl[128]);
+ for(i=0; i<128; i++) x3a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int State_insert(data,key)
+struct state *data;
+struct config *key;
+{
+ x3node *np;
+ int h;
+ int ph;
+
+ if( x3a==0 ) return 0;
+ ph = statehash(key);
+ h = ph & (x3a->size-1);
+ np = x3a->ht[h];
+ while( np ){
+ if( statecmp(np->key,key)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x3a->count>=x3a->size ){
+ /* Need to make the hash table bigger */
+ int i,size;
+ struct s_x3 array;
+ array.size = size = x3a->size*2;
+ array.count = x3a->count;
+ array.tbl = (x3node*)malloc(
+ (sizeof(x3node) + sizeof(x3node*))*size );
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x3node**)&(array.tbl[size]);
+ for(i=0; i<size; i++) array.ht[i] = 0;
+ for(i=0; i<x3a->count; i++){
+ x3node *oldnp, *newnp;
+ oldnp = &(x3a->tbl[i]);
+ h = statehash(oldnp->key) & (size-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->key = oldnp->key;
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x3a->tbl);
+ *x3a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x3a->size-1);
+ np = &(x3a->tbl[x3a->count++]);
+ np->key = key;
+ np->data = data;
+ if( x3a->ht[h] ) x3a->ht[h]->from = &(np->next);
+ np->next = x3a->ht[h];
+ x3a->ht[h] = np;
+ np->from = &(x3a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+struct state *State_find(key)
+struct config *key;
+{
+ int h;
+ x3node *np;
+
+ if( x3a==0 ) return 0;
+ h = statehash(key) & (x3a->size-1);
+ np = x3a->ht[h];
+ while( np ){
+ if( statecmp(np->key,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc. Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct state **State_arrayof()
+{
+ struct state **array;
+ int i,size;
+ if( x3a==0 ) return 0;
+ size = x3a->count;
+ array = (struct state **)malloc( sizeof(struct state *)*size );
+ if( array ){
+ for(i=0; i<size; i++) array[i] = x3a->tbl[i].data;
+ }
+ return array;
+}
+
+/* Hash a configuration */
+PRIVATE int confighash(a)
+struct config *a;
+{
+ int h=0;
+ h = h*571 + a->rp->index*37 + a->dot;
+ return h;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x4".
+*/
+struct s_x4 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x4node *tbl; /* The data stored here */
+ struct s_x4node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x4".
+*/
+typedef struct s_x4node {
+ struct config *data; /* The data */
+ struct s_x4node *next; /* Next entry with the same hash */
+ struct s_x4node **from; /* Previous link */
+} x4node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x4 *x4a;
+
+/* Allocate a new associative array */
+void Configtable_init(){
+ if( x4a ) return;
+ x4a = (struct s_x4*)malloc( sizeof(struct s_x4) );
+ if( x4a ){
+ x4a->size = 64;
+ x4a->count = 0;
+ x4a->tbl = (x4node*)malloc(
+ (sizeof(x4node) + sizeof(x4node*))*64 );
+ if( x4a->tbl==0 ){
+ free(x4a);
+ x4a = 0;
+ }else{
+ int i;
+ x4a->ht = (x4node**)&(x4a->tbl[64]);
+ for(i=0; i<64; i++) x4a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Configtable_insert(data)
+struct config *data;
+{
+ x4node *np;
+ int h;
+ int ph;
+
+ if( x4a==0 ) return 0;
+ ph = confighash(data);
+ h = ph & (x4a->size-1);
+ np = x4a->ht[h];
+ while( np ){
+ if( Configcmp(np->data,data)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x4a->count>=x4a->size ){
+ /* Need to make the hash table bigger */
+ int i,size;
+ struct s_x4 array;
+ array.size = size = x4a->size*2;
+ array.count = x4a->count;
+ array.tbl = (x4node*)malloc(
+ (sizeof(x4node) + sizeof(x4node*))*size );
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x4node**)&(array.tbl[size]);
+ for(i=0; i<size; i++) array.ht[i] = 0;
+ for(i=0; i<x4a->count; i++){
+ x4node *oldnp, *newnp;
+ oldnp = &(x4a->tbl[i]);
+ h = confighash(oldnp->data) & (size-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x4a->tbl);
+ *x4a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x4a->size-1);
+ np = &(x4a->tbl[x4a->count++]);
+ np->data = data;
+ if( x4a->ht[h] ) x4a->ht[h]->from = &(np->next);
+ np->next = x4a->ht[h];
+ x4a->ht[h] = np;
+ np->from = &(x4a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+struct config *Configtable_find(key)
+struct config *key;
+{
+ int h;
+ x4node *np;
+
+ if( x4a==0 ) return 0;
+ h = confighash(key) & (x4a->size-1);
+ np = x4a->ht[h];
+ while( np ){
+ if( Configcmp(np->data,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Remove all data from the table. Pass each data to the function "f"
+** as it is removed. ("f" may be null to avoid this step.) */
+void Configtable_clear(f)
+int(*f)(/* struct config * */);
+{
+ int i;
+ if( x4a==0 || x4a->count==0 ) return;
+ if( f ) for(i=0; i<x4a->count; i++) (*f)(x4a->tbl[i].data);
+ for(i=0; i<x4a->size; i++) x4a->ht[i] = 0;
+ x4a->count = 0;
+ return;
+}
diff --git a/src/lempar.c b/src/lempar.c
new file mode 100644
index 0000000..ee1edbf
--- /dev/null
+++ b/src/lempar.c
@@ -0,0 +1,687 @@
+/* Driver template for the LEMON parser generator.
+** The author disclaims copyright to this source code.
+*/
+/* First off, code is include which follows the "include" declaration
+** in the input file. */
+#include <stdio.h>
+%%
+/* Next is all token values, in a form suitable for use by makeheaders.
+** This section will be null unless lemon is run with the -m switch.
+*/
+/*
+** These constants (all generated automatically by the parser generator)
+** specify the various kinds of tokens (terminals) that the parser
+** understands.
+**
+** Each symbol here is a terminal symbol in the grammar.
+*/
+%%
+/* Make sure the INTERFACE macro is defined.
+*/
+#ifndef INTERFACE
+# define INTERFACE 1
+#endif
+/* The next thing included is series of defines which control
+** various aspects of the generated parser.
+** YYCODETYPE is the data type used for storing terminal
+** and nonterminal numbers. "unsigned char" is
+** used if there are fewer than 250 terminals
+** and nonterminals. "int" is used otherwise.
+** YYNOCODE is a number of type YYCODETYPE which corresponds
+** to no legal terminal or nonterminal number. This
+** number is used to fill in empty slots of the hash
+** table.
+** YYFALLBACK If defined, this indicates that one or more tokens
+** have fall-back values which should be used if the
+** original value of the token will not parse.
+** YYACTIONTYPE is the data type used for storing terminal
+** and nonterminal numbers. "unsigned char" is
+** used if there are fewer than 250 rules and
+** states combined. "int" is used otherwise.
+** ParseTOKENTYPE is the data type used for minor tokens given
+** directly to the parser from the tokenizer.
+** YYMINORTYPE is the data type used for all minor tokens.
+** This is typically a union of many types, one of
+** which is ParseTOKENTYPE. The entry in the union
+** for base tokens is called "yy0".
+** YYSTACKDEPTH is the maximum depth of the parser's stack.
+** ParseARG_SDECL A static variable declaration for the %extra_argument
+** ParseARG_PDECL A parameter declaration for the %extra_argument
+** ParseARG_STORE Code to store %extra_argument into yypParser
+** ParseARG_FETCH Code to extract %extra_argument from yypParser
+** YYNSTATE the combined number of states.
+** YYNRULE the number of rules in the grammar
+** YYERRORSYMBOL is the code number of the error symbol. If not
+** defined, then do no error processing.
+*/
+%%
+#define YY_NO_ACTION (YYNSTATE+YYNRULE+2)
+#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1)
+#define YY_ERROR_ACTION (YYNSTATE+YYNRULE)
+
+/* Next are that tables used to determine what action to take based on the
+** current state and lookahead token. These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.
+**
+** Suppose the action integer is N. Then the action is determined as
+** follows
+**
+** 0 <= N < YYNSTATE Shift N. That is, push the lookahead
+** token onto the stack and goto state N.
+**
+** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE.
+**
+** N == YYNSTATE+YYNRULE A syntax error has occurred.
+**
+** N == YYNSTATE+YYNRULE+1 The parser accepts its input.
+**
+** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused
+** slots in the yy_action[] table.
+**
+** The action table is constructed as a single large table named yy_action[].
+** Given state S and lookahead X, the action is computed as
+**
+** yy_action[ yy_shift_ofst[S] + X ]
+**
+** If the index value yy_shift_ofst[S]+X is out of range or if the value
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S]
+** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table
+** and that yy_default[S] should be used instead.
+**
+** The formula above is for computing the action when the lookahead is
+** a terminal symbol. If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the yy_reduce_ofst[] array is used in place of
+** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of
+** YY_SHIFT_USE_DFLT.
+**
+** The following are the tables generated in this section:
+**
+** yy_action[] A single table containing all actions.
+** yy_lookahead[] A table containing the lookahead for each entry in
+** yy_action. Used to detect hash collisions.
+** yy_shift_ofst[] For each state, the offset into yy_action for
+** shifting terminals.
+** yy_reduce_ofst[] For each state, the offset into yy_action for
+** shifting non-terminals after a reduce.
+** yy_default[] Default action for each state.
+*/
+%%
+#define YY_SZ_ACTTAB (sizeof(yy_action)/sizeof(yy_action[0]))
+
+/* The next table maps tokens into fallback tokens. If a construct
+** like the following:
+**
+** %fallback ID X Y Z.
+**
+** appears in the grammer, then ID becomes a fallback token for X, Y,
+** and Z. Whenever one of the tokens X, Y, or Z is input to the parser
+** but it does not parse, the type of the token is changed to ID and
+** the parse is retried before an error is thrown.
+*/
+#ifdef YYFALLBACK
+static const YYCODETYPE yyFallback[] = {
+%%
+};
+#endif /* YYFALLBACK */
+
+/* The following structure represents a single element of the
+** parser's stack. Information stored includes:
+**
+** + The state number for the parser at this level of the stack.
+**
+** + The value of the token stored at this level of the stack.
+** (In other words, the "major" token.)
+**
+** + The semantic value stored at this level of the stack. This is
+** the information used by the action routines in the grammar.
+** It is sometimes called the "minor" token.
+*/
+struct yyStackEntry {
+ int stateno; /* The state-number */
+ int major; /* The major token value. This is the code
+ ** number for the token at this stack level */
+ YYMINORTYPE minor; /* The user-supplied minor token value. This
+ ** is the value of the token */
+};
+typedef struct yyStackEntry yyStackEntry;
+
+/* The state of the parser is completely contained in an instance of
+** the following structure */
+struct yyParser {
+ int yyidx; /* Index of top element in stack */
+ int yyerrcnt; /* Shifts left before out of the error */
+ ParseARG_SDECL /* A place to hold %extra_argument */
+ yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */
+};
+typedef struct yyParser yyParser;
+
+#ifndef NDEBUG
+#include <stdio.h>
+static FILE *yyTraceFILE = 0;
+static char *yyTracePrompt = 0;
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/*
+** Turn parser tracing on by giving a stream to which to write the trace
+** and a prompt to preface each trace message. Tracing is turned off
+** by making either argument NULL
+**
+** Inputs:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+** If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+** line of trace output. If NULL, then tracing is
+** turned off.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void ParseTrace(FILE *TraceFILE, char *zTracePrompt){
+ yyTraceFILE = TraceFILE;
+ yyTracePrompt = zTracePrompt;
+ if( yyTraceFILE==0 ) yyTracePrompt = 0;
+ else if( yyTracePrompt==0 ) yyTraceFILE = 0;
+}
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing shifts, the names of all terminals and nonterminals
+** are required. The following table supplies these names */
+static const char *yyTokenName[] = {
+%%
+};
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *yyRuleName[] = {
+%%
+};
+#endif /* NDEBUG */
+
+/*
+** This function returns the symbolic name associated with a token
+** value.
+*/
+const char *ParseTokenName(int tokenType){
+#ifndef NDEBUG
+ if( tokenType>0 && tokenType<(sizeof(yyTokenName)/sizeof(yyTokenName[0])) ){
+ return yyTokenName[tokenType];
+ }else{
+ return "Unknown";
+ }
+#else
+ return "";
+#endif
+}
+
+/*
+** This function allocates a new parser.
+** The only argument is a pointer to a function which works like
+** malloc.
+**
+** Inputs:
+** A pointer to the function used to allocate memory.
+**
+** Outputs:
+** A pointer to a parser. This pointer is used in subsequent calls
+** to Parse and ParseFree.
+*/
+void *ParseAlloc(void *(*mallocProc)(size_t)){
+ yyParser *pParser;
+ pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) );
+ if( pParser ){
+ pParser->yyidx = -1;
+ }
+ return pParser;
+}
+
+/* The following function deletes the value associated with a
+** symbol. The symbol can be either a terminal or nonterminal.
+** "yymajor" is the symbol code, and "yypminor" is a pointer to
+** the value.
+*/
+static void yy_destructor(YYCODETYPE yymajor, YYMINORTYPE *yypminor){
+ switch( yymajor ){
+ /* Here is inserted the actions which take place when a
+ ** terminal or non-terminal is destroyed. This can happen
+ ** when the symbol is popped from the stack during a
+ ** reduce or during error processing or when a parser is
+ ** being destroyed before it is finished parsing.
+ **
+ ** Note: during a reduce, the only symbols destroyed are those
+ ** which appear on the RHS of the rule, but which are not used
+ ** inside the C code.
+ */
+%%
+ default: break; /* If no destructor action specified: do nothing */
+ }
+}
+
+/*
+** Pop the parser's stack once.
+**
+** If there is a destructor routine associated with the token which
+** is popped from the stack, then call it.
+**
+** Return the major token number for the symbol popped.
+*/
+static int yy_pop_parser_stack(yyParser *pParser){
+ YYCODETYPE yymajor;
+ yyStackEntry *yytos = &pParser->yystack[pParser->yyidx];
+
+ if( pParser->yyidx<0 ) return 0;
+#ifndef NDEBUG
+ if( yyTraceFILE && pParser->yyidx>=0 ){
+ fprintf(yyTraceFILE,"%sPopping %s\n",
+ yyTracePrompt,
+ yyTokenName[yytos->major]);
+ }
+#endif
+ yymajor = yytos->major;
+ yy_destructor( yymajor, &yytos->minor);
+ pParser->yyidx--;
+ return yymajor;
+}
+
+/*
+** Deallocate and destroy a parser. Destructors are all called for
+** all stack elements before shutting the parser down.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser. This should be a pointer
+** obtained from ParseAlloc.
+** <li> A pointer to a function used to reclaim memory obtained
+** from malloc.
+** </ul>
+*/
+void ParseFree(
+ void *p, /* The parser to be deleted */
+ void (*freeProc)(void*) /* Function used to reclaim memory */
+){
+ yyParser *pParser = (yyParser*)p;
+ if( pParser==0 ) return;
+ while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser);
+ (*freeProc)((void*)pParser);
+}
+
+/*
+** Find the appropriate action for a parser given the terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead. If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_shift_action(
+ yyParser *pParser, /* The parser */
+ int iLookAhead /* The look-ahead token */
+){
+ int i;
+ int stateno = pParser->yystack[pParser->yyidx].stateno;
+
+ /* if( pParser->yyidx<0 ) return YY_NO_ACTION; */
+ i = yy_shift_ofst[stateno];
+ if( i==YY_SHIFT_USE_DFLT ){
+ return yy_default[stateno];
+ }
+ if( iLookAhead==YYNOCODE ){
+ return YY_NO_ACTION;
+ }
+ i += iLookAhead;
+ if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+#ifdef YYFALLBACK
+ int iFallback; /* Fallback token */
+ if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
+ && (iFallback = yyFallback[iLookAhead])!=0 ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
+ yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
+ }
+#endif
+ return yy_find_shift_action(pParser, iFallback);
+ }
+#endif
+ return yy_default[stateno];
+ }else{
+ return yy_action[i];
+ }
+}
+
+/*
+** Find the appropriate action for a parser given the non-terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead. If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_reduce_action(
+ yyParser *pParser, /* The parser */
+ int iLookAhead /* The look-ahead token */
+){
+ int i;
+ int stateno = pParser->yystack[pParser->yyidx].stateno;
+
+ i = yy_reduce_ofst[stateno];
+ if( i==YY_REDUCE_USE_DFLT ){
+ return yy_default[stateno];
+ }
+ if( iLookAhead==YYNOCODE ){
+ return YY_NO_ACTION;
+ }
+ i += iLookAhead;
+ if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+ return yy_default[stateno];
+ }else{
+ return yy_action[i];
+ }
+}
+
+/*
+** Perform a shift action.
+*/
+static void yy_shift(
+ yyParser *yypParser, /* The parser to be shifted */
+ int yyNewState, /* The new state to shift in */
+ int yyMajor, /* The major token to shift in */
+ YYMINORTYPE *yypMinor /* Pointer ot the minor token to shift in */
+){
+ yyStackEntry *yytos;
+ yypParser->yyidx++;
+ if( yypParser->yyidx>=YYSTACKDEPTH ){
+ ParseARG_FETCH;
+ yypParser->yyidx--;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will execute if the parser
+ ** stack every overflows */
+%%
+ ParseARG_STORE; /* Suppress warning about unused %extra_argument var */
+ return;
+ }
+ yytos = &yypParser->yystack[yypParser->yyidx];
+ yytos->stateno = yyNewState;
+ yytos->major = yyMajor;
+ yytos->minor = *yypMinor;
+#ifndef NDEBUG
+ if( yyTraceFILE && yypParser->yyidx>0 ){
+ int i;
+ fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState);
+ fprintf(yyTraceFILE,"%sStack:",yyTracePrompt);
+ for(i=1; i<=yypParser->yyidx; i++)
+ fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]);
+ fprintf(yyTraceFILE,"\n");
+ }
+#endif
+}
+
+/* The following table contains information about every rule that
+** is used during the reduce.
+*/
+static struct {
+ YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */
+ unsigned char nrhs; /* Number of right-hand side symbols in the rule */
+} yyRuleInfo[] = {
+%%
+};
+
+static void yy_accept(yyParser*); /* Forward Declaration */
+
+/*
+** Perform a reduce action and the shift that must immediately
+** follow the reduce.
+*/
+static void yy_reduce(
+ yyParser *yypParser, /* The parser */
+ int yyruleno /* Number of the rule by which to reduce */
+){
+ int yygoto; /* The next state */
+ int yyact; /* The next action */
+ YYMINORTYPE yygotominor; /* The LHS of the rule reduced */
+ yyStackEntry *yymsp; /* The top of the parser's stack */
+ int yysize; /* Amount to pop the stack */
+ ParseARG_FETCH;
+ yymsp = &yypParser->yystack[yypParser->yyidx];
+#ifndef NDEBUG
+ if( yyTraceFILE && yyruleno>=0
+ && yyruleno<sizeof(yyRuleName)/sizeof(yyRuleName[0]) ){
+ fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt,
+ yyRuleName[yyruleno]);
+ }
+#endif /* NDEBUG */
+
+ switch( yyruleno ){
+ /* Beginning here are the reduction cases. A typical example
+ ** follows:
+ ** case 0:
+ ** #line <lineno> <grammarfile>
+ ** { ... } // User supplied code
+ ** #line <lineno> <thisfile>
+ ** break;
+ */
+%%
+ };
+ yygoto = yyRuleInfo[yyruleno].lhs;
+ yysize = yyRuleInfo[yyruleno].nrhs;
+ yypParser->yyidx -= yysize;
+ yyact = yy_find_reduce_action(yypParser,yygoto);
+ if( yyact < YYNSTATE ){
+ yy_shift(yypParser,yyact,yygoto,&yygotominor);
+ }else if( yyact == YYNSTATE + YYNRULE + 1 ){
+ yy_accept(yypParser);
+ }
+}
+
+/*
+** The following code executes when the parse fails
+*/
+static void yy_parse_failed(
+ yyParser *yypParser /* The parser */
+){
+ ParseARG_FETCH;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser fails */
+%%
+ ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following code executes when a syntax error first occurs.
+*/
+static void yy_syntax_error(
+ yyParser *yypParser, /* The parser */
+ int yymajor, /* The major type of the error token */
+ YYMINORTYPE yyminor /* The minor type of the error token */
+){
+ ParseARG_FETCH;
+#define TOKEN (yyminor.yy0)
+%%
+ ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+ yyParser *yypParser /* The parser */
+){
+ ParseARG_FETCH;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser accepts */
+%%
+ ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "ParseAlloc" which describes the current state of the parser.
+** The second argument is the major token number. The third is
+** the minor token. The fourth optional argument is whatever the
+** user wants (and specified in the grammar) and is available for
+** use by the action routines.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void Parse(
+ void *yyp, /* The parser */
+ int yymajor, /* The major token code number */
+ ParseTOKENTYPE yyminor /* The value for the token */
+ ParseARG_PDECL /* Optional %extra_argument parameter */
+){
+ YYMINORTYPE yyminorunion;
+ int yyact; /* The parser action. */
+ int yyendofinput; /* True if we are at the end of input */
+ int yyerrorhit = 0; /* True if yymajor has invoked an error */
+ yyParser *yypParser; /* The parser */
+
+ /* (re)initialize the parser, if necessary */
+ yypParser = (yyParser*)yyp;
+ if( yypParser->yyidx<0 ){
+ if( yymajor==0 ) return;
+ yypParser->yyidx = 0;
+ yypParser->yyerrcnt = -1;
+ yypParser->yystack[0].stateno = 0;
+ yypParser->yystack[0].major = 0;
+ }
+ yyminorunion.yy0 = yyminor;
+ yyendofinput = (yymajor==0);
+ ParseARG_STORE;
+
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+
+ do{
+ yyact = yy_find_shift_action(yypParser,yymajor);
+ if( yyact<YYNSTATE ){
+ yy_shift(yypParser,yyact,yymajor,&yyminorunion);
+ yypParser->yyerrcnt--;
+ if( yyendofinput && yypParser->yyidx>=0 ){
+ yymajor = 0;
+ }else{
+ yymajor = YYNOCODE;
+ }
+ }else if( yyact < YYNSTATE + YYNRULE ){
+ yy_reduce(yypParser,yyact-YYNSTATE);
+ }else if( yyact == YY_ERROR_ACTION ){
+ int yymx;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
+ }
+#endif
+#ifdef YYERRORSYMBOL
+ /* A syntax error has occurred.
+ ** The response to an error depends upon whether or not the
+ ** grammar defines an error token "ERROR".
+ **
+ ** This is what we do if the grammar does define ERROR:
+ **
+ ** * Call the %syntax_error function.
+ **
+ ** * Begin popping the stack until we enter a state where
+ ** it is legal to shift the error symbol, then shift
+ ** the error symbol.
+ **
+ ** * Set the error count to three.
+ **
+ ** * Begin accepting and shifting new tokens. No new error
+ ** processing will occur until three tokens have been
+ ** shifted successfully.
+ **
+ */
+ if( yypParser->yyerrcnt<0 ){
+ yy_syntax_error(yypParser,yymajor,yyminorunion);
+ }
+ yymx = yypParser->yystack[yypParser->yyidx].major;
+ if( yymx==YYERRORSYMBOL || yyerrorhit ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sDiscard input token %s\n",
+ yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+ yy_destructor(yymajor,&yyminorunion);
+ yymajor = YYNOCODE;
+ }else{
+ while(
+ yypParser->yyidx >= 0 &&
+ yymx != YYERRORSYMBOL &&
+ (yyact = yy_find_shift_action(yypParser,YYERRORSYMBOL)) >= YYNSTATE
+ ){
+ yy_pop_parser_stack(yypParser);
+ }
+ if( yypParser->yyidx < 0 || yymajor==0 ){
+ yy_destructor(yymajor,&yyminorunion);
+ yy_parse_failed(yypParser);
+ yymajor = YYNOCODE;
+ }else if( yymx!=YYERRORSYMBOL ){
+ YYMINORTYPE u2;
+ u2.YYERRSYMDT = 0;
+ yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2);
+ }
+ }
+ yypParser->yyerrcnt = 3;
+ yyerrorhit = 1;
+#else /* YYERRORSYMBOL is not defined */
+ /* This is what we do if the grammar does not define ERROR:
+ **
+ ** * Report an error message, and throw away the input token.
+ **
+ ** * If the input token is $, then fail the parse.
+ **
+ ** As before, subsequent error messages are suppressed until
+ ** three input tokens have been successfully shifted.
+ */
+ if( yypParser->yyerrcnt<=0 ){
+ yy_syntax_error(yypParser,yymajor,yyminorunion);
+ }
+ yypParser->yyerrcnt = 3;
+ yy_destructor(yymajor,&yyminorunion);
+ if( yyendofinput ){
+ yy_parse_failed(yypParser);
+ }
+ yymajor = YYNOCODE;
+#endif
+ }else{
+ yy_accept(yypParser);
+ yymajor = YYNOCODE;
+ }
+ }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 );
+ return;
+}
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..402e302
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,256 @@
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_SYSLOG_H
+#include <syslog.h>
+#endif
+
+#include "log.h"
+#include "array.h"
+
+#ifdef HAVE_VALGRIND_VALGRIND_H
+#include <valgrind/valgrind.h>
+#endif
+
+#ifndef O_LARGEFILE
+# define O_LARGEFILE 0
+#endif
+
+/**
+ * open the errorlog
+ *
+ * we have 3 possibilities:
+ * - stderr (default)
+ * - syslog
+ * - logfile
+ *
+ * if the open failed, report to the user and die
+ *
+ */
+
+int log_error_open(server *srv) {
+ int fd;
+ int close_stderr = 1;
+
+#ifdef HAVE_SYSLOG_H
+ /* perhaps someone wants to use syslog() */
+ openlog("lighttpd", LOG_CONS | LOG_PID, LOG_DAEMON);
+#endif
+ srv->errorlog_mode = ERRORLOG_STDERR;
+
+ if (srv->srvconf.errorlog_use_syslog) {
+ srv->errorlog_mode = ERRORLOG_SYSLOG;
+ } else if (!buffer_is_empty(srv->srvconf.errorlog_file)) {
+ const char *logfile = srv->srvconf.errorlog_file->ptr;
+
+ if (-1 == (srv->errorlog_fd = open(logfile, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) {
+ log_error_write(srv, __FILE__, __LINE__, "SSSS",
+ "opening errorlog '", logfile,
+ "' failed: ", strerror(errno));
+
+ return -1;
+ }
+#ifdef FD_CLOEXEC
+ /* close fd on exec (cgi) */
+ fcntl(srv->errorlog_fd, F_SETFD, FD_CLOEXEC);
+#endif
+ srv->errorlog_mode = ERRORLOG_FILE;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "server started");
+
+#ifdef HAVE_VALGRIND_VALGRIND_H
+ /* don't close stderr for debugging purposes if run in valgrind */
+ if (RUNNING_ON_VALGRIND) close_stderr = 0;
+#endif
+ if (srv->errorlog_mode == ERRORLOG_STDERR) close_stderr = 0;
+
+ /* move stderr to /dev/null */
+ if (close_stderr &&
+ -1 != (fd = open("/dev/null", O_WRONLY))) {
+ close(STDERR_FILENO);
+ dup2(fd, STDERR_FILENO);
+ close(fd);
+ }
+ return 0;
+}
+
+/**
+ * open the errorlog
+ *
+ * if the open failed, report to the user and die
+ * if no filename is given, use syslog instead
+ *
+ */
+
+int log_error_cycle(server *srv) {
+ /* only cycle if we are not in syslog-mode */
+
+ if (srv->errorlog_mode == ERRORLOG_FILE) {
+ const char *logfile = srv->srvconf.errorlog_file->ptr;
+ /* already check of opening time */
+
+ int new_fd;
+
+ if (-1 == (new_fd = open(logfile, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) {
+ /* write to old log */
+ log_error_write(srv, __FILE__, __LINE__, "SSSSS",
+ "cycling errorlog '", logfile,
+ "' failed: ", strerror(errno),
+ ", falling back to syslog()");
+
+ close(srv->errorlog_fd);
+ srv->errorlog_fd = -1;
+#ifdef HAVE_SYSLOG_H
+ srv->errorlog_mode = ERRORLOG_SYSLOG;
+#endif
+ } else {
+ /* ok, new log is open, close the old one */
+ close(srv->errorlog_fd);
+ srv->errorlog_fd = new_fd;
+ }
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "logfiles cycled");
+
+ return 0;
+}
+
+int log_error_close(server *srv) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "server stopped");
+
+ switch(srv->errorlog_mode) {
+ case ERRORLOG_FILE:
+ close(srv->errorlog_fd);
+ break;
+ case ERRORLOG_SYSLOG:
+#ifdef HAVE_SYSLOG_H
+ closelog();
+#endif
+ break;
+ case ERRORLOG_STDERR:
+ break;
+ }
+
+ return 0;
+}
+
+int log_error_write(server *srv, const char *filename, unsigned int line, const char *fmt, ...) {
+ va_list ap;
+
+ switch(srv->errorlog_mode) {
+ case ERRORLOG_FILE:
+ case ERRORLOG_STDERR:
+ /* cache the generated timestamp */
+ if (srv->cur_ts != srv->last_generated_debug_ts) {
+ buffer_prepare_copy(srv->ts_debug_str, 255);
+ strftime(srv->ts_debug_str->ptr, srv->ts_debug_str->size - 1, "%Y-%m-%d %H:%M:%S", localtime(&(srv->cur_ts)));
+ srv->ts_debug_str->used = strlen(srv->ts_debug_str->ptr) + 1;
+
+ srv->last_generated_debug_ts = srv->cur_ts;
+ }
+
+ buffer_copy_string_buffer(srv->errorlog_buf, srv->ts_debug_str);
+ BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, ": (");
+ break;
+ case ERRORLOG_SYSLOG:
+ /* syslog is generating its own timestamps */
+ BUFFER_COPY_STRING_CONST(srv->errorlog_buf, "(");
+ break;
+ }
+
+ buffer_append_string(srv->errorlog_buf, filename);
+ BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, ".");
+ buffer_append_long(srv->errorlog_buf, line);
+ BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, ") ");
+
+
+ for(va_start(ap, fmt); *fmt; fmt++) {
+ int d;
+ char *s;
+ buffer *b;
+ off_t o;
+
+ switch(*fmt) {
+ case 's': /* string */
+ s = va_arg(ap, char *);
+ buffer_append_string(srv->errorlog_buf, s);
+ BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, " ");
+ break;
+ case 'b': /* buffer */
+ b = va_arg(ap, buffer *);
+ buffer_append_string_buffer(srv->errorlog_buf, b);
+ BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, " ");
+ break;
+ case 'd': /* int */
+ d = va_arg(ap, int);
+ buffer_append_long(srv->errorlog_buf, d);
+ BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, " ");
+ break;
+ case 'o': /* off_t */
+ o = va_arg(ap, off_t);
+ buffer_append_off_t(srv->errorlog_buf, o);
+ BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, " ");
+ break;
+ case 'x': /* int (hex) */
+ d = va_arg(ap, int);
+ BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, "0x");
+ buffer_append_long_hex(srv->errorlog_buf, d);
+ BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, " ");
+ break;
+ case 'S': /* string */
+ s = va_arg(ap, char *);
+ buffer_append_string(srv->errorlog_buf, s);
+ break;
+ case 'B': /* buffer */
+ b = va_arg(ap, buffer *);
+ buffer_append_string_buffer(srv->errorlog_buf, b);
+ break;
+ case 'D': /* int */
+ d = va_arg(ap, int);
+ buffer_append_long(srv->errorlog_buf, d);
+ break;
+ case '(':
+ case ')':
+ case '<':
+ case '>':
+ case ',':
+ case ' ':
+ buffer_append_string_len(srv->errorlog_buf, fmt, 1);
+ break;
+ }
+ }
+ va_end(ap);
+
+ switch(srv->errorlog_mode) {
+ case ERRORLOG_FILE:
+ BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, "\n");
+ write(srv->errorlog_fd, srv->errorlog_buf->ptr, srv->errorlog_buf->used - 1);
+ break;
+ case ERRORLOG_STDERR:
+ BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, "\n");
+ write(STDERR_FILENO, srv->errorlog_buf->ptr, srv->errorlog_buf->used - 1);
+ break;
+ case ERRORLOG_SYSLOG:
+ syslog(LOG_ERR, "%s", srv->errorlog_buf->ptr);
+ break;
+ }
+
+ return 0;
+}
+
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..bffee3a
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,13 @@
+#ifndef _LOG_H_
+#define _LOG_H_
+
+#include "server.h"
+
+#define WP() log_error_write(srv, __FILE__, __LINE__, "");
+
+int log_error_open(server *srv);
+int log_error_close(server *srv);
+int log_error_write(server *srv, const char *filename, unsigned int line, const char *fmt, ...);
+int log_error_cycle(server *srv);
+
+#endif
diff --git a/src/md5.c b/src/md5.c
new file mode 100644
index 0000000..8b688f6
--- /dev/null
+++ b/src/md5.c
@@ -0,0 +1,355 @@
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+rights reserved.
+
+License to copy and use this software is granted provided that it
+is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+Algorithm" in all material mentioning or referencing this software
+or this function.
+
+License is also granted to make and use derivative works provided
+that such works are identified as "derived from the RSA Data
+Security, Inc. MD5 Message-Digest Algorithm" in all material
+mentioning or referencing the derived work.
+
+RSA Data Security, Inc. makes no representations concerning either
+the merchantability of this software or the suitability of this
+software for any particular purpose. It is provided "as is"
+without express or implied warranty of any kind.
+
+These notices must be retained in any copies of any part of this
+documentation and/or software.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef USE_OPENSSL
+#include <string.h>
+
+#include "md5.h"
+
+/* Constants for MD5Transform routine.
+ */
+
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+static void MD5Transform (UINT4 [4], unsigned char [64]);
+static void Encode (unsigned char *, UINT4 *, unsigned int);
+static void Decode (UINT4 *, unsigned char *, unsigned int);
+
+#ifdef HAVE_MEMCPY
+#define MD5_memcpy(output, input, len) memcpy((output), (input), (len))
+#else
+static void MD5_memcpy (POINTER, POINTER, unsigned int);
+#endif
+#ifdef HAVE_MEMSET
+#define MD5_memset(output, value, len) memset((output), (value), (len))
+#else
+static void MD5_memset (POINTER, int, unsigned int);
+#endif
+
+static unsigned char PADDING[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G, H and I are basic MD5 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context.
+ */
+void MD5_Init (context)
+MD5_CTX *context; /* context */
+{
+ context->count[0] = context->count[1] = 0;
+ /* Load magic initialization constants.
+*/
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+}
+
+/* MD5 block update operation. Continues an MD5 message-digest
+ operation, processing another message block, and updating the
+ context.
+ */
+void MD5_Update (context, input, inputLen)
+MD5_CTX *context; /* context */
+unsigned char *input; /* input block */
+unsigned int inputLen; /* length of input block */
+{
+ unsigned int i, ndx, partLen;
+
+ /* Compute number of bytes mod 64 */
+ ndx = (unsigned int)((context->count[0] >> 3) & 0x3F);
+
+ /* Update number of bits */
+ if ((context->count[0] += ((UINT4)inputLen << 3))
+
+ < ((UINT4)inputLen << 3))
+ context->count[1]++;
+ context->count[1] += ((UINT4)inputLen >> 29);
+
+ partLen = 64 - ndx;
+
+ /* Transform as many times as possible.
+*/
+ if (inputLen >= partLen) {
+ MD5_memcpy
+ ((POINTER)&context->buffer[ndx], (POINTER)input, partLen);
+ MD5Transform (context->state, context->buffer);
+
+ for (i = partLen; i + 63 < inputLen; i += 64)
+ MD5Transform (context->state, &input[i]);
+
+ ndx = 0;
+ }
+ else
+ i = 0;
+
+ /* Buffer remaining input */
+ MD5_memcpy
+ ((POINTER)&context->buffer[ndx], (POINTER)&input[i],
+ inputLen-i);
+}
+
+/* MD5 finalization. Ends an MD5 message-digest operation, writing the
+ the message digest and zeroizing the context.
+ */
+void MD5_Final (digest, context)
+unsigned char digest[16]; /* message digest */
+MD5_CTX *context; /* context */
+{
+ unsigned char bits[8];
+ unsigned int ndx, padLen;
+
+ /* Save number of bits */
+ Encode (bits, context->count, 8);
+
+ /* Pad out to 56 mod 64.
+*/
+ ndx = (unsigned int)((context->count[0] >> 3) & 0x3f);
+ padLen = (ndx < 56) ? (56 - ndx) : (120 - ndx);
+ MD5_Update (context, PADDING, padLen);
+
+ /* Append length (before padding) */
+ MD5_Update (context, bits, 8);
+
+ /* Store state in digest */
+ Encode (digest, context->state, 16);
+
+ /* Zeroize sensitive information.
+*/
+ MD5_memset ((POINTER)context, 0, sizeof (*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block.
+ */
+static void MD5Transform (state, block)
+UINT4 state[4];
+unsigned char block[64];
+{
+ UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+
+ Decode (x, block, 64);
+
+ /* Round 1 */
+ FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+ FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+ FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+ FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+ FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+ FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+ FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+ FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+ FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+ FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+ FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+ GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+ GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+ GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+ GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+ GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+ GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+ GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+
+ GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+ GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+ GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+ GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+ HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+ HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+ HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+ HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+ HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+ HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+ HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
+ HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+ HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+ HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+ II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+ II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+ II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+ II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+ II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+ II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+ II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+ II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+ II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+ II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+ /* Zeroize sensitive information.
+
+*/
+ MD5_memset ((POINTER)x, 0, sizeof (x));
+}
+
+/* Encodes input (UINT4) into output (unsigned char). Assumes len is
+ a multiple of 4.
+ */
+static void Encode (output, input, len)
+unsigned char *output;
+UINT4 *input;
+unsigned int len;
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4) {
+ output[j] = (unsigned char)(input[i] & 0xff);
+ output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
+ output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
+ output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
+ }
+}
+
+/* Decodes input (unsigned char) into output (UINT4). Assumes len is
+ a multiple of 4.
+ */
+static void Decode (output, input, len)
+UINT4 *output;
+unsigned char *input;
+unsigned int len;
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
+ (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
+}
+
+/* Note: Replace "for loop" with standard memcpy if possible.
+ */
+#ifndef HAVE_MEMCPY
+static void MD5_memcpy (output, input, len)
+POINTER output;
+POINTER input;
+unsigned int len;
+{
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+
+ output[i] = input[i];
+}
+#endif
+/* Note: Replace "for loop" with standard memset if possible.
+ */
+#ifndef HAVE_MEMSET
+static void MD5_memset (output, value, len)
+POINTER output;
+int value;
+unsigned int len;
+{
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+ ((char *)output)[i] = (char)value;
+}
+#endif
+#endif
diff --git a/src/md5.h b/src/md5.h
new file mode 100644
index 0000000..a1c0d76
--- /dev/null
+++ b/src/md5.h
@@ -0,0 +1,47 @@
+/* MD5.H - header file for MD5C.C
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+rights reserved.
+
+License to copy and use this software is granted provided that it
+is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+Algorithm" in all material mentioning or referencing this software
+or this function.
+
+License is also granted to make and use derivative works provided
+that such works are identified as "derived from the RSA Data
+Security, Inc. MD5 Message-Digest Algorithm" in all material
+mentioning or referencing the derived work.
+
+RSA Data Security, Inc. makes no representations concerning either
+the merchantability of this software or the suitability of this
+software for any particular purpose. It is provided "as is"
+without express or implied warranty of any kind.
+
+These notices must be retained in any copies of any part of this
+documentation and/or software.
+ */
+#include <limits.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+
+#define UINT4 uint32_t
+#define UINT2 uint16_t
+#define POINTER unsigned char *
+
+/* MD5 context. */
+typedef struct {
+ UINT4 state[4]; /* state (ABCD) */
+ UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */
+ unsigned char buffer[64]; /* input buffer */
+} MD5_CTX;
+
+void MD5_Init (MD5_CTX *);
+void MD5_Update (MD5_CTX *, unsigned char *, unsigned int);
+void MD5_Final (unsigned char [16], MD5_CTX *);
+
diff --git a/src/mod_access.c b/src/mod_access.c
new file mode 100644
index 0000000..f3f7071
--- /dev/null
+++ b/src/mod_access.c
@@ -0,0 +1,167 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+typedef struct {
+ array *access_deny;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_access_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+FREE_FUNC(mod_access_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->access_deny);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_access_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "url.access-deny", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->access_deny = array_init();
+
+ cv[0].destination = s->access_deny;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_access_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(access_deny);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.access-deny"))) {
+ PATCH(access_deny);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_access_uri_handler) {
+ plugin_data *p = p_d;
+ int s_len;
+ size_t k;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_access_patch_connection(srv, con, p);
+
+ s_len = con->uri.path->used - 1;
+
+ for (k = 0; k < p->conf.access_deny->used; k++) {
+ data_string *ds = (data_string *)p->conf.access_deny->data[k];
+ int ct_len = ds->value->used - 1;
+
+ if (ct_len > s_len) continue;
+
+ if (ds->value->used == 0) continue;
+
+ /* if we have a case-insensitive FS we have to lower-case the URI here too */
+
+ if (con->conf.force_lowercase_filenames) {
+ if (0 == strncasecmp(con->uri.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) {
+ con->http_status = 403;
+
+ return HANDLER_FINISHED;
+ }
+ } else {
+ if (0 == strncmp(con->uri.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) {
+ con->http_status = 403;
+
+ return HANDLER_FINISHED;
+ }
+ }
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+
+int mod_access_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("access");
+
+ p->init = mod_access_init;
+ p->set_defaults = mod_access_set_defaults;
+ p->handle_uri_clean = mod_access_uri_handler;
+ p->cleanup = mod_access_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_accesslog.c b/src/mod_accesslog.c
new file mode 100644
index 0000000..bdd0dbc
--- /dev/null
+++ b/src/mod_accesslog.c
@@ -0,0 +1,850 @@
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+
+#include <stdio.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "inet_ntop_cache.h"
+
+#include "sys-socket.h"
+
+#ifdef HAVE_SYSLOG_H
+# include <syslog.h>
+#endif
+
+typedef struct {
+ char key;
+ enum {
+ FORMAT_UNSET,
+ FORMAT_UNSUPPORTED,
+ FORMAT_PERCENT,
+ FORMAT_REMOTE_HOST,
+ FORMAT_REMOTE_IDENT,
+ FORMAT_REMOTE_USER,
+ FORMAT_TIMESTAMP,
+ FORMAT_REQUEST_LINE,
+ FORMAT_STATUS,
+ FORMAT_BYTES_OUT_NO_HEADER,
+ FORMAT_HEADER,
+
+ FORMAT_REMOTE_ADDR,
+ FORMAT_LOCAL_ADDR,
+ FORMAT_COOKIE,
+ FORMAT_TIME_USED_MS,
+ FORMAT_ENV,
+ FORMAT_FILENAME,
+ FORMAT_REQUEST_PROTOCOL,
+ FORMAT_REQUEST_METHOD,
+ FORMAT_SERVER_PORT,
+ FORMAT_QUERY_STRING,
+ FORMAT_TIME_USED,
+ FORMAT_URL,
+ FORMAT_SERVER_NAME,
+ FORMAT_HTTP_HOST,
+ FORMAT_CONNECTION_STATUS,
+ FORMAT_BYTES_IN,
+ FORMAT_BYTES_OUT,
+
+ FORMAT_RESPONSE_HEADER
+ } type;
+} format_mapping;
+
+/**
+ *
+ *
+ * "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
+ *
+ */
+
+const format_mapping fmap[] =
+{
+ { '%', FORMAT_PERCENT },
+ { 'h', FORMAT_REMOTE_HOST },
+ { 'l', FORMAT_REMOTE_IDENT },
+ { 'u', FORMAT_REMOTE_USER },
+ { 't', FORMAT_TIMESTAMP },
+ { 'r', FORMAT_REQUEST_LINE },
+ { 's', FORMAT_STATUS },
+ { 'b', FORMAT_BYTES_OUT_NO_HEADER },
+ { 'i', FORMAT_HEADER },
+
+ { 'a', FORMAT_REMOTE_ADDR },
+ { 'A', FORMAT_LOCAL_ADDR },
+ { 'B', FORMAT_BYTES_OUT_NO_HEADER },
+ { 'C', FORMAT_COOKIE },
+ { 'D', FORMAT_TIME_USED_MS },
+ { 'e', FORMAT_ENV },
+ { 'f', FORMAT_FILENAME },
+ { 'H', FORMAT_REQUEST_PROTOCOL },
+ { 'm', FORMAT_REQUEST_METHOD },
+ { 'n', FORMAT_UNSUPPORTED }, /* we have no notes */
+ { 'p', FORMAT_SERVER_PORT },
+ { 'P', FORMAT_UNSUPPORTED }, /* we are only one process */
+ { 'q', FORMAT_QUERY_STRING },
+ { 'T', FORMAT_TIME_USED },
+ { 'U', FORMAT_URL }, /* w/o querystring */
+ { 'v', FORMAT_SERVER_NAME },
+ { 'V', FORMAT_HTTP_HOST },
+ { 'X', FORMAT_CONNECTION_STATUS },
+ { 'I', FORMAT_BYTES_IN },
+ { 'O', FORMAT_BYTES_OUT },
+
+ { 'o', FORMAT_RESPONSE_HEADER },
+
+ { '\0', FORMAT_UNSET }
+};
+
+
+typedef struct {
+ enum { FIELD_UNSET, FIELD_STRING, FIELD_FORMAT } type;
+
+ buffer *string;
+ int field;
+} format_field;
+
+typedef struct {
+ format_field **ptr;
+
+ size_t used;
+ size_t size;
+} format_fields;
+
+typedef struct {
+ buffer *access_logfile;
+ buffer *format;
+ unsigned short use_syslog;
+
+
+ int log_access_fd;
+ time_t last_generated_accesslog_ts;
+ time_t *last_generated_accesslog_ts_ptr;
+
+
+ buffer *access_logbuffer;
+ buffer *ts_accesslog_str;
+
+ format_fields *parsed_format;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config **config_storage;
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_accesslog_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+int accesslog_parse_format(server *srv, format_fields *fields, buffer *format) {
+ size_t i, j, k = 0, start = 0;
+
+ for (i = 0; i < format->used - 1; i++) {
+
+ switch(format->ptr[i]) {
+ case '%':
+ if (start != i) {
+ /* copy the string */
+ if (fields->size == 0) {
+ fields->size = 16;
+ fields->used = 0;
+ fields->ptr = malloc(fields->size * sizeof(format_fields * ));
+ } else if (fields->used == fields->size) {
+ fields->size += 16;
+ fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_fields * ));
+ }
+
+ fields->ptr[fields->used] = malloc(sizeof(format_fields));
+ fields->ptr[fields->used]->type = FIELD_STRING;
+ fields->ptr[fields->used]->string = buffer_init();
+
+ buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + start, i - start);
+
+ fields->used++;
+ }
+
+
+ /* we need a new field */
+
+ if (fields->size == 0) {
+ fields->size = 16;
+ fields->used = 0;
+ fields->ptr = malloc(fields->size * sizeof(format_fields * ));
+ } else if (fields->used == fields->size) {
+ fields->size += 16;
+ fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_fields * ));
+ }
+
+ /* search for the terminating command */
+ switch (format->ptr[i+1]) {
+ case '>':
+ case '<':
+ /* only for s */
+
+ for (j = 0; fmap[j].key != '\0'; j++) {
+ if (fmap[j].key != format->ptr[i+2]) continue;
+
+ /* found key */
+
+ fields->ptr[fields->used] = malloc(sizeof(format_fields));
+ fields->ptr[fields->used]->type = FIELD_FORMAT;
+ fields->ptr[fields->used]->field = fmap[j].type;
+ fields->ptr[fields->used]->string = NULL;
+
+ fields->used++;
+
+ break;
+ }
+
+ if (fmap[j].key == '\0') {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed");
+ return -1;
+ }
+
+ start = i + 3;
+
+ break;
+ case '{':
+ /* go forward to } */
+
+ for (k = i+2; k < format->used - 1; k++) {
+ if (format->ptr[k] == '}') break;
+ }
+
+ if (k == format->used - 1) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed");
+ return -1;
+ }
+ if (format->ptr[k+1] == '\0') {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed");
+ return -1;
+ }
+
+ for (j = 0; fmap[j].key != '\0'; j++) {
+ if (fmap[j].key != format->ptr[k+1]) continue;
+
+ /* found key */
+
+ fields->ptr[fields->used] = malloc(sizeof(format_fields));
+ fields->ptr[fields->used]->type = FIELD_FORMAT;
+ fields->ptr[fields->used]->field = fmap[j].type;
+ fields->ptr[fields->used]->string = buffer_init();
+
+ buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + i + 2, k - (i + 2));
+
+ fields->used++;
+
+ break;
+ }
+
+ if (fmap[j].key == '\0') {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed");
+ return -1;
+ }
+
+ start = k + 2;
+
+ break;
+ default:
+ for (j = 0; fmap[j].key != '\0'; j++) {
+ if (fmap[j].key != format->ptr[i+1]) continue;
+
+ /* found key */
+
+ fields->ptr[fields->used] = malloc(sizeof(format_fields));
+ fields->ptr[fields->used]->type = FIELD_FORMAT;
+ fields->ptr[fields->used]->field = fmap[j].type;
+ fields->ptr[fields->used]->string = NULL;
+
+ fields->used++;
+
+ break;
+ }
+
+ if (fmap[j].key == '\0') {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed");
+ return -1;
+ }
+
+ start = i + 2;
+
+ break;
+ }
+
+ break;
+ }
+ }
+
+ if (start < i) {
+ /* copy the string */
+ if (fields->size == 0) {
+ fields->size = 16;
+ fields->used = 0;
+ fields->ptr = malloc(fields->size * sizeof(format_fields * ));
+ } else if (fields->used == fields->size) {
+ fields->size += 16;
+ fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_fields * ));
+ }
+
+ fields->ptr[fields->used] = malloc(sizeof(format_fields));
+ fields->ptr[fields->used]->type = FIELD_STRING;
+ fields->ptr[fields->used]->string = buffer_init();
+
+ buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + start, i - start);
+
+ fields->used++;
+ }
+
+ return 0;
+}
+
+FREE_FUNC(mod_accesslog_free) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (!s) continue;
+
+ if (s->access_logbuffer->used) {
+ if (s->use_syslog) {
+# ifdef HAVE_SYSLOG_H
+ if (s->access_logbuffer->used > 2) {
+ syslog(LOG_INFO, "%*s", s->access_logbuffer->used - 2, s->access_logbuffer->ptr);
+ }
+# endif
+ } else if (s->log_access_fd != -1) {
+ write(s->log_access_fd, s->access_logbuffer->ptr, s->access_logbuffer->used - 1);
+ }
+ }
+
+ if (s->log_access_fd != -1) close(s->log_access_fd);
+
+ buffer_free(s->ts_accesslog_str);
+ buffer_free(s->access_logbuffer);
+ buffer_free(s->format);
+ buffer_free(s->access_logfile);
+
+ if (s->parsed_format) {
+ size_t j;
+ for (j = 0; j < s->parsed_format->used; j++) {
+ if (s->parsed_format->ptr[j]->string) buffer_free(s->parsed_format->ptr[j]->string);
+ free(s->parsed_format->ptr[j]);
+ }
+ free(s->parsed_format->ptr);
+ free(s->parsed_format);
+ }
+
+ free(s);
+ }
+
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(log_access_open) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "accesslog.filename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "accesslog.use-syslog", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
+ { "accesslog.format", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->access_logfile = buffer_init();
+ s->format = buffer_init();
+ s->access_logbuffer = buffer_init();
+ s->ts_accesslog_str = buffer_init();
+ s->log_access_fd = -1;
+ s->last_generated_accesslog_ts = 0;
+ s->last_generated_accesslog_ts_ptr = &(s->last_generated_accesslog_ts);
+
+
+ cv[0].destination = s->access_logfile;
+ cv[1].destination = &(s->use_syslog);
+ cv[2].destination = s->format;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (i == 0 && buffer_is_empty(s->format)) {
+ /* set a default logfile string */
+
+ buffer_copy_string(s->format, "%h %V %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"");
+ }
+
+ /* parse */
+
+ if (s->format->used) {
+ s->parsed_format = calloc(1, sizeof(*(s->parsed_format)));
+
+ if (-1 == accesslog_parse_format(srv, s->parsed_format, s->format)) {
+
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "parsing accesslog-definition failed:", s->format);
+
+ return HANDLER_ERROR;
+ }
+#if 0
+ /* debugging */
+ for (j = 0; j < s->parsed_format->used; j++) {
+ switch (s->parsed_format->ptr[j]->type) {
+ case FIELD_FORMAT:
+ log_error_write(srv, __FILE__, __LINE__, "ssds",
+ "config:", "format", s->parsed_format->ptr[j]->field,
+ s->parsed_format->ptr[j]->string ?
+ s->parsed_format->ptr[j]->string->ptr : "" );
+ break;
+ case FIELD_STRING:
+ log_error_write(srv, __FILE__, __LINE__, "ssbs", "config:", "string '", s->parsed_format->ptr[j]->string, "'");
+ break;
+ default:
+ break;
+ }
+ }
+#endif
+ }
+
+ if (s->use_syslog) {
+ /* ignore the next checks */
+ continue;
+ }
+
+ if (buffer_is_empty(s->access_logfile)) continue;
+
+ if (s->access_logfile->ptr[0] == '|') {
+#ifdef HAVE_FORK
+ /* create write pipe and spawn process */
+
+ int to_log_fds[2];
+ pid_t pid;
+
+ if (pipe(to_log_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed: ", strerror(errno));
+ return HANDLER_ERROR;
+ }
+
+ /* fork, execve */
+ switch (pid = fork()) {
+ case 0:
+ /* child */
+
+ close(STDIN_FILENO);
+ dup2(to_log_fds[0], STDIN_FILENO);
+ close(to_log_fds[0]);
+ /* not needed */
+ close(to_log_fds[1]);
+
+ /* we don't need the client socket */
+ for (i = 3; i < 256; i++) {
+ close(i);
+ }
+
+ /* exec the log-process (skip the | )
+ *
+ */
+
+ execl("/bin/sh", "sh", "-c", s->access_logfile->ptr + 1, NULL);
+
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "spawning log-process failed: ", strerror(errno),
+ s->access_logfile->ptr + 1);
+
+ exit(-1);
+ break;
+ case -1:
+ /* error */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed: ", strerror(errno));
+ break;
+ default:
+ close(to_log_fds[0]);
+
+ s->log_access_fd = to_log_fds[1];
+
+ break;
+ }
+#else
+ return -1;
+#endif
+ } else if (-1 == (s->log_access_fd =
+ open(s->access_logfile->ptr, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) {
+
+ log_error_write(srv, __FILE__, __LINE__, "ssb",
+ "opening access-log failed:",
+ strerror(errno), s->access_logfile);
+
+ return HANDLER_ERROR;
+ }
+ fcntl(s->log_access_fd, F_SETFD, FD_CLOEXEC);
+
+ }
+
+ return HANDLER_GO_ON;
+}
+
+SIGHUP_FUNC(log_access_cycle) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ if (!p->config_storage) return HANDLER_GO_ON;
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (s->access_logbuffer->used) {
+ if (s->use_syslog) {
+#ifdef HAVE_SYSLOG_H
+ if (s->access_logbuffer->used > 2) {
+ /* syslog appends a \n on its own */
+ syslog(LOG_INFO, "%*s", s->access_logbuffer->used - 2, s->access_logbuffer->ptr);
+ }
+#endif
+ } else if (s->log_access_fd != -1) {
+ write(s->log_access_fd, s->access_logbuffer->ptr, s->access_logbuffer->used - 1);
+ }
+
+ buffer_reset(s->access_logbuffer);
+ }
+
+ if (s->use_syslog == 0 &&
+ !buffer_is_empty(s->access_logfile) &&
+ s->access_logfile->ptr[0] != '|') {
+
+ close(s->log_access_fd);
+
+ if (-1 == (s->log_access_fd =
+ open(s->access_logfile->ptr, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) {
+
+ log_error_write(srv, __FILE__, __LINE__, "ss", "cycling access-log failed:", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_accesslog_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(access_logfile);
+ PATCH(format);
+ PATCH(log_access_fd);
+ PATCH(last_generated_accesslog_ts_ptr);
+ PATCH(access_logbuffer);
+ PATCH(ts_accesslog_str);
+ PATCH(parsed_format);
+ PATCH(use_syslog);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.filename"))) {
+ PATCH(access_logfile);
+ PATCH(log_access_fd);
+ PATCH(last_generated_accesslog_ts_ptr);
+ PATCH(access_logbuffer);
+ PATCH(ts_accesslog_str);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.format"))) {
+ PATCH(format);
+ PATCH(parsed_format);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.use-syslog"))) {
+ PATCH(use_syslog);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+REQUESTDONE_FUNC(log_access_write) {
+ plugin_data *p = p_d;
+ buffer *b;
+ size_t j;
+
+ int newts = 0;
+ data_string *ds;
+
+ mod_accesslog_patch_connection(srv, con, p);
+
+ b = p->conf.access_logbuffer;
+ if (b->used == 0) {
+ buffer_copy_string(b, "");
+ }
+
+ for (j = 0; j < p->conf.parsed_format->used; j++) {
+ switch(p->conf.parsed_format->ptr[j]->type) {
+ case FIELD_STRING:
+ buffer_append_string_buffer(b, p->conf.parsed_format->ptr[j]->string);
+ break;
+ case FIELD_FORMAT:
+ switch(p->conf.parsed_format->ptr[j]->field) {
+ case FORMAT_TIMESTAMP:
+
+ /* cache the generated timestamp */
+ if (srv->cur_ts != *(p->conf.last_generated_accesslog_ts_ptr)) {
+ struct tm tm;
+#if defined(HAVE_STRUCT_TM_GMTOFF)
+ long scd, hrs, min;
+#endif
+
+ buffer_prepare_copy(p->conf.ts_accesslog_str, 255);
+#if defined(HAVE_STRUCT_TM_GMTOFF)
+# ifdef HAVE_LOCALTIME_R
+ localtime_r(&(srv->cur_ts), &tm);
+ strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, "[%d/%b/%Y:%H:%M:%S ", &tm);
+# else
+ strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, "[%d/%b/%Y:%H:%M:%S ", localtime_r(&(srv->cur_ts)));
+# endif
+ p->conf.ts_accesslog_str->used = strlen(p->conf.ts_accesslog_str->ptr) + 1;
+
+ buffer_append_string(p->conf.ts_accesslog_str, tm.tm_gmtoff >= 0 ? "+" : "-");
+
+ scd = abs(tm.tm_gmtoff);
+ hrs = scd / 3600;
+ min = (scd % 3600) / 60;
+
+ /* hours */
+ if (hrs < 10) buffer_append_string(p->conf.ts_accesslog_str, "0");
+ buffer_append_long(p->conf.ts_accesslog_str, hrs);
+
+ if (min < 10) buffer_append_string(p->conf.ts_accesslog_str, "0");
+ buffer_append_long(p->conf.ts_accesslog_str, min);
+ BUFFER_APPEND_STRING_CONST(p->conf.ts_accesslog_str, "]");
+#else
+#ifdef HAVE_GMTIME_R
+ gmtime_r(&(srv->cur_ts), &tm);
+ strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, "[%d/%b/%Y:%H:%M:%S +0000]", &tm);
+#else
+ strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, "[%d/%b/%Y:%H:%M:%S +0000]", gmtime(&(srv->cur_ts)));
+#endif
+ p->conf.ts_accesslog_str->used = strlen(p->conf.ts_accesslog_str->ptr) + 1;
+#endif
+
+ *(p->conf.last_generated_accesslog_ts_ptr) = srv->cur_ts;
+ newts = 1;
+ }
+
+ buffer_append_string_buffer(b, p->conf.ts_accesslog_str);
+
+ break;
+ case FORMAT_REMOTE_HOST:
+
+ /* handle inet_ntop cache */
+
+ buffer_append_string(b, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
+
+ break;
+ case FORMAT_REMOTE_IDENT:
+ /* ident */
+ BUFFER_APPEND_STRING_CONST(b, "-");
+ break;
+ case FORMAT_REMOTE_USER:
+ if (con->authed_user->used > 1) {
+ buffer_append_string_buffer(b, con->authed_user);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "-");
+ }
+ break;
+ case FORMAT_REQUEST_LINE:
+ if (con->request.request_line->used) {
+ buffer_append_string_buffer(b, con->request.request_line);
+ }
+ break;
+ case FORMAT_STATUS:
+ buffer_append_long(b, con->http_status);
+ break;
+
+ case FORMAT_BYTES_OUT_NO_HEADER:
+ if (con->bytes_written > 0) {
+ buffer_append_off_t(b,
+ con->bytes_written - con->bytes_header <= 0 ? 0 : con->bytes_written - con->bytes_header);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "-");
+ }
+ break;
+ case FORMAT_HEADER:
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, p->conf.parsed_format->ptr[j]->string->ptr))) {
+ buffer_append_string_buffer(b, ds->value);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "-");
+ }
+ break;
+ case FORMAT_RESPONSE_HEADER:
+ if (NULL != (ds = (data_string *)array_get_element(con->response.headers, p->conf.parsed_format->ptr[j]->string->ptr))) {
+ buffer_append_string_buffer(b, ds->value);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "-");
+ }
+ break;
+ case FORMAT_FILENAME:
+ if (con->physical.path->used > 1) {
+ buffer_append_string_buffer(b, con->physical.path);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "-");
+ }
+ break;
+ case FORMAT_BYTES_OUT:
+ if (con->bytes_written > 0) {
+ buffer_append_off_t(b, con->bytes_written);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "-");
+ }
+ break;
+ case FORMAT_BYTES_IN:
+ if (con->bytes_read > 0) {
+ buffer_append_off_t(b, con->bytes_read);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "-");
+ }
+ break;
+ case FORMAT_TIME_USED:
+ buffer_append_long(b, srv->cur_ts - con->request_start);
+ break;
+ case FORMAT_SERVER_NAME:
+ if (con->server_name->used > 1) {
+ buffer_append_string_buffer(b, con->server_name);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "-");
+ }
+ break;
+ case FORMAT_HTTP_HOST:
+ if (con->uri.authority->used > 1) {
+ buffer_append_string_buffer(b, con->uri.authority);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "-");
+ }
+ break;
+ case FORMAT_REQUEST_PROTOCOL:
+ buffer_append_string(b,
+ con->request.http_version == HTTP_VERSION_1_1 ? "HTTP/1.1" : "HTTP/1.0");
+ break;
+ case FORMAT_REQUEST_METHOD:
+ buffer_append_string(b, get_http_method_name(con->request.http_method));
+ break;
+ case FORMAT_SERVER_PORT:
+ buffer_append_long(b, srv->srvconf.port);
+ break;
+ case FORMAT_QUERY_STRING:
+ buffer_append_string_buffer(b, con->uri.query);
+ break;
+ case FORMAT_URL:
+ buffer_append_string_buffer(b, con->uri.path_raw);
+ break;
+ case FORMAT_CONNECTION_STATUS:
+ switch(con->keep_alive) {
+ case 0: buffer_append_string(b, "-"); break;
+ default: buffer_append_string(b, "+"); break;
+ }
+ break;
+ default:
+ /*
+ { 'a', FORMAT_REMOTE_ADDR },
+ { 'A', FORMAT_LOCAL_ADDR },
+ { 'C', FORMAT_COOKIE },
+ { 'D', FORMAT_TIME_USED_MS },
+ { 'e', FORMAT_ENV },
+ */
+
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "\n");
+
+ if (p->conf.use_syslog || /* syslog doesn't cache */
+ (p->conf.access_logfile->used && p->conf.access_logfile->ptr[0] != '|') || /* pipes don't cache */
+ newts ||
+ b->used > BUFFER_MAX_REUSE_SIZE) {
+ if (p->conf.use_syslog) {
+#ifdef HAVE_SYSLOG_H
+ if (b->used > 2) {
+ /* syslog appends a \n on its own */
+ syslog(LOG_INFO, "%*s", b->used - 2, b->ptr);
+ }
+#endif
+ } else if (p->conf.log_access_fd != -1) {
+ write(p->conf.log_access_fd, b->ptr, b->used - 1);
+ }
+ buffer_reset(b);
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+int mod_accesslog_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("accesslog");
+
+ p->init = mod_accesslog_init;
+ p->set_defaults= log_access_open;
+ p->cleanup = mod_accesslog_free;
+
+ p->handle_request_done = log_access_write;
+ p->handle_sighup = log_access_cycle;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_alias.c b/src/mod_alias.c
new file mode 100644
index 0000000..23570e4
--- /dev/null
+++ b/src/mod_alias.c
@@ -0,0 +1,199 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+/* plugin config for all request/connections */
+typedef struct {
+ array *alias;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_alias_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_alias_free) {
+ plugin_data *p = p_d;
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->alias);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_alias_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "alias.url", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->alias = array_init();
+ cv[0].destination = s->alias;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ if (s->alias->used >= 2) {
+ const array *a = s->alias;
+ size_t j, k;
+
+ for (j = 0; j < a->used; j ++) {
+ const buffer *prefix = a->data[a->sorted[j]]->key;
+ for (k = j + 1; k < a->used; k ++) {
+ const buffer *key = a->data[a->sorted[k]]->key;
+
+ if (key->used < prefix->used) {
+ break;
+ }
+ if (memcmp(key->ptr, prefix->ptr, prefix->used - 1) != 0) {
+ break;
+ }
+ /* ok, they have same prefix. check position */
+ if (a->sorted[j] < a->sorted[k]) {
+ fprintf(stderr, "url.alias: `%s' will never match as `%s' matched first\n",
+ key->ptr,
+ prefix->ptr);
+ return HANDLER_ERROR;
+ }
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_alias_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(alias);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("alias.url"))) {
+ PATCH(alias);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+PHYSICALPATH_FUNC(mod_alias_physical_handler) {
+ plugin_data *p = p_d;
+ int uri_len, basedir_len;
+ char *uri_ptr;
+ size_t k;
+
+ if (con->physical.path->used == 0) return HANDLER_GO_ON;
+
+ mod_alias_patch_connection(srv, con, p);
+
+ /* not to include the tailing slash */
+ basedir_len = (con->physical.basedir->used - 1) - 1;
+ uri_len = con->physical.path->used - 1 - basedir_len;
+ uri_ptr = con->physical.path->ptr + basedir_len;
+
+ for (k = 0; k < p->conf.alias->used; k++) {
+ data_string *ds = (data_string *)p->conf.alias->data[k];
+ int alias_len = ds->key->used - 1;
+
+ if (alias_len > uri_len) continue;
+ if (ds->key->used == 0) continue;
+
+ if (0 == strncmp(uri_ptr, ds->key->ptr, alias_len)) {
+ /* matched */
+
+ buffer_copy_string_buffer(con->physical.basedir, ds->value);
+ buffer_copy_string_buffer(srv->tmp_buf, ds->value);
+ buffer_append_string(srv->tmp_buf, uri_ptr + alias_len);
+ buffer_copy_string_buffer(con->physical.path, srv->tmp_buf);
+
+ return HANDLER_GO_ON;
+ }
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_alias_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("alias");
+
+ p->init = mod_alias_init;
+ p->handle_physical= mod_alias_physical_handler;
+ p->set_defaults = mod_alias_set_defaults;
+ p->cleanup = mod_alias_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_auth.c b/src/mod_auth.c
new file mode 100644
index 0000000..9b791d4
--- /dev/null
+++ b/src/mod_auth.c
@@ -0,0 +1,630 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "plugin.h"
+#include "http_auth.h"
+#include "log.h"
+#include "response.h"
+
+handler_t auth_ldap_init(server *srv, mod_auth_plugin_config *s);
+
+
+/**
+ * the basic and digest auth framework
+ *
+ * - config handling
+ * - protocol handling
+ *
+ * http_auth.c
+ * http_auth_digest.c
+ *
+ * do the real work
+ */
+
+INIT_FUNC(mod_auth_init) {
+ mod_auth_plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->tmp_buf = buffer_init();
+
+ p->auth_user = buffer_init();
+#ifdef USE_LDAP
+ p->ldap_filter = buffer_init();
+#endif
+
+ return p;
+}
+
+FREE_FUNC(mod_auth_free) {
+ mod_auth_plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ buffer_free(p->tmp_buf);
+ buffer_free(p->auth_user);
+#ifdef USE_LDAP
+ buffer_free(p->ldap_filter);
+#endif
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ mod_auth_plugin_config *s = p->config_storage[i];
+
+ if (!s) continue;
+
+ array_free(s->auth_require);
+ buffer_free(s->auth_plain_groupfile);
+ buffer_free(s->auth_plain_userfile);
+ buffer_free(s->auth_htdigest_userfile);
+ buffer_free(s->auth_htpasswd_userfile);
+ buffer_free(s->auth_backend_conf);
+
+ buffer_free(s->auth_ldap_hostname);
+ buffer_free(s->auth_ldap_basedn);
+ buffer_free(s->auth_ldap_binddn);
+ buffer_free(s->auth_ldap_bindpw);
+ buffer_free(s->auth_ldap_filter);
+ buffer_free(s->auth_ldap_cafile);
+
+#ifdef USE_LDAP
+ buffer_free(s->ldap_filter_pre);
+ buffer_free(s->ldap_filter_post);
+
+ if (s->ldap) ldap_unbind_s(s->ldap);
+#endif
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_auth_patch_connection(server *srv, connection *con, mod_auth_plugin_data *p) {
+ size_t i, j;
+ mod_auth_plugin_config *s = p->config_storage[0];
+
+ PATCH(auth_backend);
+ PATCH(auth_plain_groupfile);
+ PATCH(auth_plain_userfile);
+ PATCH(auth_htdigest_userfile);
+ PATCH(auth_htpasswd_userfile);
+ PATCH(auth_require);
+ PATCH(auth_debug);
+ PATCH(auth_ldap_hostname);
+ PATCH(auth_ldap_basedn);
+ PATCH(auth_ldap_binddn);
+ PATCH(auth_ldap_bindpw);
+ PATCH(auth_ldap_filter);
+ PATCH(auth_ldap_cafile);
+ PATCH(auth_ldap_starttls);
+#ifdef USE_LDAP
+ PATCH(ldap);
+ PATCH(ldap_filter_pre);
+ PATCH(ldap_filter_post);
+#endif
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend"))) {
+ PATCH(auth_backend);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.plain.groupfile"))) {
+ PATCH(auth_plain_groupfile);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.plain.userfile"))) {
+ PATCH(auth_plain_userfile);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.htdigest.userfile"))) {
+ PATCH(auth_htdigest_userfile);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.htpasswd.userfile"))) {
+ PATCH(auth_htpasswd_userfile);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.require"))) {
+ PATCH(auth_require);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.debug"))) {
+ PATCH(auth_debug);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.hostname"))) {
+ PATCH(auth_ldap_hostname);
+#ifdef USE_LDAP
+ PATCH(ldap);
+ PATCH(ldap_filter_pre);
+ PATCH(ldap_filter_post);
+#endif
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.base-dn"))) {
+ PATCH(auth_ldap_basedn);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.filter"))) {
+ PATCH(auth_ldap_filter);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.ca-file"))) {
+ PATCH(auth_ldap_cafile);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.starttls"))) {
+ PATCH(auth_ldap_starttls);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+static handler_t mod_auth_uri_handler(server *srv, connection *con, void *p_d) {
+ size_t k;
+ int auth_required = 0, auth_satisfied = 0;
+ char *http_authorization = NULL;
+ data_string *ds;
+ mod_auth_plugin_data *p = p_d;
+ array *req;
+
+ /* select the right config */
+ mod_auth_patch_connection(srv, con, p);
+
+ if (p->conf.auth_require == NULL) return HANDLER_GO_ON;
+
+ /*
+ * AUTH
+ *
+ */
+
+ /* do we have to ask for auth ? */
+
+ auth_required = 0;
+ auth_satisfied = 0;
+
+ /* search auth-directives for path */
+ for (k = 0; k < p->conf.auth_require->used; k++) {
+ buffer *req = p->conf.auth_require->data[k]->key;
+
+ if (req->used == 0) continue;
+ if (con->uri.path->used < req->used) continue;
+
+ /* if we have a case-insensitive FS we have to lower-case the URI here too */
+
+ if (con->conf.force_lowercase_filenames) {
+ if (0 == strncasecmp(con->uri.path->ptr, req->ptr, req->used - 1)) {
+ auth_required = 1;
+ break;
+ }
+ } else {
+ if (0 == strncmp(con->uri.path->ptr, req->ptr, req->used - 1)) {
+ auth_required = 1;
+ break;
+ }
+ }
+ }
+
+ /* nothing to do for us */
+ if (auth_required == 0) return HANDLER_GO_ON;
+
+ req = ((data_array *)(p->conf.auth_require->data[k]))->value;
+
+ /* try to get Authorization-header */
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Authorization"))) {
+ http_authorization = ds->value->ptr;
+ }
+
+ if (ds && ds->value && ds->value->used) {
+ char *auth_realm;
+ data_string *method;
+
+ method = (data_string *)array_get_element(req, "method");
+
+ /* parse auth-header */
+ if (NULL != (auth_realm = strchr(http_authorization, ' '))) {
+ int auth_type_len = auth_realm - http_authorization;
+
+ if ((auth_type_len == 5) &&
+ (0 == strncmp(http_authorization, "Basic", auth_type_len))) {
+
+ if (0 == strcmp(method->value->ptr, "basic")) {
+ auth_satisfied = http_auth_basic_check(srv, con, p, req, con->uri.path, auth_realm+1);
+ }
+ } else if ((auth_type_len == 6) &&
+ (0 == strncmp(http_authorization, "Digest", auth_type_len))) {
+ if (0 == strcmp(method->value->ptr, "digest")) {
+ if (-1 == (auth_satisfied = http_auth_digest_check(srv, con, p, req, con->uri.path, auth_realm+1))) {
+ con->http_status = 400;
+
+ /* a field was missing */
+
+ return HANDLER_FINISHED;
+ }
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "unknown authentification type:",
+ http_authorization);
+ }
+ }
+ }
+
+ if (!auth_satisfied) {
+ data_string *method, *realm;
+ method = (data_string *)array_get_element(req, "method");
+ realm = (data_string *)array_get_element(req, "realm");
+
+ con->http_status = 401;
+
+ if (0 == strcmp(method->value->ptr, "basic")) {
+ buffer_copy_string(p->tmp_buf, "Basic realm=\"");
+ buffer_append_string_buffer(p->tmp_buf, realm->value);
+ buffer_append_string(p->tmp_buf, "\"");
+
+ response_header_insert(srv, con, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(p->tmp_buf));
+ } else if (0 == strcmp(method->value->ptr, "digest")) {
+ char hh[33];
+ http_auth_digest_generate_nonce(srv, p, srv->tmp_buf, hh);
+
+ buffer_copy_string(p->tmp_buf, "Digest realm=\"");
+ buffer_append_string_buffer(p->tmp_buf, realm->value);
+ buffer_append_string(p->tmp_buf, "\", nonce=\"");
+ buffer_append_string(p->tmp_buf, hh);
+ buffer_append_string(p->tmp_buf, "\", qop=\"auth\"");
+
+ response_header_insert(srv, con, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(p->tmp_buf));
+ } else {
+ /* evil */
+ }
+ return HANDLER_FINISHED;
+ } else {
+ /* the REMOTE_USER header */
+
+ buffer_copy_string_buffer(con->authed_user, p->auth_user);
+ }
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_auth_set_defaults) {
+ mod_auth_plugin_data *p = p_d;
+ size_t i;
+
+ config_values_t cv[] = {
+ { "auth.backend", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "auth.backend.plain.groupfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.plain.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.require", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.ldap.hostname", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.ldap.base-dn", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.ldap.filter", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.ldap.ca-file", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.ldap.starttls", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.ldap.bind-dn", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.ldap.bind-pw", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 10 */
+ { "auth.backend.htdigest.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.htpasswd.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 13 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ mod_auth_plugin_config *s;
+ size_t n;
+ data_array *da;
+ array *ca;
+
+ s = calloc(1, sizeof(mod_auth_plugin_config));
+ s->auth_plain_groupfile = buffer_init();
+ s->auth_plain_userfile = buffer_init();
+ s->auth_htdigest_userfile = buffer_init();
+ s->auth_htpasswd_userfile = buffer_init();
+ s->auth_backend_conf = buffer_init();
+
+ s->auth_ldap_hostname = buffer_init();
+ s->auth_ldap_basedn = buffer_init();
+ s->auth_ldap_binddn = buffer_init();
+ s->auth_ldap_bindpw = buffer_init();
+ s->auth_ldap_filter = buffer_init();
+ s->auth_ldap_cafile = buffer_init();
+ s->auth_ldap_starttls = 0;
+ s->auth_debug = 0;
+
+ s->auth_require = array_init();
+
+#ifdef USE_LDAP
+ s->ldap_filter_pre = buffer_init();
+ s->ldap_filter_post = buffer_init();
+ s->ldap = NULL;
+#endif
+
+ cv[0].destination = s->auth_backend_conf;
+ cv[1].destination = s->auth_plain_groupfile;
+ cv[2].destination = s->auth_plain_userfile;
+ cv[3].destination = s->auth_require;
+ cv[4].destination = s->auth_ldap_hostname;
+ cv[5].destination = s->auth_ldap_basedn;
+ cv[6].destination = s->auth_ldap_filter;
+ cv[7].destination = s->auth_ldap_cafile;
+ cv[8].destination = &(s->auth_ldap_starttls);
+ cv[9].destination = s->auth_ldap_binddn;
+ cv[10].destination = s->auth_ldap_bindpw;
+ cv[11].destination = s->auth_htdigest_userfile;
+ cv[12].destination = s->auth_htpasswd_userfile;
+ cv[13].destination = &(s->auth_debug);
+
+ p->config_storage[i] = s;
+ ca = ((data_config *)srv->config_context->data[i])->value;
+
+ if (0 != config_insert_values_global(srv, ca, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (s->auth_backend_conf->used) {
+ if (0 == strcmp(s->auth_backend_conf->ptr, "htpasswd")) {
+ s->auth_backend = AUTH_BACKEND_HTPASSWD;
+ } else if (0 == strcmp(s->auth_backend_conf->ptr, "htdigest")) {
+ s->auth_backend = AUTH_BACKEND_HTDIGEST;
+ } else if (0 == strcmp(s->auth_backend_conf->ptr, "plain")) {
+ s->auth_backend = AUTH_BACKEND_PLAIN;
+ } else if (0 == strcmp(s->auth_backend_conf->ptr, "ldap")) {
+ s->auth_backend = AUTH_BACKEND_LDAP;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "auth.backend not supported:", s->auth_backend_conf);
+
+ return HANDLER_ERROR;
+ }
+ }
+
+ /* no auth.require for this section */
+ if (NULL == (da = (data_array *)array_get_element(ca, "auth.require"))) continue;
+
+ if (da->type != TYPE_ARRAY) continue;
+
+ for (n = 0; n < da->value->used; n++) {
+ size_t m;
+ data_array *da_file = (data_array *)da->value->data[n];
+ const char *method, *realm, *require;
+
+ if (da->value->data[n]->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ", "auth.require", "[", da->value->data[n]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ method = realm = require = NULL;
+
+ for (m = 0; m < da_file->value->used; m++) {
+ if (da_file->value->data[m]->type == TYPE_STRING) {
+ if (0 == strcmp(da_file->value->data[m]->key->ptr, "method")) {
+ method = ((data_string *)(da_file->value->data[m]))->value->ptr;
+ } else if (0 == strcmp(da_file->value->data[m]->key->ptr, "realm")) {
+ realm = ((data_string *)(da_file->value->data[m]))->value->ptr;
+ } else if (0 == strcmp(da_file->value->data[m]->key->ptr, "require")) {
+ require = ((data_string *)(da_file->value->data[m]))->value->ptr;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs", "unexpected type for key: ", "auth.require", "[", da_file->value->data[m]->key, "](string)");
+ return HANDLER_ERROR;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs", "unexpected type for key: ", "auth.require", "[", da_file->value->data[m]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+ }
+
+ if (method == NULL) {
+ log_error_write(srv, __FILE__, __LINE__, "sssss", "missing entry for key: ", "auth.require", "[", "method", "](string)");
+ return HANDLER_ERROR;
+ } else {
+ if (0 != strcmp(method, "basic") &&
+ 0 != strcmp(method, "digest")) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "auth.require->method has to be either 'basic' or 'digest'");
+ return HANDLER_ERROR;
+ }
+ }
+
+ if (realm == NULL) {
+ log_error_write(srv, __FILE__, __LINE__, "sssss", "missing entry for key: ", "auth.require", "[", "realm", "](string)");
+ return HANDLER_ERROR;
+ }
+
+ if (require == NULL) {
+ log_error_write(srv, __FILE__, __LINE__, "sssss", "missing entry for key: ", "auth.require", "[", "require", "](string)");
+ return HANDLER_ERROR;
+ }
+
+ if (method && realm && require) {
+ data_string *ds;
+ data_array *a;
+
+ a = data_array_init();
+ buffer_copy_string_buffer(a->key, da_file->key);
+
+ ds = data_string_init();
+
+ buffer_copy_string(ds->key, "method");
+ buffer_copy_string(ds->value, method);
+
+ array_insert_unique(a->value, (data_unset *)ds);
+
+ ds = data_string_init();
+
+ buffer_copy_string(ds->key, "realm");
+ buffer_copy_string(ds->value, realm);
+
+ array_insert_unique(a->value, (data_unset *)ds);
+
+ ds = data_string_init();
+
+ buffer_copy_string(ds->key, "require");
+ buffer_copy_string(ds->value, require);
+
+ array_insert_unique(a->value, (data_unset *)ds);
+
+ array_insert_unique(s->auth_require, (data_unset *)a);
+ }
+ }
+
+ switch(s->auth_backend) {
+ case AUTH_BACKEND_PLAIN:
+ if (s->auth_plain_userfile->used) {
+ int fd;
+ /* try to read */
+ if (-1 == (fd = open(s->auth_plain_userfile->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "opening auth.backend.plain.userfile:", s->auth_plain_userfile,
+ "failed:", strerror(errno));
+ return HANDLER_ERROR;
+ }
+ close(fd);
+ }
+ break;
+ case AUTH_BACKEND_HTPASSWD:
+ if (s->auth_htpasswd_userfile->used) {
+ int fd;
+ /* try to read */
+ if (-1 == (fd = open(s->auth_htpasswd_userfile->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "opening auth.backend.htpasswd.userfile:", s->auth_htpasswd_userfile,
+ "failed:", strerror(errno));
+ return HANDLER_ERROR;
+ }
+ close(fd);
+ }
+ break;
+ case AUTH_BACKEND_HTDIGEST:
+ if (s->auth_htdigest_userfile->used) {
+ int fd;
+ /* try to read */
+ if (-1 == (fd = open(s->auth_htdigest_userfile->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "opening auth.backend.htdigest.userfile:", s->auth_htdigest_userfile,
+ "failed:", strerror(errno));
+ return HANDLER_ERROR;
+ }
+ close(fd);
+ }
+ break;
+ case AUTH_BACKEND_LDAP: {
+ handler_t ret = auth_ldap_init(srv, s);
+ if (ret == HANDLER_ERROR)
+ return (ret);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+handler_t auth_ldap_init(server *srv, mod_auth_plugin_config *s) {
+#ifdef USE_LDAP
+ int ret;
+#if 0
+ if (s->auth_ldap_basedn->used == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "ldap: auth.backend.ldap.base-dn has to be set");
+
+ return HANDLER_ERROR;
+ }
+#endif
+
+ if (s->auth_ldap_filter->used) {
+ char *dollar;
+
+ /* parse filter */
+
+ if (NULL == (dollar = strchr(s->auth_ldap_filter->ptr, '$'))) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "ldap: auth.backend.ldap.filter is missing a replace-operator '$'");
+
+ return HANDLER_ERROR;
+ }
+
+ buffer_copy_string_len(s->ldap_filter_pre, s->auth_ldap_filter->ptr, dollar - s->auth_ldap_filter->ptr);
+ buffer_copy_string(s->ldap_filter_post, dollar+1);
+ }
+
+ if (s->auth_ldap_hostname->used) {
+ if (NULL == (s->ldap = ldap_init(s->auth_ldap_hostname->ptr, LDAP_PORT))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap ...", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ ret = LDAP_VERSION3;
+ if (LDAP_OPT_SUCCESS != (ret = ldap_set_option(s->ldap, LDAP_OPT_PROTOCOL_VERSION, &ret))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
+
+ return HANDLER_ERROR;
+ }
+
+ if (s->auth_ldap_starttls) {
+ /* if no CA file is given, it is ok, as we will use encryption
+ * if the server requires a CAfile it will tell us */
+ if (!buffer_is_empty(s->auth_ldap_cafile)) {
+ if (LDAP_OPT_SUCCESS != (ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE,
+ s->auth_ldap_cafile->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "Loading CA certificate failed:", ldap_err2string(ret));
+
+ return HANDLER_ERROR;
+ }
+ }
+
+ if (LDAP_OPT_SUCCESS != (ret = ldap_start_tls_s(s->ldap, NULL, NULL))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap startTLS failed:", ldap_err2string(ret));
+
+ return HANDLER_ERROR;
+ }
+ }
+
+
+ /* 1. */
+ if (s->auth_ldap_binddn->used) {
+ if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(s->ldap, s->auth_ldap_binddn->ptr, s->auth_ldap_bindpw->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
+
+ return HANDLER_ERROR;
+ }
+ } else {
+ if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(s->ldap, NULL, NULL))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
+
+ return HANDLER_ERROR;
+ }
+ }
+ }
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s", "no ldap support available");
+ return HANDLER_ERROR;
+#endif
+ return HANDLER_GO_ON;
+}
+
+int mod_auth_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("auth");
+ p->init = mod_auth_init;
+ p->set_defaults = mod_auth_set_defaults;
+ p->handle_uri_clean = mod_auth_uri_handler;
+ p->cleanup = mod_auth_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_auth.h b/src/mod_auth.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/mod_auth.h
diff --git a/src/mod_cgi.c b/src/mod_cgi.c
new file mode 100644
index 0000000..883bf98
--- /dev/null
+++ b/src/mod_cgi.c
@@ -0,0 +1,1305 @@
+#include <sys/types.h>
+#ifdef __WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/mman.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fdevent.h>
+#include <signal.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "server.h"
+#include "keyvalue.h"
+#include "log.h"
+#include "connections.h"
+#include "joblist.h"
+#include "http_chunk.h"
+
+#include "plugin.h"
+
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+enum {EOL_UNSET, EOL_N, EOL_RN};
+
+typedef struct {
+ char **ptr;
+
+ size_t size;
+ size_t used;
+} char_array;
+
+typedef struct {
+ pid_t *ptr;
+ size_t used;
+ size_t size;
+} buffer_pid_t;
+
+typedef struct {
+ array *cgi;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer_pid_t cgi_pid;
+
+ buffer *tmp_buf;
+ buffer *parse_response;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+typedef struct {
+ pid_t pid;
+ int fd;
+ int fde_ndx; /* index into the fd-event buffer */
+
+ connection *remote_conn; /* dumb pointer */
+ plugin_data *plugin_data; /* dumb pointer */
+
+ buffer *response;
+ buffer *response_header;
+} handler_ctx;
+
+static handler_ctx * cgi_handler_ctx_init() {
+ handler_ctx *hctx = calloc(1, sizeof(*hctx));
+
+ assert(hctx);
+
+ hctx->response = buffer_init();
+ hctx->response_header = buffer_init();
+
+ return hctx;
+}
+
+static void cgi_handler_ctx_free(handler_ctx *hctx) {
+ buffer_free(hctx->response);
+ buffer_free(hctx->response_header);
+
+ free(hctx);
+}
+
+enum {FDEVENT_HANDLED_UNSET, FDEVENT_HANDLED_FINISHED, FDEVENT_HANDLED_NOT_FINISHED, FDEVENT_HANDLED_ERROR};
+
+INIT_FUNC(mod_cgi_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ assert(p);
+
+ p->tmp_buf = buffer_init();
+ p->parse_response = buffer_init();
+
+ return p;
+}
+
+
+FREE_FUNC(mod_cgi_free) {
+ plugin_data *p = p_d;
+ buffer_pid_t *r = &(p->cgi_pid);
+
+ UNUSED(srv);
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->cgi);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+
+ if (r->ptr) free(r->ptr);
+
+ buffer_free(p->tmp_buf);
+ buffer_free(p->parse_response);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "cgi.assign", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET}
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ assert(s);
+
+ s->cgi = array_init();
+
+ cv[0].destination = s->cgi;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+static int cgi_pid_add(server *srv, plugin_data *p, pid_t pid) {
+ int m = -1;
+ size_t i;
+ buffer_pid_t *r = &(p->cgi_pid);
+
+ UNUSED(srv);
+
+ for (i = 0; i < r->used; i++) {
+ if (r->ptr[i] > m) m = r->ptr[i];
+ }
+
+ if (r->size == 0) {
+ r->size = 16;
+ r->ptr = malloc(sizeof(*r->ptr) * r->size);
+ } else if (r->used == r->size) {
+ r->size += 16;
+ r->ptr = realloc(r->ptr, sizeof(*r->ptr) * r->size);
+ }
+
+ r->ptr[r->used++] = pid;
+
+ return m;
+}
+
+static int cgi_pid_del(server *srv, plugin_data *p, pid_t pid) {
+ size_t i;
+ buffer_pid_t *r = &(p->cgi_pid);
+
+ UNUSED(srv);
+
+ for (i = 0; i < r->used; i++) {
+ if (r->ptr[i] == pid) break;
+ }
+
+ if (i != r->used) {
+ /* found */
+
+ if (i != r->used - 1) {
+ r->ptr[i] = r->ptr[r->used - 1];
+ }
+ r->used--;
+ }
+
+ return 0;
+}
+
+static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in, int eol) {
+ char *ns;
+ const char *s;
+ int line = 0;
+
+ UNUSED(srv);
+
+ buffer_copy_string_buffer(p->parse_response, in);
+
+ for (s = p->parse_response->ptr;
+ NULL != (ns = (eol == EOL_RN ? strstr(s, "\r\n") : strchr(s, '\n')));
+ s = ns + (eol == EOL_RN ? 2 : 1), line++) {
+ const char *key, *value;
+ int key_len;
+ data_string *ds;
+
+ ns[0] = '\0';
+
+ if (line == 0 &&
+ 0 == strncmp(s, "HTTP/1.", 7)) {
+ /* non-parsed header ... we parse them anyway */
+
+ if ((s[7] == '1' ||
+ s[7] == '0') &&
+ s[8] == ' ') {
+ int status;
+ /* after the space should be a status code for us */
+
+ status = strtol(s+9, NULL, 10);
+
+ if (con->http_status >= 100 &&
+ con->http_status < 1000) {
+ /* we expected 3 digits and didn't got them */
+ con->parsed_response |= HTTP_STATUS;
+ con->http_status = status;
+ }
+ }
+ } else {
+
+ key = s;
+ if (NULL == (value = strchr(s, ':'))) {
+ /* we expect: "<key>: <value>\r\n" */
+ continue;
+ }
+
+ key_len = value - key;
+ value += 1;
+
+ /* skip LWS */
+ while (*value == ' ' || *value == '\t') value++;
+
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
+ ds = data_response_init();
+ }
+ buffer_copy_string_len(ds->key, key, key_len);
+ buffer_copy_string(ds->value, value);
+
+ array_insert_unique(con->response.headers, (data_unset *)ds);
+
+ switch(key_len) {
+ case 4:
+ if (0 == strncasecmp(key, "Date", key_len)) {
+ con->parsed_response |= HTTP_DATE;
+ }
+ break;
+ case 6:
+ if (0 == strncasecmp(key, "Status", key_len)) {
+ con->http_status = strtol(value, NULL, 10);
+ con->parsed_response |= HTTP_STATUS;
+ }
+ break;
+ case 8:
+ if (0 == strncasecmp(key, "Location", key_len)) {
+ con->parsed_response |= HTTP_LOCATION;
+ }
+ break;
+ case 10:
+ if (0 == strncasecmp(key, "Connection", key_len)) {
+ con->response.keep_alive = (0 == strcasecmp(value, "Keep-Alive")) ? 1 : 0;
+ con->parsed_response |= HTTP_CONNECTION;
+ }
+ break;
+ case 14:
+ if (0 == strncasecmp(key, "Content-Length", key_len)) {
+ con->response.content_length = strtol(value, NULL, 10);
+ con->parsed_response |= HTTP_CONTENT_LENGTH;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /* CGI/1.1 rev 03 - 7.2.1.2 */
+ if ((con->parsed_response & HTTP_LOCATION) &&
+ !(con->parsed_response & HTTP_STATUS)) {
+ con->http_status = 302;
+ }
+
+ return 0;
+}
+
+
+static int cgi_demux_response(server *srv, handler_ctx *hctx) {
+ plugin_data *p = hctx->plugin_data;
+ connection *con = hctx->remote_conn;
+
+ while(1) {
+ int n;
+
+ buffer_prepare_copy(hctx->response, 1024);
+ if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) {
+ if (errno == EAGAIN || errno == EINTR) {
+ /* would block, wait for signal */
+ return FDEVENT_HANDLED_NOT_FINISHED;
+ }
+ /* error */
+ log_error_write(srv, __FILE__, __LINE__, "sdd", strerror(errno), con->fd, hctx->fd);
+ return FDEVENT_HANDLED_ERROR;
+ }
+
+ if (n == 0) {
+ /* read finished */
+
+ con->file_finished = 1;
+
+ /* send final chunk */
+ http_chunk_append_mem(srv, con, NULL, 0);
+ joblist_append(srv, con);
+
+ return FDEVENT_HANDLED_FINISHED;
+ }
+
+ hctx->response->ptr[n] = '\0';
+ hctx->response->used = n+1;
+
+ /* split header from body */
+
+ if (con->file_started == 0) {
+ char *c;
+ int in_header = 0;
+ int header_end = 0;
+ int cp, eol = EOL_UNSET;
+ size_t used = 0;
+
+ buffer_append_string_buffer(hctx->response_header, hctx->response);
+
+ /* nph (non-parsed headers) */
+ if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) in_header = 1;
+
+ /* search for the \r\n\r\n or \n\n in the string */
+ for (c = hctx->response_header->ptr, cp = 0, used = hctx->response_header->used - 1; used; c++, cp++, used--) {
+ if (*c == ':') in_header = 1;
+ else if (*c == '\n') {
+ if (in_header == 0) {
+ /* got a response without a response header */
+
+ c = NULL;
+ header_end = 1;
+ break;
+ }
+
+ if (eol == EOL_UNSET) eol = EOL_N;
+
+ if (*(c+1) == '\n') {
+ header_end = 1;
+ break;
+ }
+
+ } else if (used > 1 && *c == '\r' && *(c+1) == '\n') {
+ if (in_header == 0) {
+ /* got a response without a response header */
+
+ c = NULL;
+ header_end = 1;
+ break;
+ }
+
+ if (eol == EOL_UNSET) eol = EOL_RN;
+
+ if (used > 3 &&
+ *(c+2) == '\r' &&
+ *(c+3) == '\n') {
+ header_end = 1;
+ break;
+ }
+
+ /* skip the \n */
+ c++;
+ cp++;
+ used--;
+ }
+ }
+
+ if (header_end) {
+ if (c == NULL) {
+ /* no header, but a body */
+
+ if (con->request.http_version == HTTP_VERSION_1_1) {
+ con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
+ }
+
+ http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used);
+ joblist_append(srv, con);
+ } else {
+ size_t hlen = c - hctx->response_header->ptr + (eol == EOL_RN ? 4 : 2);
+ size_t blen = hctx->response_header->used - hlen - 1;
+
+ /* a small hack: terminate after at the second \r */
+ hctx->response_header->used = hlen + 1 - (eol == EOL_RN ? 2 : 1);
+ hctx->response_header->ptr[hlen - (eol == EOL_RN ? 2 : 1)] = '\0';
+
+ /* parse the response header */
+ cgi_response_parse(srv, con, p, hctx->response_header, eol);
+
+ /* enable chunked-transfer-encoding */
+ if (con->request.http_version == HTTP_VERSION_1_1 &&
+ !(con->parsed_response & HTTP_CONTENT_LENGTH)) {
+ con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
+ }
+
+ if ((hctx->response->used != hlen) && blen > 0) {
+ http_chunk_append_mem(srv, con, c + (eol == EOL_RN ? 4: 2), blen + 1);
+ joblist_append(srv, con);
+ }
+ }
+
+ con->file_started = 1;
+ }
+ } else {
+ http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used);
+ joblist_append(srv, con);
+ }
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), b->ptr);
+#endif
+ }
+
+ return FDEVENT_HANDLED_NOT_FINISHED;
+}
+
+static handler_t cgi_connection_close(server *srv, handler_ctx *hctx) {
+ int status;
+ pid_t pid;
+ plugin_data *p;
+ connection *con;
+
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+ p = hctx->plugin_data;
+ con = hctx->remote_conn;
+
+ if (con->mode != p->id) return HANDLER_GO_ON;
+
+#ifndef __WIN32
+
+ /* the connection to the browser went away, but we still have a connection
+ * to the CGI script
+ *
+ * close cgi-connection
+ */
+
+ if (hctx->fd != -1) {
+ /* close connection to the cgi-script */
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+
+ if (close(hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno));
+ }
+
+ hctx->fd = -1;
+ hctx->fde_ndx = -1;
+ }
+
+ pid = hctx->pid;
+
+ con->plugin_ctx[p->id] = NULL;
+
+ /* is this a good idea ? */
+ cgi_handler_ctx_free(hctx);
+
+ /* if waitpid hasn't been called by response.c yet, do it here */
+ if (pid) {
+ /* check if the CGI-script is already gone */
+ switch(waitpid(pid, &status, WNOHANG)) {
+ case 0:
+ /* not finished yet */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) child isn't done yet, pid:", pid);
+#endif
+ break;
+ case -1:
+ /* */
+ if (errno == EINTR) break;
+
+ /*
+ * errno == ECHILD happens if _subrequest catches the process-status before
+ * we have read the response of the cgi process
+ *
+ * -> catch status
+ * -> WAIT_FOR_EVENT
+ * -> read response
+ * -> we get here with waitpid == ECHILD
+ *
+ */
+ if (errno == ECHILD) return HANDLER_GO_ON;
+
+ log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno));
+ return HANDLER_ERROR;
+ default:
+ /* Send an error if we haven't sent any data yet */
+ if (0 == con->file_started) {
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ con->http_status = 500;
+ con->mode = DIRECT;
+ }
+
+ if (WIFEXITED(status)) {
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) cgi exited fine, pid:", pid);
+#endif
+ pid = 0;
+
+ return HANDLER_GO_ON;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "cgi died, pid:", pid);
+ pid = 0;
+ return HANDLER_GO_ON;
+ }
+ }
+
+
+ kill(pid, SIGTERM);
+
+ /* cgi-script is still alive, queue the PID for removal */
+ cgi_pid_add(srv, p, pid);
+ }
+#endif
+ return HANDLER_GO_ON;
+}
+
+static handler_t cgi_connection_close_callback(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ return cgi_connection_close(srv, con->plugin_ctx[p->id]);
+}
+
+
+static handler_t cgi_handle_fdevent(void *s, void *ctx, int revents) {
+ server *srv = (server *)s;
+ handler_ctx *hctx = ctx;
+ connection *con = hctx->remote_conn;
+
+ joblist_append(srv, con);
+
+ if (hctx->fd == -1) {
+ log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), "invalid cgi-fd");
+
+ return HANDLER_ERROR;
+ }
+
+ if (revents & FDEVENT_IN) {
+ switch (cgi_demux_response(srv, hctx)) {
+ case FDEVENT_HANDLED_NOT_FINISHED:
+ break;
+ case FDEVENT_HANDLED_FINISHED:
+ /* we are done */
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), "finished");
+#endif
+ cgi_connection_close(srv, hctx);
+
+ /* if we get a IN|HUP and have read everything don't exec the close twice */
+ return HANDLER_FINISHED;
+ case FDEVENT_HANDLED_ERROR:
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ con->http_status = 500;
+ con->mode = DIRECT;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "demuxer failed: ");
+ break;
+ }
+ }
+
+ if (revents & FDEVENT_OUT) {
+ /* nothing to do */
+ }
+
+ /* perhaps this issue is already handled */
+ if (revents & FDEVENT_HUP) {
+ /* check if we still have a unfinished header package which is a body in reality */
+ if (con->file_started == 0 &&
+ hctx->response_header->used) {
+ con->file_started = 1;
+ http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used);
+ joblist_append(srv, con);
+ }
+
+ if (con->file_finished == 0) {
+ http_chunk_append_mem(srv, con, NULL, 0);
+ joblist_append(srv, con);
+ }
+
+ con->file_finished = 1;
+
+ if (chunkqueue_is_empty(con->write_queue)) {
+ /* there is nothing left to write */
+ connection_set_state(srv, con, CON_STATE_RESPONSE_END);
+ } else {
+ /* used the write-handler to finish the request on demand */
+
+ }
+
+# if 0
+ log_error_write(srv, __FILE__, __LINE__, "sddd", "got HUP from cgi", con->fd, hctx->fd, revents);
+# endif
+
+ /* rtsigs didn't liked the close */
+ cgi_connection_close(srv, hctx);
+ } else if (revents & FDEVENT_ERR) {
+ con->file_finished = 1;
+
+ /* kill all connections to the cgi process */
+ cgi_connection_close(srv, hctx);
+#if 1
+ log_error_write(srv, __FILE__, __LINE__, "s", "cgi-FDEVENT_ERR");
+#endif
+ return HANDLER_ERROR;
+ }
+
+ return HANDLER_FINISHED;
+}
+
+
+static int cgi_env_add(char_array *env, const char *key, size_t key_len, const char *val, size_t val_len) {
+ char *dst;
+
+ if (!key || !val) return -1;
+
+ dst = malloc(key_len + val_len + 3);
+ memcpy(dst, key, key_len);
+ dst[key_len] = '=';
+ /* add the \0 from the value */
+ memcpy(dst + key_len + 1, val, val_len + 1);
+
+ if (env->size == 0) {
+ env->size = 16;
+ env->ptr = malloc(env->size * sizeof(*env->ptr));
+ } else if (env->size == env->used) {
+ env->size += 16;
+ env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
+ }
+
+ env->ptr[env->used++] = dst;
+
+ return 0;
+}
+
+static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *cgi_handler) {
+ pid_t pid;
+
+#ifdef HAVE_IPV6
+ char b2[INET6_ADDRSTRLEN + 1];
+#endif
+
+ int to_cgi_fds[2];
+ int from_cgi_fds[2];
+ struct stat st;
+
+#ifndef __WIN32
+
+ if (cgi_handler->used > 1) {
+ /* stat the exec file */
+ if (-1 == (stat(cgi_handler->ptr, &st))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "stat for cgi-handler", cgi_handler,
+ "failed:", strerror(errno));
+ return -1;
+ }
+ }
+
+ if (pipe(to_cgi_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
+ return -1;
+ }
+
+ if (pipe(from_cgi_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
+ return -1;
+ }
+
+ /* fork, execve */
+ switch (pid = fork()) {
+ case 0: {
+ /* child */
+ char **args;
+ int argc;
+ int i = 0;
+ char buf[32];
+ size_t n;
+ char_array env;
+ char *c;
+ const char *s;
+ server_socket *srv_sock = con->srv_socket;
+
+ /* move stdout to from_cgi_fd[1] */
+ close(STDOUT_FILENO);
+ dup2(from_cgi_fds[1], STDOUT_FILENO);
+ close(from_cgi_fds[1]);
+ /* not needed */
+ close(from_cgi_fds[0]);
+
+ /* move the stdin to to_cgi_fd[0] */
+ close(STDIN_FILENO);
+ dup2(to_cgi_fds[0], STDIN_FILENO);
+ close(to_cgi_fds[0]);
+ /* not needed */
+ close(to_cgi_fds[1]);
+
+ /* HACK:
+ * this is not nice, but it works
+ *
+ * we feed the stderr of the CGI to our errorlog, if possible
+ */
+ if (srv->errorlog_mode == ERRORLOG_FILE) {
+ close(STDERR_FILENO);
+ dup2(srv->errorlog_fd, STDERR_FILENO);
+ }
+
+ /* create environment */
+ env.ptr = NULL;
+ env.size = 0;
+ env.used = 0;
+
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_NAME"/"PACKAGE_VERSION));
+
+ if (!buffer_is_empty(con->server_name)) {
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), CONST_BUF_LEN(con->server_name));
+ } else {
+#ifdef HAVE_IPV6
+ s = inet_ntop(srv_sock->addr.plain.sa_family,
+ srv_sock->addr.plain.sa_family == AF_INET6 ?
+ (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
+ (const void *) &(srv_sock->addr.ipv4.sin_addr),
+ b2, sizeof(b2)-1);
+#else
+ s = inet_ntoa(srv_sock->addr.ipv4.sin_addr);
+#endif
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s));
+ }
+ cgi_env_add(&env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1"));
+
+ s = get_http_version_name(con->request.http_version);
+
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s));
+
+ ltostr(buf,
+#ifdef HAVE_IPV6
+ ntohs(srv_sock->addr.plain.sa_family == AF_INET6 ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port)
+#else
+ ntohs(srv_sock->addr.ipv4.sin_port)
+#endif
+ );
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_PORT"), buf, strlen(buf));
+
+#ifdef HAVE_IPV6
+ s = inet_ntop(srv_sock->addr.plain.sa_family,
+ srv_sock->addr.plain.sa_family == AF_INET6 ?
+ (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
+ (const void *) &(srv_sock->addr.ipv4.sin_addr),
+ b2, sizeof(b2)-1);
+#else
+ s = inet_ntoa(srv_sock->addr.ipv4.sin_addr);
+#endif
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s));
+
+ s = get_http_method_name(con->request.http_method);
+ cgi_env_add(&env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s));
+
+ if (!buffer_is_empty(con->request.pathinfo)) {
+ cgi_env_add(&env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo));
+ }
+ cgi_env_add(&env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200"));
+ cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query));
+ cgi_env_add(&env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri));
+
+
+#ifdef HAVE_IPV6
+ s = inet_ntop(con->dst_addr.plain.sa_family,
+ con->dst_addr.plain.sa_family == AF_INET6 ?
+ (const void *) &(con->dst_addr.ipv6.sin6_addr) :
+ (const void *) &(con->dst_addr.ipv4.sin_addr),
+ b2, sizeof(b2)-1);
+#else
+ s = inet_ntoa(con->dst_addr.ipv4.sin_addr);
+#endif
+ cgi_env_add(&env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s));
+
+ ltostr(buf,
+#ifdef HAVE_IPV6
+ ntohs(con->dst_addr.plain.sa_family == AF_INET6 ? con->dst_addr.ipv6.sin6_port : con->dst_addr.ipv4.sin_port)
+#else
+ ntohs(con->dst_addr.ipv4.sin_port)
+#endif
+ );
+ cgi_env_add(&env, CONST_STR_LEN("REMOTE_PORT"), buf, strlen(buf));
+
+ if (!buffer_is_empty(con->authed_user)) {
+ cgi_env_add(&env, CONST_STR_LEN("REMOTE_USER"),
+ CONST_BUF_LEN(con->authed_user));
+ }
+
+ /* request.content_length < SSIZE_MAX, see request.c */
+ ltostr(buf, con->request.content_length);
+ cgi_env_add(&env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf));
+ cgi_env_add(&env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path));
+ cgi_env_add(&env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path));
+ cgi_env_add(&env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root));
+
+ /* for valgrind */
+ if (NULL != (s = getenv("LD_PRELOAD"))) {
+ cgi_env_add(&env, CONST_STR_LEN("LD_PRELOAD"), s, strlen(s));
+ }
+
+ if (NULL != (s = getenv("LD_LIBRARY_PATH"))) {
+ cgi_env_add(&env, CONST_STR_LEN("LD_LIBRARY_PATH"), s, strlen(s));
+ }
+#ifdef __CYGWIN__
+ /* CYGWIN needs SYSTEMROOT */
+ if (NULL != (s = getenv("SYSTEMROOT"))) {
+ cgi_env_add(&env, CONST_STR_LEN("SYSTEMROOT"), s, strlen(s));
+ }
+#endif
+
+ for (n = 0; n < con->request.headers->used; n++) {
+ data_string *ds;
+
+ ds = (data_string *)con->request.headers->data[n];
+
+ if (ds->value->used && ds->key->used) {
+ size_t j;
+
+ buffer_reset(p->tmp_buf);
+
+ if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
+ buffer_copy_string(p->tmp_buf, "HTTP_");
+ p->tmp_buf->used--; /* strip \0 after HTTP_ */
+ }
+
+ buffer_prepare_append(p->tmp_buf, ds->key->used + 2);
+
+ for (j = 0; j < ds->key->used - 1; j++) {
+ char cr = '_';
+ if (light_isalpha(ds->key->ptr[j])) {
+ /* upper-case */
+ cr = ds->key->ptr[j] & ~32;
+ } else if (light_isdigit(ds->key->ptr[j])) {
+ /* copy */
+ cr = ds->key->ptr[j];
+ }
+ p->tmp_buf->ptr[p->tmp_buf->used++] = cr;
+ }
+ p->tmp_buf->ptr[p->tmp_buf->used++] = '\0';
+
+ cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value));
+ }
+ }
+
+ for (n = 0; n < con->environment->used; n++) {
+ data_string *ds;
+
+ ds = (data_string *)con->environment->data[n];
+
+ if (ds->value->used && ds->key->used) {
+ size_t j;
+
+ buffer_reset(p->tmp_buf);
+
+ buffer_prepare_append(p->tmp_buf, ds->key->used + 2);
+
+ for (j = 0; j < ds->key->used - 1; j++) {
+ p->tmp_buf->ptr[p->tmp_buf->used++] =
+ isalpha((unsigned char)ds->key->ptr[j]) ?
+ toupper((unsigned char)ds->key->ptr[j]) : '_';
+ }
+ p->tmp_buf->ptr[p->tmp_buf->used++] = '\0';
+
+ cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value));
+ }
+ }
+
+ if (env.size == env.used) {
+ env.size += 16;
+ env.ptr = realloc(env.ptr, env.size * sizeof(*env.ptr));
+ }
+
+ env.ptr[env.used] = NULL;
+
+ /* set up args */
+ argc = 3;
+ args = malloc(sizeof(*args) * argc);
+ i = 0;
+
+ if (cgi_handler->used > 1) {
+ args[i++] = cgi_handler->ptr;
+ }
+ args[i++] = con->physical.path->ptr;
+ args[i++] = NULL;
+
+ /* search for the last / */
+ if (NULL != (c = strrchr(con->physical.path->ptr, '/'))) {
+ *c = '\0';
+
+ /* change to the physical directory */
+ if (-1 == chdir(con->physical.path->ptr)) {
+ log_error_write(srv, __FILE__, __LINE__, "ssb", "chdir failed:", strerror(errno), con->physical.path);
+ }
+ *c = '/';
+ }
+
+ /* we don't need the client socket */
+ for (i = 3; i < 256; i++) {
+ if (i != srv->errorlog_fd) close(i);
+ }
+
+ /* exec the cgi */
+ execve(args[0], args, env.ptr);
+
+ log_error_write(srv, __FILE__, __LINE__, "sss", "CGI failed:", strerror(errno), args[0]);
+
+ /* */
+ SEGFAULT();
+ break;
+ }
+ case -1:
+ /* error */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
+ break;
+ default: {
+ handler_ctx *hctx;
+ /* father */
+
+ if (con->request.content_length) {
+ chunkqueue *cq = con->request_content_queue;
+ chunk *c;
+
+ assert(chunkqueue_length(cq) == (off_t)con->request.content_length);
+
+ /* there is content to send */
+ for (c = cq->first; c; c = cq->first) {
+ int r = 0;
+
+ /* copy all chunks */
+ switch(c->type) {
+ case FILE_CHUNK:
+
+ if (c->file.mmap.start == MAP_FAILED) {
+ if (-1 == c->file.fd && /* open the file if not already open */
+ -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
+
+ return -1;
+ }
+
+ c->file.mmap.length = c->file.length;
+
+ if (MAP_FAILED == (c->file.mmap.start = mmap(0, c->file.mmap.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ",
+ strerror(errno), c->file.name, c->file.fd);
+
+ return -1;
+ }
+
+ close(c->file.fd);
+ c->file.fd = -1;
+
+ /* chunk_reset() or chunk_free() will cleanup for us */
+ }
+
+ if ((r = write(to_cgi_fds[1], c->file.mmap.start + c->offset, c->file.length - c->offset)) < 0) {
+ switch(errno) {
+ case ENOSPC:
+ con->http_status = 507;
+
+ break;
+ default:
+ con->http_status = 403;
+ break;
+ }
+ }
+ break;
+ case MEM_CHUNK:
+ if ((r = write(to_cgi_fds[1], c->mem->ptr + c->offset, c->mem->used - c->offset - 1)) < 0) {
+ switch(errno) {
+ case ENOSPC:
+ con->http_status = 507;
+
+ break;
+ default:
+ con->http_status = 403;
+ break;
+ }
+ }
+ break;
+ case UNUSED_CHUNK:
+ break;
+ }
+
+ if (r > 0) {
+ c->offset += r;
+ cq->bytes_out += r;
+ } else {
+ break;
+ }
+ chunkqueue_remove_finished_chunks(cq);
+ }
+ }
+
+ close(from_cgi_fds[1]);
+
+ close(to_cgi_fds[0]);
+ close(to_cgi_fds[1]);
+
+ /* register PID and wait for them asyncronously */
+ con->mode = p->id;
+ buffer_reset(con->physical.path);
+
+ hctx = cgi_handler_ctx_init();
+
+ hctx->remote_conn = con;
+ hctx->plugin_data = p;
+ hctx->pid = pid;
+ hctx->fd = from_cgi_fds[0];
+ hctx->fde_ndx = -1;
+
+ con->plugin_ctx[p->id] = hctx;
+
+ fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx);
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+
+ if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
+
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+
+ log_error_write(srv, __FILE__, __LINE__, "sd", "cgi close:", hctx->fd);
+
+ close(hctx->fd);
+
+ cgi_handler_ctx_free(hctx);
+
+ con->plugin_ctx[p->id] = NULL;
+
+ return -1;
+ }
+
+ break;
+ }
+ }
+
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(cgi);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.assign"))) {
+ PATCH(cgi);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(cgi_is_handled) {
+ size_t k, s_len;
+ plugin_data *p = p_d;
+ buffer *fn = con->physical.path;
+
+ if (fn->used == 0) return HANDLER_ERROR;
+
+ mod_cgi_patch_connection(srv, con, p);
+
+ s_len = fn->used - 1;
+
+ for (k = 0; k < p->conf.cgi->used; k++) {
+ data_string *ds = (data_string *)p->conf.cgi->data[k];
+ size_t ct_len = ds->key->used - 1;
+
+ if (ds->key->used == 0) continue;
+ if (s_len < ct_len) continue;
+
+ if (0 == strncmp(fn->ptr + s_len - ct_len, ds->key->ptr, ct_len)) {
+ if (cgi_create_env(srv, con, p, ds->value)) {
+ con->http_status = 500;
+
+ buffer_reset(con->physical.path);
+ return HANDLER_FINISHED;
+ }
+ /* one handler is enough for the request */
+ break;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+TRIGGER_FUNC(cgi_trigger) {
+ plugin_data *p = p_d;
+ size_t ndx;
+ /* the trigger handle only cares about lonely PID which we have to wait for */
+#ifndef __WIN32
+
+ for (ndx = 0; ndx < p->cgi_pid.used; ndx++) {
+ int status;
+
+ switch(waitpid(p->cgi_pid.ptr[ndx], &status, WNOHANG)) {
+ case 0:
+ /* not finished yet */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) child isn't done yet, pid:", p->cgi_pid.ptr[ndx]);
+#endif
+ break;
+ case -1:
+ log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno));
+
+ return HANDLER_ERROR;
+ default:
+
+ if (WIFEXITED(status)) {
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) cgi exited fine, pid:", p->cgi_pid.ptr[ndx]);
+#endif
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "cgi died ?");
+ }
+
+ cgi_pid_del(srv, p, p->cgi_pid.ptr[ndx]);
+ /* del modified the buffer structure
+ * and copies the last entry to the current one
+ * -> recheck the current index
+ */
+ ndx--;
+ }
+ }
+#endif
+ return HANDLER_GO_ON;
+}
+
+SUBREQUEST_FUNC(mod_cgi_handle_subrequest) {
+ int status;
+ plugin_data *p = p_d;
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+
+ if (con->mode != p->id) return HANDLER_GO_ON;
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdd", "subrequest, pid =", hctx, hctx->pid);
+#endif
+ if (hctx->pid == 0) return HANDLER_FINISHED;
+#ifndef __WIN32
+ switch(waitpid(hctx->pid, &status, WNOHANG)) {
+ case 0:
+ /* we only have for events here if we don't have the header yet,
+ * otherwise the event-handler will send us the incoming data */
+ if (con->file_started) return HANDLER_FINISHED;
+
+ return HANDLER_WAIT_FOR_EVENT;
+ case -1:
+ if (errno == EINTR) return HANDLER_WAIT_FOR_EVENT;
+
+ if (errno == ECHILD && con->file_started == 0) {
+ /*
+ * second round but still not response
+ */
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno));
+ con->mode = DIRECT;
+ con->http_status = 500;
+
+ hctx->pid = 0;
+
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+
+ if (close(hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno));
+ }
+
+ cgi_handler_ctx_free(hctx);
+
+ con->plugin_ctx[p->id] = NULL;
+
+ return HANDLER_FINISHED;
+ default:
+ /* cgi process exited cleanly
+ *
+ * check if we already got the response
+ */
+
+ if (!con->file_started) return HANDLER_WAIT_FOR_EVENT;
+
+ if (WIFEXITED(status)) {
+ /* nothing */
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "cgi died ?");
+
+ con->mode = DIRECT;
+ con->http_status = 500;
+
+ }
+
+ hctx->pid = 0;
+
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+
+ if (close(hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno));
+ }
+
+ cgi_handler_ctx_free(hctx);
+
+ con->plugin_ctx[p->id] = NULL;
+ return HANDLER_FINISHED;
+ }
+#else
+ return HANDLER_ERROR;
+#endif
+}
+
+
+int mod_cgi_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("cgi");
+
+ p->connection_reset = cgi_connection_close_callback;
+ p->handle_subrequest_start = cgi_is_handled;
+ p->handle_subrequest = mod_cgi_handle_subrequest;
+#if 0
+ p->handle_fdevent = cgi_handle_fdevent;
+#endif
+ p->handle_trigger = cgi_trigger;
+ p->init = mod_cgi_init;
+ p->cleanup = mod_cgi_free;
+ p->set_defaults = mod_fastcgi_set_defaults;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_cml.c b/src/mod_cml.c
new file mode 100644
index 0000000..f5b1768
--- /dev/null
+++ b/src/mod_cml.c
@@ -0,0 +1,325 @@
+#include <sys/stat.h>
+#include <time.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include "buffer.h"
+#include "server.h"
+#include "log.h"
+#include "plugin.h"
+#include "response.h"
+
+#include "stream.h"
+
+#include "mod_cml.h"
+
+/* init the plugin data */
+INIT_FUNC(mod_cml_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->basedir = buffer_init();
+ p->baseurl = buffer_init();
+ p->trigger_handler = buffer_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_cml_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free(s->ext);
+
+ buffer_free(s->mc_namespace);
+ buffer_free(s->power_magnet);
+ array_free(s->mc_hosts);
+
+#if defined(HAVE_MEMCACHE_H)
+ if (s->mc) mc_free(s->mc);
+#endif
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->trigger_handler);
+ buffer_free(p->basedir);
+ buffer_free(p->baseurl);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_cml_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "cml.extension", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "cml.memcache-hosts", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "cml.memcache-namespace", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "cml.power-magnet", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->ext = buffer_init();
+ s->mc_hosts = array_init();
+ s->mc_namespace = buffer_init();
+ s->power_magnet = buffer_init();
+#if defined(HAVE_MEMCACHE_H)
+ s->mc = NULL;
+#endif
+
+ cv[0].destination = s->ext;
+ cv[1].destination = s->mc_hosts;
+ cv[2].destination = s->mc_namespace;
+ cv[3].destination = s->power_magnet;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (s->mc_hosts->used) {
+#if defined(HAVE_MEMCACHE_H)
+ size_t k;
+ s->mc = mc_new();
+
+ for (k = 0; k < s->mc_hosts->used; k++) {
+ data_string *ds = (data_string *)s->mc_hosts->data[k];
+
+ if (0 != mc_server_add4(s->mc, ds->value->ptr)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "connection to host failed:",
+ ds->value);
+
+ return HANDLER_ERROR;
+ }
+ }
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "memcache support is not compiled in but cml.memcache-hosts is set, aborting");
+ return HANDLER_ERROR;
+#endif
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_cml_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(ext);
+#if defined(HAVE_MEMCACHE_H)
+ PATCH(mc);
+#endif
+ PATCH(mc_namespace);
+ PATCH(power_magnet);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("cml.extension"))) {
+ PATCH(ext);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cml.memcache-hosts"))) {
+#if defined(HAVE_MEMCACHE_H)
+ PATCH(mc);
+#endif
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cml.memcache-namespace"))) {
+ PATCH(mc_namespace);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cml.power-magnet"))) {
+ PATCH(power_magnet);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+int cache_call_lua(server *srv, connection *con, plugin_data *p, buffer *cml_file) {
+ buffer *b;
+ char *c;
+ int ret;
+
+ /* cleanup basedir */
+ b = p->baseurl;
+ buffer_copy_string_buffer(b, con->uri.path);
+ for (c = b->ptr + b->used - 1; c > b->ptr && *c != '/'; c--);
+
+ if (*c == '/') {
+ b->used = c - b->ptr + 2;
+ *(c+1) = '\0';
+ }
+
+ b = p->basedir;
+ buffer_copy_string_buffer(b, con->physical.path);
+ for (c = b->ptr + b->used - 1; c > b->ptr && *c != '/'; c--);
+
+ if (*c == '/') {
+ b->used = c - b->ptr + 2;
+ *(c+1) = '\0';
+ }
+
+
+ /* prepare variables
+ * - cookie-based
+ * - get-param-based
+ */
+
+ return cache_parse_lua(srv, con, p, cml_file);
+
+}
+
+URIHANDLER_FUNC(mod_cml_power_magnet) {
+ plugin_data *p = p_d;
+
+ mod_cml_patch_connection(srv, con, p);
+
+ buffer_reset(p->basedir);
+ buffer_reset(p->baseurl);
+ buffer_reset(p->trigger_handler);
+
+ if (buffer_is_empty(p->conf.power_magnet)) return HANDLER_GO_ON;
+
+ /*
+ * power-magnet:
+ * cml.power-magnet = server.docroot + "/rewrite.cml"
+ *
+ * is called on EACH request, take the original REQUEST_URI and modifies the
+ * request header as neccesary.
+ *
+ * First use:
+ * if file_exists("/maintainance.html") {
+ * output_include = ( "/maintainance.html" )
+ * return CACHE_HIT
+ * }
+ *
+ * as we only want to rewrite HTML like requests we should cover it in a conditional
+ *
+ * */
+
+ switch(cache_call_lua(srv, con, p, p->conf.power_magnet)) {
+ case -1:
+ /* error */
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "cache-error");
+ }
+ con->http_status = 500;
+ return HANDLER_COMEBACK;
+ case 0:
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "cache-hit");
+ }
+ /* cache-hit */
+ buffer_reset(con->physical.path);
+ return HANDLER_FINISHED;
+ case 1:
+ /* cache miss */
+ return HANDLER_GO_ON;
+ default:
+ con->http_status = 500;
+ return HANDLER_COMEBACK;
+ }
+}
+
+URIHANDLER_FUNC(mod_cml_is_handled) {
+ plugin_data *p = p_d;
+
+ if (buffer_is_empty(con->physical.path)) return HANDLER_ERROR;
+
+ mod_cml_patch_connection(srv, con, p);
+
+ buffer_reset(p->basedir);
+ buffer_reset(p->baseurl);
+ buffer_reset(p->trigger_handler);
+
+ if (buffer_is_empty(p->conf.ext)) return HANDLER_GO_ON;
+
+ if (!buffer_is_equal_right_len(con->physical.path, p->conf.ext, p->conf.ext->used - 1)) {
+ return HANDLER_GO_ON;
+ }
+
+ switch(cache_call_lua(srv, con, p, con->physical.path)) {
+ case -1:
+ /* error */
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "cache-error");
+ }
+ con->http_status = 500;
+ return HANDLER_COMEBACK;
+ case 0:
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "cache-hit");
+ }
+ /* cache-hit */
+ buffer_reset(con->physical.path);
+ return HANDLER_FINISHED;
+ case 1:
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "cache-miss");
+ }
+ /* cache miss */
+ return HANDLER_COMEBACK;
+ default:
+ con->http_status = 500;
+ return HANDLER_COMEBACK;
+ }
+}
+
+int mod_cml_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("cache");
+
+ p->init = mod_cml_init;
+ p->cleanup = mod_cml_free;
+ p->set_defaults = mod_cml_set_defaults;
+
+ p->handle_subrequest_start = mod_cml_is_handled;
+ p->handle_physical = mod_cml_power_magnet;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_cml.h b/src/mod_cml.h
new file mode 100644
index 0000000..992af68
--- /dev/null
+++ b/src/mod_cml.h
@@ -0,0 +1,43 @@
+#ifndef _MOD_CACHE_H_
+#define _MOD_CACHE_H_
+
+#include "buffer.h"
+#include "server.h"
+#include "response.h"
+
+#include "stream.h"
+#include "plugin.h"
+
+#if defined(HAVE_MEMCACHE_H)
+#include <memcache.h>
+#endif
+
+#define plugin_data mod_cache_plugin_data
+
+typedef struct {
+ buffer *ext;
+
+ array *mc_hosts;
+ buffer *mc_namespace;
+#if defined(HAVE_MEMCACHE_H)
+ struct memcache *mc;
+#endif
+ buffer *power_magnet;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *basedir;
+ buffer *baseurl;
+
+ buffer *trigger_handler;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn);
+
+#endif
diff --git a/src/mod_cml_funcs.c b/src/mod_cml_funcs.c
new file mode 100644
index 0000000..46a1f16
--- /dev/null
+++ b/src/mod_cml_funcs.c
@@ -0,0 +1,301 @@
+#include <sys/stat.h>
+#include <time.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <stdio.h>
+
+#include "buffer.h"
+#include "server.h"
+#include "log.h"
+#include "plugin.h"
+#include "response.h"
+
+#include "mod_cml.h"
+#include "mod_cml_funcs.h"
+
+#ifdef USE_OPENSSL
+# include <openssl/md5.h>
+#else
+# include "md5.h"
+#endif
+
+#define HASHLEN 16
+typedef unsigned char HASH[HASHLEN];
+#define HASHHEXLEN 32
+typedef char HASHHEX[HASHHEXLEN+1];
+#ifdef USE_OPENSSL
+#define IN const
+#else
+#define IN
+#endif
+#define OUT
+
+#ifdef HAVE_LUA_H
+
+int f_crypto_md5(lua_State *L) {
+ MD5_CTX Md5Ctx;
+ HASH HA1;
+ buffer b;
+ char hex[33];
+ int n = lua_gettop(L);
+
+ b.ptr = hex;
+ b.used = 0;
+ b.size = sizeof(hex);
+
+ if (n != 1) {
+ lua_pushstring(L, "md5: expected one argument");
+ lua_error(L);
+ }
+
+ if (!lua_isstring(L, 1)) {
+ lua_pushstring(L, "md5: argument has to be a string");
+ lua_error(L);
+ }
+
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)lua_tostring(L, 1), lua_strlen(L, 1));
+ MD5_Final(HA1, &Md5Ctx);
+
+ buffer_copy_string_hex(&b, (char *)HA1, 16);
+
+ lua_pushstring(L, b.ptr);
+
+ return 1;
+}
+
+
+int f_file_mtime(lua_State *L) {
+ struct stat st;
+ int n = lua_gettop(L);
+
+ if (n != 1) {
+ lua_pushstring(L, "file_mtime: expected one argument");
+ lua_error(L);
+ }
+
+ if (!lua_isstring(L, 1)) {
+ lua_pushstring(L, "file_mtime: argument has to be a string");
+ lua_error(L);
+ }
+
+ if (-1 == stat(lua_tostring(L, 1), &st)) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_pushnumber(L, st.st_mtime);
+
+ return 1;
+}
+
+int f_dir_files_iter(lua_State *L) {
+ DIR *d;
+ struct dirent *de;
+
+ d = lua_touserdata(L, lua_upvalueindex(1));
+
+ if (NULL == (de = readdir(d))) {
+ /* EOF */
+ closedir(d);
+
+ return 0;
+ } else {
+ lua_pushstring(L, de->d_name);
+ return 1;
+ }
+}
+
+int f_dir_files(lua_State *L) {
+ DIR *d;
+ int n = lua_gettop(L);
+
+ if (n != 1) {
+ lua_pushstring(L, "dir_files: expected one argument");
+ lua_error(L);
+ }
+
+ if (!lua_isstring(L, 1)) {
+ lua_pushstring(L, "dir_files: argument has to be a string");
+ lua_error(L);
+ }
+
+ /* check if there is a valid DIR handle on the stack */
+ if (NULL == (d = opendir(lua_tostring(L, 1)))) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ /* push d into registry */
+ lua_pushlightuserdata(L, d);
+ lua_pushcclosure(L, f_dir_files_iter, 1);
+
+ return 1;
+}
+
+int f_file_isreg(lua_State *L) {
+ struct stat st;
+ int n = lua_gettop(L);
+
+ if (n != 1) {
+ lua_pushstring(L, "file_isreg: expected one argument");
+ lua_error(L);
+ }
+
+ if (!lua_isstring(L, 1)) {
+ lua_pushstring(L, "file_isreg: argument has to be a string");
+ lua_error(L);
+ }
+
+ if (-1 == stat(lua_tostring(L, 1), &st)) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_pushnumber(L, S_ISREG(st.st_mode));
+
+ return 1;
+}
+
+int f_file_isdir(lua_State *L) {
+ struct stat st;
+ int n = lua_gettop(L);
+
+ if (n != 1) {
+ lua_pushstring(L, "file_isreg: expected one argument");
+ lua_error(L);
+ }
+
+ if (!lua_isstring(L, 1)) {
+ lua_pushstring(L, "file_isreg: argument has to be a string");
+ lua_error(L);
+ }
+
+ if (-1 == stat(lua_tostring(L, 1), &st)) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_pushnumber(L, S_ISDIR(st.st_mode));
+
+ return 1;
+}
+
+
+
+#ifdef HAVE_MEMCACHE_H
+int f_memcache_exists(lua_State *L) {
+ char *r;
+ int n = lua_gettop(L);
+ struct memcache *mc;
+
+ if (!lua_islightuserdata(L, lua_upvalueindex(1))) {
+ lua_pushstring(L, "where is my userdata ?");
+ lua_error(L);
+ }
+
+ mc = lua_touserdata(L, lua_upvalueindex(1));
+
+ if (n != 1) {
+ lua_pushstring(L, "expected one argument");
+ lua_error(L);
+ }
+
+ if (!lua_isstring(L, 1)) {
+ lua_pushstring(L, "argument has to be a string");
+ lua_error(L);
+ }
+
+ if (NULL == (r = mc_aget(mc,
+ lua_tostring(L, 1), lua_strlen(L, 1)))) {
+
+ lua_pushboolean(L, 0);
+ return 1;
+ }
+
+ free(r);
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+int f_memcache_get_string(lua_State *L) {
+ char *r;
+ int n = lua_gettop(L);
+
+ struct memcache *mc;
+
+ if (!lua_islightuserdata(L, lua_upvalueindex(1))) {
+ lua_pushstring(L, "where is my userdata ?");
+ lua_error(L);
+ }
+
+ mc = lua_touserdata(L, lua_upvalueindex(1));
+
+
+ if (n != 1) {
+ lua_pushstring(L, "expected one argument");
+ lua_error(L);
+ }
+
+ if (!lua_isstring(L, 1)) {
+ lua_pushstring(L, "argument has to be a string");
+ lua_error(L);
+ }
+
+ if (NULL == (r = mc_aget(mc,
+ lua_tostring(L, 1), lua_strlen(L, 1)))) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_pushstring(L, r);
+
+ free(r);
+
+ return 1;
+}
+
+int f_memcache_get_long(lua_State *L) {
+ char *r;
+ int n = lua_gettop(L);
+
+ struct memcache *mc;
+
+ if (!lua_islightuserdata(L, lua_upvalueindex(1))) {
+ lua_pushstring(L, "where is my userdata ?");
+ lua_error(L);
+ }
+
+ mc = lua_touserdata(L, lua_upvalueindex(1));
+
+
+ if (n != 1) {
+ lua_pushstring(L, "expected one argument");
+ lua_error(L);
+ }
+
+ if (!lua_isstring(L, 1)) {
+ lua_pushstring(L, "argument has to be a string");
+ lua_error(L);
+ }
+
+ if (NULL == (r = mc_aget(mc,
+ lua_tostring(L, 1), lua_strlen(L, 1)))) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_pushnumber(L, strtol(r, NULL, 10));
+
+ free(r);
+
+ return 1;
+}
+#endif
+
+#endif
diff --git a/src/mod_cml_funcs.h b/src/mod_cml_funcs.h
new file mode 100644
index 0000000..42e067c
--- /dev/null
+++ b/src/mod_cml_funcs.h
@@ -0,0 +1,21 @@
+#ifndef _MOD_CML_FUNCS_H_
+#define _MOD_CML_FUNCS_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_LUA_H
+#include <lua.h>
+
+int f_crypto_md5(lua_State *L);
+int f_file_mtime(lua_State *L);
+int f_file_isreg(lua_State *L);
+int f_file_isdir(lua_State *L);
+int f_dir_files(lua_State *L);
+
+int f_memcache_exists(lua_State *L);
+int f_memcache_get_string(lua_State *L);
+int f_memcache_get_long(lua_State *L);
+#endif
+#endif
diff --git a/src/mod_cml_lua.c b/src/mod_cml_lua.c
new file mode 100644
index 0000000..76e979f
--- /dev/null
+++ b/src/mod_cml_lua.c
@@ -0,0 +1,468 @@
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <time.h>
+
+#include "mod_cml.h"
+#include "mod_cml_funcs.h"
+#include "log.h"
+#include "stream.h"
+
+#include "stat_cache.h"
+
+#ifdef USE_OPENSSL
+# include <openssl/md5.h>
+#else
+# include "md5.h"
+#endif
+
+#define HASHLEN 16
+typedef unsigned char HASH[HASHLEN];
+#define HASHHEXLEN 32
+typedef char HASHHEX[HASHHEXLEN+1];
+#ifdef USE_OPENSSL
+#define IN const
+#else
+#define IN
+#endif
+#define OUT
+
+#ifdef HAVE_LUA_H
+
+#include <lua.h>
+#include <lualib.h>
+
+typedef struct {
+ stream st;
+ int done;
+} readme;
+
+static const char * load_file(lua_State *L, void *data, size_t *size) {
+ readme *rm = data;
+
+ UNUSED(L);
+
+ if (rm->done) return 0;
+
+ *size = rm->st.size;
+ rm->done = 1;
+ return rm->st.start;
+}
+
+static int lua_to_c_get_string(lua_State *L, const char *varname, buffer *b) {
+ int curelem;
+
+ lua_pushstring(L, varname);
+
+ curelem = lua_gettop(L);
+ lua_gettable(L, LUA_GLOBALSINDEX);
+
+ /* it should be a table */
+ if (!lua_isstring(L, curelem)) {
+ lua_settop(L, curelem - 1);
+
+ return -1;
+ }
+
+ buffer_copy_string(b, lua_tostring(L, curelem));
+
+ lua_pop(L, 1);
+
+ assert(curelem - 1 == lua_gettop(L));
+
+ return 0;
+}
+
+static int lua_to_c_is_table(lua_State *L, const char *varname) {
+ int curelem;
+
+ lua_pushstring(L, varname);
+
+ curelem = lua_gettop(L);
+ lua_gettable(L, LUA_GLOBALSINDEX);
+
+ /* it should be a table */
+ if (!lua_istable(L, curelem)) {
+ lua_settop(L, curelem - 1);
+
+ return 0;
+ }
+
+ lua_settop(L, curelem - 1);
+
+ assert(curelem - 1 == lua_gettop(L));
+
+ return 1;
+}
+
+static int c_to_lua_push(lua_State *L, int tbl, const char *key, size_t key_len, const char *val, size_t val_len) {
+ lua_pushlstring(L, key, key_len);
+ lua_pushlstring(L, val, val_len);
+ lua_settable(L, tbl);
+
+ return 0;
+}
+
+
+int cache_export_get_params(lua_State *L, int tbl, buffer *qrystr) {
+ size_t is_key = 1;
+ size_t i;
+ char *key = NULL, *val = NULL;
+
+ key = qrystr->ptr;
+
+ /* we need the \0 */
+ for (i = 0; i < qrystr->used; i++) {
+ switch(qrystr->ptr[i]) {
+ case '=':
+ if (is_key) {
+ val = qrystr->ptr + i + 1;
+
+ qrystr->ptr[i] = '\0';
+
+ is_key = 0;
+ }
+
+ break;
+ case '&':
+ case '\0': /* fin symbol */
+ if (!is_key) {
+ /* we need at least a = since the last & */
+
+ /* terminate the value */
+ qrystr->ptr[i] = '\0';
+
+ c_to_lua_push(L, tbl,
+ key, strlen(key),
+ val, strlen(val));
+ }
+
+ key = qrystr->ptr + i + 1;
+ val = NULL;
+ is_key = 1;
+ break;
+ }
+ }
+
+ return 0;
+}
+#if 0
+int cache_export_cookie_params(server *srv, connection *con, plugin_data *p) {
+ data_unset *d;
+
+ UNUSED(srv);
+
+ if (NULL != (d = array_get_element(con->request.headers, "Cookie"))) {
+ data_string *ds = (data_string *)d;
+ size_t key = 0, value = 0;
+ size_t is_key = 1, is_sid = 0;
+ size_t i;
+
+ /* found COOKIE */
+ if (!DATA_IS_STRING(d)) return -1;
+ if (ds->value->used == 0) return -1;
+
+ if (ds->value->ptr[0] == '\0' ||
+ ds->value->ptr[0] == '=' ||
+ ds->value->ptr[0] == ';') return -1;
+
+ buffer_reset(p->session_id);
+ for (i = 0; i < ds->value->used; i++) {
+ switch(ds->value->ptr[i]) {
+ case '=':
+ if (is_key) {
+ if (0 == strncmp(ds->value->ptr + key, "PHPSESSID", i - key)) {
+ /* found PHP-session-id-key */
+ is_sid = 1;
+ }
+ value = i + 1;
+
+ is_key = 0;
+ }
+
+ break;
+ case ';':
+ if (is_sid) {
+ buffer_copy_string_len(p->session_id, ds->value->ptr + value, i - value);
+ }
+
+ is_sid = 0;
+ key = i + 1;
+ value = 0;
+ is_key = 1;
+ break;
+ case ' ':
+ if (is_key == 1 && key == i) key = i + 1;
+ if (is_key == 0 && value == i) value = i + 1;
+ break;
+ case '\0':
+ if (is_sid) {
+ buffer_copy_string_len(p->session_id, ds->value->ptr + value, i - value);
+ }
+ /* fin */
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+#endif
+
+int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn) {
+ lua_State *L;
+ readme rm;
+ int ret = -1;
+ buffer *b = buffer_init();
+ int header_tbl = 0;
+
+ rm.done = 0;
+ stream_open(&rm.st, fn);
+
+ /* push the lua file to the interpreter and see what happends */
+ L = lua_open();
+
+ luaopen_base(L);
+ luaopen_table(L);
+ luaopen_string(L);
+ luaopen_math(L);
+ luaopen_io(L);
+
+ /* register functions */
+ lua_register(L, "md5", f_crypto_md5);
+ lua_register(L, "file_mtime", f_file_mtime);
+ lua_register(L, "file_isreg", f_file_isreg);
+ lua_register(L, "file_isdir", f_file_isreg);
+ lua_register(L, "dir_files", f_dir_files);
+
+#ifdef HAVE_MEMCACHE_H
+ lua_pushliteral(L, "memcache_get_long");
+ lua_pushlightuserdata(L, p->conf.mc);
+ lua_pushcclosure(L, f_memcache_get_long, 1);
+ lua_settable(L, LUA_GLOBALSINDEX);
+
+ lua_pushliteral(L, "memcache_get_string");
+ lua_pushlightuserdata(L, p->conf.mc);
+ lua_pushcclosure(L, f_memcache_get_string, 1);
+ lua_settable(L, LUA_GLOBALSINDEX);
+
+ lua_pushliteral(L, "memcache_exists");
+ lua_pushlightuserdata(L, p->conf.mc);
+ lua_pushcclosure(L, f_memcache_exists, 1);
+ lua_settable(L, LUA_GLOBALSINDEX);
+#endif
+ /* register CGI environment */
+ lua_pushliteral(L, "request");
+ lua_newtable(L);
+ lua_settable(L, LUA_GLOBALSINDEX);
+
+ lua_pushliteral(L, "request");
+ header_tbl = lua_gettop(L);
+ lua_gettable(L, LUA_GLOBALSINDEX);
+
+ c_to_lua_push(L, header_tbl, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri));
+ c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path));
+ c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path));
+ c_to_lua_push(L, header_tbl, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root));
+ if (!buffer_is_empty(con->request.pathinfo)) {
+ c_to_lua_push(L, header_tbl, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo));
+ }
+
+ c_to_lua_push(L, header_tbl, CONST_STR_LEN("CWD"), CONST_BUF_LEN(p->basedir));
+ c_to_lua_push(L, header_tbl, CONST_STR_LEN("BASEURL"), CONST_BUF_LEN(p->baseurl));
+
+ /* register GET parameter */
+ lua_pushliteral(L, "get");
+ lua_newtable(L);
+ lua_settable(L, LUA_GLOBALSINDEX);
+
+ lua_pushliteral(L, "get");
+ header_tbl = lua_gettop(L);
+ lua_gettable(L, LUA_GLOBALSINDEX);
+
+ buffer_copy_string_buffer(b, con->uri.query);
+ cache_export_get_params(L, header_tbl, b);
+ buffer_reset(b);
+
+ /* 2 default constants */
+ lua_pushliteral(L, "CACHE_HIT");
+ lua_pushboolean(L, 0);
+ lua_settable(L, LUA_GLOBALSINDEX);
+
+ lua_pushliteral(L, "CACHE_MISS");
+ lua_pushboolean(L, 1);
+ lua_settable(L, LUA_GLOBALSINDEX);
+
+ /* load lua program */
+ if (lua_load(L, load_file, &rm, fn->ptr) || lua_pcall(L,0,1,0)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ lua_tostring(L,-1));
+
+ goto error;
+ }
+
+ /* get return value */
+ ret = (int)lua_tonumber(L, -1);
+ lua_pop(L, 1);
+
+ /* fetch the data from lua */
+ lua_to_c_get_string(L, "trigger_handler", p->trigger_handler);
+
+ if (0 == lua_to_c_get_string(L, "output_contenttype", b)) {
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(b));
+ }
+
+ if (ret == 0) {
+ /* up to now it is a cache-hit, check if all files exist */
+
+ int curelem;
+ time_t mtime = 0;
+
+ if (!lua_to_c_is_table(L, "output_include")) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "output_include is missing or not a table");
+ ret = -1;
+
+ goto error;
+ }
+
+ lua_pushstring(L, "output_include");
+
+ curelem = lua_gettop(L);
+ lua_gettable(L, LUA_GLOBALSINDEX);
+
+ /* HOW-TO build a etag ?
+ * as we don't just have one file we have to take the stat()
+ * from all base files, merge them and build the etag from
+ * it later.
+ *
+ * The mtime of the content is the mtime of the freshest base file
+ *
+ * */
+
+ lua_pushnil(L); /* first key */
+ while (lua_next(L, curelem) != 0) {
+ stat_cache_entry *sce = NULL;
+ /* key' is at index -2 and value' at index -1 */
+
+ if (lua_isstring(L, -1)) {
+ const char *s = lua_tostring(L, -1);
+
+ /* the file is relative, make it absolute */
+ if (s[0] != '/') {
+ buffer_copy_string_buffer(b, p->basedir);
+ buffer_append_string(b, lua_tostring(L, -1));
+ } else {
+ buffer_copy_string(b, lua_tostring(L, -1));
+ }
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, b, &sce)) {
+ /* stat failed */
+
+ switch(errno) {
+ case ENOENT:
+ /* a file is missing, call the handler to generate it */
+ if (!buffer_is_empty(p->trigger_handler)) {
+ ret = 1; /* cache-miss */
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "a file is missing, calling handler");
+
+ break;
+ } else {
+ /* handler not set -> 500 */
+ ret = -1;
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "a file missing and no handler set");
+
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ chunkqueue_append_file(con->write_queue, b, 0, sce->st.st_size);
+ if (sce->st.st_mtime > mtime) mtime = sce->st.st_mtime;
+ }
+ } else {
+ /* not a string */
+ ret = -1;
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "not a string");
+ break;
+ }
+
+ lua_pop(L, 1); /* removes value'; keeps key' for next iteration */
+ }
+
+ lua_settop(L, curelem - 1);
+
+ if (ret == 0) {
+ data_string *ds;
+ char timebuf[sizeof("Sat, 23 Jul 2005 21:20:01 GMT")];
+ buffer tbuf;
+
+ con->file_finished = 1;
+
+ ds = (data_string *)array_get_element(con->response.headers, "Last-Modified");
+
+ /* no Last-Modified specified */
+ if ((mtime) && (NULL == ds)) {
+
+ strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&mtime));
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), timebuf, sizeof(timebuf) - 1);
+
+
+ tbuf.ptr = timebuf;
+ tbuf.used = sizeof(timebuf);
+ tbuf.size = sizeof(timebuf);
+ } else if (ds) {
+ tbuf.ptr = ds->value->ptr;
+ tbuf.used = ds->value->used;
+ tbuf.size = ds->value->size;
+ } else {
+ tbuf.size = 0;
+ tbuf.used = 0;
+ tbuf.ptr = NULL;
+ }
+
+ if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, &tbuf)) {
+ /* ok, the client already has our content,
+ * no need to send it again */
+
+ chunkqueue_reset(con->write_queue);
+ ret = 0; /* cache-hit */
+ }
+ } else {
+ chunkqueue_reset(con->write_queue);
+ }
+ }
+
+ if (ret == 1 && !buffer_is_empty(p->trigger_handler)) {
+ /* cache-miss */
+ buffer_copy_string_buffer(con->uri.path, p->baseurl);
+ buffer_append_string_buffer(con->uri.path, p->trigger_handler);
+
+ buffer_copy_string_buffer(con->physical.path, p->basedir);
+ buffer_append_string_buffer(con->physical.path, p->trigger_handler);
+
+ chunkqueue_reset(con->write_queue);
+ }
+
+error:
+ lua_close(L);
+
+ stream_close(&rm.st);
+ buffer_free(b);
+
+ return ret /* cache-error */;
+}
+#else
+int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn) {
+ /* error */
+ return -1;
+}
+#endif
diff --git a/src/mod_compress.c b/src/mod_compress.c
new file mode 100644
index 0000000..fc78220
--- /dev/null
+++ b/src/mod_compress.c
@@ -0,0 +1,701 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+#include "response.h"
+#include "stat_cache.h"
+
+#include "plugin.h"
+
+#include "crc32.h"
+#include "etag.h"
+
+#if defined HAVE_ZLIB_H && defined HAVE_LIBZ
+# define USE_ZLIB
+# include <zlib.h>
+#endif
+
+#if defined HAVE_BZLIB_H && defined HAVE_LIBBZ2
+# define USE_BZ2LIB
+/* we don't need stdio interface */
+# define BZ_NO_STDIO
+# include <bzlib.h>
+#endif
+
+#include "sys-mmap.h"
+
+/* request: accept-encoding */
+#define HTTP_ACCEPT_ENCODING_IDENTITY BV(0)
+#define HTTP_ACCEPT_ENCODING_GZIP BV(1)
+#define HTTP_ACCEPT_ENCODING_DEFLATE BV(2)
+#define HTTP_ACCEPT_ENCODING_COMPRESS BV(3)
+#define HTTP_ACCEPT_ENCODING_BZIP2 BV(4)
+
+#ifdef __WIN32
+#define mkdir(x,y) mkdir(x)
+#endif
+
+typedef struct {
+ buffer *compress_cache_dir;
+ array *compress;
+ off_t compress_max_filesize; /** max filesize in kb */
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer *ofn;
+ buffer *b;
+
+ plugin_config **config_storage;
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_compress_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->ofn = buffer_init();
+ p->b = buffer_init();
+
+ return p;
+}
+
+FREE_FUNC(mod_compress_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ buffer_free(p->ofn);
+ buffer_free(p->b);
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (!s) continue;
+
+ array_free(s->compress);
+ buffer_free(s->compress_cache_dir);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_compress_setdefaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "compress.cache-dir", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "compress.filetype", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
+ { "compress.max-filesize", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->compress_cache_dir = buffer_init();
+ s->compress = array_init();
+ s->compress_max_filesize = 0;
+
+ cv[0].destination = s->compress_cache_dir;
+ cv[1].destination = s->compress;
+ cv[2].destination = &(s->compress_max_filesize);
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (!buffer_is_empty(s->compress_cache_dir)) {
+ struct stat st;
+ if (0 != stat(s->compress_cache_dir->ptr, &st)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "can't stat compress.cache-dir",
+ s->compress_cache_dir, strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+
+}
+
+#ifdef USE_ZLIB
+static int deflate_file_to_buffer_gzip(server *srv, connection *con, plugin_data *p, char *start, off_t st_size, time_t mtime) {
+ unsigned char *c;
+ unsigned long crc;
+ z_stream z;
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ z.zalloc = Z_NULL;
+ z.zfree = Z_NULL;
+ z.opaque = Z_NULL;
+
+ if (Z_OK != deflateInit2(&z,
+ Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED,
+ -MAX_WBITS, /* supress zlib-header */
+ 8,
+ Z_DEFAULT_STRATEGY)) {
+ return -1;
+ }
+
+ z.next_in = (unsigned char *)start;
+ z.avail_in = st_size;
+ z.total_in = 0;
+
+
+ buffer_prepare_copy(p->b, (z.avail_in * 1.1) + 12 + 18);
+
+ /* write gzip header */
+
+ c = (unsigned char *)p->b->ptr;
+ c[0] = 0x1f;
+ c[1] = 0x8b;
+ c[2] = Z_DEFLATED;
+ c[3] = 0; /* options */
+ c[4] = (mtime >> 0) & 0xff;
+ c[5] = (mtime >> 8) & 0xff;
+ c[6] = (mtime >> 16) & 0xff;
+ c[7] = (mtime >> 24) & 0xff;
+ c[8] = 0x00; /* extra flags */
+ c[9] = 0x03; /* UNIX */
+
+ p->b->used = 10;
+ z.next_out = (unsigned char *)p->b->ptr + p->b->used;
+ z.avail_out = p->b->size - p->b->used - 8;
+ z.total_out = 0;
+
+ if (Z_STREAM_END != deflate(&z, Z_FINISH)) {
+ deflateEnd(&z);
+ return -1;
+ }
+
+ /* trailer */
+ p->b->used += z.total_out;
+
+ crc = generate_crc32c(start, st_size);
+
+ c = (unsigned char *)p->b->ptr + p->b->used;
+
+ c[0] = (crc >> 0) & 0xff;
+ c[1] = (crc >> 8) & 0xff;
+ c[2] = (crc >> 16) & 0xff;
+ c[3] = (crc >> 24) & 0xff;
+ c[4] = (z.total_in >> 0) & 0xff;
+ c[5] = (z.total_in >> 8) & 0xff;
+ c[6] = (z.total_in >> 16) & 0xff;
+ c[7] = (z.total_in >> 24) & 0xff;
+ p->b->used += 8;
+
+ if (Z_OK != deflateEnd(&z)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int deflate_file_to_buffer_deflate(server *srv, connection *con, plugin_data *p, unsigned char *start, off_t st_size) {
+ z_stream z;
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ z.zalloc = Z_NULL;
+ z.zfree = Z_NULL;
+ z.opaque = Z_NULL;
+
+ if (Z_OK != deflateInit2(&z,
+ Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED,
+ -MAX_WBITS, /* supress zlib-header */
+ 8,
+ Z_DEFAULT_STRATEGY)) {
+ return -1;
+ }
+
+ z.next_in = start;
+ z.avail_in = st_size;
+ z.total_in = 0;
+
+ buffer_prepare_copy(p->b, (z.avail_in * 1.1) + 12);
+
+ z.next_out = (unsigned char *)p->b->ptr;
+ z.avail_out = p->b->size;
+ z.total_out = 0;
+
+ if (Z_STREAM_END != deflate(&z, Z_FINISH)) {
+ deflateEnd(&z);
+ return -1;
+ }
+
+ /* trailer */
+ p->b->used += z.total_out;
+
+ if (Z_OK != deflateEnd(&z)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif
+
+#ifdef USE_BZ2LIB
+static int deflate_file_to_buffer_bzip2(server *srv, connection *con, plugin_data *p, unsigned char *start, off_t st_size) {
+ bz_stream bz;
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ bz.bzalloc = NULL;
+ bz.bzfree = NULL;
+ bz.opaque = NULL;
+
+ if (BZ_OK != BZ2_bzCompressInit(&bz,
+ 9, /* blocksize = 900k */
+ 0, /* no output */
+ 0)) { /* workFactor: default */
+ return -1;
+ }
+
+ bz.next_in = (char *)start;
+ bz.avail_in = st_size;
+ bz.total_in_lo32 = 0;
+ bz.total_in_hi32 = 0;
+
+ buffer_prepare_copy(p->b, (bz.avail_in * 1.1) + 12);
+
+ bz.next_out = p->b->ptr;
+ bz.avail_out = p->b->size;
+ bz.total_out_lo32 = 0;
+ bz.total_out_hi32 = 0;
+
+ if (BZ_STREAM_END != BZ2_bzCompress(&bz, BZ_FINISH)) {
+ BZ2_bzCompressEnd(&bz);
+ return -1;
+ }
+
+ /* file is too large for now */
+ if (bz.total_out_hi32) return -1;
+
+ /* trailer */
+ p->b->used = bz.total_out_lo32;
+
+ if (BZ_OK != BZ2_bzCompressEnd(&bz)) {
+ return -1;
+ }
+
+ return 0;
+}
+#endif
+
+static int deflate_file_to_file(server *srv, connection *con, plugin_data *p, buffer *fn, stat_cache_entry *sce, int type) {
+ int ifd, ofd;
+ int ret = -1;
+ void *start;
+ const char *filename = fn->ptr;
+ ssize_t r;
+
+ /* overflow */
+ if ((off_t)(sce->st.st_size * 1.1) < sce->st.st_size) return -1;
+
+ /* don't mmap files > 128Mb
+ *
+ * we could use a sliding window, but currently there is no need for it
+ */
+
+ if (sce->st.st_size > 128 * 1024 * 1024) return -1;
+
+ buffer_reset(p->ofn);
+ buffer_copy_string_buffer(p->ofn, p->conf.compress_cache_dir);
+ BUFFER_APPEND_SLASH(p->ofn);
+
+ if (0 == strncmp(con->physical.path->ptr, con->physical.doc_root->ptr, con->physical.doc_root->used-1)) {
+ size_t offset = p->ofn->used - 1;
+ char *dir, *nextdir;
+
+ buffer_append_string(p->ofn, con->physical.path->ptr + con->physical.doc_root->used - 1);
+
+ buffer_copy_string_buffer(p->b, p->ofn);
+
+ /* mkdir -p ... */
+ for (dir = p->b->ptr + offset; NULL != (nextdir = strchr(dir, '/')); dir = nextdir + 1) {
+ *nextdir = '\0';
+
+ if (-1 == mkdir(p->b->ptr, 0700)) {
+ if (errno != EEXIST) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "creating cache-directory", p->b, "failed", strerror(errno));
+
+ return -1;
+ }
+ }
+
+ *nextdir = '/';
+ }
+ } else {
+ buffer_append_string_buffer(p->ofn, con->uri.path);
+ }
+
+ switch(type) {
+ case HTTP_ACCEPT_ENCODING_GZIP:
+ buffer_append_string(p->ofn, "-gzip-");
+ break;
+ case HTTP_ACCEPT_ENCODING_DEFLATE:
+ buffer_append_string(p->ofn, "-deflate-");
+ break;
+ case HTTP_ACCEPT_ENCODING_BZIP2:
+ buffer_append_string(p->ofn, "-bzip2-");
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "unknown compression type", type);
+ return -1;
+ }
+
+ buffer_append_string_buffer(p->ofn, sce->etag);
+
+ if (-1 == (ofd = open(p->ofn->ptr, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0600))) {
+ if (errno == EEXIST) {
+ /* cache-entry exists */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "bs", p->ofn, "compress-cache hit");
+#endif
+ buffer_copy_string_buffer(con->physical.path, p->ofn);
+
+ return 0;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "creating cachefile", p->ofn, "failed", strerror(errno));
+
+ return -1;
+ }
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "bs", p->ofn, "compress-cache miss");
+#endif
+ if (-1 == (ifd = open(filename, O_RDONLY | O_BINARY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "opening plain-file", fn, "failed", strerror(errno));
+
+ close(ofd);
+
+ return -1;
+ }
+
+
+ if (MAP_FAILED == (start = mmap(NULL, sce->st.st_size, PROT_READ, MAP_SHARED, ifd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "mmaping", fn, "failed", strerror(errno));
+
+ close(ofd);
+ close(ifd);
+ return -1;
+ }
+
+ switch(type) {
+#ifdef USE_ZLIB
+ case HTTP_ACCEPT_ENCODING_GZIP:
+ ret = deflate_file_to_buffer_gzip(srv, con, p, start, sce->st.st_size, sce->st.st_mtime);
+ break;
+ case HTTP_ACCEPT_ENCODING_DEFLATE:
+ ret = deflate_file_to_buffer_deflate(srv, con, p, start, sce->st.st_size);
+ break;
+#endif
+#ifdef USE_BZ2LIB
+ case HTTP_ACCEPT_ENCODING_BZIP2:
+ ret = deflate_file_to_buffer_bzip2(srv, con, p, start, sce->st.st_size);
+ break;
+#endif
+ default:
+ ret = -1;
+ break;
+ }
+
+ if (-1 == (r = write(ofd, p->b->ptr, p->b->used))) {
+ munmap(start, sce->st.st_size);
+ close(ofd);
+ close(ifd);
+ return -1;
+ }
+
+ if ((size_t)r != p->b->used) {
+
+ }
+
+ munmap(start, sce->st.st_size);
+ close(ofd);
+ close(ifd);
+
+ if (ret != 0) return -1;
+
+ buffer_copy_string_buffer(con->physical.path, p->ofn);
+
+ return 0;
+}
+
+static int deflate_file_to_buffer(server *srv, connection *con, plugin_data *p, buffer *fn, stat_cache_entry *sce, int type) {
+ int ifd;
+ int ret = -1;
+ void *start;
+ buffer *b;
+
+ /* overflow */
+ if ((off_t)(sce->st.st_size * 1.1) < sce->st.st_size) return -1;
+
+ /* don't mmap files > 128M
+ *
+ * we could use a sliding window, but currently there is no need for it
+ */
+
+ if (sce->st.st_size > 128 * 1024 * 1024) return -1;
+
+
+ if (-1 == (ifd = open(fn->ptr, O_RDONLY | O_BINARY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "opening plain-file", fn, "failed", strerror(errno));
+
+ return -1;
+ }
+
+
+ if (MAP_FAILED == (start = mmap(NULL, sce->st.st_size, PROT_READ, MAP_SHARED, ifd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "mmaping", fn, "failed", strerror(errno));
+
+ close(ifd);
+ return -1;
+ }
+
+ switch(type) {
+#ifdef USE_ZLIB
+ case HTTP_ACCEPT_ENCODING_GZIP:
+ ret = deflate_file_to_buffer_gzip(srv, con, p, start, sce->st.st_size, sce->st.st_mtime);
+ break;
+ case HTTP_ACCEPT_ENCODING_DEFLATE:
+ ret = deflate_file_to_buffer_deflate(srv, con, p, start, sce->st.st_size);
+ break;
+#endif
+#ifdef USE_BZ2LIB
+ case HTTP_ACCEPT_ENCODING_BZIP2:
+ ret = deflate_file_to_buffer_bzip2(srv, con, p, start, sce->st.st_size);
+ break;
+#endif
+ default:
+ ret = -1;
+ break;
+ }
+
+ munmap(start, sce->st.st_size);
+ close(ifd);
+
+ if (ret != 0) return -1;
+
+ chunkqueue_reset(con->write_queue);
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ buffer_copy_memory(b, p->b->ptr, p->b->used + 1);
+
+ buffer_reset(con->physical.path);
+
+ con->file_finished = 1;
+ con->file_started = 1;
+
+ return 0;
+}
+
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_compress_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(compress_cache_dir);
+ PATCH(compress);
+ PATCH(compress_max_filesize);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.cache-dir"))) {
+ PATCH(compress_cache_dir);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.filetype"))) {
+ PATCH(compress);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.max-filesize"))) {
+ PATCH(compress_max_filesize);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+PHYSICALPATH_FUNC(mod_compress_physical) {
+ plugin_data *p = p_d;
+ size_t m;
+ off_t max_fsize;
+ stat_cache_entry *sce = NULL;
+
+ /* only GET and POST can get compressed */
+ if (con->request.http_method != HTTP_METHOD_GET &&
+ con->request.http_method != HTTP_METHOD_POST) {
+ return HANDLER_GO_ON;
+ }
+
+ if (buffer_is_empty(con->physical.path)) {
+ return HANDLER_GO_ON;
+ }
+
+ mod_compress_patch_connection(srv, con, p);
+
+ max_fsize = p->conf.compress_max_filesize;
+
+ stat_cache_get_entry(srv, con, con->physical.path, &sce);
+
+ /* don't compress files that are too large as we need to much time to handle them */
+ if (max_fsize && (sce->st.st_size >> 10) > max_fsize) return HANDLER_GO_ON;
+
+ /* check if mimetype is in compress-config */
+ for (m = 0; m < p->conf.compress->used; m++) {
+ data_string *compress_ds = (data_string *)p->conf.compress->data[m];
+
+ if (!compress_ds) {
+ log_error_write(srv, __FILE__, __LINE__, "sbb", "evil", con->physical.path, con->uri.path);
+
+ return HANDLER_GO_ON;
+ }
+
+ if (buffer_is_equal(compress_ds->value, sce->content_type)) {
+ /* mimetype found */
+ data_string *ds;
+
+ /* the response might change according to Accept-Encoding */
+ response_header_insert(srv, con, CONST_STR_LEN("Vary"), CONST_STR_LEN("Accept-Encoding"));
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Accept-Encoding"))) {
+ int accept_encoding = 0;
+ char *value = ds->value->ptr;
+ int srv_encodings = 0;
+ int matched_encodings = 0;
+
+ /* get client side support encodings */
+ if (NULL != strstr(value, "gzip")) accept_encoding |= HTTP_ACCEPT_ENCODING_GZIP;
+ if (NULL != strstr(value, "deflate")) accept_encoding |= HTTP_ACCEPT_ENCODING_DEFLATE;
+ if (NULL != strstr(value, "compress")) accept_encoding |= HTTP_ACCEPT_ENCODING_COMPRESS;
+ if (NULL != strstr(value, "bzip2")) accept_encoding |= HTTP_ACCEPT_ENCODING_BZIP2;
+ if (NULL != strstr(value, "identity")) accept_encoding |= HTTP_ACCEPT_ENCODING_IDENTITY;
+
+ /* get server side supported ones */
+#ifdef USE_BZ2LIB
+ srv_encodings |= HTTP_ACCEPT_ENCODING_BZIP2;
+#endif
+#ifdef USE_ZLIB
+ srv_encodings |= HTTP_ACCEPT_ENCODING_GZIP;
+ srv_encodings |= HTTP_ACCEPT_ENCODING_DEFLATE;
+#endif
+
+ /* find matching entries */
+ matched_encodings = accept_encoding & srv_encodings;
+
+ if (matched_encodings) {
+ const char *dflt_gzip = "gzip";
+ const char *dflt_deflate = "deflate";
+ const char *dflt_bzip2 = "bzip2";
+
+ const char *compression_name = NULL;
+ int compression_type = 0;
+
+ /* select best matching encoding */
+ if (matched_encodings & HTTP_ACCEPT_ENCODING_BZIP2) {
+ compression_type = HTTP_ACCEPT_ENCODING_BZIP2;
+ compression_name = dflt_bzip2;
+ } else if (matched_encodings & HTTP_ACCEPT_ENCODING_GZIP) {
+ compression_type = HTTP_ACCEPT_ENCODING_GZIP;
+ compression_name = dflt_gzip;
+ } else if (matched_encodings & HTTP_ACCEPT_ENCODING_DEFLATE) {
+ compression_type = HTTP_ACCEPT_ENCODING_DEFLATE;
+ compression_name = dflt_deflate;
+ }
+
+ /* deflate it */
+ if (p->conf.compress_cache_dir->used) {
+ if (0 == deflate_file_to_file(srv, con, p,
+ con->physical.path, sce, compression_type)) {
+ buffer *mtime;
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Encoding"), compression_name, strlen(compression_name));
+
+ mtime = strftime_cache_get(srv, sce->st.st_mtime);
+ response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
+
+ etag_mutate(con->physical.etag, sce->etag);
+ response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
+
+ return HANDLER_GO_ON;
+ }
+ } else if (0 == deflate_file_to_buffer(srv, con, p,
+ con->physical.path, sce, compression_type)) {
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Encoding"), compression_name, strlen(compression_name));
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
+
+ return HANDLER_FINISHED;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+int mod_compress_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("compress");
+
+ p->init = mod_compress_init;
+ p->set_defaults = mod_compress_setdefaults;
+ p->handle_subrequest_start = mod_compress_physical;
+ p->cleanup = mod_compress_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_dirlisting.c b/src/mod_dirlisting.c
new file mode 100644
index 0000000..69eb1e9
--- /dev/null
+++ b/src/mod_dirlisting.c
@@ -0,0 +1,888 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "response.h"
+#include "stat_cache.h"
+#include "stream.h"
+
+/**
+ * this is a dirlisting for a lighttpd plugin
+ */
+
+
+#ifdef HAVE_SYS_SYSLIMITS_H
+#include <sys/syslimits.h>
+#endif
+
+#ifdef HAVE_ATTR_ATTRIBUTES_H
+#include <attr/attributes.h>
+#endif
+
+/* plugin config for all request/connections */
+
+typedef struct {
+#ifdef HAVE_PCRE_H
+ pcre *regex;
+#endif
+ buffer *string;
+} excludes;
+
+typedef struct {
+ excludes **ptr;
+
+ size_t used;
+ size_t size;
+} excludes_buffer;
+
+typedef struct {
+ unsigned short dir_listing;
+ unsigned short hide_dot_files;
+ unsigned short show_readme;
+ unsigned short hide_readme_file;
+ unsigned short show_header;
+ unsigned short hide_header_file;
+
+ excludes_buffer *excludes;
+
+ buffer *external_css;
+ buffer *encoding;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *tmp_buf;
+ buffer *content_charset;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+excludes_buffer *excludes_buffer_init(void) {
+ excludes_buffer *exb;
+
+ exb = calloc(1, sizeof(*exb));
+
+ return exb;
+}
+
+int excludes_buffer_append(excludes_buffer *exb, buffer *string) {
+#ifdef HAVE_PCRE_H
+ size_t i;
+ const char *errptr;
+ int erroff;
+
+ if (!string) return -1;
+
+ if (exb->size == 0) {
+ exb->size = 4;
+ exb->used = 0;
+
+ exb->ptr = malloc(exb->size * sizeof(*exb->ptr));
+
+ for(i = 0; i < exb->size ; i++) {
+ exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
+ }
+ } else if (exb->used == exb->size) {
+ exb->size += 4;
+
+ exb->ptr = realloc(exb->ptr, exb->size * sizeof(*exb->ptr));
+
+ for(i = exb->used; i < exb->size; i++) {
+ exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
+ }
+ }
+
+
+ if (NULL == (exb->ptr[exb->used]->regex = pcre_compile(string->ptr, 0,
+ &errptr, &erroff, NULL))) {
+ return -1;
+ }
+
+ exb->ptr[exb->used]->string = buffer_init();
+ buffer_copy_string_buffer(exb->ptr[exb->used]->string, string);
+
+ exb->used++;
+
+ return 0;
+#else
+ UNUSED(exb);
+ UNUSED(string);
+
+ return -1;
+#endif
+}
+
+void excludes_buffer_free(excludes_buffer *exb) {
+#ifdef HAVE_PCRE_H
+ size_t i;
+
+ for (i = 0; i < exb->size; i++) {
+ if (exb->ptr[i]->regex) pcre_free(exb->ptr[i]->regex);
+ if (exb->ptr[i]->string) buffer_free(exb->ptr[i]->string);
+ free(exb->ptr[i]);
+ }
+
+ if (exb->ptr) free(exb->ptr);
+#endif
+
+ free(exb);
+}
+
+/* init the plugin data */
+INIT_FUNC(mod_dirlisting_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->tmp_buf = buffer_init();
+ p->content_charset = buffer_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_dirlisting_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (!s) continue;
+
+ excludes_buffer_free(s->excludes);
+ buffer_free(s->external_css);
+ buffer_free(s->encoding);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->tmp_buf);
+ buffer_free(p->content_charset);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+static int parse_config_entry(server *srv, plugin_config *s, array *ca, const char *option) {
+ data_unset *du;
+
+ if (NULL != (du = array_get_element(ca, option))) {
+ data_array *da = (data_array *)du;
+ size_t j;
+
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", option, "array of strings");
+
+ return HANDLER_ERROR;
+ }
+
+ da = (data_array *)du;
+
+ for (j = 0; j < da->value->used; j++) {
+ if (da->value->data[j]->type != TYPE_STRING) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ", option, "[",
+ da->value->data[j]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ if (0 != excludes_buffer_append(s->excludes,
+ ((data_string *)(da->value->data[j]))->value)) {
+#ifdef HAVE_PCRE_H
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "pcre-compile failed for", ((data_string *)(da->value->data[j]))->value);
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "pcre support is missing, please install libpcre and the headers");
+#endif
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "dir-listing.exclude", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "dir-listing.activate", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "dir-listing.hide-dotfiles", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "dir-listing.external-css", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { "dir-listing.encoding", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
+ { "dir-listing.show-readme", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
+ { "dir-listing.hide-readme-file", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
+ { "dir-listing.show-header", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
+ { "dir-listing.hide-header-file", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
+ { "server.dir-listing", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 9 */
+
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ array *ca;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->excludes = excludes_buffer_init();
+ s->dir_listing = 0;
+ s->external_css = buffer_init();
+ s->hide_dot_files = 0;
+ s->show_readme = 0;
+ s->hide_readme_file = 0;
+ s->show_header = 0;
+ s->hide_header_file = 0;
+ s->encoding = buffer_init();
+
+ cv[0].destination = s->excludes;
+ cv[1].destination = &(s->dir_listing);
+ cv[2].destination = &(s->hide_dot_files);
+ cv[3].destination = s->external_css;
+ cv[4].destination = s->encoding;
+ cv[5].destination = &(s->show_readme);
+ cv[6].destination = &(s->hide_readme_file);
+ cv[7].destination = &(s->show_header);
+ cv[8].destination = &(s->hide_header_file);
+ cv[9].destination = &(s->dir_listing); /* old name */
+
+ p->config_storage[i] = s;
+ ca = ((data_config *)srv->config_context->data[i])->value;
+
+ if (0 != config_insert_values_global(srv, ca, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ parse_config_entry(srv, s, ca, "dir-listing.exclude");
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_dirlisting_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(dir_listing);
+ PATCH(external_css);
+ PATCH(hide_dot_files);
+ PATCH(encoding);
+ PATCH(show_readme);
+ PATCH(hide_readme_file);
+ PATCH(show_header);
+ PATCH(hide_header_file);
+ PATCH(excludes);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.activate")) ||
+ buffer_is_equal_string(du->key, CONST_STR_LEN("server.dir-listing"))) {
+ PATCH(dir_listing);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.hide-dotfiles"))) {
+ PATCH(hide_dot_files);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.external-css"))) {
+ PATCH(external_css);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.encoding"))) {
+ PATCH(encoding);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.show-readme"))) {
+ PATCH(show_readme);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.hide-readme-file"))) {
+ PATCH(hide_readme_file);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.show-header"))) {
+ PATCH(show_header);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.hide-header-file"))) {
+ PATCH(hide_header_file);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.excludes"))) {
+ PATCH(excludes);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+typedef struct {
+ size_t namelen;
+ time_t mtime;
+ off_t size;
+} dirls_entry_t;
+
+typedef struct {
+ dirls_entry_t **ent;
+ size_t used;
+ size_t size;
+} dirls_list_t;
+
+#define DIRLIST_ENT_NAME(ent) ((char*)(ent) + sizeof(dirls_entry_t))
+#define DIRLIST_BLOB_SIZE 16
+
+/* simple combsort algorithm */
+static void http_dirls_sort(dirls_entry_t **ent, int num) {
+ int gap = num;
+ int i, j;
+ int swapped;
+ dirls_entry_t *tmp;
+
+ do {
+ gap = (gap * 10) / 13;
+ if (gap == 9 || gap == 10)
+ gap = 11;
+ if (gap < 1)
+ gap = 1;
+ swapped = 0;
+
+ for (i = 0; i < num - gap; i++) {
+ j = i + gap;
+ if (strcmp(DIRLIST_ENT_NAME(ent[i]), DIRLIST_ENT_NAME(ent[j])) > 0) {
+ tmp = ent[i];
+ ent[i] = ent[j];
+ ent[j] = tmp;
+ swapped = 1;
+ }
+ }
+
+ } while (gap > 1 || swapped);
+}
+
+/* buffer must be able to hold "999.9K"
+ * conversion is simple but not perfect
+ */
+static int http_list_directory_sizefmt(char *buf, off_t size) {
+ const char unit[] = "KMGTPE"; /* Kilo, Mega, Tera, Peta, Exa */
+ const char *u = unit - 1; /* u will always increment at least once */
+ int remain;
+ char *out = buf;
+
+ if (size < 100)
+ size += 99;
+ if (size < 100)
+ size = 0;
+
+ while (1) {
+ remain = (int) size & 1023;
+ size >>= 10;
+ u++;
+ if ((size & (~0 ^ 1023)) == 0)
+ break;
+ }
+
+ remain /= 100;
+ if (remain > 9)
+ remain = 9;
+ if (size > 999) {
+ size = 0;
+ remain = 9;
+ u++;
+ }
+
+ out += ltostr(out, size);
+ out[0] = '.';
+ out[1] = remain + '0';
+ out[2] = *u;
+ out[3] = '\0';
+
+ return (out + 3 - buf);
+}
+
+static void http_list_directory_header(server *srv, connection *con, plugin_data *p, buffer *out) {
+ UNUSED(srv);
+
+ BUFFER_APPEND_STRING_CONST(out,
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n"
+ "<head>\n"
+ "<title>Index of "
+ );
+ buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
+ BUFFER_APPEND_STRING_CONST(out, "</title>\n");
+
+ if (p->conf.external_css->used > 1) {
+ BUFFER_APPEND_STRING_CONST(out, "<link rel=\"stylesheet\" type=\"text/css\" href=\"");
+ buffer_append_string_buffer(out, p->conf.external_css);
+ BUFFER_APPEND_STRING_CONST(out, "\" />\n");
+ } else {
+ BUFFER_APPEND_STRING_CONST(out,
+ "<style type=\"text/css\">\n"
+ "a, a:active {text-decoration: none; color: blue;}\n"
+ "a:visited {color: #48468F;}\n"
+ "a:hover, a:focus {text-decoration: underline; color: red;}\n"
+ "body {background-color: #F5F5F5;}\n"
+ "h2 {margin-bottom: 12px;}\n"
+ "table {margin-left: 12px;}\n"
+ "th, td {"
+ " font-family: \"Courier New\", Courier, monospace;"
+ " font-size: 10pt;"
+ " text-align: left;"
+ "}\n"
+ "th {"
+ " font-weight: bold;"
+ " padding-right: 14px;"
+ " padding-bottom: 3px;"
+ "}\n"
+ );
+ BUFFER_APPEND_STRING_CONST(out,
+ "td {padding-right: 14px;}\n"
+ "td.s, th.s {text-align: right;}\n"
+ "div.list {"
+ " background-color: white;"
+ " border-top: 1px solid #646464;"
+ " border-bottom: 1px solid #646464;"
+ " padding-top: 10px;"
+ " padding-bottom: 14px;"
+ "}\n"
+ "div.foot {"
+ " font-family: \"Courier New\", Courier, monospace;"
+ " font-size: 10pt;"
+ " color: #787878;"
+ " padding-top: 4px;"
+ "}\n"
+ "</style>\n"
+ );
+ }
+
+ BUFFER_APPEND_STRING_CONST(out, "</head>\n<body>\n");
+
+ /* HEADER.txt */
+ if (p->conf.show_header) {
+ stream s;
+ /* if we have a HEADER file, display it in <pre class="header"></pre> */
+
+ buffer_copy_string_buffer(p->tmp_buf, con->physical.path);
+ BUFFER_APPEND_SLASH(p->tmp_buf);
+ BUFFER_APPEND_STRING_CONST(p->tmp_buf, "HEADER.txt");
+
+ if (-1 != stream_open(&s, p->tmp_buf)) {
+ BUFFER_APPEND_STRING_CONST(out, "<pre class=\"header\">");
+ buffer_append_string_encoded(out, s.start, s.size, ENCODING_MINIMAL_XML);
+ BUFFER_APPEND_STRING_CONST(out, "</pre>");
+ }
+ stream_close(&s);
+ }
+
+ BUFFER_APPEND_STRING_CONST(out, "<h2>Index of ");
+ buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
+ BUFFER_APPEND_STRING_CONST(out,
+ "</h2>\n"
+ "<div class=\"list\">\n"
+ "<table cellpadding=\"0\" cellspacing=\"0\">\n"
+ "<thead>"
+ "<tr>"
+ "<th class=\"n\">Name</th>"
+ "<th class=\"m\">Last Modified</th>"
+ "<th class=\"s\">Size</th>"
+ "<th class=\"t\">Type</th>"
+ "</tr>"
+ "</thead>\n"
+ "<tbody>\n"
+ "<tr>"
+ "<td class=\"n\"><a href=\"../\">Parent Directory</a>/</td>"
+ "<td class=\"m\">&nbsp;</td>"
+ "<td class=\"s\">- &nbsp;</td>"
+ "<td class=\"t\">Directory</td>"
+ "</tr>\n"
+ );
+}
+
+static void http_list_directory_footer(server *srv, connection *con, plugin_data *p, buffer *out) {
+ UNUSED(srv);
+
+ BUFFER_APPEND_STRING_CONST(out,
+ "</tbody>\n"
+ "</table>\n"
+ "</div>\n"
+ );
+
+ if (p->conf.show_readme) {
+ stream s;
+ /* if we have a README file, display it in <pre class="readme"></pre> */
+
+ buffer_copy_string_buffer(p->tmp_buf, con->physical.path);
+ BUFFER_APPEND_SLASH(p->tmp_buf);
+ BUFFER_APPEND_STRING_CONST(p->tmp_buf, "README.txt");
+
+ if (-1 != stream_open(&s, p->tmp_buf)) {
+ BUFFER_APPEND_STRING_CONST(out, "<pre class=\"readme\">");
+ buffer_append_string_encoded(out, s.start, s.size, ENCODING_MINIMAL_XML);
+ BUFFER_APPEND_STRING_CONST(out, "</pre>");
+ }
+ stream_close(&s);
+ }
+
+ BUFFER_APPEND_STRING_CONST(out,
+ "<div class=\"foot\">"
+ );
+
+ if (buffer_is_empty(con->conf.server_tag)) {
+ BUFFER_APPEND_STRING_CONST(out, PACKAGE_NAME "/" PACKAGE_VERSION);
+ } else {
+ buffer_append_string_buffer(out, con->conf.server_tag);
+ }
+
+ BUFFER_APPEND_STRING_CONST(out,
+ "</div>\n"
+ "</body>\n"
+ "</html>\n"
+ );
+}
+
+static int http_list_directory(server *srv, connection *con, plugin_data *p, buffer *dir) {
+ DIR *dp;
+ buffer *out;
+ struct dirent *dent;
+ struct stat st;
+ char *path, *path_file;
+ size_t i;
+ int hide_dotfiles = p->conf.hide_dot_files;
+ dirls_list_t dirs, files, *list;
+ dirls_entry_t *tmp;
+ char sizebuf[sizeof("999.9K")];
+ char datebuf[sizeof("2005-Jan-01 22:23:24")];
+ size_t k;
+ const char *content_type;
+ long name_max;
+#ifdef HAVE_XATTR
+ char attrval[128];
+ int attrlen;
+#endif
+#ifdef HAVE_LOCALTIME_R
+ struct tm tm;
+#endif
+
+ if (dir->used == 0) return -1;
+
+ i = dir->used - 1;
+
+#ifdef HAVE_PATHCONF
+ if (-1 == (name_max = pathconf(dir->ptr, _PC_NAME_MAX))) {
+#ifdef NAME_MAX
+ name_max = NAME_MAX;
+#else
+ name_max = 256; /* stupid default */
+#endif
+ }
+#elif defined __WIN32
+ name_max = FILENAME_MAX;
+#else
+ name_max = NAME_MAX;
+#endif
+
+ path = malloc(dir->used + name_max);
+ assert(path);
+ strcpy(path, dir->ptr);
+ path_file = path + i;
+
+ if (NULL == (dp = opendir(path))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "opendir failed:", dir, strerror(errno));
+
+ free(path);
+ return -1;
+ }
+
+ dirs.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
+ assert(dirs.ent);
+ dirs.size = DIRLIST_BLOB_SIZE;
+ dirs.used = 0;
+ files.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
+ assert(files.ent);
+ files.size = DIRLIST_BLOB_SIZE;
+ files.used = 0;
+
+ while ((dent = readdir(dp)) != NULL) {
+ unsigned short exclude_match = 0;
+
+ if (dent->d_name[0] == '.') {
+ if (hide_dotfiles)
+ continue;
+ if (dent->d_name[1] == '\0')
+ continue;
+ if (dent->d_name[1] == '.' && dent->d_name[2] == '\0')
+ continue;
+ }
+
+ if (p->conf.hide_readme_file) {
+ if (strcmp(dent->d_name, "README.txt") == 0)
+ continue;
+ }
+ if (p->conf.hide_header_file) {
+ if (strcmp(dent->d_name, "HEADER.txt") == 0)
+ continue;
+ }
+
+ /* compare d_name against excludes array
+ * elements, skipping any that match.
+ */
+#ifdef HAVE_PCRE_H
+ for(i = 0; i < p->conf.excludes->used; i++) {
+ int n;
+#define N 10
+ int ovec[N * 3];
+ pcre *regex = p->conf.excludes->ptr[i]->regex;
+
+ if ((n = pcre_exec(regex, NULL, dent->d_name,
+ strlen(dent->d_name), 0, 0, ovec, 3 * N)) < 0) {
+ if (n != PCRE_ERROR_NOMATCH) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "execution error while matching:", n);
+
+ return -1;
+ }
+ }
+ else {
+ exclude_match = 1;
+ break;
+ }
+ }
+
+ if (exclude_match) {
+ continue;
+ }
+#endif
+
+ i = strlen(dent->d_name);
+
+ /* NOTE: the manual says, d_name is never more than NAME_MAX
+ * so this should actually not be a buffer-overflow-risk
+ */
+ if (i > (size_t)name_max) continue;
+
+ memcpy(path_file, dent->d_name, i + 1);
+ if (stat(path, &st) != 0)
+ continue;
+
+ list = &files;
+ if (S_ISDIR(st.st_mode))
+ list = &dirs;
+
+ if (list->used == list->size) {
+ list->size += DIRLIST_BLOB_SIZE;
+ list->ent = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size);
+ assert(list->ent);
+ }
+
+ tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i);
+ tmp->mtime = st.st_mtime;
+ tmp->size = st.st_size;
+ tmp->namelen = i;
+ memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1);
+
+ list->ent[list->used++] = tmp;
+ }
+ closedir(dp);
+
+ if (dirs.used) http_dirls_sort(dirs.ent, dirs.used);
+
+ if (files.used) http_dirls_sort(files.ent, files.used);
+
+ out = chunkqueue_get_append_buffer(con->write_queue);
+ BUFFER_COPY_STRING_CONST(out, "<?xml version=\"1.0\" encoding=\"");
+ if (buffer_is_empty(p->conf.encoding)) {
+ BUFFER_APPEND_STRING_CONST(out, "iso-8859-1");
+ } else {
+ buffer_append_string_buffer(out, p->conf.encoding);
+ }
+ BUFFER_APPEND_STRING_CONST(out, "\"?>\n");
+ http_list_directory_header(srv, con, p, out);
+
+ /* directories */
+ for (i = 0; i < dirs.used; i++) {
+ tmp = dirs.ent[i];
+
+#ifdef HAVE_LOCALTIME_R
+ localtime_r(&(tmp->mtime), &tm);
+ strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
+#else
+ strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
+#endif
+
+ BUFFER_APPEND_STRING_CONST(out, "<tr><td class=\"n\"><a href=\"");
+ buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
+ BUFFER_APPEND_STRING_CONST(out, "/\">");
+ buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
+ BUFFER_APPEND_STRING_CONST(out, "</a>/</td><td class=\"m\">");
+ buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
+ BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"s\">- &nbsp;</td><td class=\"t\">Directory</td></tr>\n");
+
+ free(tmp);
+ }
+
+ /* files */
+ for (i = 0; i < files.used; i++) {
+ tmp = files.ent[i];
+
+ content_type = NULL;
+#ifdef HAVE_XATTR
+
+ if (con->conf.use_xattr) {
+ memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1);
+ attrlen = sizeof(attrval) - 1;
+ if (attr_get(path, "Content-Type", attrval, &attrlen, 0) == 0) {
+ attrval[attrlen] = '\0';
+ content_type = attrval;
+ }
+ }
+#endif
+
+ if (content_type == NULL) {
+ content_type = "application/octet-stream";
+ for (k = 0; k < con->conf.mimetypes->used; k++) {
+ data_string *ds = (data_string *)con->conf.mimetypes->data[k];
+ size_t ct_len;
+
+ if (ds->key->used == 0)
+ continue;
+
+ ct_len = ds->key->used - 1;
+ if (tmp->namelen < ct_len)
+ continue;
+
+ if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) {
+ content_type = ds->value->ptr;
+ break;
+ }
+ }
+ }
+
+#ifdef HAVE_LOCALTIME_R
+ localtime_r(&(tmp->mtime), &tm);
+ strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
+#else
+ strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
+#endif
+ http_list_directory_sizefmt(sizebuf, tmp->size);
+
+ BUFFER_APPEND_STRING_CONST(out, "<tr><td class=\"n\"><a href=\"");
+ buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
+ BUFFER_APPEND_STRING_CONST(out, "\">");
+ buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
+ BUFFER_APPEND_STRING_CONST(out, "</a></td><td class=\"m\">");
+ buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
+ BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"s\">");
+ buffer_append_string(out, sizebuf);
+ BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"t\">");
+ buffer_append_string(out, content_type);
+ BUFFER_APPEND_STRING_CONST(out, "</td></tr>\n");
+
+ free(tmp);
+ }
+
+ free(files.ent);
+ free(dirs.ent);
+ free(path);
+
+ http_list_directory_footer(srv, con, p, out);
+
+ /* Insert possible charset to Content-Type */
+ if (buffer_is_empty(p->conf.encoding)) {
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+ } else {
+ buffer_copy_string(p->content_charset, "text/html; charset=");
+ buffer_append_string_buffer(p->content_charset, p->conf.encoding);
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset));
+ }
+
+ con->file_finished = 1;
+
+ return 0;
+}
+
+
+
+URIHANDLER_FUNC(mod_dirlisting_subrequest) {
+ plugin_data *p = p_d;
+ stat_cache_entry *sce = NULL;
+
+ UNUSED(srv);
+
+ if (con->physical.path->used == 0) return HANDLER_GO_ON;
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+ if (con->uri.path->ptr[con->uri.path->used - 2] != '/') return HANDLER_GO_ON;
+
+ mod_dirlisting_patch_connection(srv, con, p);
+
+ if (!p->conf.dir_listing) return HANDLER_GO_ON;
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- handling the request as Dir-Listing");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI :", con->uri.path);
+ }
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
+ fprintf(stderr, "%s.%d: %s\n", __FILE__, __LINE__, con->physical.path->ptr);
+ SEGFAULT();
+ }
+
+ if (!S_ISDIR(sce->st.st_mode)) return HANDLER_GO_ON;
+
+ if (http_list_directory(srv, con, p, con->physical.path)) {
+ /* dirlisting failed */
+ con->http_status = 403;
+ }
+
+ buffer_reset(con->physical.path);
+
+ /* not found */
+ return HANDLER_FINISHED;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_dirlisting_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("dirlisting");
+
+ p->init = mod_dirlisting_init;
+ p->handle_subrequest_start = mod_dirlisting_subrequest;
+ p->set_defaults = mod_dirlisting_set_defaults;
+ p->cleanup = mod_dirlisting_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_evasive.c b/src/mod_evasive.c
new file mode 100644
index 0000000..b9d19ca
--- /dev/null
+++ b/src/mod_evasive.c
@@ -0,0 +1,178 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "inet_ntop_cache.h"
+
+/**
+ * mod_evasive
+ *
+ * we indent to implement all features the mod_evasive from apache has
+ *
+ * - limit of connections per IP
+ * - provide a list of block-listed ip/networks (no access)
+ * - provide a white-list of ips/network which is not affected by the limit
+ * (hmm, conditionals might be enough)
+ * - provide a bandwidth limiter per IP
+ *
+ * started by:
+ * - w1zzard@techpowerup.com
+ */
+
+typedef struct {
+ unsigned short max_conns;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_evasive_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+FREE_FUNC(mod_evasive_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_evasive_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "evasive.max-conns-per-ip", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->max_conns = 0;
+
+ cv[0].destination = &(s->max_conns);
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_evasive_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(max_conns);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("evasive.max-conns-per-ip"))) {
+ PATCH(max_conns);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_evasive_uri_handler) {
+ plugin_data *p = p_d;
+ size_t conns_by_ip = 0;
+ size_t j;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_evasive_patch_connection(srv, con, p);
+
+ /* no limit set, nothing to block */
+ if (p->conf.max_conns == 0) return HANDLER_GO_ON;
+
+ for (j = 0; j < srv->conns->used; j++) {
+ connection *c = srv->conns->ptr[j];
+
+ /* check if other connections are already actively serving data for the same IP
+ * we can only ban connections which are already behind the 'read request' state
+ * */
+ if (c->dst_addr.ipv4.sin_addr.s_addr == con->dst_addr.ipv4.sin_addr.s_addr &&
+ c->state > CON_STATE_REQUEST_END) {
+ conns_by_ip++;
+
+ if (conns_by_ip > p->conf.max_conns) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ inet_ntop_cache_get_ip(srv, &(con->dst_addr)),
+ "turned away. Too many connections.");
+
+ con->http_status = 403;
+ return HANDLER_FINISHED;
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+int mod_evasive_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("evasive");
+
+ p->init = mod_evasive_init;
+ p->set_defaults = mod_evasive_set_defaults;
+ p->handle_uri_clean = mod_evasive_uri_handler;
+ p->cleanup = mod_evasive_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_evhost.c b/src/mod_evhost.c
new file mode 100644
index 0000000..bc8adb6
--- /dev/null
+++ b/src/mod_evhost.c
@@ -0,0 +1,334 @@
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "plugin.h"
+#include "log.h"
+#include "response.h"
+#include "stat_cache.h"
+
+typedef struct {
+ /* unparsed pieces */
+ buffer *path_pieces_raw;
+
+ /* pieces for path creation */
+ size_t len;
+ buffer **path_pieces;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer *tmp_buf;
+
+ plugin_config **config_storage;
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_evhost_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->tmp_buf = buffer_init();
+
+ return p;
+}
+
+FREE_FUNC(mod_evhost_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (!s) continue;
+
+ if(s->path_pieces) {
+ size_t j;
+ for (j = 0; j < s->len; j++) {
+ buffer_free(s->path_pieces[j]);
+ }
+
+ free(s->path_pieces);
+ }
+
+ buffer_free(s->path_pieces_raw);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->tmp_buf);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+static void mod_evhost_parse_pattern(plugin_config *s) {
+ char *ptr = s->path_pieces_raw->ptr,*pos;
+
+ s->path_pieces = NULL;
+
+ for(pos=ptr;*ptr;ptr++) {
+ if(*ptr == '%') {
+ s->path_pieces = realloc(s->path_pieces,(s->len+2) * sizeof(*s->path_pieces));
+ s->path_pieces[s->len] = buffer_init();
+ s->path_pieces[s->len+1] = buffer_init();
+
+ buffer_copy_string_len(s->path_pieces[s->len],pos,ptr-pos);
+ pos = ptr + 2;
+
+ buffer_copy_string_len(s->path_pieces[s->len+1],ptr++,2);
+
+ s->len += 2;
+ }
+ }
+
+ if(*pos != '\0') {
+ s->path_pieces = realloc(s->path_pieces,(s->len+1) * sizeof(*s->path_pieces));
+ s->path_pieces[s->len] = buffer_init();
+
+ buffer_append_memory(s->path_pieces[s->len],pos,ptr-pos);
+
+ s->len += 1;
+ }
+}
+
+SETDEFAULTS_FUNC(mod_evhost_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ /**
+ *
+ * #
+ * # define a pattern for the host url finding
+ * # %% => % sign
+ * # %0 => domain name + tld
+ * # %1 => tld
+ * # %2 => domain name without tld
+ * # %3 => subdomain 1 name
+ * # %4 => subdomain 2 name
+ * #
+ * evhost.path-pattern = "/home/ckruse/dev/www/%3/htdocs/"
+ *
+ */
+
+ config_values_t cv[] = {
+ { "evhost.path-pattern", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->path_pieces_raw = buffer_init();
+ s->path_pieces = NULL;
+ s->len = 0;
+
+ cv[0].destination = s->path_pieces_raw;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (s->path_pieces_raw->used != 0) {
+ mod_evhost_parse_pattern(s);
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+/**
+ * assign the different parts of the domain to array-indezes
+ * - %0 - full hostname (authority w/o port)
+ * - %1 - tld
+ * - %2 - domain.tld
+ * - %3 -
+ */
+
+static int mod_evhost_parse_host(connection *con,array *host) {
+ /* con->uri.authority->used is always > 0 if we come here */
+ register char *ptr = con->uri.authority->ptr + con->uri.authority->used - 1;
+ char *colon = ptr; /* needed to filter out the colon (if exists) */
+ int first = 1;
+ data_string *ds;
+ int i;
+
+ /* first, find the domain + tld */
+ for(;ptr > con->uri.authority->ptr;ptr--) {
+ if(*ptr == '.') {
+ if(first) first = 0;
+ else break;
+ } else if(*ptr == ':') {
+ colon = ptr;
+ first = 1;
+ }
+ }
+
+ ds = data_string_init();
+ buffer_copy_string(ds->key,"%0");
+
+ /* if we stopped at a dot, skip the dot */
+ if (*ptr == '.') ptr++;
+ buffer_copy_string_len(ds->value, ptr, colon-ptr);
+
+ array_insert_unique(host,(data_unset *)ds);
+
+ /* if the : is not the start of the authority, go on parsing the hostname */
+
+ if (colon != con->uri.authority->ptr) {
+ for(ptr = colon - 1, i = 1; ptr > con->uri.authority->ptr; ptr--) {
+ if(*ptr == '.') {
+ if (ptr != colon - 1) {
+ /* is something between the dots */
+ ds = data_string_init();
+ buffer_copy_string(ds->key,"%");
+ buffer_append_long(ds->key, i++);
+ buffer_copy_string_len(ds->value,ptr+1,colon-ptr-1);
+
+ array_insert_unique(host,(data_unset *)ds);
+ }
+ colon = ptr;
+ }
+ }
+
+ /* if the . is not the first charactor of the hostname */
+ if (colon != ptr) {
+ ds = data_string_init();
+ buffer_copy_string(ds->key,"%");
+ buffer_append_long(ds->key, i++);
+ buffer_copy_string_len(ds->value,ptr,colon-ptr);
+
+ array_insert_unique(host,(data_unset *)ds);
+ }
+ }
+
+ return 0;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_evhost_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(path_pieces);
+ PATCH(len);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("evhost.path-pattern"))) {
+ PATCH(path_pieces);
+ PATCH(len);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+
+static handler_t mod_evhost_uri_handler(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ size_t i;
+ array *parsed_host;
+ register char *ptr;
+ int not_good = 0;
+ stat_cache_entry *sce = NULL;
+
+ /* not authority set */
+ if (con->uri.authority->used == 0) return HANDLER_GO_ON;
+
+ mod_evhost_patch_connection(srv, con, p);
+
+ /* missing even default(global) conf */
+ if (0 == p->conf.len) {
+ return HANDLER_GO_ON;
+ }
+
+ parsed_host = array_init();
+
+ mod_evhost_parse_host(con, parsed_host);
+
+ /* build document-root */
+ buffer_reset(p->tmp_buf);
+
+ for (i = 0; i < p->conf.len; i++) {
+ ptr = p->conf.path_pieces[i]->ptr;
+ if (*ptr == '%') {
+ data_string *ds;
+
+ if (*(ptr+1) == '%') {
+ /* %% */
+ BUFFER_APPEND_STRING_CONST(p->tmp_buf,"%");
+ } else if (NULL != (ds = (data_string *)array_get_element(parsed_host,p->conf.path_pieces[i]->ptr))) {
+ if (ds->value->used) {
+ buffer_append_string_buffer(p->tmp_buf,ds->value);
+ }
+ } else {
+ /* unhandled %-sequence */
+ }
+ } else {
+ buffer_append_string_buffer(p->tmp_buf,p->conf.path_pieces[i]);
+ }
+ }
+
+ BUFFER_APPEND_SLASH(p->tmp_buf);
+
+ array_free(parsed_host);
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, p->tmp_buf, &sce)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf);
+ not_good = 1;
+ } else if(!S_ISDIR(sce->st.st_mode)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "not a directory:", p->tmp_buf);
+ not_good = 1;
+ }
+
+ if (!not_good) {
+ buffer_copy_string_buffer(con->physical.doc_root, p->tmp_buf);
+ }
+
+ return HANDLER_GO_ON;
+}
+
+int mod_evhost_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("evhost");
+ p->init = mod_evhost_init;
+ p->set_defaults = mod_evhost_set_defaults;
+ p->handle_docroot = mod_evhost_uri_handler;
+ p->cleanup = mod_evhost_free;
+
+ p->data = NULL;
+
+ return 0;
+}
+
+/* eof */
diff --git a/src/mod_expire.c b/src/mod_expire.c
new file mode 100644
index 0000000..619b542
--- /dev/null
+++ b/src/mod_expire.c
@@ -0,0 +1,361 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+#include "response.h"
+
+#include "plugin.h"
+#include "stat_cache.h"
+
+/**
+ * this is a expire module for a lighttpd
+ *
+ * set 'Expires:' HTTP Headers on demand
+ */
+
+
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ array *expire_url;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *expire_tstmp;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_expire_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->expire_tstmp = buffer_init();
+
+ buffer_prepare_copy(p->expire_tstmp, 255);
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_expire_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ buffer_free(p->expire_tstmp);
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->expire_url);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+static int mod_expire_get_offset(server *srv, plugin_data *p, buffer *expire, int *offset) {
+ char *ts;
+ int type = -1;
+ int retts = 0;
+
+ UNUSED(p);
+
+ /*
+ * parse
+ *
+ * '(access|modification) [plus] {<num> <type>}*'
+ *
+ * e.g. 'access 1 years'
+ */
+
+ if (expire->used == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "empty:");
+ return -1;
+ }
+
+ ts = expire->ptr;
+
+ if (0 == strncmp(ts, "access ", 7)) {
+ type = 0;
+ ts += 7;
+ } else if (0 == strncmp(ts, "modification ", 13)) {
+ type = 1;
+ ts += 13;
+ } else {
+ /* invalid type-prefix */
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "invalid <base>:", ts);
+ return -1;
+ }
+
+ if (0 == strncmp(ts, "plus ", 5)) {
+ /* skip the optional plus */
+ ts += 5;
+ }
+
+ /* the rest is just <number> (years|months|days|hours|minutes|seconds) */
+ while (1) {
+ char *space, *err;
+ int num;
+
+ if (NULL == (space = strchr(ts, ' '))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "missing space after <num>:", ts);
+ return -1;
+ }
+
+ num = strtol(ts, &err, 10);
+ if (*err != ' ') {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "missing <type> after <num>:", ts);
+ return -1;
+ }
+
+ ts = space + 1;
+
+ if (NULL != (space = strchr(ts, ' '))) {
+ int slen;
+ /* */
+
+ slen = space - ts;
+
+ if (slen == 5 &&
+ 0 == strncmp(ts, "years", slen)) {
+ num *= 60 * 60 * 24 * 30 * 12;
+ } else if (slen == 6 &&
+ 0 == strncmp(ts, "months", slen)) {
+ num *= 60 * 60 * 24 * 30;
+ } else if (slen == 4 &&
+ 0 == strncmp(ts, "days", slen)) {
+ num *= 60 * 60 * 24;
+ } else if (slen == 5 &&
+ 0 == strncmp(ts, "hours", slen)) {
+ num *= 60 * 60;
+ } else if (slen == 7 &&
+ 0 == strncmp(ts, "minutes", slen)) {
+ num *= 60;
+ } else if (slen == 7 &&
+ 0 == strncmp(ts, "seconds", slen)) {
+ num *= 1;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "unknown type:", ts);
+ return -1;
+ }
+
+ retts += num;
+
+ ts = space + 1;
+ } else {
+ if (0 == strcmp(ts, "years")) {
+ num *= 60 * 60 * 24 * 30 * 12;
+ } else if (0 == strcmp(ts, "months")) {
+ num *= 60 * 60 * 24 * 30;
+ } else if (0 == strcmp(ts, "days")) {
+ num *= 60 * 60 * 24;
+ } else if (0 == strcmp(ts, "hours")) {
+ num *= 60 * 60;
+ } else if (0 == strcmp(ts, "minutes")) {
+ num *= 60;
+ } else if (0 == strcmp(ts, "seconds")) {
+ num *= 1;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "unknown type:", ts);
+ return -1;
+ }
+
+ retts += num;
+
+ break;
+ }
+ }
+
+ if (offset != NULL) *offset = retts;
+
+ return type;
+}
+
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_expire_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0, k;
+
+ config_values_t cv[] = {
+ { "expire.url", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->expire_url = array_init();
+
+ cv[0].destination = s->expire_url;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ for (k = 0; k < s->expire_url->used; k++) {
+ data_string *ds = (data_string *)s->expire_url->data[k];
+
+ /* parse lines */
+ if (-1 == mod_expire_get_offset(srv, p, ds->value, NULL)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "parsing expire.url failed:", ds->value);
+ return HANDLER_ERROR;
+ }
+ }
+ }
+
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_expire_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(expire_url);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("expire.url"))) {
+ PATCH(expire_url);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_expire_path_handler) {
+ plugin_data *p = p_d;
+ int s_len;
+ size_t k;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_expire_patch_connection(srv, con, p);
+
+ s_len = con->uri.path->used - 1;
+
+ for (k = 0; k < p->conf.expire_url->used; k++) {
+ data_string *ds = (data_string *)p->conf.expire_url->data[k];
+ int ct_len = ds->key->used - 1;
+
+ if (ct_len > s_len) continue;
+ if (ds->key->used == 0) continue;
+
+ if (0 == strncmp(con->uri.path->ptr, ds->key->ptr, ct_len)) {
+ int ts;
+ time_t t;
+ size_t len;
+ stat_cache_entry *sce = NULL;
+
+ stat_cache_get_entry(srv, con, con->physical.path, &sce);
+
+ switch(mod_expire_get_offset(srv, p, ds->value, &ts)) {
+ case 0:
+ /* access */
+ t = (ts + srv->cur_ts);
+ break;
+ case 1:
+ /* modification */
+
+ t = (ts + sce->st.st_mtime);
+ break;
+ default:
+ /* -1 is handled at parse-time */
+ break;
+ }
+
+
+ if (0 == (len = strftime(p->expire_tstmp->ptr, p->expire_tstmp->size - 1,
+ "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(t))))) {
+ /* could not set expire header, out of mem */
+
+ return HANDLER_GO_ON;
+
+ }
+
+ p->expire_tstmp->used = len + 1;
+
+ /* HTTP/1.0 */
+ response_header_overwrite(srv, con, CONST_STR_LEN("Expires"), CONST_BUF_LEN(p->expire_tstmp));
+
+ /* HTTP/1.1 */
+ buffer_copy_string(p->expire_tstmp, "max-age=");
+ buffer_append_long(p->expire_tstmp, ts);
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Cache-Control"), CONST_BUF_LEN(p->expire_tstmp));
+
+ return HANDLER_GO_ON;
+ }
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_expire_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("expire");
+
+ p->init = mod_expire_init;
+ p->handle_subrequest_start = mod_expire_path_handler;
+ p->set_defaults = mod_expire_set_defaults;
+ p->cleanup = mod_expire_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_fastcgi.c b/src/mod_fastcgi.c
new file mode 100644
index 0000000..da20c76
--- /dev/null
+++ b/src/mod_fastcgi.c
@@ -0,0 +1,3874 @@
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <signal.h>
+
+#include "buffer.h"
+#include "server.h"
+#include "keyvalue.h"
+#include "log.h"
+
+#include "http_chunk.h"
+#include "fdevent.h"
+#include "connections.h"
+#include "response.h"
+#include "joblist.h"
+
+#include "plugin.h"
+
+#include "inet_ntop_cache.h"
+#include "stat_cache.h"
+
+#include <fastcgi.h>
+#include <stdio.h>
+
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#include "sys-socket.h"
+
+
+#ifndef UNIX_PATH_MAX
+# define UNIX_PATH_MAX 108
+#endif
+
+#ifdef HAVE_SYS_UIO_H
+#include <sys/uio.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+
+/*
+ *
+ * TODO:
+ *
+ * - add timeout for a connect to a non-fastcgi process
+ * (use state_timestamp + state)
+ *
+ */
+
+typedef struct fcgi_proc {
+ size_t id; /* id will be between 1 and max_procs */
+ buffer *socket; /* config.socket + "-" + id */
+ unsigned port; /* config.port + pno */
+
+ pid_t pid; /* PID of the spawned process (0 if not spawned locally) */
+
+
+ size_t load; /* number of requests waiting on this process */
+
+ time_t last_used; /* see idle_timeout */
+ size_t requests; /* see max_requests */
+ struct fcgi_proc *prev, *next; /* see first */
+
+ time_t disabled_until; /* this proc is disabled until, use something else until than */
+
+ int is_local;
+
+ enum {
+ PROC_STATE_UNSET, /* init-phase */
+ PROC_STATE_RUNNING, /* alive */
+ PROC_STATE_DIED_WAIT_FOR_PID,
+ PROC_STATE_KILLED, /* was killed as we don't have the load anymore */
+ PROC_STATE_DIED, /* marked as dead, should be restarted */
+ PROC_STATE_DISABLED /* proc disabled as it resulted in an error */
+ } state;
+} fcgi_proc;
+
+typedef struct {
+ /* the key that is used to reference this value */
+ buffer *id;
+
+ /* list of processes handling this extension
+ * sorted by lowest load
+ *
+ * whenever a job is done move it up in the list
+ * until it is sorted, move it down as soon as the
+ * job is started
+ */
+ fcgi_proc *first;
+ fcgi_proc *unused_procs;
+
+ /*
+ * spawn at least min_procs, at max_procs.
+ *
+ * as soon as the load of the first entry
+ * is max_load_per_proc we spawn a new one
+ * and add it to the first entry and give it
+ * the load
+ *
+ */
+
+ unsigned short min_procs;
+ unsigned short max_procs;
+ size_t num_procs; /* how many procs are started */
+ size_t active_procs; /* how many of them are really running */
+
+ unsigned short max_load_per_proc;
+
+ /*
+ * kick the process from the list if it was not
+ * used for idle_timeout until min_procs is
+ * reached. this helps to get the processlist
+ * small again we had a small peak load.
+ *
+ */
+
+ unsigned short idle_timeout;
+
+ /*
+ * time after a disabled remote connection is tried to be re-enabled
+ *
+ *
+ */
+
+ unsigned short disable_time;
+
+ /*
+ * same fastcgi processes get a little bit larger
+ * than wanted. max_requests_per_proc kills a
+ * process after a number of handled requests.
+ *
+ */
+ size_t max_requests_per_proc;
+
+
+ /* config */
+
+ /*
+ * host:port
+ *
+ * if host is one of the local IP adresses the
+ * whole connection is local
+ *
+ * if tcp/ip should be used host AND port have
+ * to be specified
+ *
+ */
+ buffer *host;
+ unsigned short port;
+
+ /*
+ * Unix Domain Socket
+ *
+ * instead of TCP/IP we can use Unix Domain Sockets
+ * - more secure (you have fileperms to play with)
+ * - more control (on locally)
+ * - more speed (no extra overhead)
+ */
+ buffer *unixsocket;
+
+ /* if socket is local we can start the fastcgi
+ * process ourself
+ *
+ * bin-path is the path to the binary
+ *
+ * check min_procs and max_procs for the number
+ * of process to start-up
+ */
+ buffer *bin_path;
+
+ /* bin-path is set bin-environment is taken to
+ * create the environement before starting the
+ * FastCGI process
+ *
+ */
+ array *bin_env;
+
+ array *bin_env_copy;
+
+ /*
+ * docroot-translation between URL->phys and the
+ * remote host
+ *
+ * reasons:
+ * - different dir-layout if remote
+ * - chroot if local
+ *
+ */
+ buffer *docroot;
+
+ /*
+ * fastcgi-mode:
+ * - responser
+ * - authorizer
+ *
+ */
+ unsigned short mode;
+
+ /*
+ * check_local tell you if the phys file is stat()ed
+ * or not. FastCGI doesn't care if the service is
+ * remote. If the web-server side doesn't contain
+ * the fastcgi-files we should not stat() for them
+ * and say '404 not found'.
+ */
+ unsigned short check_local;
+
+ /*
+ * append PATH_INFO to SCRIPT_FILENAME
+ *
+ * php needs this if cgi.fix_pathinfo is provied
+ *
+ */
+
+ unsigned short break_scriptfilename_for_php;
+
+ /*
+ * If the backend includes X-LIGHTTPD-send-file in the response
+ * we use the value as filename and ignore the content.
+ *
+ */
+ unsigned short allow_xsendfile;
+
+ ssize_t load; /* replace by host->load */
+
+ size_t max_id; /* corresponds most of the time to
+ num_procs.
+
+ only if a process is killed max_id waits for the process itself
+ to die and decrements its afterwards */
+
+ buffer *strip_request_uri;
+} fcgi_extension_host;
+
+/*
+ * one extension can have multiple hosts assigned
+ * one host can spawn additional processes on the same
+ * socket (if we control it)
+ *
+ * ext -> host -> procs
+ * 1:n 1:n
+ *
+ * if the fastcgi process is remote that whole goes down
+ * to
+ *
+ * ext -> host -> procs
+ * 1:n 1:1
+ *
+ * in case of PHP and FCGI_CHILDREN we have again a procs
+ * but we don't control it directly.
+ *
+ */
+
+typedef struct {
+ buffer *key; /* like .php */
+
+ fcgi_extension_host **hosts;
+
+ size_t used;
+ size_t size;
+} fcgi_extension;
+
+typedef struct {
+ fcgi_extension **exts;
+
+ size_t used;
+ size_t size;
+} fcgi_exts;
+
+
+typedef struct {
+ fcgi_exts *exts;
+
+ array *ext_mapping;
+
+ int debug;
+} plugin_config;
+
+typedef struct {
+ size_t *ptr;
+ size_t used;
+ size_t size;
+} buffer_uint;
+
+typedef struct {
+ char **ptr;
+
+ size_t size;
+ size_t used;
+} char_array;
+
+/* generic plugin data, shared between all connections */
+typedef struct {
+ PLUGIN_DATA;
+ buffer_uint fcgi_request_id;
+
+ buffer *fcgi_env;
+
+ buffer *path;
+ buffer *parse_response;
+
+ buffer *statuskey;
+
+ plugin_config **config_storage;
+
+ plugin_config conf; /* this is only used as long as no handler_ctx is setup */
+} plugin_data;
+
+/* connection specific data */
+typedef enum {
+ FCGI_STATE_UNSET,
+ FCGI_STATE_INIT,
+ FCGI_STATE_CONNECT_DELAYED,
+ FCGI_STATE_PREPARE_WRITE,
+ FCGI_STATE_WRITE,
+ FCGI_STATE_READ
+} fcgi_connection_state_t;
+
+typedef struct {
+ fcgi_proc *proc;
+ fcgi_extension_host *host;
+ fcgi_extension *ext;
+
+ fcgi_connection_state_t state;
+ time_t state_timestamp;
+
+ int reconnects; /* number of reconnect attempts */
+
+ chunkqueue *rb; /* read queue */
+ chunkqueue *wb; /* write queue */
+
+ buffer *response_header;
+
+ size_t request_id;
+ int fd; /* fd to the fastcgi process */
+ int fde_ndx; /* index into the fd-event buffer */
+
+ pid_t pid;
+ int got_proc;
+
+ int send_content_body;
+
+ plugin_config conf;
+
+ connection *remote_conn; /* dumb pointer */
+ plugin_data *plugin_data; /* dumb pointer */
+} handler_ctx;
+
+
+/* ok, we need a prototype */
+static handler_t fcgi_handle_fdevent(void *s, void *ctx, int revents);
+
+int fcgi_proclist_sort_down(server *srv, fcgi_extension_host *host, fcgi_proc *proc);
+
+data_integer *status_counter_get_counter(server *srv, const char *s, size_t len) {
+ data_integer *di;
+
+ if (NULL == (di = (data_integer *)array_get_element(srv->status, s))) {
+ /* not found, create it */
+
+ if (NULL == (di = (data_integer *)array_get_unused_element(srv->status, TYPE_INTEGER))) {
+ di = data_integer_init();
+ }
+ buffer_copy_string_len(di->key, s, len);
+ di->value = 0;
+
+ array_insert_unique(srv->status, (data_unset *)di);
+ }
+ return di;
+}
+
+/* dummies of the statistic framework functions
+ * they will be moved to a statistics.c later */
+int status_counter_inc(server *srv, const char *s, size_t len) {
+ data_integer *di = status_counter_get_counter(srv, s, len);
+
+ di->value++;
+
+ return 0;
+}
+
+int status_counter_dec(server *srv, const char *s, size_t len) {
+ data_integer *di = status_counter_get_counter(srv, s, len);
+
+ if (di->value > 0) di->value--;
+
+ return 0;
+}
+
+int status_counter_set(server *srv, const char *s, size_t len, int val) {
+ data_integer *di = status_counter_get_counter(srv, s, len);
+
+ di->value = val;
+
+ return 0;
+}
+
+int fastcgi_status_copy_procname(buffer *b, fcgi_extension_host *host, fcgi_proc *proc) {
+ buffer_copy_string(b, "fastcgi.backend.");
+ buffer_append_string_buffer(b, host->id);
+ buffer_append_string(b, ".");
+ buffer_append_long(b, proc->id);
+
+ return 0;
+}
+
+int fastcgi_status_init(server *srv, buffer *b, fcgi_extension_host *host, fcgi_proc *proc) {
+#define CLEAN(x) \
+ fastcgi_status_copy_procname(b, host, proc); \
+ buffer_append_string(b, x); \
+ status_counter_set(srv, CONST_BUF_LEN(b), 0);
+
+ CLEAN(".disabled");
+ CLEAN(".died");
+ CLEAN(".overloaded");
+ CLEAN(".connected");
+ CLEAN(".load");
+
+#undef CLEAN
+
+ return 0;
+}
+
+static handler_ctx * handler_ctx_init() {
+ handler_ctx * hctx;
+
+ hctx = calloc(1, sizeof(*hctx));
+ assert(hctx);
+
+ hctx->fde_ndx = -1;
+
+ hctx->response_header = buffer_init();
+
+ hctx->request_id = 0;
+ hctx->state = FCGI_STATE_INIT;
+ hctx->proc = NULL;
+
+ hctx->fd = -1;
+
+ hctx->reconnects = 0;
+ hctx->send_content_body = 1;
+
+ hctx->rb = chunkqueue_init();
+ hctx->wb = chunkqueue_init();
+
+ return hctx;
+}
+
+static void handler_ctx_free(handler_ctx *hctx) {
+ if (hctx->host) {
+ hctx->host->load--;
+ hctx->host = NULL;
+ }
+
+ buffer_free(hctx->response_header);
+
+ chunkqueue_free(hctx->rb);
+ chunkqueue_free(hctx->wb);
+
+ free(hctx);
+}
+
+fcgi_proc *fastcgi_process_init() {
+ fcgi_proc *f;
+
+ f = calloc(1, sizeof(*f));
+ f->socket = buffer_init();
+
+ f->prev = NULL;
+ f->next = NULL;
+
+ return f;
+}
+
+void fastcgi_process_free(fcgi_proc *f) {
+ if (!f) return;
+
+ fastcgi_process_free(f->next);
+
+ buffer_free(f->socket);
+
+ free(f);
+}
+
+fcgi_extension_host *fastcgi_host_init() {
+ fcgi_extension_host *f;
+
+ f = calloc(1, sizeof(*f));
+
+ f->id = buffer_init();
+ f->host = buffer_init();
+ f->unixsocket = buffer_init();
+ f->docroot = buffer_init();
+ f->bin_path = buffer_init();
+ f->bin_env = array_init();
+ f->bin_env_copy = array_init();
+ f->strip_request_uri = buffer_init();
+
+ return f;
+}
+
+void fastcgi_host_free(fcgi_extension_host *h) {
+ if (!h) return;
+
+ buffer_free(h->id);
+ buffer_free(h->host);
+ buffer_free(h->unixsocket);
+ buffer_free(h->docroot);
+ buffer_free(h->bin_path);
+ buffer_free(h->strip_request_uri);
+ array_free(h->bin_env);
+ array_free(h->bin_env_copy);
+
+ fastcgi_process_free(h->first);
+ fastcgi_process_free(h->unused_procs);
+
+ free(h);
+
+}
+
+fcgi_exts *fastcgi_extensions_init() {
+ fcgi_exts *f;
+
+ f = calloc(1, sizeof(*f));
+
+ return f;
+}
+
+void fastcgi_extensions_free(fcgi_exts *f) {
+ size_t i;
+
+ if (!f) return;
+
+ for (i = 0; i < f->used; i++) {
+ fcgi_extension *fe;
+ size_t j;
+
+ fe = f->exts[i];
+
+ for (j = 0; j < fe->used; j++) {
+ fcgi_extension_host *h;
+
+ h = fe->hosts[j];
+
+ fastcgi_host_free(h);
+ }
+
+ buffer_free(fe->key);
+ free(fe->hosts);
+
+ free(fe);
+ }
+
+ free(f->exts);
+
+ free(f);
+}
+
+int fastcgi_extension_insert(fcgi_exts *ext, buffer *key, fcgi_extension_host *fh) {
+ fcgi_extension *fe;
+ size_t i;
+
+ /* there is something */
+
+ for (i = 0; i < ext->used; i++) {
+ if (buffer_is_equal(key, ext->exts[i]->key)) {
+ break;
+ }
+ }
+
+ if (i == ext->used) {
+ /* filextension is new */
+ fe = calloc(1, sizeof(*fe));
+ assert(fe);
+ fe->key = buffer_init();
+ buffer_copy_string_buffer(fe->key, key);
+
+ /* */
+
+ if (ext->size == 0) {
+ ext->size = 8;
+ ext->exts = malloc(ext->size * sizeof(*(ext->exts)));
+ assert(ext->exts);
+ } else if (ext->used == ext->size) {
+ ext->size += 8;
+ ext->exts = realloc(ext->exts, ext->size * sizeof(*(ext->exts)));
+ assert(ext->exts);
+ }
+ ext->exts[ext->used++] = fe;
+ } else {
+ fe = ext->exts[i];
+ }
+
+ if (fe->size == 0) {
+ fe->size = 4;
+ fe->hosts = malloc(fe->size * sizeof(*(fe->hosts)));
+ assert(fe->hosts);
+ } else if (fe->size == fe->used) {
+ fe->size += 4;
+ fe->hosts = realloc(fe->hosts, fe->size * sizeof(*(fe->hosts)));
+ assert(fe->hosts);
+ }
+
+ fe->hosts[fe->used++] = fh;
+
+ return 0;
+
+}
+
+INIT_FUNC(mod_fastcgi_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->fcgi_env = buffer_init();
+
+ p->path = buffer_init();
+ p->parse_response = buffer_init();
+
+ p->statuskey = buffer_init();
+
+ return p;
+}
+
+
+FREE_FUNC(mod_fastcgi_free) {
+ plugin_data *p = p_d;
+ buffer_uint *r = &(p->fcgi_request_id);
+
+ UNUSED(srv);
+
+ if (r->ptr) free(r->ptr);
+
+ buffer_free(p->fcgi_env);
+ buffer_free(p->path);
+ buffer_free(p->parse_response);
+ buffer_free(p->statuskey);
+
+ if (p->config_storage) {
+ size_t i, j, n;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+ fcgi_exts *exts;
+
+ if (!s) continue;
+
+ exts = s->exts;
+
+ for (j = 0; j < exts->used; j++) {
+ fcgi_extension *ex;
+
+ ex = exts->exts[j];
+
+ for (n = 0; n < ex->used; n++) {
+ fcgi_proc *proc;
+ fcgi_extension_host *host;
+
+ host = ex->hosts[n];
+
+ for (proc = host->first; proc; proc = proc->next) {
+ if (proc->pid != 0) kill(proc->pid, SIGTERM);
+
+ if (proc->is_local &&
+ !buffer_is_empty(proc->socket)) {
+ unlink(proc->socket->ptr);
+ }
+ }
+
+ for (proc = host->unused_procs; proc; proc = proc->next) {
+ if (proc->pid != 0) kill(proc->pid, SIGTERM);
+
+ if (proc->is_local &&
+ !buffer_is_empty(proc->socket)) {
+ unlink(proc->socket->ptr);
+ }
+ }
+ }
+ }
+
+ fastcgi_extensions_free(s->exts);
+ array_free(s->ext_mapping);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+static int env_add(char_array *env, const char *key, size_t key_len, const char *val, size_t val_len) {
+ char *dst;
+
+ if (!key || !val) return -1;
+
+ dst = malloc(key_len + val_len + 3);
+ memcpy(dst, key, key_len);
+ dst[key_len] = '=';
+ /* add the \0 from the value */
+ memcpy(dst + key_len + 1, val, val_len + 1);
+
+ if (env->size == 0) {
+ env->size = 16;
+ env->ptr = malloc(env->size * sizeof(*env->ptr));
+ } else if (env->size == env->used + 1) {
+ env->size += 16;
+ env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
+ }
+
+ env->ptr[env->used++] = dst;
+
+ return 0;
+}
+
+static int parse_binpath(char_array *env, buffer *b) {
+ char *start;
+ size_t i;
+ /* search for spaces */
+
+ start = b->ptr;
+ for (i = 0; i < b->used - 1; i++) {
+ switch(b->ptr[i]) {
+ case ' ':
+ case '\t':
+ /* a WS, stop here and copy the argument */
+
+ if (env->size == 0) {
+ env->size = 16;
+ env->ptr = malloc(env->size * sizeof(*env->ptr));
+ } else if (env->size == env->used) {
+ env->size += 16;
+ env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
+ }
+
+ b->ptr[i] = '\0';
+
+ env->ptr[env->used++] = start;
+
+ start = b->ptr + i + 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (env->size == 0) {
+ env->size = 16;
+ env->ptr = malloc(env->size * sizeof(*env->ptr));
+ } else if (env->size == env->used) { /* we need one extra for the terminating NULL */
+ env->size += 16;
+ env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
+ }
+
+ /* the rest */
+ env->ptr[env->used++] = start;
+
+ if (env->size == 0) {
+ env->size = 16;
+ env->ptr = malloc(env->size * sizeof(*env->ptr));
+ } else if (env->size == env->used) { /* we need one extra for the terminating NULL */
+ env->size += 16;
+ env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
+ }
+
+ /* terminate */
+ env->ptr[env->used++] = NULL;
+
+ return 0;
+}
+
+static int fcgi_spawn_connection(server *srv,
+ plugin_data *p,
+ fcgi_extension_host *host,
+ fcgi_proc *proc) {
+ int fcgi_fd;
+ int socket_type, status;
+ struct timeval tv = { 0, 100 * 1000 };
+#ifdef HAVE_SYS_UN_H
+ struct sockaddr_un fcgi_addr_un;
+#endif
+ struct sockaddr_in fcgi_addr_in;
+ struct sockaddr *fcgi_addr;
+
+ socklen_t servlen;
+
+#ifndef HAVE_FORK
+ return -1;
+#endif
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sdb",
+ "new proc, socket:", proc->port, proc->socket);
+ }
+
+ if (!buffer_is_empty(proc->socket)) {
+ memset(&fcgi_addr, 0, sizeof(fcgi_addr));
+
+#ifdef HAVE_SYS_UN_H
+ fcgi_addr_un.sun_family = AF_UNIX;
+ strcpy(fcgi_addr_un.sun_path, proc->socket->ptr);
+
+#ifdef SUN_LEN
+ servlen = SUN_LEN(&fcgi_addr_un);
+#else
+ /* stevens says: */
+ servlen = proc->socket->used - 1 + sizeof(fcgi_addr_un.sun_family);
+#endif
+ socket_type = AF_UNIX;
+ fcgi_addr = (struct sockaddr *) &fcgi_addr_un;
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: Unix Domain sockets are not supported.");
+ return -1;
+#endif
+ } else {
+ fcgi_addr_in.sin_family = AF_INET;
+
+ if (buffer_is_empty(host->host)) {
+ fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
+ } else {
+ struct hostent *he;
+
+ /* set a usefull default */
+ fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
+
+
+ if (NULL == (he = gethostbyname(host->host->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "sdb", "gethostbyname failed: ",
+ h_errno, host->host);
+ return -1;
+ }
+
+ if (he->h_addrtype != AF_INET) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "addr-type != AF_INET: ", he->h_addrtype);
+ return -1;
+ }
+
+ if (he->h_length != sizeof(struct in_addr)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "addr-length != sizeof(in_addr): ", he->h_length);
+ return -1;
+ }
+
+ memcpy(&(fcgi_addr_in.sin_addr.s_addr), he->h_addr_list[0], he->h_length);
+
+ }
+ fcgi_addr_in.sin_port = htons(proc->port);
+ servlen = sizeof(fcgi_addr_in);
+
+ socket_type = AF_INET;
+ fcgi_addr = (struct sockaddr *) &fcgi_addr_in;
+ }
+
+ if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "failed:", strerror(errno));
+ return -1;
+ }
+
+ if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) {
+ /* server is not up, spawn in */
+ pid_t child;
+ int val;
+
+ if (!buffer_is_empty(proc->socket)) {
+ unlink(proc->socket->ptr);
+ }
+
+ close(fcgi_fd);
+
+ /* reopen socket */
+ if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "socket failed:", strerror(errno));
+ return -1;
+ }
+
+ val = 1;
+ if (setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "socketsockopt failed:", strerror(errno));
+ return -1;
+ }
+
+ /* create socket */
+ if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbds",
+ "bind failed for:",
+ proc->socket,
+ proc->port,
+ strerror(errno));
+ return -1;
+ }
+
+ if (-1 == listen(fcgi_fd, 1024)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "listen failed:", strerror(errno));
+ return -1;
+ }
+
+#ifdef HAVE_FORK
+ switch ((child = fork())) {
+ case 0: {
+ size_t i = 0;
+ char *c;
+ char_array env;
+ char_array arg;
+
+ /* create environment */
+ env.ptr = NULL;
+ env.size = 0;
+ env.used = 0;
+
+ arg.ptr = NULL;
+ arg.size = 0;
+ arg.used = 0;
+
+ if(fcgi_fd != FCGI_LISTENSOCK_FILENO) {
+ close(FCGI_LISTENSOCK_FILENO);
+ dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO);
+ close(fcgi_fd);
+ }
+
+ /* we don't need the client socket */
+ for (i = 3; i < 256; i++) {
+ close(i);
+ }
+
+ /* build clean environment */
+ if (host->bin_env_copy->used) {
+ for (i = 0; i < host->bin_env_copy->used; i++) {
+ data_string *ds = (data_string *)host->bin_env_copy->data[i];
+ char *ge;
+
+ if (NULL != (ge = getenv(ds->value->ptr))) {
+ env_add(&env, CONST_BUF_LEN(ds->value), ge, strlen(ge));
+ }
+ }
+ } else {
+ for (i = 0; environ[i]; i++) {
+ char *eq;
+
+ if (NULL != (eq = strchr(environ[i], '='))) {
+ env_add(&env, environ[i], eq - environ[i], eq+1, strlen(eq+1));
+ }
+ }
+ }
+
+ /* create environment */
+ for (i = 0; i < host->bin_env->used; i++) {
+ data_string *ds = (data_string *)host->bin_env->data[i];
+
+ env_add(&env, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value));
+ }
+
+ for (i = 0; i < env.used; i++) {
+ /* search for PHP_FCGI_CHILDREN */
+ if (0 == strncmp(env.ptr[i], "PHP_FCGI_CHILDREN=", sizeof("PHP_FCGI_CHILDREN=") - 1)) break;
+ }
+
+ /* not found, add a default */
+ if (i == env.used) {
+ env_add(&env, CONST_STR_LEN("PHP_FCGI_CHILDREN"), CONST_STR_LEN("1"));
+ }
+
+ env.ptr[env.used] = NULL;
+
+ parse_binpath(&arg, host->bin_path);
+
+ /* chdir into the base of the bin-path,
+ * search for the last / */
+ if (NULL != (c = strrchr(arg.ptr[0], '/'))) {
+ *c = '\0';
+
+ /* change to the physical directory */
+ if (-1 == chdir(arg.ptr[0])) {
+ *c = '/';
+ log_error_write(srv, __FILE__, __LINE__, "sss", "chdir failed:", strerror(errno), arg.ptr[0]);
+ }
+ *c = '/';
+ }
+
+
+ /* exec the cgi */
+ execve(arg.ptr[0], arg.ptr, env.ptr);
+
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "execve failed for:", host->bin_path, strerror(errno));
+
+ exit(errno);
+
+ break;
+ }
+ case -1:
+ /* error */
+ break;
+ default:
+ /* father */
+
+ /* wait */
+ select(0, NULL, NULL, NULL, &tv);
+
+ switch (waitpid(child, &status, WNOHANG)) {
+ case 0:
+ /* child still running after timeout, good */
+ break;
+ case -1:
+ /* no PID found ? should never happen */
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "pid not found:", strerror(errno));
+ return -1;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "the fastcgi-backend", host->bin_path, "failed to start:");
+ /* the child should not terminate at all */
+ if (WIFEXITED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sdb",
+ "child exited with status",
+ WEXITSTATUS(status), host->bin_path);
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "if you try do run PHP as FastCGI backend make sure you use the FastCGI enabled version.\n"
+ "You can find out if it is the right one by executing 'php -v' and it should display '(cgi-fcgi)' "
+ "in the output, NOT (cgi) NOR (cli)\n"
+ "For more information check http://www.lighttpd.net/documentation/fastcgi.html#preparing-php-as-a-fastcgi-program");
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "If this is PHP on Gentoo add fastcgi to the USE flags");
+ } else if (WIFSIGNALED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "terminated by signal:",
+ WTERMSIG(status));
+
+ if (WTERMSIG(status) == 11) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "to be exact: it seg-fault, crashed, died, ... you get the idea." );
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "If this is PHP try to remove the byte-code caches for now and try again.");
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:",
+ status);
+ }
+ return -1;
+ }
+
+ /* register process */
+ proc->pid = child;
+ proc->last_used = srv->cur_ts;
+ proc->is_local = 1;
+
+ break;
+ }
+#endif
+ } else {
+ proc->is_local = 0;
+ proc->pid = 0;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "(debug) socket is already used, won't spawn:",
+ proc->socket);
+ }
+ }
+
+ proc->state = PROC_STATE_RUNNING;
+ host->active_procs++;
+
+ close(fcgi_fd);
+
+ return 0;
+}
+
+
+SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
+ plugin_data *p = p_d;
+ data_unset *du;
+ size_t i = 0;
+ buffer *fcgi_mode = buffer_init();
+
+ config_values_t cv[] = {
+ { "fastcgi.server", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "fastcgi.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "fastcgi.map-extensions", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ array *ca;
+
+ s = malloc(sizeof(plugin_config));
+ s->exts = fastcgi_extensions_init();
+ s->debug = 0;
+ s->ext_mapping = array_init();
+
+ cv[0].destination = s->exts;
+ cv[1].destination = &(s->debug);
+ cv[2].destination = s->ext_mapping;
+
+ p->config_storage[i] = s;
+ ca = ((data_config *)srv->config_context->data[i])->value;
+
+ if (0 != config_insert_values_global(srv, ca, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ /*
+ * <key> = ( ... )
+ */
+
+ if (NULL != (du = array_get_element(ca, "fastcgi.server"))) {
+ size_t j;
+ data_array *da = (data_array *)du;
+
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", "fastcgi.server", "array of strings");
+
+ return HANDLER_ERROR;
+ }
+
+
+ /*
+ * fastcgi.server = ( "<ext>" => ( ... ),
+ * "<ext>" => ( ... ) )
+ */
+
+ for (j = 0; j < da->value->used; j++) {
+ size_t n;
+ data_array *da_ext = (data_array *)da->value->data[j];
+
+ if (da->value->data[j]->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ", "fastcgi.server",
+ "[", da->value->data[j]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ /*
+ * da_ext->key == name of the extension
+ */
+
+ /*
+ * fastcgi.server = ( "<ext>" =>
+ * ( "<host>" => ( ... ),
+ * "<host>" => ( ... )
+ * ),
+ * "<ext>" => ... )
+ */
+
+ for (n = 0; n < da_ext->value->used; n++) {
+ data_array *da_host = (data_array *)da_ext->value->data[n];
+
+ fcgi_extension_host *host;
+
+ config_values_t fcv[] = {
+ { "host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "docroot", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "mode", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "socket", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { "bin-path", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
+
+ { "check-local", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
+ { "port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
+ { "min-procs-not-working", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 7 this is broken for now */
+ { "max-procs", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
+ { "max-load-per-proc", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 9 */
+ { "idle-timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 10 */
+ { "disable-time", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 11 */
+
+ { "bin-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
+ { "bin-copy-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 13 */
+
+ { "broken-scriptfilename", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 14 */
+ { "allow-x-send-file", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */
+ { "strip-request-uri", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 16 */
+
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (da_host->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "ssSBS",
+ "unexpected type for key:",
+ "fastcgi.server",
+ "[", da_host->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ host = fastcgi_host_init();
+
+ buffer_copy_string_buffer(host->id, da_host->key);
+
+ host->check_local = 1;
+ host->min_procs = 4;
+ host->max_procs = 4;
+ host->max_load_per_proc = 1;
+ host->idle_timeout = 60;
+ host->mode = FCGI_RESPONDER;
+ host->disable_time = 60;
+ host->break_scriptfilename_for_php = 0;
+ host->allow_xsendfile = 0; /* handle X-LIGHTTPD-send-file */
+
+ fcv[0].destination = host->host;
+ fcv[1].destination = host->docroot;
+ fcv[2].destination = fcgi_mode;
+ fcv[3].destination = host->unixsocket;
+ fcv[4].destination = host->bin_path;
+
+ fcv[5].destination = &(host->check_local);
+ fcv[6].destination = &(host->port);
+ fcv[7].destination = &(host->min_procs);
+ fcv[8].destination = &(host->max_procs);
+ fcv[9].destination = &(host->max_load_per_proc);
+ fcv[10].destination = &(host->idle_timeout);
+ fcv[11].destination = &(host->disable_time);
+
+ fcv[12].destination = host->bin_env;
+ fcv[13].destination = host->bin_env_copy;
+ fcv[14].destination = &(host->break_scriptfilename_for_php);
+ fcv[15].destination = &(host->allow_xsendfile);
+ fcv[16].destination = host->strip_request_uri;
+
+ if (0 != config_insert_values_internal(srv, da_host->value, fcv)) {
+ return HANDLER_ERROR;
+ }
+
+ if ((!buffer_is_empty(host->host) || host->port) &&
+ !buffer_is_empty(host->unixsocket)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "either host+port or socket");
+
+ return HANDLER_ERROR;
+ }
+
+ if (!buffer_is_empty(host->unixsocket)) {
+ /* unix domain socket */
+
+ if (host->unixsocket->used > UNIX_PATH_MAX - 2) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "path of the unixdomain socket is too large");
+ return HANDLER_ERROR;
+ }
+ } else {
+ /* tcp/ip */
+
+ if (buffer_is_empty(host->host) &&
+ buffer_is_empty(host->bin_path)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbbbs",
+ "missing key (string):",
+ da->key,
+ da_ext->key,
+ da_host->key,
+ "host");
+
+ return HANDLER_ERROR;
+ } else if (host->port == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sbbbs",
+ "missing key (short):",
+ da->key,
+ da_ext->key,
+ da_host->key,
+ "port");
+ return HANDLER_ERROR;
+ }
+ }
+
+ if (!buffer_is_empty(host->bin_path)) {
+ /* a local socket + self spawning */
+ size_t pno;
+
+ /* HACK: just to make sure the adaptive spawing is disabled */
+ host->min_procs = host->max_procs;
+
+ if (host->min_procs > host->max_procs) host->max_procs = host->min_procs;
+ if (host->max_load_per_proc < 1) host->max_load_per_proc = 0;
+
+ if (s->debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbsdsbsdsd",
+ "--- fastcgi spawning local",
+ "\n\tproc:", host->bin_path,
+ "\n\tport:", host->port,
+ "\n\tsocket", host->unixsocket,
+ "\n\tmin-procs:", host->min_procs,
+ "\n\tmax-procs:", host->max_procs);
+ }
+
+ for (pno = 0; pno < host->min_procs; pno++) {
+ fcgi_proc *proc;
+
+ proc = fastcgi_process_init();
+ proc->id = host->num_procs++;
+ host->max_id++;
+
+ if (buffer_is_empty(host->unixsocket)) {
+ proc->port = host->port + pno;
+ } else {
+ buffer_copy_string_buffer(proc->socket, host->unixsocket);
+ buffer_append_string(proc->socket, "-");
+ buffer_append_long(proc->socket, pno);
+ }
+
+ if (s->debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd",
+ "--- fastcgi spawning",
+ "\n\tport:", host->port,
+ "\n\tsocket", host->unixsocket,
+ "\n\tcurrent:", pno, "/", host->min_procs);
+ }
+
+ if (fcgi_spawn_connection(srv, p, host, proc)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "[ERROR]: spawning fcgi failed.");
+ return HANDLER_ERROR;
+ }
+
+ fastcgi_status_init(srv, p->statuskey, host, proc);
+
+ proc->next = host->first;
+ if (host->first) host->first->prev = proc;
+
+ host->first = proc;
+ }
+ } else {
+ fcgi_proc *proc;
+
+ proc = fastcgi_process_init();
+ proc->id = host->num_procs++;
+ host->max_id++;
+ host->active_procs++;
+ proc->state = PROC_STATE_RUNNING;
+
+ if (buffer_is_empty(host->unixsocket)) {
+ proc->port = host->port;
+ } else {
+ buffer_copy_string_buffer(proc->socket, host->unixsocket);
+ }
+
+ fastcgi_status_init(srv, p->statuskey, host, proc);
+
+ host->first = proc;
+
+ host->min_procs = 1;
+ host->max_procs = 1;
+ }
+
+ if (!buffer_is_empty(fcgi_mode)) {
+ if (strcmp(fcgi_mode->ptr, "responder") == 0) {
+ host->mode = FCGI_RESPONDER;
+ } else if (strcmp(fcgi_mode->ptr, "authorizer") == 0) {
+ host->mode = FCGI_AUTHORIZER;
+ if (buffer_is_empty(host->docroot)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: docroot is required for authorizer mode.");
+ return HANDLER_ERROR;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "WARNING: unknown fastcgi mode:",
+ fcgi_mode, "(ignored, mode set to responder)");
+ }
+ }
+
+ /* if extension already exists, take it */
+ fastcgi_extension_insert(s->exts, da_ext->key, host);
+ }
+ }
+ }
+ }
+
+ buffer_free(fcgi_mode);
+
+ return HANDLER_GO_ON;
+}
+
+static int fcgi_set_state(server *srv, handler_ctx *hctx, fcgi_connection_state_t state) {
+ hctx->state = state;
+ hctx->state_timestamp = srv->cur_ts;
+
+ return 0;
+}
+
+
+static size_t fcgi_requestid_new(server *srv, plugin_data *p) {
+ size_t m = 0;
+ size_t i;
+ buffer_uint *r = &(p->fcgi_request_id);
+
+ UNUSED(srv);
+
+ for (i = 0; i < r->used; i++) {
+ if (r->ptr[i] > m) m = r->ptr[i];
+ }
+
+ if (r->size == 0) {
+ r->size = 16;
+ r->ptr = malloc(sizeof(*r->ptr) * r->size);
+ } else if (r->used == r->size) {
+ r->size += 16;
+ r->ptr = realloc(r->ptr, sizeof(*r->ptr) * r->size);
+ }
+
+ r->ptr[r->used++] = ++m;
+
+ return m;
+}
+
+static int fcgi_requestid_del(server *srv, plugin_data *p, size_t request_id) {
+ size_t i;
+ buffer_uint *r = &(p->fcgi_request_id);
+
+ UNUSED(srv);
+
+ for (i = 0; i < r->used; i++) {
+ if (r->ptr[i] == request_id) break;
+ }
+
+ if (i != r->used) {
+ /* found */
+
+ if (i != r->used - 1) {
+ r->ptr[i] = r->ptr[r->used - 1];
+ }
+ r->used--;
+ }
+
+ return 0;
+}
+void fcgi_connection_close(server *srv, handler_ctx *hctx) {
+ plugin_data *p;
+ connection *con;
+
+ if (NULL == hctx) return;
+
+ p = hctx->plugin_data;
+ con = hctx->remote_conn;
+
+ if (con->mode != p->id) {
+ WP();
+ return;
+ }
+
+ if (hctx->fd != -1) {
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+ close(hctx->fd);
+ srv->cur_fds--;
+ }
+
+ if (hctx->request_id != 0) {
+ fcgi_requestid_del(srv, p, hctx->request_id);
+ }
+
+ if (hctx->host && hctx->proc) {
+ if (hctx->got_proc) {
+ /* after the connect the process gets a load */
+ hctx->proc->load--;
+
+ status_counter_dec(srv, CONST_STR_LEN("fastcgi.active-requests"));
+
+ fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc);
+ buffer_append_string(p->statuskey, ".load");
+
+ status_counter_set(srv, CONST_BUF_LEN(p->statuskey), hctx->proc->load);
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sddb",
+ "release proc:",
+ hctx->fd,
+ hctx->proc->pid, hctx->proc->socket);
+ }
+ }
+
+ fcgi_proclist_sort_down(srv, hctx->host, hctx->proc);
+ }
+
+
+ handler_ctx_free(hctx);
+ con->plugin_ctx[p->id] = NULL;
+}
+
+static int fcgi_reconnect(server *srv, handler_ctx *hctx) {
+ plugin_data *p = hctx->plugin_data;
+
+ /* child died
+ *
+ * 1.
+ *
+ * connect was ok, connection was accepted
+ * but the php accept loop checks after the accept if it should die or not.
+ *
+ * if yes we can only detect it at a write()
+ *
+ * next step is resetting this attemp and setup a connection again
+ *
+ * if we have more then 5 reconnects for the same request, die
+ *
+ * 2.
+ *
+ * we have a connection but the child died by some other reason
+ *
+ */
+
+ if (hctx->fd != -1) {
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+ close(hctx->fd);
+ srv->cur_fds--;
+ }
+
+ fcgi_requestid_del(srv, p, hctx->request_id);
+
+ fcgi_set_state(srv, hctx, FCGI_STATE_INIT);
+
+ hctx->request_id = 0;
+ hctx->reconnects++;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sddb",
+ "release proc:",
+ hctx->fd,
+ hctx->proc->pid, hctx->proc->socket);
+ }
+
+ if (hctx->proc) {
+ hctx->proc->load--;
+ fcgi_proclist_sort_down(srv, hctx->host, hctx->proc);
+ }
+
+ /* perhaps another host gives us more luck */
+ hctx->host->load--;
+ hctx->host = NULL;
+
+ return 0;
+}
+
+
+static handler_t fcgi_connection_reset(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ fcgi_connection_close(srv, con->plugin_ctx[p->id]);
+
+ return HANDLER_GO_ON;
+}
+
+
+static int fcgi_env_add(buffer *env, const char *key, size_t key_len, const char *val, size_t val_len) {
+ size_t len;
+
+ if (!key || !val) return -1;
+
+ len = key_len + val_len;
+
+ len += key_len > 127 ? 4 : 1;
+ len += val_len > 127 ? 4 : 1;
+
+ buffer_prepare_append(env, len);
+
+ if (key_len > 127) {
+ env->ptr[env->used++] = ((key_len >> 24) & 0xff) | 0x80;
+ env->ptr[env->used++] = (key_len >> 16) & 0xff;
+ env->ptr[env->used++] = (key_len >> 8) & 0xff;
+ env->ptr[env->used++] = (key_len >> 0) & 0xff;
+ } else {
+ env->ptr[env->used++] = (key_len >> 0) & 0xff;
+ }
+
+ if (val_len > 127) {
+ env->ptr[env->used++] = ((val_len >> 24) & 0xff) | 0x80;
+ env->ptr[env->used++] = (val_len >> 16) & 0xff;
+ env->ptr[env->used++] = (val_len >> 8) & 0xff;
+ env->ptr[env->used++] = (val_len >> 0) & 0xff;
+ } else {
+ env->ptr[env->used++] = (val_len >> 0) & 0xff;
+ }
+
+ memcpy(env->ptr + env->used, key, key_len);
+ env->used += key_len;
+ memcpy(env->ptr + env->used, val, val_len);
+ env->used += val_len;
+
+ return 0;
+}
+
+static int fcgi_header(FCGI_Header * header, unsigned char type, size_t request_id, int contentLength, unsigned char paddingLength) {
+ header->version = FCGI_VERSION_1;
+ header->type = type;
+ header->requestIdB0 = request_id & 0xff;
+ header->requestIdB1 = (request_id >> 8) & 0xff;
+ header->contentLengthB0 = contentLength & 0xff;
+ header->contentLengthB1 = (contentLength >> 8) & 0xff;
+ header->paddingLength = paddingLength;
+ header->reserved = 0;
+
+ return 0;
+}
+/**
+ *
+ * returns
+ * -1 error
+ * 0 connected
+ * 1 not connected yet
+ */
+
+typedef enum {
+ CONNECTION_UNSET,
+ CONNECTION_OK,
+ CONNECTION_DELAYED, /* retry after event, take same host */
+ CONNECTION_OVERLOADED, /* disable for 1 seconds, take another backend */
+ CONNECTION_DEAD /* disable for 60 seconds, take another backend */
+} connection_result_t;
+
+static connection_result_t fcgi_establish_connection(server *srv, handler_ctx *hctx) {
+ struct sockaddr *fcgi_addr;
+ struct sockaddr_in fcgi_addr_in;
+#ifdef HAVE_SYS_UN_H
+ struct sockaddr_un fcgi_addr_un;
+#endif
+ socklen_t servlen;
+
+ fcgi_extension_host *host = hctx->host;
+ fcgi_proc *proc = hctx->proc;
+ int fcgi_fd = hctx->fd;
+
+ memset(&fcgi_addr, 0, sizeof(fcgi_addr));
+
+ if (!buffer_is_empty(proc->socket)) {
+#ifdef HAVE_SYS_UN_H
+ /* use the unix domain socket */
+ fcgi_addr_un.sun_family = AF_UNIX;
+ strcpy(fcgi_addr_un.sun_path, proc->socket->ptr);
+#ifdef SUN_LEN
+ servlen = SUN_LEN(&fcgi_addr_un);
+#else
+ /* stevens says: */
+ servlen = proc->socket->used - 1 + sizeof(fcgi_addr_un.sun_family);
+#endif
+ fcgi_addr = (struct sockaddr *) &fcgi_addr_un;
+#else
+ return -1;
+#endif
+ } else {
+ fcgi_addr_in.sin_family = AF_INET;
+ if (0 == inet_aton(host->host->ptr, &(fcgi_addr_in.sin_addr))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "converting IP-adress failed for", host->host,
+ "\nBe sure to specify an IP address here");
+
+ return -1;
+ }
+ fcgi_addr_in.sin_port = htons(proc->port);
+ servlen = sizeof(fcgi_addr_in);
+
+ fcgi_addr = (struct sockaddr *) &fcgi_addr_in;
+ }
+
+ if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) {
+ if (errno == EINPROGRESS ||
+ errno == EALREADY ||
+ errno == EINTR) {
+ if (hctx->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "connect delayed, will continue later:", host->host);
+ }
+
+ return CONNECTION_DELAYED;
+ } else if (errno == EAGAIN) {
+ if (hctx->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "This means that the you have more incoming requests than your fastcgi-backend can handle in parallel. "
+ "Perhaps it helps to spawn more fastcgi backend or php-children, if not decrease server.max-connections."
+ "The load for this fastcgi backend is:", proc->load);
+ }
+
+ return CONNECTION_OVERLOADED;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssdb",
+ "connect failed:",
+ strerror(errno),
+ proc->port, proc->socket);
+
+ return CONNECTION_DEAD;
+ }
+ }
+
+ hctx->reconnects = 0;
+ if (hctx->conf.debug > 1) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connect succeeded: ", fcgi_fd);
+ }
+
+ return CONNECTION_OK;
+}
+
+static int fcgi_env_add_request_headers(server *srv, connection *con, plugin_data *p) {
+ size_t i;
+
+ for (i = 0; i < con->request.headers->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->request.headers->data[i];
+
+ if (ds->value->used && ds->key->used) {
+ size_t j;
+ buffer_reset(srv->tmp_buf);
+
+ if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
+ BUFFER_COPY_STRING_CONST(srv->tmp_buf, "HTTP_");
+ srv->tmp_buf->used--;
+ }
+
+ buffer_prepare_append(srv->tmp_buf, ds->key->used + 2);
+ for (j = 0; j < ds->key->used - 1; j++) {
+ char c = '_';
+ if (light_isalpha(ds->key->ptr[j])) {
+ /* upper-case */
+ c = ds->key->ptr[j] & ~32;
+ } else if (light_isdigit(ds->key->ptr[j])) {
+ /* copy */
+ c = ds->key->ptr[j];
+ }
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] = c;
+ }
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0';
+
+ fcgi_env_add(p->fcgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value));
+ }
+ }
+
+ for (i = 0; i < con->environment->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->environment->data[i];
+
+ if (ds->value->used && ds->key->used) {
+ size_t j;
+ buffer_reset(srv->tmp_buf);
+
+ buffer_prepare_append(srv->tmp_buf, ds->key->used + 2);
+ for (j = 0; j < ds->key->used - 1; j++) {
+ char c = '_';
+ if (light_isalpha(ds->key->ptr[j])) {
+ /* upper-case */
+ c = ds->key->ptr[j] & ~32;
+ } else if (light_isdigit(ds->key->ptr[j])) {
+ /* copy */
+ c = ds->key->ptr[j];
+ }
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] = c;
+ }
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0';
+
+ fcgi_env_add(p->fcgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value));
+ }
+ }
+
+ return 0;
+}
+
+
+static int fcgi_create_env(server *srv, handler_ctx *hctx, size_t request_id) {
+ FCGI_BeginRequestRecord beginRecord;
+ FCGI_Header header;
+ buffer *b;
+
+ char buf[32];
+ const char *s;
+#ifdef HAVE_IPV6
+ char b2[INET6_ADDRSTRLEN + 1];
+#endif
+
+ plugin_data *p = hctx->plugin_data;
+ fcgi_extension_host *host= hctx->host;
+
+ connection *con = hctx->remote_conn;
+ server_socket *srv_sock = con->srv_socket;
+
+ sock_addr our_addr;
+ socklen_t our_addr_len;
+
+ /* send FCGI_BEGIN_REQUEST */
+
+ fcgi_header(&(beginRecord.header), FCGI_BEGIN_REQUEST, request_id, sizeof(beginRecord.body), 0);
+ beginRecord.body.roleB0 = host->mode;
+ beginRecord.body.roleB1 = 0;
+ beginRecord.body.flags = 0;
+ memset(beginRecord.body.reserved, 0, sizeof(beginRecord.body.reserved));
+
+ b = chunkqueue_get_append_buffer(hctx->wb);
+
+ buffer_copy_memory(b, (const char *)&beginRecord, sizeof(beginRecord));
+
+ /* send FCGI_PARAMS */
+ buffer_prepare_copy(p->fcgi_env, 1024);
+
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_NAME"/"PACKAGE_VERSION));
+
+ if (con->server_name->used) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_NAME"), CONST_BUF_LEN(con->server_name));
+ } else {
+#ifdef HAVE_IPV6
+ s = inet_ntop(srv_sock->addr.plain.sa_family,
+ srv_sock->addr.plain.sa_family == AF_INET6 ?
+ (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
+ (const void *) &(srv_sock->addr.ipv4.sin_addr),
+ b2, sizeof(b2)-1);
+#else
+ s = inet_ntoa(srv_sock->addr.ipv4.sin_addr);
+#endif
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s));
+ }
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1"));
+
+ ltostr(buf,
+#ifdef HAVE_IPV6
+ ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port)
+#else
+ ntohs(srv_sock->addr.ipv4.sin_port)
+#endif
+ );
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_PORT"), buf, strlen(buf));
+
+ /* get the server-side of the connection to the client */
+ our_addr_len = sizeof(our_addr);
+
+ if (-1 == getsockname(con->fd, &(our_addr.plain), &our_addr_len)) {
+ s = inet_ntop_cache_get_ip(srv, &(srv_sock->addr));
+ } else {
+ s = inet_ntop_cache_get_ip(srv, &(our_addr));
+ }
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s));
+
+ ltostr(buf,
+#ifdef HAVE_IPV6
+ ntohs(con->dst_addr.plain.sa_family ? con->dst_addr.ipv6.sin6_port : con->dst_addr.ipv4.sin_port)
+#else
+ ntohs(con->dst_addr.ipv4.sin_port)
+#endif
+ );
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REMOTE_PORT"), buf, strlen(buf));
+
+ s = inet_ntop_cache_get_ip(srv, &(con->dst_addr));
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s));
+
+ if (!buffer_is_empty(con->authed_user)) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REMOTE_USER"),
+ CONST_BUF_LEN(con->authed_user));
+ }
+
+ if (con->request.content_length > 0 && host->mode != FCGI_AUTHORIZER) {
+ /* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */
+
+ /* request.content_length < SSIZE_MAX, see request.c */
+ ltostr(buf, con->request.content_length);
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf));
+ }
+
+ if (host->mode != FCGI_AUTHORIZER) {
+ /*
+ * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to
+ * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html
+ * (6.1.14, 6.1.6, 6.1.7)
+ * For AUTHORIZER mode these headers should be omitted.
+ */
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path));
+
+ if (!buffer_is_empty(con->request.pathinfo)) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo));
+
+ /* PATH_TRANSLATED is only defined if PATH_INFO is set */
+
+ if (!buffer_is_empty(host->docroot)) {
+ buffer_copy_string_buffer(p->path, host->docroot);
+ } else {
+ buffer_copy_string_buffer(p->path, con->physical.doc_root);
+ }
+ buffer_append_string_buffer(p->path, con->request.pathinfo);
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_TRANSLATED"), CONST_BUF_LEN(p->path));
+ } else {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"), CONST_STR_LEN(""));
+ }
+ }
+
+ /*
+ * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual
+ * http://www.php.net/manual/en/reserved.variables.php
+ * treatment of PATH_TRANSLATED is different from the one of CGI specs.
+ * TODO: this code should be checked against cgi.fix_pathinfo php
+ * parameter.
+ */
+
+ if (!buffer_is_empty(host->docroot)) {
+ /*
+ * rewrite SCRIPT_FILENAME
+ *
+ */
+
+ buffer_copy_string_buffer(p->path, host->docroot);
+ buffer_append_string_buffer(p->path, con->uri.path);
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(p->path));
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(host->docroot));
+ } else {
+ buffer_copy_string_buffer(p->path, con->physical.path);
+
+ /* cgi.fix_pathinfo need a broken SCRIPT_FILENAME to find out what PATH_INFO is itself
+ *
+ * see src/sapi/cgi_main.c, init_request_info()
+ */
+ if (host->break_scriptfilename_for_php) {
+ buffer_append_string_buffer(p->path, con->request.pathinfo);
+ }
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(p->path));
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root));
+ }
+
+ if (host->strip_request_uri->used > 1) {
+ /* we need at least one char to strip off */
+ /**
+ * /app1/index/list
+ *
+ * stripping /app1 or /app1/ should lead to
+ *
+ * /index/list
+ *
+ */
+ if ('/' != host->strip_request_uri->ptr[host->strip_request_uri->used - 2]) {
+ /* fix the user-input to have / as last char */
+ buffer_append_string(host->strip_request_uri, "/");
+ }
+
+ if (con->request.orig_uri->used >= host->strip_request_uri->used &&
+ 0 == strncmp(con->request.orig_uri->ptr, host->strip_request_uri->ptr, host->strip_request_uri->used - 1)) {
+ /* the left is the same */
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"),
+ con->request.orig_uri->ptr + (host->strip_request_uri->used - 2),
+ con->request.orig_uri->used - (host->strip_request_uri->used - 2));
+ } else {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri));
+ }
+ } else {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri));
+ }
+ if (!buffer_is_equal(con->request.uri, con->request.orig_uri)) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REDIRECT_URI"), CONST_BUF_LEN(con->request.uri));
+ }
+ if (!buffer_is_empty(con->uri.query)) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query));
+ } else {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_STR_LEN(""));
+ }
+
+ s = get_http_method_name(con->request.http_method);
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s));
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")); /* if php is compiled with --force-redirect */
+ s = get_http_version_name(con->request.http_version);
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s));
+
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("HTTPS"), CONST_STR_LEN("on"));
+ }
+#endif
+
+
+ fcgi_env_add_request_headers(srv, con, p);
+
+ fcgi_header(&(header), FCGI_PARAMS, request_id, p->fcgi_env->used, 0);
+ buffer_append_memory(b, (const char *)&header, sizeof(header));
+ buffer_append_memory(b, (const char *)p->fcgi_env->ptr, p->fcgi_env->used);
+
+ fcgi_header(&(header), FCGI_PARAMS, request_id, 0, 0);
+ buffer_append_memory(b, (const char *)&header, sizeof(header));
+
+ b->used++; /* add virtual \0 */
+ hctx->wb->bytes_in += b->used - 1;
+
+ if (con->request.content_length) {
+ chunkqueue *req_cq = con->request_content_queue;
+ chunk *req_c;
+ off_t offset;
+
+ /* something to send ? */
+ for (offset = 0, req_c = req_cq->first; offset != req_cq->bytes_in; ) {
+ off_t weWant = req_cq->bytes_in - offset > FCGI_MAX_LENGTH ? FCGI_MAX_LENGTH : req_cq->bytes_in - offset;
+ off_t written = 0;
+ off_t weHave = 0;
+
+ /* we announce toWrite octects
+ * now take all the request_content chunk that we need to fill this request
+ * */
+
+ b = chunkqueue_get_append_buffer(hctx->wb);
+ fcgi_header(&(header), FCGI_STDIN, request_id, weWant, 0);
+ buffer_copy_memory(b, (const char *)&header, sizeof(header));
+ hctx->wb->bytes_in += sizeof(header);
+
+ if (p->conf.debug > 10) {
+ fprintf(stderr, "%s.%d: tosend: %lld / %lld\n", __FILE__, __LINE__, offset, req_cq->bytes_in);
+ }
+
+ for (written = 0; written != weWant; ) {
+ if (p->conf.debug > 10) {
+ fprintf(stderr, "%s.%d: chunk: %lld / %lld\n", __FILE__, __LINE__, written, weWant);
+ }
+
+ switch (req_c->type) {
+ case FILE_CHUNK:
+ weHave = req_c->file.length - req_c->offset;
+
+ if (weHave > weWant - written) weHave = weWant - written;
+
+ if (p->conf.debug > 10) {
+ fprintf(stderr, "%s.%d: sending %lld bytes from (%lld / %lld) %s\n",
+ __FILE__, __LINE__,
+ weHave,
+ req_c->offset,
+ req_c->file.length,
+ req_c->file.name->ptr);
+ }
+
+ assert(weHave != 0);
+
+ chunkqueue_append_file(hctx->wb, req_c->file.name, req_c->offset, weHave);
+
+ req_c->offset += weHave;
+ req_cq->bytes_out += weHave;
+ written += weHave;
+
+ hctx->wb->bytes_in += weHave;
+
+ /* steal the tempfile
+ *
+ * This is tricky:
+ * - we reference the tempfile from the request-content-queue several times
+ * if the req_c is larger than FCGI_MAX_LENGTH
+ * - we can't simply cleanup the request-content-queue as soon as possible
+ * as it would remove the tempfiles
+ * - the idea is to 'steal' the tempfiles and attach the is_temp flag to the last
+ * referencing chunk of the fastcgi-write-queue
+ *
+ * */
+
+ if (req_c->offset == req_c->file.length) {
+ chunk *c;
+
+ if (p->conf.debug > 10) {
+ fprintf(stderr, "%s.%d: next chunk\n", __FILE__, __LINE__);
+ }
+ c = hctx->wb->last;
+
+ assert(c->type == FILE_CHUNK);
+ assert(req_c->file.is_temp == 1);
+
+ c->file.is_temp = 1;
+ req_c->file.is_temp = 0;
+
+ chunkqueue_remove_finished_chunks(req_cq);
+
+ req_c = req_cq->first;
+ }
+
+ break;
+ case MEM_CHUNK:
+ /* append to the buffer */
+ weHave = req_c->mem->used - 1 - req_c->offset;
+
+ if (weHave > weWant - written) weHave = weWant - written;
+
+ buffer_append_memory(b, req_c->mem->ptr + req_c->offset, weHave);
+
+ req_c->offset += weHave;
+ req_cq->bytes_out += weHave;
+ written += weHave;
+
+ hctx->wb->bytes_in += weHave;
+
+ if (req_c->offset == req_c->mem->used - 1) {
+ chunkqueue_remove_finished_chunks(req_cq);
+
+ req_c = req_cq->first;
+ }
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ b->used++; /* add virtual \0 */
+ offset += weWant;
+ }
+ }
+
+ b = chunkqueue_get_append_buffer(hctx->wb);
+ /* terminate STDIN */
+ fcgi_header(&(header), FCGI_STDIN, request_id, 0, 0);
+ buffer_copy_memory(b, (const char *)&header, sizeof(header));
+ b->used++; /* add virtual \0 */
+
+ hctx->wb->bytes_in += sizeof(header);
+
+#if 0
+ for (i = 0; i < hctx->write_buffer->used; i++) {
+ fprintf(stderr, "%02x ", hctx->write_buffer->ptr[i]);
+ if ((i+1) % 16 == 0) {
+ size_t j;
+ for (j = i-15; j <= i; j++) {
+ fprintf(stderr, "%c",
+ isprint((unsigned char)hctx->write_buffer->ptr[j]) ? hctx->write_buffer->ptr[j] : '.');
+ }
+ fprintf(stderr, "\n");
+ }
+ }
+#endif
+
+ return 0;
+}
+
+static int fcgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) {
+ char *s, *ns;
+
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+ fcgi_extension_host *host= hctx->host;
+
+ UNUSED(srv);
+
+ buffer_copy_string_buffer(p->parse_response, in);
+
+ /* search for \n */
+ for (s = p->parse_response->ptr; NULL != (ns = strchr(s, '\n')); s = ns + 1) {
+ char *key, *value;
+ int key_len;
+ data_string *ds;
+
+ /* a good day. Someone has read the specs and is sending a \r\n to us */
+
+ if (ns > p->parse_response->ptr &&
+ *(ns-1) == '\r') {
+ *(ns-1) = '\0';
+ }
+
+ ns[0] = '\0';
+
+ key = s;
+ if (NULL == (value = strchr(s, ':'))) {
+ /* we expect: "<key>: <value>\n" */
+ continue;
+ }
+
+ key_len = value - key;
+
+ value++;
+ /* strip WS */
+ while (*value == ' ' || *value == '\t') value++;
+
+ if (host->mode != FCGI_AUTHORIZER ||
+ !(con->http_status == 0 ||
+ con->http_status == 200)) {
+ /* authorizers shouldn't affect the response headers sent back to the client */
+
+ /* don't forward Status: */
+ if (0 != strncasecmp(key, "Status", key_len)) {
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
+ ds = data_response_init();
+ }
+ buffer_copy_string_len(ds->key, key, key_len);
+ buffer_copy_string(ds->value, value);
+
+ array_insert_unique(con->response.headers, (data_unset *)ds);
+ }
+ }
+
+ switch(key_len) {
+ case 4:
+ if (0 == strncasecmp(key, "Date", key_len)) {
+ con->parsed_response |= HTTP_DATE;
+ }
+ break;
+ case 6:
+ if (0 == strncasecmp(key, "Status", key_len)) {
+ con->http_status = strtol(value, NULL, 10);
+ con->parsed_response |= HTTP_STATUS;
+ }
+ break;
+ case 8:
+ if (0 == strncasecmp(key, "Location", key_len)) {
+ con->parsed_response |= HTTP_LOCATION;
+ }
+ break;
+ case 10:
+ if (0 == strncasecmp(key, "Connection", key_len)) {
+ con->response.keep_alive = (0 == strcasecmp(value, "Keep-Alive")) ? 1 : 0;
+ con->parsed_response |= HTTP_CONNECTION;
+ }
+ break;
+ case 14:
+ if (0 == strncasecmp(key, "Content-Length", key_len)) {
+ con->response.content_length = strtol(value, NULL, 10);
+ con->parsed_response |= HTTP_CONTENT_LENGTH;
+
+ if (con->response.content_length < 0) con->response.content_length = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* CGI/1.1 rev 03 - 7.2.1.2 */
+ if ((con->parsed_response & HTTP_LOCATION) &&
+ !(con->parsed_response & HTTP_STATUS)) {
+ con->http_status = 302;
+ }
+
+ return 0;
+}
+
+typedef struct {
+ buffer *b;
+ size_t len;
+ int type;
+ int padding;
+ size_t request_id;
+} fastcgi_response_packet;
+
+static int fastcgi_get_packet(server *srv, handler_ctx *hctx, fastcgi_response_packet *packet) {
+ chunk * c;
+ size_t offset = 0;
+ size_t toread = 0;
+ FCGI_Header *header;
+
+ if (!hctx->rb->first) return -1;
+
+ packet->b = buffer_init();
+ packet->len = 0;
+ packet->type = 0;
+ packet->padding = 0;
+ packet->request_id = 0;
+
+ /* get at least the FastCGI header */
+ for (c = hctx->rb->first; c; c = c->next) {
+ if (packet->b->used == 0) {
+ buffer_copy_string_len(packet->b, c->mem->ptr + c->offset, c->mem->used - c->offset - 1);
+ } else {
+ buffer_append_string_len(packet->b, c->mem->ptr + c->offset, c->mem->used - c->offset - 1);
+ }
+
+ if (packet->b->used >= sizeof(*header) + 1) break;
+ }
+
+ if ((packet->b->used == 0) ||
+ (packet->b->used - 1 < sizeof(FCGI_Header))) {
+ /* no header */
+ buffer_free(packet->b);
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "FastCGI: header too small");
+ return -1;
+ }
+
+ /* we have at least a header, now check how much me have to fetch */
+ header = (FCGI_Header *)(packet->b->ptr);
+
+ packet->len = (header->contentLengthB0 | (header->contentLengthB1 << 8)) + header->paddingLength;
+ packet->request_id = (header->requestIdB0 | (header->requestIdB1 << 8));
+ packet->type = header->type;
+ packet->padding = header->paddingLength;
+
+ /* the first bytes in packet->b are the header */
+ offset = sizeof(*header);
+
+ /* ->b should only be the content */
+ buffer_copy_string(packet->b, ""); /* used == 1 */
+
+ if (packet->len) {
+ /* copy the content */
+ for (; c && (packet->b->used < packet->len + 1); c = c->next) {
+ size_t weWant = packet->len - (packet->b->used - 1);
+ size_t weHave = c->mem->used - c->offset - offset - 1;
+
+ if (weHave > weWant) weHave = weWant;
+
+ buffer_append_string_len(packet->b, c->mem->ptr + c->offset + offset, weHave);
+
+ /* we only skipped the first 8 bytes as they are the fcgi header */
+ offset = 0;
+ }
+
+ if (packet->b->used < packet->len + 1) {
+ /* we didn't got the full packet */
+
+ buffer_free(packet->b);
+ return -1;
+ }
+
+ packet->b->used -= packet->padding;
+ packet->b->ptr[packet->b->used - 1] = '\0';
+ }
+
+ /* tag the chunks as read */
+ toread = packet->len + sizeof(FCGI_Header);
+ for (c = hctx->rb->first; c && toread; c = c->next) {
+ if (c->mem->used - c->offset - 1 <= toread) {
+ /* we read this whole buffer, move it to unused */
+ toread -= c->mem->used - c->offset - 1;
+ c->offset = c->mem->used - 1; /* everthing has been written */
+ } else {
+ c->offset += toread;
+ toread = 0;
+ }
+ }
+
+ chunkqueue_remove_finished_chunks(hctx->rb);
+
+ return 0;
+}
+
+static int fcgi_demux_response(server *srv, handler_ctx *hctx) {
+ int fin = 0;
+ int toread;
+ ssize_t r;
+
+ plugin_data *p = hctx->plugin_data;
+ connection *con = hctx->remote_conn;
+ int fcgi_fd = hctx->fd;
+ fcgi_extension_host *host= hctx->host;
+ fcgi_proc *proc = hctx->proc;
+
+ /*
+ * check how much we have to read
+ */
+ if (ioctl(hctx->fd, FIONREAD, &toread)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "unexpected end-of-file (perhaps the fastcgi process died):",
+ fcgi_fd);
+ return -1;
+ }
+
+ /* init read-buffer */
+
+ if (toread > 0) {
+ buffer *b;
+
+ b = chunkqueue_get_append_buffer(hctx->rb);
+ buffer_prepare_copy(b, toread + 1);
+
+ /* append to read-buffer */
+ if (-1 == (r = read(hctx->fd, b->ptr, toread))) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "unexpected end-of-file (perhaps the fastcgi process died):",
+ fcgi_fd, strerror(errno));
+ return -1;
+ }
+
+ /* this should be catched by the b > 0 above */
+ assert(r);
+
+ b->used = r + 1; /* one extra for the fake \0 */
+ b->ptr[b->used - 1] = '\0';
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsbsbsd",
+ "unexpected end-of-file (perhaps the fastcgi process died):",
+ "pid:", proc->pid,
+ "socket:", proc->socket,
+ "host:", host->host,
+ "port:", proc->port);
+
+ return -1;
+ }
+
+ /*
+ * parse the fastcgi packets and forward the content to the write-queue
+ *
+ */
+ while (fin == 0) {
+ fastcgi_response_packet packet;
+
+ /* check if we have at least one packet */
+ if (0 != fastcgi_get_packet(srv, hctx, &packet)) {
+ /* no full packet */
+ break;
+ }
+
+ switch(packet.type) {
+ case FCGI_STDOUT:
+ if (packet.len == 0) break;
+
+ /* is the header already finished */
+ if (0 == con->file_started) {
+ char *c;
+ size_t blen;
+ data_string *ds;
+
+ /* search for header terminator
+ *
+ * if we start with \r\n check if last packet terminated with \r\n
+ * if we start with \n check if last packet terminated with \n
+ * search for \r\n\r\n
+ * search for \n\n
+ */
+
+ if (hctx->response_header->used == 0) {
+ buffer_copy_string_buffer(hctx->response_header, packet.b);
+ } else {
+ buffer_append_string_buffer(hctx->response_header, packet.b);
+ }
+
+ if (NULL != (c = buffer_search_string_len(hctx->response_header, CONST_STR_LEN("\r\n\r\n")))) {
+ blen = hctx->response_header->used - (c - hctx->response_header->ptr) - 4;
+ hctx->response_header->used = (c - hctx->response_header->ptr) + 3;
+ c += 4; /* point the the start of the response */
+ } else if (NULL != (c = buffer_search_string_len(hctx->response_header, CONST_STR_LEN("\n\n")))) {
+ blen = hctx->response_header->used - (c - hctx->response_header->ptr) - 2;
+ hctx->response_header->used = c - hctx->response_header->ptr + 2;
+ c += 2; /* point the the start of the response */
+ } else {
+ /* no luck, no header found */
+ break;
+ }
+
+ /* parse the response header */
+ fcgi_response_parse(srv, con, p, hctx->response_header);
+
+ con->file_started = 1;
+
+ if (host->mode == FCGI_AUTHORIZER &&
+ (con->http_status == 0 ||
+ con->http_status == 200)) {
+ /* a authorizer with approved the static request, ignore the content here */
+ hctx->send_content_body = 0;
+ }
+
+ if (host->allow_xsendfile &&
+ NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-LIGHTTPD-send-file"))) {
+ stat_cache_entry *sce;
+
+ if (HANDLER_ERROR != stat_cache_get_entry(srv, con, ds->value, &sce)) {
+ /* found */
+
+ http_chunk_append_file(srv, con, ds->value, 0, sce->st.st_size);
+ hctx->send_content_body = 0; /* ignore the content */
+ joblist_append(srv, con);
+ }
+ }
+
+
+ if (hctx->send_content_body && blen > 1) {
+ /* enable chunked-transfer-encoding */
+ if (con->request.http_version == HTTP_VERSION_1_1 &&
+ !(con->parsed_response & HTTP_CONTENT_LENGTH)) {
+ con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
+ }
+
+ http_chunk_append_mem(srv, con, c, blen);
+ joblist_append(srv, con);
+ }
+ } else if (hctx->send_content_body && packet.b->used > 1) {
+ if (con->request.http_version == HTTP_VERSION_1_1 &&
+ !(con->parsed_response & HTTP_CONTENT_LENGTH)) {
+ /* enable chunked-transfer-encoding */
+ con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
+ }
+
+ http_chunk_append_mem(srv, con, packet.b->ptr, packet.b->used);
+ joblist_append(srv, con);
+ }
+ break;
+ case FCGI_STDERR:
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "FastCGI-stderr:", packet.b);
+
+ break;
+ case FCGI_END_REQUEST:
+ con->file_finished = 1;
+
+ if (host->mode != FCGI_AUTHORIZER ||
+ !(con->http_status == 0 ||
+ con->http_status == 200)) {
+ /* send chunk-end if nesseary */
+ http_chunk_append_mem(srv, con, NULL, 0);
+ joblist_append(srv, con);
+ }
+
+ fin = 1;
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "FastCGI: header.type not handled: ", packet.type);
+ break;
+ }
+ buffer_free(packet.b);
+ }
+
+ return fin;
+}
+
+int fcgi_proclist_sort_up(server *srv, fcgi_extension_host *host, fcgi_proc *proc) {
+ fcgi_proc *p;
+
+ UNUSED(srv);
+
+ /* we have been the smallest of the current list
+ * and we want to insert the node sorted as soon
+ * possible
+ *
+ * 1 0 0 0 1 1 1
+ * | ^
+ * | |
+ * +------+
+ *
+ */
+
+ /* nothing to sort, only one element */
+ if (host->first == proc && proc->next == NULL) return 0;
+
+ for (p = proc; p->next && p->next->load < proc->load; p = p->next);
+
+ /* no need to move something
+ *
+ * 1 2 2 2 3 3 3
+ * ^
+ * |
+ * +
+ *
+ */
+ if (p == proc) return 0;
+
+ if (host->first == proc) {
+ /* we have been the first elememt */
+
+ host->first = proc->next;
+ host->first->prev = NULL;
+ }
+
+ /* disconnect proc */
+
+ if (proc->prev) proc->prev->next = proc->next;
+ if (proc->next) proc->next->prev = proc->prev;
+
+ /* proc should be right of p */
+
+ proc->next = p->next;
+ proc->prev = p;
+ if (p->next) p->next->prev = proc;
+ p->next = proc;
+#if 0
+ for(p = host->first; p; p = p->next) {
+ log_error_write(srv, __FILE__, __LINE__, "dd",
+ p->pid, p->load);
+ }
+#else
+ UNUSED(srv);
+#endif
+
+ return 0;
+}
+
+int fcgi_proclist_sort_down(server *srv, fcgi_extension_host *host, fcgi_proc *proc) {
+ fcgi_proc *p;
+
+ UNUSED(srv);
+
+ /* we have been the smallest of the current list
+ * and we want to insert the node sorted as soon
+ * possible
+ *
+ * 0 0 0 0 1 0 1
+ * ^ |
+ * | |
+ * +----------+
+ *
+ *
+ * the basic is idea is:
+ * - the last active fastcgi process should be still
+ * in ram and is not swapped out yet
+ * - processes that are not reused will be killed
+ * after some time by the trigger-handler
+ * - remember it as:
+ * everything > 0 is hot
+ * all unused procs are colder the more right they are
+ * ice-cold processes are propably unused since more
+ * than 'unused-timeout', are swaped out and won't be
+ * reused in the next seconds anyway.
+ *
+ */
+
+ /* nothing to sort, only one element */
+ if (host->first == proc && proc->next == NULL) return 0;
+
+ for (p = host->first; p != proc && p->load < proc->load; p = p->next);
+
+
+ /* no need to move something
+ *
+ * 1 2 2 2 3 3 3
+ * ^
+ * |
+ * +
+ *
+ */
+ if (p == proc) return 0;
+
+ /* we have to move left. If we are already the first element
+ * we are done */
+ if (host->first == proc) return 0;
+
+ /* release proc */
+ if (proc->prev) proc->prev->next = proc->next;
+ if (proc->next) proc->next->prev = proc->prev;
+
+ /* proc should be left of p */
+ proc->next = p;
+ proc->prev = p->prev;
+ if (p->prev) p->prev->next = proc;
+ p->prev = proc;
+
+ if (proc->prev == NULL) host->first = proc;
+#if 0
+ for(p = host->first; p; p = p->next) {
+ log_error_write(srv, __FILE__, __LINE__, "dd",
+ p->pid, p->load);
+ }
+#else
+ UNUSED(srv);
+#endif
+
+ return 0;
+}
+
+static int fcgi_restart_dead_procs(server *srv, plugin_data *p, fcgi_extension_host *host) {
+ fcgi_proc *proc;
+
+ for (proc = host->first; proc; proc = proc->next) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sbdbdddd",
+ "proc:",
+ host->host, proc->port,
+ proc->socket,
+ proc->state,
+ proc->is_local,
+ proc->load,
+ proc->pid);
+ }
+
+ if (!proc->is_local) {
+ /*
+ * external servers might get disabled
+ *
+ * enable the server again, perhaps it is back again
+ */
+
+ if ((proc->state == PROC_STATE_DISABLED) &&
+ (srv->cur_ts > proc->disabled_until)) {
+ proc->state = PROC_STATE_RUNNING;
+ host->active_procs++;
+
+ fastcgi_status_copy_procname(p->statuskey, host, proc);
+ buffer_append_string(p->statuskey, ".disabled");
+
+ status_counter_set(srv, CONST_BUF_LEN(p->statuskey), 0);
+
+ log_error_write(srv, __FILE__, __LINE__, "sbdb",
+ "fcgi-server re-enabled:",
+ host->host, host->port,
+ host->unixsocket);
+ }
+ } else {
+ /* the child should not terminate at all */
+ int status;
+
+ if (proc->state == PROC_STATE_DIED_WAIT_FOR_PID) {
+ switch(waitpid(proc->pid, &status, WNOHANG)) {
+ case 0:
+ /* child is still alive */
+ break;
+ case -1:
+ break;
+ default:
+ if (WIFEXITED(status)) {
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdsd",
+ "child exited, pid:", proc->pid,
+ "status:", WEXITSTATUS(status));
+#endif
+ } else if (WIFSIGNALED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child signaled:",
+ WTERMSIG(status));
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:",
+ status);
+ }
+
+ proc->state = PROC_STATE_DIED;
+ break;
+ }
+ }
+
+ /*
+ * local servers might died, but we restart them
+ *
+ */
+ if (proc->state == PROC_STATE_DIED &&
+ proc->load == 0) {
+ /* restart the child */
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd",
+ "--- fastcgi spawning",
+ "\n\tport:", host->port,
+ "\n\tsocket", host->unixsocket,
+ "\n\tcurrent:", 1, "/", host->min_procs);
+ }
+
+ if (fcgi_spawn_connection(srv, p, host, proc)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: spawning fcgi failed.");
+ return HANDLER_ERROR;
+ }
+
+ fcgi_proclist_sort_down(srv, host, proc);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) {
+ plugin_data *p = hctx->plugin_data;
+ fcgi_extension_host *host= hctx->host;
+ connection *con = hctx->remote_conn;
+
+ int ret;
+
+ /* sanity check */
+ if (!host ||
+ ((!host->host->used || !host->port) && !host->unixsocket->used)) {
+ log_error_write(srv, __FILE__, __LINE__, "sxddd",
+ "write-req: error",
+ host,
+ host->host->used,
+ host->port,
+ host->unixsocket->used);
+ return HANDLER_ERROR;
+ }
+
+ /* we can't handle this in the switch as we have to fall through in it */
+ if (hctx->state == FCGI_STATE_CONNECT_DELAYED) {
+ int socket_error;
+ socklen_t socket_error_len = sizeof(socket_error);
+
+ /* try to finish the connect() */
+ if (0 != getsockopt(hctx->fd, SOL_SOCKET, SO_ERROR, &socket_error, &socket_error_len)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "getsockopt failed:", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+ if (socket_error != 0) {
+ if (!hctx->proc->is_local || p->conf.debug) {
+ /* local procs get restarted */
+
+ log_error_write(srv, __FILE__, __LINE__, "sssd",
+ "establishing connection failed:", strerror(socket_error),
+ "port:", hctx->proc->port);
+ }
+
+ hctx->proc->disabled_until = srv->cur_ts + 10;
+
+ fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc);
+ buffer_append_string(p->statuskey, ".died");
+
+ status_counter_inc(srv, CONST_BUF_LEN(p->statuskey));
+
+ return HANDLER_ERROR;
+ }
+ /* go on with preparing the request */
+ hctx->state = FCGI_STATE_PREPARE_WRITE;
+ }
+
+
+ switch(hctx->state) {
+ case FCGI_STATE_CONNECT_DELAYED:
+ /* should never happen */
+ break;
+ case FCGI_STATE_INIT:
+ /* do we have a running process for this host (max-procs) ? */
+
+ for (hctx->proc = hctx->host->first;
+ hctx->proc && hctx->proc->state != PROC_STATE_RUNNING;
+ hctx->proc = hctx->proc->next);
+
+ /* all childs are dead */
+ if (hctx->proc == NULL) {
+ hctx->fde_ndx = -1;
+
+ return HANDLER_ERROR;
+ }
+
+ ret = host->unixsocket->used ? AF_UNIX : AF_INET;
+
+ if (-1 == (hctx->fd = socket(ret, SOCK_STREAM, 0))) {
+ if (errno == EMFILE ||
+ errno == EINTR) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "wait for fd at connection:", con->fd);
+
+ return HANDLER_WAIT_FOR_FD;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdd",
+ "socket failed:", strerror(errno), srv->cur_fds, srv->max_fds);
+ return HANDLER_ERROR;
+ }
+ hctx->fde_ndx = -1;
+
+ srv->cur_fds++;
+
+ fdevent_register(srv->ev, hctx->fd, fcgi_handle_fdevent, hctx);
+
+ if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "fcntl failed:", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ if (hctx->proc->is_local) {
+ hctx->pid = hctx->proc->pid;
+ }
+
+ switch (fcgi_establish_connection(srv, hctx)) {
+ case CONNECTION_DELAYED:
+ /* connection is in progress, wait for an event and call getsockopt() below */
+
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ fcgi_set_state(srv, hctx, FCGI_STATE_CONNECT_DELAYED);
+ return HANDLER_WAIT_FOR_EVENT;
+ case CONNECTION_OVERLOADED:
+ /* cool down the backend, it is overloaded
+ * -> EAGAIN */
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdsdb",
+ "backend is overloaded, we disable it for a 2 seconds and send the request to another backend instead:",
+ "reconnects:", hctx->reconnects,
+ "load:", host->load,
+ host->unixsocket);
+
+
+ hctx->proc->disabled_until = srv->cur_ts + 2;
+
+ fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc);
+ buffer_append_string(p->statuskey, ".overloaded");
+
+ status_counter_inc(srv, CONST_BUF_LEN(p->statuskey));
+
+ return HANDLER_ERROR;
+ case CONNECTION_DEAD:
+ /* we got a hard error from the backend like
+ * - ECONNREFUSED for tcp-ip sockets
+ * - ENOENT for unix-domain-sockets
+ *
+ * for check if the host is back in 10 seconds
+ * */
+
+ hctx->proc->disabled_until = srv->cur_ts + 10;
+
+ fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc);
+ buffer_append_string(p->statuskey, ".died");
+
+ status_counter_inc(srv, CONST_BUF_LEN(p->statuskey));
+
+ return HANDLER_ERROR;
+ case CONNECTION_OK:
+ /* everything is ok, go on */
+
+ fcgi_set_state(srv, hctx, FCGI_STATE_PREPARE_WRITE);
+
+ break;
+ case CONNECTION_UNSET:
+ break;
+ }
+
+ case FCGI_STATE_PREPARE_WRITE:
+ /* ok, we have the connection */
+
+ hctx->proc->load++;
+ hctx->proc->last_used = srv->cur_ts;
+ hctx->got_proc = 1;
+
+ status_counter_inc(srv, CONST_STR_LEN("fastcgi.requests"));
+ status_counter_inc(srv, CONST_STR_LEN("fastcgi.active-requests"));
+
+ fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc);
+ buffer_append_string(p->statuskey, ".connected");
+
+ status_counter_inc(srv, CONST_BUF_LEN(p->statuskey));
+
+ fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc);
+ buffer_append_string(p->statuskey, ".load");
+
+ status_counter_set(srv, CONST_BUF_LEN(p->statuskey), hctx->proc->load);
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sddbdd",
+ "got proc:",
+ hctx->fd,
+ hctx->proc->pid,
+ hctx->proc->socket,
+ hctx->proc->port,
+ hctx->proc->load);
+ }
+
+ /* move the proc-list entry down the list */
+ fcgi_proclist_sort_up(srv, hctx->host, hctx->proc);
+
+ if (hctx->request_id == 0) {
+ hctx->request_id = fcgi_requestid_new(srv, p);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "fcgi-request is already in use:", hctx->request_id);
+ }
+
+ /* fall through */
+ fcgi_create_env(srv, hctx, hctx->request_id);
+
+ fcgi_set_state(srv, hctx, FCGI_STATE_WRITE);
+
+ /* fall through */
+ case FCGI_STATE_WRITE:
+ ret = srv->network_backend_write(srv, con, hctx->fd, hctx->wb);
+
+ chunkqueue_remove_finished_chunks(hctx->wb);
+
+ if (ret < 0) {
+ switch(errno) {
+ case ENOTCONN:
+ /* the connection got dropped after accept()
+ *
+ * this is most of the time a PHP which dies
+ * after PHP_FCGI_MAX_REQUESTS
+ *
+ */
+ if (hctx->wb->bytes_out == 0 &&
+ hctx->reconnects < 5) {
+ usleep(10000); /* take away the load of the webserver
+ * to let the php a chance to restart
+ */
+
+ fcgi_reconnect(srv, hctx);
+
+ return HANDLER_WAIT_FOR_FD;
+ }
+
+ /* not reconnected ... why
+ *
+ * far@#lighttpd report this for FreeBSD
+ *
+ */
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdsd",
+ "[REPORT ME] connection was dropped after accept(). reconnect() denied:",
+ "write-offset:", hctx->wb->bytes_out,
+ "reconnect attempts:", hctx->reconnects);
+
+ return HANDLER_ERROR;
+ case EAGAIN:
+ case EINTR:
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ return HANDLER_WAIT_FOR_EVENT;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "write failed:", strerror(errno), errno);
+
+ return HANDLER_ERROR;
+ }
+ }
+
+ if (hctx->wb->bytes_out == hctx->wb->bytes_in) {
+ /* we don't need the out event anymore */
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+ fcgi_set_state(srv, hctx, FCGI_STATE_READ);
+ } else {
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+
+ break;
+ case FCGI_STATE_READ:
+ /* waiting for a response */
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state");
+ return HANDLER_ERROR;
+ }
+
+ return HANDLER_WAIT_FOR_EVENT;
+}
+
+
+/* might be called on fdevent after a connect() is delay too
+ * */
+SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) {
+ plugin_data *p = p_d;
+
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+ fcgi_proc *proc;
+ fcgi_extension_host *host;
+
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+ /* not my job */
+ if (con->mode != p->id) return HANDLER_GO_ON;
+
+ /* we don't have a host yet, choose one
+ * -> this happens in the first round
+ * and when the host died and we have to select a new one */
+ if (hctx->host == NULL) {
+ size_t k;
+ int ndx, used = -1;
+
+ /* get best server */
+ for (k = 0, ndx = -1; k < hctx->ext->used; k++) {
+ host = hctx->ext->hosts[k];
+
+ /* we should have at least one proc that can do something */
+ if (host->active_procs == 0) continue;
+
+ if (used == -1 || host->load < used) {
+ used = host->load;
+
+ ndx = k;
+ }
+ }
+
+ /* found a server */
+ if (ndx == -1) {
+ /* all hosts are down */
+
+ fcgi_connection_close(srv, hctx);
+
+ con->http_status = 500;
+ con->mode = DIRECT;
+
+ return HANDLER_FINISHED;
+ }
+
+ host = hctx->ext->hosts[ndx];
+
+ /*
+ * if check-local is disabled, use the uri.path handler
+ *
+ */
+
+ /* init handler-context */
+ hctx->host = host;
+
+ /* we put a connection on this host, move the other new connections to other hosts
+ *
+ * as soon as hctx->host is unassigned, decrease the load again */
+ hctx->host->load++;
+ hctx->proc = NULL;
+ } else {
+ host = hctx->host;
+ }
+
+ /* ok, create the request */
+ switch(fcgi_write_request(srv, hctx)) {
+ case HANDLER_ERROR:
+ proc = hctx->proc;
+ host = hctx->host;
+
+ if (hctx->state == FCGI_STATE_INIT ||
+ hctx->state == FCGI_STATE_CONNECT_DELAYED) {
+ /* connect() or getsockopt() failed,
+ * restart the request-handling
+ */
+ if (proc) {
+ if (proc->is_local) {
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sbdb",
+ "connect() to fastcgi failed, restarting the request-handling:",
+ host->host,
+ proc->port,
+ proc->socket);
+ }
+
+ /*
+ * several hctx might reference the same proc
+ *
+ * Only one of them should mark the proc as dead all the other
+ * ones should just take a new one.
+ *
+ * If a new proc was started with the old struct this might lead
+ * the mark a perfect proc as dead otherwise
+ *
+ */
+ if (proc->state == PROC_STATE_RUNNING &&
+ hctx->pid == proc->pid) {
+ proc->state = PROC_STATE_DIED_WAIT_FOR_PID;
+ }
+ } else {
+ proc->state = PROC_STATE_DISABLED;
+ }
+ host->active_procs--;
+ fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc);
+ buffer_append_string(p->statuskey, ".disabled");
+
+ status_counter_set(srv, CONST_BUF_LEN(p->statuskey), 1);
+ }
+
+ fcgi_restart_dead_procs(srv, p, host);
+
+ /* cleanup this request and let the request handler start this request again */
+ if (hctx->reconnects < 5) {
+ fcgi_reconnect(srv, hctx);
+ joblist_append(srv, con); /* in case we come from the event-handler */
+
+ return HANDLER_WAIT_FOR_FD;
+ } else {
+ fcgi_connection_close(srv, hctx);
+
+ buffer_reset(con->physical.path);
+ con->mode = DIRECT;
+ con->http_status = 500;
+ joblist_append(srv, con); /* in case we come from the event-handler */
+
+ return HANDLER_FINISHED;
+ }
+ } else {
+ fcgi_connection_close(srv, hctx);
+
+ buffer_reset(con->physical.path);
+ con->mode = DIRECT;
+ con->http_status = 503;
+ joblist_append(srv, con); /* really ? */
+
+ return HANDLER_FINISHED;
+ }
+ case HANDLER_WAIT_FOR_EVENT:
+ if (con->file_started == 1) {
+ return HANDLER_FINISHED;
+ } else {
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+ case HANDLER_WAIT_FOR_FD:
+ return HANDLER_WAIT_FOR_FD;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "s", "subrequest write-req default");
+ return HANDLER_ERROR;
+ }
+}
+
+static handler_t fcgi_handle_fdevent(void *s, void *ctx, int revents) {
+ server *srv = (server *)s;
+ handler_ctx *hctx = ctx;
+ connection *con = hctx->remote_conn;
+ plugin_data *p = hctx->plugin_data;
+
+ fcgi_proc *proc = hctx->proc;
+ fcgi_extension_host *host= hctx->host;
+
+ if ((revents & FDEVENT_IN) &&
+ hctx->state == FCGI_STATE_READ) {
+ switch (fcgi_demux_response(srv, hctx)) {
+ case 0:
+ break;
+ case 1:
+
+ if (host->mode == FCGI_AUTHORIZER &&
+ (con->http_status == 200 ||
+ con->http_status == 0)) {
+ /*
+ * If we are here in AUTHORIZER mode then a request for autorizer
+ * was proceeded already, and status 200 has been returned. We need
+ * now to handle autorized request.
+ */
+
+ buffer_copy_string_buffer(con->physical.doc_root, host->docroot);
+
+ buffer_copy_string_buffer(con->physical.path, host->docroot);
+ buffer_append_string_buffer(con->physical.path, con->uri.path);
+ fcgi_connection_close(srv, hctx);
+
+ con->mode = DIRECT;
+ con->file_started = 1; /* fcgi_extension won't touch the request afterwards */
+ } else {
+ /* we are done */
+ fcgi_connection_close(srv, hctx);
+ }
+
+ joblist_append(srv, con);
+ return HANDLER_FINISHED;
+ case -1:
+ if (proc->pid && proc->state != PROC_STATE_DIED) {
+ int status;
+
+ /* only fetch the zombie if it is not already done */
+
+ switch(waitpid(proc->pid, &status, WNOHANG)) {
+ case 0:
+ /* child is still alive */
+ break;
+ case -1:
+ break;
+ default:
+ /* the child should not terminate at all */
+ if (WIFEXITED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sdsd",
+ "child exited, pid:", proc->pid,
+ "status:", WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child signaled:",
+ WTERMSIG(status));
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:",
+ status);
+ }
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd",
+ "--- fastcgi spawning",
+ "\n\tport:", host->port,
+ "\n\tsocket", host->unixsocket,
+ "\n\tcurrent:", 1, "/", host->min_procs);
+ }
+
+ if (fcgi_spawn_connection(srv, p, host, proc)) {
+ /* child died */
+ proc->state = PROC_STATE_DIED;
+ } else {
+ fcgi_proclist_sort_down(srv, host, proc);
+ }
+
+ break;
+ }
+ }
+
+ if (con->file_started == 0) {
+ /* nothing has been send out yet, try to use another child */
+
+ if (hctx->wb->bytes_out == 0 &&
+ hctx->reconnects < 5) {
+ fcgi_reconnect(srv, hctx);
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdsd",
+ "response not received, request not sent, reconnecting.",
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+ return HANDLER_WAIT_FOR_FD;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "sosdsd",
+ "response not received, request sent:", hctx->wb->bytes_out,
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+ fcgi_connection_close(srv, hctx);
+
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ buffer_reset(con->physical.path);
+ con->http_status = 500;
+ con->mode = DIRECT;
+ } else {
+ /* response might have been already started, kill the connection */
+ fcgi_connection_close(srv, hctx);
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdsd",
+ "response already sent out, termination connection",
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ }
+
+ /* */
+
+
+ joblist_append(srv, con);
+ return HANDLER_FINISHED;
+ }
+ }
+
+ if (revents & FDEVENT_OUT) {
+ if (hctx->state == FCGI_STATE_CONNECT_DELAYED ||
+ hctx->state == FCGI_STATE_WRITE) {
+ /* we are allowed to send something out
+ *
+ * 1. in a unfinished connect() call
+ * 2. in a unfinished write() call (long POST request)
+ */
+ return mod_fastcgi_handle_subrequest(srv, con, p);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "got a FDEVENT_OUT and didn't know why:",
+ hctx->state);
+ }
+ }
+
+ /* perhaps this issue is already handled */
+ if (revents & FDEVENT_HUP) {
+ if (hctx->state == FCGI_STATE_CONNECT_DELAYED) {
+ /* getoptsock will catch this one (right ?)
+ *
+ * if we are in connect we might get a EINPROGRESS
+ * in the first call and a FDEVENT_HUP in the
+ * second round
+ *
+ * FIXME: as it is a bit ugly.
+ *
+ */
+ return mod_fastcgi_handle_subrequest(srv, con, p);
+ } else if (hctx->state == FCGI_STATE_READ &&
+ hctx->proc->port == 0) {
+ /* FIXME:
+ *
+ * ioctl says 8192 bytes to read from PHP and we receive directly a HUP for the socket
+ * even if the FCGI_FIN packet is not received yet
+ */
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbSBSDSd",
+ "error: unexpected close of fastcgi connection for",
+ con->uri.path,
+ "(no fastcgi process on host: ",
+ host->host,
+ ", port: ",
+ host->port,
+ " ?)",
+ hctx->state);
+
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ fcgi_connection_close(srv, hctx);
+ joblist_append(srv, con);
+ }
+ } else if (revents & FDEVENT_ERR) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "fcgi: got a FDEVENT_ERR. Don't know why.");
+ /* kill all connections to the fastcgi process */
+
+
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ fcgi_connection_close(srv, hctx);
+ joblist_append(srv, con);
+ }
+
+ return HANDLER_FINISHED;
+}
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int fcgi_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(exts);
+ PATCH(debug);
+ PATCH(ext_mapping);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("fastcgi.server"))) {
+ PATCH(exts);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("fastcgi.debug"))) {
+ PATCH(debug);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("fastcgi.map-extensions"))) {
+ PATCH(ext_mapping);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+
+static handler_t fcgi_check_extension(server *srv, connection *con, void *p_d, int uri_path_handler) {
+ plugin_data *p = p_d;
+ size_t s_len;
+ size_t k;
+ buffer *fn;
+ fcgi_extension *extension = NULL;
+ fcgi_extension_host *host = NULL;
+
+ /* Possibly, we processed already this request */
+ if (con->file_started == 1) return HANDLER_GO_ON;
+
+ fn = uri_path_handler ? con->uri.path : con->physical.path;
+
+ if (fn->used == 0) {
+ return HANDLER_ERROR;
+ }
+
+ s_len = fn->used - 1;
+
+ fcgi_patch_connection(srv, con, p);
+
+ /* fastcgi.map-extensions maps extensions to existing fastcgi.server entries
+ *
+ * fastcgi.map-extensions = ( ".php3" => ".php" )
+ *
+ * fastcgi.server = ( ".php" => ... )
+ *
+ * */
+
+ /* check if extension-mapping matches */
+ for (k = 0; k < p->conf.ext_mapping->used; k++) {
+ data_string *ds = (data_string *)p->conf.ext_mapping->data[k];
+ size_t ct_len; /* length of the config entry */
+
+ if (ds->key->used == 0) continue;
+
+ ct_len = ds->key->used - 1;
+
+ if (s_len < ct_len) continue;
+
+ /* found a mapping */
+ if (0 == strncmp(fn->ptr + s_len - ct_len, ds->key->ptr, ct_len)) {
+ /* check if we know the extension */
+
+ /* we can reuse k here */
+ for (k = 0; k < p->conf.exts->used; k++) {
+ extension = p->conf.exts->exts[k];
+
+ if (buffer_is_equal(ds->value, extension->key)) {
+ break;
+ }
+ }
+
+ if (k == p->conf.exts->used) {
+ /* found nothign */
+ extension = NULL;
+ }
+ break;
+ }
+ }
+
+ if (extension == NULL) {
+ /* check if extension matches */
+ for (k = 0; k < p->conf.exts->used; k++) {
+ size_t ct_len; /* length of the config entry */
+
+ extension = p->conf.exts->exts[k];
+
+ if (extension->key->used == 0) continue;
+
+ ct_len = extension->key->used - 1;
+
+ if (s_len < ct_len) continue;
+
+ /* check extension in the form "/fcgi_pattern" */
+ if (*(extension->key->ptr) == '/' && strncmp(fn->ptr, extension->key->ptr, ct_len) == 0) {
+ break;
+ } else if (0 == strncmp(fn->ptr + s_len - ct_len, extension->key->ptr, ct_len)) {
+ /* check extension in the form ".fcg" */
+ break;
+ }
+ }
+ /* extension doesn't match */
+ if (k == p->conf.exts->used) {
+ return HANDLER_GO_ON;
+ }
+ }
+
+ /* check if we have at least one server for this extension up and running */
+ for (k = 0; k < extension->used; k++) {
+ host = extension->hosts[k];
+
+ /* we should have at least one proc that can do somthing */
+ if (host->active_procs == 0) {
+ host = NULL;
+
+ continue;
+ }
+
+ /* we found one host that is alive */
+ break;
+ }
+
+ if (!host) {
+ /* sorry, we don't have a server alive for this ext */
+ buffer_reset(con->physical.path);
+ con->http_status = 500;
+
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "no fcgi-handler found for:",
+ fn);
+
+ return HANDLER_FINISHED;
+ }
+
+ /*
+ * if check-local is disabled, use the uri.path handler
+ *
+ */
+
+ /* init handler-context */
+ if (uri_path_handler) {
+ if (host->check_local == 0) {
+ handler_ctx *hctx;
+ char *pathinfo;
+
+ hctx = handler_ctx_init();
+
+ hctx->remote_conn = con;
+ hctx->plugin_data = p;
+ hctx->proc = NULL;
+ hctx->ext = extension;
+
+
+ hctx->conf.exts = p->conf.exts;
+ hctx->conf.debug = p->conf.debug;
+
+ con->plugin_ctx[p->id] = hctx;
+
+ con->mode = p->id;
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "handling it in mod_fastcgi");
+ }
+
+ /* the prefix is the SCRIPT_NAME,
+ * everthing from start to the next slash
+ * this is important for check-local = "disable"
+ *
+ * if prefix = /admin.fcgi
+ *
+ * /admin.fcgi/foo/bar
+ *
+ * SCRIPT_NAME = /admin.fcgi
+ * PATH_INFO = /foo/bar
+ *
+ * if prefix = /fcgi-bin/
+ *
+ * /fcgi-bin/foo/bar
+ *
+ * SCRIPT_NAME = /fcgi-bin/foo
+ * PATH_INFO = /bar
+ *
+ */
+
+ /* the rewrite is only done for /prefix/? matches */
+ if (extension->key->ptr[0] == '/' &&
+ con->uri.path->used > extension->key->used &&
+ NULL != (pathinfo = strchr(con->uri.path->ptr + extension->key->used - 1, '/'))) {
+ /* rewrite uri.path and pathinfo */
+
+ buffer_copy_string(con->request.pathinfo, pathinfo);
+
+ con->uri.path->used -= con->request.pathinfo->used - 1;
+ con->uri.path->ptr[con->uri.path->used - 1] = '\0';
+ }
+ }
+ } else {
+ handler_ctx *hctx;
+ hctx = handler_ctx_init();
+
+ hctx->remote_conn = con;
+ hctx->plugin_data = p;
+ hctx->proc = NULL;
+ hctx->ext = extension;
+
+ hctx->conf.exts = p->conf.exts;
+ hctx->conf.debug = p->conf.debug;
+
+ con->plugin_ctx[p->id] = hctx;
+
+ con->mode = p->id;
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "handling it in mod_fastcgi");
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+/* uri-path handler */
+static handler_t fcgi_check_extension_1(server *srv, connection *con, void *p_d) {
+ return fcgi_check_extension(srv, con, p_d, 1);
+}
+
+/* start request handler */
+static handler_t fcgi_check_extension_2(server *srv, connection *con, void *p_d) {
+ return fcgi_check_extension(srv, con, p_d, 0);
+}
+
+JOBLIST_FUNC(mod_fastcgi_handle_joblist) {
+ plugin_data *p = p_d;
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+
+ if (hctx == NULL) return HANDLER_GO_ON;
+
+ if (hctx->fd != -1) {
+ switch (hctx->state) {
+ case FCGI_STATE_READ:
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+
+ break;
+ case FCGI_STATE_CONNECT_DELAYED:
+ case FCGI_STATE_WRITE:
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ break;
+ case FCGI_STATE_INIT:
+ /* at reconnect */
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "unhandled fcgi.state", hctx->state);
+ break;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+static handler_t fcgi_connection_close_callback(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ fcgi_connection_close(srv, con->plugin_ctx[p->id]);
+
+ return HANDLER_GO_ON;
+}
+
+TRIGGER_FUNC(mod_fastcgi_handle_trigger) {
+ plugin_data *p = p_d;
+ size_t i, j, n;
+
+
+ /* perhaps we should kill a connect attempt after 10-15 seconds
+ *
+ * currently we wait for the TCP timeout which is on Linux 180 seconds
+ *
+ *
+ *
+ */
+
+ /* check all childs if they are still up */
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *conf;
+ fcgi_exts *exts;
+
+ conf = p->config_storage[i];
+
+ exts = conf->exts;
+
+ for (j = 0; j < exts->used; j++) {
+ fcgi_extension *ex;
+
+ ex = exts->exts[j];
+
+ for (n = 0; n < ex->used; n++) {
+
+ fcgi_proc *proc;
+ unsigned long sum_load = 0;
+ fcgi_extension_host *host;
+
+ host = ex->hosts[n];
+
+ fcgi_restart_dead_procs(srv, p, host);
+
+ for (proc = host->first; proc; proc = proc->next) {
+ sum_load += proc->load;
+ }
+
+ if (host->num_procs &&
+ host->num_procs < host->max_procs &&
+ (sum_load / host->num_procs) > host->max_load_per_proc) {
+ /* overload, spawn new child */
+ fcgi_proc *fp = NULL;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "overload detected, spawning a new child");
+ }
+
+ for (fp = host->unused_procs; fp && fp->pid != 0; fp = fp->next);
+
+ if (fp) {
+ if (fp == host->unused_procs) host->unused_procs = fp->next;
+
+ if (fp->next) fp->next->prev = NULL;
+
+ host->max_id++;
+ } else {
+ fp = fastcgi_process_init();
+ fp->id = host->max_id++;
+ }
+
+ host->num_procs++;
+
+ if (buffer_is_empty(host->unixsocket)) {
+ fp->port = host->port + fp->id;
+ } else {
+ buffer_copy_string_buffer(fp->socket, host->unixsocket);
+ buffer_append_string(fp->socket, "-");
+ buffer_append_long(fp->socket, fp->id);
+ }
+
+ if (fcgi_spawn_connection(srv, p, host, fp)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: spawning fcgi failed.");
+ return HANDLER_ERROR;
+ }
+
+ fp->prev = NULL;
+ fp->next = host->first;
+ if (host->first) {
+ host->first->prev = fp;
+ }
+ host->first = fp;
+ }
+
+ for (proc = host->first; proc; proc = proc->next) {
+ if (proc->load != 0) break;
+ if (host->num_procs <= host->min_procs) break;
+ if (proc->pid == 0) continue;
+
+ if (srv->cur_ts - proc->last_used > host->idle_timeout) {
+ /* a proc is idling for a long time now,
+ * terminated it */
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbsd",
+ "idle-timeout reached, terminating child:",
+ "socket:", proc->socket,
+ "pid", proc->pid);
+ }
+
+
+ if (proc->next) proc->next->prev = proc->prev;
+ if (proc->prev) proc->prev->next = proc->next;
+
+ if (proc->prev == NULL) host->first = proc->next;
+
+ proc->prev = NULL;
+ proc->next = host->unused_procs;
+
+ if (host->unused_procs) host->unused_procs->prev = proc;
+ host->unused_procs = proc;
+
+ kill(proc->pid, SIGTERM);
+
+ proc->state = PROC_STATE_KILLED;
+
+ log_error_write(srv, __FILE__, __LINE__, "ssbsd",
+ "killed:",
+ "socket:", proc->socket,
+ "pid", proc->pid);
+
+ host->num_procs--;
+
+ /* proc is now in unused, let the next second handle the next process */
+ break;
+ }
+ }
+
+ for (proc = host->unused_procs; proc; proc = proc->next) {
+ int status;
+
+ if (proc->pid == 0) continue;
+
+ switch (waitpid(proc->pid, &status, WNOHANG)) {
+ case 0:
+ /* child still running after timeout, good */
+ break;
+ case -1:
+ if (errno != EINTR) {
+ /* no PID found ? should never happen */
+ log_error_write(srv, __FILE__, __LINE__, "sddss",
+ "pid ", proc->pid, proc->state,
+ "not found:", strerror(errno));
+
+#if 0
+ if (errno == ECHILD) {
+ /* someone else has cleaned up for us */
+ proc->pid = 0;
+ proc->state = PROC_STATE_UNSET;
+ }
+#endif
+ }
+ break;
+ default:
+ /* the child should not terminate at all */
+ if (WIFEXITED(status)) {
+ if (proc->state != PROC_STATE_KILLED) {
+ log_error_write(srv, __FILE__, __LINE__, "sdb",
+ "child exited:",
+ WEXITSTATUS(status), proc->socket);
+ }
+ } else if (WIFSIGNALED(status)) {
+ if (WTERMSIG(status) != SIGTERM) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child signaled:",
+ WTERMSIG(status));
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:",
+ status);
+ }
+ proc->pid = 0;
+ proc->state = PROC_STATE_UNSET;
+ host->max_id--;
+ }
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+int mod_fastcgi_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("fastcgi");
+
+ p->init = mod_fastcgi_init;
+ p->cleanup = mod_fastcgi_free;
+ p->set_defaults = mod_fastcgi_set_defaults;
+ p->connection_reset = fcgi_connection_reset;
+ p->handle_connection_close = fcgi_connection_close_callback;
+ p->handle_uri_clean = fcgi_check_extension_1;
+ p->handle_subrequest_start = fcgi_check_extension_2;
+ p->handle_subrequest = mod_fastcgi_handle_subrequest;
+ p->handle_joblist = mod_fastcgi_handle_joblist;
+ p->handle_trigger = mod_fastcgi_handle_trigger;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_indexfile.c b/src/mod_indexfile.c
new file mode 100644
index 0000000..4a784c6
--- /dev/null
+++ b/src/mod_indexfile.c
@@ -0,0 +1,219 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "stat_cache.h"
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ array *indexfiles;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *tmp_buf;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_indexfile_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->tmp_buf = buffer_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_indexfile_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (!s) continue;
+
+ array_free(s->indexfiles);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->tmp_buf);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_indexfile_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "index-file.names", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "server.indexfiles", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->indexfiles = array_init();
+
+ cv[0].destination = s->indexfiles;
+ cv[1].destination = s->indexfiles; /* old name for [0] */
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_indexfile_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(indexfiles);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.indexfiles"))) {
+ PATCH(indexfiles);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("index-file.names"))) {
+ PATCH(indexfiles);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_indexfile_subrequest) {
+ plugin_data *p = p_d;
+ size_t k;
+ stat_cache_entry *sce = NULL;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+ if (con->uri.path->ptr[con->uri.path->used - 2] != '/') return HANDLER_GO_ON;
+
+ mod_indexfile_patch_connection(srv, con, p);
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- handling the request as Indexfile");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI :", con->uri.path);
+ }
+
+ /* indexfile */
+ for (k = 0; k < p->conf.indexfiles->used; k++) {
+ data_string *ds = (data_string *)p->conf.indexfiles->data[k];
+
+ if (ds->value && ds->value->ptr[0] == '/') {
+ /* if the index-file starts with a prefix as use this file as
+ * index-generator */
+ buffer_copy_string_buffer(p->tmp_buf, con->physical.doc_root);
+ } else {
+ buffer_copy_string_buffer(p->tmp_buf, con->physical.path);
+ }
+ buffer_append_string_buffer(p->tmp_buf, ds->value);
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, p->tmp_buf, &sce)) {
+ if (errno == EACCES) {
+ con->http_status = 403;
+ buffer_reset(con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+
+ if (errno != ENOENT &&
+ errno != ENOTDIR) {
+ /* we have no idea what happend. let's tell the user so. */
+
+ con->http_status = 500;
+
+ log_error_write(srv, __FILE__, __LINE__, "ssbsb",
+ "file not found ... or so: ", strerror(errno),
+ con->uri.path,
+ "->", con->physical.path);
+
+ buffer_reset(con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+ continue;
+ }
+
+ /* rewrite uri.path to the real path (/ -> /index.php) */
+ buffer_append_string_buffer(con->uri.path, ds->value);
+ buffer_copy_string_buffer(con->physical.path, p->tmp_buf);
+
+ /* fce is already set up a few lines above */
+
+ return HANDLER_GO_ON;
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_indexfile_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("indexfile");
+
+ p->init = mod_indexfile_init;
+ p->handle_subrequest_start = mod_indexfile_subrequest;
+ p->set_defaults = mod_indexfile_set_defaults;
+ p->cleanup = mod_indexfile_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_mysql_vhost.c b/src/mod_mysql_vhost.c
new file mode 100644
index 0000000..bf13e09
--- /dev/null
+++ b/src/mod_mysql_vhost.c
@@ -0,0 +1,431 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <strings.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_MYSQL
+#include <mysql.h>
+#endif
+
+#include "plugin.h"
+#include "log.h"
+
+#include "stat_cache.h"
+#ifdef DEBUG_MOD_MYSQL_VHOST
+#define DEBUG
+#endif
+
+/*
+ * Plugin for lighttpd to use MySQL
+ * for domain to directory lookups,
+ * i.e virtual hosts (vhosts).
+ *
+ * Optionally sets fcgi_offset and fcgi_arg
+ * in preparation for fcgi.c to handle
+ * per-user fcgi chroot jails.
+ *
+ * /ada@riksnet.se 2004-12-06
+ */
+
+#ifdef HAVE_MYSQL
+typedef struct {
+ MYSQL *mysql;
+
+ buffer *mydb;
+ buffer *myuser;
+ buffer *mypass;
+ buffer *mysock;
+
+ buffer *hostname;
+ unsigned short port;
+
+ buffer *mysql_pre;
+ buffer *mysql_post;
+} plugin_config;
+
+/* global plugin data */
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *tmp_buf;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* per connection plugin data */
+typedef struct {
+ buffer *server_name;
+ buffer *document_root;
+ buffer *fcgi_arg;
+ unsigned fcgi_offset;
+} plugin_connection_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_mysql_vhost_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->tmp_buf = buffer_init();
+
+ return p;
+}
+
+/* cleanup the plugin data */
+SERVER_FUNC(mod_mysql_vhost_cleanup) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+#ifdef DEBUG
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "mod_mysql_vhost_cleanup", p ? "yes" : "NO");
+#endif
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (!s) continue;
+
+ mysql_close(s->mysql);
+
+ buffer_free(s->mydb);
+ buffer_free(s->myuser);
+ buffer_free(s->mypass);
+ buffer_free(s->mysock);
+ buffer_free(s->mysql_pre);
+ buffer_free(s->mysql_post);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+ buffer_free(p->tmp_buf);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle the plugin per connection data */
+static void* mod_mysql_vhost_connection_data(server *srv, connection *con, void *p_d)
+{
+ plugin_data *p = p_d;
+ plugin_connection_data *c = con->plugin_ctx[p->id];
+
+ UNUSED(srv);
+
+#ifdef DEBUG
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "mod_mysql_connection_data", c ? "old" : "NEW");
+#endif
+
+ if (c) return c;
+ c = calloc(1, sizeof(*c));
+
+ c->server_name = buffer_init();
+ c->document_root = buffer_init();
+ c->fcgi_arg = buffer_init();
+ c->fcgi_offset = 0;
+
+ return con->plugin_ctx[p->id] = c;
+}
+
+/* destroy the plugin per connection data */
+CONNECTION_FUNC(mod_mysql_vhost_handle_connection_close) {
+ plugin_data *p = p_d;
+ plugin_connection_data *c = con->plugin_ctx[p->id];
+
+ UNUSED(srv);
+
+#ifdef DEBUG
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "mod_mysql_vhost_handle_connection_close", c ? "yes" : "NO");
+#endif
+
+ if (!c) return HANDLER_GO_ON;
+
+ buffer_free(c->server_name);
+ buffer_free(c->document_root);
+ buffer_free(c->fcgi_arg);
+ c->fcgi_offset = 0;
+
+ free(c);
+
+ con->plugin_ctx[p->id] = NULL;
+ return HANDLER_GO_ON;
+}
+
+/* set configuration values */
+SERVER_FUNC(mod_mysql_vhost_set_defaults) {
+ plugin_data *p = p_d;
+
+ char *qmark;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "mysql-vhost.db", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
+ { "mysql-vhost.user", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
+ { "mysql-vhost.pass", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
+ { "mysql-vhost.sock", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
+ { "mysql-vhost.sql", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
+ { "mysql-vhost.hostname", NULL, T_CONFIG_STRING,T_CONFIG_SCOPE_SERVER },
+ { "mysql-vhost.port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ buffer *sel;
+
+
+ s = calloc(1, sizeof(plugin_config));
+ s->mydb = buffer_init();
+ s->myuser = buffer_init();
+ s->mypass = buffer_init();
+ s->mysock = buffer_init();
+ s->hostname = buffer_init();
+ s->port = 0; /* default port for mysql */
+ sel = buffer_init();
+ s->mysql = NULL;
+
+ s->mysql_pre = buffer_init();
+ s->mysql_post = buffer_init();
+
+ cv[0].destination = s->mydb;
+ cv[1].destination = s->myuser;
+ cv[2].destination = s->mypass;
+ cv[3].destination = s->mysock;
+ cv[4].destination = sel;
+ cv[5].destination = s->hostname;
+ cv[6].destination = &(s->port);
+
+ p->config_storage[i] = s;
+
+ if (config_insert_values_global(srv,
+ ((data_config *)srv->config_context->data[i])->value,
+ cv)) return HANDLER_ERROR;
+
+ s->mysql_pre = buffer_init();
+ s->mysql_post = buffer_init();
+
+ if (sel->used && (qmark = index(sel->ptr, '?'))) {
+ *qmark = '\0';
+ buffer_copy_string(s->mysql_pre, sel->ptr);
+ buffer_copy_string(s->mysql_post, qmark+1);
+ } else {
+ buffer_copy_string_buffer(s->mysql_pre, sel);
+ }
+
+ /* required:
+ * - username
+ * - database
+ *
+ * optional:
+ * - password, default: empty
+ * - socket, default: mysql default
+ * - hostname, if set overrides socket
+ * - port, default: 3306
+ */
+
+ /* all have to be set */
+ if (!(buffer_is_empty(s->myuser) ||
+ buffer_is_empty(s->mydb))) {
+
+ int fd;
+
+ if (NULL == (s->mysql = mysql_init(NULL))) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "mysql_init() failed, exiting...");
+
+ return HANDLER_ERROR;
+ }
+#define FOO(x) (s->x->used ? s->x->ptr : NULL)
+
+ if (!mysql_real_connect(s->mysql, FOO(hostname), FOO(myuser), FOO(mypass),
+ FOO(mydb), s->port, FOO(mysock), 0)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(s->mysql));
+
+ return HANDLER_ERROR;
+ }
+#undef FOO
+ /* set close_on_exec for mysql the hard way */
+ /* Note: this only works as it is done during startup, */
+ /* otherwise we cannot be sure that mysql is fd i-1 */
+ if (-1 == (fd = open("/dev/null", 0))) {
+ close(fd);
+ fcntl(fd-1, F_SETFD, FD_CLOEXEC);
+ }
+ }
+ }
+
+
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_mysql_vhost_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(mysql_pre);
+ PATCH(mysql_post);
+#ifdef HAVE_MYSQL
+ PATCH(mysql);
+#endif
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("mysql-vhost.sql"))) {
+ PATCH(mysql_pre);
+ PATCH(mysql_post);
+ }
+ }
+
+ if (s->mysql) {
+ PATCH(mysql);
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+
+/* handle document root request */
+CONNECTION_FUNC(mod_mysql_vhost_handle_docroot) {
+ plugin_data *p = p_d;
+ plugin_connection_data *c;
+ stat_cache_entry *sce;
+
+ unsigned cols;
+ MYSQL_ROW row;
+ MYSQL_RES *result = NULL;
+
+ /* no host specified? */
+ if (!con->uri.authority->used) return HANDLER_GO_ON;
+
+ mod_mysql_vhost_patch_connection(srv, con, p);
+
+ if (!p->conf.mysql) return HANDLER_GO_ON;
+
+ /* sets up connection data if not done yet */
+ c = mod_mysql_vhost_connection_data(srv, con, p_d);
+
+ /* check if cached this connection */
+ if (c->server_name->used && /* con->uri.authority->used && */
+ buffer_is_equal(c->server_name, con->uri.authority)) goto GO_ON;
+
+ /* build and run SQL query */
+ buffer_copy_string_buffer(p->tmp_buf, p->conf.mysql_pre);
+ if (p->conf.mysql_post->used) {
+ buffer_append_string_buffer(p->tmp_buf, con->uri.authority);
+ buffer_append_string_buffer(p->tmp_buf, p->conf.mysql_post);
+ }
+ if (mysql_query(p->conf.mysql, p->tmp_buf->ptr)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(p->conf.mysql));
+ goto ERR500;
+ }
+ result = mysql_store_result(p->conf.mysql);
+ cols = mysql_num_fields(result);
+ row = mysql_fetch_row(result);
+ if (!row || cols < 1) {
+ /* no such virtual host */
+ mysql_free_result(result);
+ return HANDLER_GO_ON;
+ }
+
+ /* sanity check that really is a directory */
+ buffer_copy_string(p->tmp_buf, row[0]);
+ BUFFER_APPEND_SLASH(p->tmp_buf);
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, p->tmp_buf, &sce)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf);
+ goto ERR500;
+ }
+ if (!S_ISDIR(sce->st.st_mode)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Not a directory", p->tmp_buf);
+ goto ERR500;
+ }
+
+ /* cache the data */
+ buffer_copy_string_buffer(c->server_name, con->uri.authority);
+ buffer_copy_string_buffer(c->document_root, p->tmp_buf);
+
+ /* fcgi_offset and fcgi_arg are optional */
+ if (cols > 1 && row[1]) {
+ c->fcgi_offset = atoi(row[1]);
+
+ if (cols > 2 && row[2]) {
+ buffer_copy_string(c->fcgi_arg, row[2]);
+ } else {
+ c->fcgi_arg->used = 0;
+ }
+ } else {
+ c->fcgi_offset = c->fcgi_arg->used = 0;
+ }
+ mysql_free_result(result);
+
+ /* fix virtual server and docroot */
+GO_ON: buffer_copy_string_buffer(con->server_name, c->server_name);
+ buffer_copy_string_buffer(con->physical.doc_root, c->document_root);
+
+#ifdef DEBUG
+ log_error_write(srv, __FILE__, __LINE__, "sbbdb",
+ result ? "NOT CACHED" : "cached",
+ con->server_name, con->physical.doc_root,
+ c->fcgi_offset, c->fcgi_arg);
+#endif
+ return HANDLER_GO_ON;
+
+ERR500: if (result) mysql_free_result(result);
+ con->http_status = 500; /* Internal Error */
+ return HANDLER_FINISHED;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+int mod_mysql_vhost_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("mysql_vhost");
+
+ p->init = mod_mysql_vhost_init;
+ p->cleanup = mod_mysql_vhost_cleanup;
+ p->handle_request_done = mod_mysql_vhost_handle_connection_close;
+
+ p->set_defaults = mod_mysql_vhost_set_defaults;
+ p->handle_docroot = mod_mysql_vhost_handle_docroot;
+
+ return 0;
+}
+#else
+/* we don't have mysql support, this plugin does nothing */
+int mod_mysql_vhost_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("mysql_vhost");
+
+ return 0;
+}
+#endif
diff --git a/src/mod_proxy.c b/src/mod_proxy.c
new file mode 100644
index 0000000..572de62
--- /dev/null
+++ b/src/mod_proxy.c
@@ -0,0 +1,1324 @@
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "buffer.h"
+#include "server.h"
+#include "keyvalue.h"
+#include "log.h"
+
+#include "http_chunk.h"
+#include "fdevent.h"
+#include "connections.h"
+#include "response.h"
+#include "joblist.h"
+
+#include "plugin.h"
+
+#include "inet_ntop_cache.h"
+#include "crc32.h"
+
+#include <stdio.h>
+
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#include "sys-socket.h"
+
+#define data_proxy data_fastcgi
+#define data_proxy_init data_fastcgi_init
+
+#define PROXY_RETRY_TIMEOUT 60
+
+/**
+ *
+ * the proxy module is based on the fastcgi module
+ *
+ * 28.06.2004 Jan Kneschke The first release
+ * 01.07.2004 Evgeny Rodichev Several bugfixes and cleanups
+ * - co-ordinate up- and downstream flows correctly (proxy_demux_response
+ * and proxy_handle_fdevent)
+ * - correctly transfer upstream http_response_status;
+ * - some unused structures removed.
+ *
+ * TODO: - delay upstream read if write_queue is too large
+ * (to prevent memory eating, like in apache). Shoud be
+ * configurable).
+ * - persistent connection with upstream servers
+ * - HTTP/1.1
+ */
+typedef enum {
+ PROXY_BALANCE_UNSET,
+ PROXY_BALANCE_FAIR,
+ PROXY_BALANCE_HASH,
+ PROXY_BALANCE_RR
+} proxy_balance_t;
+
+typedef struct {
+ array *extensions;
+ int debug;
+
+ proxy_balance_t balance;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *parse_response;
+ buffer *balance_buf;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+typedef enum {
+ PROXY_STATE_INIT,
+ PROXY_STATE_CONNECT,
+ PROXY_STATE_PREPARE_WRITE,
+ PROXY_STATE_WRITE,
+ PROXY_STATE_READ,
+ PROXY_STATE_ERROR
+} proxy_connection_state_t;
+
+enum { PROXY_STDOUT, PROXY_END_REQUEST };
+
+typedef struct {
+ proxy_connection_state_t state;
+ time_t state_timestamp;
+
+ data_proxy *host;
+
+ buffer *response;
+ buffer *response_header;
+
+ chunkqueue *wb;
+
+ int fd; /* fd to the proxy process */
+ int fde_ndx; /* index into the fd-event buffer */
+
+ size_t path_info_offset; /* start of path_info in uri.path */
+
+ connection *remote_conn; /* dump pointer */
+ plugin_data *plugin_data; /* dump pointer */
+} handler_ctx;
+
+
+/* ok, we need a prototype */
+static handler_t proxy_handle_fdevent(void *s, void *ctx, int revents);
+
+static handler_ctx * handler_ctx_init() {
+ handler_ctx * hctx;
+
+
+ hctx = calloc(1, sizeof(*hctx));
+
+ hctx->state = PROXY_STATE_INIT;
+ hctx->host = NULL;
+
+ hctx->response = buffer_init();
+ hctx->response_header = buffer_init();
+
+ hctx->wb = chunkqueue_init();
+
+ hctx->fd = -1;
+ hctx->fde_ndx = -1;
+
+ return hctx;
+}
+
+static void handler_ctx_free(handler_ctx *hctx) {
+ buffer_free(hctx->response);
+ buffer_free(hctx->response_header);
+ chunkqueue_free(hctx->wb);
+
+ free(hctx);
+}
+
+INIT_FUNC(mod_proxy_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->parse_response = buffer_init();
+ p->balance_buf = buffer_init();
+
+ return p;
+}
+
+
+FREE_FUNC(mod_proxy_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ buffer_free(p->parse_response);
+ buffer_free(p->balance_buf);
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (s) {
+
+ array_free(s->extensions);
+
+ free(s);
+ }
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_proxy_set_defaults) {
+ plugin_data *p = p_d;
+ data_unset *du;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "proxy.server", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "proxy.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "proxy.balance", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ array *ca;
+
+ s = malloc(sizeof(plugin_config));
+ s->extensions = array_init();
+ s->debug = 0;
+
+ cv[0].destination = s->extensions;
+ cv[1].destination = &(s->debug);
+ cv[2].destination = p->balance_buf;
+
+ buffer_reset(p->balance_buf);
+
+ p->config_storage[i] = s;
+ ca = ((data_config *)srv->config_context->data[i])->value;
+
+ if (0 != config_insert_values_global(srv, ca, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (buffer_is_empty(p->balance_buf)) {
+ s->balance = PROXY_BALANCE_FAIR;
+ } else if (buffer_is_equal_string(p->balance_buf, CONST_STR_LEN("fair"))) {
+ s->balance = PROXY_BALANCE_FAIR;
+ } else if (buffer_is_equal_string(p->balance_buf, CONST_STR_LEN("round-robin"))) {
+ s->balance = PROXY_BALANCE_RR;
+ } else if (buffer_is_equal_string(p->balance_buf, CONST_STR_LEN("hash"))) {
+ s->balance = PROXY_BALANCE_HASH;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "proxy.balance has to be one of: fair, round-robin, hash, but not:", p->balance_buf);
+ return HANDLER_ERROR;
+ }
+
+ if (NULL != (du = array_get_element(ca, "proxy.server"))) {
+ size_t j;
+ data_array *da = (data_array *)du;
+
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", "proxy.server", "array of strings");
+
+ return HANDLER_ERROR;
+ }
+
+ /*
+ * proxy.server = ( "<ext>" => ...,
+ * "<ext>" => ... )
+ */
+
+ for (j = 0; j < da->value->used; j++) {
+ data_array *da_ext = (data_array *)da->value->data[j];
+ size_t n;
+
+ if (da_ext->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ", "proxy.server",
+ "[", da->value->data[j]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ /*
+ * proxy.server = ( "<ext>" =>
+ * ( "<host>" => ( ... ),
+ * "<host>" => ( ... )
+ * ),
+ * "<ext>" => ... )
+ */
+
+ for (n = 0; n < da_ext->value->used; n++) {
+ data_array *da_host = (data_array *)da_ext->value->data[n];
+
+ data_proxy *df;
+ data_array *dfa;
+
+ config_values_t pcv[] = {
+ { "host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (da_host->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "ssSBS",
+ "unexpected type for key:",
+ "proxy.server",
+ "[", da_ext->value->data[n]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ df = data_proxy_init();
+
+ df->port = 80;
+
+ buffer_copy_string_buffer(df->key, da_host->key);
+
+ pcv[0].destination = df->host;
+ pcv[1].destination = &(df->port);
+
+ if (0 != config_insert_values_internal(srv, da_host->value, pcv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (buffer_is_empty(df->host)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbbbs",
+ "missing key (string):",
+ da->key,
+ da_ext->key,
+ da_host->key,
+ "host");
+
+ return HANDLER_ERROR;
+ }
+
+ /* if extension already exists, take it */
+
+ if (NULL == (dfa = (data_array *)array_get_element(s->extensions, da_ext->key->ptr))) {
+ dfa = data_array_init();
+
+ buffer_copy_string_buffer(dfa->key, da_ext->key);
+
+ array_insert_unique(dfa->value, (data_unset *)df);
+ array_insert_unique(s->extensions, (data_unset *)dfa);
+ } else {
+ array_insert_unique(dfa->value, (data_unset *)df);
+ }
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+void proxy_connection_close(server *srv, handler_ctx *hctx) {
+ plugin_data *p;
+ connection *con;
+
+ if (NULL == hctx) return;
+
+ p = hctx->plugin_data;
+ con = hctx->remote_conn;
+
+ if (hctx->fd != -1) {
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+
+ close(hctx->fd);
+ srv->cur_fds--;
+ }
+
+ handler_ctx_free(hctx);
+ con->plugin_ctx[p->id] = NULL;
+}
+
+static int proxy_establish_connection(server *srv, handler_ctx *hctx) {
+ struct sockaddr *proxy_addr;
+ struct sockaddr_in proxy_addr_in;
+ socklen_t servlen;
+
+ plugin_data *p = hctx->plugin_data;
+ data_proxy *host= hctx->host;
+ int proxy_fd = hctx->fd;
+
+ memset(&proxy_addr, 0, sizeof(proxy_addr));
+
+ proxy_addr_in.sin_family = AF_INET;
+ proxy_addr_in.sin_addr.s_addr = inet_addr(host->host->ptr);
+ proxy_addr_in.sin_port = htons(host->port);
+ servlen = sizeof(proxy_addr_in);
+
+ proxy_addr = (struct sockaddr *) &proxy_addr_in;
+
+ if (-1 == connect(proxy_fd, proxy_addr, servlen)) {
+ if (errno == EINPROGRESS || errno == EALREADY) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connect delayed:", proxy_fd);
+ }
+
+ return 1;
+ } else {
+
+ log_error_write(srv, __FILE__, __LINE__, "sdsd",
+ "connect failed:", proxy_fd, strerror(errno), errno);
+
+ return -1;
+ }
+ }
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connect succeeded: ", proxy_fd);
+ }
+
+ return 0;
+}
+
+void proxy_set_header(connection *con, const char *key, const char *value) {
+ data_string *ds_dst;
+
+ if (NULL == (ds_dst = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
+ ds_dst = data_string_init();
+ }
+
+ buffer_copy_string(ds_dst->key, key);
+ buffer_copy_string(ds_dst->value, value);
+ array_insert_unique(con->request.headers, (data_unset *)ds_dst);
+}
+
+void proxy_append_header(connection *con, const char *key, const char *value) {
+ data_string *ds_dst;
+
+ if (NULL == (ds_dst = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
+ ds_dst = data_string_init();
+ }
+
+ buffer_copy_string(ds_dst->key, key);
+ buffer_append_string(ds_dst->value, value);
+ array_insert_unique(con->request.headers, (data_unset *)ds_dst);
+}
+
+
+static int proxy_create_env(server *srv, handler_ctx *hctx) {
+ size_t i;
+
+ connection *con = hctx->remote_conn;
+ buffer *b;
+
+ /* build header */
+
+ b = chunkqueue_get_append_buffer(hctx->wb);
+
+ /* request line */
+ buffer_copy_string(b, get_http_method_name(con->request.http_method));
+ BUFFER_APPEND_STRING_CONST(b, " ");
+
+ buffer_append_string_buffer(b, con->request.uri);
+ BUFFER_APPEND_STRING_CONST(b, " HTTP/1.0\r\n");
+
+ proxy_append_header(con, "X-Forwarded-For", (char *)inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
+ /* http_host is NOT is just a pointer to a buffer
+ * which is NULL if it is not set */
+ if (con->request.http_host &&
+ !buffer_is_empty(con->request.http_host)) {
+ proxy_set_header(con, "X-Host", con->request.http_host->ptr);
+ }
+ proxy_set_header(con, "X-Forwarded-Proto", con->conf.is_ssl ? "https" : "http");
+
+ /* request header */
+ for (i = 0; i < con->request.headers->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->request.headers->data[i];
+
+ if (ds->value->used && ds->key->used) {
+ if (buffer_is_equal_string(ds->key, CONST_STR_LEN("Connection"))) continue;
+
+ buffer_append_string_buffer(b, ds->key);
+ BUFFER_APPEND_STRING_CONST(b, ": ");
+ buffer_append_string_buffer(b, ds->value);
+ BUFFER_APPEND_STRING_CONST(b, "\r\n");
+ }
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "\r\n");
+
+ hctx->wb->bytes_in += b->used - 1;
+ /* body */
+
+ if (con->request.content_length) {
+ chunkqueue *req_cq = con->request_content_queue;
+ chunk *req_c;
+ off_t offset;
+
+ /* something to send ? */
+ for (offset = 0, req_c = req_cq->first; offset != req_cq->bytes_in; req_c = req_c->next) {
+ off_t weWant = req_cq->bytes_in - offset;
+ off_t weHave = 0;
+
+ /* we announce toWrite octects
+ * now take all the request_content chunk that we need to fill this request
+ * */
+
+ switch (req_c->type) {
+ case FILE_CHUNK:
+ weHave = req_c->file.length - req_c->offset;
+
+ if (weHave > weWant) weHave = weWant;
+
+ chunkqueue_append_file(hctx->wb, req_c->file.name, req_c->offset, weHave);
+
+ req_c->offset += weHave;
+ req_cq->bytes_out += weHave;
+
+ hctx->wb->bytes_in += weHave;
+
+ break;
+ case MEM_CHUNK:
+ /* append to the buffer */
+ weHave = req_c->mem->used - 1 - req_c->offset;
+
+ if (weHave > weWant) weHave = weWant;
+
+ b = chunkqueue_get_append_buffer(hctx->wb);
+ buffer_append_memory(b, req_c->mem->ptr + req_c->offset, weHave);
+ b->used++; /* add virtual \0 */
+
+ req_c->offset += weHave;
+ req_cq->bytes_out += weHave;
+
+ hctx->wb->bytes_in += weHave;
+
+ break;
+ default:
+ break;
+ }
+
+ offset += weHave;
+ }
+
+ }
+
+ return 0;
+}
+
+static int proxy_set_state(server *srv, handler_ctx *hctx, proxy_connection_state_t state) {
+ hctx->state = state;
+ hctx->state_timestamp = srv->cur_ts;
+
+ return 0;
+}
+
+
+static int proxy_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) {
+ char *s, *ns;
+ int http_response_status = -1;
+
+ UNUSED(srv);
+
+ /* \r\n -> \0\0 */
+
+ buffer_copy_string_buffer(p->parse_response, in);
+
+ for (s = p->parse_response->ptr; NULL != (ns = strstr(s, "\r\n")); s = ns + 2) {
+ char *key, *value;
+ int key_len;
+ data_string *ds;
+ int copy_header;
+
+ ns[0] = '\0';
+ ns[1] = '\0';
+
+ if (-1 == http_response_status) {
+ /* The first line of a Response message is the Status-Line */
+
+ for (key=s; *key && *key != ' '; key++);
+
+ if (*key) {
+ http_response_status = (int) strtol(key, NULL, 10);
+ if (http_response_status <= 0) http_response_status = 502;
+ } else {
+ http_response_status = 502;
+ }
+
+ con->http_status = http_response_status;
+ con->parsed_response |= HTTP_STATUS;
+ continue;
+ }
+
+ if (NULL == (value = strchr(s, ':'))) {
+ /* now we expect: "<key>: <value>\n" */
+
+ continue;
+ }
+
+ key = s;
+ key_len = value - key;
+
+ value++;
+ /* strip WS */
+ while (*value == ' ' || *value == '\t') value++;
+
+ copy_header = 1;
+
+ switch(key_len) {
+ case 4:
+ if (0 == strncasecmp(key, "Date", key_len)) {
+ con->parsed_response |= HTTP_DATE;
+ }
+ break;
+ case 8:
+ if (0 == strncasecmp(key, "Location", key_len)) {
+ con->parsed_response |= HTTP_LOCATION;
+ }
+ break;
+ case 10:
+ if (0 == strncasecmp(key, "Connection", key_len)) {
+ copy_header = 0;
+ }
+ break;
+ case 14:
+ if (0 == strncasecmp(key, "Content-Length", key_len)) {
+ con->response.content_length = strtol(value, NULL, 10);
+ con->parsed_response |= HTTP_CONTENT_LENGTH;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (copy_header) {
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
+ ds = data_response_init();
+ }
+ buffer_copy_string_len(ds->key, key, key_len);
+ buffer_copy_string(ds->value, value);
+
+ array_insert_unique(con->response.headers, (data_unset *)ds);
+ }
+ }
+
+ return 0;
+}
+
+
+static int proxy_demux_response(server *srv, handler_ctx *hctx) {
+ int fin = 0;
+ int b;
+ ssize_t r;
+
+ plugin_data *p = hctx->plugin_data;
+ connection *con = hctx->remote_conn;
+ int proxy_fd = hctx->fd;
+
+ /* check how much we have to read */
+ if (ioctl(hctx->fd, FIONREAD, &b)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "ioctl failed: ",
+ proxy_fd);
+ return -1;
+ }
+
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "proxy - have to read:", b);
+ }
+
+ if (b > 0) {
+ if (hctx->response->used == 0) {
+ /* avoid too small buffer */
+ buffer_prepare_append(hctx->response, b + 1);
+ hctx->response->used = 1;
+ } else {
+ buffer_prepare_append(hctx->response, hctx->response->used + b);
+ }
+
+ if (-1 == (r = read(hctx->fd, hctx->response->ptr + hctx->response->used - 1, b))) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "unexpected end-of-file (perhaps the proxy process died):",
+ proxy_fd, strerror(errno));
+ return -1;
+ }
+
+ /* this should be catched by the b > 0 above */
+ assert(r);
+
+ hctx->response->used += r;
+ hctx->response->ptr[hctx->response->used - 1] = '\0';
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdsbs",
+ "demux: Response buffer len", hctx->response->used, ":", hctx->response, ":");
+#endif
+
+ if (0 == con->got_response) {
+ con->got_response = 1;
+ buffer_prepare_copy(hctx->response_header, 128);
+ }
+
+ if (0 == con->file_started) {
+ char *c;
+
+ /* search for the \r\n\r\n in the string */
+ if (NULL != (c = buffer_search_string_len(hctx->response, "\r\n\r\n", 4))) {
+ size_t hlen = c - hctx->response->ptr + 4;
+ size_t blen = hctx->response->used - hlen - 1;
+ /* found */
+
+ buffer_append_string_len(hctx->response_header, hctx->response->ptr, c - hctx->response->ptr + 4);
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Header:", hctx->response_header);
+#endif
+ /* parse the response header */
+ proxy_response_parse(srv, con, p, hctx->response_header);
+
+ /* enable chunked-transfer-encoding */
+ if (con->request.http_version == HTTP_VERSION_1_1 &&
+ !(con->parsed_response & HTTP_CONTENT_LENGTH)) {
+ con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
+ }
+
+ con->file_started = 1;
+ if (blen) {
+ http_chunk_append_mem(srv, con, c + 4, blen + 1);
+ joblist_append(srv, con);
+ }
+ hctx->response->used = 0;
+ }
+ } else {
+ http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used);
+ joblist_append(srv, con);
+ hctx->response->used = 0;
+ }
+
+ } else {
+ /* reading from upstream done */
+ con->file_finished = 1;
+
+ http_chunk_append_mem(srv, con, NULL, 0);
+ joblist_append(srv, con);
+
+ fin = 1;
+ }
+
+ return fin;
+}
+
+
+static handler_t proxy_write_request(server *srv, handler_ctx *hctx) {
+ data_proxy *host= hctx->host;
+ plugin_data *p = hctx->plugin_data;
+ connection *con = hctx->remote_conn;
+
+ int ret;
+
+ if (!host ||
+ (!host->host->used || !host->port)) return -1;
+
+ switch(hctx->state) {
+ case PROXY_STATE_INIT:
+ if (-1 == (hctx->fd = socket(AF_INET, SOCK_STREAM, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed: ", strerror(errno));
+ return HANDLER_ERROR;
+ }
+ hctx->fde_ndx = -1;
+
+ srv->cur_fds++;
+
+ fdevent_register(srv->ev, hctx->fd, proxy_handle_fdevent, hctx);
+
+ if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ /* fall through */
+
+ case PROXY_STATE_CONNECT:
+ /* try to finish the connect() */
+ if (hctx->state == PROXY_STATE_INIT) {
+ /* first round */
+ switch (proxy_establish_connection(srv, hctx)) {
+ case 1:
+ proxy_set_state(srv, hctx, PROXY_STATE_CONNECT);
+
+ /* connection is in progress, wait for an event and call getsockopt() below */
+
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ return HANDLER_WAIT_FOR_EVENT;
+ case -1:
+ /* if ECONNREFUSED choose another connection -> FIXME */
+ hctx->fde_ndx = -1;
+
+ return HANDLER_ERROR;
+ default:
+ /* everything is ok, go on */
+ break;
+ }
+ } else {
+ int socket_error;
+ socklen_t socket_error_len = sizeof(socket_error);
+
+ /* we don't need it anymore */
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+
+ /* try to finish the connect() */
+ if (0 != getsockopt(hctx->fd, SOL_SOCKET, SO_ERROR, &socket_error, &socket_error_len)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "getsockopt failed:", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+ if (socket_error != 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "establishing connection failed:", strerror(socket_error),
+ "port:", hctx->host->port);
+
+ return HANDLER_ERROR;
+ }
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "proxy - connect - delayed success");
+ }
+ }
+
+ proxy_set_state(srv, hctx, PROXY_STATE_PREPARE_WRITE);
+ /* fall through */
+ case PROXY_STATE_PREPARE_WRITE:
+ proxy_create_env(srv, hctx);
+
+ proxy_set_state(srv, hctx, PROXY_STATE_WRITE);
+
+ /* fall through */
+ case PROXY_STATE_WRITE:;
+ ret = srv->network_backend_write(srv, con, hctx->fd, hctx->wb);
+
+ chunkqueue_remove_finished_chunks(hctx->wb);
+
+ if (-1 == ret) {
+ if (errno != EAGAIN &&
+ errno != EINTR) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed:", strerror(errno), errno);
+
+ return HANDLER_ERROR;
+ } else {
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+ }
+
+ if (hctx->wb->bytes_out == hctx->wb->bytes_in) {
+ proxy_set_state(srv, hctx, PROXY_STATE_READ);
+
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+ } else {
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+
+ return HANDLER_WAIT_FOR_EVENT;
+ case PROXY_STATE_READ:
+ /* waiting for a response */
+ return HANDLER_WAIT_FOR_EVENT;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state");
+ return HANDLER_ERROR;
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_proxy_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(extensions);
+ PATCH(debug);
+ PATCH(balance);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.server"))) {
+ PATCH(extensions);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.debug"))) {
+ PATCH(debug);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.balance"))) {
+ PATCH(balance);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+SUBREQUEST_FUNC(mod_proxy_handle_subrequest) {
+ plugin_data *p = p_d;
+
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+ data_proxy *host;
+
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+ mod_proxy_patch_connection(srv, con, p);
+
+ host = hctx->host;
+
+ /* not my job */
+ if (con->mode != p->id) return HANDLER_GO_ON;
+
+ /* ok, create the request */
+ switch(proxy_write_request(srv, hctx)) {
+ case HANDLER_ERROR:
+ log_error_write(srv, __FILE__, __LINE__, "sbdd", "proxy-server disabled:",
+ host->host,
+ host->port,
+ hctx->fd);
+
+ /* disable this server */
+ host->is_disabled = 1;
+ host->disable_ts = srv->cur_ts;
+
+ proxy_connection_close(srv, hctx);
+
+ /* reset the enviroment and restart the sub-request */
+ buffer_reset(con->physical.path);
+ con->mode = DIRECT;
+
+ joblist_append(srv, con);
+
+ /* mis-using HANDLER_WAIT_FOR_FD to break out of the loop
+ * and hope that the childs will be restarted
+ *
+ */
+
+ return HANDLER_WAIT_FOR_FD;
+ case HANDLER_WAIT_FOR_EVENT:
+ return HANDLER_WAIT_FOR_EVENT;
+ case HANDLER_WAIT_FOR_FD:
+ return HANDLER_WAIT_FOR_FD;
+ default:
+ break;
+ }
+
+ if (con->file_started == 1) {
+ return HANDLER_FINISHED;
+ } else {
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+}
+
+static handler_t proxy_handle_fdevent(void *s, void *ctx, int revents) {
+ server *srv = (server *)s;
+ handler_ctx *hctx = ctx;
+ connection *con = hctx->remote_conn;
+ plugin_data *p = hctx->plugin_data;
+
+
+ if ((revents & FDEVENT_IN) &&
+ hctx->state == PROXY_STATE_READ) {
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "proxy: fdevent-in", hctx->state);
+ }
+
+ switch (proxy_demux_response(srv, hctx)) {
+ case 0:
+ break;
+ case 1:
+ hctx->host->usage--;
+
+ /* we are done */
+ proxy_connection_close(srv, hctx);
+
+ joblist_append(srv, con);
+ return HANDLER_FINISHED;
+ case -1:
+ if (con->file_started == 0) {
+ /* nothing has been send out yet, send a 500 */
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ con->http_status = 500;
+ con->mode = DIRECT;
+ } else {
+ /* response might have been already started, kill the connection */
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ }
+
+ joblist_append(srv, con);
+ return HANDLER_FINISHED;
+ }
+ }
+
+ if (revents & FDEVENT_OUT) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "proxy: fdevent-out", hctx->state);
+ }
+
+ if (hctx->state == PROXY_STATE_CONNECT ||
+ hctx->state == PROXY_STATE_WRITE) {
+ /* we are allowed to send something out
+ *
+ * 1. in a unfinished connect() call
+ * 2. in a unfinished write() call (long POST request)
+ */
+ return mod_proxy_handle_subrequest(srv, con, p);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "proxy: out", hctx->state);
+ }
+ }
+
+ /* perhaps this issue is already handled */
+ if (revents & FDEVENT_HUP) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "proxy: fdevent-hup", hctx->state);
+ }
+
+ if (hctx->state == PROXY_STATE_CONNECT) {
+ /* connect() -> EINPROGRESS -> HUP */
+
+ /**
+ * what is proxy is doing if it can't reach the next hop ?
+ *
+ */
+
+ proxy_connection_close(srv, hctx);
+ joblist_append(srv, con);
+
+ con->http_status = 503;
+ con->mode = DIRECT;
+
+ return HANDLER_FINISHED;
+ }
+
+ con->file_finished = 1;
+
+ proxy_connection_close(srv, hctx);
+ joblist_append(srv, con);
+ } else if (revents & FDEVENT_ERR) {
+ /* kill all connections to the proxy process */
+
+ log_error_write(srv, __FILE__, __LINE__, "sd", "proxy-FDEVENT_ERR, but no HUP", revents);
+
+ joblist_append(srv, con);
+ proxy_connection_close(srv, hctx);
+ }
+
+ return HANDLER_FINISHED;
+}
+
+static handler_t mod_proxy_check_extension(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ size_t s_len;
+ unsigned long last_max = ULONG_MAX;
+ int max_usage = INT_MAX;
+ int ndx = -1;
+ size_t k;
+ buffer *fn;
+ data_array *extension = NULL;
+ size_t path_info_offset;
+
+ /* Possibly, we processed already this request */
+ if (con->file_started == 1) return HANDLER_GO_ON;
+
+ mod_proxy_patch_connection(srv, con, p);
+
+ fn = con->uri.path;
+
+ if (fn->used == 0) {
+ return HANDLER_ERROR;
+ }
+
+ s_len = fn->used - 1;
+
+
+ path_info_offset = 0;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "proxy - start");
+ }
+
+ /* check if extension matches */
+ for (k = 0; k < p->conf.extensions->used; k++) {
+ size_t ct_len;
+
+ extension = (data_array *)p->conf.extensions->data[k];
+
+ if (extension->key->used == 0) continue;
+
+ ct_len = extension->key->used - 1;
+
+ if (s_len < ct_len) continue;
+
+ /* check extension in the form "/proxy_pattern" */
+ if (*(extension->key->ptr) == '/' && strncmp(fn->ptr, extension->key->ptr, ct_len) == 0) {
+ if (s_len > ct_len + 1) {
+ char *pi_offset;
+
+ if (0 != (pi_offset = strchr(fn->ptr + ct_len + 1, '/'))) {
+ path_info_offset = pi_offset - fn->ptr;
+ }
+ }
+ break;
+ } else if (0 == strncmp(fn->ptr + s_len - ct_len, extension->key->ptr, ct_len)) {
+ /* check extension in the form ".fcg" */
+ break;
+ }
+ }
+
+ if (k == p->conf.extensions->used) {
+ return HANDLER_GO_ON;
+ }
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "proxy - ext found");
+ }
+
+ switch(p->conf.balance) {
+ case PROXY_BALANCE_HASH:
+ /* hash balancing */
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "proxy - used hash balancing, hosts:", extension->value->used);
+ }
+
+ for (k = 0, ndx = -1, last_max = ULONG_MAX; k < extension->value->used; k++) {
+ data_proxy *host = (data_proxy *)extension->value->data[k];
+ unsigned long cur_max;
+
+ if (host->is_disabled) continue;
+
+ cur_max = generate_crc32c(CONST_BUF_LEN(con->uri.path)) +
+ generate_crc32c(CONST_BUF_LEN(host->host)) + /* we can cache this */
+ generate_crc32c(CONST_BUF_LEN(con->uri.authority));
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sbbbd",
+ "proxy - election:",
+ con->uri.path,
+ host->host,
+ con->uri.authority,
+ cur_max);
+ }
+
+ if ((last_max == ULONG_MAX) || /* first round */
+ (cur_max > last_max)) {
+ last_max = cur_max;
+
+ ndx = k;
+ }
+ }
+
+ break;
+ case PROXY_BALANCE_FAIR:
+ /* fair balancing */
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "proxy - used fair balancing");
+ }
+
+ for (k = 0, ndx = -1, max_usage = INT_MAX; k < extension->value->used; k++) {
+ data_proxy *host = (data_proxy *)extension->value->data[k];
+
+ if (host->is_disabled) continue;
+
+ if (host->usage < max_usage) {
+ max_usage = host->usage;
+
+ ndx = k;
+ }
+ }
+
+ break;
+ case PROXY_BALANCE_RR:
+ /* round robin */
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "proxy - used round-robin balancing");
+ }
+
+ /* just to be sure */
+ assert(extension->value->used < INT_MAX);
+
+ for (k = 0, ndx = -1, max_usage = INT_MAX; k < extension->value->used; k++) {
+ data_proxy *host = (data_proxy *)extension->value->data[k];
+
+ if (host->is_disabled) continue;
+
+ /* first usable ndx */
+ if (max_usage == INT_MAX) {
+ max_usage = k;
+ }
+
+ /* get next ndx */
+ if ((int)k > host->last_used_ndx) {
+ ndx = k;
+ host->last_used_ndx = k;
+
+ break;
+ }
+ }
+
+ /* didn't found a higher id, wrap to the start */
+ if (ndx != -1 && max_usage != INT_MAX) {
+ ndx = max_usage;
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ /* found a server */
+ if (ndx != -1) {
+ data_proxy *host = (data_proxy *)extension->value->data[ndx];
+
+ /*
+ * if check-local is disabled, use the uri.path handler
+ *
+ */
+
+ /* init handler-context */
+ handler_ctx *hctx;
+ hctx = handler_ctx_init();
+
+ hctx->path_info_offset = path_info_offset;
+ hctx->remote_conn = con;
+ hctx->plugin_data = p;
+ hctx->host = host;
+
+ con->plugin_ctx[p->id] = hctx;
+
+ host->usage++;
+
+ con->mode = p->id;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sbd",
+ "proxy - found a host",
+ host->host, host->port);
+ }
+
+ return HANDLER_GO_ON;
+ } else {
+ /* no handler found */
+ con->http_status = 500;
+
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "no proxy-handler found for:",
+ fn);
+
+ return HANDLER_FINISHED;
+ }
+ return HANDLER_GO_ON;
+}
+
+static handler_t mod_proxy_connection_close_callback(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ proxy_connection_close(srv, con->plugin_ctx[p->id]);
+
+ return HANDLER_GO_ON;
+}
+
+/**
+ *
+ * the trigger re-enables the disabled connections after the timeout is over
+ *
+ * */
+
+TRIGGER_FUNC(mod_proxy_trigger) {
+ plugin_data *p = p_d;
+
+ if (p->config_storage) {
+ size_t i, n, k;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (!s) continue;
+
+ /* get the extensions for all configs */
+
+ for (k = 0; k < s->extensions->used; k++) {
+ data_array *extension = (data_array *)s->extensions->data[k];
+
+ /* get all hosts */
+ for (n = 0; n < extension->value->used; n++) {
+ data_proxy *host = (data_proxy *)extension->value->data[n];
+
+ if (!host->is_disabled ||
+ srv->cur_ts - host->disable_ts < 5) continue;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbd",
+ "proxy - re-enabled:",
+ host->host, host->port);
+
+ host->is_disabled = 0;
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+int mod_proxy_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("proxy");
+
+ p->init = mod_proxy_init;
+ p->cleanup = mod_proxy_free;
+ p->set_defaults = mod_proxy_set_defaults;
+ p->connection_reset = mod_proxy_connection_close_callback; /* end of req-resp cycle */
+ p->handle_connection_close = mod_proxy_connection_close_callback; /* end of client connection */
+ p->handle_uri_clean = mod_proxy_check_extension;
+ p->handle_subrequest = mod_proxy_handle_subrequest;
+ p->handle_trigger = mod_proxy_trigger;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_redirect.c b/src/mod_redirect.c
new file mode 100644
index 0000000..079e756
--- /dev/null
+++ b/src/mod_redirect.c
@@ -0,0 +1,277 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+#include "response.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+typedef struct {
+ pcre_keyvalue_buffer *redirect;
+ data_config *context; /* to which apply me */
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer *match_buf;
+ buffer *location;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_redirect_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->match_buf = buffer_init();
+ p->location = buffer_init();
+
+ return p;
+}
+
+FREE_FUNC(mod_redirect_free) {
+ plugin_data *p = p_d;
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ pcre_keyvalue_buffer_free(s->redirect);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+
+ buffer_free(p->match_buf);
+ buffer_free(p->location);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_redirect_set_defaults) {
+ plugin_data *p = p_d;
+ data_unset *du;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "url.redirect", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ /* 0 */
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ size_t j;
+ array *ca;
+ data_array *da = (data_array *)du;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->redirect = pcre_keyvalue_buffer_init();
+
+ cv[0].destination = s->redirect;
+
+ p->config_storage[i] = s;
+ ca = ((data_config *)srv->config_context->data[i])->value;
+
+ if (0 != config_insert_values_global(srv, ca, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (NULL == (du = array_get_element(ca, "url.redirect"))) {
+ /* no url.redirect defined */
+ continue;
+ }
+
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", "url.redirect", "array of strings");
+
+ return HANDLER_ERROR;
+ }
+
+ da = (data_array *)du;
+
+ for (j = 0; j < da->value->used; j++) {
+ if (da->value->data[j]->type != TYPE_STRING) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ",
+ "url.redirect",
+ "[", da->value->data[j]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ if (0 != pcre_keyvalue_buffer_append(s->redirect,
+ ((data_string *)(da->value->data[j]))->key->ptr,
+ ((data_string *)(da->value->data[j]))->value->ptr)) {
+
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "pcre-compile failed for", da->value->data[j]->key);
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+#ifdef HAVE_PCRE_H
+static int mod_redirect_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ p->conf.redirect = s->redirect;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (0 == strcmp(du->key->ptr, "url.redirect")) {
+ p->conf.redirect = s->redirect;
+ p->conf.context = dc;
+ }
+ }
+ }
+
+ return 0;
+}
+#endif
+static handler_t mod_redirect_uri_handler(server *srv, connection *con, void *p_data) {
+#ifdef HAVE_PCRE_H
+ plugin_data *p = p_data;
+ size_t i;
+
+ /*
+ * REWRITE URL
+ *
+ * e.g. redirect /base/ to /index.php?section=base
+ *
+ */
+
+ mod_redirect_patch_connection(srv, con, p);
+
+ buffer_copy_string_buffer(p->match_buf, con->request.uri);
+
+ for (i = 0; i < p->conf.redirect->used; i++) {
+ pcre *match;
+ pcre_extra *extra;
+ const char *pattern;
+ size_t pattern_len;
+ int n;
+ pcre_keyvalue *kv = p->conf.redirect->kv[i];
+# define N 10
+ int ovec[N * 3];
+
+ match = kv->key;
+ extra = kv->key_extra;
+ pattern = kv->value->ptr;
+ pattern_len = kv->value->used - 1;
+
+ if ((n = pcre_exec(match, extra, p->match_buf->ptr, p->match_buf->used - 1, 0, 0, ovec, 3 * N)) < 0) {
+ if (n != PCRE_ERROR_NOMATCH) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "execution error while matching: ", n);
+ return HANDLER_ERROR;
+ }
+ } else {
+ const char **list;
+ size_t start, end;
+ size_t k;
+
+ /* it matched */
+ pcre_get_substring_list(p->match_buf->ptr, ovec, n, &list);
+
+ /* search for $[0-9] */
+
+ buffer_reset(p->location);
+
+ start = 0; end = pattern_len;
+ for (k = 0; k < pattern_len; k++) {
+ if ((pattern[k] == '$' || pattern[k] == '%') &&
+ isdigit((unsigned char)pattern[k + 1])) {
+ /* got one */
+
+ size_t num = pattern[k + 1] - '0';
+
+ end = k;
+
+ buffer_append_string_len(p->location, pattern + start, end - start);
+
+ if (pattern[k] == '$') {
+ /* n is always > 0 */
+ if (num < (size_t)n) {
+ buffer_append_string(p->location, list[num]);
+ }
+ } else {
+ config_append_cond_match_buffer(con, p->conf.context, p->location, num);
+ }
+
+ k++;
+ start = k + 1;
+ }
+ }
+
+ buffer_append_string_len(p->location, pattern + start, pattern_len - start);
+
+ pcre_free(list);
+
+ response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->location));
+
+ con->http_status = 301;
+ con->file_finished = 1;
+
+ return HANDLER_FINISHED;
+ }
+ }
+#undef N
+
+#else
+ UNUSED(srv);
+ UNUSED(con);
+ UNUSED(p_data);
+#endif
+
+ return HANDLER_GO_ON;
+}
+
+
+int mod_redirect_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("redirect");
+
+ p->init = mod_redirect_init;
+ p->handle_uri_clean = mod_redirect_uri_handler;
+ p->set_defaults = mod_redirect_set_defaults;
+ p->cleanup = mod_redirect_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_rewrite.c b/src/mod_rewrite.c
new file mode 100644
index 0000000..ff152a9
--- /dev/null
+++ b/src/mod_rewrite.c
@@ -0,0 +1,450 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+typedef struct {
+#ifdef HAVE_PCRE_H
+ pcre *key;
+#endif
+
+ buffer *value;
+
+ int once;
+} rewrite_rule;
+
+typedef struct {
+ rewrite_rule **ptr;
+
+ size_t used;
+ size_t size;
+} rewrite_rule_buffer;
+
+typedef struct {
+ rewrite_rule_buffer *rewrite;
+ data_config *context; /* to which apply me */
+} plugin_config;
+
+typedef struct {
+ enum { REWRITE_STATE_UNSET, REWRITE_STATE_FINISHED} state;
+ int loops;
+} handler_ctx;
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer *match_buf;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+static handler_ctx * handler_ctx_init() {
+ handler_ctx * hctx;
+
+ hctx = calloc(1, sizeof(*hctx));
+
+ hctx->state = REWRITE_STATE_UNSET;
+ hctx->loops = 0;
+
+ return hctx;
+}
+
+static void handler_ctx_free(handler_ctx *hctx) {
+ free(hctx);
+}
+
+rewrite_rule_buffer *rewrite_rule_buffer_init(void) {
+ rewrite_rule_buffer *kvb;
+
+ kvb = calloc(1, sizeof(*kvb));
+
+ return kvb;
+}
+
+int rewrite_rule_buffer_append(rewrite_rule_buffer *kvb, buffer *key, buffer *value, int once) {
+#ifdef HAVE_PCRE_H
+ size_t i;
+ const char *errptr;
+ int erroff;
+
+ if (!key) return -1;
+
+ if (kvb->size == 0) {
+ kvb->size = 4;
+ kvb->used = 0;
+
+ kvb->ptr = malloc(kvb->size * sizeof(*kvb->ptr));
+
+ for(i = 0; i < kvb->size; i++) {
+ kvb->ptr[i] = calloc(1, sizeof(**kvb->ptr));
+ }
+ } else if (kvb->used == kvb->size) {
+ kvb->size += 4;
+
+ kvb->ptr = realloc(kvb->ptr, kvb->size * sizeof(*kvb->ptr));
+
+ for(i = kvb->used; i < kvb->size; i++) {
+ kvb->ptr[i] = calloc(1, sizeof(**kvb->ptr));
+ }
+ }
+
+ if (NULL == (kvb->ptr[kvb->used]->key = pcre_compile(key->ptr,
+ 0, &errptr, &erroff, NULL))) {
+
+ return -1;
+ }
+
+ kvb->ptr[kvb->used]->value = buffer_init();
+ buffer_copy_string_buffer(kvb->ptr[kvb->used]->value, value);
+ kvb->ptr[kvb->used]->once = once;
+
+ kvb->used++;
+
+ return 0;
+#else
+ UNUSED(kvb);
+ UNUSED(value);
+ UNUSED(once);
+ UNUSED(key);
+
+ return -1;
+#endif
+}
+
+void rewrite_rule_buffer_free(rewrite_rule_buffer *kvb) {
+#ifdef HAVE_PCRE_H
+ size_t i;
+
+ for (i = 0; i < kvb->size; i++) {
+ if (kvb->ptr[i]->key) pcre_free(kvb->ptr[i]->key);
+ if (kvb->ptr[i]->value) buffer_free(kvb->ptr[i]->value);
+ free(kvb->ptr[i]);
+ }
+
+ if (kvb->ptr) free(kvb->ptr);
+#endif
+
+ free(kvb);
+}
+
+
+INIT_FUNC(mod_rewrite_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->match_buf = buffer_init();
+
+ return p;
+}
+
+FREE_FUNC(mod_rewrite_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ buffer_free(p->match_buf);
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+ rewrite_rule_buffer_free(s->rewrite);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+static int parse_config_entry(server *srv, plugin_config *s, array *ca, const char *option, int once) {
+ data_unset *du;
+
+ if (NULL != (du = array_get_element(ca, option))) {
+ data_array *da = (data_array *)du;
+ size_t j;
+
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", option, "array of strings");
+
+ return HANDLER_ERROR;
+ }
+
+ da = (data_array *)du;
+
+ for (j = 0; j < da->value->used; j++) {
+ if (da->value->data[j]->type != TYPE_STRING) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ",
+ option,
+ "[", da->value->data[j]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ if (0 != rewrite_rule_buffer_append(s->rewrite,
+ ((data_string *)(da->value->data[j]))->key,
+ ((data_string *)(da->value->data[j]))->value,
+ once)) {
+#ifdef HAVE_PCRE_H
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "pcre-compile failed for", da->value->data[j]->key);
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "pcre support is missing, please install libpcre and the headers");
+#endif
+ }
+ }
+ }
+
+ return 0;
+}
+
+SETDEFAULTS_FUNC(mod_rewrite_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "url.rewrite-repeat", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "url.rewrite-once", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+
+ /* old names, still supported
+ *
+ * url.rewrite remapped to url.rewrite-once
+ * url.rewrite-final is url.rewrite-once
+ *
+ */
+ { "url.rewrite", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "url.rewrite-final", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ /* 0 */
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ array *ca;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->rewrite = rewrite_rule_buffer_init();
+
+ cv[0].destination = s->rewrite;
+ cv[1].destination = s->rewrite;
+ cv[2].destination = s->rewrite;
+
+ p->config_storage[i] = s;
+ ca = ((data_config *)srv->config_context->data[i])->value;
+
+ if (0 != config_insert_values_global(srv, ca, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ parse_config_entry(srv, s, ca, "url.rewrite-once", 1);
+ parse_config_entry(srv, s, ca, "url.rewrite-final", 1);
+ parse_config_entry(srv, s, ca, "url.rewrite", 1);
+ parse_config_entry(srv, s, ca, "url.rewrite-repeat", 0);
+ }
+
+ return HANDLER_GO_ON;
+}
+#ifdef HAVE_PCRE_H
+static int mod_rewrite_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+ p->conf.rewrite = s->rewrite;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ if (COMP_HTTP_URL == dc->comp) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite"))) {
+ p->conf.rewrite = s->rewrite;
+ p->conf.context = dc;
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-once"))) {
+ p->conf.rewrite = s->rewrite;
+ p->conf.context = dc;
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-repeat"))) {
+ p->conf.rewrite = s->rewrite;
+ p->conf.context = dc;
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-final"))) {
+ p->conf.rewrite = s->rewrite;
+ p->conf.context = dc;
+ }
+ }
+ }
+
+ return 0;
+}
+#endif
+URIHANDLER_FUNC(mod_rewrite_con_reset) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (con->plugin_ctx[p->id]) {
+ handler_ctx_free(con->plugin_ctx[p->id]);
+ con->plugin_ctx[p->id] = NULL;
+ }
+
+ return HANDLER_GO_ON;
+}
+
+URIHANDLER_FUNC(mod_rewrite_uri_handler) {
+#ifdef HAVE_PCRE_H
+ plugin_data *p = p_d;
+ size_t i;
+ handler_ctx *hctx;
+
+ /*
+ * REWRITE URL
+ *
+ * e.g. rewrite /base/ to /index.php?section=base
+ *
+ */
+
+ if (con->plugin_ctx[p->id]) {
+ hctx = con->plugin_ctx[p->id];
+
+ if (hctx->loops++ > 100) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ENDLESS LOOP IN rewrite-rule DETECTED ... aborting request, perhaps you want to use url.rewrite-once instead of url.rewrite-repeat");
+
+ return HANDLER_ERROR;
+ }
+
+ if (hctx->state == REWRITE_STATE_FINISHED) return HANDLER_GO_ON;
+ }
+
+ mod_rewrite_patch_connection(srv, con, p);
+
+ if (!p->conf.rewrite) return HANDLER_GO_ON;
+
+ buffer_copy_string_buffer(p->match_buf, con->request.uri);
+
+ for (i = 0; i < p->conf.rewrite->used; i++) {
+ pcre *match;
+ const char *pattern;
+ size_t pattern_len;
+ int n;
+ rewrite_rule *rule = p->conf.rewrite->ptr[i];
+# define N 10
+ int ovec[N * 3];
+
+ match = rule->key;
+ pattern = rule->value->ptr;
+ pattern_len = rule->value->used - 1;
+
+ if ((n = pcre_exec(match, NULL, p->match_buf->ptr, p->match_buf->used - 1, 0, 0, ovec, 3 * N)) < 0) {
+ if (n != PCRE_ERROR_NOMATCH) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "execution error while matching: ", n);
+ return HANDLER_ERROR;
+ }
+ } else {
+ const char **list;
+ size_t start, end;
+ size_t k;
+
+ /* it matched */
+ pcre_get_substring_list(p->match_buf->ptr, ovec, n, &list);
+
+ /* search for $[0-9] */
+
+ buffer_reset(con->request.uri);
+
+ start = 0; end = pattern_len;
+ for (k = 0; k < pattern_len; k++) {
+ if ((pattern[k] == '$' || pattern[k] == '%') &&
+ isdigit((unsigned char)pattern[k + 1])) {
+ /* got one */
+
+ size_t num = pattern[k + 1] - '0';
+
+ end = k;
+
+ buffer_append_string_len(con->request.uri, pattern + start, end - start);
+
+ if (pattern[k] == '$') {
+ /* n is always > 0 */
+ if (num < (size_t)n) {
+ buffer_append_string(con->request.uri, list[num]);
+ }
+ } else {
+ config_append_cond_match_buffer(con, p->conf.context, con->request.uri, num);
+ }
+
+ k++;
+ start = k + 1;
+ }
+ }
+
+ buffer_append_string_len(con->request.uri, pattern + start, pattern_len - start);
+
+ pcre_free(list);
+
+ hctx = handler_ctx_init();
+
+ con->plugin_ctx[p->id] = hctx;
+
+ if (rule->once) hctx->state = REWRITE_STATE_FINISHED;
+
+ return HANDLER_COMEBACK;
+ }
+ }
+#undef N
+
+#else
+ UNUSED(srv);
+ UNUSED(con);
+ UNUSED(p_d);
+#endif
+
+ return HANDLER_GO_ON;
+}
+
+int mod_rewrite_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("rewrite");
+
+ p->init = mod_rewrite_init;
+ /* it has to stay _raw as we are matching on uri + querystring
+ */
+
+ p->handle_uri_raw = mod_rewrite_uri_handler;
+ p->set_defaults = mod_rewrite_set_defaults;
+ p->cleanup = mod_rewrite_free;
+ p->connection_reset = mod_rewrite_con_reset;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_rrdtool.c b/src/mod_rrdtool.c
new file mode 100644
index 0000000..c7b897a
--- /dev/null
+++ b/src/mod_rrdtool.c
@@ -0,0 +1,449 @@
+#define _GNU_SOURCE
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+
+#include "server.h"
+#include "connections.h"
+#include "response.h"
+#include "connections.h"
+#include "log.h"
+
+#include "plugin.h"
+#ifdef HAVE_FORK
+/* no need for waitpid if we don't have fork */
+#include <sys/wait.h>
+#endif
+typedef struct {
+ buffer *path_rrdtool_bin;
+ buffer *path_rrd;
+
+ double requests, *requests_ptr;
+ double bytes_written, *bytes_written_ptr;
+ double bytes_read, *bytes_read_ptr;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *cmd;
+ buffer *resp;
+
+ int read_fd, write_fd;
+ pid_t rrdtool_pid;
+
+ int rrdtool_running;
+
+ plugin_config **config_storage;
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_rrd_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->resp = buffer_init();
+ p->cmd = buffer_init();
+
+ return p;
+}
+
+FREE_FUNC(mod_rrd_free) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free(s->path_rrdtool_bin);
+ buffer_free(s->path_rrd);
+
+ free(s);
+ }
+ }
+ buffer_free(p->cmd);
+ buffer_free(p->resp);
+
+ free(p->config_storage);
+
+ if (p->rrdtool_pid) {
+ int status;
+ close(p->read_fd);
+ close(p->write_fd);
+#ifdef HAVE_FORK
+ /* collect status */
+ waitpid(p->rrdtool_pid, &status, 0);
+#endif
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+int mod_rrd_create_pipe(server *srv, plugin_data *p) {
+ pid_t pid;
+
+ int to_rrdtool_fds[2];
+ int from_rrdtool_fds[2];
+#ifdef HAVE_FORK
+ if (pipe(to_rrdtool_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "pipe failed: ", strerror(errno));
+ return -1;
+ }
+
+ if (pipe(from_rrdtool_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "pipe failed: ", strerror(errno));
+ return -1;
+ }
+
+ /* fork, execve */
+ switch (pid = fork()) {
+ case 0: {
+ /* child */
+ char **args;
+ int argc;
+ int i = 0;
+ char *dash = "-";
+
+ /* move stdout to from_rrdtool_fd[1] */
+ close(STDOUT_FILENO);
+ dup2(from_rrdtool_fds[1], STDOUT_FILENO);
+ close(from_rrdtool_fds[1]);
+ /* not needed */
+ close(from_rrdtool_fds[0]);
+
+ /* move the stdin to to_rrdtool_fd[0] */
+ close(STDIN_FILENO);
+ dup2(to_rrdtool_fds[0], STDIN_FILENO);
+ close(to_rrdtool_fds[0]);
+ /* not needed */
+ close(to_rrdtool_fds[1]);
+
+ close(STDERR_FILENO);
+
+ if (srv->errorlog_mode == ERRORLOG_FILE) {
+ dup2(srv->errorlog_fd, STDERR_FILENO);
+ close(srv->errorlog_fd);
+ }
+
+ /* set up args */
+ argc = 3;
+ args = malloc(sizeof(*args) * argc);
+ i = 0;
+
+ args[i++] = p->conf.path_rrdtool_bin->ptr;
+ args[i++] = dash;
+ args[i++] = NULL;
+
+ /* we don't need the client socket */
+ for (i = 3; i < 256; i++) {
+ close(i);
+ }
+
+ /* exec the cgi */
+ execv(args[0], args);
+
+ log_error_write(srv, __FILE__, __LINE__, "sss", "spawing rrdtool failed: ", strerror(errno), args[0]);
+
+ /* */
+ SEGFAULT();
+ break;
+ }
+ case -1:
+ /* error */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed: ", strerror(errno));
+ break;
+ default: {
+ /* father */
+
+ close(from_rrdtool_fds[1]);
+ close(to_rrdtool_fds[0]);
+
+ /* register PID and wait for them asyncronously */
+ p->write_fd = to_rrdtool_fds[1];
+ p->read_fd = from_rrdtool_fds[0];
+ p->rrdtool_pid = pid;
+
+ break;
+ }
+ }
+
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+static int mod_rrdtool_create_rrd(server *srv, plugin_data *p, plugin_config *s) {
+ struct stat st;
+
+ /* check if DB already exists */
+ if (0 == stat(s->path_rrd->ptr, &st)) {
+ /* check if it is plain file */
+ if (!S_ISREG(st.st_mode)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "not a regular file:", s->path_rrd);
+ return HANDLER_ERROR;
+ }
+ } else {
+ int r ;
+ /* create a new one */
+
+ BUFFER_COPY_STRING_CONST(p->cmd, "create ");
+ buffer_append_string_buffer(p->cmd, s->path_rrd);
+ buffer_append_string(p->cmd, " --step 60 ");
+ buffer_append_string(p->cmd, "DS:InOctets:ABSOLUTE:600:U:U ");
+ buffer_append_string(p->cmd, "DS:OutOctets:ABSOLUTE:600:U:U ");
+ buffer_append_string(p->cmd, "DS:Requests:ABSOLUTE:600:U:U ");
+ buffer_append_string(p->cmd, "RRA:AVERAGE:0.5:1:600 ");
+ buffer_append_string(p->cmd, "RRA:AVERAGE:0.5:6:700 ");
+ buffer_append_string(p->cmd, "RRA:AVERAGE:0.5:24:775 ");
+ buffer_append_string(p->cmd, "RRA:AVERAGE:0.5:288:797 ");
+ buffer_append_string(p->cmd, "RRA:MAX:0.5:1:600 ");
+ buffer_append_string(p->cmd, "RRA:MAX:0.5:6:700 ");
+ buffer_append_string(p->cmd, "RRA:MAX:0.5:24:775 ");
+ buffer_append_string(p->cmd, "RRA:MAX:0.5:288:797 ");
+ buffer_append_string(p->cmd, "RRA:MIN:0.5:1:600 ");
+ buffer_append_string(p->cmd, "RRA:MIN:0.5:6:700 ");
+ buffer_append_string(p->cmd, "RRA:MIN:0.5:24:775 ");
+ buffer_append_string(p->cmd, "RRA:MIN:0.5:288:797\n");
+
+ if (-1 == (r = write(p->write_fd, p->cmd->ptr, p->cmd->used - 1))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "rrdtool-write: failed", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ buffer_prepare_copy(p->resp, 4096);
+ if (-1 == (r = read(p->read_fd, p->resp->ptr, p->resp->size))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "rrdtool-read: failed", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ p->resp->used = r;
+
+ if (p->resp->ptr[0] != 'O' ||
+ p->resp->ptr[1] != 'K') {
+ log_error_write(srv, __FILE__, __LINE__, "sbb",
+ "rrdtool-response:", p->cmd, p->resp);
+
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_rrd_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(path_rrdtool_bin);
+ PATCH(path_rrd);
+
+ p->conf.bytes_written_ptr = &(s->bytes_written);
+ p->conf.bytes_read_ptr = &(s->bytes_read);
+ p->conf.requests_ptr = &(s->requests);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("rrdtool.db-name"))) {
+ PATCH(path_rrd);
+ /* get pointers to double values */
+
+ p->conf.bytes_written_ptr = &(s->bytes_written);
+ p->conf.bytes_read_ptr = &(s->bytes_read);
+ p->conf.requests_ptr = &(s->requests);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+SETDEFAULTS_FUNC(mod_rrd_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ config_values_t cv[] = {
+ { "rrdtool.binary", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
+ { "rrdtool.db-name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->path_rrdtool_bin = buffer_init();
+ s->path_rrd = buffer_init();
+ s->requests = 0;
+ s->bytes_written = 0;
+ s->bytes_read = 0;
+
+ cv[0].destination = s->path_rrdtool_bin;
+ cv[1].destination = s->path_rrd;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (i > 0 && !buffer_is_empty(s->path_rrdtool_bin)) {
+ /* path_rrdtool_bin is a global option */
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "rrdtool.binary can only be set as a global option.");
+
+ return HANDLER_ERROR;
+ }
+
+ }
+
+ p->conf.path_rrdtool_bin = p->config_storage[0]->path_rrdtool_bin;
+ p->rrdtool_running = 0;
+
+ /* check for dir */
+
+ if (buffer_is_empty(p->conf.path_rrdtool_bin)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "rrdtool.binary has to be set");
+ return HANDLER_ERROR;
+ }
+
+ /* open the pipe to rrdtool */
+ if (mod_rrd_create_pipe(srv, p)) {
+ return HANDLER_ERROR;
+ }
+
+ p->rrdtool_running = 1;
+
+ return HANDLER_GO_ON;
+}
+
+TRIGGER_FUNC(mod_rrd_trigger) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ if (!p->rrdtool_running) return HANDLER_GO_ON;
+ if ((srv->cur_ts % 60) != 0) return HANDLER_GO_ON;
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+ int r;
+
+ if (buffer_is_empty(s->path_rrd)) continue;
+
+ /* write the data down every minute */
+
+ if (HANDLER_GO_ON != mod_rrdtool_create_rrd(srv, p, s)) return HANDLER_ERROR;
+
+ BUFFER_COPY_STRING_CONST(p->cmd, "update ");
+ buffer_append_string_buffer(p->cmd, s->path_rrd);
+ BUFFER_APPEND_STRING_CONST(p->cmd, " N:");
+ buffer_append_off_t(p->cmd, s->bytes_read);
+ BUFFER_APPEND_STRING_CONST(p->cmd, ":");
+ buffer_append_off_t(p->cmd, s->bytes_written);
+ BUFFER_APPEND_STRING_CONST(p->cmd, ":");
+ buffer_append_long(p->cmd, s->requests);
+ BUFFER_APPEND_STRING_CONST(p->cmd, "\n");
+
+ if (-1 == (r = write(p->write_fd, p->cmd->ptr, p->cmd->used - 1))) {
+ p->rrdtool_running = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "rrdtool-write: failed", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ buffer_prepare_copy(p->resp, 4096);
+ if (-1 == (r = read(p->read_fd, p->resp->ptr, p->resp->size))) {
+ p->rrdtool_running = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "rrdtool-read: failed", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ p->resp->used = r;
+
+ if (p->resp->ptr[0] != 'O' ||
+ p->resp->ptr[1] != 'K') {
+ p->rrdtool_running = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbb",
+ "rrdtool-response:", p->cmd, p->resp);
+
+ return HANDLER_ERROR;
+ }
+ s->requests = 0;
+ s->bytes_written = 0;
+ s->bytes_read = 0;
+ }
+
+ return HANDLER_GO_ON;
+}
+
+REQUESTDONE_FUNC(mod_rrd_account) {
+ plugin_data *p = p_d;
+
+ mod_rrd_patch_connection(srv, con, p);
+
+ *(p->conf.requests_ptr) += 1;
+ *(p->conf.bytes_written_ptr) += con->bytes_written;
+ *(p->conf.bytes_read_ptr) += con->bytes_read;
+
+ return HANDLER_GO_ON;
+}
+
+int mod_rrdtool_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("rrd");
+
+ p->init = mod_rrd_init;
+ p->cleanup = mod_rrd_free;
+ p->set_defaults= mod_rrd_set_defaults;
+
+ p->handle_trigger = mod_rrd_trigger;
+ p->handle_request_done = mod_rrd_account;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_scgi.c b/src/mod_scgi.c
new file mode 100644
index 0000000..2c1df2f
--- /dev/null
+++ b/src/mod_scgi.c
@@ -0,0 +1,3089 @@
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <signal.h>
+
+#include "buffer.h"
+#include "server.h"
+#include "keyvalue.h"
+#include "log.h"
+
+#include "http_chunk.h"
+#include "fdevent.h"
+#include "connections.h"
+#include "response.h"
+#include "joblist.h"
+
+#include "plugin.h"
+
+#include "inet_ntop_cache.h"
+
+#include <stdio.h>
+
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#include "sys-socket.h"
+
+
+#ifndef UNIX_PATH_MAX
+# define UNIX_PATH_MAX 108
+#endif
+
+#ifdef HAVE_SYS_UIO_H
+#include <sys/uio.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+enum {EOL_UNSET, EOL_N, EOL_RN};
+
+/*
+ *
+ * TODO:
+ *
+ * - add timeout for a connect to a non-scgi process
+ * (use state_timestamp + state)
+ *
+ */
+
+typedef struct scgi_proc {
+ size_t id; /* id will be between 1 and max_procs */
+ buffer *socket; /* config.socket + "-" + id */
+ unsigned port; /* config.port + pno */
+
+ pid_t pid; /* PID of the spawned process (0 if not spawned locally) */
+
+
+ size_t load; /* number of requests waiting on this process */
+
+ time_t last_used; /* see idle_timeout */
+ size_t requests; /* see max_requests */
+ struct scgi_proc *prev, *next; /* see first */
+
+ time_t disable_ts; /* replace by host->something */
+
+ int is_local;
+
+ enum { PROC_STATE_UNSET, /* init-phase */
+ PROC_STATE_RUNNING, /* alive */
+ PROC_STATE_DIED_WAIT_FOR_PID,
+ PROC_STATE_KILLED, /* was killed as we don't have the load anymore */
+ PROC_STATE_DIED, /* marked as dead, should be restarted */
+ PROC_STATE_DISABLED /* proc disabled as it resulted in an error */
+ } state;
+} scgi_proc;
+
+typedef struct {
+ /* list of processes handling this extension
+ * sorted by lowest load
+ *
+ * whenever a job is done move it up in the list
+ * until it is sorted, move it down as soon as the
+ * job is started
+ */
+ scgi_proc *first;
+ scgi_proc *unused_procs;
+
+ /*
+ * spawn at least min_procs, at max_procs.
+ *
+ * as soon as the load of the first entry
+ * is max_load_per_proc we spawn a new one
+ * and add it to the first entry and give it
+ * the load
+ *
+ */
+
+ unsigned short min_procs;
+ unsigned short max_procs;
+ size_t num_procs; /* how many procs are started */
+ size_t active_procs; /* how many of them are really running */
+
+ unsigned short max_load_per_proc;
+
+ /*
+ * kick the process from the list if it was not
+ * used for idle_timeout until min_procs is
+ * reached. this helps to get the processlist
+ * small again we had a small peak load.
+ *
+ */
+
+ unsigned short idle_timeout;
+
+ /*
+ * time after a disabled remote connection is tried to be re-enabled
+ *
+ *
+ */
+
+ unsigned short disable_time;
+
+ /*
+ * same scgi processes get a little bit larger
+ * than wanted. max_requests_per_proc kills a
+ * process after a number of handled requests.
+ *
+ */
+ size_t max_requests_per_proc;
+
+
+ /* config */
+
+ /*
+ * host:port
+ *
+ * if host is one of the local IP adresses the
+ * whole connection is local
+ *
+ * if tcp/ip should be used host AND port have
+ * to be specified
+ *
+ */
+ buffer *host;
+ unsigned short port;
+
+ /*
+ * Unix Domain Socket
+ *
+ * instead of TCP/IP we can use Unix Domain Sockets
+ * - more secure (you have fileperms to play with)
+ * - more control (on locally)
+ * - more speed (no extra overhead)
+ */
+ buffer *unixsocket;
+
+ /* if socket is local we can start the scgi
+ * process ourself
+ *
+ * bin-path is the path to the binary
+ *
+ * check min_procs and max_procs for the number
+ * of process to start-up
+ */
+ buffer *bin_path;
+
+ /* bin-path is set bin-environment is taken to
+ * create the environement before starting the
+ * FastCGI process
+ *
+ */
+ array *bin_env;
+
+ array *bin_env_copy;
+
+ /*
+ * docroot-translation between URL->phys and the
+ * remote host
+ *
+ * reasons:
+ * - different dir-layout if remote
+ * - chroot if local
+ *
+ */
+ buffer *docroot;
+
+ /*
+ * check_local tell you if the phys file is stat()ed
+ * or not. FastCGI doesn't care if the service is
+ * remote. If the web-server side doesn't contain
+ * the scgi-files we should not stat() for them
+ * and say '404 not found'.
+ */
+ unsigned short check_local;
+
+ /*
+ * append PATH_INFO to SCRIPT_FILENAME
+ *
+ * php needs this if cgi.fix_pathinfo is provied
+ *
+ */
+
+ ssize_t load; /* replace by host->load */
+
+ size_t max_id; /* corresponds most of the time to
+ num_procs.
+
+ only if a process is killed max_id waits for the process itself
+ to die and decrements its afterwards */
+} scgi_extension_host;
+
+/*
+ * one extension can have multiple hosts assigned
+ * one host can spawn additional processes on the same
+ * socket (if we control it)
+ *
+ * ext -> host -> procs
+ * 1:n 1:n
+ *
+ * if the scgi process is remote that whole goes down
+ * to
+ *
+ * ext -> host -> procs
+ * 1:n 1:1
+ *
+ * in case of PHP and FCGI_CHILDREN we have again a procs
+ * but we don't control it directly.
+ *
+ */
+
+typedef struct {
+ buffer *key; /* like .php */
+
+ scgi_extension_host **hosts;
+
+ size_t used;
+ size_t size;
+} scgi_extension;
+
+typedef struct {
+ scgi_extension **exts;
+
+ size_t used;
+ size_t size;
+} scgi_exts;
+
+
+typedef struct {
+ scgi_exts *exts;
+
+ int debug;
+} plugin_config;
+
+typedef struct {
+ char **ptr;
+
+ size_t size;
+ size_t used;
+} char_array;
+
+/* generic plugin data, shared between all connections */
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *scgi_env;
+
+ buffer *path;
+ buffer *parse_response;
+
+ plugin_config **config_storage;
+
+ plugin_config conf; /* this is only used as long as no handler_ctx is setup */
+} plugin_data;
+
+/* connection specific data */
+typedef enum { FCGI_STATE_INIT, FCGI_STATE_CONNECT, FCGI_STATE_PREPARE_WRITE,
+ FCGI_STATE_WRITE, FCGI_STATE_READ
+} scgi_connection_state_t;
+
+typedef struct {
+ buffer *response;
+ size_t response_len;
+ int response_type;
+ int response_padding;
+
+ scgi_proc *proc;
+ scgi_extension_host *host;
+
+ scgi_connection_state_t state;
+ time_t state_timestamp;
+
+ int reconnects; /* number of reconnect attempts */
+
+ read_buffer *rb;
+ chunkqueue *wb;
+
+ buffer *response_header;
+
+ int delayed; /* flag to mark that the connect() is delayed */
+
+ size_t request_id;
+ int fd; /* fd to the scgi process */
+ int fde_ndx; /* index into the fd-event buffer */
+
+ pid_t pid;
+ int got_proc;
+
+ plugin_config conf;
+
+ connection *remote_conn; /* dumb pointer */
+ plugin_data *plugin_data; /* dumb pointer */
+} handler_ctx;
+
+
+/* ok, we need a prototype */
+static handler_t scgi_handle_fdevent(void *s, void *ctx, int revents);
+
+int scgi_proclist_sort_down(server *srv, scgi_extension_host *host, scgi_proc *proc);
+
+
+
+static handler_ctx * handler_ctx_init() {
+ handler_ctx * hctx;
+
+ hctx = calloc(1, sizeof(*hctx));
+ assert(hctx);
+
+ hctx->fde_ndx = -1;
+
+ hctx->response = buffer_init();
+ hctx->response_header = buffer_init();
+
+ hctx->request_id = 0;
+ hctx->state = FCGI_STATE_INIT;
+ hctx->proc = NULL;
+
+ hctx->response_len = 0;
+ hctx->response_type = 0;
+ hctx->response_padding = 0;
+ hctx->fd = -1;
+
+ hctx->reconnects = 0;
+
+ hctx->wb = chunkqueue_init();
+
+ return hctx;
+}
+
+static void handler_ctx_free(handler_ctx *hctx) {
+ buffer_free(hctx->response);
+ buffer_free(hctx->response_header);
+
+ chunkqueue_free(hctx->wb);
+
+ if (hctx->rb) {
+ if (hctx->rb->ptr) free(hctx->rb->ptr);
+ free(hctx->rb);
+ }
+
+ free(hctx);
+}
+
+scgi_proc *scgi_process_init() {
+ scgi_proc *f;
+
+ f = calloc(1, sizeof(*f));
+ f->socket = buffer_init();
+
+ f->prev = NULL;
+ f->next = NULL;
+
+ return f;
+}
+
+void scgi_process_free(scgi_proc *f) {
+ if (!f) return;
+
+ scgi_process_free(f->next);
+
+ buffer_free(f->socket);
+
+ free(f);
+}
+
+scgi_extension_host *scgi_host_init() {
+ scgi_extension_host *f;
+
+ f = calloc(1, sizeof(*f));
+
+ f->host = buffer_init();
+ f->unixsocket = buffer_init();
+ f->docroot = buffer_init();
+ f->bin_path = buffer_init();
+ f->bin_env = array_init();
+ f->bin_env_copy = array_init();
+
+ return f;
+}
+
+void scgi_host_free(scgi_extension_host *h) {
+ if (!h) return;
+
+ buffer_free(h->host);
+ buffer_free(h->unixsocket);
+ buffer_free(h->docroot);
+ buffer_free(h->bin_path);
+ array_free(h->bin_env);
+ array_free(h->bin_env_copy);
+
+ scgi_process_free(h->first);
+ scgi_process_free(h->unused_procs);
+
+ free(h);
+
+}
+
+scgi_exts *scgi_extensions_init() {
+ scgi_exts *f;
+
+ f = calloc(1, sizeof(*f));
+
+ return f;
+}
+
+void scgi_extensions_free(scgi_exts *f) {
+ size_t i;
+
+ if (!f) return;
+
+ for (i = 0; i < f->used; i++) {
+ scgi_extension *fe;
+ size_t j;
+
+ fe = f->exts[i];
+
+ for (j = 0; j < fe->used; j++) {
+ scgi_extension_host *h;
+
+ h = fe->hosts[j];
+
+ scgi_host_free(h);
+ }
+
+ buffer_free(fe->key);
+ free(fe->hosts);
+
+ free(fe);
+ }
+
+ free(f->exts);
+
+ free(f);
+}
+
+int scgi_extension_insert(scgi_exts *ext, buffer *key, scgi_extension_host *fh) {
+ scgi_extension *fe;
+ size_t i;
+
+ /* there is something */
+
+ for (i = 0; i < ext->used; i++) {
+ if (buffer_is_equal(key, ext->exts[i]->key)) {
+ break;
+ }
+ }
+
+ if (i == ext->used) {
+ /* filextension is new */
+ fe = calloc(1, sizeof(*fe));
+ assert(fe);
+ fe->key = buffer_init();
+ buffer_copy_string_buffer(fe->key, key);
+
+ /* */
+
+ if (ext->size == 0) {
+ ext->size = 8;
+ ext->exts = malloc(ext->size * sizeof(*(ext->exts)));
+ assert(ext->exts);
+ } else if (ext->used == ext->size) {
+ ext->size += 8;
+ ext->exts = realloc(ext->exts, ext->size * sizeof(*(ext->exts)));
+ assert(ext->exts);
+ }
+ ext->exts[ext->used++] = fe;
+ } else {
+ fe = ext->exts[i];
+ }
+
+ if (fe->size == 0) {
+ fe->size = 4;
+ fe->hosts = malloc(fe->size * sizeof(*(fe->hosts)));
+ assert(fe->hosts);
+ } else if (fe->size == fe->used) {
+ fe->size += 4;
+ fe->hosts = realloc(fe->hosts, fe->size * sizeof(*(fe->hosts)));
+ assert(fe->hosts);
+ }
+
+ fe->hosts[fe->used++] = fh;
+
+ return 0;
+
+}
+
+INIT_FUNC(mod_scgi_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->scgi_env = buffer_init();
+
+ p->path = buffer_init();
+ p->parse_response = buffer_init();
+
+ return p;
+}
+
+
+FREE_FUNC(mod_scgi_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ buffer_free(p->scgi_env);
+ buffer_free(p->path);
+ buffer_free(p->parse_response);
+
+ if (p->config_storage) {
+ size_t i, j, n;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+ scgi_exts *exts;
+
+ if (!s) continue;
+
+ exts = s->exts;
+
+ for (j = 0; j < exts->used; j++) {
+ scgi_extension *ex;
+
+ ex = exts->exts[j];
+
+ for (n = 0; n < ex->used; n++) {
+ scgi_proc *proc;
+ scgi_extension_host *host;
+
+ host = ex->hosts[n];
+
+ for (proc = host->first; proc; proc = proc->next) {
+ if (proc->pid != 0) kill(proc->pid, SIGTERM);
+
+ if (proc->is_local &&
+ !buffer_is_empty(proc->socket)) {
+ unlink(proc->socket->ptr);
+ }
+ }
+
+ for (proc = host->unused_procs; proc; proc = proc->next) {
+ if (proc->pid != 0) kill(proc->pid, SIGTERM);
+
+ if (proc->is_local &&
+ !buffer_is_empty(proc->socket)) {
+ unlink(proc->socket->ptr);
+ }
+ }
+ }
+ }
+
+ scgi_extensions_free(s->exts);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+static int env_add(char_array *env, const char *key, size_t key_len, const char *val, size_t val_len) {
+ char *dst;
+
+ if (!key || !val) return -1;
+
+ dst = malloc(key_len + val_len + 3);
+ memcpy(dst, key, key_len);
+ dst[key_len] = '=';
+ /* add the \0 from the value */
+ memcpy(dst + key_len + 1, val, val_len + 1);
+
+ if (env->size == 0) {
+ env->size = 16;
+ env->ptr = malloc(env->size * sizeof(*env->ptr));
+ } else if (env->size == env->used) {
+ env->size += 16;
+ env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
+ }
+
+ env->ptr[env->used++] = dst;
+
+ return 0;
+}
+
+static int scgi_spawn_connection(server *srv,
+ plugin_data *p,
+ scgi_extension_host *host,
+ scgi_proc *proc) {
+ int scgi_fd;
+ int socket_type, status;
+ struct timeval tv = { 0, 100 * 1000 };
+#ifdef HAVE_SYS_UN_H
+ struct sockaddr_un scgi_addr_un;
+#endif
+ struct sockaddr_in scgi_addr_in;
+ struct sockaddr *scgi_addr;
+
+ socklen_t servlen;
+
+#ifndef HAVE_FORK
+ return -1;
+#endif
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sdb",
+ "new proc, socket:", proc->port, proc->socket);
+ }
+
+ if (!buffer_is_empty(proc->socket)) {
+ memset(&scgi_addr, 0, sizeof(scgi_addr));
+
+#ifdef HAVE_SYS_UN_H
+ scgi_addr_un.sun_family = AF_UNIX;
+ strcpy(scgi_addr_un.sun_path, proc->socket->ptr);
+
+#ifdef SUN_LEN
+ servlen = SUN_LEN(&scgi_addr_un);
+#else
+ /* stevens says: */
+ servlen = proc->socket->used - 1 + sizeof(scgi_addr_un.sun_family);
+#endif
+ socket_type = AF_UNIX;
+ scgi_addr = (struct sockaddr *) &scgi_addr_un;
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: Unix Domain sockets are not supported.");
+ return -1;
+#endif
+ } else {
+ scgi_addr_in.sin_family = AF_INET;
+
+ if (buffer_is_empty(host->host)) {
+ scgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
+ } else {
+ struct hostent *he;
+
+ /* set a usefull default */
+ scgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
+
+
+ if (NULL == (he = gethostbyname(host->host->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "sdb", "gethostbyname failed: ",
+ h_errno, host->host);
+ return -1;
+ }
+
+ if (he->h_addrtype != AF_INET) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "addr-type != AF_INET: ", he->h_addrtype);
+ return -1;
+ }
+
+ if (he->h_length != sizeof(struct in_addr)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "addr-length != sizeof(in_addr): ", he->h_length);
+ return -1;
+ }
+
+ memcpy(&(scgi_addr_in.sin_addr.s_addr), he->h_addr_list[0], he->h_length);
+
+ }
+ scgi_addr_in.sin_port = htons(proc->port);
+ servlen = sizeof(scgi_addr_in);
+
+ socket_type = AF_INET;
+ scgi_addr = (struct sockaddr *) &scgi_addr_in;
+ }
+
+ if (-1 == (scgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "failed:", strerror(errno));
+ return -1;
+ }
+
+ if (-1 == connect(scgi_fd, scgi_addr, servlen)) {
+ /* server is not up, spawn in */
+ pid_t child;
+ int val;
+
+ if (!buffer_is_empty(proc->socket)) {
+ unlink(proc->socket->ptr);
+ }
+
+ close(scgi_fd);
+
+ /* reopen socket */
+ if (-1 == (scgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "socket failed:", strerror(errno));
+ return -1;
+ }
+
+ val = 1;
+ if (setsockopt(scgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "socketsockopt failed:", strerror(errno));
+ return -1;
+ }
+
+ /* create socket */
+ if (-1 == bind(scgi_fd, scgi_addr, servlen)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbds",
+ "bind failed for:",
+ proc->socket,
+ proc->port,
+ strerror(errno));
+ return -1;
+ }
+
+ if (-1 == listen(scgi_fd, 1024)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "listen failed:", strerror(errno));
+ return -1;
+ }
+
+#ifdef HAVE_FORK
+ switch ((child = fork())) {
+ case 0: {
+ buffer *b;
+ size_t i = 0;
+ int fd = 0;
+ char_array env;
+
+
+ /* create environment */
+ env.ptr = NULL;
+ env.size = 0;
+ env.used = 0;
+
+ /* we don't need the client socket */
+ for (fd = 3; fd < 256; fd++) {
+ if (fd != 2 && fd != scgi_fd) close(fd);
+ }
+
+ /* build clean environment */
+ if (host->bin_env_copy->used) {
+ for (i = 0; i < host->bin_env_copy->used; i++) {
+ data_string *ds = (data_string *)host->bin_env_copy->data[i];
+ char *ge;
+
+ if (NULL != (ge = getenv(ds->value->ptr))) {
+ env_add(&env, CONST_BUF_LEN(ds->value), ge, strlen(ge));
+ }
+ }
+ } else {
+ for (i = 0; environ[i]; i++) {
+ char *eq;
+
+ if (NULL != (eq = strchr(environ[i], '='))) {
+ env_add(&env, environ[i], eq - environ[i], eq+1, strlen(eq+1));
+ }
+ }
+ }
+
+ /* create environment */
+ for (i = 0; i < host->bin_env->used; i++) {
+ data_string *ds = (data_string *)host->bin_env->data[i];
+
+ env_add(&env, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value));
+ }
+
+ for (i = 0; i < env.used; i++) {
+ /* search for PHP_FCGI_CHILDREN */
+ if (0 == strncmp(env.ptr[i], "PHP_FCGI_CHILDREN=", sizeof("PHP_FCGI_CHILDREN=") - 1)) break;
+ }
+
+ /* not found, add a default */
+ if (i == env.used) {
+ env_add(&env, CONST_STR_LEN("PHP_FCGI_CHILDREN"), CONST_STR_LEN("1"));
+ }
+
+ env.ptr[env.used] = NULL;
+
+ b = buffer_init();
+ buffer_copy_string(b, "exec ");
+ buffer_append_string_buffer(b, host->bin_path);
+
+ /* exec the cgi */
+ execle("/bin/sh", "sh", "-c", b->ptr, NULL, env.ptr);
+
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "execl failed for:", host->bin_path, strerror(errno));
+
+ exit(errno);
+
+ break;
+ }
+ case -1:
+ /* error */
+ break;
+ default:
+ /* father */
+
+ /* wait */
+ select(0, NULL, NULL, NULL, &tv);
+
+ switch (waitpid(child, &status, WNOHANG)) {
+ case 0:
+ /* child still running after timeout, good */
+ break;
+ case -1:
+ /* no PID found ? should never happen */
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "pid not found:", strerror(errno));
+ return -1;
+ default:
+ /* the child should not terminate at all */
+ if (WIFEXITED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child exited (is this a SCGI binary ?):",
+ WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child signaled:",
+ WTERMSIG(status));
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:",
+ status);
+ }
+ return -1;
+ }
+
+ /* register process */
+ proc->pid = child;
+ proc->last_used = srv->cur_ts;
+ proc->is_local = 1;
+
+ break;
+ }
+#endif
+ } else {
+ proc->is_local = 0;
+ proc->pid = 0;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "(debug) socket is already used, won't spawn:",
+ proc->socket);
+ }
+ }
+
+ proc->state = PROC_STATE_RUNNING;
+ host->active_procs++;
+
+ close(scgi_fd);
+
+ return 0;
+}
+
+
+SETDEFAULTS_FUNC(mod_scgi_set_defaults) {
+ plugin_data *p = p_d;
+ data_unset *du;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "scgi.server", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "scgi.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ array *ca;
+
+ s = malloc(sizeof(plugin_config));
+ s->exts = scgi_extensions_init();
+ s->debug = 0;
+
+ cv[0].destination = s->exts;
+ cv[1].destination = &(s->debug);
+
+ p->config_storage[i] = s;
+ ca = ((data_config *)srv->config_context->data[i])->value;
+
+ if (0 != config_insert_values_global(srv, ca, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ /*
+ * <key> = ( ... )
+ */
+
+ if (NULL != (du = array_get_element(ca, "scgi.server"))) {
+ size_t j;
+ data_array *da = (data_array *)du;
+
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", "scgi.server", "array of strings");
+
+ return HANDLER_ERROR;
+ }
+
+
+ /*
+ * scgi.server = ( "<ext>" => ( ... ),
+ * "<ext>" => ( ... ) )
+ */
+
+ for (j = 0; j < da->value->used; j++) {
+ size_t n;
+ data_array *da_ext = (data_array *)da->value->data[j];
+
+ if (da->value->data[j]->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ", "scgi.server",
+ "[", da->value->data[j]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ /*
+ * da_ext->key == name of the extension
+ */
+
+ /*
+ * scgi.server = ( "<ext>" =>
+ * ( "<host>" => ( ... ),
+ * "<host>" => ( ... )
+ * ),
+ * "<ext>" => ... )
+ */
+
+ for (n = 0; n < da_ext->value->used; n++) {
+ data_array *da_host = (data_array *)da_ext->value->data[n];
+
+ scgi_extension_host *df;
+
+ config_values_t fcv[] = {
+ { "host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "docroot", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "socket", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "bin-path", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+
+ { "check-local", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
+ { "port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
+ { "min-procs-not-working", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 7 this is broken for now */
+ { "max-procs", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
+ { "max-load-per-proc", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
+ { "idle-timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 9 */
+ { "disable-time", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 10 */
+
+ { "bin-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 11 */
+ { "bin-copy-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
+
+
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (da_host->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "ssSBS",
+ "unexpected type for key:",
+ "scgi.server",
+ "[", da_host->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ df = scgi_host_init();
+
+ df->check_local = 1;
+ df->min_procs = 4;
+ df->max_procs = 4;
+ df->max_load_per_proc = 1;
+ df->idle_timeout = 60;
+ df->disable_time = 60;
+
+ fcv[0].destination = df->host;
+ fcv[1].destination = df->docroot;
+ fcv[2].destination = df->unixsocket;
+ fcv[3].destination = df->bin_path;
+
+ fcv[4].destination = &(df->check_local);
+ fcv[5].destination = &(df->port);
+ fcv[6].destination = &(df->min_procs);
+ fcv[7].destination = &(df->max_procs);
+ fcv[8].destination = &(df->max_load_per_proc);
+ fcv[9].destination = &(df->idle_timeout);
+ fcv[10].destination = &(df->disable_time);
+
+ fcv[11].destination = df->bin_env;
+ fcv[12].destination = df->bin_env_copy;
+
+
+ if (0 != config_insert_values_internal(srv, da_host->value, fcv)) {
+ return HANDLER_ERROR;
+ }
+
+ if ((!buffer_is_empty(df->host) || df->port) &&
+ !buffer_is_empty(df->unixsocket)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "either host+port or socket");
+
+ return HANDLER_ERROR;
+ }
+
+ if (!buffer_is_empty(df->unixsocket)) {
+ /* unix domain socket */
+
+ if (df->unixsocket->used > UNIX_PATH_MAX - 2) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "path of the unixdomain socket is too large");
+ return HANDLER_ERROR;
+ }
+ } else {
+ /* tcp/ip */
+
+ if (buffer_is_empty(df->host) &&
+ buffer_is_empty(df->bin_path)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbbbs",
+ "missing key (string):",
+ da->key,
+ da_ext->key,
+ da_host->key,
+ "host");
+
+ return HANDLER_ERROR;
+ } else if (df->port == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sbbbs",
+ "missing key (short):",
+ da->key,
+ da_ext->key,
+ da_host->key,
+ "port");
+ return HANDLER_ERROR;
+ }
+ }
+
+ if (!buffer_is_empty(df->bin_path)) {
+ /* a local socket + self spawning */
+ size_t pno;
+
+ if (df->min_procs > df->max_procs) df->max_procs = df->min_procs;
+ if (df->max_load_per_proc < 1) df->max_load_per_proc = 0;
+
+ if (s->debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbsdsbsdsd",
+ "--- scgi spawning local",
+ "\n\tproc:", df->bin_path,
+ "\n\tport:", df->port,
+ "\n\tsocket", df->unixsocket,
+ "\n\tmin-procs:", df->min_procs,
+ "\n\tmax-procs:", df->max_procs);
+ }
+
+ for (pno = 0; pno < df->min_procs; pno++) {
+ scgi_proc *proc;
+
+ proc = scgi_process_init();
+ proc->id = df->num_procs++;
+ df->max_id++;
+
+ if (buffer_is_empty(df->unixsocket)) {
+ proc->port = df->port + pno;
+ } else {
+ buffer_copy_string_buffer(proc->socket, df->unixsocket);
+ buffer_append_string(proc->socket, "-");
+ buffer_append_long(proc->socket, pno);
+ }
+
+ if (s->debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd",
+ "--- scgi spawning",
+ "\n\tport:", df->port,
+ "\n\tsocket", df->unixsocket,
+ "\n\tcurrent:", pno, "/", df->min_procs);
+ }
+
+ if (scgi_spawn_connection(srv, p, df, proc)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "[ERROR]: spawning fcgi failed.");
+ return HANDLER_ERROR;
+ }
+
+ proc->next = df->first;
+ if (df->first) df->first->prev = proc;
+
+ df->first = proc;
+ }
+ } else {
+ scgi_proc *fp;
+
+ fp = scgi_process_init();
+ fp->id = df->num_procs++;
+ df->max_id++;
+ df->active_procs++;
+ fp->state = PROC_STATE_RUNNING;
+
+ if (buffer_is_empty(df->unixsocket)) {
+ fp->port = df->port;
+ } else {
+ buffer_copy_string_buffer(fp->socket, df->unixsocket);
+ }
+
+ df->first = fp;
+
+ df->min_procs = 1;
+ df->max_procs = 1;
+ }
+
+ /* if extension already exists, take it */
+ scgi_extension_insert(s->exts, da_ext->key, df);
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+static int scgi_set_state(server *srv, handler_ctx *hctx, scgi_connection_state_t state) {
+ hctx->state = state;
+ hctx->state_timestamp = srv->cur_ts;
+
+ return 0;
+}
+
+
+void scgi_connection_cleanup(server *srv, handler_ctx *hctx) {
+ plugin_data *p;
+ connection *con;
+
+ if (NULL == hctx) return;
+
+ p = hctx->plugin_data;
+ con = hctx->remote_conn;
+
+ if (con->mode != p->id) {
+ WP();
+ return;
+ }
+
+ if (hctx->fd != -1) {
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+ close(hctx->fd);
+ srv->cur_fds--;
+ }
+
+ if (hctx->host && hctx->proc) {
+ hctx->host->load--;
+
+ if (hctx->got_proc) {
+ /* after the connect the process gets a load */
+ hctx->proc->load--;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sddb",
+ "release proc:",
+ hctx->fd,
+ hctx->proc->pid, hctx->proc->socket);
+ }
+ }
+
+ scgi_proclist_sort_down(srv, hctx->host, hctx->proc);
+ }
+
+
+ handler_ctx_free(hctx);
+ con->plugin_ctx[p->id] = NULL;
+}
+
+static int scgi_reconnect(server *srv, handler_ctx *hctx) {
+ plugin_data *p = hctx->plugin_data;
+
+ /* child died
+ *
+ * 1.
+ *
+ * connect was ok, connection was accepted
+ * but the php accept loop checks after the accept if it should die or not.
+ *
+ * if yes we can only detect it at a write()
+ *
+ * next step is resetting this attemp and setup a connection again
+ *
+ * if we have more then 5 reconnects for the same request, die
+ *
+ * 2.
+ *
+ * we have a connection but the child died by some other reason
+ *
+ */
+
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+ close(hctx->fd);
+ srv->cur_fds--;
+
+ scgi_set_state(srv, hctx, FCGI_STATE_INIT);
+
+ hctx->request_id = 0;
+ hctx->reconnects++;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sddb",
+ "release proc:",
+ hctx->fd,
+ hctx->proc->pid, hctx->proc->socket);
+ }
+
+ hctx->proc->load--;
+ scgi_proclist_sort_down(srv, hctx->host, hctx->proc);
+
+ return 0;
+}
+
+
+static handler_t scgi_connection_reset(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ scgi_connection_cleanup(srv, con->plugin_ctx[p->id]);
+
+ return HANDLER_GO_ON;
+}
+
+
+static int scgi_env_add(buffer *env, const char *key, size_t key_len, const char *val, size_t val_len) {
+ size_t len;
+
+ if (!key || !val) return -1;
+
+ len = key_len + val_len + 2;
+
+ buffer_prepare_append(env, len);
+
+ /* include the NUL */
+ memcpy(env->ptr + env->used, key, key_len + 1);
+ env->used += key_len + 1;
+ memcpy(env->ptr + env->used, val, val_len + 1);
+ env->used += val_len + 1;
+
+ return 0;
+}
+
+
+/**
+ *
+ * returns
+ * -1 error
+ * 0 connected
+ * 1 not connected yet
+ */
+
+static int scgi_establish_connection(server *srv, handler_ctx *hctx) {
+ struct sockaddr *scgi_addr;
+ struct sockaddr_in scgi_addr_in;
+#ifdef HAVE_SYS_UN_H
+ struct sockaddr_un scgi_addr_un;
+#endif
+ socklen_t servlen;
+
+ scgi_extension_host *host = hctx->host;
+ scgi_proc *proc = hctx->proc;
+ int scgi_fd = hctx->fd;
+
+ memset(&scgi_addr, 0, sizeof(scgi_addr));
+
+ if (!buffer_is_empty(proc->socket)) {
+#ifdef HAVE_SYS_UN_H
+ /* use the unix domain socket */
+ scgi_addr_un.sun_family = AF_UNIX;
+ strcpy(scgi_addr_un.sun_path, proc->socket->ptr);
+#ifdef SUN_LEN
+ servlen = SUN_LEN(&scgi_addr_un);
+#else
+ /* stevens says: */
+ servlen = proc->socket->used - 1 + sizeof(scgi_addr_un.sun_family);
+#endif
+ scgi_addr = (struct sockaddr *) &scgi_addr_un;
+#else
+ return -1;
+#endif
+ } else {
+ scgi_addr_in.sin_family = AF_INET;
+ if (0 == inet_aton(host->host->ptr, &(scgi_addr_in.sin_addr))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "converting IP-adress failed for", host->host,
+ "\nBe sure to specify an IP address here");
+
+ return -1;
+ }
+ scgi_addr_in.sin_port = htons(proc->port);
+ servlen = sizeof(scgi_addr_in);
+
+ scgi_addr = (struct sockaddr *) &scgi_addr_in;
+ }
+
+ if (-1 == connect(scgi_fd, scgi_addr, servlen)) {
+ if (errno == EINPROGRESS ||
+ errno == EALREADY ||
+ errno == EINTR) {
+ if (hctx->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connect delayed, will continue later:", scgi_fd);
+ }
+
+ return 1;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sdsddb",
+ "connect failed:", scgi_fd,
+ strerror(errno), errno,
+ proc->port, proc->socket);
+
+ if (errno == EAGAIN) {
+ /* this is Linux only */
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "If this happend on Linux: You have been run out of local ports. "
+ "Check the manual, section Performance how to handle this.");
+ }
+
+ return -1;
+ }
+ }
+ if (hctx->conf.debug > 1) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connect succeeded: ", scgi_fd);
+ }
+
+
+
+ return 0;
+}
+
+static int scgi_env_add_request_headers(server *srv, connection *con, plugin_data *p) {
+ size_t i;
+
+ for (i = 0; i < con->request.headers->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->request.headers->data[i];
+
+ if (ds->value->used && ds->key->used) {
+ size_t j;
+ buffer_reset(srv->tmp_buf);
+
+ if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
+ BUFFER_COPY_STRING_CONST(srv->tmp_buf, "HTTP_");
+ srv->tmp_buf->used--;
+ }
+
+ buffer_prepare_append(srv->tmp_buf, ds->key->used + 2);
+ for (j = 0; j < ds->key->used - 1; j++) {
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] =
+ light_isalpha(ds->key->ptr[j]) ?
+ ds->key->ptr[j] & ~32 : '_';
+ }
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0';
+
+ scgi_env_add(p->scgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value));
+ }
+ }
+
+ for (i = 0; i < con->environment->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->environment->data[i];
+
+ if (ds->value->used && ds->key->used) {
+ size_t j;
+ buffer_reset(srv->tmp_buf);
+
+ buffer_prepare_append(srv->tmp_buf, ds->key->used + 2);
+ for (j = 0; j < ds->key->used - 1; j++) {
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] =
+ isalpha((unsigned char)ds->key->ptr[j]) ?
+ toupper((unsigned char)ds->key->ptr[j]) : '_';
+ }
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0';
+
+ scgi_env_add(p->scgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value));
+ }
+ }
+
+ return 0;
+}
+
+
+static int scgi_create_env(server *srv, handler_ctx *hctx) {
+ char buf[32];
+ const char *s;
+#ifdef HAVE_IPV6
+ char b2[INET6_ADDRSTRLEN + 1];
+#endif
+ buffer *b;
+
+ plugin_data *p = hctx->plugin_data;
+ scgi_extension_host *host= hctx->host;
+
+ connection *con = hctx->remote_conn;
+ server_socket *srv_sock = con->srv_socket;
+
+ sock_addr our_addr;
+ socklen_t our_addr_len;
+
+ buffer_prepare_copy(p->scgi_env, 1024);
+
+ /* CGI-SPEC 6.1.2, FastCGI spec 6.3 and SCGI spec */
+
+ /* request.content_length < SSIZE_MAX, see request.c */
+ ltostr(buf, con->request.content_length);
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf));
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("SCGI"), CONST_STR_LEN("1"));
+
+
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_NAME"/"PACKAGE_VERSION));
+
+ if (con->server_name->used) {
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_NAME"), CONST_BUF_LEN(con->server_name));
+ } else {
+#ifdef HAVE_IPV6
+ s = inet_ntop(srv_sock->addr.plain.sa_family,
+ srv_sock->addr.plain.sa_family == AF_INET6 ?
+ (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
+ (const void *) &(srv_sock->addr.ipv4.sin_addr),
+ b2, sizeof(b2)-1);
+#else
+ s = inet_ntoa(srv_sock->addr.ipv4.sin_addr);
+#endif
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s));
+ }
+
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1"));
+
+ ltostr(buf,
+#ifdef HAVE_IPV6
+ ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port)
+#else
+ ntohs(srv_sock->addr.ipv4.sin_port)
+#endif
+ );
+
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_PORT"), buf, strlen(buf));
+
+ /* get the server-side of the connection to the client */
+ our_addr_len = sizeof(our_addr);
+
+ if (-1 == getsockname(con->fd, &(our_addr.plain), &our_addr_len)) {
+ s = inet_ntop_cache_get_ip(srv, &(srv_sock->addr));
+ } else {
+ s = inet_ntop_cache_get_ip(srv, &(our_addr));
+ }
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s));
+
+ ltostr(buf,
+#ifdef HAVE_IPV6
+ ntohs(con->dst_addr.plain.sa_family ? con->dst_addr.ipv6.sin6_port : con->dst_addr.ipv4.sin_port)
+#else
+ ntohs(con->dst_addr.ipv4.sin_port)
+#endif
+ );
+
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("REMOTE_PORT"), buf, strlen(buf));
+
+ s = inet_ntop_cache_get_ip(srv, &(con->dst_addr));
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s));
+
+ if (!buffer_is_empty(con->authed_user)) {
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("REMOTE_USER"),
+ CONST_BUF_LEN(con->authed_user));
+ }
+
+
+ /*
+ * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to
+ * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html
+ * (6.1.14, 6.1.6, 6.1.7)
+ */
+
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path));
+
+ if (!buffer_is_empty(con->request.pathinfo)) {
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo));
+
+ /* PATH_TRANSLATED is only defined if PATH_INFO is set */
+
+ if (!buffer_is_empty(host->docroot)) {
+ buffer_copy_string_buffer(p->path, host->docroot);
+ } else {
+ buffer_copy_string_buffer(p->path, con->physical.doc_root);
+ }
+ buffer_append_string_buffer(p->path, con->request.pathinfo);
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("PATH_TRANSLATED"), CONST_BUF_LEN(p->path));
+ } else {
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("PATH_INFO"), CONST_STR_LEN(""));
+ }
+
+ /*
+ * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual
+ * http://www.php.net/manual/en/reserved.variables.php
+ * treatment of PATH_TRANSLATED is different from the one of CGI specs.
+ * TODO: this code should be checked against cgi.fix_pathinfo php
+ * parameter.
+ */
+
+ if (!buffer_is_empty(host->docroot)) {
+ /*
+ * rewrite SCRIPT_FILENAME
+ *
+ */
+
+ buffer_copy_string_buffer(p->path, host->docroot);
+ buffer_append_string_buffer(p->path, con->uri.path);
+
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(p->path));
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(host->docroot));
+ } else {
+ buffer_copy_string_buffer(p->path, con->physical.path);
+
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(p->path));
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root));
+ }
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri));
+ if (!buffer_is_equal(con->request.uri, con->request.orig_uri)) {
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("REDIRECT_URI"), CONST_BUF_LEN(con->request.uri));
+ }
+ if (!buffer_is_empty(con->uri.query)) {
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query));
+ } else {
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_STR_LEN(""));
+ }
+
+ s = get_http_method_name(con->request.http_method);
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s));
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")); /* if php is compiled with --force-redirect */
+ s = get_http_version_name(con->request.http_version);
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s));
+
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ scgi_env_add(p->scgi_env, CONST_STR_LEN("HTTPS"), CONST_STR_LEN("on"));
+ }
+#endif
+
+ scgi_env_add_request_headers(srv, con, p);
+
+ b = chunkqueue_get_append_buffer(hctx->wb);
+
+ buffer_append_long(b, p->scgi_env->used);
+ buffer_append_string_len(b, CONST_STR_LEN(":"));
+ buffer_append_string_len(b, (const char *)p->scgi_env->ptr, p->scgi_env->used);
+ buffer_append_string_len(b, CONST_STR_LEN(","));
+
+ hctx->wb->bytes_in += b->used - 1;
+
+ if (con->request.content_length) {
+ chunkqueue *req_cq = con->request_content_queue;
+ chunk *req_c;
+ off_t offset;
+
+ /* something to send ? */
+ for (offset = 0, req_c = req_cq->first; offset != req_cq->bytes_in; req_c = req_c->next) {
+ off_t weWant = req_cq->bytes_in - offset;
+ off_t weHave = 0;
+
+ /* we announce toWrite octects
+ * now take all the request_content chunk that we need to fill this request
+ * */
+
+ switch (req_c->type) {
+ case FILE_CHUNK:
+ weHave = req_c->file.length - req_c->offset;
+
+ if (weHave > weWant) weHave = weWant;
+
+ chunkqueue_append_file(hctx->wb, req_c->file.name, req_c->offset, weHave);
+
+ req_c->offset += weHave;
+ req_cq->bytes_out += weHave;
+
+ hctx->wb->bytes_in += weHave;
+
+ break;
+ case MEM_CHUNK:
+ /* append to the buffer */
+ weHave = req_c->mem->used - 1 - req_c->offset;
+
+ if (weHave > weWant) weHave = weWant;
+
+ b = chunkqueue_get_append_buffer(hctx->wb);
+ buffer_append_memory(b, req_c->mem->ptr + req_c->offset, weHave);
+ b->used++; /* add virtual \0 */
+
+ req_c->offset += weHave;
+ req_cq->bytes_out += weHave;
+
+ hctx->wb->bytes_in += weHave;
+
+ break;
+ default:
+ break;
+ }
+
+ offset += weHave;
+ }
+ }
+
+#if 0
+ for (i = 0; i < hctx->write_buffer->used; i++) {
+ fprintf(stderr, "%02x ", hctx->write_buffer->ptr[i]);
+ if ((i+1) % 16 == 0) {
+ size_t j;
+ for (j = i-15; j <= i; j++) {
+ fprintf(stderr, "%c",
+ isprint((unsigned char)hctx->write_buffer->ptr[j]) ? hctx->write_buffer->ptr[j] : '.');
+ }
+ fprintf(stderr, "\n");
+ }
+ }
+#endif
+
+ return 0;
+}
+
+static int scgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in, int eol) {
+ char *ns;
+ const char *s;
+ int line = 0;
+
+ UNUSED(srv);
+
+ buffer_copy_string_buffer(p->parse_response, in);
+
+ for (s = p->parse_response->ptr;
+ NULL != (ns = (eol == EOL_RN ? strstr(s, "\r\n") : strchr(s, '\n')));
+ s = ns + (eol == EOL_RN ? 2 : 1), line++) {
+ const char *key, *value;
+ int key_len;
+ data_string *ds;
+
+ ns[0] = '\0';
+
+ if (line == 0 &&
+ 0 == strncmp(s, "HTTP/1.", 7)) {
+ /* non-parsed header ... we parse them anyway */
+
+ if ((s[7] == '1' ||
+ s[7] == '0') &&
+ s[8] == ' ') {
+ int status;
+ /* after the space should be a status code for us */
+
+ status = strtol(s+9, NULL, 10);
+
+ if (con->http_status >= 100 &&
+ con->http_status < 1000) {
+ /* we expected 3 digits and didn't got them */
+ con->parsed_response |= HTTP_STATUS;
+ con->http_status = status;
+ }
+ }
+ } else {
+
+ key = s;
+ if (NULL == (value = strchr(s, ':'))) {
+ /* we expect: "<key>: <value>\r\n" */
+ continue;
+ }
+
+ key_len = value - key;
+ value += 1;
+
+ /* skip LWS */
+ while (*value == ' ' || *value == '\t') value++;
+
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
+ ds = data_response_init();
+ }
+ buffer_copy_string_len(ds->key, key, key_len);
+ buffer_copy_string(ds->value, value);
+
+ array_insert_unique(con->response.headers, (data_unset *)ds);
+
+ switch(key_len) {
+ case 4:
+ if (0 == strncasecmp(key, "Date", key_len)) {
+ con->parsed_response |= HTTP_DATE;
+ }
+ break;
+ case 6:
+ if (0 == strncasecmp(key, "Status", key_len)) {
+ con->http_status = strtol(value, NULL, 10);
+ con->parsed_response |= HTTP_STATUS;
+ }
+ break;
+ case 8:
+ if (0 == strncasecmp(key, "Location", key_len)) {
+ con->parsed_response |= HTTP_LOCATION;
+ }
+ break;
+ case 10:
+ if (0 == strncasecmp(key, "Connection", key_len)) {
+ con->response.keep_alive = (0 == strcasecmp(value, "Keep-Alive")) ? 1 : 0;
+ con->parsed_response |= HTTP_CONNECTION;
+ }
+ break;
+ case 14:
+ if (0 == strncasecmp(key, "Content-Length", key_len)) {
+ con->response.content_length = strtol(value, NULL, 10);
+ con->parsed_response |= HTTP_CONTENT_LENGTH;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /* CGI/1.1 rev 03 - 7.2.1.2 */
+ if ((con->parsed_response & HTTP_LOCATION) &&
+ !(con->parsed_response & HTTP_STATUS)) {
+ con->http_status = 302;
+ }
+
+ return 0;
+}
+
+
+static int scgi_demux_response(server *srv, handler_ctx *hctx) {
+ plugin_data *p = hctx->plugin_data;
+ connection *con = hctx->remote_conn;
+
+ while(1) {
+ int n;
+
+ buffer_prepare_copy(hctx->response, 1024);
+ if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) {
+ if (errno == EAGAIN || errno == EINTR) {
+ /* would block, wait for signal */
+ return 0;
+ }
+ /* error */
+ log_error_write(srv, __FILE__, __LINE__, "sdd", strerror(errno), con->fd, hctx->fd);
+ return -1;
+ }
+
+ if (n == 0) {
+ /* read finished */
+
+ con->file_finished = 1;
+
+ /* send final chunk */
+ http_chunk_append_mem(srv, con, NULL, 0);
+ joblist_append(srv, con);
+
+ return 1;
+ }
+
+ hctx->response->ptr[n] = '\0';
+ hctx->response->used = n+1;
+
+ /* split header from body */
+
+ if (con->file_started == 0) {
+ char *c;
+ int in_header = 0;
+ int header_end = 0;
+ int cp, eol = EOL_UNSET;
+ size_t used = 0;
+
+ buffer_append_string_buffer(hctx->response_header, hctx->response);
+
+ /* nph (non-parsed headers) */
+ if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) in_header = 1;
+
+ /* search for the \r\n\r\n or \n\n in the string */
+ for (c = hctx->response_header->ptr, cp = 0, used = hctx->response_header->used - 1; used; c++, cp++, used--) {
+ if (*c == ':') in_header = 1;
+ else if (*c == '\n') {
+ if (in_header == 0) {
+ /* got a response without a response header */
+
+ c = NULL;
+ header_end = 1;
+ break;
+ }
+
+ if (eol == EOL_UNSET) eol = EOL_N;
+
+ if (*(c+1) == '\n') {
+ header_end = 1;
+ break;
+ }
+
+ } else if (used > 1 && *c == '\r' && *(c+1) == '\n') {
+ if (in_header == 0) {
+ /* got a response without a response header */
+
+ c = NULL;
+ header_end = 1;
+ break;
+ }
+
+ if (eol == EOL_UNSET) eol = EOL_RN;
+
+ if (used > 3 &&
+ *(c+2) == '\r' &&
+ *(c+3) == '\n') {
+ header_end = 1;
+ break;
+ }
+
+ /* skip the \n */
+ c++;
+ cp++;
+ used--;
+ }
+ }
+
+ if (header_end) {
+ if (c == NULL) {
+ /* no header, but a body */
+
+ if (con->request.http_version == HTTP_VERSION_1_1) {
+ con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
+ }
+
+ http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used);
+ joblist_append(srv, con);
+ } else {
+ size_t hlen = c - hctx->response_header->ptr + (eol == EOL_RN ? 4 : 2);
+ size_t blen = hctx->response_header->used - hlen - 1;
+
+ /* a small hack: terminate after at the second \r */
+ hctx->response_header->used = hlen + 1 - (eol == EOL_RN ? 2 : 1);
+ hctx->response_header->ptr[hlen - (eol == EOL_RN ? 2 : 1)] = '\0';
+
+ /* parse the response header */
+ scgi_response_parse(srv, con, p, hctx->response_header, eol);
+
+ /* enable chunked-transfer-encoding */
+ if (con->request.http_version == HTTP_VERSION_1_1 &&
+ !(con->parsed_response & HTTP_CONTENT_LENGTH)) {
+ con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
+ }
+
+ if ((hctx->response->used != hlen) && blen > 0) {
+ http_chunk_append_mem(srv, con, c + (eol == EOL_RN ? 4: 2), blen + 1);
+ joblist_append(srv, con);
+ }
+ }
+
+ con->file_started = 1;
+ }
+ } else {
+ http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used);
+ joblist_append(srv, con);
+ }
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), b->ptr);
+#endif
+ }
+
+ return 0;
+}
+
+
+int scgi_proclist_sort_up(server *srv, scgi_extension_host *host, scgi_proc *proc) {
+ scgi_proc *p;
+
+ UNUSED(srv);
+
+ /* we have been the smallest of the current list
+ * and we want to insert the node sorted as soon
+ * possible
+ *
+ * 1 0 0 0 1 1 1
+ * | ^
+ * | |
+ * +------+
+ *
+ */
+
+ /* nothing to sort, only one element */
+ if (host->first == proc && proc->next == NULL) return 0;
+
+ for (p = proc; p->next && p->next->load < proc->load; p = p->next);
+
+ /* no need to move something
+ *
+ * 1 2 2 2 3 3 3
+ * ^
+ * |
+ * +
+ *
+ */
+ if (p == proc) return 0;
+
+ if (host->first == proc) {
+ /* we have been the first elememt */
+
+ host->first = proc->next;
+ host->first->prev = NULL;
+ }
+
+ /* disconnect proc */
+
+ if (proc->prev) proc->prev->next = proc->next;
+ if (proc->next) proc->next->prev = proc->prev;
+
+ /* proc should be right of p */
+
+ proc->next = p->next;
+ proc->prev = p;
+ if (p->next) p->next->prev = proc;
+ p->next = proc;
+#if 0
+ for(p = host->first; p; p = p->next) {
+ log_error_write(srv, __FILE__, __LINE__, "dd",
+ p->pid, p->load);
+ }
+#else
+ UNUSED(srv);
+#endif
+
+ return 0;
+}
+
+int scgi_proclist_sort_down(server *srv, scgi_extension_host *host, scgi_proc *proc) {
+ scgi_proc *p;
+
+ UNUSED(srv);
+
+ /* we have been the smallest of the current list
+ * and we want to insert the node sorted as soon
+ * possible
+ *
+ * 0 0 0 0 1 0 1
+ * ^ |
+ * | |
+ * +----------+
+ *
+ *
+ * the basic is idea is:
+ * - the last active scgi process should be still
+ * in ram and is not swapped out yet
+ * - processes that are not reused will be killed
+ * after some time by the trigger-handler
+ * - remember it as:
+ * everything > 0 is hot
+ * all unused procs are colder the more right they are
+ * ice-cold processes are propably unused since more
+ * than 'unused-timeout', are swaped out and won't be
+ * reused in the next seconds anyway.
+ *
+ */
+
+ /* nothing to sort, only one element */
+ if (host->first == proc && proc->next == NULL) return 0;
+
+ for (p = host->first; p != proc && p->load < proc->load; p = p->next);
+
+
+ /* no need to move something
+ *
+ * 1 2 2 2 3 3 3
+ * ^
+ * |
+ * +
+ *
+ */
+ if (p == proc) return 0;
+
+ /* we have to move left. If we are already the first element
+ * we are done */
+ if (host->first == proc) return 0;
+
+ /* release proc */
+ if (proc->prev) proc->prev->next = proc->next;
+ if (proc->next) proc->next->prev = proc->prev;
+
+ /* proc should be left of p */
+ proc->next = p;
+ proc->prev = p->prev;
+ if (p->prev) p->prev->next = proc;
+ p->prev = proc;
+
+ if (proc->prev == NULL) host->first = proc;
+#if 0
+ for(p = host->first; p; p = p->next) {
+ log_error_write(srv, __FILE__, __LINE__, "dd",
+ p->pid, p->load);
+ }
+#else
+ UNUSED(srv);
+#endif
+
+ return 0;
+}
+
+static int scgi_restart_dead_procs(server *srv, plugin_data *p, scgi_extension_host *host) {
+ scgi_proc *proc;
+
+ for (proc = host->first; proc; proc = proc->next) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sbdbdddd",
+ "proc:",
+ host->host, proc->port,
+ proc->socket,
+ proc->state,
+ proc->is_local,
+ proc->load,
+ proc->pid);
+ }
+
+ if (0 == proc->is_local) {
+ /*
+ * external servers might get disabled
+ *
+ * enable the server again, perhaps it is back again
+ */
+
+ if ((proc->state == PROC_STATE_DISABLED) &&
+ (srv->cur_ts - proc->disable_ts > host->disable_time)) {
+ proc->state = PROC_STATE_RUNNING;
+ host->active_procs++;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbdb",
+ "fcgi-server re-enabled:",
+ host->host, host->port,
+ host->unixsocket);
+ }
+ } else {
+ /* the child should not terminate at all */
+ int status;
+
+ if (proc->state == PROC_STATE_DIED_WAIT_FOR_PID) {
+ switch(waitpid(proc->pid, &status, WNOHANG)) {
+ case 0:
+ /* child is still alive */
+ break;
+ case -1:
+ break;
+ default:
+ if (WIFEXITED(status)) {
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdsd",
+ "child exited, pid:", proc->pid,
+ "status:", WEXITSTATUS(status));
+#endif
+ } else if (WIFSIGNALED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child signaled:",
+ WTERMSIG(status));
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:",
+ status);
+ }
+
+ proc->state = PROC_STATE_DIED;
+ break;
+ }
+ }
+
+ /*
+ * local servers might died, but we restart them
+ *
+ */
+ if (proc->state == PROC_STATE_DIED &&
+ proc->load == 0) {
+ /* restart the child */
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd",
+ "--- scgi spawning",
+ "\n\tport:", host->port,
+ "\n\tsocket", host->unixsocket,
+ "\n\tcurrent:", 1, "/", host->min_procs);
+ }
+
+ if (scgi_spawn_connection(srv, p, host, proc)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: spawning fcgi failed.");
+ return HANDLER_ERROR;
+ }
+
+ scgi_proclist_sort_down(srv, host, proc);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+static handler_t scgi_write_request(server *srv, handler_ctx *hctx) {
+ plugin_data *p = hctx->plugin_data;
+ scgi_extension_host *host= hctx->host;
+ connection *con = hctx->remote_conn;
+
+ int ret;
+
+ /* sanity check */
+ if (!host ||
+ ((!host->host->used || !host->port) && !host->unixsocket->used)) {
+ log_error_write(srv, __FILE__, __LINE__, "sxddd",
+ "write-req: error",
+ host,
+ host->host->used,
+ host->port,
+ host->unixsocket->used);
+ return HANDLER_ERROR;
+ }
+
+
+ switch(hctx->state) {
+ case FCGI_STATE_INIT:
+ ret = host->unixsocket->used ? AF_UNIX : AF_INET;
+
+ if (-1 == (hctx->fd = socket(ret, SOCK_STREAM, 0))) {
+ if (errno == EMFILE ||
+ errno == EINTR) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "wait for fd at connection:", con->fd);
+
+ return HANDLER_WAIT_FOR_FD;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdd",
+ "socket failed:", strerror(errno), srv->cur_fds, srv->max_fds);
+ return HANDLER_ERROR;
+ }
+ hctx->fde_ndx = -1;
+
+ srv->cur_fds++;
+
+ fdevent_register(srv->ev, hctx->fd, scgi_handle_fdevent, hctx);
+
+ if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "fcntl failed: ", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ /* fall through */
+ case FCGI_STATE_CONNECT:
+ if (hctx->state == FCGI_STATE_INIT) {
+ for (hctx->proc = hctx->host->first;
+ hctx->proc && hctx->proc->state != PROC_STATE_RUNNING;
+ hctx->proc = hctx->proc->next);
+
+ /* all childs are dead */
+ if (hctx->proc == NULL) {
+ hctx->fde_ndx = -1;
+
+ return HANDLER_ERROR;
+ }
+
+ if (hctx->proc->is_local) {
+ hctx->pid = hctx->proc->pid;
+ }
+
+ switch (scgi_establish_connection(srv, hctx)) {
+ case 1:
+ scgi_set_state(srv, hctx, FCGI_STATE_CONNECT);
+
+ /* connection is in progress, wait for an event and call getsockopt() below */
+
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ return HANDLER_WAIT_FOR_EVENT;
+ case -1:
+ /* if ECONNREFUSED choose another connection -> FIXME */
+ hctx->fde_ndx = -1;
+
+ return HANDLER_ERROR;
+ default:
+ /* everything is ok, go on */
+ break;
+ }
+
+
+ } else {
+ int socket_error;
+ socklen_t socket_error_len = sizeof(socket_error);
+
+ /* try to finish the connect() */
+ if (0 != getsockopt(hctx->fd, SOL_SOCKET, SO_ERROR, &socket_error, &socket_error_len)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "getsockopt failed:", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+ if (socket_error != 0) {
+ if (!hctx->proc->is_local || p->conf.debug) {
+ /* local procs get restarted */
+
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "establishing connection failed:", strerror(socket_error),
+ "port:", hctx->proc->port);
+ }
+
+ return HANDLER_ERROR;
+ }
+ }
+
+ /* ok, we have the connection */
+
+ hctx->proc->load++;
+ hctx->proc->last_used = srv->cur_ts;
+ hctx->got_proc = 1;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sddbdd",
+ "got proc:",
+ hctx->fd,
+ hctx->proc->pid,
+ hctx->proc->socket,
+ hctx->proc->port,
+ hctx->proc->load);
+ }
+
+ /* move the proc-list entry down the list */
+ scgi_proclist_sort_up(srv, hctx->host, hctx->proc);
+
+ scgi_set_state(srv, hctx, FCGI_STATE_PREPARE_WRITE);
+ /* fall through */
+ case FCGI_STATE_PREPARE_WRITE:
+ scgi_create_env(srv, hctx);
+
+ scgi_set_state(srv, hctx, FCGI_STATE_WRITE);
+
+ /* fall through */
+ case FCGI_STATE_WRITE:
+ ret = srv->network_backend_write(srv, con, hctx->fd, hctx->wb);
+
+ chunkqueue_remove_finished_chunks(hctx->wb);
+
+ if (-1 == ret) {
+ if (errno == ENOTCONN) {
+ /* the connection got dropped after accept()
+ *
+ * this is most of the time a PHP which dies
+ * after PHP_FCGI_MAX_REQUESTS
+ *
+ */
+ if (hctx->wb->bytes_out == 0 &&
+ hctx->reconnects < 5) {
+ usleep(10000); /* take away the load of the webserver
+ * to let the php a chance to restart
+ */
+
+ scgi_reconnect(srv, hctx);
+
+ return HANDLER_WAIT_FOR_FD;
+ }
+
+ /* not reconnected ... why
+ *
+ * far@#lighttpd report this for FreeBSD
+ *
+ */
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdsd",
+ "[REPORT ME] connection was dropped after accept(). reconnect() denied:",
+ "write-offset:", hctx->wb->bytes_out,
+ "reconnect attempts:", hctx->reconnects);
+
+ return HANDLER_ERROR;
+ }
+
+ if ((errno != EAGAIN) &&
+ (errno != EINTR)) {
+
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "write failed:", strerror(errno), errno);
+
+ return HANDLER_ERROR;
+ } else {
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+ }
+
+ if (hctx->wb->bytes_out == hctx->wb->bytes_in) {
+ /* we don't need the out event anymore */
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+ scgi_set_state(srv, hctx, FCGI_STATE_READ);
+ } else {
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+
+ break;
+ case FCGI_STATE_READ:
+ /* waiting for a response */
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state");
+ return HANDLER_ERROR;
+ }
+
+ return HANDLER_WAIT_FOR_EVENT;
+}
+
+SUBREQUEST_FUNC(mod_scgi_handle_subrequest) {
+ plugin_data *p = p_d;
+
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+ scgi_proc *proc;
+ scgi_extension_host *host;
+
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+ /* not my job */
+ if (con->mode != p->id) return HANDLER_GO_ON;
+
+ /* ok, create the request */
+ switch(scgi_write_request(srv, hctx)) {
+ case HANDLER_ERROR:
+ proc = hctx->proc;
+ host = hctx->host;
+
+ if (proc &&
+ 0 == proc->is_local &&
+ proc->state != PROC_STATE_DISABLED) {
+ /* only disable remote servers as we don't manage them*/
+
+ log_error_write(srv, __FILE__, __LINE__, "sbdb", "fcgi-server disabled:",
+ host->host,
+ proc->port,
+ proc->socket);
+
+ /* disable this server */
+ proc->disable_ts = srv->cur_ts;
+ proc->state = PROC_STATE_DISABLED;
+ host->active_procs--;
+ }
+
+ if (hctx->state == FCGI_STATE_INIT ||
+ hctx->state == FCGI_STATE_CONNECT) {
+ /* connect() or getsockopt() failed,
+ * restart the request-handling
+ */
+ if (proc && proc->is_local) {
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sbdb", "connect() to scgi failed, restarting the request-handling:",
+ host->host,
+ proc->port,
+ proc->socket);
+ }
+
+ /*
+ * several hctx might reference the same proc
+ *
+ * Only one of them should mark the proc as dead all the other
+ * ones should just take a new one.
+ *
+ * If a new proc was started with the old struct this might lead
+ * the mark a perfect proc as dead otherwise
+ *
+ */
+ if (proc->state == PROC_STATE_RUNNING &&
+ hctx->pid == proc->pid) {
+ proc->state = PROC_STATE_DIED_WAIT_FOR_PID;
+ }
+ }
+ scgi_restart_dead_procs(srv, p, host);
+
+ scgi_connection_cleanup(srv, hctx);
+
+ buffer_reset(con->physical.path);
+ con->mode = DIRECT;
+ joblist_append(srv, con);
+
+ /* mis-using HANDLER_WAIT_FOR_FD to break out of the loop
+ * and hope that the childs will be restarted
+ *
+ */
+ return HANDLER_WAIT_FOR_FD;
+ } else {
+ scgi_connection_cleanup(srv, hctx);
+
+ buffer_reset(con->physical.path);
+ con->mode = DIRECT;
+ con->http_status = 503;
+
+ return HANDLER_FINISHED;
+ }
+ case HANDLER_WAIT_FOR_EVENT:
+ if (con->file_started == 1) {
+ return HANDLER_FINISHED;
+ } else {
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+ case HANDLER_WAIT_FOR_FD:
+ return HANDLER_WAIT_FOR_FD;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "s", "subrequest write-req default");
+ return HANDLER_ERROR;
+ }
+}
+
+static handler_t scgi_connection_close(server *srv, handler_ctx *hctx) {
+ plugin_data *p;
+ connection *con;
+
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+ p = hctx->plugin_data;
+ con = hctx->remote_conn;
+
+ if (con->mode != p->id) return HANDLER_GO_ON;
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdsd",
+ "emergency exit: scgi:",
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+
+
+ scgi_connection_cleanup(srv, hctx);
+
+ return HANDLER_FINISHED;
+}
+
+
+static handler_t scgi_handle_fdevent(void *s, void *ctx, int revents) {
+ server *srv = (server *)s;
+ handler_ctx *hctx = ctx;
+ connection *con = hctx->remote_conn;
+ plugin_data *p = hctx->plugin_data;
+
+ scgi_proc *proc = hctx->proc;
+ scgi_extension_host *host= hctx->host;
+
+ if ((revents & FDEVENT_IN) &&
+ hctx->state == FCGI_STATE_READ) {
+ switch (scgi_demux_response(srv, hctx)) {
+ case 0:
+ break;
+ case 1:
+ /* we are done */
+ scgi_connection_cleanup(srv, hctx);
+
+ joblist_append(srv, con);
+ return HANDLER_FINISHED;
+ case -1:
+ if (proc->pid && proc->state != PROC_STATE_DIED) {
+ int status;
+
+ /* only fetch the zombie if it is not already done */
+
+ switch(waitpid(proc->pid, &status, WNOHANG)) {
+ case 0:
+ /* child is still alive */
+ break;
+ case -1:
+ break;
+ default:
+ /* the child should not terminate at all */
+ if (WIFEXITED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sdsd",
+ "child exited, pid:", proc->pid,
+ "status:", WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child signaled:",
+ WTERMSIG(status));
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:",
+ status);
+ }
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd",
+ "--- scgi spawning",
+ "\n\tport:", host->port,
+ "\n\tsocket", host->unixsocket,
+ "\n\tcurrent:", 1, "/", host->min_procs);
+ }
+
+ if (scgi_spawn_connection(srv, p, host, proc)) {
+ /* child died */
+ proc->state = PROC_STATE_DIED;
+ } else {
+ scgi_proclist_sort_down(srv, host, proc);
+ }
+
+ break;
+ }
+ }
+
+ if (con->file_started == 0) {
+ /* nothing has been send out yet, try to use another child */
+
+ if (hctx->wb->bytes_out == 0 &&
+ hctx->reconnects < 5) {
+ scgi_reconnect(srv, hctx);
+
+ log_error_write(srv, __FILE__, __LINE__, "sdsdsd",
+ "response not sent, request not sent, reconnection.",
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+ return HANDLER_WAIT_FOR_FD;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "sdsdsd",
+ "response not sent, request sent:", hctx->wb->bytes_out,
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+ scgi_connection_cleanup(srv, hctx);
+
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ buffer_reset(con->physical.path);
+ con->http_status = 500;
+ con->mode = DIRECT;
+ } else {
+ /* response might have been already started, kill the connection */
+ scgi_connection_cleanup(srv, hctx);
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdsd",
+ "response already sent out, termination connection",
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ }
+
+ /* */
+
+
+ joblist_append(srv, con);
+ return HANDLER_FINISHED;
+ }
+ }
+
+ if (revents & FDEVENT_OUT) {
+ if (hctx->state == FCGI_STATE_CONNECT ||
+ hctx->state == FCGI_STATE_WRITE) {
+ /* we are allowed to send something out
+ *
+ * 1. in a unfinished connect() call
+ * 2. in a unfinished write() call (long POST request)
+ */
+ return mod_scgi_handle_subrequest(srv, con, p);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "got a FDEVENT_OUT and didn't know why:",
+ hctx->state);
+ }
+ }
+
+ /* perhaps this issue is already handled */
+ if (revents & FDEVENT_HUP) {
+ if (hctx->state == FCGI_STATE_CONNECT) {
+ /* getoptsock will catch this one (right ?)
+ *
+ * if we are in connect we might get a EINPROGRESS
+ * in the first call and a FDEVENT_HUP in the
+ * second round
+ *
+ * FIXME: as it is a bit ugly.
+ *
+ */
+ return mod_scgi_handle_subrequest(srv, con, p);
+ } else if (hctx->state == FCGI_STATE_READ &&
+ hctx->proc->port == 0) {
+ /* FIXME:
+ *
+ * ioctl says 8192 bytes to read from PHP and we receive directly a HUP for the socket
+ * even if the FCGI_FIN packet is not received yet
+ */
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbSBSDSd",
+ "error: unexpected close of scgi connection for",
+ con->uri.path,
+ "(no scgi process on host: ",
+ host->host,
+ ", port: ",
+ host->port,
+ " ?)",
+ hctx->state);
+
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ scgi_connection_close(srv, hctx);
+ joblist_append(srv, con);
+ }
+ } else if (revents & FDEVENT_ERR) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "fcgi: got a FDEVENT_ERR. Don't know why.");
+ /* kill all connections to the scgi process */
+
+
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ scgi_connection_close(srv, hctx);
+ joblist_append(srv, con);
+ }
+
+ return HANDLER_FINISHED;
+}
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int scgi_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(exts);
+ PATCH(debug);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("scgi.server"))) {
+ PATCH(exts);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("scgi.debug"))) {
+ PATCH(debug);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+
+static handler_t scgi_check_extension(server *srv, connection *con, void *p_d, int uri_path_handler) {
+ plugin_data *p = p_d;
+ size_t s_len;
+ int used = -1;
+ int ndx;
+ size_t k;
+ buffer *fn;
+ scgi_extension *extension = NULL;
+
+ /* Possibly, we processed already this request */
+ if (con->file_started == 1) return HANDLER_GO_ON;
+
+ fn = uri_path_handler ? con->uri.path : con->physical.path;
+
+ if (buffer_is_empty(fn)) return HANDLER_GO_ON;
+
+ s_len = fn->used - 1;
+
+ scgi_patch_connection(srv, con, p);
+
+ /* check if extension matches */
+ for (k = 0; k < p->conf.exts->used; k++) {
+ size_t ct_len;
+
+ extension = p->conf.exts->exts[k];
+
+ if (extension->key->used == 0) continue;
+
+ ct_len = extension->key->used - 1;
+
+ if (s_len < ct_len) continue;
+
+ /* check extension in the form "/scgi_pattern" */
+ if (*(extension->key->ptr) == '/' && strncmp(fn->ptr, extension->key->ptr, ct_len) == 0) {
+ break;
+ } else if (0 == strncmp(fn->ptr + s_len - ct_len, extension->key->ptr, ct_len)) {
+ /* check extension in the form ".fcg" */
+ break;
+ }
+ }
+
+ /* extension doesn't match */
+ if (k == p->conf.exts->used) {
+ return HANDLER_GO_ON;
+ }
+
+ /* get best server */
+ for (k = 0, ndx = -1; k < extension->used; k++) {
+ scgi_extension_host *host = extension->hosts[k];
+
+ /* we should have at least one proc that can do somthing */
+ if (host->active_procs == 0) continue;
+
+ if (used == -1 || host->load < used) {
+ used = host->load;
+
+ ndx = k;
+ }
+ }
+
+ /* found a server */
+ if (ndx != -1) {
+ scgi_extension_host *host = extension->hosts[ndx];
+
+ /*
+ * if check-local is disabled, use the uri.path handler
+ *
+ */
+
+ /* init handler-context */
+ if (uri_path_handler) {
+ if (host->check_local == 0) {
+ handler_ctx *hctx;
+ char *pathinfo;
+
+ hctx = handler_ctx_init();
+
+ hctx->remote_conn = con;
+ hctx->plugin_data = p;
+ hctx->host = host;
+ hctx->proc = NULL;
+
+ hctx->conf.exts = p->conf.exts;
+ hctx->conf.debug = p->conf.debug;
+
+ con->plugin_ctx[p->id] = hctx;
+
+ host->load++;
+
+ con->mode = p->id;
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "handling it in mod_scgi");
+ }
+
+ /* the prefix is the SCRIPT_NAME,
+ * everthing from start to the next slash
+ * this is important for check-local = "disable"
+ *
+ * if prefix = /admin.fcgi
+ *
+ * /admin.fcgi/foo/bar
+ *
+ * SCRIPT_NAME = /admin.fcgi
+ * PATH_INFO = /foo/bar
+ *
+ * if prefix = /fcgi-bin/
+ *
+ * /fcgi-bin/foo/bar
+ *
+ * SCRIPT_NAME = /fcgi-bin/foo
+ * PATH_INFO = /bar
+ *
+ */
+
+ /* the rewrite is only done for /prefix/? matches */
+ if (extension->key->ptr[0] == '/' &&
+ con->uri.path->used > extension->key->used &&
+ NULL != (pathinfo = strchr(con->uri.path->ptr + extension->key->used - 1, '/'))) {
+ /* rewrite uri.path and pathinfo */
+
+ buffer_copy_string(con->request.pathinfo, pathinfo);
+
+ con->uri.path->used -= con->request.pathinfo->used - 1;
+ con->uri.path->ptr[con->uri.path->used - 1] = '\0';
+ }
+ }
+ return HANDLER_GO_ON;
+ } else {
+ handler_ctx *hctx;
+ hctx = handler_ctx_init();
+
+ hctx->remote_conn = con;
+ hctx->plugin_data = p;
+ hctx->host = host;
+ hctx->proc = NULL;
+
+ hctx->conf.exts = p->conf.exts;
+ hctx->conf.debug = p->conf.debug;
+
+ con->plugin_ctx[p->id] = hctx;
+
+ host->load++;
+
+ con->mode = p->id;
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "handling it in mod_fastcgi");
+ }
+
+ return HANDLER_GO_ON;
+ }
+ } else {
+ /* no handler found */
+ buffer_reset(con->physical.path);
+ con->http_status = 500;
+
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "no fcgi-handler found for:",
+ fn);
+
+ return HANDLER_FINISHED;
+ }
+ return HANDLER_GO_ON;
+}
+
+/* uri-path handler */
+static handler_t scgi_check_extension_1(server *srv, connection *con, void *p_d) {
+ return scgi_check_extension(srv, con, p_d, 1);
+}
+
+/* start request handler */
+static handler_t scgi_check_extension_2(server *srv, connection *con, void *p_d) {
+ return scgi_check_extension(srv, con, p_d, 0);
+}
+
+JOBLIST_FUNC(mod_scgi_handle_joblist) {
+ plugin_data *p = p_d;
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+
+ if (hctx == NULL) return HANDLER_GO_ON;
+
+ if (hctx->fd != -1) {
+ switch (hctx->state) {
+ case FCGI_STATE_READ:
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+
+ break;
+ case FCGI_STATE_CONNECT:
+ case FCGI_STATE_WRITE:
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ break;
+ case FCGI_STATE_INIT:
+ /* at reconnect */
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "unhandled fcgi.state", hctx->state);
+ break;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+static handler_t scgi_connection_close_callback(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ return scgi_connection_close(srv, con->plugin_ctx[p->id]);
+}
+
+TRIGGER_FUNC(mod_scgi_handle_trigger) {
+ plugin_data *p = p_d;
+ size_t i, j, n;
+
+
+ /* perhaps we should kill a connect attempt after 10-15 seconds
+ *
+ * currently we wait for the TCP timeout which is on Linux 180 seconds
+ *
+ *
+ *
+ */
+
+ /* check all childs if they are still up */
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *conf;
+ scgi_exts *exts;
+
+ conf = p->config_storage[i];
+
+ exts = conf->exts;
+
+ for (j = 0; j < exts->used; j++) {
+ scgi_extension *ex;
+
+ ex = exts->exts[j];
+
+ for (n = 0; n < ex->used; n++) {
+
+ scgi_proc *proc;
+ unsigned long sum_load = 0;
+ scgi_extension_host *host;
+
+ host = ex->hosts[n];
+
+ scgi_restart_dead_procs(srv, p, host);
+
+ for (proc = host->first; proc; proc = proc->next) {
+ sum_load += proc->load;
+ }
+
+ if (host->num_procs &&
+ host->num_procs < host->max_procs &&
+ (sum_load / host->num_procs) > host->max_load_per_proc) {
+ /* overload, spawn new child */
+ scgi_proc *fp = NULL;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "overload detected, spawning a new child");
+ }
+
+ for (fp = host->unused_procs; fp && fp->pid != 0; fp = fp->next);
+
+ if (fp) {
+ if (fp == host->unused_procs) host->unused_procs = fp->next;
+
+ if (fp->next) fp->next->prev = NULL;
+
+ host->max_id++;
+ } else {
+ fp = scgi_process_init();
+ fp->id = host->max_id++;
+ }
+
+ host->num_procs++;
+
+ if (buffer_is_empty(host->unixsocket)) {
+ fp->port = host->port + fp->id;
+ } else {
+ buffer_copy_string_buffer(fp->socket, host->unixsocket);
+ buffer_append_string(fp->socket, "-");
+ buffer_append_long(fp->socket, fp->id);
+ }
+
+ if (scgi_spawn_connection(srv, p, host, fp)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: spawning fcgi failed.");
+ return HANDLER_ERROR;
+ }
+
+ fp->prev = NULL;
+ fp->next = host->first;
+ if (host->first) {
+ host->first->prev = fp;
+ }
+ host->first = fp;
+ }
+
+ for (proc = host->first; proc; proc = proc->next) {
+ if (proc->load != 0) break;
+ if (host->num_procs <= host->min_procs) break;
+ if (proc->pid == 0) continue;
+
+ if (srv->cur_ts - proc->last_used > host->idle_timeout) {
+ /* a proc is idling for a long time now,
+ * terminated it */
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbsd",
+ "idle-timeout reached, terminating child:",
+ "socket:", proc->socket,
+ "pid", proc->pid);
+ }
+
+
+ if (proc->next) proc->next->prev = proc->prev;
+ if (proc->prev) proc->prev->next = proc->next;
+
+ if (proc->prev == NULL) host->first = proc->next;
+
+ proc->prev = NULL;
+ proc->next = host->unused_procs;
+
+ if (host->unused_procs) host->unused_procs->prev = proc;
+ host->unused_procs = proc;
+
+ kill(proc->pid, SIGTERM);
+
+ proc->state = PROC_STATE_KILLED;
+
+ log_error_write(srv, __FILE__, __LINE__, "ssbsd",
+ "killed:",
+ "socket:", proc->socket,
+ "pid", proc->pid);
+
+ host->num_procs--;
+
+ /* proc is now in unused, let the next second handle the next process */
+ break;
+ }
+ }
+
+ for (proc = host->unused_procs; proc; proc = proc->next) {
+ int status;
+
+ if (proc->pid == 0) continue;
+
+ switch (waitpid(proc->pid, &status, WNOHANG)) {
+ case 0:
+ /* child still running after timeout, good */
+ break;
+ case -1:
+ if (errno != EINTR) {
+ /* no PID found ? should never happen */
+ log_error_write(srv, __FILE__, __LINE__, "sddss",
+ "pid ", proc->pid, proc->state,
+ "not found:", strerror(errno));
+
+#if 0
+ if (errno == ECHILD) {
+ /* someone else has cleaned up for us */
+ proc->pid = 0;
+ proc->state = PROC_STATE_UNSET;
+ }
+#endif
+ }
+ break;
+ default:
+ /* the child should not terminate at all */
+ if (WIFEXITED(status)) {
+ if (proc->state != PROC_STATE_KILLED) {
+ log_error_write(srv, __FILE__, __LINE__, "sdb",
+ "child exited:",
+ WEXITSTATUS(status), proc->socket);
+ }
+ } else if (WIFSIGNALED(status)) {
+ if (WTERMSIG(status) != SIGTERM) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child signaled:",
+ WTERMSIG(status));
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:",
+ status);
+ }
+ proc->pid = 0;
+ proc->state = PROC_STATE_UNSET;
+ host->max_id--;
+ }
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+int mod_scgi_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("scgi");
+
+ p->init = mod_scgi_init;
+ p->cleanup = mod_scgi_free;
+ p->set_defaults = mod_scgi_set_defaults;
+ p->connection_reset = scgi_connection_reset;
+ p->handle_connection_close = scgi_connection_close_callback;
+ p->handle_uri_clean = scgi_check_extension_1;
+ p->handle_subrequest_start = scgi_check_extension_2;
+ p->handle_subrequest = mod_scgi_handle_subrequest;
+ p->handle_joblist = mod_scgi_handle_joblist;
+ p->handle_trigger = mod_scgi_handle_trigger;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_secure_download.c b/src/mod_secure_download.c
new file mode 100644
index 0000000..1ea5a50
--- /dev/null
+++ b/src/mod_secure_download.c
@@ -0,0 +1,305 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef USE_OPENSSL
+# include <openssl/md5.h>
+#else
+# include "md5.h"
+#endif
+
+#define HASHLEN 16
+typedef unsigned char HASH[HASHLEN];
+#define HASHHEXLEN 32
+typedef char HASHHEX[HASHHEXLEN+1];
+#ifdef USE_OPENSSL
+#define IN const
+#else
+#define IN
+#endif
+#define OUT
+
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ buffer *doc_root;
+ buffer *secret;
+ buffer *uri_prefix;
+
+ unsigned short timeout;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *md5;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_secdownload_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->md5 = buffer_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_secdownload_free) {
+ plugin_data *p = p_d;
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free(s->secret);
+ buffer_free(s->doc_root);
+ buffer_free(s->uri_prefix);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->md5);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_secdownload_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "secdownload.secret", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "secdownload.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "secdownload.uri-prefix", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "secdownload.timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->secret = buffer_init();
+ s->doc_root = buffer_init();
+ s->uri_prefix = buffer_init();
+ s->timeout = 60;
+
+ cv[0].destination = s->secret;
+ cv[1].destination = s->doc_root;
+ cv[2].destination = s->uri_prefix;
+ cv[3].destination = &(s->timeout);
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+/**
+ * checks if the supplied string is a MD5 string
+ *
+ * @param str a possible MD5 string
+ * @return if the supplied string is a valid MD5 string 1 is returned otherwise 0
+ */
+
+int is_hex_len(const char *str, size_t len) {
+ size_t i;
+
+ if (NULL == str) return 0;
+
+ for (i = 0; i < len && *str; i++, str++) {
+ /* illegal characters */
+ if (!((*str >= '0' && *str <= '9') ||
+ (*str >= 'a' && *str <= 'f') ||
+ (*str >= 'A' && *str <= 'F'))
+ ) {
+ return 0;
+ }
+ }
+
+ return i == len;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_secdownload_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(secret);
+ PATCH(doc_root);
+ PATCH(uri_prefix);
+ PATCH(timeout);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.secret"))) {
+ PATCH(secret);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.document-root"))) {
+ PATCH(doc_root);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.uri-prefix"))) {
+ PATCH(uri_prefix);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.timeout"))) {
+ PATCH(timeout);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+
+URIHANDLER_FUNC(mod_secdownload_uri_handler) {
+ plugin_data *p = p_d;
+ MD5_CTX Md5Ctx;
+ HASH HA1;
+ const char *rel_uri, *ts_str, *md5_str;
+ time_t ts = 0;
+ size_t i;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_secdownload_patch_connection(srv, con, p);
+
+ if (buffer_is_empty(p->conf.uri_prefix)) return HANDLER_GO_ON;
+
+ if (buffer_is_empty(p->conf.secret)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "secdownload.secret has to be set");
+ return HANDLER_ERROR;
+ }
+
+ if (buffer_is_empty(p->conf.doc_root)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "secdownload.document-root has to be set");
+ return HANDLER_ERROR;
+ }
+
+ /*
+ * /<uri-prefix>[a-f0-9]{32}/[a-f0-9]{8}/<rel-path>
+ */
+
+ if (0 != strncmp(con->uri.path->ptr, p->conf.uri_prefix->ptr, p->conf.uri_prefix->used - 1)) return HANDLER_GO_ON;
+
+ md5_str = con->uri.path->ptr + p->conf.uri_prefix->used - 1;
+
+ if (!is_hex_len(md5_str, 32)) return HANDLER_GO_ON;
+ if (*(md5_str + 32) != '/') return HANDLER_GO_ON;
+
+ ts_str = md5_str + 32 + 1;
+
+ if (!is_hex_len(ts_str, 8)) return HANDLER_GO_ON;
+ if (*(ts_str + 8) != '/') return HANDLER_GO_ON;
+
+ for (i = 0; i < 8; i++) {
+ ts = (ts << 4) + hex2int(*(ts_str + i));
+ }
+
+ /* timed-out */
+ if (srv->cur_ts - ts > p->conf.timeout ||
+ srv->cur_ts - ts < -p->conf.timeout) {
+ con->http_status = 408;
+
+ return HANDLER_FINISHED;
+ }
+
+ rel_uri = ts_str + 8;
+
+ /* checking MD5
+ *
+ * <secret><rel-path><timestamp-hex>
+ */
+
+ buffer_copy_string_buffer(p->md5, p->conf.secret);
+ buffer_append_string(p->md5, rel_uri);
+ buffer_append_string_len(p->md5, ts_str, 8);
+
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)p->md5->ptr, p->md5->used - 1);
+ MD5_Final(HA1, &Md5Ctx);
+
+ buffer_copy_string_hex(p->md5, (char *)HA1, 16);
+
+ if (0 != strncmp(md5_str, p->md5->ptr, 32)) {
+ con->http_status = 403;
+
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "md5 invalid:",
+ md5_str, p->md5->ptr);
+
+ return HANDLER_FINISHED;
+ }
+
+ /* starting with the last / we should have relative-path to the docroot
+ */
+
+ buffer_copy_string_buffer(con->physical.doc_root, p->conf.doc_root);
+ buffer_copy_string(con->physical.rel_path, rel_uri);
+ buffer_copy_string_buffer(con->physical.path, con->physical.doc_root);
+ buffer_append_string_buffer(con->physical.path, con->physical.rel_path);
+
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_secdownload_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("secdownload");
+
+ p->init = mod_secdownload_init;
+ p->handle_physical = mod_secdownload_uri_handler;
+ p->set_defaults = mod_secdownload_set_defaults;
+ p->cleanup = mod_secdownload_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_setenv.c b/src/mod_setenv.c
new file mode 100644
index 0000000..9501554
--- /dev/null
+++ b/src/mod_setenv.c
@@ -0,0 +1,247 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "response.h"
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ int handled; /* make sure that we only apply the headers once */
+} handler_ctx;
+
+typedef struct {
+ array *request_header;
+ array *response_header;
+
+ array *environment;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+static handler_ctx * handler_ctx_init() {
+ handler_ctx * hctx;
+
+ hctx = calloc(1, sizeof(*hctx));
+
+ hctx->handled = 0;
+
+ return hctx;
+}
+
+static void handler_ctx_free(handler_ctx *hctx) {
+ free(hctx);
+}
+
+
+/* init the plugin data */
+INIT_FUNC(mod_setenv_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_setenv_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->request_header);
+ array_free(s->response_header);
+ array_free(s->environment);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_setenv_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "setenv.add-request-header", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "setenv.add-response-header", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "setenv.add-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->request_header = array_init();
+ s->response_header = array_init();
+ s->environment = array_init();
+
+ cv[0].destination = s->request_header;
+ cv[1].destination = s->response_header;
+ cv[2].destination = s->environment;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_setenv_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(request_header);
+ PATCH(response_header);
+ PATCH(environment);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("setenv.add-request-header"))) {
+ PATCH(request_header);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("setenv.add-response-header"))) {
+ PATCH(response_header);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("setenv.add-environment"))) {
+ PATCH(environment);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_setenv_uri_handler) {
+ plugin_data *p = p_d;
+ size_t k;
+ handler_ctx *hctx;
+
+ if (con->plugin_ctx[p->id]) {
+ hctx = con->plugin_ctx[p->id];
+ } else {
+ hctx = handler_ctx_init();
+
+ con->plugin_ctx[p->id] = hctx;
+ }
+
+ if (hctx->handled) {
+ return HANDLER_GO_ON;
+ }
+
+ hctx->handled = 1;
+
+ mod_setenv_patch_connection(srv, con, p);
+
+ for (k = 0; k < p->conf.request_header->used; k++) {
+ data_string *ds = (data_string *)p->conf.request_header->data[k];
+ data_string *ds_dst;
+
+ if (NULL == (ds_dst = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
+ ds_dst = data_string_init();
+ }
+
+ buffer_copy_string_buffer(ds_dst->key, ds->key);
+ buffer_copy_string_buffer(ds_dst->value, ds->value);
+
+ array_insert_unique(con->request.headers, (data_unset *)ds_dst);
+ }
+
+ for (k = 0; k < p->conf.environment->used; k++) {
+ data_string *ds = (data_string *)p->conf.environment->data[k];
+ data_string *ds_dst;
+
+ if (NULL == (ds_dst = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) {
+ ds_dst = data_string_init();
+ }
+
+ buffer_copy_string_buffer(ds_dst->key, ds->key);
+ buffer_copy_string_buffer(ds_dst->value, ds->value);
+
+ array_insert_unique(con->environment, (data_unset *)ds_dst);
+ }
+
+ for (k = 0; k < p->conf.response_header->used; k++) {
+ data_string *ds = (data_string *)p->conf.response_header->data[k];
+
+ response_header_insert(srv, con, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value));
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+REQUESTDONE_FUNC(mod_setenv_reset) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (con->plugin_ctx[p->id]) {
+ handler_ctx_free(con->plugin_ctx[p->id]);
+ con->plugin_ctx[p->id] = NULL;
+ }
+
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_setenv_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("setenv");
+
+ p->init = mod_setenv_init;
+ p->handle_uri_clean = mod_setenv_uri_handler;
+ p->set_defaults = mod_setenv_set_defaults;
+ p->cleanup = mod_setenv_free;
+
+ p->handle_request_done = mod_setenv_reset;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_simple_vhost.c b/src/mod_simple_vhost.c
new file mode 100644
index 0000000..8f81384
--- /dev/null
+++ b/src/mod_simple_vhost.c
@@ -0,0 +1,281 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+#include "stat_cache.h"
+
+#include "plugin.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+typedef struct {
+ buffer *server_root;
+ buffer *default_host;
+ buffer *document_root;
+
+ buffer *docroot_cache_key;
+ buffer *docroot_cache_value;
+ buffer *docroot_cache_servername;
+
+ unsigned short debug;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *doc_root;
+
+ plugin_config **config_storage;
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_simple_vhost_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->doc_root = buffer_init();
+
+ return p;
+}
+
+FREE_FUNC(mod_simple_vhost_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free(s->document_root);
+ buffer_free(s->default_host);
+ buffer_free(s->server_root);
+
+ buffer_free(s->docroot_cache_key);
+ buffer_free(s->docroot_cache_value);
+ buffer_free(s->docroot_cache_servername);
+
+ free(s);
+ }
+
+ free(p->config_storage);
+ }
+
+ buffer_free(p->doc_root);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_simple_vhost_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ config_values_t cv[] = {
+ { "simple-vhost.server-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "simple-vhost.default-host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "simple-vhost.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "simple-vhost.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+
+ s->server_root = buffer_init();
+ s->default_host = buffer_init();
+ s->document_root = buffer_init();
+
+ s->docroot_cache_key = buffer_init();
+ s->docroot_cache_value = buffer_init();
+ s->docroot_cache_servername = buffer_init();
+
+ s->debug = 0;
+
+ cv[0].destination = s->server_root;
+ cv[1].destination = s->default_host;
+ cv[2].destination = s->document_root;
+ cv[3].destination = &(s->debug);
+
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+static int build_doc_root(server *srv, connection *con, plugin_data *p, buffer *out, buffer *host) {
+ stat_cache_entry *sce = NULL;
+
+ buffer_prepare_copy(out, 128);
+
+ if (p->conf.server_root->used) {
+ buffer_copy_string_buffer(out, p->conf.server_root);
+
+ if (host->used) {
+ /* a hostname has to start with a alpha-numerical character
+ * and must not contain a slash "/"
+ */
+ char *dp;
+
+ BUFFER_APPEND_SLASH(out);
+
+ if (NULL == (dp = strchr(host->ptr, ':'))) {
+ buffer_append_string_buffer(out, host);
+ } else {
+ buffer_append_string_len(out, host->ptr, dp - host->ptr);
+ }
+ }
+ BUFFER_APPEND_SLASH(out);
+
+ if (p->conf.document_root->used > 2 && p->conf.document_root->ptr[0] == '/') {
+ buffer_append_string_len(out, p->conf.document_root->ptr + 1, p->conf.document_root->used - 2);
+ } else {
+ buffer_append_string_buffer(out, p->conf.document_root);
+ BUFFER_APPEND_SLASH(out);
+ }
+ } else {
+ buffer_copy_string_buffer(out, con->conf.document_root);
+ BUFFER_APPEND_SLASH(out);
+ }
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, out, &sce)) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), out);
+ }
+ return -1;
+ } else if (!S_ISDIR(sce->st.st_mode)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_simple_vhost_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(server_root);
+ PATCH(default_host);
+ PATCH(document_root);
+
+ PATCH(docroot_cache_key);
+ PATCH(docroot_cache_value);
+ PATCH(docroot_cache_servername);
+
+ PATCH(debug);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("simple-vhost.server-root"))) {
+ PATCH(server_root);
+ PATCH(docroot_cache_key);
+ PATCH(docroot_cache_value);
+ PATCH(docroot_cache_servername);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("simple-vhost.default-host"))) {
+ PATCH(default_host);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("simple-vhost.document-root"))) {
+ PATCH(document_root);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("simple-vhost.debug"))) {
+ PATCH(debug);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+static handler_t mod_simple_vhost_docroot(server *srv, connection *con, void *p_data) {
+ plugin_data *p = p_data;
+
+ /*
+ * cache the last successfull translation from hostname (authority) to docroot
+ * - this saves us a stat() call
+ *
+ */
+
+ mod_simple_vhost_patch_connection(srv, con, p);
+
+ if (p->conf.docroot_cache_key->used &&
+ con->uri.authority->used &&
+ buffer_is_equal(p->conf.docroot_cache_key, con->uri.authority)) {
+ /* cache hit */
+ buffer_copy_string_buffer(con->physical.doc_root, p->conf.docroot_cache_value);
+ buffer_copy_string_buffer(con->server_name, p->conf.docroot_cache_servername);
+ } else {
+ /* build document-root */
+ if ((con->uri.authority->used == 0) ||
+ build_doc_root(srv, con, p, p->doc_root, con->uri.authority)) {
+ /* not found, fallback the default-host */
+ if (build_doc_root(srv, con, p,
+ p->doc_root,
+ p->conf.default_host)) {
+ return HANDLER_GO_ON;
+ } else {
+ buffer_copy_string_buffer(con->server_name, p->conf.default_host);
+ }
+ } else {
+ buffer_copy_string_buffer(con->server_name, con->uri.authority);
+ }
+
+ /* copy to cache */
+ buffer_copy_string_buffer(p->conf.docroot_cache_key, con->uri.authority);
+ buffer_copy_string_buffer(p->conf.docroot_cache_value, p->doc_root);
+ buffer_copy_string_buffer(p->conf.docroot_cache_servername, con->server_name);
+
+ buffer_copy_string_buffer(con->physical.doc_root, p->doc_root);
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+int mod_simple_vhost_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("simple_vhost");
+
+ p->init = mod_simple_vhost_init;
+ p->set_defaults = mod_simple_vhost_set_defaults;
+ p->handle_docroot = mod_simple_vhost_docroot;
+ p->cleanup = mod_simple_vhost_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_skeleton.c b/src/mod_skeleton.c
new file mode 100644
index 0000000..a3fa186
--- /dev/null
+++ b/src/mod_skeleton.c
@@ -0,0 +1,210 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/**
+ * this is a skeleton for a lighttpd plugin
+ *
+ * just replaces every occurance of 'skeleton' by your plugin name
+ *
+ * e.g. in vim:
+ *
+ * :%s/skeleton/myhandler/
+ *
+ */
+
+
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ array *match;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *match_buf;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+typedef struct {
+ size_t foo;
+} handler_ctx;
+
+static handler_ctx * handler_ctx_init() {
+ handler_ctx * hctx;
+
+ hctx = calloc(1, sizeof(*hctx));
+
+ return hctx;
+}
+
+static void handler_ctx_free(handler_ctx *hctx) {
+
+ free(hctx);
+}
+
+/* init the plugin data */
+INIT_FUNC(mod_skeleton_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->match_buf = buffer_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_skeleton_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (!s) continue;
+
+ array_free(s->match);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->match_buf);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_skeleton_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "skeleton.array", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->match = array_init();
+
+ cv[0].destination = s->match;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_skeleton_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(match);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("skeleton.array"))) {
+ PATCH(match);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_skeleton_uri_handler) {
+ plugin_data *p = p_d;
+ int s_len;
+ size_t k, i;
+
+ UNUSED(srv);
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_skeleton_patch_connection(srv, con, p);
+
+ s_len = con->uri.path->used - 1;
+
+ for (k = 0; k < p->conf.match->used; k++) {
+ data_string *ds = (data_string *)p->conf.match->data[k];
+ int ct_len = ds->value->used - 1;
+
+ if (ct_len > s_len) continue;
+ if (ds->value->used == 0) continue;
+
+ if (0 == strncmp(con->uri.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) {
+ con->http_status = 403;
+
+ return HANDLER_FINISHED;
+ }
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_skeleton_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("skeleton");
+
+ p->init = mod_skeleton_init;
+ p->handle_uri_clean = mod_skeleton_uri_handler;
+ p->set_defaults = mod_skeleton_set_defaults;
+ p->cleanup = mod_skeleton_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_ssi.c b/src/mod_ssi.c
new file mode 100644
index 0000000..b6a19d7
--- /dev/null
+++ b/src/mod_ssi.c
@@ -0,0 +1,1086 @@
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+#include "stat_cache.h"
+
+#include "plugin.h"
+#include "stream.h"
+
+#include "response.h"
+
+#include "mod_ssi.h"
+
+#include "inet_ntop_cache.h"
+
+#include "sys-socket.h"
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_FORK
+#include <sys/wait.h>
+#endif
+
+#ifdef HAVE_SYS_FILIO_H
+#include <sys/filio.h>
+#endif
+
+/* init the plugin data */
+INIT_FUNC(mod_ssi_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->timefmt = buffer_init();
+ p->stat_fn = buffer_init();
+
+ p->ssi_vars = array_init();
+ p->ssi_cgi_env = array_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_ssi_free) {
+ plugin_data *p = p_d;
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->ssi_extension);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ array_free(p->ssi_vars);
+ array_free(p->ssi_cgi_env);
+#ifdef HAVE_PCRE_H
+ pcre_free(p->ssi_regex);
+#endif
+ buffer_free(p->timefmt);
+ buffer_free(p->stat_fn);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_ssi_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+#ifdef HAVE_PCRE_H
+ const char *errptr;
+ int erroff;
+#endif
+
+ config_values_t cv[] = {
+ { "ssi.extension", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->ssi_extension = array_init();
+
+ cv[0].destination = s->ssi_extension;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+#ifdef HAVE_PCRE_H
+ /* allow 2 params */
+ if (NULL == (p->ssi_regex = pcre_compile("<!--#([a-z]+)\\s+(?:([a-z]+)=\"(.*?)(?<!\\\\)\"\\s*)?(?:([a-z]+)=\"(.*?)(?<!\\\\)\"\\s*)?-->", 0, &errptr, &erroff, NULL))) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "ssi: pcre ",
+ erroff, errptr);
+ return HANDLER_ERROR;
+ }
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "mod_ssi: pcre support is missing, please recompile with pcre support or remove mod_ssi from the list of modules");
+ return HANDLER_ERROR;
+#endif
+
+ return HANDLER_GO_ON;
+}
+
+int ssi_env_add(array *env, const char *key, const char *val) {
+ data_string *ds;
+
+ if (NULL == (ds = (data_string *)array_get_unused_element(env, TYPE_STRING))) {
+ ds = data_string_init();
+ }
+ buffer_copy_string(ds->key, key);
+ buffer_copy_string(ds->value, val);
+
+ array_insert_unique(env, (data_unset *)ds);
+
+ return 0;
+}
+
+/**
+ *
+ * the next two functions are take from fcgi.c
+ *
+ */
+
+static int ssi_env_add_request_headers(server *srv, connection *con, plugin_data *p) {
+ size_t i;
+
+ for (i = 0; i < con->request.headers->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->request.headers->data[i];
+
+ if (ds->value->used && ds->key->used) {
+ size_t j;
+ buffer_reset(srv->tmp_buf);
+
+ /* don't forward the Authorization: Header */
+ if (0 == strcasecmp(ds->key->ptr, "AUTHORIZATION")) {
+ continue;
+ }
+
+ if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
+ buffer_copy_string(srv->tmp_buf, "HTTP_");
+ srv->tmp_buf->used--;
+ }
+
+ buffer_prepare_append(srv->tmp_buf, ds->key->used + 2);
+ for (j = 0; j < ds->key->used - 1; j++) {
+ char c = '_';
+ if (light_isalpha(ds->key->ptr[j])) {
+ /* upper-case */
+ c = ds->key->ptr[j] & ~32;
+ } else if (light_isdigit(ds->key->ptr[j])) {
+ /* copy */
+ c = ds->key->ptr[j];
+ }
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] = c;
+ }
+ srv->tmp_buf->ptr[srv->tmp_buf->used] = '\0';
+
+ ssi_env_add(p->ssi_cgi_env, srv->tmp_buf->ptr, ds->value->ptr);
+ }
+ }
+
+ return 0;
+}
+
+static int build_ssi_cgi_vars(server *srv, connection *con, plugin_data *p) {
+ char buf[32];
+
+ server_socket *srv_sock = con->srv_socket;
+
+#ifdef HAVE_IPV6
+ char b2[INET6_ADDRSTRLEN + 1];
+#endif
+
+#define CONST_STRING(x) \
+ x
+
+ array_reset(p->ssi_cgi_env);
+
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_SOFTWARE"), PACKAGE_NAME"/"PACKAGE_VERSION);
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_NAME"),
+#ifdef HAVE_IPV6
+ inet_ntop(srv_sock->addr.plain.sa_family,
+ srv_sock->addr.plain.sa_family == AF_INET6 ?
+ (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
+ (const void *) &(srv_sock->addr.ipv4.sin_addr),
+ b2, sizeof(b2)-1)
+#else
+ inet_ntoa(srv_sock->addr.ipv4.sin_addr)
+#endif
+ );
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("GATEWAY_INTERFACE"), "CGI/1.1");
+
+ ltostr(buf,
+#ifdef HAVE_IPV6
+ ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port)
+#else
+ ntohs(srv_sock->addr.ipv4.sin_port)
+#endif
+ );
+
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PORT"), buf);
+
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("REMOTE_ADDR"),
+ inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
+
+ if (con->authed_user->used) {
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("REMOTE_USER"),
+ con->authed_user->ptr);
+ }
+
+ if (con->request.content_length > 0) {
+ /* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */
+
+ /* request.content_length < SSIZE_MAX, see request.c */
+ ltostr(buf, con->request.content_length);
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("CONTENT_LENGTH"), buf);
+ }
+
+ /*
+ * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to
+ * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html
+ * (6.1.14, 6.1.6, 6.1.7)
+ */
+
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_NAME"), con->uri.path->ptr);
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), "");
+
+ /*
+ * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual
+ * http://www.php.net/manual/en/reserved.variables.php
+ * treatment of PATH_TRANSLATED is different from the one of CGI specs.
+ * TODO: this code should be checked against cgi.fix_pathinfo php
+ * parameter.
+ */
+
+ if (con->request.pathinfo->used) {
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), con->request.pathinfo->ptr);
+ }
+
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_FILENAME"), con->physical.path->ptr);
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("DOCUMENT_ROOT"), con->physical.doc_root->ptr);
+
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_URI"), con->request.uri->ptr);
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("QUERY_STRING"), con->uri.query->used ? con->uri.query->ptr : "");
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_METHOD"), get_http_method_name(con->request.http_method));
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("REDIRECT_STATUS"), "200");
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PROTOCOL"), get_http_version_name(con->request.http_version));
+
+ ssi_env_add_request_headers(srv, con, p);
+
+ return 0;
+}
+
+static int process_ssi_stmt(server *srv, connection *con, plugin_data *p,
+ const char **l, size_t n) {
+ size_t i, ssicmd = 0;
+ char buf[255];
+ buffer *b = NULL;
+
+ struct {
+ const char *var;
+ enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD,
+ SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF,
+ SSI_ELSE, SSI_ENDIF, SSI_EXEC } type;
+ } ssicmds[] = {
+ { "echo", SSI_ECHO },
+ { "include", SSI_INCLUDE },
+ { "flastmod", SSI_FLASTMOD },
+ { "fsize", SSI_FSIZE },
+ { "config", SSI_CONFIG },
+ { "printenv", SSI_PRINTENV },
+ { "set", SSI_SET },
+ { "if", SSI_IF },
+ { "elif", SSI_ELIF },
+ { "endif", SSI_ENDIF },
+ { "else", SSI_ELSE },
+ { "exec", SSI_EXEC },
+
+ { NULL, SSI_UNSET }
+ };
+
+ for (i = 0; ssicmds[i].var; i++) {
+ if (0 == strcmp(l[1], ssicmds[i].var)) {
+ ssicmd = ssicmds[i].type;
+ break;
+ }
+ }
+
+ switch(ssicmd) {
+ case SSI_ECHO: {
+ /* echo */
+ int var = 0, enc = 0;
+ const char *var_val = NULL;
+ stat_cache_entry *sce = NULL;
+
+ struct {
+ const char *var;
+ enum { SSI_ECHO_UNSET, SSI_ECHO_DATE_GMT, SSI_ECHO_DATE_LOCAL, SSI_ECHO_DOCUMENT_NAME, SSI_ECHO_DOCUMENT_URI,
+ SSI_ECHO_LAST_MODIFIED, SSI_ECHO_USER_NAME } type;
+ } echovars[] = {
+ { "DATE_GMT", SSI_ECHO_DATE_GMT },
+ { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL },
+ { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME },
+ { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI },
+ { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED },
+ { "USER_NAME", SSI_ECHO_USER_NAME },
+
+ { NULL, SSI_ECHO_UNSET }
+ };
+
+ struct {
+ const char *var;
+ enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
+ } encvars[] = {
+ { "url", SSI_ENC_URL },
+ { "none", SSI_ENC_NONE },
+ { "entity", SSI_ENC_ENTITY },
+
+ { NULL, SSI_ENC_UNSET }
+ };
+
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "var")) {
+ int j;
+
+ var_val = l[i+1];
+
+ for (j = 0; echovars[j].var; j++) {
+ if (0 == strcmp(l[i+1], echovars[j].var)) {
+ var = echovars[j].type;
+ break;
+ }
+ }
+ } else if (0 == strcmp(l[i], "encoding")) {
+ int j;
+
+ for (j = 0; encvars[j].var; j++) {
+ if (0 == strcmp(l[i+1], encvars[j].var)) {
+ enc = encvars[j].type;
+ break;
+ }
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+
+ if (p->if_is_false) break;
+
+ if (!var_val) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: ",
+ l[1], "var is missing");
+ break;
+ }
+
+ stat_cache_get_entry(srv, con, con->physical.path, &sce);
+
+ switch(var) {
+ case SSI_ECHO_USER_NAME: {
+ struct passwd *pw;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+#ifdef HAVE_PWD_H
+ if (NULL == (pw = getpwuid(sce->st.st_uid))) {
+ buffer_copy_long(b, sce->st.st_uid);
+ } else {
+ buffer_copy_string(b, pw->pw_name);
+ }
+#else
+ buffer_copy_long(b, sce->st.st_uid);
+#endif
+ break;
+ }
+ case SSI_ECHO_LAST_MODIFIED: {
+ time_t t = sce->st.st_mtime;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
+ buffer_copy_string(b, "(none)");
+ } else {
+ buffer_copy_string(b, buf);
+ }
+ break;
+ }
+ case SSI_ECHO_DATE_LOCAL: {
+ time_t t = time(NULL);
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
+ buffer_copy_string(b, "(none)");
+ } else {
+ buffer_copy_string(b, buf);
+ }
+ break;
+ }
+ case SSI_ECHO_DATE_GMT: {
+ time_t t = time(NULL);
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) {
+ buffer_copy_string(b, "(none)");
+ } else {
+ buffer_copy_string(b, buf);
+ }
+ break;
+ }
+ case SSI_ECHO_DOCUMENT_NAME: {
+ char *sl;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
+ buffer_copy_string_buffer(b, con->physical.path);
+ } else {
+ buffer_copy_string(b, sl + 1);
+ }
+ break;
+ }
+ case SSI_ECHO_DOCUMENT_URI: {
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ buffer_copy_string_buffer(b, con->uri.path);
+ break;
+ }
+ default: {
+ data_string *ds;
+ /* check if it is a cgi-var */
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, var_val))) {
+ buffer_copy_string_buffer(b, ds->value);
+ } else {
+ buffer_copy_string(b, "(none)");
+ }
+
+ break;
+ }
+ }
+ break;
+ }
+ case SSI_INCLUDE:
+ case SSI_FLASTMOD:
+ case SSI_FSIZE: {
+ const char * file_path = NULL, *virt_path = NULL;
+ struct stat st;
+ char *sl;
+
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "file")) {
+ file_path = l[i+1];
+ } else if (0 == strcmp(l[i], "virtual")) {
+ virt_path = l[i+1];
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+
+ if (!file_path && !virt_path) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: ",
+ l[1], "file or virtual are missing");
+ break;
+ }
+
+ if (file_path && virt_path) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: ",
+ l[1], "only one of file and virtual is allowed here");
+ break;
+ }
+
+
+ if (p->if_is_false) break;
+
+ if (file_path) {
+ /* current doc-root */
+ if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
+ buffer_copy_string(p->stat_fn, "/");
+ } else {
+ buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1);
+ }
+
+ /* fn */
+ if (NULL == (sl = strrchr(file_path, '/'))) {
+ buffer_append_string(p->stat_fn, file_path);
+ } else {
+ buffer_append_string(p->stat_fn, sl + 1);
+ }
+ } else {
+ /* virtual */
+
+ if (virt_path[0] == '/') {
+ buffer_copy_string(p->stat_fn, virt_path);
+ } else {
+ /* there is always a / */
+ sl = strrchr(con->uri.path->ptr, '/');
+
+ buffer_copy_string_len(p->stat_fn, con->uri.path->ptr, sl - con->uri.path->ptr + 1);
+ buffer_append_string(p->stat_fn, virt_path);
+ }
+
+ buffer_urldecode_path(p->stat_fn);
+ buffer_path_simplify(srv->tmp_buf, p->stat_fn);
+
+ /* we have an uri */
+
+ buffer_copy_string_buffer(p->stat_fn, con->physical.doc_root);
+ buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
+ }
+
+ if (0 == stat(p->stat_fn->ptr, &st)) {
+ time_t t = st.st_mtime;
+
+ switch (ssicmd) {
+ case SSI_FSIZE:
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ if (p->sizefmt) {
+ int j = 0;
+ const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL };
+
+ off_t s = st.st_size;
+
+ for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++);
+
+ buffer_copy_off_t(b, s);
+ buffer_append_string(b, abr[j]);
+ } else {
+ buffer_copy_off_t(b, st.st_size);
+ }
+ break;
+ case SSI_FLASTMOD:
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
+ buffer_copy_string(b, "(none)");
+ } else {
+ buffer_copy_string(b, buf);
+ }
+ break;
+ case SSI_INCLUDE:
+ chunkqueue_append_file(con->write_queue, p->stat_fn, 0, st.st_size);
+ break;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "ssi: stating failed ",
+ p->stat_fn, strerror(errno));
+ }
+ break;
+ }
+ case SSI_SET: {
+ const char *key = NULL, *val = NULL;
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "var")) {
+ key = l[i+1];
+ } else if (0 == strcmp(l[i], "value")) {
+ val = l[i+1];
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+
+ if (p->if_is_false) break;
+
+ if (key && val) {
+ data_string *ds;
+
+ if (NULL == (ds = (data_string *)array_get_unused_element(p->ssi_vars, TYPE_STRING))) {
+ ds = data_string_init();
+ }
+ buffer_copy_string(ds->key, key);
+ buffer_copy_string(ds->value, val);
+
+ array_insert_unique(p->ssi_vars, (data_unset *)ds);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: var and value have to be set in",
+ l[0], l[1]);
+ }
+ break;
+ }
+ case SSI_CONFIG:
+ if (p->if_is_false) break;
+
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "timefmt")) {
+ buffer_copy_string(p->timefmt, l[i+1]);
+ } else if (0 == strcmp(l[i], "sizefmt")) {
+ if (0 == strcmp(l[i+1], "abbrev")) {
+ p->sizefmt = 1;
+ } else if (0 == strcmp(l[i+1], "abbrev")) {
+ p->sizefmt = 0;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sssss",
+ "ssi: unknow value for attribute '",
+ l[i],
+ "' for ",
+ l[1], l[i+1]);
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+ break;
+ case SSI_PRINTENV:
+ if (p->if_is_false) break;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ buffer_copy_string(b, "<pre>");
+ for (i = 0; i < p->ssi_vars->used; i++) {
+ data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]];
+
+ buffer_append_string_buffer(b, ds->key);
+ buffer_append_string(b, ": ");
+ buffer_append_string_buffer(b, ds->value);
+ buffer_append_string(b, "<br />");
+
+ }
+ buffer_append_string(b, "</pre>");
+
+ break;
+ case SSI_EXEC: {
+ const char *cmd = NULL;
+ pid_t pid;
+ int from_exec_fds[2];
+
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "cmd")) {
+ cmd = l[i+1];
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+
+ if (p->if_is_false) break;
+
+ /* create a return pipe and send output to the html-page
+ *
+ * as exec is assumed evil it is implemented synchronously
+ */
+
+ if (!cmd) break;
+#ifdef HAVE_FORK
+ if (pipe(from_exec_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "pipe failed: ", strerror(errno));
+ return -1;
+ }
+
+ /* fork, execve */
+ switch (pid = fork()) {
+ case 0: {
+ /* move stdout to from_rrdtool_fd[1] */
+ close(STDOUT_FILENO);
+ dup2(from_exec_fds[1], STDOUT_FILENO);
+ close(from_exec_fds[1]);
+ /* not needed */
+ close(from_exec_fds[0]);
+
+ /* close stdin */
+ close(STDIN_FILENO);
+
+ execl("/bin/sh", "sh", "-c", cmd, NULL);
+
+ log_error_write(srv, __FILE__, __LINE__, "sss", "spawing exec failed:", strerror(errno), cmd);
+
+ /* */
+ SEGFAULT();
+ break;
+ }
+ case -1:
+ /* error */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
+ break;
+ default: {
+ /* father */
+ int status;
+ ssize_t r;
+
+ close(from_exec_fds[1]);
+
+ /* wait for the client to end */
+ if (-1 == waitpid(pid, &status, 0)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno));
+ } else if (WIFEXITED(status)) {
+ int toread;
+ /* read everything from client and paste it into the output */
+
+ while(1) {
+ if (ioctl(from_exec_fds[0], FIONREAD, &toread)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "unexpected end-of-file (perhaps the ssi-exec process died)");
+ return -1;
+ }
+
+ if (toread > 0) {
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ buffer_prepare_copy(b, toread + 1);
+
+ if ((r = read(from_exec_fds[0], b->ptr, b->size - 1)) < 0) {
+ /* read failed */
+ break;
+ } else {
+ b->used = r;
+ b->ptr[b->used++] = '\0';
+ }
+ } else {
+ break;
+ }
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "process exited abnormally");
+ }
+ close(from_exec_fds[0]);
+
+ break;
+ }
+ }
+#else
+
+ return -1;
+#endif
+
+ break;
+ }
+ case SSI_IF: {
+ const char *expr = NULL;
+
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "expr")) {
+ expr = l[i+1];
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+
+ if (!expr) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: ",
+ l[1], "expr missing");
+ break;
+ }
+
+ if ((!p->if_is_false) &&
+ ((p->if_is_false_level == 0) ||
+ (p->if_level < p->if_is_false_level))) {
+ switch (ssi_eval_expr(srv, con, p, expr)) {
+ case -1:
+ case 0:
+ p->if_is_false = 1;
+ p->if_is_false_level = p->if_level;
+ break;
+ case 1:
+ p->if_is_false = 0;
+ break;
+ }
+ }
+
+ p->if_level++;
+
+ break;
+ }
+ case SSI_ELSE:
+ p->if_level--;
+
+ if (p->if_is_false) {
+ if ((p->if_level == p->if_is_false_level) &&
+ (p->if_is_false_endif == 0)) {
+ p->if_is_false = 0;
+ }
+ } else {
+ p->if_is_false = 1;
+
+ p->if_is_false_level = p->if_level;
+ }
+ p->if_level++;
+
+ break;
+ case SSI_ELIF: {
+ const char *expr = NULL;
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "expr")) {
+ expr = l[i+1];
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+
+ if (!expr) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: ",
+ l[1], "expr missing");
+ break;
+ }
+
+ p->if_level--;
+
+ if (p->if_level == p->if_is_false_level) {
+ if ((p->if_is_false) &&
+ (p->if_is_false_endif == 0)) {
+ switch (ssi_eval_expr(srv, con, p, expr)) {
+ case -1:
+ case 0:
+ p->if_is_false = 1;
+ p->if_is_false_level = p->if_level;
+ break;
+ case 1:
+ p->if_is_false = 0;
+ break;
+ }
+ } else {
+ p->if_is_false = 1;
+ p->if_is_false_level = p->if_level;
+ p->if_is_false_endif = 1;
+ }
+ }
+
+ p->if_level++;
+
+ break;
+ }
+ case SSI_ENDIF:
+ p->if_level--;
+
+ if (p->if_level == p->if_is_false_level) {
+ p->if_is_false = 0;
+ p->if_is_false_endif = 0;
+ }
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "ssi: unknow ssi-command:",
+ l[1]);
+ break;
+ }
+
+ return 0;
+
+}
+
+static int mod_ssi_handle_request(server *srv, connection *con, plugin_data *p) {
+ stream s;
+#ifdef HAVE_PCRE_H
+ int i, n;
+
+#define N 10
+ int ovec[N * 3];
+#endif
+
+ /* get a stream to the file */
+
+ array_reset(p->ssi_vars);
+ array_reset(p->ssi_cgi_env);
+ buffer_copy_string(p->timefmt, "%a, %d %b %Y %H:%M:%S %Z");
+ p->sizefmt = 0;
+ build_ssi_cgi_vars(srv, con, p);
+ p->if_is_false = 0;
+
+ if (-1 == stream_open(&s, con->physical.path)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "stream-open: ", con->physical.path);
+ return -1;
+ }
+
+
+ /**
+ * <!--#element attribute=value attribute=value ... -->
+ *
+ * config DONE
+ * errmsg -- missing
+ * sizefmt DONE
+ * timefmt DONE
+ * echo DONE
+ * var DONE
+ * encoding -- missing
+ * exec DONE
+ * cgi -- never
+ * cmd DONE
+ * fsize DONE
+ * file DONE
+ * virtual DONE
+ * flastmod DONE
+ * file DONE
+ * virtual DONE
+ * include DONE
+ * file DONE
+ * virtual DONE
+ * printenv DONE
+ * set DONE
+ * var DONE
+ * value DONE
+ *
+ * if DONE
+ * elif DONE
+ * else DONE
+ * endif DONE
+ *
+ *
+ * expressions
+ * AND, OR DONE
+ * comp DONE
+ * ${...} -- missing
+ * $... DONE
+ * '...' DONE
+ * ( ... ) DONE
+ *
+ *
+ *
+ * ** all DONE **
+ * DATE_GMT
+ * The current date in Greenwich Mean Time.
+ * DATE_LOCAL
+ * The current date in the local time zone.
+ * DOCUMENT_NAME
+ * The filename (excluding directories) of the document requested by the user.
+ * DOCUMENT_URI
+ * The (%-decoded) URL path of the document requested by the user. Note that in the case of nested include files, this is not then URL for the current document.
+ * LAST_MODIFIED
+ * The last modification date of the document requested by the user.
+ * USER_NAME
+ * Contains the owner of the file which included it.
+ *
+ */
+#ifdef HAVE_PCRE_H
+ for (i = 0; (n = pcre_exec(p->ssi_regex, NULL, s.start, s.size, i, 0, ovec, N * 3)) > 0; i = ovec[1]) {
+ const char **l;
+ /* take every think from last offset to current match pos */
+
+ if (!p->if_is_false) chunkqueue_append_file(con->write_queue, con->physical.path, i, ovec[0] - i);
+
+ pcre_get_substring_list(s.start, ovec, n, &l);
+ process_ssi_stmt(srv, con, p, l, n);
+ pcre_free_substring_list(l);
+ }
+
+ switch(n) {
+ case PCRE_ERROR_NOMATCH:
+ /* copy everything/the rest */
+ chunkqueue_append_file(con->write_queue, con->physical.path, i, s.size - i);
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "execution error while matching: ", n);
+ break;
+ }
+#endif
+
+
+ stream_close(&s);
+
+ con->file_started = 1;
+ con->file_finished = 1;
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+
+ /* reset physical.path */
+ buffer_reset(con->physical.path);
+
+ return 0;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_ssi_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(ssi_extension);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.extension"))) {
+ PATCH(ssi_extension);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_ssi_physical_path) {
+ plugin_data *p = p_d;
+ size_t k;
+
+ if (con->physical.path->used == 0) return HANDLER_GO_ON;
+
+ mod_ssi_patch_connection(srv, con, p);
+
+ for (k = 0; k < p->conf.ssi_extension->used; k++) {
+ data_string *ds = (data_string *)p->conf.ssi_extension->data[k];
+
+ if (ds->value->used == 0) continue;
+
+ if (buffer_is_equal_right_len(con->physical.path, ds->value, ds->value->used - 1)) {
+ /* handle ssi-request */
+
+ if (mod_ssi_handle_request(srv, con, p)) {
+ /* on error */
+ con->http_status = 500;
+ }
+
+ return HANDLER_FINISHED;
+ }
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_ssi_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("ssi");
+
+ p->init = mod_ssi_init;
+ p->handle_subrequest_start = mod_ssi_physical_path;
+ p->set_defaults = mod_ssi_set_defaults;
+ p->cleanup = mod_ssi_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_ssi.h b/src/mod_ssi.h
new file mode 100644
index 0000000..80f03ed
--- /dev/null
+++ b/src/mod_ssi.h
@@ -0,0 +1,43 @@
+#ifndef _MOD_SSI_H_
+#define _MOD_SSI_H_
+
+#include "base.h"
+#include "buffer.h"
+#include "array.h"
+
+#include "plugin.h"
+
+#ifdef HAVE_PCRE_H
+#include <pcre.h>
+#endif
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ array *ssi_extension;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+#ifdef HAVE_PCRE_H
+ pcre *ssi_regex;
+#endif
+ buffer *timefmt;
+ int sizefmt;
+
+ buffer *stat_fn;
+
+ array *ssi_vars;
+ array *ssi_cgi_env;
+
+ int if_level, if_is_false_level, if_is_false, if_is_false_endif;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+int ssi_eval_expr(server *srv, connection *con, plugin_data *p, const char *expr);
+
+#endif
diff --git a/src/mod_ssi_expr.c b/src/mod_ssi_expr.c
new file mode 100644
index 0000000..98959ab
--- /dev/null
+++ b/src/mod_ssi_expr.c
@@ -0,0 +1,324 @@
+#include <ctype.h>
+#include <string.h>
+
+#include "buffer.h"
+#include "log.h"
+#include "mod_ssi.h"
+#include "mod_ssi_expr.h"
+#include "mod_ssi_exprparser.h"
+
+typedef struct {
+ const char *input;
+ size_t offset;
+ size_t size;
+
+ int line_pos;
+
+ int in_key;
+ int in_brace;
+ int in_cond;
+} ssi_tokenizer_t;
+
+ssi_val_t *ssi_val_init() {
+ ssi_val_t *s;
+
+ s = calloc(1, sizeof(*s));
+
+ return s;
+}
+
+void ssi_val_free(ssi_val_t *s) {
+ if (s->str) buffer_free(s->str);
+
+ free(s);
+}
+
+int ssi_val_tobool(ssi_val_t *B) {
+ if (B->type == SSI_TYPE_STRING) {
+ return B->str->used > 1 ? 1 : 0;
+ } else {
+ return B->bo;
+ }
+}
+
+static int ssi_expr_tokenizer(server *srv, connection *con, plugin_data *p,
+ ssi_tokenizer_t *t, int *token_id, buffer *token) {
+ int tid = 0;
+ size_t i;
+
+ UNUSED(con);
+
+ for (tid = 0; tid == 0 && t->offset < t->size && t->input[t->offset] ; ) {
+ char c = t->input[t->offset];
+ data_string *ds;
+
+ switch (c) {
+ case '=':
+ tid = TK_EQ;
+
+ t->offset++;
+ t->line_pos++;
+
+ buffer_copy_string(token, "(=)");
+
+ break;
+ case '>':
+ if (t->input[t->offset + 1] == '=') {
+ t->offset += 2;
+ t->line_pos += 2;
+
+ tid = TK_GE;
+
+ buffer_copy_string(token, "(>=)");
+ } else {
+ t->offset += 1;
+ t->line_pos += 1;
+
+ tid = TK_GT;
+
+ buffer_copy_string(token, "(>)");
+ }
+
+ break;
+ case '<':
+ if (t->input[t->offset + 1] == '=') {
+ t->offset += 2;
+ t->line_pos += 2;
+
+ tid = TK_LE;
+
+ buffer_copy_string(token, "(<=)");
+ } else {
+ t->offset += 1;
+ t->line_pos += 1;
+
+ tid = TK_LT;
+
+ buffer_copy_string(token, "(<)");
+ }
+
+ break;
+
+ case '!':
+ if (t->input[t->offset + 1] == '=') {
+ t->offset += 2;
+ t->line_pos += 2;
+
+ tid = TK_NE;
+
+ buffer_copy_string(token, "(!=)");
+ } else {
+ t->offset += 1;
+ t->line_pos += 1;
+
+ tid = TK_NOT;
+
+ buffer_copy_string(token, "(!)");
+ }
+
+ break;
+ case '&':
+ if (t->input[t->offset + 1] == '&') {
+ t->offset += 2;
+ t->line_pos += 2;
+
+ tid = TK_AND;
+
+ buffer_copy_string(token, "(&&)");
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "pos:", t->line_pos,
+ "missing second &");
+ return -1;
+ }
+
+ break;
+ case '|':
+ if (t->input[t->offset + 1] == '|') {
+ t->offset += 2;
+ t->line_pos += 2;
+
+ tid = TK_OR;
+
+ buffer_copy_string(token, "(||)");
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "pos:", t->line_pos,
+ "missing second |");
+ return -1;
+ }
+
+ break;
+ case '\t':
+ case ' ':
+ t->offset++;
+ t->line_pos++;
+ break;
+
+ case '\'':
+ /* search for the terminating " */
+ for (i = 1; t->input[t->offset + i] && t->input[t->offset + i] != '\''; i++);
+
+ if (t->input[t->offset + i]) {
+ tid = TK_VALUE;
+
+ buffer_copy_string_len(token, t->input + t->offset + 1, i-1);
+
+ t->offset += i + 1;
+ t->line_pos += i + 1;
+ } else {
+ /* ERROR */
+
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "pos:", t->line_pos,
+ "missing closing quote");
+
+ return -1;
+ }
+
+ break;
+ case '(':
+ t->offset++;
+ t->in_brace++;
+
+ tid = TK_LPARAN;
+
+ buffer_copy_string(token, "(");
+ break;
+ case ')':
+ t->offset++;
+ t->in_brace--;
+
+ tid = TK_RPARAN;
+
+ buffer_copy_string(token, ")");
+ break;
+ case '$':
+ if (t->input[t->offset + 1] == '{') {
+ for (i = 2; t->input[t->offset + i] && t->input[t->offset + i] != '}'; i++);
+
+ if (t->input[t->offset + i] != '}') {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "pos:", t->line_pos,
+ "missing closing quote");
+
+ return -1;
+ }
+
+ buffer_copy_string_len(token, t->input + t->offset + 2, i-3);
+ } else {
+ for (i = 1; isalpha(t->input[t->offset + i]) || t->input[t->offset + i] == '_'; i++);
+
+ buffer_copy_string_len(token, t->input + t->offset + 1, i-1);
+ }
+
+ tid = TK_VALUE;
+
+ if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, token->ptr))) {
+ buffer_copy_string_buffer(token, ds->value);
+ } else if (NULL != (ds = (data_string *)array_get_element(p->ssi_vars, token->ptr))) {
+ buffer_copy_string_buffer(token, ds->value);
+ } else {
+ buffer_copy_string(token, "");
+ }
+
+ t->offset += i;
+ t->line_pos += i;
+
+ break;
+ default:
+ for (i = 0; isgraph(t->input[t->offset + i]); i++) {
+ char d = t->input[t->offset + i];
+ switch(d) {
+ case ' ':
+ case '\t':
+ case ')':
+ case '(':
+ case '\'':
+ case '=':
+ case '!':
+ case '<':
+ case '>':
+ case '&':
+ case '|':
+ break;
+ }
+ }
+
+ tid = TK_VALUE;
+
+ buffer_copy_string_len(token, t->input + t->offset, i);
+
+ t->offset += i;
+ t->line_pos += i;
+
+ break;
+ }
+ }
+
+ if (tid) {
+ *token_id = tid;
+
+ return 1;
+ } else if (t->offset < t->size) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "pos:", t->line_pos,
+ "foobar");
+ }
+ return 0;
+}
+
+int ssi_eval_expr(server *srv, connection *con, plugin_data *p, const char *expr) {
+ ssi_tokenizer_t t;
+ void *pParser;
+ int token_id;
+ buffer *token;
+ ssi_ctx_t context;
+ int ret;
+
+ t.input = expr;
+ t.offset = 0;
+ t.size = strlen(expr);
+ t.line_pos = 1;
+
+ t.in_key = 1;
+ t.in_brace = 0;
+ t.in_cond = 0;
+
+ context.ok = 1;
+ context.srv = srv;
+
+ /* default context */
+
+ pParser = ssiexprparserAlloc( malloc );
+ token = buffer_init();
+ while((1 == (ret = ssi_expr_tokenizer(srv, con, p, &t, &token_id, token))) && context.ok) {
+ ssiexprparser(pParser, token_id, token, &context);
+
+ token = buffer_init();
+ }
+ ssiexprparser(pParser, 0, token, &context);
+ ssiexprparserFree(pParser, free );
+
+ buffer_free(token);
+
+ if (ret == -1) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "expr parser failed");
+ return -1;
+ }
+
+ if (context.ok == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "pos:", t.line_pos,
+ "parser failed somehow near here");
+ return -1;
+ }
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "expr: ",
+ expr,
+ context.val.bo);
+#endif
+ return context.val.bo;
+}
diff --git a/src/mod_ssi_expr.h b/src/mod_ssi_expr.h
new file mode 100644
index 0000000..b484f78
--- /dev/null
+++ b/src/mod_ssi_expr.h
@@ -0,0 +1,31 @@
+#ifndef _MOD_SSI_EXPR_H_
+#define _MOD_SSI_EXPR_H_
+
+#include "buffer.h"
+
+typedef struct {
+ enum { SSI_TYPE_UNSET, SSI_TYPE_BOOL, SSI_TYPE_STRING } type;
+
+ buffer *str;
+ int bo;
+} ssi_val_t;
+
+typedef struct {
+ int ok;
+
+ ssi_val_t val;
+
+ void *srv;
+} ssi_ctx_t;
+
+typedef enum { SSI_COND_UNSET, SSI_COND_LE, SSI_COND_GE, SSI_COND_EQ, SSI_COND_NE, SSI_COND_LT, SSI_COND_GT } ssi_expr_cond;
+
+void *ssiexprparserAlloc(void *(*mallocProc)(size_t));
+void ssiexprparserFree(void *p, void (*freeProc)(void*));
+void ssiexprparser(void *yyp, int yymajor, buffer *yyminor, ssi_ctx_t *ctx);
+
+int ssi_val_tobool(ssi_val_t *B);
+ssi_val_t *ssi_val_init();
+void ssi_val_free(ssi_val_t *s);
+
+#endif
diff --git a/src/mod_ssi_exprparser.c b/src/mod_ssi_exprparser.c
new file mode 100644
index 0000000..65ec4dc
--- /dev/null
+++ b/src/mod_ssi_exprparser.c
@@ -0,0 +1,951 @@
+/* Driver template for the LEMON parser generator.
+** The author disclaims copyright to this source code.
+*/
+/* First off, code is include which follows the "include" declaration
+** in the input file. */
+#include <stdio.h>
+#line 6 "./mod_ssi_exprparser.y"
+
+#include <assert.h>
+#include <string.h>
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "mod_ssi_expr.h"
+#include "buffer.h"
+
+#line 18 "mod_ssi_exprparser.c"
+/* Next is all token values, in a form suitable for use by makeheaders.
+** This section will be null unless lemon is run with the -m switch.
+*/
+/*
+** These constants (all generated automatically by the parser generator)
+** specify the various kinds of tokens (terminals) that the parser
+** understands.
+**
+** Each symbol here is a terminal symbol in the grammar.
+*/
+/* Make sure the INTERFACE macro is defined.
+*/
+#ifndef INTERFACE
+# define INTERFACE 1
+#endif
+/* The next thing included is series of defines which control
+** various aspects of the generated parser.
+** YYCODETYPE is the data type used for storing terminal
+** and nonterminal numbers. "unsigned char" is
+** used if there are fewer than 250 terminals
+** and nonterminals. "int" is used otherwise.
+** YYNOCODE is a number of type YYCODETYPE which corresponds
+** to no legal terminal or nonterminal number. This
+** number is used to fill in empty slots of the hash
+** table.
+** YYFALLBACK If defined, this indicates that one or more tokens
+** have fall-back values which should be used if the
+** original value of the token will not parse.
+** YYACTIONTYPE is the data type used for storing terminal
+** and nonterminal numbers. "unsigned char" is
+** used if there are fewer than 250 rules and
+** states combined. "int" is used otherwise.
+** ssiexprparserTOKENTYPE is the data type used for minor tokens given
+** directly to the parser from the tokenizer.
+** YYMINORTYPE is the data type used for all minor tokens.
+** This is typically a union of many types, one of
+** which is ssiexprparserTOKENTYPE. The entry in the union
+** for base tokens is called "yy0".
+** YYSTACKDEPTH is the maximum depth of the parser's stack.
+** ssiexprparserARG_SDECL A static variable declaration for the %extra_argument
+** ssiexprparserARG_PDECL A parameter declaration for the %extra_argument
+** ssiexprparserARG_STORE Code to store %extra_argument into yypParser
+** ssiexprparserARG_FETCH Code to extract %extra_argument from yypParser
+** YYNSTATE the combined number of states.
+** YYNRULE the number of rules in the grammar
+** YYERRORSYMBOL is the code number of the error symbol. If not
+** defined, then do no error processing.
+*/
+/*  */
+#define YYCODETYPE unsigned char
+#define YYNOCODE 20
+#define YYACTIONTYPE unsigned char
+#define ssiexprparserTOKENTYPE buffer *
+typedef union {
+ ssiexprparserTOKENTYPE yy0;
+ int yy8;
+ buffer * yy19;
+ ssi_val_t * yy29;
+ int yy39;
+} YYMINORTYPE;
+#define YYSTACKDEPTH 100
+#define ssiexprparserARG_SDECL ssi_ctx_t *ctx;
+#define ssiexprparserARG_PDECL ,ssi_ctx_t *ctx
+#define ssiexprparserARG_FETCH ssi_ctx_t *ctx = yypParser->ctx
+#define ssiexprparserARG_STORE yypParser->ctx = ctx
+#define YYNSTATE 23
+#define YYNRULE 16
+#define YYERRORSYMBOL 13
+#define YYERRSYMDT yy39
+#define YY_NO_ACTION (YYNSTATE+YYNRULE+2)
+#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1)
+#define YY_ERROR_ACTION (YYNSTATE+YYNRULE)
+
+/* Next are that tables used to determine what action to take based on the
+** current state and lookahead token. These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.
+**
+** Suppose the action integer is N. Then the action is determined as
+** follows
+**
+** 0 <= N < YYNSTATE Shift N. That is, push the lookahead
+** token onto the stack and goto state N.
+**
+** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE.
+**
+** N == YYNSTATE+YYNRULE A syntax error has occurred.
+**
+** N == YYNSTATE+YYNRULE+1 The parser accepts its input.
+**
+** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused
+** slots in the yy_action[] table.
+**
+** The action table is constructed as a single large table named yy_action[].
+** Given state S and lookahead X, the action is computed as
+**
+** yy_action[ yy_shift_ofst[S] + X ]
+**
+** If the index value yy_shift_ofst[S]+X is out of range or if the value
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S]
+** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table
+** and that yy_default[S] should be used instead.
+**
+** The formula above is for computing the action when the lookahead is
+** a terminal symbol. If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the yy_reduce_ofst[] array is used in place of
+** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of
+** YY_SHIFT_USE_DFLT.
+**
+** The following are the tables generated in this section:
+**
+** yy_action[] A single table containing all actions.
+** yy_lookahead[] A table containing the lookahead for each entry in
+** yy_action. Used to detect hash collisions.
+** yy_shift_ofst[] For each state, the offset into yy_action for
+** shifting terminals.
+** yy_reduce_ofst[] For each state, the offset into yy_action for
+** shifting non-terminals after a reduce.
+** yy_default[] Default action for each state.
+*/
+static YYACTIONTYPE yy_action[] = {
+ /* 0 */ 5, 7, 17, 18, 22, 20, 21, 19, 2, 14,
+ /* 10 */ 1, 23, 40, 9, 11, 3, 16, 2, 14, 12,
+ /* 20 */ 4, 14, 5, 7, 6, 14, 7, 8, 14, 10,
+ /* 30 */ 14, 13, 37, 37, 15,
+};
+static YYCODETYPE yy_lookahead[] = {
+ /* 0 */ 1, 2, 3, 4, 5, 6, 7, 8, 14, 15,
+ /* 10 */ 16, 0, 18, 9, 10, 17, 12, 14, 15, 16,
+ /* 20 */ 14, 15, 1, 2, 14, 15, 2, 14, 15, 14,
+ /* 30 */ 15, 11, 19, 19, 12,
+};
+#define YY_SHIFT_USE_DFLT (-2)
+static signed char yy_shift_ofst[] = {
+ /* 0 */ 4, 11, -1, 4, 21, 4, 24, 4, -2, 4,
+ /* 10 */ -2, 4, 20, -2, 22, -2, -2, -2, -2, -2,
+ /* 20 */ -2, -2, -2,
+};
+#define YY_REDUCE_USE_DFLT (-7)
+static signed char yy_reduce_ofst[] = {
+ /* 0 */ -6, -7, -2, 6, -7, 10, -7, 13, -7, 15,
+ /* 10 */ -7, 3, -7, -7, -7, -7, -7, -7, -7, -7,
+ /* 20 */ -7, -7, -7,
+};
+static YYACTIONTYPE yy_default[] = {
+ /* 0 */ 39, 39, 25, 39, 24, 39, 26, 39, 27, 39,
+ /* 10 */ 28, 39, 39, 29, 30, 32, 31, 33, 34, 35,
+ /* 20 */ 36, 37, 38,
+};
+#define YY_SZ_ACTTAB (sizeof(yy_action)/sizeof(yy_action[0]))
+
+/* The next table maps tokens into fallback tokens. If a construct
+** like the following:
+**
+** %fallback ID X Y Z.
+**
+** appears in the grammer, then ID becomes a fallback token for X, Y,
+** and Z. Whenever one of the tokens X, Y, or Z is input to the parser
+** but it does not parse, the type of the token is changed to ID and
+** the parse is retried before an error is thrown.
+*/
+#ifdef YYFALLBACK
+static const YYCODETYPE yyFallback[] = {
+};
+#endif /* YYFALLBACK */
+
+/* The following structure represents a single element of the
+** parser's stack. Information stored includes:
+**
+** + The state number for the parser at this level of the stack.
+**
+** + The value of the token stored at this level of the stack.
+** (In other words, the "major" token.)
+**
+** + The semantic value stored at this level of the stack. This is
+** the information used by the action routines in the grammar.
+** It is sometimes called the "minor" token.
+*/
+struct yyStackEntry {
+ int stateno; /* The state-number */
+ int major; /* The major token value. This is the code
+ ** number for the token at this stack level */
+ YYMINORTYPE minor; /* The user-supplied minor token value. This
+ ** is the value of the token */
+};
+typedef struct yyStackEntry yyStackEntry;
+
+/* The state of the parser is completely contained in an instance of
+** the following structure */
+struct yyParser {
+ int yyidx; /* Index of top element in stack */
+ int yyerrcnt; /* Shifts left before out of the error */
+ ssiexprparserARG_SDECL /* A place to hold %extra_argument */
+ yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */
+};
+typedef struct yyParser yyParser;
+
+#ifndef NDEBUG
+#include <stdio.h>
+static FILE *yyTraceFILE = 0;
+static char *yyTracePrompt = 0;
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/*
+** Turn parser tracing on by giving a stream to which to write the trace
+** and a prompt to preface each trace message. Tracing is turned off
+** by making either argument NULL
+**
+** Inputs:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+** If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+** line of trace output. If NULL, then tracing is
+** turned off.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void ssiexprparserTrace(FILE *TraceFILE, char *zTracePrompt){
+ yyTraceFILE = TraceFILE;
+ yyTracePrompt = zTracePrompt;
+ if( yyTraceFILE==0 ) yyTracePrompt = 0;
+ else if( yyTracePrompt==0 ) yyTraceFILE = 0;
+}
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing shifts, the names of all terminals and nonterminals
+** are required. The following table supplies these names */
+static const char *yyTokenName[] = {
+ "$", "AND", "OR", "EQ",
+ "NE", "GT", "GE", "LT",
+ "LE", "NOT", "LPARAN", "RPARAN",
+ "VALUE", "error", "expr", "value",
+ "exprline", "cond", "input",
+};
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *yyRuleName[] = {
+ /* 0 */ "input ::= exprline",
+ /* 1 */ "exprline ::= expr cond expr",
+ /* 2 */ "exprline ::= expr",
+ /* 3 */ "expr ::= expr AND expr",
+ /* 4 */ "expr ::= expr OR expr",
+ /* 5 */ "expr ::= NOT expr",
+ /* 6 */ "expr ::= LPARAN exprline RPARAN",
+ /* 7 */ "expr ::= value",
+ /* 8 */ "value ::= VALUE",
+ /* 9 */ "value ::= value VALUE",
+ /* 10 */ "cond ::= EQ",
+ /* 11 */ "cond ::= NE",
+ /* 12 */ "cond ::= LE",
+ /* 13 */ "cond ::= GE",
+ /* 14 */ "cond ::= LT",
+ /* 15 */ "cond ::= GT",
+};
+#endif /* NDEBUG */
+
+/*
+** This function returns the symbolic name associated with a token
+** value.
+*/
+const char *ssiexprparserTokenName(int tokenType){
+#ifndef NDEBUG
+ if( tokenType>0 && tokenType<(sizeof(yyTokenName)/sizeof(yyTokenName[0])) ){
+ return yyTokenName[tokenType];
+ }else{
+ return "Unknown";
+ }
+#else
+ return "";
+#endif
+}
+
+/*
+** This function allocates a new parser.
+** The only argument is a pointer to a function which works like
+** malloc.
+**
+** Inputs:
+** A pointer to the function used to allocate memory.
+**
+** Outputs:
+** A pointer to a parser. This pointer is used in subsequent calls
+** to ssiexprparser and ssiexprparserFree.
+*/
+void *ssiexprparserAlloc(void *(*mallocProc)(size_t)){
+ yyParser *pParser;
+ pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) );
+ if( pParser ){
+ pParser->yyidx = -1;
+ }
+ return pParser;
+}
+
+/* The following function deletes the value associated with a
+** symbol. The symbol can be either a terminal or nonterminal.
+** "yymajor" is the symbol code, and "yypminor" is a pointer to
+** the value.
+*/
+static void yy_destructor(YYCODETYPE yymajor, YYMINORTYPE *yypminor){
+ switch( yymajor ){
+ /* Here is inserted the actions which take place when a
+ ** terminal or non-terminal is destroyed. This can happen
+ ** when the symbol is popped from the stack during a
+ ** reduce or during error processing or when a parser is
+ ** being destroyed before it is finished parsing.
+ **
+ ** Note: during a reduce, the only symbols destroyed are those
+ ** which appear on the RHS of the rule, but which are not used
+ ** inside the C code.
+ */
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+#line 24 "./mod_ssi_exprparser.y"
+{ buffer_free((yypminor->yy0)); }
+#line 350 "mod_ssi_exprparser.c"
+ break;
+ default: break; /* If no destructor action specified: do nothing */
+ }
+}
+
+/*
+** Pop the parser's stack once.
+**
+** If there is a destructor routine associated with the token which
+** is popped from the stack, then call it.
+**
+** Return the major token number for the symbol popped.
+*/
+static int yy_pop_parser_stack(yyParser *pParser){
+ YYCODETYPE yymajor;
+ yyStackEntry *yytos = &pParser->yystack[pParser->yyidx];
+
+ if( pParser->yyidx<0 ) return 0;
+#ifndef NDEBUG
+ if( yyTraceFILE && pParser->yyidx>=0 ){
+ fprintf(yyTraceFILE,"%sPopping %s\n",
+ yyTracePrompt,
+ yyTokenName[yytos->major]);
+ }
+#endif
+ yymajor = yytos->major;
+ yy_destructor( yymajor, &yytos->minor);
+ pParser->yyidx--;
+ return yymajor;
+}
+
+/*
+** Deallocate and destroy a parser. Destructors are all called for
+** all stack elements before shutting the parser down.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser. This should be a pointer
+** obtained from ssiexprparserAlloc.
+** <li> A pointer to a function used to reclaim memory obtained
+** from malloc.
+** </ul>
+*/
+void ssiexprparserFree(
+ void *p, /* The parser to be deleted */
+ void (*freeProc)(void*) /* Function used to reclaim memory */
+){
+ yyParser *pParser = (yyParser*)p;
+ if( pParser==0 ) return;
+ while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser);
+ (*freeProc)((void*)pParser);
+}
+
+/*
+** Find the appropriate action for a parser given the terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead. If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_shift_action(
+ yyParser *pParser, /* The parser */
+ int iLookAhead /* The look-ahead token */
+){
+ int i;
+ int stateno = pParser->yystack[pParser->yyidx].stateno;
+
+ /* if( pParser->yyidx<0 ) return YY_NO_ACTION; */
+ i = yy_shift_ofst[stateno];
+ if( i==YY_SHIFT_USE_DFLT ){
+ return yy_default[stateno];
+ }
+ if( iLookAhead==YYNOCODE ){
+ return YY_NO_ACTION;
+ }
+ i += iLookAhead;
+ if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+#ifdef YYFALLBACK
+ int iFallback; /* Fallback token */
+ if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
+ && (iFallback = yyFallback[iLookAhead])!=0 ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
+ yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
+ }
+#endif
+ return yy_find_shift_action(pParser, iFallback);
+ }
+#endif
+ return yy_default[stateno];
+ }else{
+ return yy_action[i];
+ }
+}
+
+/*
+** Find the appropriate action for a parser given the non-terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead. If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_reduce_action(
+ yyParser *pParser, /* The parser */
+ int iLookAhead /* The look-ahead token */
+){
+ int i;
+ int stateno = pParser->yystack[pParser->yyidx].stateno;
+
+ i = yy_reduce_ofst[stateno];
+ if( i==YY_REDUCE_USE_DFLT ){
+ return yy_default[stateno];
+ }
+ if( iLookAhead==YYNOCODE ){
+ return YY_NO_ACTION;
+ }
+ i += iLookAhead;
+ if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+ return yy_default[stateno];
+ }else{
+ return yy_action[i];
+ }
+}
+
+/*
+** Perform a shift action.
+*/
+static void yy_shift(
+ yyParser *yypParser, /* The parser to be shifted */
+ int yyNewState, /* The new state to shift in */
+ int yyMajor, /* The major token to shift in */
+ YYMINORTYPE *yypMinor /* Pointer ot the minor token to shift in */
+){
+ yyStackEntry *yytos;
+ yypParser->yyidx++;
+ if( yypParser->yyidx>=YYSTACKDEPTH ){
+ ssiexprparserARG_FETCH;
+ yypParser->yyidx--;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will execute if the parser
+ ** stack every overflows */
+ ssiexprparserARG_STORE; /* Suppress warning about unused %extra_argument var */
+ return;
+ }
+ yytos = &yypParser->yystack[yypParser->yyidx];
+ yytos->stateno = yyNewState;
+ yytos->major = yyMajor;
+ yytos->minor = *yypMinor;
+#ifndef NDEBUG
+ if( yyTraceFILE && yypParser->yyidx>0 ){
+ int i;
+ fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState);
+ fprintf(yyTraceFILE,"%sStack:",yyTracePrompt);
+ for(i=1; i<=yypParser->yyidx; i++)
+ fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]);
+ fprintf(yyTraceFILE,"\n");
+ }
+#endif
+}
+
+/* The following table contains information about every rule that
+** is used during the reduce.
+*/
+static struct {
+ YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */
+ unsigned char nrhs; /* Number of right-hand side symbols in the rule */
+} yyRuleInfo[] = {
+ { 18, 1 },
+ { 16, 3 },
+ { 16, 1 },
+ { 14, 3 },
+ { 14, 3 },
+ { 14, 2 },
+ { 14, 3 },
+ { 14, 1 },
+ { 15, 1 },
+ { 15, 2 },
+ { 17, 1 },
+ { 17, 1 },
+ { 17, 1 },
+ { 17, 1 },
+ { 17, 1 },
+ { 17, 1 },
+};
+
+static void yy_accept(yyParser*); /* Forward Declaration */
+
+/*
+** Perform a reduce action and the shift that must immediately
+** follow the reduce.
+*/
+static void yy_reduce(
+ yyParser *yypParser, /* The parser */
+ int yyruleno /* Number of the rule by which to reduce */
+){
+ int yygoto; /* The next state */
+ int yyact; /* The next action */
+ YYMINORTYPE yygotominor; /* The LHS of the rule reduced */
+ yyStackEntry *yymsp; /* The top of the parser's stack */
+ int yysize; /* Amount to pop the stack */
+ ssiexprparserARG_FETCH;
+ yymsp = &yypParser->yystack[yypParser->yyidx];
+#ifndef NDEBUG
+ if( yyTraceFILE && yyruleno>=0
+ && yyruleno<sizeof(yyRuleName)/sizeof(yyRuleName[0]) ){
+ fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt,
+ yyRuleName[yyruleno]);
+ }
+#endif /* NDEBUG */
+
+ switch( yyruleno ){
+ /* Beginning here are the reduction cases. A typical example
+ ** follows:
+ ** case 0:
+ ** #line <lineno> <grammarfile>
+ ** { ... } // User supplied code
+ ** #line <lineno> <thisfile>
+ ** break;
+ */
+ case 0:
+#line 31 "./mod_ssi_exprparser.y"
+{
+ ctx->val.bo = ssi_val_tobool(yymsp[0].minor.yy29);
+ ctx->val.type = SSI_TYPE_BOOL;
+
+ ssi_val_free(yymsp[0].minor.yy29);
+}
+#line 586 "mod_ssi_exprparser.c"
+ break;
+ case 1:
+#line 38 "./mod_ssi_exprparser.y"
+{
+ int cmp;
+
+ if (yymsp[-2].minor.yy29->type == SSI_TYPE_STRING &&
+ yymsp[0].minor.yy29->type == SSI_TYPE_STRING) {
+ cmp = strcmp(yymsp[-2].minor.yy29->str->ptr, yymsp[0].minor.yy29->str->ptr);
+ } else {
+ cmp = ssi_val_tobool(yymsp[-2].minor.yy29) - ssi_val_tobool(yymsp[0].minor.yy29);
+ }
+
+ yygotominor.yy29 = yymsp[-2].minor.yy29;
+
+ switch(yymsp[-1].minor.yy8) {
+ case SSI_COND_EQ: yygotominor.yy29->bo = (cmp == 0) ? 1 : 0; break;
+ case SSI_COND_NE: yygotominor.yy29->bo = (cmp != 0) ? 1 : 0; break;
+ case SSI_COND_GE: yygotominor.yy29->bo = (cmp >= 0) ? 1 : 0; break;
+ case SSI_COND_GT: yygotominor.yy29->bo = (cmp > 0) ? 1 : 0; break;
+ case SSI_COND_LE: yygotominor.yy29->bo = (cmp <= 0) ? 1 : 0; break;
+ case SSI_COND_LT: yygotominor.yy29->bo = (cmp < 0) ? 1 : 0; break;
+ }
+
+ yygotominor.yy29->type = SSI_TYPE_BOOL;
+
+ ssi_val_free(yymsp[0].minor.yy29);
+}
+#line 615 "mod_ssi_exprparser.c"
+ break;
+ case 2:
+#line 63 "./mod_ssi_exprparser.y"
+{
+ yygotominor.yy29 = yymsp[0].minor.yy29;
+}
+#line 622 "mod_ssi_exprparser.c"
+ break;
+ case 3:
+#line 66 "./mod_ssi_exprparser.y"
+{
+ int e;
+
+ e = ssi_val_tobool(yymsp[-2].minor.yy29) && ssi_val_tobool(yymsp[0].minor.yy29);
+
+ yygotominor.yy29 = yymsp[-2].minor.yy29;
+ yygotominor.yy29->bo = e;
+ yygotominor.yy29->type = SSI_TYPE_BOOL;
+ ssi_val_free(yymsp[0].minor.yy29);
+}
+#line 636 "mod_ssi_exprparser.c"
+ yy_destructor(1,&yymsp[-1].minor);
+ break;
+ case 4:
+#line 77 "./mod_ssi_exprparser.y"
+{
+ int e;
+
+ e = ssi_val_tobool(yymsp[-2].minor.yy29) || ssi_val_tobool(yymsp[0].minor.yy29);
+
+ yygotominor.yy29 = yymsp[-2].minor.yy29;
+ yygotominor.yy29->bo = e;
+ yygotominor.yy29->type = SSI_TYPE_BOOL;
+ ssi_val_free(yymsp[0].minor.yy29);
+}
+#line 651 "mod_ssi_exprparser.c"
+ yy_destructor(2,&yymsp[-1].minor);
+ break;
+ case 5:
+#line 88 "./mod_ssi_exprparser.y"
+{
+ int e;
+
+ e = !ssi_val_tobool(yymsp[0].minor.yy29);
+
+ yygotominor.yy29 = yymsp[0].minor.yy29;
+ yygotominor.yy29->bo = e;
+ yygotominor.yy29->type = SSI_TYPE_BOOL;
+}
+#line 665 "mod_ssi_exprparser.c"
+ yy_destructor(9,&yymsp[-1].minor);
+ break;
+ case 6:
+#line 97 "./mod_ssi_exprparser.y"
+{
+ yygotominor.yy29 = yymsp[-1].minor.yy29;
+}
+#line 673 "mod_ssi_exprparser.c"
+ yy_destructor(10,&yymsp[-2].minor);
+ yy_destructor(11,&yymsp[0].minor);
+ break;
+ case 7:
+#line 101 "./mod_ssi_exprparser.y"
+{
+ yygotominor.yy29 = ssi_val_init();
+ yygotominor.yy29->str = yymsp[0].minor.yy19;
+ yygotominor.yy29->type = SSI_TYPE_STRING;
+}
+#line 684 "mod_ssi_exprparser.c"
+ break;
+ case 8:
+#line 107 "./mod_ssi_exprparser.y"
+{
+ yygotominor.yy19 = buffer_init_string(yymsp[0].minor.yy0->ptr);
+}
+#line 691 "mod_ssi_exprparser.c"
+ break;
+ case 9:
+#line 111 "./mod_ssi_exprparser.y"
+{
+ yygotominor.yy19 = yymsp[-1].minor.yy19;
+ buffer_append_string_buffer(yygotominor.yy19, yymsp[0].minor.yy0);
+}
+#line 699 "mod_ssi_exprparser.c"
+ break;
+ case 10:
+#line 116 "./mod_ssi_exprparser.y"
+{ yygotominor.yy8 = SSI_COND_EQ; }
+#line 704 "mod_ssi_exprparser.c"
+ yy_destructor(3,&yymsp[0].minor);
+ break;
+ case 11:
+#line 117 "./mod_ssi_exprparser.y"
+{ yygotominor.yy8 = SSI_COND_NE; }
+#line 710 "mod_ssi_exprparser.c"
+ yy_destructor(4,&yymsp[0].minor);
+ break;
+ case 12:
+#line 118 "./mod_ssi_exprparser.y"
+{ yygotominor.yy8 = SSI_COND_LE; }
+#line 716 "mod_ssi_exprparser.c"
+ yy_destructor(8,&yymsp[0].minor);
+ break;
+ case 13:
+#line 119 "./mod_ssi_exprparser.y"
+{ yygotominor.yy8 = SSI_COND_GE; }
+#line 722 "mod_ssi_exprparser.c"
+ yy_destructor(6,&yymsp[0].minor);
+ break;
+ case 14:
+#line 120 "./mod_ssi_exprparser.y"
+{ yygotominor.yy8 = SSI_COND_LT; }
+#line 728 "mod_ssi_exprparser.c"
+ yy_destructor(7,&yymsp[0].minor);
+ break;
+ case 15:
+#line 121 "./mod_ssi_exprparser.y"
+{ yygotominor.yy8 = SSI_COND_GT; }
+#line 734 "mod_ssi_exprparser.c"
+ yy_destructor(5,&yymsp[0].minor);
+ break;
+ };
+ yygoto = yyRuleInfo[yyruleno].lhs;
+ yysize = yyRuleInfo[yyruleno].nrhs;
+ yypParser->yyidx -= yysize;
+ yyact = yy_find_reduce_action(yypParser,yygoto);
+ if( yyact < YYNSTATE ){
+ yy_shift(yypParser,yyact,yygoto,&yygotominor);
+ }else if( yyact == YYNSTATE + YYNRULE + 1 ){
+ yy_accept(yypParser);
+ }
+}
+
+/*
+** The following code executes when the parse fails
+*/
+static void yy_parse_failed(
+ yyParser *yypParser /* The parser */
+){
+ ssiexprparserARG_FETCH;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser fails */
+#line 16 "./mod_ssi_exprparser.y"
+
+ ctx->ok = 0;
+
+#line 768 "mod_ssi_exprparser.c"
+ ssiexprparserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following code executes when a syntax error first occurs.
+*/
+static void yy_syntax_error(
+ yyParser *yypParser, /* The parser */
+ int yymajor, /* The major type of the error token */
+ YYMINORTYPE yyminor /* The minor type of the error token */
+){
+ ssiexprparserARG_FETCH;
+#define TOKEN (yyminor.yy0)
+ ssiexprparserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+ yyParser *yypParser /* The parser */
+){
+ ssiexprparserARG_FETCH;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser accepts */
+ ssiexprparserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "ssiexprparserAlloc" which describes the current state of the parser.
+** The second argument is the major token number. The third is
+** the minor token. The fourth optional argument is whatever the
+** user wants (and specified in the grammar) and is available for
+** use by the action routines.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void ssiexprparser(
+ void *yyp, /* The parser */
+ int yymajor, /* The major token code number */
+ ssiexprparserTOKENTYPE yyminor /* The value for the token */
+ ssiexprparserARG_PDECL /* Optional %extra_argument parameter */
+){
+ YYMINORTYPE yyminorunion;
+ int yyact; /* The parser action. */
+ int yyendofinput; /* True if we are at the end of input */
+ int yyerrorhit = 0; /* True if yymajor has invoked an error */
+ yyParser *yypParser; /* The parser */
+
+ /* (re)initialize the parser, if necessary */
+ yypParser = (yyParser*)yyp;
+ if( yypParser->yyidx<0 ){
+ if( yymajor==0 ) return;
+ yypParser->yyidx = 0;
+ yypParser->yyerrcnt = -1;
+ yypParser->yystack[0].stateno = 0;
+ yypParser->yystack[0].major = 0;
+ }
+ yyminorunion.yy0 = yyminor;
+ yyendofinput = (yymajor==0);
+ ssiexprparserARG_STORE;
+
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+
+ do{
+ yyact = yy_find_shift_action(yypParser,yymajor);
+ if( yyact<YYNSTATE ){
+ yy_shift(yypParser,yyact,yymajor,&yyminorunion);
+ yypParser->yyerrcnt--;
+ if( yyendofinput && yypParser->yyidx>=0 ){
+ yymajor = 0;
+ }else{
+ yymajor = YYNOCODE;
+ }
+ }else if( yyact < YYNSTATE + YYNRULE ){
+ yy_reduce(yypParser,yyact-YYNSTATE);
+ }else if( yyact == YY_ERROR_ACTION ){
+ int yymx;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
+ }
+#endif
+#ifdef YYERRORSYMBOL
+ /* A syntax error has occurred.
+ ** The response to an error depends upon whether or not the
+ ** grammar defines an error token "ERROR".
+ **
+ ** This is what we do if the grammar does define ERROR:
+ **
+ ** * Call the %syntax_error function.
+ **
+ ** * Begin popping the stack until we enter a state where
+ ** it is legal to shift the error symbol, then shift
+ ** the error symbol.
+ **
+ ** * Set the error count to three.
+ **
+ ** * Begin accepting and shifting new tokens. No new error
+ ** processing will occur until three tokens have been
+ ** shifted successfully.
+ **
+ */
+ if( yypParser->yyerrcnt<0 ){
+ yy_syntax_error(yypParser,yymajor,yyminorunion);
+ }
+ yymx = yypParser->yystack[yypParser->yyidx].major;
+ if( yymx==YYERRORSYMBOL || yyerrorhit ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sDiscard input token %s\n",
+ yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+ yy_destructor(yymajor,&yyminorunion);
+ yymajor = YYNOCODE;
+ }else{
+ while(
+ yypParser->yyidx >= 0 &&
+ yymx != YYERRORSYMBOL &&
+ (yyact = yy_find_shift_action(yypParser,YYERRORSYMBOL)) >= YYNSTATE
+ ){
+ yy_pop_parser_stack(yypParser);
+ }
+ if( yypParser->yyidx < 0 || yymajor==0 ){
+ yy_destructor(yymajor,&yyminorunion);
+ yy_parse_failed(yypParser);
+ yymajor = YYNOCODE;
+ }else if( yymx!=YYERRORSYMBOL ){
+ YYMINORTYPE u2;
+ u2.YYERRSYMDT = 0;
+ yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2);
+ }
+ }
+ yypParser->yyerrcnt = 3;
+ yyerrorhit = 1;
+#else /* YYERRORSYMBOL is not defined */
+ /* This is what we do if the grammar does not define ERROR:
+ **
+ ** * Report an error message, and throw away the input token.
+ **
+ ** * If the input token is $, then fail the parse.
+ **
+ ** As before, subsequent error messages are suppressed until
+ ** three input tokens have been successfully shifted.
+ */
+ if( yypParser->yyerrcnt<=0 ){
+ yy_syntax_error(yypParser,yymajor,yyminorunion);
+ }
+ yypParser->yyerrcnt = 3;
+ yy_destructor(yymajor,&yyminorunion);
+ if( yyendofinput ){
+ yy_parse_failed(yypParser);
+ }
+ yymajor = YYNOCODE;
+#endif
+ }else{
+ yy_accept(yypParser);
+ yymajor = YYNOCODE;
+ }
+ }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 );
+ return;
+}
diff --git a/src/mod_ssi_exprparser.h b/src/mod_ssi_exprparser.h
new file mode 100644
index 0000000..eb55ea5
--- /dev/null
+++ b/src/mod_ssi_exprparser.h
@@ -0,0 +1,12 @@
+#define TK_AND 1
+#define TK_OR 2
+#define TK_EQ 3
+#define TK_NE 4
+#define TK_GT 5
+#define TK_GE 6
+#define TK_LT 7
+#define TK_LE 8
+#define TK_NOT 9
+#define TK_LPARAN 10
+#define TK_RPARAN 11
+#define TK_VALUE 12
diff --git a/src/mod_ssi_exprparser.y b/src/mod_ssi_exprparser.y
new file mode 100644
index 0000000..c123941
--- /dev/null
+++ b/src/mod_ssi_exprparser.y
@@ -0,0 +1,121 @@
+%token_prefix TK_
+%token_type {buffer *}
+%extra_argument {ssi_ctx_t *ctx}
+%name ssiexprparser
+
+%include {
+#include <assert.h>
+#include <string.h>
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "mod_ssi_expr.h"
+#include "buffer.h"
+}
+
+%parse_failure {
+ ctx->ok = 0;
+}
+
+%type expr { ssi_val_t * }
+%type value { buffer * }
+%type exprline { ssi_val_t * }
+%type cond { int }
+%token_destructor { buffer_free($$); }
+
+%left AND.
+%left OR.
+%nonassoc EQ NE GT GE LT LE.
+%right NOT.
+
+input ::= exprline(B). {
+ ctx->val.bo = ssi_val_tobool(B);
+ ctx->val.type = SSI_TYPE_BOOL;
+
+ ssi_val_free(B);
+}
+
+exprline(A) ::= expr(B) cond(C) expr(D). {
+ int cmp;
+
+ if (B->type == SSI_TYPE_STRING &&
+ D->type == SSI_TYPE_STRING) {
+ cmp = strcmp(B->str->ptr, D->str->ptr);
+ } else {
+ cmp = ssi_val_tobool(B) - ssi_val_tobool(D);
+ }
+
+ A = B;
+
+ switch(C) {
+ case SSI_COND_EQ: A->bo = (cmp == 0) ? 1 : 0; break;
+ case SSI_COND_NE: A->bo = (cmp != 0) ? 1 : 0; break;
+ case SSI_COND_GE: A->bo = (cmp >= 0) ? 1 : 0; break;
+ case SSI_COND_GT: A->bo = (cmp > 0) ? 1 : 0; break;
+ case SSI_COND_LE: A->bo = (cmp <= 0) ? 1 : 0; break;
+ case SSI_COND_LT: A->bo = (cmp < 0) ? 1 : 0; break;
+ }
+
+ A->type = SSI_TYPE_BOOL;
+
+ ssi_val_free(D);
+}
+exprline(A) ::= expr(B). {
+ A = B;
+}
+expr(A) ::= expr(B) AND expr(C). {
+ int e;
+
+ e = ssi_val_tobool(B) && ssi_val_tobool(C);
+
+ A = B;
+ A->bo = e;
+ A->type = SSI_TYPE_BOOL;
+ ssi_val_free(C);
+}
+
+expr(A) ::= expr(B) OR expr(C). {
+ int e;
+
+ e = ssi_val_tobool(B) || ssi_val_tobool(C);
+
+ A = B;
+ A->bo = e;
+ A->type = SSI_TYPE_BOOL;
+ ssi_val_free(C);
+}
+
+expr(A) ::= NOT expr(B). {
+ int e;
+
+ e = !ssi_val_tobool(B);
+
+ A = B;
+ A->bo = e;
+ A->type = SSI_TYPE_BOOL;
+}
+expr(A) ::= LPARAN exprline(B) RPARAN. {
+ A = B;
+}
+
+expr(A) ::= value(B). {
+ A = ssi_val_init();
+ A->str = B;
+ A->type = SSI_TYPE_STRING;
+}
+
+value(A) ::= VALUE(B). {
+ A = buffer_init_string(B->ptr);
+}
+
+value(A) ::= value(B) VALUE(C). {
+ A = B;
+ buffer_append_string_buffer(A, C);
+}
+
+cond(A) ::= EQ. { A = SSI_COND_EQ; }
+cond(A) ::= NE. { A = SSI_COND_NE; }
+cond(A) ::= LE. { A = SSI_COND_LE; }
+cond(A) ::= GE. { A = SSI_COND_GE; }
+cond(A) ::= LT. { A = SSI_COND_LT; }
+cond(A) ::= GT. { A = SSI_COND_GT; }
diff --git a/src/mod_staticfile.c b/src/mod_staticfile.c
new file mode 100644
index 0000000..6fac65b
--- /dev/null
+++ b/src/mod_staticfile.c
@@ -0,0 +1,492 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "stat_cache.h"
+#include "etag.h"
+#include "http_chunk.h"
+#include "response.h"
+
+/**
+ * this is a staticfile for a lighttpd plugin
+ *
+ */
+
+
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ array *exclude_ext;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *range_buf;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_staticfile_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->range_buf = buffer_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_staticfile_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->exclude_ext);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+ buffer_free(p->range_buf);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_staticfile_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "static-file.exclude-extensions", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->exclude_ext = array_init();
+
+ cv[0].destination = s->exclude_ext;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_staticfile_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(exclude_ext);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("static-file.exclude-extensions"))) {
+ PATCH(exclude_ext);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+static int http_response_parse_range(server *srv, connection *con, plugin_data *p) {
+ int multipart = 0;
+ int error;
+ off_t start, end;
+ const char *s, *minus;
+ char *boundary = "fkj49sn38dcn3";
+ data_string *ds;
+ stat_cache_entry *sce = NULL;
+ buffer *content_type = NULL;
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
+ SEGFAULT();
+ }
+
+ start = 0;
+ end = sce->st.st_size - 1;
+
+ con->response.content_length = 0;
+
+ if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) {
+ content_type = ds->value;
+ }
+
+ for (s = con->request.http_range, error = 0;
+ !error && *s && NULL != (minus = strchr(s, '-')); ) {
+ char *err;
+ off_t la, le;
+
+ if (s == minus) {
+ /* -<stop> */
+
+ le = strtoll(s, &err, 10);
+
+ if (le == 0) {
+ /* RFC 2616 - 14.35.1 */
+
+ con->http_status = 416;
+ error = 1;
+ } else if (*err == '\0') {
+ /* end */
+ s = err;
+
+ end = sce->st.st_size - 1;
+ start = sce->st.st_size + le;
+ } else if (*err == ',') {
+ multipart = 1;
+ s = err + 1;
+
+ end = sce->st.st_size - 1;
+ start = sce->st.st_size + le;
+ } else {
+ error = 1;
+ }
+
+ } else if (*(minus+1) == '\0' || *(minus+1) == ',') {
+ /* <start>- */
+
+ la = strtoll(s, &err, 10);
+
+ if (err == minus) {
+ /* ok */
+
+ if (*(err + 1) == '\0') {
+ s = err + 1;
+
+ end = sce->st.st_size - 1;
+ start = la;
+
+ } else if (*(err + 1) == ',') {
+ multipart = 1;
+ s = err + 2;
+
+ end = sce->st.st_size - 1;
+ start = la;
+ } else {
+ error = 1;
+ }
+ } else {
+ /* error */
+ error = 1;
+ }
+ } else {
+ /* <start>-<stop> */
+
+ la = strtoll(s, &err, 10);
+
+ if (err == minus) {
+ le = strtoll(minus+1, &err, 10);
+
+ /* RFC 2616 - 14.35.1 */
+ if (la > le) {
+ error = 1;
+ }
+
+ if (*err == '\0') {
+ /* ok, end*/
+ s = err;
+
+ end = le;
+ start = la;
+ } else if (*err == ',') {
+ multipart = 1;
+ s = err + 1;
+
+ end = le;
+ start = la;
+ } else {
+ /* error */
+
+ error = 1;
+ }
+ } else {
+ /* error */
+
+ error = 1;
+ }
+ }
+
+ if (!error) {
+ if (start < 0) start = 0;
+
+ /* RFC 2616 - 14.35.1 */
+ if (end > sce->st.st_size - 1) end = sce->st.st_size - 1;
+
+ if (start > sce->st.st_size - 1) {
+ error = 1;
+
+ con->http_status = 416;
+ }
+ }
+
+ if (!error) {
+ if (multipart) {
+ /* write boundary-header */
+ buffer *b;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ buffer_copy_string(b, "\r\n--");
+ buffer_append_string(b, boundary);
+
+ /* write Content-Range */
+ buffer_append_string(b, "\r\nContent-Range: bytes ");
+ buffer_append_off_t(b, start);
+ buffer_append_string(b, "-");
+ buffer_append_off_t(b, end);
+ buffer_append_string(b, "/");
+ buffer_append_off_t(b, sce->st.st_size);
+
+ buffer_append_string(b, "\r\nContent-Type: ");
+ buffer_append_string_buffer(b, content_type);
+
+ /* write END-OF-HEADER */
+ buffer_append_string(b, "\r\n\r\n");
+
+ con->response.content_length += b->used - 1;
+
+ }
+
+ chunkqueue_append_file(con->write_queue, con->physical.path, start, end - start + 1);
+ con->response.content_length += end - start + 1;
+ }
+ }
+
+ /* something went wrong */
+ if (error) return -1;
+
+ if (multipart) {
+ /* add boundary end */
+ buffer *b;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ buffer_copy_string_len(b, "\r\n--", 4);
+ buffer_append_string(b, boundary);
+ buffer_append_string_len(b, "--\r\n", 4);
+
+ con->response.content_length += b->used - 1;
+
+ /* set header-fields */
+
+ buffer_copy_string(p->range_buf, "multipart/byteranges; boundary=");
+ buffer_append_string(p->range_buf, boundary);
+
+ /* overwrite content-type */
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf));
+ } else {
+ /* add Content-Range-header */
+
+ buffer_copy_string(p->range_buf, "bytes ");
+ buffer_append_off_t(p->range_buf, start);
+ buffer_append_string(p->range_buf, "-");
+ buffer_append_off_t(p->range_buf, end);
+ buffer_append_string(p->range_buf, "/");
+ buffer_append_off_t(p->range_buf, sce->st.st_size);
+
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf));
+ }
+
+ /* ok, the file is set-up */
+ return 0;
+}
+
+URIHANDLER_FUNC(mod_staticfile_subrequest) {
+ plugin_data *p = p_d;
+ size_t k;
+ int s_len;
+ stat_cache_entry *sce = NULL;
+ buffer *mtime;
+ data_string *ds;
+
+ /* someone else has done a decision for us */
+ if (con->http_status != 0) return HANDLER_GO_ON;
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+ if (con->physical.path->used == 0) return HANDLER_GO_ON;
+
+ /* someone else has handled this request */
+ if (con->mode != DIRECT) return HANDLER_GO_ON;
+
+ /* we only handle GET, POST and HEAD */
+ switch(con->request.http_method) {
+ case HTTP_METHOD_GET:
+ case HTTP_METHOD_POST:
+ case HTTP_METHOD_HEAD:
+ break;
+ default:
+ return HANDLER_GO_ON;
+ }
+
+ mod_staticfile_patch_connection(srv, con, p);
+
+ s_len = con->uri.path->used - 1;
+
+ /* ignore certain extensions */
+ for (k = 0; k < p->conf.exclude_ext->used; k++) {
+ ds = (data_string *)p->conf.exclude_ext->data[k];
+
+ if (ds->value->used == 0) continue;
+
+ if (buffer_is_equal_right_len(con->physical.path, ds->value, ds->value->used - 1)) {
+ return HANDLER_GO_ON;
+ }
+ }
+
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- handling file as static file");
+ }
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
+ con->http_status = 403;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbsb",
+ "not a regular file:", con->uri.path,
+ "->", con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+
+ /* we only handline regular files */
+ if (!S_ISREG(sce->st.st_mode)) {
+ con->http_status = 404;
+
+ if (con->conf.log_file_not_found) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsb",
+ "not a regular file:", con->uri.path,
+ "->", sce->name);
+ }
+
+ return HANDLER_FINISHED;
+ }
+
+ /* mod_compress might set several data directly, don't overwrite them */
+
+ /* set response content-type, if not set already */
+
+ if (NULL == array_get_element(con->response.headers, "Content-Type")) {
+ if (buffer_is_empty(sce->content_type)) {
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
+ } else {
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
+ }
+ }
+
+ if (NULL == array_get_element(con->response.headers, "ETag")) {
+ /* generate e-tag */
+ etag_mutate(con->physical.etag, sce->etag);
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
+ }
+ response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes"));
+
+ /* prepare header */
+ if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) {
+ mtime = strftime_cache_get(srv, sce->st.st_mtime);
+ response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
+ } else {
+ mtime = ds->value;
+ }
+
+ if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
+ return HANDLER_FINISHED;
+ } else if (con->request.http_range && con->conf.range_requests) {
+ int do_range_request = 1;
+ /* check if we have a conditional GET */
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If-Range"))) {
+ /* if the value is the same as our ETag, we do a Range-request,
+ * otherwise a full 200 */
+
+ if (!buffer_is_equal(ds->value, con->physical.etag)) {
+ do_range_request = 0;
+ }
+ }
+
+ if (do_range_request) {
+ /* content prepared, I'm done */
+ con->file_finished = 1;
+
+ if (0 == http_response_parse_range(srv, con, p)) {
+ con->http_status = 206;
+ }
+ return HANDLER_FINISHED;
+ }
+ }
+
+ /* if we are still here, prepare body */
+
+ /* we add it here for all requests
+ * the HEAD request will drop it afterwards again
+ */
+ http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size);
+
+ con->file_finished = 1;
+
+ return HANDLER_FINISHED;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_staticfile_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("staticfile");
+
+ p->init = mod_staticfile_init;
+ p->handle_subrequest_start = mod_staticfile_subrequest;
+ p->set_defaults = mod_staticfile_set_defaults;
+ p->cleanup = mod_staticfile_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_status.c b/src/mod_status.c
new file mode 100644
index 0000000..f5a35e9
--- /dev/null
+++ b/src/mod_status.c
@@ -0,0 +1,841 @@
+#define _GNU_SOURCE
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+#include <stdio.h>
+
+#include "server.h"
+#include "connections.h"
+#include "response.h"
+#include "connections.h"
+#include "log.h"
+
+#include "plugin.h"
+
+#include "inet_ntop_cache.h"
+
+typedef struct {
+ buffer *config_url;
+ buffer *status_url;
+ buffer *statistics_url;
+
+ int sort;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ double traffic_out;
+ double requests;
+
+ double mod_5s_traffic_out[5];
+ double mod_5s_requests[5];
+ size_t mod_5s_ndx;
+
+ double rel_traffic_out;
+ double rel_requests;
+
+ double abs_traffic_out;
+ double abs_requests;
+
+ double bytes_written;
+
+ buffer *module_list;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_status_init) {
+ plugin_data *p;
+ size_t i;
+
+ p = calloc(1, sizeof(*p));
+
+ p->traffic_out = p->requests = 0;
+ p->rel_traffic_out = p->rel_requests = 0;
+ p->abs_traffic_out = p->abs_requests = 0;
+ p->bytes_written = 0;
+ p->module_list = buffer_init();
+
+ for (i = 0; i < 5; i++) {
+ p->mod_5s_traffic_out[i] = p->mod_5s_requests[i] = 0;
+ }
+
+ return p;
+}
+
+FREE_FUNC(mod_status_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ buffer_free(p->module_list);
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free(s->status_url);
+ buffer_free(s->statistics_url);
+ buffer_free(s->config_url);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_status_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ config_values_t cv[] = {
+ { "status.status-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "status.config-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "status.enable-sort", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
+ { "status.statistics-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->config_url = buffer_init();
+ s->status_url = buffer_init();
+ s->sort = 1;
+ s->statistics_url = buffer_init();
+
+ cv[0].destination = s->status_url;
+ cv[1].destination = s->config_url;
+ cv[2].destination = &(s->sort);
+ cv[3].destination = s->statistics_url;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+
+static int mod_status_row_append(buffer *b, const char *key, const char *value) {
+ BUFFER_APPEND_STRING_CONST(b, " <tr>\n");
+ BUFFER_APPEND_STRING_CONST(b, " <td><b>");
+ buffer_append_string(b, key);
+ BUFFER_APPEND_STRING_CONST(b, "</b></td>\n");
+ BUFFER_APPEND_STRING_CONST(b, " <td>");
+ buffer_append_string(b, value);
+ BUFFER_APPEND_STRING_CONST(b, "</td>\n");
+ BUFFER_APPEND_STRING_CONST(b, " </tr>\n");
+
+ return 0;
+}
+
+static int mod_status_header_append(buffer *b, const char *key) {
+ BUFFER_APPEND_STRING_CONST(b, " <tr>\n");
+ BUFFER_APPEND_STRING_CONST(b, " <th colspan=\"2\">");
+ buffer_append_string(b, key);
+ BUFFER_APPEND_STRING_CONST(b, "</th>\n");
+ BUFFER_APPEND_STRING_CONST(b, " </tr>\n");
+
+ return 0;
+}
+
+static int mod_status_header_append_sort(buffer *b, void *p_d, const char* key) {
+ plugin_data *p = p_d;
+
+ if (p->conf.sort) {
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\"><a href=\"#\" class=\"sortheader\" onclick=\"resort(this);return false;\">");
+ buffer_append_string(b, key);
+ BUFFER_APPEND_STRING_CONST(b, "<span class=\"sortarrow\"></span></a></th>\n");
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">");
+ buffer_append_string(b, key);
+ BUFFER_APPEND_STRING_CONST(b, "</th>\n");
+ }
+
+ return 0;
+}
+
+static int mod_status_get_multiplier(double *avg, char *multiplier, int size) {
+ *multiplier = ' ';
+
+ if (*avg > size) { *avg /= size; *multiplier = 'k'; }
+ if (*avg > size) { *avg /= size; *multiplier = 'M'; }
+ if (*avg > size) { *avg /= size; *multiplier = 'G'; }
+ if (*avg > size) { *avg /= size; *multiplier = 'T'; }
+ if (*avg > size) { *avg /= size; *multiplier = 'P'; }
+ if (*avg > size) { *avg /= size; *multiplier = 'E'; }
+ if (*avg > size) { *avg /= size; *multiplier = 'Z'; }
+ if (*avg > size) { *avg /= size; *multiplier = 'Y'; }
+
+ return 0;
+}
+
+static handler_t mod_status_handle_server_status_html(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ buffer *b;
+ size_t j;
+ double avg;
+ char multiplier = '\0';
+ char buf[32];
+ time_t ts;
+
+ int days, hours, mins, seconds;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ BUFFER_COPY_STRING_CONST(b,
+ "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
+ " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
+ " <head>\n"
+ " <title>Status</title>\n");
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " <style type=\"text/css\">\n"
+ " table.status { border: black solid thin; }\n"
+ " td.int { background-color: #f0f0f0; text-align: right }\n"
+ " td.string { background-color: #f0f0f0; text-align: left }\n"
+ " th.status { background-color: black; color: white; font-weight: bold; }\n"
+ " a.sortheader { background-color: black; color: white; font-weight: bold; text-decoration: none; display: block; }\n"
+ " span.sortarrow { color: white; text-decoration: none; }\n"
+ " </style>\n");
+
+ if (p->conf.sort) {
+ BUFFER_APPEND_STRING_CONST(b,
+ "<script type=\"text/javascript\">\n"
+ "// <!--\n"
+ "var sort_column;\n"
+ "var prev_span = null;\n");
+
+ BUFFER_APPEND_STRING_CONST(b,
+ "function get_inner_text(el) {\n"
+ " if((typeof el == 'string')||(typeof el == 'undefined'))\n"
+ " return el;\n"
+ " if(el.innerText)\n"
+ " return el.innerText;\n"
+ " else {\n"
+ " var str = \"\";\n"
+ " var cs = el.childNodes;\n"
+ " var l = cs.length;\n"
+ " for (i=0;i<l;i++) {\n"
+ " if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n"
+ " else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n"
+ " }\n"
+ " }\n"
+ " return str;\n"
+ "}\n");
+
+ BUFFER_APPEND_STRING_CONST(b,
+ "function sortfn(a,b) {\n"
+ " var at = get_inner_text(a.cells[sort_column]);\n"
+ " var bt = get_inner_text(b.cells[sort_column]);\n"
+ " if (a.cells[sort_column].className == 'int') {\n"
+ " return parseInt(at)-parseInt(bt);\n"
+ " } else {\n"
+ " aa = at.toLowerCase();\n"
+ " bb = bt.toLowerCase();\n"
+ " if (aa==bb) return 0;\n"
+ " else if (aa<bb) return -1;\n"
+ " else return 1;\n"
+ " }\n"
+ "}\n");
+
+ BUFFER_APPEND_STRING_CONST(b,
+ "function resort(lnk) {\n"
+ " var span = lnk.childNodes[1];\n"
+ " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n"
+ " var rows = new Array();\n"
+ " for (j=1;j<table.rows.length;j++)\n"
+ " rows[j-1] = table.rows[j];\n"
+ " sort_column = lnk.parentNode.cellIndex;\n"
+ " rows.sort(sortfn);\n");
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " if (prev_span != null) prev_span.innerHTML = '';\n"
+ " if (span.getAttribute('sortdir')=='down') {\n"
+ " span.innerHTML = '&uarr;';\n"
+ " span.setAttribute('sortdir','up');\n"
+ " rows.reverse();\n"
+ " } else {\n"
+ " span.innerHTML = '&darr;';\n"
+ " span.setAttribute('sortdir','down');\n"
+ " }\n"
+ " for (i=0;i<rows.length;i++)\n"
+ " table.tBodies[0].appendChild(rows[i]);\n"
+ " prev_span = span;\n"
+ "}\n"
+ "// -->\n"
+ "</script>\n");
+ }
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " </head>\n"
+ " <body>\n");
+
+
+
+ /* connection listing */
+ BUFFER_APPEND_STRING_CONST(b, "<h1>Server-Status</h1>");
+
+ BUFFER_APPEND_STRING_CONST(b, "<table class=\"status\">");
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Hostname</td><td class=\"string\">");
+ buffer_append_string_buffer(b, con->uri.authority);
+ BUFFER_APPEND_STRING_CONST(b, " (");
+ buffer_append_string_buffer(b, con->server_name);
+ BUFFER_APPEND_STRING_CONST(b, ")</td></tr>\n");
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Uptime</td><td class=\"string\">");
+
+ ts = srv->cur_ts - srv->startup_ts;
+
+ days = ts / (60 * 60 * 24);
+ ts %= (60 * 60 * 24);
+
+ hours = ts / (60 * 60);
+ ts %= (60 * 60);
+
+ mins = ts / (60);
+ ts %= (60);
+
+ seconds = ts;
+
+ if (days) {
+ buffer_append_long(b, days);
+ BUFFER_APPEND_STRING_CONST(b, " days ");
+ }
+
+ if (hours) {
+ buffer_append_long(b, hours);
+ BUFFER_APPEND_STRING_CONST(b, " hours ");
+ }
+
+ if (mins) {
+ buffer_append_long(b, mins);
+ BUFFER_APPEND_STRING_CONST(b, " min ");
+ }
+
+ buffer_append_long(b, seconds);
+ BUFFER_APPEND_STRING_CONST(b, " s");
+
+ BUFFER_APPEND_STRING_CONST(b, "</td></tr>\n");
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Started at</td><td class=\"string\">");
+
+ ts = srv->startup_ts;
+
+ strftime(buf, sizeof(buf) - 1, "%Y-%m-%d %H:%M:%S", localtime(&ts));
+ buffer_append_string(b, buf);
+ BUFFER_APPEND_STRING_CONST(b, "</td></tr>\n");
+
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><th colspan=\"2\">absolute (since start)</th></tr>\n");
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Requests</td><td class=\"string\">");
+ avg = p->abs_requests;
+
+ mod_status_get_multiplier(&avg, &multiplier, 1000);
+
+ buffer_append_long(b, avg);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ if (multiplier) buffer_append_string_len(b, &multiplier, 1);
+ BUFFER_APPEND_STRING_CONST(b, "req</td></tr>\n");
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Traffic</td><td class=\"string\">");
+ avg = p->abs_traffic_out;
+
+ mod_status_get_multiplier(&avg, &multiplier, 1024);
+
+ sprintf(buf, "%.2f", avg);
+ buffer_append_string(b, buf);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ if (multiplier) buffer_append_string_len(b, &multiplier, 1);
+ BUFFER_APPEND_STRING_CONST(b, "byte</td></tr>\n");
+
+
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><th colspan=\"2\">average (since start)</th></tr>\n");
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Requests</td><td class=\"string\">");
+ avg = p->abs_requests / (srv->cur_ts - srv->startup_ts);
+
+ mod_status_get_multiplier(&avg, &multiplier, 1000);
+
+ buffer_append_long(b, avg);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ if (multiplier) buffer_append_string_len(b, &multiplier, 1);
+ BUFFER_APPEND_STRING_CONST(b, "req/s</td></tr>\n");
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Traffic</td><td class=\"string\">");
+ avg = p->abs_traffic_out / (srv->cur_ts - srv->startup_ts);
+
+ mod_status_get_multiplier(&avg, &multiplier, 1024);
+
+ sprintf(buf, "%.2f", avg);
+ buffer_append_string(b, buf);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ if (multiplier) buffer_append_string_len(b, &multiplier, 1);
+ BUFFER_APPEND_STRING_CONST(b, "byte/s</td></tr>\n");
+
+
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><th colspan=\"2\">average (5s sliding average)</th></tr>\n");
+ for (j = 0, avg = 0; j < 5; j++) {
+ avg += p->mod_5s_requests[j];
+ }
+
+ avg /= 5;
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Requests</td><td class=\"string\">");
+
+ mod_status_get_multiplier(&avg, &multiplier, 1000);
+
+ buffer_append_long(b, avg);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ if (multiplier) buffer_append_string_len(b, &multiplier, 1);
+
+ BUFFER_APPEND_STRING_CONST(b, "req/s</td></tr>\n");
+
+ for (j = 0, avg = 0; j < 5; j++) {
+ avg += p->mod_5s_traffic_out[j];
+ }
+
+ avg /= 5;
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Traffic</td><td class=\"string\">");
+
+ mod_status_get_multiplier(&avg, &multiplier, 1024);
+
+ sprintf(buf, "%.2f", avg);
+ buffer_append_string(b, buf);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ if (multiplier) buffer_append_string_len(b, &multiplier, 1);
+ BUFFER_APPEND_STRING_CONST(b, "byte/s</td></tr>\n");
+
+ BUFFER_APPEND_STRING_CONST(b, "</table>\n");
+
+
+ BUFFER_APPEND_STRING_CONST(b, "<hr />\n<pre><b>legend</b>\n");
+ BUFFER_APPEND_STRING_CONST(b, ". = connect, C = close, E = hard error\n");
+ BUFFER_APPEND_STRING_CONST(b, "r = read, R = read-POST, W = write, h = handle-request\n");
+ BUFFER_APPEND_STRING_CONST(b, "q = request-start, Q = request-end\n");
+ BUFFER_APPEND_STRING_CONST(b, "s = response-start, S = response-end\n");
+
+ BUFFER_APPEND_STRING_CONST(b, "<b>");
+ buffer_append_long(b, srv->conns->used);
+ BUFFER_APPEND_STRING_CONST(b, " connections</b>\n");
+
+ for (j = 0; j < srv->conns->used; j++) {
+ connection *c = srv->conns->ptr[j];
+ const char *state = connection_get_short_state(c->state);
+
+ buffer_append_string_len(b, state, 1);
+
+ if (((j + 1) % 50) == 0) {
+ BUFFER_APPEND_STRING_CONST(b, "\n");
+ }
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "\n</pre><hr />\n<h2>Connections</h2>\n");
+
+ BUFFER_APPEND_STRING_CONST(b, "<table class=\"status\">\n");
+ BUFFER_APPEND_STRING_CONST(b, "<tr>");
+ mod_status_header_append_sort(b, p_d, "Client IP");
+ mod_status_header_append_sort(b, p_d, "Read");
+ mod_status_header_append_sort(b, p_d, "Written");
+ mod_status_header_append_sort(b, p_d, "State");
+ mod_status_header_append_sort(b, p_d, "Time");
+ mod_status_header_append_sort(b, p_d, "Host");
+ mod_status_header_append_sort(b, p_d, "URI");
+ mod_status_header_append_sort(b, p_d, "File");
+ BUFFER_APPEND_STRING_CONST(b, "</tr>\n");
+
+ for (j = 0; j < srv->conns->used; j++) {
+ connection *c = srv->conns->ptr[j];
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td class=\"string\">");
+
+ buffer_append_string(b, inet_ntop_cache_get_ip(srv, &(c->dst_addr)));
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"int\">");
+
+ if (con->request.content_length) {
+ buffer_append_long(b, c->request_content_queue->bytes_in);
+ BUFFER_APPEND_STRING_CONST(b, "/");
+ buffer_append_long(b, c->request.content_length);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "0/0");
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"int\">");
+
+ buffer_append_off_t(b, chunkqueue_written(c->write_queue));
+ BUFFER_APPEND_STRING_CONST(b, "/");
+ buffer_append_off_t(b, chunkqueue_length(c->write_queue));
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">");
+
+ buffer_append_string(b, connection_get_state(c->state));
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"int\">");
+
+ buffer_append_long(b, srv->cur_ts - c->request_start);
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">");
+
+ if (buffer_is_empty(c->server_name)) {
+ buffer_append_string_buffer(b, c->uri.authority);
+ }
+ else {
+ buffer_append_string_buffer(b, c->server_name);
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">");
+
+ if (!buffer_is_empty(c->uri.path)) {
+ buffer_append_string_encoded(b, CONST_BUF_LEN(c->uri.path), ENCODING_HTML);
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">");
+
+ buffer_append_string_buffer(b, c->physical.path);
+
+ BUFFER_APPEND_STRING_CONST(b, "</td></tr>\n");
+ }
+
+
+ BUFFER_APPEND_STRING_CONST(b,
+ "</table>\n");
+
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " </body>\n"
+ "</html>\n"
+ );
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+
+ return 0;
+}
+
+
+static handler_t mod_status_handle_server_status_text(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ buffer *b;
+ double avg;
+ time_t ts;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ /* output total number of requests */
+ BUFFER_APPEND_STRING_CONST(b, "Total Accesses: ");
+ avg = p->abs_requests;
+ buffer_append_long(b, avg);
+ BUFFER_APPEND_STRING_CONST(b, "\n");
+
+ /* output total traffic out in kbytes */
+ BUFFER_APPEND_STRING_CONST(b, "Total kBytes: ");
+ avg = p->abs_traffic_out / 1024;
+ buffer_append_long(b, avg);
+ BUFFER_APPEND_STRING_CONST(b, "\n");
+
+ /* output uptime */
+ BUFFER_APPEND_STRING_CONST(b, "Uptime: ");
+ ts = srv->cur_ts - srv->startup_ts;
+ buffer_append_long(b, ts);
+ BUFFER_APPEND_STRING_CONST(b, "\n");
+
+ /* output busy servers */
+ BUFFER_APPEND_STRING_CONST(b, "BusyServers: ");
+ buffer_append_long(b, srv->conns->used);
+ BUFFER_APPEND_STRING_CONST(b, "\n");
+
+ /* set text/plain output */
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain"));
+
+ return 0;
+}
+
+static handler_t mod_status_handle_server_statistics(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ buffer *b, *m = p->module_list;
+ size_t i;
+ array *st = srv->status;
+
+ if (0 == st->used) {
+ /* we have nothing to send */
+ con->http_status = 204;
+ con->file_finished = 1;
+
+ return HANDLER_FINISHED;
+ }
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ for (i = 0; i < st->used; i++) {
+ size_t ndx = st->sorted[i];
+
+ buffer_append_string_buffer(b, st->data[ndx]->key);
+ buffer_append_string(b, ": ");
+ buffer_append_long(b, ((data_integer *)(st->data[ndx]))->value);
+ buffer_append_string(b, "\n");
+ }
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain"));
+
+ con->http_status = 200;
+ con->file_finished = 1;
+
+ return HANDLER_FINISHED;
+}
+
+
+static handler_t mod_status_handle_server_status(server *srv, connection *con, void *p_d) {
+
+ if (buffer_is_equal_string(con->uri.query, CONST_STR_LEN("auto"))) {
+ mod_status_handle_server_status_text(srv, con, p_d);
+ } else {
+ mod_status_handle_server_status_html(srv, con, p_d);
+ }
+
+ con->http_status = 200;
+ con->file_finished = 1;
+
+ return HANDLER_FINISHED;
+}
+
+
+static handler_t mod_status_handle_server_config(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ buffer *b, *m = p->module_list;
+ size_t i;
+
+ struct ev_map { fdevent_handler_t et; const char *name; } event_handlers[] =
+ {
+ /* - poll is most reliable
+ * - select works everywhere
+ * - linux-* are experimental
+ */
+#ifdef USE_POLL
+ { FDEVENT_HANDLER_POLL, "poll" },
+#endif
+#ifdef USE_SELECT
+ { FDEVENT_HANDLER_SELECT, "select" },
+#endif
+#ifdef USE_LINUX_EPOLL
+ { FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll" },
+#endif
+#ifdef USE_LINUX_SIGIO
+ { FDEVENT_HANDLER_LINUX_RTSIG, "linux-rtsig" },
+#endif
+#ifdef USE_SOLARIS_DEVPOLL
+ { FDEVENT_HANDLER_SOLARIS_DEVPOLL,"solaris-devpoll" },
+#endif
+#ifdef USE_FREEBSD_KQUEUE
+ { FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue" },
+#endif
+ { FDEVENT_HANDLER_UNSET, NULL }
+ };
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ BUFFER_COPY_STRING_CONST(b,
+ "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
+ " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
+ " <head>\n"
+ " <title>Status</title>\n"
+ " </head>\n"
+ " <body>\n"
+ " <h1>" PACKAGE_NAME " " PACKAGE_VERSION "</h1>\n"
+ " <table border=\"1\">\n");
+
+ mod_status_header_append(b, "Server-Features");
+#ifdef HAVE_PCRE_H
+ mod_status_row_append(b, "RegEx Conditionals", "enabled");
+#else
+ mod_status_row_append(b, "RegEx Conditionals", "disabled - pcre missing");
+#endif
+ mod_status_header_append(b, "Network Engine");
+
+ for (i = 0; event_handlers[i].name; i++) {
+ if (event_handlers[i].et == srv->event_handler) {
+ mod_status_row_append(b, "fd-Event-Handler", event_handlers[i].name);
+ break;
+ }
+ }
+
+ mod_status_header_append(b, "Config-File-Settings");
+
+ for (i = 0; i < srv->plugins.used; i++) {
+ plugin **ps = srv->plugins.ptr;
+
+ plugin *pl = ps[i];
+
+ if (i == 0) {
+ buffer_copy_string_buffer(m, pl->name);
+ } else {
+ BUFFER_APPEND_STRING_CONST(m, "<br />");
+ buffer_append_string_buffer(m, pl->name);
+ }
+ }
+
+ mod_status_row_append(b, "Loaded Modules", m->ptr);
+
+ BUFFER_APPEND_STRING_CONST(b, " </table>\n");
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " </body>\n"
+ "</html>\n"
+ );
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+
+ con->http_status = 200;
+ con->file_finished = 1;
+
+ return HANDLER_FINISHED;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_status_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(status_url);
+ PATCH(config_url);
+ PATCH(sort);
+ PATCH(statistics_url);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.status-url"))) {
+ PATCH(status_url);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.config-url"))) {
+ PATCH(config_url);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.enable-sort"))) {
+ PATCH(sort);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.statistics-url"))) {
+ PATCH(statistics_url);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static handler_t mod_status_handler(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ mod_status_patch_connection(srv, con, p);
+
+ if (!buffer_is_empty(p->conf.status_url) &&
+ buffer_is_equal(p->conf.status_url, con->uri.path)) {
+ return mod_status_handle_server_status(srv, con, p_d);
+ } else if (!buffer_is_empty(p->conf.config_url) &&
+ buffer_is_equal(p->conf.config_url, con->uri.path)) {
+ return mod_status_handle_server_config(srv, con, p_d);
+ } else if (!buffer_is_empty(p->conf.statistics_url) &&
+ buffer_is_equal(p->conf.statistics_url, con->uri.path)) {
+ return mod_status_handle_server_statistics(srv, con, p_d);
+ }
+
+ return HANDLER_GO_ON;
+}
+
+TRIGGER_FUNC(mod_status_trigger) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ /* check all connections */
+ for (i = 0; i < srv->conns->used; i++) {
+ connection *c = srv->conns->ptr[i];
+
+ p->bytes_written += c->bytes_written_cur_second;
+ }
+
+ /* a sliding average */
+ p->mod_5s_traffic_out[p->mod_5s_ndx] = p->bytes_written;
+ p->mod_5s_requests [p->mod_5s_ndx] = p->requests;
+
+ p->mod_5s_ndx = (p->mod_5s_ndx+1) % 5;
+
+ p->abs_traffic_out += p->bytes_written;
+ p->rel_traffic_out += p->bytes_written;
+
+ p->bytes_written = 0;
+
+ /* reset storage - second */
+ p->traffic_out = 0;
+ p->requests = 0;
+
+ return HANDLER_GO_ON;
+}
+
+REQUESTDONE_FUNC(mod_status_account) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ p->requests++;
+ p->rel_requests++;
+ p->abs_requests++;
+
+ p->bytes_written += con->bytes_written_cur_second;
+
+ return HANDLER_GO_ON;
+}
+
+int mod_status_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("status");
+
+ p->init = mod_status_init;
+ p->cleanup = mod_status_free;
+ p->set_defaults= mod_status_set_defaults;
+
+ p->handle_uri_clean = mod_status_handler;
+ p->handle_trigger = mod_status_trigger;
+ p->handle_request_done = mod_status_account;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_trigger_b4_dl.c b/src/mod_trigger_b4_dl.c
new file mode 100644
index 0000000..8281ec0
--- /dev/null
+++ b/src/mod_trigger_b4_dl.c
@@ -0,0 +1,586 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+#include "response.h"
+#include "inet_ntop_cache.h"
+
+#if defined(HAVE_GDBM_H)
+#include <gdbm.h>
+#endif
+
+#if defined(HAVE_PCRE_H)
+#include <pcre.h>
+#endif
+
+#if defined(HAVE_MEMCACHE_H)
+#include <memcache.h>
+#endif
+
+/**
+ * this is a trigger_b4_dl for a lighttpd plugin
+ *
+ */
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ buffer *db_filename;
+
+ buffer *trigger_url;
+ buffer *download_url;
+ buffer *deny_url;
+
+ array *mc_hosts;
+ buffer *mc_namespace;
+#if defined(HAVE_PCRE_H)
+ pcre *trigger_regex;
+ pcre *download_regex;
+#endif
+#if defined(HAVE_GDBM_H)
+ GDBM_FILE db;
+#endif
+
+#if defined(HAVE_MEMCACHE_H)
+ struct memcache *mc;
+#endif
+
+ unsigned short trigger_timeout;
+ unsigned short debug;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *tmp_buf;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_trigger_b4_dl_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->tmp_buf = buffer_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_trigger_b4_dl_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (!s) continue;
+
+ buffer_free(s->db_filename);
+ buffer_free(s->download_url);
+ buffer_free(s->trigger_url);
+ buffer_free(s->deny_url);
+
+ buffer_free(s->mc_namespace);
+ array_free(s->mc_hosts);
+
+#if defined(HAVE_PCRE_H)
+ if (s->trigger_regex) pcre_free(s->trigger_regex);
+ if (s->download_regex) pcre_free(s->download_regex);
+#endif
+#if defined(HAVE_GDBM_H)
+ if (s->db) gdbm_close(s->db);
+#endif
+#if defined(HAVE_MEMCACHE_H)
+ if (s->mc) mc_free(s->mc);
+#endif
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->tmp_buf);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_trigger_b4_dl_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+
+ config_values_t cv[] = {
+ { "trigger-before-download.gdbm-filename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "trigger-before-download.trigger-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "trigger-before-download.download-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "trigger-before-download.deny-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { "trigger-before-download.trigger-timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
+ { "trigger-before-download.memcache-hosts", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
+ { "trigger-before-download.memcache-namespace", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
+ { "trigger-before-download.debug", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+#if defined(HAVE_PCRE_H)
+ const char *errptr;
+ int erroff;
+#endif
+
+ s = calloc(1, sizeof(plugin_config));
+ s->db_filename = buffer_init();
+ s->download_url = buffer_init();
+ s->trigger_url = buffer_init();
+ s->deny_url = buffer_init();
+ s->mc_hosts = array_init();
+ s->mc_namespace = buffer_init();
+
+ cv[0].destination = s->db_filename;
+ cv[1].destination = s->trigger_url;
+ cv[2].destination = s->download_url;
+ cv[3].destination = s->deny_url;
+ cv[4].destination = &(s->trigger_timeout);
+ cv[5].destination = s->mc_hosts;
+ cv[6].destination = s->mc_namespace;
+ cv[7].destination = &(s->debug);
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+#if defined(HAVE_GDBM_H)
+ if (!buffer_is_empty(s->db_filename)) {
+ if (NULL == (s->db = gdbm_open(s->db_filename->ptr, 4096, GDBM_WRCREAT | GDBM_NOLOCK, S_IRUSR | S_IWUSR, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "gdbm-open failed");
+ return HANDLER_ERROR;
+ }
+ }
+#endif
+#if defined(HAVE_PCRE_H)
+ if (!buffer_is_empty(s->download_url)) {
+ if (NULL == (s->download_regex = pcre_compile(s->download_url->ptr,
+ 0, &errptr, &erroff, NULL))) {
+
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "compiling regex for download-url failed:",
+ s->download_url, "pos:", erroff);
+ return HANDLER_ERROR;
+ }
+ }
+
+ if (!buffer_is_empty(s->trigger_url)) {
+ if (NULL == (s->trigger_regex = pcre_compile(s->trigger_url->ptr,
+ 0, &errptr, &erroff, NULL))) {
+
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "compiling regex for trigger-url failed:",
+ s->trigger_url, "pos:", erroff);
+
+ return HANDLER_ERROR;
+ }
+ }
+#endif
+
+ if (s->mc_hosts->used) {
+#if defined(HAVE_MEMCACHE_H)
+ size_t k;
+ s->mc = mc_new();
+
+ for (k = 0; k < s->mc_hosts->used; k++) {
+ data_string *ds = (data_string *)s->mc_hosts->data[k];
+
+ if (0 != mc_server_add4(s->mc, ds->value->ptr)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "connection to host failed:",
+ ds->value);
+
+ return HANDLER_ERROR;
+ }
+ }
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "memcache support is not compiled in but trigger-before-download.memcache-hosts is set, aborting");
+ return HANDLER_ERROR;
+#endif
+ }
+
+
+#if (!defined(HAVE_GDBM_H) && !defined(HAVE_MEMCACHE_H)) || !defined(HAVE_PCRE_H)
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "(either gdbm or libmemcache) and pcre are require, but were not found, aborting");
+ return HANDLER_ERROR;
+#endif
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_trigger_b4_dl_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+#if defined(HAVE_GDBM)
+ PATCH(db);
+#endif
+#if defined(HAVE_PCRE_H)
+ PATCH(download_regex);
+ PATCH(trigger_regex);
+#endif
+ PATCH(trigger_timeout);
+ PATCH(deny_url);
+ PATCH(mc_namespace);
+ PATCH(debug);
+#if defined(HAVE_MEMCACHE_H)
+ PATCH(mc);
+#endif
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.download-url"))) {
+#if defined(HAVE_PCRE_H)
+ PATCH(download_regex);
+#endif
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.trigger-url"))) {
+# if defined(HAVE_PCRE_H)
+ PATCH(trigger_regex);
+# endif
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.gdbm-filename"))) {
+#if defined(HAVE_GDBM_H)
+ PATCH(db);
+#endif
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.trigger-timeout"))) {
+ PATCH(trigger_timeout);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.debug"))) {
+ PATCH(debug);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.deny-url"))) {
+ PATCH(deny_url);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.memcache-namespace"))) {
+ PATCH(mc_namespace);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.memcache-hosts"))) {
+#if defined(HAVE_MEMCACHE_H)
+ PATCH(mc);
+#endif
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_trigger_b4_dl_uri_handler) {
+ plugin_data *p = p_d;
+ const char *remote_ip;
+ data_string *ds;
+
+#if defined(HAVE_PCRE_H)
+ int n;
+# define N 10
+ int ovec[N * 3];
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_trigger_b4_dl_patch_connection(srv, con, p);
+
+ if (!p->conf.trigger_regex || !p->conf.download_regex) return HANDLER_GO_ON;
+
+# if !defined(HAVE_GDBM_H) && !defined(HAVE_MEMCACHE_H)
+ return HANDLER_GO_ON;
+# elif defined(HAVE_GDBM_H) && defined(HAVE_MEMCACHE_H)
+ if (!p->conf.db && !p->conf.mc) return HANDLER_GO_ON;
+ if (p->conf.db && p->conf.mc) {
+ /* can't decide which one */
+
+ return HANDLER_GO_ON;
+ }
+# elif defined(HAVE_GDBM_H)
+ if (!p->conf.db) return HANDLER_GO_ON;
+# else
+ if (!p->conf.mc) return HANDLER_GO_ON;
+# endif
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "X-Forwarded-For"))) {
+ /* X-Forwarded-For contains the ip behind the proxy */
+
+ remote_ip = ds->value->ptr;
+
+ /* memcache can't handle spaces */
+ } else {
+ remote_ip = inet_ntop_cache_get_ip(srv, &(con->dst_addr));
+ }
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "(debug) remote-ip:", remote_ip);
+ }
+
+ /* check if URL is a trigger -> insert IP into DB */
+ if ((n = pcre_exec(p->conf.trigger_regex, NULL, con->uri.path->ptr, con->uri.path->used - 1, 0, 0, ovec, 3 * N)) < 0) {
+ if (n != PCRE_ERROR_NOMATCH) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "execution error while matching:", n);
+
+ return HANDLER_ERROR;
+ }
+ } else {
+# if defined(HAVE_GDBM_H)
+ if (p->conf.db) {
+ /* the trigger matched */
+ datum key, val;
+
+ key.dptr = (char *)remote_ip;
+ key.dsize = strlen(remote_ip);
+
+ val.dptr = (char *)&(srv->cur_ts);
+ val.dsize = sizeof(srv->cur_ts);
+
+ if (0 != gdbm_store(p->conf.db, key, val, GDBM_REPLACE)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "insert failed");
+ }
+ }
+# endif
+# if defined(HAVE_MEMCACHE_H)
+ if (p->conf.mc) {
+ size_t i;
+ buffer_copy_string_buffer(p->tmp_buf, p->conf.mc_namespace);
+ buffer_append_string(p->tmp_buf, remote_ip);
+
+ for (i = 0; i < p->tmp_buf->used - 1; i++) {
+ if (p->tmp_buf->ptr[i] == ' ') p->tmp_buf->ptr[i] = '-';
+ }
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "(debug) triggered IP:", p->tmp_buf);
+ }
+
+ if (0 != mc_set(p->conf.mc,
+ CONST_BUF_LEN(p->tmp_buf),
+ (char *)&(srv->cur_ts), sizeof(srv->cur_ts),
+ p->conf.trigger_timeout, 0)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "insert failed");
+ }
+ }
+# endif
+ }
+
+ /* check if URL is a download -> check IP in DB, update timestamp */
+ if ((n = pcre_exec(p->conf.download_regex, NULL, con->uri.path->ptr, con->uri.path->used - 1, 0, 0, ovec, 3 * N)) < 0) {
+ if (n != PCRE_ERROR_NOMATCH) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "execution error while matching: ", n);
+ return HANDLER_ERROR;
+ }
+ } else {
+ /* the download uri matched */
+# if defined(HAVE_GDBM_H)
+ if (p->conf.db) {
+ datum key, val;
+ time_t last_hit;
+
+ key.dptr = (char *)remote_ip;
+ key.dsize = strlen(remote_ip);
+
+ val = gdbm_fetch(p->conf.db, key);
+
+ if (val.dptr == NULL) {
+ /* not found, redirect */
+
+ response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->conf.deny_url));
+
+ con->http_status = 307;
+
+ return HANDLER_FINISHED;
+ }
+
+ last_hit = *(time_t *)(val.dptr);
+
+ free(val.dptr);
+
+ if (srv->cur_ts - last_hit > p->conf.trigger_timeout) {
+ /* found, but timeout, redirect */
+
+ response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->conf.deny_url));
+ con->http_status = 307;
+
+ if (p->conf.db) {
+ if (0 != gdbm_delete(p->conf.db, key)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "delete failed");
+ }
+ }
+
+ return HANDLER_FINISHED;
+ }
+
+ val.dptr = (char *)&(srv->cur_ts);
+ val.dsize = sizeof(srv->cur_ts);
+
+ if (0 != gdbm_store(p->conf.db, key, val, GDBM_REPLACE)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "insert failed");
+ }
+ }
+# endif
+
+# if defined(HAVE_MEMCACHE_H)
+ if (p->conf.mc) {
+ void *r;
+ size_t i;
+
+ buffer_copy_string_buffer(p->tmp_buf, p->conf.mc_namespace);
+ buffer_append_string(p->tmp_buf, remote_ip);
+
+ for (i = 0; i < p->tmp_buf->used - 1; i++) {
+ if (p->tmp_buf->ptr[i] == ' ') p->tmp_buf->ptr[i] = '-';
+ }
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "(debug) checking IP:", p->tmp_buf);
+ }
+
+ /**
+ *
+ * memcached is do expiration for us, as long as we can fetch it every thing is ok
+ * and the timestamp is updated
+ *
+ */
+ if (NULL == (r = mc_aget(p->conf.mc,
+ CONST_BUF_LEN(p->tmp_buf)
+ ))) {
+
+ response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->conf.deny_url));
+
+ con->http_status = 307;
+
+ return HANDLER_FINISHED;
+ }
+
+ free(r);
+
+ /* set a new timeout */
+ if (0 != mc_set(p->conf.mc,
+ CONST_BUF_LEN(p->tmp_buf),
+ (char *)&(srv->cur_ts), sizeof(srv->cur_ts),
+ p->conf.trigger_timeout, 0)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "insert failed");
+ }
+ }
+# endif
+ }
+
+#else
+ UNUSED(srv);
+ UNUSED(con);
+ UNUSED(p_d);
+#endif
+
+ return HANDLER_GO_ON;
+}
+
+#if defined(HAVE_GDBM_H)
+TRIGGER_FUNC(mod_trigger_b4_dl_handle_trigger) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ /* check DB each minute */
+ if (srv->cur_ts % 60 != 0) return HANDLER_GO_ON;
+
+ /* cleanup */
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+ datum key, val, okey;
+
+ if (!s->db) continue;
+
+ okey.dptr = NULL;
+
+ /* according to the manual this loop + delete does delete all entries on its way
+ *
+ * we don't care as the next round will remove them. We don't have to perfect here.
+ */
+ for (key = gdbm_firstkey(s->db); key.dptr; key = gdbm_nextkey(s->db, okey)) {
+ time_t last_hit;
+ if (okey.dptr) {
+ free(okey.dptr);
+ okey.dptr = NULL;
+ }
+
+ val = gdbm_fetch(s->db, key);
+
+ last_hit = *(time_t *)(val.dptr);
+
+ free(val.dptr);
+
+ if (srv->cur_ts - last_hit > s->trigger_timeout) {
+ gdbm_delete(s->db, key);
+ }
+
+ okey = key;
+ }
+ if (okey.dptr) free(okey.dptr);
+
+ /* reorg once a day */
+ if ((srv->cur_ts % (60 * 60 * 24) != 0)) gdbm_reorganize(s->db);
+ }
+ return HANDLER_GO_ON;
+}
+#endif
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_trigger_b4_dl_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("trigger_b4_dl");
+
+ p->init = mod_trigger_b4_dl_init;
+ p->handle_uri_clean = mod_trigger_b4_dl_uri_handler;
+ p->set_defaults = mod_trigger_b4_dl_set_defaults;
+#if defined(HAVE_GDBM_H)
+ p->handle_trigger = mod_trigger_b4_dl_handle_trigger;
+#endif
+ p->cleanup = mod_trigger_b4_dl_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_userdir.c b/src/mod_userdir.c
new file mode 100644
index 0000000..9612fa8
--- /dev/null
+++ b/src/mod_userdir.c
@@ -0,0 +1,294 @@
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "response.h"
+
+#include "plugin.h"
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+/* plugin config for all request/connections */
+typedef struct {
+ array *exclude_user;
+ array *include_user;
+ buffer *path;
+ buffer *basepath;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *username;
+ buffer *temp_path;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_userdir_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->username = buffer_init();
+ p->temp_path = buffer_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_userdir_free) {
+ plugin_data *p = p_d;
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->include_user);
+ array_free(s->exclude_user);
+ buffer_free(s->path);
+ buffer_free(s->basepath);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->username);
+ buffer_free(p->temp_path);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_userdir_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ config_values_t cv[] = {
+ { "userdir.path", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "userdir.exclude-user", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "userdir.include-user", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "userdir.basepath", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->exclude_user = array_init();
+ s->include_user = array_init();
+ s->path = buffer_init();
+ s->basepath = buffer_init();
+
+ cv[0].destination = s->path;
+ cv[1].destination = s->exclude_user;
+ cv[2].destination = s->include_user;
+ cv[3].destination = s->basepath;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_userdir_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(path);
+ PATCH(exclude_user);
+ PATCH(include_user);
+ PATCH(basepath);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.path"))) {
+ PATCH(path);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.exclude-user"))) {
+ PATCH(exclude_user);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.include-user"))) {
+ PATCH(include_user);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.basepath"))) {
+ PATCH(basepath);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_userdir_docroot_handler) {
+ plugin_data *p = p_d;
+ int uri_len;
+ size_t k;
+ char *rel_url;
+#ifdef HAVE_PWD_H
+ struct passwd *pwd = NULL;
+#endif
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_userdir_patch_connection(srv, con, p);
+
+ uri_len = con->uri.path->used - 1;
+
+ /* /~user/foo.html -> /home/user/public_html/foo.html */
+
+ if (con->uri.path->ptr[0] != '/' ||
+ con->uri.path->ptr[1] != '~') return HANDLER_GO_ON;
+
+ if (NULL == (rel_url = strchr(con->uri.path->ptr + 2, '/'))) {
+ /* / is missing -> redirect to .../ as we are a user - DIRECTORY ! :) */
+ http_response_redirect_to_directory(srv, con);
+
+ return HANDLER_FINISHED;
+ }
+
+ /* /~/ is a empty username, catch it directly */
+ if (0 == rel_url - (con->uri.path->ptr + 2)) {
+ return HANDLER_GO_ON;
+ }
+
+ buffer_copy_string_len(p->username, con->uri.path->ptr + 2, rel_url - (con->uri.path->ptr + 2));
+
+ if (buffer_is_empty(p->conf.basepath)
+#ifdef HAVE_PWD_H
+ && NULL == (pwd = getpwnam(p->username->ptr))
+#endif
+ ) {
+ /* user not found */
+ return HANDLER_GO_ON;
+ }
+
+
+ for (k = 0; k < p->conf.exclude_user->used; k++) {
+ data_string *ds = (data_string *)p->conf.exclude_user->data[k];
+
+ if (buffer_is_equal(ds->value, p->username)) {
+ /* user in exclude list */
+ return HANDLER_GO_ON;
+ }
+ }
+
+ if (p->conf.include_user->used) {
+ int found_user = 0;
+ for (k = 0; k < p->conf.include_user->used; k++) {
+ data_string *ds = (data_string *)p->conf.include_user->data[k];
+
+ if (buffer_is_equal(ds->value, p->username)) {
+ /* user in include list */
+ found_user = 1;
+ break;
+ }
+ }
+
+ if (!found_user) return HANDLER_GO_ON;
+ }
+
+ /* we build the physical path */
+
+ if (buffer_is_empty(p->conf.basepath)) {
+#ifdef HAVE_PWD_H
+ buffer_copy_string(p->temp_path, pwd->pw_dir);
+#endif
+ } else {
+ char *cp;
+ /* check if the username is valid
+ * a request for /~../ should lead to a directory traversal
+ * limiting to [-_a-z0-9.] should fix it */
+
+ for (cp = p->username->ptr; *cp; cp++) {
+ char c = *cp;
+
+ if (!(c == '-' ||
+ c == '_' ||
+ c == '.' ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9'))) {
+
+ return HANDLER_GO_ON;
+ }
+ }
+
+ buffer_copy_string_buffer(p->temp_path, p->conf.basepath);
+ BUFFER_APPEND_SLASH(p->temp_path);
+ buffer_append_string_buffer(p->temp_path, p->username);
+ }
+ BUFFER_APPEND_SLASH(p->temp_path);
+ buffer_append_string_buffer(p->temp_path, p->conf.path);
+
+ if (buffer_is_empty(p->conf.basepath)) {
+ struct stat st;
+ int ret;
+
+ ret = stat(p->temp_path->ptr, &st);
+ if (ret < 0 || S_ISDIR(st.st_mode) != 1) {
+ return HANDLER_GO_ON;
+ }
+ }
+
+ BUFFER_APPEND_SLASH(p->temp_path);
+ buffer_append_string(p->temp_path, rel_url + 1); /* skip the / */
+ buffer_copy_string_buffer(con->physical.path, p->temp_path);
+
+ buffer_reset(p->temp_path);
+
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_userdir_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("userdir");
+
+ p->init = mod_userdir_init;
+ p->handle_physical = mod_userdir_docroot_handler;
+ p->set_defaults = mod_userdir_set_defaults;
+ p->cleanup = mod_userdir_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_usertrack.c b/src/mod_usertrack.c
new file mode 100644
index 0000000..d3314c2
--- /dev/null
+++ b/src/mod_usertrack.c
@@ -0,0 +1,270 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#ifdef USE_OPENSSL
+# include <openssl/md5.h>
+#else
+# include "md5.h"
+#endif
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ buffer *cookie_name;
+ buffer *cookie_domain;
+ unsigned short cookie_max_age;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_usertrack_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_usertrack_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free(s->cookie_name);
+ buffer_free(s->cookie_domain);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_usertrack_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "usertrack.cookie-name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "usertrack.cookie-max-age", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "usertrack.cookie-domain", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+
+ { "usertrack.cookiename", NULL, T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->cookie_name = buffer_init();
+ s->cookie_domain = buffer_init();
+ s->cookie_max_age = 0;
+
+ cv[0].destination = s->cookie_name;
+ cv[1].destination = &(s->cookie_max_age);
+ cv[2].destination = s->cookie_domain;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (buffer_is_empty(s->cookie_name)) {
+ buffer_copy_string(s->cookie_name, "TRACKID");
+ } else {
+ size_t j;
+ for (j = 0; j < s->cookie_name->used - 1; j++) {
+ char c = s->cookie_name->ptr[j] | 32;
+ if (c < 'a' || c > 'z') {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "invalid character in usertrack.cookie-name:",
+ s->cookie_name);
+
+ return HANDLER_ERROR;
+ }
+ }
+ }
+
+ if (!buffer_is_empty(s->cookie_domain)) {
+ size_t j;
+ for (j = 0; j < s->cookie_domain->used - 1; j++) {
+ char c = s->cookie_domain->ptr[j];
+ if (c <= 32 || c >= 127 || c == '"' || c == '\\') {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "invalid character in usertrack.cookie-domain:",
+ s->cookie_domain);
+
+ return HANDLER_ERROR;
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_usertrack_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(cookie_name);
+ PATCH(cookie_domain);
+ PATCH(cookie_max_age);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("usertrack.cookie-name"))) {
+ PATCH(cookie_name);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("usertrack.cookie-max-age"))) {
+ PATCH(cookie_max_age);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("usertrack.cookie-domain"))) {
+ PATCH(cookie_domain);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_usertrack_uri_handler) {
+ plugin_data *p = p_d;
+ data_string *ds;
+ unsigned char h[16];
+ MD5_CTX Md5Ctx;
+ char hh[32];
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_usertrack_patch_connection(srv, con, p);
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Cookie"))) {
+ char *g;
+ /* we have a cookie, does it contain a valid name ? */
+
+ /* parse the cookie
+ *
+ * check for cookiename + (WS | '=')
+ *
+ */
+
+ if (NULL != (g = strstr(ds->value->ptr, p->conf.cookie_name->ptr))) {
+ char *nc;
+
+ /* skip WS */
+ for (nc = g + p->conf.cookie_name->used-1; *nc == ' ' || *nc == '\t'; nc++);
+
+ if (*nc == '=') {
+ /* ok, found the key of our own cookie */
+
+ if (strlen(nc) > 32) {
+ /* i'm lazy */
+ return HANDLER_GO_ON;
+ }
+ }
+ }
+ }
+
+ /* set a cookie */
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
+ ds = data_response_init();
+ }
+ buffer_copy_string(ds->key, "Set-Cookie");
+ buffer_copy_string_buffer(ds->value, p->conf.cookie_name);
+ buffer_append_string(ds->value, "=");
+
+
+ /* taken from mod_auth.c */
+
+ /* generate shared-secret */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)con->uri.path->ptr, con->uri.path->used - 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)"+", 1);
+
+ /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
+ ltostr(hh, srv->cur_ts);
+ MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
+ ltostr(hh, rand());
+ MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
+
+ MD5_Final(h, &Md5Ctx);
+
+ buffer_append_string_encoded(ds->value, (char *)h, 16, ENCODING_HEX);
+ buffer_append_string(ds->value, "; Path=/");
+ buffer_append_string(ds->value, "; Version=1");
+
+ if (!buffer_is_empty(p->conf.cookie_domain)) {
+ buffer_append_string(ds->value, "; Domain=");
+ buffer_append_string_encoded(ds->value, CONST_BUF_LEN(p->conf.cookie_domain), ENCODING_REL_URI);
+ }
+
+ if (p->conf.cookie_max_age) {
+ buffer_append_string(ds->value, "; max-age=");
+ buffer_append_long(ds->value, p->conf.cookie_max_age);
+ }
+
+ array_insert_unique(con->response.headers, (data_unset *)ds);
+
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_usertrack_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("usertrack");
+
+ p->init = mod_usertrack_init;
+ p->handle_uri_clean = mod_usertrack_uri_handler;
+ p->set_defaults = mod_usertrack_set_defaults;
+ p->cleanup = mod_usertrack_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_webdav.c b/src/mod_webdav.c
new file mode 100644
index 0000000..3306c73
--- /dev/null
+++ b/src/mod_webdav.c
@@ -0,0 +1,1854 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <assert.h>
+#include <sys/mman.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if defined(HAVE_LIBXML_H) && defined(HAVE_SQLITE3_H)
+#define USE_PROPPATCH
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+
+#include <sqlite3.h>
+#endif
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+#include "response.h"
+
+#include "plugin.h"
+
+#include "stream.h"
+#include "stat_cache.h"
+
+
+/**
+ * this is a webdav for a lighttpd plugin
+ *
+ * at least a very basic one.
+ * - for now it is read-only and we only support PROPFIND
+ *
+ */
+
+
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ unsigned short enabled;
+ unsigned short is_readonly;
+ unsigned short log_xml;
+
+ buffer *sqlite_db_name;
+#ifdef USE_PROPPATCH
+ sqlite3 *sql;
+ sqlite3_stmt *stmt_update_prop;
+ sqlite3_stmt *stmt_delete_prop;
+ sqlite3_stmt *stmt_select_prop;
+ sqlite3_stmt *stmt_select_propnames;
+
+ sqlite3_stmt *stmt_delete_uri;
+ sqlite3_stmt *stmt_move_uri;
+ sqlite3_stmt *stmt_copy_uri;
+#endif
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *tmp_buf;
+ request_uri uri;
+ physical physical;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_webdav_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->tmp_buf = buffer_init();
+
+ p->uri.scheme = buffer_init();
+ p->uri.path_raw = buffer_init();
+ p->uri.path = buffer_init();
+ p->uri.authority = buffer_init();
+
+ p->physical.path = buffer_init();
+ p->physical.rel_path = buffer_init();
+ p->physical.doc_root = buffer_init();
+ p->physical.basedir = buffer_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_webdav_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (!s) continue;
+
+ buffer_free(s->sqlite_db_name);
+#ifdef USE_PROPPATCH
+ if (s->sql) {
+ sqlite3_finalize(s->stmt_delete_prop);
+ sqlite3_finalize(s->stmt_delete_uri);
+ sqlite3_finalize(s->stmt_copy_uri);
+ sqlite3_finalize(s->stmt_move_uri);
+ sqlite3_finalize(s->stmt_update_prop);
+ sqlite3_finalize(s->stmt_select_prop);
+ sqlite3_finalize(s->stmt_select_propnames);
+ sqlite3_close(s->sql);
+ }
+#endif
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->uri.scheme);
+ buffer_free(p->uri.path_raw);
+ buffer_free(p->uri.path);
+ buffer_free(p->uri.authority);
+
+ buffer_free(p->physical.path);
+ buffer_free(p->physical.rel_path);
+ buffer_free(p->physical.doc_root);
+ buffer_free(p->physical.basedir);
+
+ buffer_free(p->tmp_buf);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_webdav_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "webdav.activate", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "webdav.is-readonly", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "webdav.sqlite-db-name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "webdav.log-xml", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->sqlite_db_name = buffer_init();
+
+ cv[0].destination = &(s->enabled);
+ cv[1].destination = &(s->is_readonly);
+ cv[2].destination = s->sqlite_db_name;
+ cv[3].destination = &(s->log_xml);
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (!buffer_is_empty(s->sqlite_db_name)) {
+#ifdef USE_PROPPATCH
+ const char *next_stmt;
+ char *err;
+
+ if (SQLITE_OK != sqlite3_open(s->sqlite_db_name->ptr, &(s->sql))) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "sqlite3_open failed");
+ return HANDLER_ERROR;
+ }
+
+ if (SQLITE_OK != sqlite3_prepare(s->sql,
+ CONST_STR_LEN("SELECT value FROM properties WHERE resource = ? AND prop = ? AND ns = ?"),
+ &(s->stmt_select_prop), &next_stmt)) {
+ /* prepare failed */
+
+ log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql));
+ return HANDLER_ERROR;
+ }
+
+ if (SQLITE_OK != sqlite3_prepare(s->sql,
+ CONST_STR_LEN("SELECT ns, prop FROM properties WHERE resource = ?"),
+ &(s->stmt_select_propnames), &next_stmt)) {
+ /* prepare failed */
+
+ log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql));
+ return HANDLER_ERROR;
+ }
+
+ if (SQLITE_OK != sqlite3_exec(s->sql,
+ "CREATE TABLE properties ("
+ " resource TEXT NOT NULL,"
+ " prop TEXT NOT NULL,"
+ " ns TEXT NOT NULL,"
+ " value TEXT NOT NULL,"
+ " PRIMARY KEY(resource, prop, ns))",
+ NULL, NULL, &err)) {
+
+ if (0 != strcmp(err, "table properties already exists")) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err);
+ sqlite3_free(err);
+
+ return HANDLER_ERROR;
+ }
+ sqlite3_free(err);
+ }
+
+ if (SQLITE_OK != sqlite3_prepare(s->sql,
+ CONST_STR_LEN("REPLACE INTO properties (resource, prop, ns, value) VALUES (?, ?, ?, ?)"),
+ &(s->stmt_update_prop), &next_stmt)) {
+ /* prepare failed */
+
+ log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql));
+ return HANDLER_ERROR;
+ }
+
+ if (SQLITE_OK != sqlite3_prepare(s->sql,
+ CONST_STR_LEN("DELETE FROM properties WHERE resource = ? AND prop = ? AND ns = ?"),
+ &(s->stmt_delete_prop), &next_stmt)) {
+ /* prepare failed */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
+
+ return HANDLER_ERROR;
+ }
+
+ if (SQLITE_OK != sqlite3_prepare(s->sql,
+ CONST_STR_LEN("DELETE FROM properties WHERE resource = ?"),
+ &(s->stmt_delete_uri), &next_stmt)) {
+ /* prepare failed */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
+
+ return HANDLER_ERROR;
+ }
+
+ if (SQLITE_OK != sqlite3_prepare(s->sql,
+ CONST_STR_LEN("INSERT INTO properties SELECT ?, prop, ns, value FROM properties WHERE resource = ?"),
+ &(s->stmt_copy_uri), &next_stmt)) {
+ /* prepare failed */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
+
+ return HANDLER_ERROR;
+ }
+
+ if (SQLITE_OK != sqlite3_prepare(s->sql,
+ CONST_STR_LEN("UPDATE properties SET resource = ? WHERE resource = ?"),
+ &(s->stmt_move_uri), &next_stmt)) {
+ /* prepare failed */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
+
+ return HANDLER_ERROR;
+ }
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s", "Sorry, no sqlite3 and libxml2 support include, compile with --with-webdav-props");
+ return HANDLER_ERROR;
+#endif
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_webdav_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(enabled);
+ PATCH(is_readonly);
+ PATCH(log_xml);
+
+#ifdef USE_PROPPATCH
+ PATCH(sql);
+ PATCH(stmt_update_prop);
+ PATCH(stmt_delete_prop);
+ PATCH(stmt_select_prop);
+ PATCH(stmt_select_propnames);
+
+ PATCH(stmt_delete_uri);
+ PATCH(stmt_move_uri);
+ PATCH(stmt_copy_uri);
+#endif
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.activate"))) {
+ PATCH(enabled);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.is-readonly"))) {
+ PATCH(is_readonly);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.log-xml"))) {
+ PATCH(log_xml);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.sqlite-db-name"))) {
+#ifdef USE_PROPPATCH
+ PATCH(sql);
+ PATCH(stmt_update_prop);
+ PATCH(stmt_delete_prop);
+ PATCH(stmt_select_prop);
+ PATCH(stmt_select_propnames);
+
+ PATCH(stmt_delete_uri);
+ PATCH(stmt_move_uri);
+ PATCH(stmt_copy_uri);
+#endif
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_webdav_uri_handler) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_webdav_patch_connection(srv, con, p);
+
+ if (!p->conf.enabled) return HANDLER_GO_ON;
+
+ switch (con->request.http_method) {
+ case HTTP_METHOD_OPTIONS:
+ /* we fake a little bit but it makes MS W2k happy and it let's us mount the volume */
+ response_header_overwrite(srv, con, CONST_STR_LEN("DAV"), CONST_STR_LEN("1,2"));
+ response_header_overwrite(srv, con, CONST_STR_LEN("MS-Author-Via"), CONST_STR_LEN("DAV"));
+
+ if (p->conf.is_readonly) {
+ response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("PROPFIND"));
+ } else {
+ response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY, PROPPATCH"));
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+static int webdav_gen_prop_tag(server *srv, connection *con,
+ char *prop_name,
+ char *prop_ns,
+ char *value,
+ buffer *b) {
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ if (value) {
+ buffer_append_string(b,"<");
+ buffer_append_string(b, prop_name);
+ buffer_append_string(b, " xmlns=\"");
+ buffer_append_string(b, prop_ns);
+ buffer_append_string(b, "\">");
+
+ buffer_append_string(b, value);
+
+ buffer_append_string(b,"</");
+ buffer_append_string(b, prop_name);
+ buffer_append_string(b, ">");
+ } else {
+ buffer_append_string(b,"<");
+ buffer_append_string(b, prop_name);
+ buffer_append_string(b, " xmlns=\"");
+ buffer_append_string(b, prop_ns);
+ buffer_append_string(b, "\"/>");
+ }
+
+ return 0;
+}
+
+
+static int webdav_gen_response_status_tag(server *srv, connection *con, physical *dst, int status, buffer *b) {
+ UNUSED(srv);
+
+ buffer_append_string(b,"<D:response xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n");
+
+ buffer_append_string(b,"<D:href>\n");
+ buffer_append_string_buffer(b, dst->rel_path);
+ buffer_append_string(b,"</D:href>\n");
+ buffer_append_string(b,"<D:status>\n");
+
+ if (con->request.http_version == HTTP_VERSION_1_1) {
+ BUFFER_COPY_STRING_CONST(b, "HTTP/1.1 ");
+ } else {
+ BUFFER_COPY_STRING_CONST(b, "HTTP/1.0 ");
+ }
+ buffer_append_long(b, status);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ buffer_append_string(b, get_http_status_name(status));
+
+ buffer_append_string(b,"</D:status>\n");
+ buffer_append_string(b,"</D:response>\n");
+
+ return 0;
+}
+
+static int webdav_delete_file(server *srv, connection *con, plugin_data *p, physical *dst, buffer *b) {
+ int status = 0;
+
+ /* try to unlink it */
+ if (-1 == unlink(dst->path->ptr)) {
+ switch(errno) {
+ case EACCES:
+ case EPERM:
+ /* 403 */
+ status = 403;
+ break;
+ default:
+ status = 501;
+ break;
+ }
+ webdav_gen_response_status_tag(srv, con, dst, status, b);
+ } else {
+#ifdef USE_PROPPATCH
+ sqlite3_stmt *stmt = p->conf.stmt_delete_uri;
+
+ if (!stmt) {
+ status = 403;
+ webdav_gen_response_status_tag(srv, con, dst, status, b);
+ } else {
+ sqlite3_reset(stmt);
+
+ /* bind the values to the insert */
+
+ sqlite3_bind_text(stmt, 1,
+ dst->rel_path->ptr,
+ dst->rel_path->used - 1,
+ SQLITE_TRANSIENT);
+
+ if (SQLITE_DONE != sqlite3_step(stmt)) {
+ /* */
+ WP();
+ }
+ }
+#endif
+ }
+
+ return (status != 0);
+}
+
+static int webdav_delete_dir(server *srv, connection *con, plugin_data *p, physical *dst, buffer *b) {
+ DIR *dir;
+ int have_multi_status = 0;
+ physical d;
+
+ d.path = buffer_init();
+ d.rel_path = buffer_init();
+
+ if (NULL != (dir = opendir(dst->path->ptr))) {
+ struct dirent *de;
+
+ while(NULL != (de = readdir(dir))) {
+ struct stat st;
+ int status = 0;
+
+ if ((de->d_name[0] == '.' && de->d_name[1] == '\0') ||
+ (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')) {
+ continue;
+ /* ignore the parent dir */
+ }
+
+ buffer_copy_string_buffer(d.path, dst->path);
+ BUFFER_APPEND_SLASH(d.path);
+ buffer_append_string(d.path, de->d_name);
+
+ buffer_copy_string_buffer(d.rel_path, dst->rel_path);
+ BUFFER_APPEND_SLASH(d.rel_path);
+ buffer_append_string(d.rel_path, de->d_name);
+
+ /* stat and unlink afterwards */
+ if (-1 == stat(d.path->ptr, &st)) {
+ /* don't about it yet, rmdir will fail too */
+ } else if (S_ISDIR(st.st_mode)) {
+ have_multi_status = webdav_delete_dir(srv, con, p, &d, b);
+
+ /* try to unlink it */
+ if (-1 == rmdir(d.path->ptr)) {
+ switch(errno) {
+ case EACCES:
+ case EPERM:
+ /* 403 */
+ status = 403;
+ break;
+ default:
+ status = 501;
+ break;
+ }
+ have_multi_status = 1;
+
+ webdav_gen_response_status_tag(srv, con, &d, status, b);
+ } else {
+#ifdef USE_PROPPATCH
+ sqlite3_stmt *stmt = p->conf.stmt_delete_uri;
+
+ status = 0;
+
+ if (stmt) {
+ sqlite3_reset(stmt);
+
+ /* bind the values to the insert */
+
+ sqlite3_bind_text(stmt, 1,
+ d.rel_path->ptr,
+ d.rel_path->used - 1,
+ SQLITE_TRANSIENT);
+
+ if (SQLITE_DONE != sqlite3_step(stmt)) {
+ /* */
+ WP();
+ }
+ }
+#endif
+ }
+ } else {
+ have_multi_status = webdav_delete_file(srv, con, p, &d, b);
+ }
+ }
+ closedir(dir);
+
+ buffer_free(d.path);
+ buffer_free(d.rel_path);
+ }
+
+ return have_multi_status;
+}
+
+static int webdav_copy_file(server *srv, connection *con, plugin_data *p, physical *src, physical *dst, int overwrite) {
+ stream s;
+ int status = 0, ofd;
+
+ UNUSED(con);
+
+ if (stream_open(&s, src->path)) {
+ return 403;
+ }
+
+ if (-1 == (ofd = open(dst->path->ptr, O_WRONLY|O_TRUNC|O_CREAT|(overwrite ? 0 : O_EXCL), 0600))) {
+ /* opening the destination failed for some reason */
+ switch(errno) {
+ case EEXIST:
+ status = 412;
+ break;
+ case EISDIR:
+ status = 409;
+ break;
+ case ENOENT:
+ /* at least one part in the middle wasn't existing */
+ status = 409;
+ break;
+ default:
+ status = 403;
+ break;
+ }
+ stream_close(&s);
+ return status;
+ }
+
+ if (-1 == write(ofd, s.start, s.size)) {
+ switch(errno) {
+ case ENOSPC:
+ status = 507;
+ break;
+ default:
+ status = 403;
+ break;
+ }
+ }
+
+ stream_close(&s);
+ close(ofd);
+
+#ifdef USE_PROPPATCH
+ if (0 == status) {
+ /* copy worked fine, copy connected properties */
+ sqlite3_stmt *stmt = p->conf.stmt_copy_uri;
+
+ if (stmt) {
+ sqlite3_reset(stmt);
+
+ /* bind the values to the insert */
+ sqlite3_bind_text(stmt, 1,
+ dst->rel_path->ptr,
+ dst->rel_path->used - 1,
+ SQLITE_TRANSIENT);
+
+ sqlite3_bind_text(stmt, 2,
+ src->rel_path->ptr,
+ src->rel_path->used - 1,
+ SQLITE_TRANSIENT);
+
+ if (SQLITE_DONE != sqlite3_step(stmt)) {
+ /* */
+ WP();
+ }
+ }
+ }
+#endif
+ return status;
+}
+
+static int webdav_copy_dir(server *srv, connection *con, plugin_data *p, physical *src, physical *dst, int overwrite) {
+ DIR *srcdir;
+ int status = 0;
+
+ if (NULL != (srcdir = opendir(src->path->ptr))) {
+ struct dirent *de;
+ physical s, d;
+
+ s.path = buffer_init();
+ s.rel_path = buffer_init();
+
+ d.path = buffer_init();
+ d.rel_path = buffer_init();
+
+ while (NULL != (de = readdir(srcdir))) {
+ struct stat st;
+
+ if ((de->d_name[0] == '.' && de->d_name[1] == '\0') ||
+ (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')) {
+ continue;
+ }
+
+ buffer_copy_string_buffer(s.path, src->path);
+ BUFFER_APPEND_SLASH(s.path);
+ buffer_append_string(s.path, de->d_name);
+
+ buffer_copy_string_buffer(d.path, dst->path);
+ BUFFER_APPEND_SLASH(d.path);
+ buffer_append_string(d.path, de->d_name);
+
+ buffer_copy_string_buffer(s.rel_path, src->rel_path);
+ BUFFER_APPEND_SLASH(s.rel_path);
+ buffer_append_string(s.rel_path, de->d_name);
+
+ buffer_copy_string_buffer(d.rel_path, dst->rel_path);
+ BUFFER_APPEND_SLASH(d.rel_path);
+ buffer_append_string(d.rel_path, de->d_name);
+
+ if (-1 == stat(s.path->ptr, &st)) {
+ /* why ? */
+ } else if (S_ISDIR(st.st_mode)) {
+ /* a directory */
+ if (-1 == mkdir(d.path->ptr, 0700) &&
+ errno != EEXIST) {
+ /* WTH ? */
+ } else {
+#ifdef USE_PROPPATCH
+ sqlite3_stmt *stmt = p->conf.stmt_copy_uri;
+
+ if (0 != (status = webdav_copy_dir(srv, con, p, &s, &d, overwrite))) {
+ break;
+ }
+ /* directory is copied, copy the properties too */
+
+ if (stmt) {
+ sqlite3_reset(stmt);
+
+ /* bind the values to the insert */
+ sqlite3_bind_text(stmt, 1,
+ dst->rel_path->ptr,
+ dst->rel_path->used - 1,
+ SQLITE_TRANSIENT);
+
+ sqlite3_bind_text(stmt, 2,
+ src->rel_path->ptr,
+ src->rel_path->used - 1,
+ SQLITE_TRANSIENT);
+
+ if (SQLITE_DONE != sqlite3_step(stmt)) {
+ /* */
+ WP();
+ }
+ }
+#endif
+ }
+ } else if (S_ISREG(st.st_mode)) {
+ /* a plain file */
+ if (0 != (status = webdav_copy_file(srv, con, p, &s, &d, overwrite))) {
+ break;
+ }
+ }
+ }
+
+ buffer_free(s.path);
+ buffer_free(s.rel_path);
+ buffer_free(d.path);
+ buffer_free(d.rel_path);
+
+ closedir(srcdir);
+ }
+
+ return status;
+}
+
+static int webdav_get_live_property(server *srv, connection *con, plugin_data *p, physical *dst, char *prop_name, buffer *b) {
+ stat_cache_entry *sce = NULL;
+ int found = 0;
+
+ UNUSED(p);
+
+ if (HANDLER_ERROR != (stat_cache_get_entry(srv, con, dst->path, &sce))) {
+ char ctime_buf[] = "2005-08-18T07:27:16Z";
+ char mtime_buf[] = "Thu, 18 Aug 2005 07:27:16 GMT";
+ size_t k;
+
+ if (0 == strcmp(prop_name, "resourcetype")) {
+ if (S_ISDIR(sce->st.st_mode)) {
+ buffer_append_string(b, "<D:resourcetype><D:collection/></D:resourcetype>");
+ found = 1;
+ }
+ } else if (0 == strcmp(prop_name, "getcontenttype")) {
+ if (S_ISDIR(sce->st.st_mode)) {
+ buffer_append_string(b, "<D:getcontenttype>httpd/unix-directory</D:getcontenttype>");
+ found = 1;
+ } else if(S_ISREG(sce->st.st_mode)) {
+ for (k = 0; k < con->conf.mimetypes->used; k++) {
+ data_string *ds = (data_string *)con->conf.mimetypes->data[k];
+
+ if (ds->key->used == 0) continue;
+
+ if (buffer_is_equal_right_len(dst->path, ds->key, ds->key->used - 1)) {
+ buffer_append_string(b,"<D:getcontenttype>");
+ buffer_append_string_buffer(b, ds->value);
+ buffer_append_string(b, "</D:getcontenttype>");
+ found = 1;
+
+ break;
+ }
+ }
+ }
+ } else if (0 == strcmp(prop_name, "creationdate")) {
+ buffer_append_string(b, "<D:creationdate ns0:dt=\"dateTime.tz\">");
+ strftime(ctime_buf, sizeof(ctime_buf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&(sce->st.st_ctime)));
+ buffer_append_string(b, ctime_buf);
+ buffer_append_string(b, "</D:creationdate>");
+ found = 1;
+ } else if (0 == strcmp(prop_name, "getlastmodified")) {
+ buffer_append_string(b,"<D:getlastmodified ns0:dt=\"dateTime.rfc1123\">");
+ strftime(mtime_buf, sizeof(mtime_buf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(sce->st.st_mtime)));
+ buffer_append_string(b, mtime_buf);
+ buffer_append_string(b, "</D:getlastmodified>");
+ found = 1;
+ } else if (0 == strcmp(prop_name, "getcontentlength")) {
+ buffer_append_string(b,"<D:getcontentlength>");
+ buffer_append_off_t(b, sce->st.st_size);
+ buffer_append_string(b, "</D:getcontentlength>");
+ found = 1;
+ } else if (0 == strcmp(prop_name, "getcontentlanguage")) {
+ buffer_append_string(b,"<D:getcontentlanguage>");
+ buffer_append_string(b, "en");
+ buffer_append_string(b, "</D:getcontentlanguage>");
+ found = 1;
+ }
+ }
+
+ return found ? 0 : -1;
+}
+
+static int webdav_get_property(server *srv, connection *con, plugin_data *p, physical *dst, char *prop_name, char *prop_ns, buffer *b) {
+ if (0 == strcmp(prop_ns, "DAV:")) {
+ /* a local 'live' property */
+ return webdav_get_live_property(srv, con, p, dst, prop_name, b);
+ } else {
+ int found = 0;
+#ifdef USE_PROPPATCH
+ sqlite3_stmt *stmt = p->conf.stmt_select_prop;
+
+ if (stmt) {
+ /* perhaps it is in sqlite3 */
+ sqlite3_reset(stmt);
+
+ /* bind the values to the insert */
+
+ sqlite3_bind_text(stmt, 1,
+ dst->rel_path->ptr,
+ dst->rel_path->used - 1,
+ SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 2,
+ prop_name,
+ strlen(prop_name),
+ SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 3,
+ prop_ns,
+ strlen(prop_ns),
+ SQLITE_TRANSIENT);
+
+ /* it is the PK */
+ while (SQLITE_ROW == sqlite3_step(p->conf.stmt_select_prop)) {
+ /* there is a row for us, we only expect a single col 'value' */
+ webdav_gen_prop_tag(srv, con, prop_name, prop_ns, (char *)sqlite3_column_text(p->conf.stmt_select_prop, 0), b);
+ found = 1;
+ }
+ }
+#endif
+ return found ? 0 : -1;
+ }
+
+ /* not found */
+ return -1;
+}
+
+typedef struct {
+ char *ns;
+ char *prop;
+} webdav_property;
+
+webdav_property live_properties[] = {
+ { "DAV:", "creationdate" },
+ { "DAV:", "displayname" },
+ { "DAV:", "getcontentlanguage" },
+ { "DAV:", "getcontentlength" },
+ { "DAV:", "getcontenttype" },
+ { "DAV:", "getetag" },
+ { "DAV:", "getlastmodified" },
+ { "DAV:", "resourcetype" },
+ { "DAV:", "lockdiscovery" },
+ { "DAV:", "source" },
+ { "DAV:", "supportedlock" },
+
+ { NULL, NULL }
+};
+
+typedef struct {
+ webdav_property **ptr;
+
+ size_t used;
+ size_t size;
+} webdav_properties;
+
+static int webdav_get_props(server *srv, connection *con, plugin_data *p, physical *dst, webdav_properties *props, buffer *b_200, buffer *b_404) {
+ size_t i;
+
+ if (props) {
+ for (i = 0; i < props->used; i++) {
+ webdav_property *prop;
+
+ prop = props->ptr[i];
+
+ if (0 != webdav_get_property(srv, con, p,
+ dst, prop->prop, prop->ns, b_200)) {
+ webdav_gen_prop_tag(srv, con, prop->prop, prop->ns, NULL, b_404);
+ }
+ }
+ } else {
+ for (i = 0; live_properties[i].prop; i++) {
+ /* a local 'live' property */
+ webdav_get_live_property(srv, con, p, dst, live_properties[i].prop, b_200);
+ }
+ }
+
+ return 0;
+}
+
+#ifdef USE_PROPPATCH
+static int webdav_parse_chunkqueue(server *srv, connection *con, plugin_data *p, chunkqueue *cq, xmlDoc **ret_xml) {
+ xmlParserCtxtPtr ctxt;
+ xmlDoc *xml;
+ int res;
+ int err;
+
+ chunk *c;
+
+ UNUSED(con);
+
+ /* read the chunks in to the XML document */
+ ctxt = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL);
+
+ for (c = cq->first; cq->bytes_out != cq->bytes_in; c = cq->first) {
+ size_t weWant = cq->bytes_out - cq->bytes_in;
+ size_t weHave;
+
+ switch(c->type) {
+ case FILE_CHUNK:
+ weHave = c->file.length - c->offset;
+
+ if (weHave > weWant) weHave = weWant;
+
+ /* xml chunks are always memory, mmap() is our friend */
+ if (c->file.mmap.start == MAP_FAILED) {
+ if (-1 == c->file.fd && /* open the file if not already open */
+ -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
+
+ return -1;
+ }
+
+ if (MAP_FAILED == (c->file.mmap.start = mmap(0, c->file.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ",
+ strerror(errno), c->file.name, c->file.fd);
+
+ return -1;
+ }
+
+ close(c->file.fd);
+ c->file.fd = -1;
+
+ c->file.mmap.length = c->file.length;
+
+ /* chunk_reset() or chunk_free() will cleanup for us */
+ }
+
+ if (XML_ERR_OK != (err = xmlParseChunk(ctxt, c->file.mmap.start + c->offset, weHave, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "sddd", "xmlParseChunk failed at:", cq->bytes_out, weHave, err);
+ }
+
+ c->offset += weHave;
+ cq->bytes_out += weHave;
+
+ break;
+ case MEM_CHUNK:
+ /* append to the buffer */
+ weHave = c->mem->used - 1 - c->offset;
+
+ if (weHave > weWant) weHave = weWant;
+
+ if (p->conf.log_xml) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "XML-request-body:", c->mem->ptr + c->offset);
+ }
+
+ if (XML_ERR_OK != (err = xmlParseChunk(ctxt, c->mem->ptr + c->offset, weHave, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "sddd", "xmlParseChunk failed at:", cq->bytes_out, weHave, err);
+ }
+
+ c->offset += weHave;
+ cq->bytes_out += weHave;
+
+ break;
+ case UNUSED_CHUNK:
+ break;
+ }
+ chunkqueue_remove_finished_chunks(cq);
+ }
+
+
+ switch ((err = xmlParseChunk(ctxt, 0, 0, 1))) {
+ case XML_ERR_DOCUMENT_END:
+ case XML_ERR_OK:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "xmlParseChunk failed at final packet:", err);
+ break;
+ }
+
+ xml = ctxt->myDoc;
+ res = ctxt->wellFormed;
+ xmlFreeParserCtxt(ctxt);
+
+ if (res == 0) {
+ xmlFreeDoc(xml);
+ } else {
+ *ret_xml = xml;
+ }
+
+ return res;
+}
+#endif
+
+URIHANDLER_FUNC(mod_webdav_subrequest_handler) {
+ plugin_data *p = p_d;
+ buffer *b;
+ DIR *dir;
+ data_string *ds;
+ int depth = -1;
+ struct stat st;
+ buffer *prop_200;
+ buffer *prop_404;
+ webdav_properties *req_props;
+
+ UNUSED(srv);
+
+ if (!p->conf.enabled) return HANDLER_GO_ON;
+ /* physical path is setup */
+ if (con->physical.path->used == 0) return HANDLER_GO_ON;
+
+ /* PROPFIND need them */
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Depth"))) {
+ depth = strtol(ds->value->ptr, NULL, 10);
+ }
+
+ switch (con->request.http_method) {
+ case HTTP_METHOD_PROPFIND:
+ /* they want to know the properties of the directory */
+ req_props = NULL;
+
+ /* is there a content-body ? */
+
+#ifdef USE_PROPPATCH
+ /* any special requests or just allprop ? */
+ if (con->request.content_length) {
+ xmlDocPtr xml;
+
+ if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) {
+ xmlNode *rootnode = xmlDocGetRootElement(xml);
+
+ assert(rootnode);
+
+ if (0 == xmlStrcmp(rootnode->name, BAD_CAST "propfind")) {
+ xmlNode *cmd;
+
+ req_props = calloc(1, sizeof(*req_props));
+
+ for (cmd = rootnode->children; cmd; cmd = cmd->next) {
+
+ if (0 == xmlStrcmp(cmd->name, BAD_CAST "prop")) {
+ /* get prop by name */
+ xmlNode *prop;
+
+ for (prop = cmd->children; prop; prop = prop->next) {
+ if (prop->type == XML_TEXT_NODE) continue; /* ignore WS */
+
+ if (prop->ns &&
+ (0 == xmlStrcmp(prop->ns->href, BAD_CAST "")) &&
+ (0 != xmlStrcmp(prop->ns->prefix, BAD_CAST ""))) {
+ size_t i;
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "no name space for:",
+ prop->name);
+
+ xmlFreeDoc(xml);
+
+ for (i = 0; i < req_props->used; i++) {
+ free(req_props->ptr[i]->ns);
+ free(req_props->ptr[i]->prop);
+ free(req_props->ptr[i]);
+ }
+ free(req_props->ptr);
+ free(req_props);
+
+ con->http_status = 400;
+ return HANDLER_FINISHED;
+ }
+
+ /* add property to requested list */
+ if (req_props->size == 0) {
+ req_props->size = 16;
+ req_props->ptr = malloc(sizeof(*(req_props->ptr)) * req_props->size);
+ } else if (req_props->used == req_props->size) {
+ req_props->size += 16;
+ req_props->ptr = realloc(req_props->ptr, sizeof(*(req_props->ptr)) * req_props->size);
+ }
+
+ req_props->ptr[req_props->used] = malloc(sizeof(webdav_property));
+ req_props->ptr[req_props->used]->ns = (char *)xmlStrdup(prop->ns ? prop->ns->href : (xmlChar *)"");
+ req_props->ptr[req_props->used]->prop = (char *)xmlStrdup(prop->name);
+ req_props->used++;
+ }
+ } else if (0 == xmlStrcmp(cmd->name, BAD_CAST "propname")) {
+ sqlite3_stmt *stmt = p->conf.stmt_select_propnames;
+
+ if (stmt) {
+ /* get all property names (EMPTY) */
+ sqlite3_reset(stmt);
+ /* bind the values to the insert */
+
+ sqlite3_bind_text(stmt, 1,
+ con->uri.path->ptr,
+ con->uri.path->used - 1,
+ SQLITE_TRANSIENT);
+
+ if (SQLITE_DONE != sqlite3_step(stmt)) {
+ WP();
+ }
+ }
+ } else if (0 == xmlStrcmp(cmd->name, BAD_CAST "allprop")) {
+ /* get all properties (EMPTY) */
+ }
+ }
+ }
+
+ xmlFreeDoc(xml);
+ } else {
+ con->http_status = 400;
+ return HANDLER_FINISHED;
+ }
+ }
+#endif
+ con->http_status = 207;
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml; charset=\"utf-8\""));
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ buffer_copy_string(b, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+
+ buffer_append_string(b,"<D:multistatus xmlns:D=\"DAV:\" xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n");
+
+ /* allprop */
+
+ prop_200 = buffer_init();
+ prop_404 = buffer_init();
+
+ switch(depth) {
+ case 0:
+ /* Depth: 0 */
+ webdav_get_props(srv, con, p, &(con->physical), req_props, prop_200, prop_404);
+
+ buffer_append_string(b,"<D:response>\n");
+ buffer_append_string(b,"<D:href>");
+ buffer_append_string_buffer(b, con->uri.scheme);
+ buffer_append_string(b,"://");
+ buffer_append_string_buffer(b, con->uri.authority);
+ buffer_append_string_encoded(b, CONST_BUF_LEN(con->uri.path), ENCODING_REL_URI);
+ buffer_append_string(b,"</D:href>\n");
+
+ if (!buffer_is_empty(prop_200)) {
+ buffer_append_string(b,"<D:propstat>\n");
+ buffer_append_string(b,"<D:prop>\n");
+
+ buffer_append_string_buffer(b, prop_200);
+
+ buffer_append_string(b,"</D:prop>\n");
+
+ buffer_append_string(b,"<D:status>HTTP/1.1 200 OK</D:status>\n");
+
+ buffer_append_string(b,"</D:propstat>\n");
+ }
+ if (!buffer_is_empty(prop_404)) {
+ buffer_append_string(b,"<D:propstat>\n");
+ buffer_append_string(b,"<D:prop>\n");
+
+ buffer_append_string_buffer(b, prop_404);
+
+ buffer_append_string(b,"</D:prop>\n");
+
+ buffer_append_string(b,"<D:status>HTTP/1.1 404 Not Found</D:status>\n");
+
+ buffer_append_string(b,"</D:propstat>\n");
+ }
+
+ buffer_append_string(b,"</D:response>\n");
+
+ break;
+ case 1:
+ if (NULL != (dir = opendir(con->physical.path->ptr))) {
+ struct dirent *de;
+ physical d;
+ physical *dst = &(con->physical);
+
+ d.path = buffer_init();
+ d.rel_path = buffer_init();
+
+ while(NULL != (de = readdir(dir))) {
+ if (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0') {
+ continue;
+ /* ignore the parent dir */
+ }
+
+ buffer_copy_string_buffer(d.path, dst->path);
+ BUFFER_APPEND_SLASH(d.path);
+
+ buffer_copy_string_buffer(d.rel_path, dst->rel_path);
+ BUFFER_APPEND_SLASH(d.rel_path);
+
+ if (de->d_name[0] == '.' && de->d_name[1] == '\0') {
+ /* don't append the . */
+ } else {
+ buffer_append_string(d.path, de->d_name);
+ buffer_append_string(d.rel_path, de->d_name);
+ }
+
+ buffer_reset(prop_200);
+ buffer_reset(prop_404);
+
+ webdav_get_props(srv, con, p, &d, req_props, prop_200, prop_404);
+
+ buffer_append_string(b,"<D:response>\n");
+ buffer_append_string(b,"<D:href>");
+ buffer_append_string_buffer(b, con->uri.scheme);
+ buffer_append_string(b,"://");
+ buffer_append_string_buffer(b, con->uri.authority);
+ buffer_append_string_encoded(b, CONST_BUF_LEN(d.rel_path), ENCODING_REL_URI);
+ buffer_append_string(b,"</D:href>\n");
+
+ if (!buffer_is_empty(prop_200)) {
+ buffer_append_string(b,"<D:propstat>\n");
+ buffer_append_string(b,"<D:prop>\n");
+
+ buffer_append_string_buffer(b, prop_200);
+
+ buffer_append_string(b,"</D:prop>\n");
+
+ buffer_append_string(b,"<D:status>HTTP/1.1 200 OK</D:status>\n");
+
+ buffer_append_string(b,"</D:propstat>\n");
+ }
+ if (!buffer_is_empty(prop_404)) {
+ buffer_append_string(b,"<D:propstat>\n");
+ buffer_append_string(b,"<D:prop>\n");
+
+ buffer_append_string_buffer(b, prop_404);
+
+ buffer_append_string(b,"</D:prop>\n");
+
+ buffer_append_string(b,"<D:status>HTTP/1.1 404 Not Found</D:status>\n");
+
+ buffer_append_string(b,"</D:propstat>\n");
+ }
+
+ buffer_append_string(b,"</D:response>\n");
+ }
+ closedir(dir);
+ buffer_free(d.path);
+ buffer_free(d.rel_path);
+ }
+ break;
+ }
+
+ if (req_props) {
+ size_t i;
+ for (i = 0; i < req_props->used; i++) {
+ free(req_props->ptr[i]->ns);
+ free(req_props->ptr[i]->prop);
+ free(req_props->ptr[i]);
+ }
+ free(req_props->ptr);
+ free(req_props);
+ }
+
+ buffer_free(prop_200);
+ buffer_free(prop_404);
+
+ buffer_append_string(b,"</D:multistatus>\n");
+
+ if (p->conf.log_xml) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "XML-response-body:", b);
+ }
+ con->file_finished = 1;
+
+ return HANDLER_FINISHED;
+ case HTTP_METHOD_MKCOL:
+ if (p->conf.is_readonly) {
+ con->http_status = 403;
+ return HANDLER_FINISHED;
+ }
+
+ if (con->request.content_length != 0) {
+ /* we don't support MKCOL with a body */
+ con->http_status = 415;
+
+ return HANDLER_FINISHED;
+ }
+
+ /* let's create the directory */
+
+ if (-1 == mkdir(con->physical.path->ptr, 0700)) {
+ switch(errno) {
+ case EPERM:
+ con->http_status = 403;
+ break;
+ case ENOENT:
+ case ENOTDIR:
+ con->http_status = 409;
+ break;
+ case EEXIST:
+ default:
+ con->http_status = 405; /* not allowed */
+ break;
+ }
+ } else {
+ con->http_status = 201;
+ }
+
+ return HANDLER_FINISHED;
+ case HTTP_METHOD_DELETE:
+ if (p->conf.is_readonly) {
+ con->http_status = 403;
+ return HANDLER_FINISHED;
+ }
+
+ /* stat and unlink afterwards */
+ if (-1 == stat(con->physical.path->ptr, &st)) {
+ /* don't about it yet, unlink will fail too */
+ switch(errno) {
+ case ENOENT:
+ con->http_status = 404;
+ break;
+ default:
+ con->http_status = 403;
+ break;
+ }
+ } else if (S_ISDIR(st.st_mode)) {
+ buffer *multi_status_resp = buffer_init();
+
+ if (webdav_delete_dir(srv, con, p, &(con->physical), multi_status_resp)) {
+ /* we got an error somewhere in between, build a 207 */
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml; charset=\"utf-8\""));
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ buffer_copy_string(b, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+
+ buffer_append_string(b,"<D:multistatus xmlns:D=\"DAV:\">\n");
+
+ buffer_append_string_buffer(b, multi_status_resp);
+
+ buffer_append_string(b,"</D:multistatus>\n");
+
+ if (p->conf.log_xml) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "XML-response-body:", b);
+ }
+
+ con->http_status = 207;
+ con->file_finished = 1;
+ } else {
+ /* everything went fine, remove the directory */
+
+ if (-1 == rmdir(con->physical.path->ptr)) {
+ switch(errno) {
+ case ENOENT:
+ con->http_status = 404;
+ break;
+ default:
+ con->http_status = 501;
+ break;
+ }
+ } else {
+ con->http_status = 204;
+ }
+ }
+
+ buffer_free(multi_status_resp);
+ } else if (-1 == unlink(con->physical.path->ptr)) {
+ switch(errno) {
+ case EPERM:
+ con->http_status = 403;
+ break;
+ case ENOENT:
+ con->http_status = 404;
+ break;
+ default:
+ con->http_status = 501;
+ break;
+ }
+ } else {
+ con->http_status = 204;
+ }
+ return HANDLER_FINISHED;
+ case HTTP_METHOD_PUT: {
+ int fd;
+ chunkqueue *cq = con->request_content_queue;
+
+ if (p->conf.is_readonly) {
+ con->http_status = 403;
+ return HANDLER_FINISHED;
+ }
+
+ assert(chunkqueue_length(cq) == (off_t)con->request.content_length);
+
+ /* taken what we have in the request-body and write it to a file */
+ if (-1 == (fd = open(con->physical.path->ptr, O_WRONLY|O_CREAT|O_TRUNC, 0600))) {
+ /* we can't open the file */
+ con->http_status = 403;
+ } else {
+ chunk *c;
+
+ con->http_status = 201; /* created */
+
+ for (c = cq->first; c; c = cq->first) {
+ int r = 0;
+
+ /* copy all chunks */
+ switch(c->type) {
+ case FILE_CHUNK:
+
+ if (c->file.mmap.start == MAP_FAILED) {
+ if (-1 == c->file.fd && /* open the file if not already open */
+ -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
+
+ return -1;
+ }
+
+ if (MAP_FAILED == (c->file.mmap.start = mmap(0, c->file.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ",
+ strerror(errno), c->file.name, c->file.fd);
+
+ return -1;
+ }
+
+ c->file.mmap.length = c->file.length;
+
+ close(c->file.fd);
+ c->file.fd = -1;
+
+ /* chunk_reset() or chunk_free() will cleanup for us */
+ }
+
+ if ((r = write(fd, c->file.mmap.start + c->offset, c->file.length - c->offset)) < 0) {
+ switch(errno) {
+ case ENOSPC:
+ con->http_status = 507;
+
+ break;
+ default:
+ con->http_status = 403;
+ break;
+ }
+ }
+ break;
+ case MEM_CHUNK:
+ if ((r = write(fd, c->mem->ptr + c->offset, c->mem->used - c->offset - 1)) < 0) {
+ switch(errno) {
+ case ENOSPC:
+ con->http_status = 507;
+
+ break;
+ default:
+ con->http_status = 403;
+ break;
+ }
+ }
+ break;
+ case UNUSED_CHUNK:
+ break;
+ }
+
+ if (r > 0) {
+ c->offset += r;
+ cq->bytes_out += r;
+ } else {
+ break;
+ }
+ chunkqueue_remove_finished_chunks(cq);
+ }
+ close(fd);
+
+ }
+ return HANDLER_FINISHED;
+ }
+ case HTTP_METHOD_MOVE:
+ case HTTP_METHOD_COPY: {
+ buffer *destination = NULL;
+ char *sep, *start;
+ int overwrite = 1;
+
+ if (p->conf.is_readonly) {
+ con->http_status = 403;
+ return HANDLER_FINISHED;
+ }
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Destination"))) {
+ destination = ds->value;
+ } else {
+ con->http_status = 400;
+ return HANDLER_FINISHED;
+ }
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Overwrite"))) {
+ if (ds->value->used != 2 ||
+ (ds->value->ptr[0] != 'F' &&
+ ds->value->ptr[0] != 'T') ) {
+ con->http_status = 400;
+ return HANDLER_FINISHED;
+ }
+ overwrite = (ds->value->ptr[0] == 'F' ? 0 : 1);
+ }
+ /* let's parse the Destination
+ *
+ * http://127.0.0.1:1025/dav/litmus/copydest
+ *
+ * - host has to be the same as the Host: header we got
+ * - we have to stay inside the document root
+ * - the query string is thrown away
+ * */
+
+ buffer_reset(p->uri.scheme);
+ buffer_reset(p->uri.path_raw);
+ buffer_reset(p->uri.authority);
+
+ start = destination->ptr;
+
+ if (NULL == (sep = strstr(start, "://"))) {
+ con->http_status = 400;
+ return HANDLER_FINISHED;
+ }
+ buffer_copy_string_len(p->uri.scheme, start, sep - start);
+
+ start = sep + 3;
+
+ if (NULL == (sep = strchr(start, '/'))) {
+ con->http_status = 400;
+ return HANDLER_FINISHED;
+ }
+ buffer_copy_string_len(p->uri.authority, start, sep - start);
+
+ start = sep + 1;
+
+ if (NULL == (sep = strchr(start, '?'))) {
+ /* no query string, good */
+ buffer_copy_string(p->uri.path_raw, start);
+ } else {
+ buffer_copy_string_len(p->uri.path_raw, start, sep - start);
+ }
+
+ if (!buffer_is_equal(p->uri.authority, con->uri.authority)) {
+ /* not the same host */
+ con->http_status = 502;
+ return HANDLER_FINISHED;
+ }
+
+ buffer_copy_string_buffer(p->tmp_buf, p->uri.path_raw);
+ buffer_urldecode_path(p->tmp_buf);
+ buffer_path_simplify(p->uri.path, p->tmp_buf);
+
+ /* we now have a URI which is clean. transform it into a physical path */
+ buffer_copy_string_buffer(p->physical.doc_root, con->physical.doc_root);
+ buffer_copy_string_buffer(p->physical.rel_path, p->uri.path);
+
+ if (con->conf.force_lowercase_filenames) {
+ buffer_to_lower(p->physical.rel_path);
+ }
+
+ buffer_copy_string_buffer(p->physical.path, p->physical.doc_root);
+ BUFFER_APPEND_SLASH(p->physical.path);
+ buffer_copy_string_buffer(p->physical.basedir, p->physical.path);
+
+ /* don't add a second / */
+ if (p->physical.rel_path->ptr[0] == '/') {
+ buffer_append_string_len(p->physical.path, p->physical.rel_path->ptr + 1, p->physical.rel_path->used - 2);
+ } else {
+ buffer_append_string_buffer(p->physical.path, p->physical.rel_path);
+ }
+
+ /* let's see if the source is a directory
+ * if yes, we fail with 501 */
+
+ if (-1 == stat(con->physical.path->ptr, &st)) {
+ /* don't about it yet, unlink will fail too */
+ switch(errno) {
+ case ENOENT:
+ con->http_status = 404;
+ break;
+ default:
+ con->http_status = 403;
+ break;
+ }
+ } else if (S_ISDIR(st.st_mode)) {
+ int r;
+ /* src is a directory */
+
+ if (-1 == stat(p->physical.path->ptr, &st)) {
+ if (-1 == mkdir(p->physical.path->ptr, 0700)) {
+ con->http_status = 403;
+ return HANDLER_FINISHED;
+ }
+ } else if (!S_ISDIR(st.st_mode)) {
+ if (overwrite == 0) {
+ /* copying into a non-dir ? */
+ con->http_status = 409;
+ return HANDLER_FINISHED;
+ } else {
+ unlink(p->physical.path->ptr);
+ if (-1 == mkdir(p->physical.path->ptr, 0700)) {
+ con->http_status = 403;
+ return HANDLER_FINISHED;
+ }
+ }
+ }
+
+ /* copy the content of src to dest */
+ if (0 != (r = webdav_copy_dir(srv, con, p, &(con->physical), &(p->physical), overwrite))) {
+ con->http_status = r;
+ return HANDLER_FINISHED;
+ }
+ if (con->request.http_method == HTTP_METHOD_MOVE) {
+ b = buffer_init();
+ webdav_delete_dir(srv, con, p, &(con->physical), b); /* content */
+ buffer_free(b);
+
+ rmdir(con->physical.path->ptr);
+ }
+ con->http_status = 201;
+ } else {
+ /* it is just a file, good */
+ int r;
+
+ /* destination exists */
+ if (0 == (r = stat(p->physical.path->ptr, &st))) {
+ if (S_ISDIR(st.st_mode)) {
+ /* file to dir/
+ * append basename to physical path */
+
+ if (NULL != (sep = strrchr(con->physical.path->ptr, '/'))) {
+ buffer_append_string(p->physical.path, sep);
+ r = stat(p->physical.path->ptr, &st);
+ }
+ }
+ }
+
+ if (-1 == r) {
+ con->http_status = 201; /* we will create a new one */
+
+ switch(errno) {
+ case ENOTDIR:
+ con->http_status = 409;
+ return HANDLER_FINISHED;
+ }
+ } else if (overwrite == 0) {
+ /* destination exists, but overwrite is not set */
+ con->http_status = 412;
+ return HANDLER_FINISHED;
+ } else {
+ con->http_status = 204; /* resource already existed */
+ }
+
+ if (con->request.http_method == HTTP_METHOD_MOVE) {
+ /* try a rename */
+
+ if (0 == rename(con->physical.path->ptr, p->physical.path->ptr)) {
+#ifdef USE_PROPPATCH
+ sqlite3_stmt *stmt = p->conf.stmt_move_uri;
+
+ if (stmt) {
+
+ sqlite3_reset(stmt);
+
+ /* bind the values to the insert */
+ sqlite3_bind_text(stmt, 1,
+ p->uri.path->ptr,
+ p->uri.path->used - 1,
+ SQLITE_TRANSIENT);
+
+ sqlite3_bind_text(stmt, 2,
+ con->uri.path->ptr,
+ con->uri.path->used - 1,
+ SQLITE_TRANSIENT);
+
+ if (SQLITE_DONE != sqlite3_step(stmt)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "sql-move failed:", sqlite3_errmsg(p->conf.sql));
+ }
+ }
+#endif
+ return HANDLER_FINISHED;
+ }
+
+ /* rename failed, fall back to COPY + DELETE */
+ }
+
+ if (0 != (r = webdav_copy_file(srv, con, p, &(con->physical), &(p->physical), overwrite))) {
+ con->http_status = r;
+
+ return HANDLER_FINISHED;
+ }
+
+ if (con->request.http_method == HTTP_METHOD_MOVE) {
+ b = buffer_init();
+ webdav_delete_file(srv, con, p, &(con->physical), b);
+ buffer_free(b);
+ }
+ }
+
+ return HANDLER_FINISHED;
+ }
+ case HTTP_METHOD_PROPPATCH: {
+ if (p->conf.is_readonly) {
+ con->http_status = 403;
+ return HANDLER_FINISHED;
+ }
+
+ /* check if destination exists */
+ if (-1 == stat(con->physical.path->ptr, &st)) {
+ switch(errno) {
+ case ENOENT:
+ con->http_status = 404;
+ break;
+ }
+ }
+
+#ifdef USE_PROPPATCH
+ if (con->request.content_length) {
+ xmlDocPtr xml;
+
+ if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) {
+ xmlNode *rootnode = xmlDocGetRootElement(xml);
+
+ if (0 == xmlStrcmp(rootnode->name, BAD_CAST "propertyupdate")) {
+ xmlNode *cmd;
+ char *err = NULL;
+ int empty_ns = 0; /* send 400 on a empty namespace attribute */
+
+ /* start response */
+
+ if (SQLITE_OK != sqlite3_exec(p->conf.sql, "BEGIN TRANSACTION", NULL, NULL, &err)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err);
+ sqlite3_free(err);
+
+ goto propmatch_cleanup;
+ }
+
+ /* a UPDATE request, we know 'set' and 'remove' */
+ for (cmd = rootnode->children; cmd; cmd = cmd->next) {
+ xmlNode *props;
+ /* either set or remove */
+
+ if ((0 == xmlStrcmp(cmd->name, BAD_CAST "set")) ||
+ (0 == xmlStrcmp(cmd->name, BAD_CAST "remove"))) {
+
+ sqlite3_stmt *stmt;
+
+ stmt = (0 == xmlStrcmp(cmd->name, BAD_CAST "remove")) ?
+ p->conf.stmt_delete_prop : p->conf.stmt_update_prop;
+
+ for (props = cmd->children; props; props = props->next) {
+ if (0 == xmlStrcmp(props->name, BAD_CAST "prop")) {
+ xmlNode *prop;
+ int r;
+
+ prop = props->children;
+
+ if (prop->ns &&
+ (0 == xmlStrcmp(prop->ns->href, BAD_CAST "")) &&
+ (0 != xmlStrcmp(prop->ns->prefix, BAD_CAST ""))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "no name space for:",
+ prop->name);
+
+ empty_ns = 1;
+ break;
+ }
+
+ sqlite3_reset(stmt);
+
+ /* bind the values to the insert */
+
+ sqlite3_bind_text(stmt, 1,
+ con->uri.path->ptr,
+ con->uri.path->used - 1,
+ SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 2,
+ (char *)prop->name,
+ strlen((char *)prop->name),
+ SQLITE_TRANSIENT);
+ if (prop->ns) {
+ sqlite3_bind_text(stmt, 3,
+ (char *)prop->ns->href,
+ strlen((char *)prop->ns->href),
+ SQLITE_TRANSIENT);
+ } else {
+ sqlite3_bind_text(stmt, 3,
+ "",
+ 0,
+ SQLITE_TRANSIENT);
+ }
+ if (stmt == p->conf.stmt_update_prop) {
+ sqlite3_bind_text(stmt, 4,
+ (char *)xmlNodeGetContent(prop),
+ strlen((char *)xmlNodeGetContent(prop)),
+ SQLITE_TRANSIENT);
+ }
+
+ if (SQLITE_DONE != (r = sqlite3_step(stmt))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "sql-set failed:", sqlite3_errmsg(p->conf.sql));
+ }
+ }
+ }
+ if (empty_ns) break;
+ }
+ }
+
+ if (empty_ns) {
+ if (SQLITE_OK != sqlite3_exec(p->conf.sql, "ROLLBACK", NULL, NULL, &err)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "can't rollback transaction:", err);
+ sqlite3_free(err);
+
+ goto propmatch_cleanup;
+ }
+
+ con->http_status = 400;
+ } else {
+ if (SQLITE_OK != sqlite3_exec(p->conf.sql, "COMMIT", NULL, NULL, &err)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "can't commit transaction:", err);
+ sqlite3_free(err);
+
+ goto propmatch_cleanup;
+ }
+ con->http_status = 200;
+ }
+ con->file_finished = 1;
+
+ return HANDLER_FINISHED;
+ }
+
+propmatch_cleanup:
+ xmlFreeDoc(xml);
+ } else {
+ con->http_status = 400;
+ return HANDLER_FINISHED;
+ }
+ }
+#endif
+ con->http_status = 501;
+ return HANDLER_FINISHED;
+ }
+ default:
+ break;
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_webdav_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("webdav");
+
+ p->init = mod_webdav_init;
+ p->handle_uri_clean = mod_webdav_uri_handler;
+ p->handle_physical = mod_webdav_subrequest_handler;
+ p->set_defaults = mod_webdav_set_defaults;
+ p->cleanup = mod_webdav_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/network.c b/src/network.c
new file mode 100644
index 0000000..922009f
--- /dev/null
+++ b/src/network.c
@@ -0,0 +1,643 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "connections.h"
+#include "plugin.h"
+#include "joblist.h"
+
+#include "network_backends.h"
+#include "sys-mmap.h"
+#include "sys-socket.h"
+
+#ifdef USE_OPENSSL
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/rand.h>
+#endif
+
+handler_t network_server_handle_fdevent(void *s, void *context, int revents) {
+ server *srv = (server *)s;
+ server_socket *srv_socket = (server_socket *)context;
+ connection *con;
+ int loops = 0;
+
+ UNUSED(context);
+
+ if (revents != FDEVENT_IN) {
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "strange event for server socket",
+ srv_socket->fd,
+ revents);
+ return HANDLER_ERROR;
+ }
+
+ /* accept()s at most 100 connections directly
+ *
+ * we jump out after 100 to give the waiting connections a chance */
+ for (loops = 0; loops < 100 && NULL != (con = connection_accept(srv, srv_socket)); loops++) {
+ handler_t r;
+
+ connection_state_machine(srv, con);
+
+ switch(r = plugins_call_handle_joblist(srv, con)) {
+ case HANDLER_FINISHED:
+ case HANDLER_GO_ON:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "d", r);
+ break;
+ }
+ }
+ return HANDLER_GO_ON;
+}
+
+int network_server_init(server *srv, buffer *host_token, specific_config *s) {
+ int val;
+ socklen_t addr_len;
+ server_socket *srv_socket;
+ char *sp;
+ unsigned int port = 0;
+ const char *host;
+ buffer *b;
+ int is_unix_domain_socket = 0;
+ int fd;
+
+#ifdef SO_ACCEPTFILTER
+ struct accept_filter_arg afa;
+#endif
+
+#ifdef __WIN32
+ WORD wVersionRequested;
+ WSADATA wsaData;
+ int err;
+
+ wVersionRequested = MAKEWORD( 2, 2 );
+
+ err = WSAStartup( wVersionRequested, &wsaData );
+ if ( err != 0 ) {
+ /* Tell the user that we could not find a usable */
+ /* WinSock DLL. */
+ return -1;
+ }
+#endif
+
+ srv_socket = calloc(1, sizeof(*srv_socket));
+ srv_socket->fd = -1;
+
+ srv_socket->srv_token = buffer_init();
+ buffer_copy_string_buffer(srv_socket->srv_token, host_token);
+
+ b = buffer_init();
+ buffer_copy_string_buffer(b, host_token);
+
+ /* ipv4:port
+ * [ipv6]:port
+ */
+ if (NULL == (sp = strrchr(b->ptr, ':'))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "value of $SERVER[\"socket\"] has to be \"ip:port\".", b);
+
+ return -1;
+ }
+
+ host = b->ptr;
+
+ /* check for [ and ] */
+ if (b->ptr[0] == '[' && *(sp-1) == ']') {
+ *(sp-1) = '\0';
+ host++;
+
+ s->use_ipv6 = 1;
+ }
+
+ *(sp++) = '\0';
+
+ port = strtol(sp, NULL, 10);
+
+ if (host[0] == '/') {
+ /* host is a unix-domain-socket */
+ is_unix_domain_socket = 1;
+ } else if (port == 0 || port > 65535) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "port out of range:", port);
+
+ return -1;
+ }
+
+ if (*host == '\0') host = NULL;
+
+ if (is_unix_domain_socket) {
+#ifdef HAVE_SYS_UN_H
+
+ srv_socket->addr.plain.sa_family = AF_UNIX;
+
+ if (-1 == (srv_socket->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno));
+ return -1;
+ }
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: Unix Domain sockets are not supported.");
+ return -1;
+#endif
+ }
+
+#ifdef HAVE_IPV6
+ if (s->use_ipv6) {
+ srv_socket->addr.plain.sa_family = AF_INET6;
+
+ if (-1 == (srv_socket->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, IPPROTO_TCP))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno));
+ return -1;
+ }
+ srv_socket->use_ipv6 = 1;
+ }
+#endif
+
+ if (srv_socket->fd == -1) {
+ srv_socket->addr.plain.sa_family = AF_INET;
+ if (-1 == (srv_socket->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, IPPROTO_TCP))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno));
+ return -1;
+ }
+ }
+
+ /* */
+ srv->cur_fds = srv_socket->fd;
+
+ val = 1;
+ if (setsockopt(srv_socket->fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "socketsockopt failed:", strerror(errno));
+ return -1;
+ }
+
+ switch(srv_socket->addr.plain.sa_family) {
+#ifdef HAVE_IPV6
+ case AF_INET6:
+ memset(&srv_socket->addr, 0, sizeof(struct sockaddr_in6));
+ srv_socket->addr.ipv6.sin6_family = AF_INET6;
+ if (host == NULL) {
+ srv_socket->addr.ipv6.sin6_addr = in6addr_any;
+ } else {
+ struct addrinfo hints, *res;
+ int r;
+
+ memset(&hints, 0, sizeof(hints));
+
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ if (0 != (r = getaddrinfo(host, NULL, &hints, &res))) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "sssss", "getaddrinfo failed: ",
+ gai_strerror(r), "'", host, "'");
+
+ return -1;
+ }
+
+ memcpy(&(srv_socket->addr), res->ai_addr, res->ai_addrlen);
+
+ freeaddrinfo(res);
+ }
+ srv_socket->addr.ipv6.sin6_port = htons(port);
+ addr_len = sizeof(struct sockaddr_in6);
+ break;
+#endif
+ case AF_INET:
+ memset(&srv_socket->addr, 0, sizeof(struct sockaddr_in));
+ srv_socket->addr.ipv4.sin_family = AF_INET;
+ if (host == NULL) {
+ srv_socket->addr.ipv4.sin_addr.s_addr = htonl(INADDR_ANY);
+ } else {
+ struct hostent *he;
+ if (NULL == (he = gethostbyname(host))) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "sds", "gethostbyname failed: ",
+ h_errno, host);
+ return -1;
+ }
+
+ if (he->h_addrtype != AF_INET) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "addr-type != AF_INET: ", he->h_addrtype);
+ return -1;
+ }
+
+ if (he->h_length != sizeof(struct in_addr)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "addr-length != sizeof(in_addr): ", he->h_length);
+ return -1;
+ }
+
+ memcpy(&(srv_socket->addr.ipv4.sin_addr.s_addr), he->h_addr_list[0], he->h_length);
+ }
+ srv_socket->addr.ipv4.sin_port = htons(port);
+
+ addr_len = sizeof(struct sockaddr_in);
+
+ break;
+ case AF_UNIX:
+ srv_socket->addr.un.sun_family = AF_UNIX;
+ strcpy(srv_socket->addr.un.sun_path, host);
+
+#ifdef SUN_LEN
+ addr_len = SUN_LEN(&srv_socket->addr.un);
+#else
+ /* stevens says: */
+ addr_len = strlen(host) + sizeof(srv_socket->addr.un.sun_family);
+#endif
+
+ /* check if the socket exists and try to connect to it. */
+ if (-1 != (fd = connect(srv_socket->fd, (struct sockaddr *) &(srv_socket->addr), addr_len))) {
+ close(fd);
+
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "server socket is still in use:",
+ host);
+
+
+ return -1;
+ }
+
+ /* connect failed */
+ switch(errno) {
+ case ECONNREFUSED:
+ unlink(host);
+ break;
+ case ENOENT:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "testing socket failed:",
+ host, strerror(errno));
+
+ return -1;
+ }
+
+ break;
+ default:
+ addr_len = 0;
+
+ return -1;
+ }
+
+ if (0 != bind(srv_socket->fd, (struct sockaddr *) &(srv_socket->addr), addr_len)) {
+ switch(srv_socket->addr.plain.sa_family) {
+ case AF_UNIX:
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "can't bind to socket:",
+ host, strerror(errno));
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssds",
+ "can't bind to port:",
+ host, port, strerror(errno));
+ break;
+ }
+ return -1;
+ }
+
+ if (-1 == listen(srv_socket->fd, 128 * 8)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "listen failed: ", strerror(errno));
+ return -1;
+ }
+
+ if (s->is_ssl) {
+#ifdef USE_OPENSSL
+ if (srv->ssl_is_init == 0) {
+ SSL_load_error_strings();
+ SSL_library_init();
+ srv->ssl_is_init = 1;
+
+ if (0 == RAND_status()) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ "not enough entropy in the pool");
+ return -1;
+ }
+ }
+
+ if (NULL == (s->ssl_ctx = SSL_CTX_new(SSLv23_server_method()))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL));
+ return -1;
+ }
+
+ if (buffer_is_empty(s->ssl_pemfile)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "ssl.pemfile has to be set");
+ return -1;
+ }
+
+ if (!buffer_is_empty(s->ssl_ca_file)) {
+ if (1 != SSL_CTX_load_verify_locations(s->ssl_ctx, s->ssl_ca_file->ptr, NULL)) {
+ log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL), s->ssl_ca_file);
+ return -1;
+ }
+ }
+
+ if (SSL_CTX_use_certificate_file(s->ssl_ctx, s->ssl_pemfile->ptr, SSL_FILETYPE_PEM) < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL), s->ssl_pemfile);
+ return -1;
+ }
+
+ if (SSL_CTX_use_PrivateKey_file (s->ssl_ctx, s->ssl_pemfile->ptr, SSL_FILETYPE_PEM) < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL), s->ssl_pemfile);
+ return -1;
+ }
+
+ if (SSL_CTX_check_private_key(s->ssl_ctx) != 1) {
+ log_error_write(srv, __FILE__, __LINE__, "sssb", "SSL:",
+ "Private key does not match the certificate public key, reason:",
+ ERR_error_string(ERR_get_error(), NULL),
+ s->ssl_pemfile);
+ return -1;
+ }
+ srv_socket->ssl_ctx = s->ssl_ctx;
+#else
+
+ buffer_free(srv_socket->srv_token);
+ free(srv_socket);
+
+ buffer_free(b);
+
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ "ssl requested but openssl support is not compiled in");
+
+ return -1;
+#endif
+ } else {
+#ifdef SO_ACCEPTFILTER
+ /*
+ * FreeBSD accf_http filter
+ *
+ */
+ memset(&afa, 0, sizeof(afa));
+ strcpy(afa.af_name, "httpready");
+ if (setsockopt(srv_socket->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)) < 0) {
+ if (errno != ENOENT) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "can't set accept-filter 'httpready': ", strerror(errno));
+ }
+ }
+#endif
+ }
+
+ srv_socket->is_ssl = s->is_ssl;
+ srv_socket->fde_ndx = -1;
+
+ if (srv->srv_sockets.size == 0) {
+ srv->srv_sockets.size = 4;
+ srv->srv_sockets.used = 0;
+ srv->srv_sockets.ptr = malloc(srv->srv_sockets.size * sizeof(server_socket));
+ } else if (srv->srv_sockets.used == srv->srv_sockets.size) {
+ srv->srv_sockets.size += 4;
+ srv->srv_sockets.ptr = realloc(srv->srv_sockets.ptr, srv->srv_sockets.size * sizeof(server_socket));
+ }
+
+ srv->srv_sockets.ptr[srv->srv_sockets.used++] = srv_socket;
+
+ buffer_free(b);
+
+ return 0;
+}
+
+int network_close(server *srv) {
+ size_t i;
+ for (i = 0; i < srv->srv_sockets.used; i++) {
+ server_socket *srv_socket = srv->srv_sockets.ptr[i];
+
+ if (srv_socket->fd != -1) {
+ /* check if server fd are already registered */
+ if (srv_socket->fde_ndx != -1) {
+ fdevent_event_del(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd);
+ fdevent_unregister(srv->ev, srv_socket->fd);
+ }
+
+ close(srv_socket->fd);
+ }
+
+ buffer_free(srv_socket->srv_token);
+
+ free(srv_socket);
+ }
+
+ free(srv->srv_sockets.ptr);
+
+ return 0;
+}
+
+typedef enum {
+ NETWORK_BACKEND_UNSET,
+ NETWORK_BACKEND_WRITE,
+ NETWORK_BACKEND_WRITEV,
+ NETWORK_BACKEND_LINUX_SENDFILE,
+ NETWORK_BACKEND_FREEBSD_SENDFILE,
+ NETWORK_BACKEND_SOLARIS_SENDFILEV
+} network_backend_t;
+
+int network_init(server *srv) {
+ buffer *b;
+ size_t i;
+ network_backend_t backend;
+
+ struct nb_map {
+ network_backend_t nb;
+ const char *name;
+ } network_backends[] = {
+ /* lowest id wins */
+#if defined USE_LINUX_SENDFILE
+ { NETWORK_BACKEND_LINUX_SENDFILE, "linux-sendfile" },
+#endif
+#if defined USE_FREEBSD_SENDFILE
+ { NETWORK_BACKEND_FREEBSD_SENDFILE, "freebsd-sendfile" },
+#endif
+#if defined USE_SOLARIS_SENDFILEV
+ { NETWORK_BACKEND_SOLARIS_SENDFILEV, "solaris-sendfilev" },
+#endif
+#if defined USE_WRITEV
+ { NETWORK_BACKEND_WRITEV, "writev" },
+#endif
+ { NETWORK_BACKEND_WRITE, "write" },
+ { NETWORK_BACKEND_UNSET, NULL }
+ };
+
+ b = buffer_init();
+
+ buffer_copy_string_buffer(b, srv->srvconf.bindhost);
+ buffer_append_string(b, ":");
+ buffer_append_long(b, srv->srvconf.port);
+
+ if (0 != network_server_init(srv, b, srv->config_storage[0])) {
+ return -1;
+ }
+ buffer_free(b);
+
+#ifdef USE_OPENSSL
+ srv->network_ssl_backend_write = network_write_chunkqueue_openssl;
+#endif
+
+ /* get a usefull default */
+ backend = network_backends[0].nb;
+
+ /* match name against known types */
+ if (!buffer_is_empty(srv->srvconf.network_backend)) {
+ for (i = 0; network_backends[i].name; i++) {
+ /**/
+ if (buffer_is_equal_string(srv->srvconf.network_backend, network_backends[i].name, strlen(network_backends[i].name))) {
+ backend = network_backends[i].nb;
+ break;
+ }
+ }
+ if (NULL == network_backends[i].name) {
+ /* we don't know it */
+
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "server.network-backend has a unknown value:",
+ srv->srvconf.network_backend);
+
+ return -1;
+ }
+ }
+
+ switch(backend) {
+ case NETWORK_BACKEND_WRITE:
+ srv->network_backend_write = network_write_chunkqueue_write;
+ break;
+#ifdef USE_WRITEV
+ case NETWORK_BACKEND_WRITEV:
+ srv->network_backend_write = network_write_chunkqueue_writev;
+ break;
+#endif
+#ifdef USE_LINUX_SENDFILE
+ case NETWORK_BACKEND_LINUX_SENDFILE:
+ srv->network_backend_write = network_write_chunkqueue_linuxsendfile;
+ break;
+#endif
+#ifdef USE_FREEBSD_SENDFILE
+ case NETWORK_BACKEND_FREEBSD_SENDFILE:
+ srv->network_backend_write = network_write_chunkqueue_freebsdsendfile;
+ break;
+#endif
+#ifdef USE_SOLARIS_SENDFILEV
+ case NETWORK_BACKEND_SOLARIS_SENDFILEV:
+ srv->network_backend_write = network_write_chunkqueue_solarissendfilev;
+ break;
+#endif
+ default:
+ return -1;
+ }
+
+ /* check for $SERVER["socket"] */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ specific_config *s = srv->config_storage[i];
+
+ /* not our stage */
+ if (COMP_SERVER_SOCKET != dc->comp) continue;
+
+ if (dc->cond != CONFIG_COND_EQ) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "only == is allowed for $SERVER[\"socket\"].");
+
+ return -1;
+ }
+
+ if (0 != network_server_init(srv, dc->string, s)) {
+ return -1;
+ }
+ }
+
+
+ return 0;
+}
+
+int network_register_fdevents(server *srv) {
+ size_t i;
+
+ if (-1 == fdevent_reset(srv->ev)) {
+ return -1;
+ }
+
+ /* register fdevents after reset */
+ for (i = 0; i < srv->srv_sockets.used; i++) {
+ server_socket *srv_socket = srv->srv_sockets.ptr[i];
+
+ fdevent_register(srv->ev, srv_socket->fd, network_server_handle_fdevent, srv_socket);
+ fdevent_event_add(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN);
+ }
+ return 0;
+}
+
+int network_write_chunkqueue(server *srv, connection *con, chunkqueue *cq) {
+ int ret = -1;
+ off_t written = 0;
+#ifdef TCP_CORK
+ int corked = 0;
+#endif
+ server_socket *srv_socket = con->srv_socket;
+
+ if (con->conf.global_kbytes_per_second &&
+ *(con->conf.global_bytes_per_second_cnt_ptr) > con->conf.global_kbytes_per_second * 1024) {
+ /* we reached the global traffic limit */
+
+ con->traffic_limit_reached = 1;
+ joblist_append(srv, con);
+
+ return 1;
+ }
+
+ written = cq->bytes_out;
+
+#ifdef TCP_CORK
+ /* Linux: put a cork into the socket as we want to combine the write() calls
+ * but only if we really have multiple chunks
+ */
+ if (cq->first && cq->first->next) {
+ corked = 1;
+ setsockopt(con->fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked));
+ }
+#endif
+
+ if (srv_socket->is_ssl) {
+#ifdef USE_OPENSSL
+ ret = srv->network_ssl_backend_write(srv, con, con->ssl, cq);
+#endif
+ } else {
+ ret = srv->network_backend_write(srv, con, con->fd, cq);
+ }
+
+ if (ret >= 0) {
+ chunkqueue_remove_finished_chunks(cq);
+ ret = chunkqueue_is_empty(cq) ? 0 : 1;
+ }
+
+#ifdef TCP_CORK
+ if (corked) {
+ corked = 0;
+ setsockopt(con->fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked));
+ }
+#endif
+
+ written = cq->bytes_out - written;
+ con->bytes_written += written;
+ con->bytes_written_cur_second += written;
+
+ *(con->conf.global_bytes_per_second_cnt_ptr) += written;
+
+ if (con->conf.kbytes_per_second &&
+ (con->bytes_written_cur_second > con->conf.kbytes_per_second * 1024)) {
+ /* we reached the traffic limit */
+
+ con->traffic_limit_reached = 1;
+ joblist_append(srv, con);
+ }
+ return ret;
+}
diff --git a/src/network.h b/src/network.h
new file mode 100644
index 0000000..99c7596
--- /dev/null
+++ b/src/network.h
@@ -0,0 +1,13 @@
+#ifndef _NETWORK_H_
+#define _NETWORK_H_
+
+#include "server.h"
+
+int network_write_chunkqueue(server *srv, connection *con, chunkqueue *c);
+
+int network_init(server *srv);
+int network_close(server *srv);
+
+int network_register_fdevents(server *srv);
+
+#endif
diff --git a/src/network_backends.h b/src/network_backends.h
new file mode 100644
index 0000000..94650a7
--- /dev/null
+++ b/src/network_backends.h
@@ -0,0 +1,58 @@
+#ifndef _NETWORK_BACKENDS_H_
+#define _NETWORK_BACKENDS_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+/* on linux 2.4.x you get either sendfile or LFS */
+#if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) && defined HAVE_WRITEV && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN
+# define USE_LINUX_SENDFILE
+# include <sys/sendfile.h>
+# include <sys/uio.h>
+#endif
+
+#if defined HAVE_SYS_UIO_H && defined HAVE_SENDFILE && defined HAVE_WRITEV && defined(__FreeBSD__)
+# define USE_FREEBSD_SENDFILE
+# include <sys/uio.h>
+#endif
+
+#if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILEV && defined HAVE_WRITEV && defined(__sun)
+# define USE_SOLARIS_SENDFILEV
+# include <sys/sendfile.h>
+# include <sys/uio.h>
+#endif
+
+#if defined HAVE_SYS_UIO_H && defined HAVE_WRITEV
+# define USE_WRITEV
+# include <sys/uio.h>
+#endif
+
+#if defined HAVE_SYS_MMAN_H && defined HAVE_MMAP
+# define USE_MMAP
+# include <sys/mman.h>
+/* NetBSD 1.3.x needs it */
+# ifndef MAP_FAILED
+# define MAP_FAILED -1
+# endif
+#endif
+
+#if defined HAVE_SYS_UIO_H && defined HAVE_WRITEV && defined HAVE_SEND_FILE && defined(__aix)
+# define USE_AIX_SENDFILE
+#endif
+
+#include "base.h"
+
+
+int network_write_chunkqueue_write(server *srv, connection *con, int fd, chunkqueue *cq);
+int network_write_chunkqueue_writev(server *srv, connection *con, int fd, chunkqueue *cq);
+int network_write_chunkqueue_linuxsendfile(server *srv, connection *con, int fd, chunkqueue *cq);
+int network_write_chunkqueue_freebsdsendfile(server *srv, connection *con, int fd, chunkqueue *cq);
+int network_write_chunkqueue_solarissendfilev(server *srv, connection *con, int fd, chunkqueue *cq);
+#ifdef USE_OPENSSL
+int network_write_chunkqueue_openssl(server *srv, connection *con, SSL *ssl, chunkqueue *cq);
+#endif
+
+#endif
diff --git a/src/network_freebsd_sendfile.c b/src/network_freebsd_sendfile.c
new file mode 100644
index 0000000..a6994c0
--- /dev/null
+++ b/src/network_freebsd_sendfile.c
@@ -0,0 +1,207 @@
+#include "network_backends.h"
+
+#ifdef USE_FREEBSD_SENDFILE
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "stat_cache.h"
+
+
+#ifndef UIO_MAXIOV
+# ifdef __FreeBSD__
+/* FreeBSD 4.7, 4.9 defined it in sys/uio.h only if _KERNEL is specified */
+# define UIO_MAXIOV 1024
+# endif
+#endif
+
+int network_write_chunkqueue_freebsdsendfile(server *srv, connection *con, int fd, chunkqueue *cq) {
+ chunk *c;
+ size_t chunks_written = 0;
+
+ for(c = cq->first; c; c = c->next, chunks_written++) {
+ int chunk_finished = 0;
+
+ switch(c->type) {
+ case MEM_CHUNK: {
+ char * offset;
+ size_t toSend;
+ ssize_t r;
+
+ size_t num_chunks, i;
+ struct iovec chunks[UIO_MAXIOV];
+ chunk *tc;
+ size_t num_bytes = 0;
+
+ /* we can't send more then SSIZE_MAX bytes in one chunk */
+
+ /* build writev list
+ *
+ * 1. limit: num_chunks < UIO_MAXIOV
+ * 2. limit: num_bytes < SSIZE_MAX
+ */
+ for(num_chunks = 0, tc = c; tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV; num_chunks++, tc = tc->next);
+
+ for(tc = c, i = 0; i < num_chunks; tc = tc->next, i++) {
+ if (tc->mem->used == 0) {
+ chunks[i].iov_base = tc->mem->ptr;
+ chunks[i].iov_len = 0;
+ } else {
+ offset = tc->mem->ptr + tc->offset;
+ toSend = tc->mem->used - 1 - tc->offset;
+
+ chunks[i].iov_base = offset;
+
+ /* protect the return value of writev() */
+ if (toSend > SSIZE_MAX ||
+ num_bytes + toSend > SSIZE_MAX) {
+ chunks[i].iov_len = SSIZE_MAX - num_bytes;
+
+ num_chunks = i + 1;
+ break;
+ } else {
+ chunks[i].iov_len = toSend;
+ }
+
+ num_bytes += toSend;
+ }
+ }
+
+ if ((r = writev(fd, chunks, num_chunks)) < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ r = 0;
+ break;
+ case EPIPE:
+ case ECONNRESET:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "writev failed:", strerror(errno), fd);
+
+ return -1;
+ }
+
+ r = 0;
+ }
+
+ /* check which chunks have been written */
+ cq->bytes_out += r;
+
+ for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) {
+ if (r >= (ssize_t)chunks[i].iov_len) {
+ /* written */
+ r -= chunks[i].iov_len;
+ tc->offset += chunks[i].iov_len;
+
+ if (chunk_finished) {
+ /* skip the chunks from further touches */
+ chunks_written++;
+ c = c->next;
+ } else {
+ /* chunks_written + c = c->next is done in the for()*/
+ chunk_finished++;
+ }
+ } else {
+ /* partially written */
+
+ tc->offset += r;
+ chunk_finished = 0;
+
+ break;
+ }
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ off_t offset, r;
+ size_t toSend;
+ stat_cache_entry *sce = NULL;
+ int ifd;
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->file.name);
+ return -1;
+ }
+
+ offset = c->file.start + c->offset;
+ /* limit the toSend to 2^31-1 bytes in a chunk */
+ toSend = c->file.length - c->offset > ((1 << 30) - 1) ?
+ ((1 << 30) - 1) : c->file.length - c->offset;
+
+ if (offset > sce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->file.name);
+
+ return -1;
+ }
+
+ if (-1 == (ifd = open(c->file.name->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
+
+ return -1;
+ }
+
+ r = 0;
+
+ /* FreeBSD sendfile() */
+ if (-1 == sendfile(ifd, fd, offset, toSend, NULL, &r, 0)) {
+ switch(errno) {
+ case EAGAIN:
+ break;
+ case ENOTCONN:
+ close(ifd);
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile: ", strerror(errno), errno);
+ close(ifd);
+ return -1;
+ }
+ }
+ close(ifd);
+
+ c->offset += r;
+ cq->bytes_out += r;
+
+ if (c->offset == c->file.length) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ default:
+
+ log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known");
+
+ return -1;
+ }
+
+ if (!chunk_finished) {
+ /* not finished yet */
+
+ break;
+ }
+ }
+
+ return chunks_written;
+}
+
+#endif
diff --git a/src/network_linux_sendfile.c b/src/network_linux_sendfile.c
new file mode 100644
index 0000000..6426568
--- /dev/null
+++ b/src/network_linux_sendfile.c
@@ -0,0 +1,245 @@
+#include "network_backends.h"
+
+#ifdef USE_LINUX_SENDFILE
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "stat_cache.h"
+
+int network_write_chunkqueue_linuxsendfile(server *srv, connection *con, int fd, chunkqueue *cq) {
+ chunk *c;
+ size_t chunks_written = 0;
+
+ for(c = cq->first; c; c = c->next, chunks_written++) {
+ int chunk_finished = 0;
+
+ switch(c->type) {
+ case MEM_CHUNK: {
+ char * offset;
+ size_t toSend;
+ ssize_t r;
+
+ size_t num_chunks, i;
+ struct iovec chunks[UIO_MAXIOV];
+ chunk *tc;
+ size_t num_bytes = 0;
+
+ /* we can't send more then SSIZE_MAX bytes in one chunk */
+
+ /* build writev list
+ *
+ * 1. limit: num_chunks < UIO_MAXIOV
+ * 2. limit: num_bytes < SSIZE_MAX
+ */
+ for (num_chunks = 0, tc = c;
+ tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV;
+ tc = tc->next, num_chunks++);
+
+ for (tc = c, i = 0; i < num_chunks; tc = tc->next, i++) {
+ if (tc->mem->used == 0) {
+ chunks[i].iov_base = tc->mem->ptr;
+ chunks[i].iov_len = 0;
+ } else {
+ offset = tc->mem->ptr + tc->offset;
+ toSend = tc->mem->used - 1 - tc->offset;
+
+ chunks[i].iov_base = offset;
+
+ /* protect the return value of writev() */
+ if (toSend > SSIZE_MAX ||
+ num_bytes + toSend > SSIZE_MAX) {
+ chunks[i].iov_len = SSIZE_MAX - num_bytes;
+
+ num_chunks = i + 1;
+ break;
+ } else {
+ chunks[i].iov_len = toSend;
+ }
+
+ num_bytes += toSend;
+ }
+ }
+
+ if ((r = writev(fd, chunks, num_chunks)) < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ r = 0;
+ break;
+ case EPIPE:
+ case ECONNRESET:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "writev failed:", strerror(errno), fd);
+
+ return -1;
+ }
+ }
+
+ /* check which chunks have been written */
+ cq->bytes_out += r;
+
+ for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) {
+ if (r >= (ssize_t)chunks[i].iov_len) {
+ /* written */
+ r -= chunks[i].iov_len;
+ tc->offset += chunks[i].iov_len;
+
+ if (chunk_finished) {
+ /* skip the chunks from further touches */
+ chunks_written++;
+ c = c->next;
+ } else {
+ /* chunks_written + c = c->next is done in the for()*/
+ chunk_finished++;
+ }
+ } else {
+ /* partially written */
+
+ tc->offset += r;
+ chunk_finished = 0;
+
+ break;
+ }
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ ssize_t r;
+ off_t offset;
+ size_t toSend;
+ stat_cache_entry *sce = NULL;
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->file.name);
+ return -1;
+ }
+
+ offset = c->file.start + c->offset;
+ /* limit the toSend to 2^31-1 bytes in a chunk */
+ toSend = c->file.length - c->offset > ((1 << 30) - 1) ?
+ ((1 << 30) - 1) : c->file.length - c->offset;
+
+ if (offset > sce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->file.name);
+
+ return -1;
+ }
+
+ /* open file if not already opened */
+ if (-1 == c->file.fd) {
+ if (-1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
+
+ return -1;
+ }
+#ifdef FD_CLOEXEC
+ fcntl(c->file.fd, F_SETFD, FD_CLOEXEC);
+#endif
+#ifdef HAVE_POSIX_FADVISE
+ /* tell the kernel that we want to stream the file */
+ if (-1 == posix_fadvise(c->file.fd, 0, 0, POSIX_FADV_SEQUENTIAL)) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "posix_fadvise failed:", strerror(errno), c->file.fd);
+ }
+#endif
+ }
+
+
+ /* Linux sendfile() */
+ if (-1 == (r = sendfile(fd, c->file.fd, &offset, toSend))) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ r = 0;
+ break;
+ case EPIPE:
+ case ECONNRESET:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "sendfile failed:", strerror(errno), fd);
+ return -1;
+ }
+ }
+
+ if (r == 0) {
+ /* we got a event to write put we couldn't. remote side closed ? */
+ return -2;
+ }
+
+#ifdef HAVE_POSIX_FADVISE
+#if 0
+#define K * 1024
+#define M * 1024 K
+#define READ_AHEAD 4 M
+ /* check if we need a new chunk */
+ if ((c->offset & ~(READ_AHEAD - 1)) != ((c->offset + r) & ~(READ_AHEAD - 1))) {
+ /* tell the kernel that we want to stream the file */
+ if (-1 == posix_fadvise(c->file.fd, (c->offset + r) & ~(READ_AHEAD - 1), READ_AHEAD, POSIX_FADV_NOREUSE)) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "posix_fadvise failed:", strerror(errno), c->file.fd);
+ }
+ }
+#endif
+#endif
+
+ c->offset += r;
+ cq->bytes_out += r;
+
+ if (c->offset == c->file.length) {
+ chunk_finished = 1;
+
+ /* chunk_free() / chunk_reset() will cleanup for us but it is a ok to be faster :) */
+
+ if (c->file.fd != -1) {
+ close(c->file.fd);
+ c->file.fd = -1;
+ }
+ }
+
+ break;
+ }
+ default:
+
+ log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known");
+
+ return -1;
+ }
+
+ if (!chunk_finished) {
+ /* not finished yet */
+
+ break;
+ }
+ }
+
+ return chunks_written;
+}
+
+#endif
+#if 0
+network_linuxsendfile_init(void) {
+ p->write = network_linuxsendfile_write_chunkset;
+}
+#endif
diff --git a/src/network_openssl.c b/src/network_openssl.c
new file mode 100644
index 0000000..b6a1b2f
--- /dev/null
+++ b/src/network_openssl.c
@@ -0,0 +1,273 @@
+#include "network_backends.h"
+
+#ifdef USE_OPENSSL
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "stat_cache.h"
+
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+
+int network_write_chunkqueue_openssl(server *srv, connection *con, SSL *ssl, chunkqueue *cq) {
+ int ssl_r;
+ chunk *c;
+ size_t chunks_written = 0;
+
+ /* this is a 64k sendbuffer
+ *
+ * it has to stay at the same location all the time to satisfy the needs
+ * of SSL_write to pass the SAME parameter in case of a _WANT_WRITE
+ *
+ * the buffer is allocated once, is NOT realloced and is NOT freed at shutdown
+ * -> we expect a 64k block to 'leak' in valgrind
+ *
+ *
+ * In reality we would like to use mmap() but we don't have a guarantee that
+ * we get the same mmap() address for each call. On openbsd the mmap() address
+ * even randomized.
+ * That means either we keep the mmap() open or we do a read() into a
+ * constant buffer
+ * */
+#define LOCAL_SEND_BUFSIZE (64 * 1024)
+ static char *local_send_buffer = NULL;
+
+ /* the remote side closed the connection before without shutdown request
+ * - IE
+ * - wget
+ * if keep-alive is disabled */
+
+ if (con->keep_alive == 0) {
+ SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
+ }
+
+ for(c = cq->first; c; c = c->next) {
+ int chunk_finished = 0;
+
+ switch(c->type) {
+ case MEM_CHUNK: {
+ char * offset;
+ size_t toSend;
+ ssize_t r;
+
+ if (c->mem->used == 0) {
+ chunk_finished = 1;
+ break;
+ }
+
+ offset = c->mem->ptr + c->offset;
+ toSend = c->mem->used - 1 - c->offset;
+
+ /**
+ * SSL_write man-page
+ *
+ * WARNING
+ * When an SSL_write() operation has to be repeated because of
+ * SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, it must be
+ * repeated with the same arguments.
+ *
+ */
+
+ if ((r = SSL_write(ssl, offset, toSend)) <= 0) {
+ unsigned long err;
+
+ switch ((ssl_r = SSL_get_error(ssl, r))) {
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ case SSL_ERROR_SYSCALL:
+ /* perhaps we have error waiting in our error-queue */
+ if (0 != (err = ERR_get_error())) {
+ do {
+ log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:",
+ ssl_r, r,
+ ERR_error_string(err, NULL));
+ } while((err = ERR_get_error()));
+ } else if (r == -1) {
+ /* no, but we have errno */
+ switch(errno) {
+ case EPIPE:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL:",
+ ssl_r, r, errno,
+ strerror(errno));
+ break;
+ }
+ } else {
+ /* neither error-queue nor errno ? */
+ log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL (error):",
+ ssl_r, r, errno,
+ strerror(errno));
+ }
+
+ return -1;
+ case SSL_ERROR_ZERO_RETURN:
+ /* clean shutdown on the remote side */
+
+ if (r == 0) return -2;
+
+ /* fall through */
+ default:
+ while((err = ERR_get_error())) {
+ log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:",
+ ssl_r, r,
+ ERR_error_string(err, NULL));
+ }
+
+ return -1;
+ }
+ } else {
+ c->offset += r;
+ cq->bytes_out += r;
+ }
+
+ if (c->offset == (off_t)c->mem->used - 1) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ char *s;
+ ssize_t r;
+ stat_cache_entry *sce = NULL;
+ int ifd;
+ int write_wait = 0;
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->file.name);
+ return -1;
+ }
+
+ if (NULL == local_send_buffer) {
+ local_send_buffer = malloc(LOCAL_SEND_BUFSIZE);
+ assert(local_send_buffer);
+ }
+
+ do {
+ off_t offset = c->file.start + c->offset;
+ off_t toSend = c->file.length - c->offset;
+
+ if (toSend > LOCAL_SEND_BUFSIZE) toSend = LOCAL_SEND_BUFSIZE;
+
+ if (-1 == (ifd = open(c->file.name->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "open failed:", strerror(errno));
+
+ return -1;
+ }
+
+
+ lseek(ifd, offset, SEEK_SET);
+ if (-1 == (toSend = read(ifd, local_send_buffer, toSend))) {
+ close(ifd);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "read failed:", strerror(errno));
+ return -1;
+ }
+
+ s = local_send_buffer;
+
+ close(ifd);
+
+ if ((r = SSL_write(ssl, s, toSend)) <= 0) {
+ unsigned long err;
+
+ switch ((ssl_r = SSL_get_error(ssl, r))) {
+ case SSL_ERROR_WANT_WRITE:
+ write_wait = 1;
+ break;
+ case SSL_ERROR_SYSCALL:
+ /* perhaps we have error waiting in our error-queue */
+ if (0 != (err = ERR_get_error())) {
+ do {
+ log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:",
+ ssl_r, r,
+ ERR_error_string(err, NULL));
+ } while((err = ERR_get_error()));
+ } else if (r == -1) {
+ /* no, but we have errno */
+ switch(errno) {
+ case EPIPE:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL:",
+ ssl_r, r, errno,
+ strerror(errno));
+ break;
+ }
+ } else {
+ /* neither error-queue nor errno ? */
+ log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL (error):",
+ ssl_r, r, errno,
+ strerror(errno));
+ }
+
+ return -1;
+ case SSL_ERROR_ZERO_RETURN:
+ /* clean shutdown on the remote side */
+
+ if (r == 0) return -2;
+
+ /* fall thourgh */
+ default:
+ while((err = ERR_get_error())) {
+ log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:",
+ ssl_r, r,
+ ERR_error_string(err, NULL));
+ }
+
+ return -1;
+ }
+ } else {
+ c->offset += r;
+ cq->bytes_out += r;
+ }
+
+ if (c->offset == c->file.length) {
+ chunk_finished = 1;
+ }
+ } while(!chunk_finished && !write_wait);
+
+ break;
+ }
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "s", "type not known");
+
+ return -1;
+ }
+
+ if (!chunk_finished) {
+ /* not finished yet */
+
+ break;
+ }
+
+ chunks_written++;
+ }
+
+ return chunks_written;
+}
+#endif
+
+#if 0
+network_openssl_init(void) {
+ p->write_ssl = network_openssl_write_chunkset;
+}
+#endif
diff --git a/src/network_solaris_sendfilev.c b/src/network_solaris_sendfilev.c
new file mode 100644
index 0000000..0ab669f
--- /dev/null
+++ b/src/network_solaris_sendfilev.c
@@ -0,0 +1,213 @@
+#include "network_backends.h"
+
+#ifdef USE_SOLARIS_SENDFILEV
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "stat_cache.h"
+
+#ifndef UIO_MAXIOV
+#define UIO_MAXIOV IOV_MAX
+#endif
+
+/**
+ * a very simple sendfilev() interface for solaris which can be optimised a lot more
+ * as solaris sendfilev() supports 'sending everythin in one syscall()'
+ *
+ * If you want such an interface and need the performance, just give me an account on
+ * a solaris box.
+ * - jan@kneschke.de
+ */
+
+
+int network_write_chunkqueue_solarissendfilev(server *srv, connection *con, int fd, chunkqueue *cq) {
+ chunk *c;
+ size_t chunks_written = 0;
+
+ for(c = cq->first; c; c = c->next, chunks_written++) {
+ int chunk_finished = 0;
+
+ switch(c->type) {
+ case MEM_CHUNK: {
+ char * offset;
+ size_t toSend;
+ ssize_t r;
+
+ size_t num_chunks, i;
+ struct iovec chunks[UIO_MAXIOV];
+ chunk *tc;
+
+ size_t num_bytes = 0;
+
+ /* we can't send more then SSIZE_MAX bytes in one chunk */
+
+ /* build writev list
+ *
+ * 1. limit: num_chunks < UIO_MAXIOV
+ * 2. limit: num_bytes < SSIZE_MAX
+ */
+ for(num_chunks = 0, tc = c; tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV; num_chunks++, tc = tc->next);
+
+ for(tc = c, i = 0; i < num_chunks; tc = tc->next, i++) {
+ if (tc->mem->used == 0) {
+ chunks[i].iov_base = tc->mem->ptr;
+ chunks[i].iov_len = 0;
+ } else {
+ offset = tc->mem->ptr + tc->offset;
+ toSend = tc->mem->used - 1 - tc->offset;
+
+ chunks[i].iov_base = offset;
+
+ /* protect the return value of writev() */
+ if (toSend > SSIZE_MAX ||
+ num_bytes + toSend > SSIZE_MAX) {
+ chunks[i].iov_len = SSIZE_MAX - num_bytes;
+
+ num_chunks = i + 1;
+ break;
+ } else {
+ chunks[i].iov_len = toSend;
+ }
+
+ num_bytes += toSend;
+ }
+ }
+
+ if ((r = writev(fd, chunks, num_chunks)) < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ r = 0;
+ break;
+ case EPIPE:
+ case ECONNRESET:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "writev failed:", strerror(errno), fd);
+
+ return -1;
+ }
+ }
+
+ /* check which chunks have been written */
+ cq->bytes_out += r;
+
+ for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) {
+ if (r >= (ssize_t)chunks[i].iov_len) {
+ /* written */
+ r -= chunks[i].iov_len;
+ tc->offset += chunks[i].iov_len;
+
+ if (chunk_finished) {
+ /* skip the chunks from further touches */
+ chunks_written++;
+ c = c->next;
+ } else {
+ /* chunks_written + c = c->next is done in the for()*/
+ chunk_finished++;
+ }
+ } else {
+ /* partially written */
+
+ tc->offset += r;
+ chunk_finished = 0;
+
+ break;
+ }
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ ssize_t r;
+ off_t offset;
+ size_t toSend, written;
+ sendfilevec_t fvec;
+ stat_cache_entry *sce = NULL;
+ int ifd;
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->file.name);
+ return -1;
+ }
+
+ offset = c->file.start + c->offset;
+ toSend = c->file.length - c->offset;
+
+ if (offset > sce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->file.name);
+
+ return -1;
+ }
+
+ if (-1 == (ifd = open(c->file.name->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
+
+ return -1;
+ }
+
+ fvec.sfv_fd = ifd;
+ fvec.sfv_flag = 0;
+ fvec.sfv_off = offset;
+ fvec.sfv_len = toSend;
+
+ /* Solaris sendfilev() */
+ if (-1 == (r = sendfilev(fd, &fvec, 1, &written))) {
+ if (errno != EAGAIN) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile: ", strerror(errno), errno);
+
+ close(ifd);
+ return -1;
+ }
+
+ r = 0;
+ }
+
+ close(ifd);
+ c->offset += written;
+ cq->bytes_out += written;
+
+ if (c->offset == c->file.length) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ default:
+
+ log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known");
+
+ return -1;
+ }
+
+ if (!chunk_finished) {
+ /* not finished yet */
+
+ break;
+ }
+ }
+
+ return chunks_written;
+}
+
+#endif
diff --git a/src/network_write.c b/src/network_write.c
new file mode 100644
index 0000000..90fc2ac
--- /dev/null
+++ b/src/network_write.c
@@ -0,0 +1,168 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "stat_cache.h"
+
+#include "sys-socket.h"
+
+#include "network_backends.h"
+
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+int network_write_chunkqueue_write(server *srv, connection *con, int fd, chunkqueue *cq) {
+ chunk *c;
+ size_t chunks_written = 0;
+
+ for(c = cq->first; c; c = c->next) {
+ int chunk_finished = 0;
+
+ switch(c->type) {
+ case MEM_CHUNK: {
+ char * offset;
+ size_t toSend;
+ ssize_t r;
+
+ if (c->mem->used == 0) {
+ chunk_finished = 1;
+ break;
+ }
+
+ offset = c->mem->ptr + c->offset;
+ toSend = c->mem->used - 1 - c->offset;
+#ifdef __WIN32
+ if ((r = send(fd, offset, toSend, 0)) < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed: ", strerror(errno), fd);
+
+ return -1;
+ }
+#else
+ if ((r = write(fd, offset, toSend)) < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed: ", strerror(errno), fd);
+
+ return -1;
+ }
+#endif
+
+ c->offset += r;
+ cq->bytes_out += r;
+
+ if (c->offset == (off_t)c->mem->used - 1) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+#ifdef USE_MMAP
+ char *p = NULL;
+#endif
+ ssize_t r;
+ off_t offset;
+ size_t toSend;
+ stat_cache_entry *sce = NULL;
+ int ifd;
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->file.name);
+ return -1;
+ }
+
+ offset = c->file.start + c->offset;
+ toSend = c->file.length - c->offset;
+
+ if (offset > sce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->file.name);
+
+ return -1;
+ }
+
+ if (-1 == (ifd = open(c->file.name->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
+
+ return -1;
+ }
+
+#if defined USE_MMAP
+ if (MAP_FAILED == (p = mmap(0, sce->st.st_size, PROT_READ, MAP_SHARED, ifd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "mmap failed: ", strerror(errno));
+
+ close(ifd);
+
+ return -1;
+ }
+ close(ifd);
+
+ if ((r = write(fd, p + offset, toSend)) <= 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "write failed: ", strerror(errno));
+ munmap(p, sce->st.st_size);
+ return -1;
+ }
+
+ munmap(p, sce->st.st_size);
+#else
+ buffer_prepare_copy(srv->tmp_buf, toSend);
+
+ lseek(ifd, offset, SEEK_SET);
+ if (-1 == (toSend = read(ifd, srv->tmp_buf->ptr, toSend))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "read: ", strerror(errno));
+ close(ifd);
+
+ return -1;
+ }
+ close(ifd);
+
+ if (-1 == (r = send(fd, srv->tmp_buf->ptr, toSend, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "write: ", strerror(errno));
+
+ return -1;
+ }
+#endif
+ c->offset += r;
+ cq->bytes_out += r;
+
+ if (c->offset == c->file.length) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ default:
+
+ log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known");
+
+ return -1;
+ }
+
+ if (!chunk_finished) {
+ /* not finished yet */
+
+ break;
+ }
+
+ chunks_written++;
+ }
+
+ return chunks_written;
+}
+
+#if 0
+network_write_init(void) {
+ p->write = network_write_write_chunkset;
+}
+#endif
diff --git a/src/network_writev.c b/src/network_writev.c
new file mode 100644
index 0000000..578048e
--- /dev/null
+++ b/src/network_writev.c
@@ -0,0 +1,343 @@
+#include "network_backends.h"
+
+#ifdef USE_WRITEV
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "stat_cache.h"
+
+#ifndef UIO_MAXIOV
+# if defined(__FreeBSD__) || defined(__APPLE__) || defined(__NetBSD__)
+/* FreeBSD 4.7 defines it in sys/uio.h only if _KERNEL is specified */
+# define UIO_MAXIOV 1024
+# elif defined(__sgi)
+/* IRIX 6.5 has sysconf(_SC_IOV_MAX) which might return 512 or bigger */
+# define UIO_MAXIOV 512
+# elif defined(__sun)
+/* Solaris (and SunOS?) defines IOV_MAX instead */
+# ifndef IOV_MAX
+# define UIO_MAXIOV 16
+# else
+# define UIO_MAXIOV IOV_MAX
+# endif
+# elif defined(IOV_MAX)
+# define UIO_MAXIOV IOV_MAX
+# else
+# error UIO_MAXIOV nor IOV_MAX are defined
+# endif
+#endif
+
+#if 0
+#define LOCAL_BUFFERING 1
+#endif
+
+int network_write_chunkqueue_writev(server *srv, connection *con, int fd, chunkqueue *cq) {
+ chunk *c;
+ size_t chunks_written = 0;
+
+ for(c = cq->first; c; c = c->next) {
+ int chunk_finished = 0;
+
+ switch(c->type) {
+ case MEM_CHUNK: {
+ char * offset;
+ size_t toSend;
+ ssize_t r;
+
+ size_t num_chunks, i;
+ struct iovec chunks[UIO_MAXIOV];
+ chunk *tc;
+ size_t num_bytes = 0;
+
+ /* we can't send more then SSIZE_MAX bytes in one chunk */
+
+ /* build writev list
+ *
+ * 1. limit: num_chunks < UIO_MAXIOV
+ * 2. limit: num_bytes < SSIZE_MAX
+ */
+ for(num_chunks = 0, tc = c; tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV; num_chunks++, tc = tc->next);
+
+ for(tc = c, i = 0; i < num_chunks; tc = tc->next, i++) {
+ if (tc->mem->used == 0) {
+ chunks[i].iov_base = tc->mem->ptr;
+ chunks[i].iov_len = 0;
+ } else {
+ offset = tc->mem->ptr + tc->offset;
+ toSend = tc->mem->used - 1 - tc->offset;
+
+ chunks[i].iov_base = offset;
+
+ /* protect the return value of writev() */
+ if (toSend > SSIZE_MAX ||
+ num_bytes + toSend > SSIZE_MAX) {
+ chunks[i].iov_len = SSIZE_MAX - num_bytes;
+
+ num_chunks = i + 1;
+ break;
+ } else {
+ chunks[i].iov_len = toSend;
+ }
+
+ num_bytes += toSend;
+ }
+ }
+
+ if ((r = writev(fd, chunks, num_chunks)) < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ r = 0;
+ break;
+ case EPIPE:
+ case ECONNRESET:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "writev failed:", strerror(errno), fd);
+
+ return -1;
+ }
+ }
+
+ cq->bytes_out += r;
+
+ /* check which chunks have been written */
+
+ for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) {
+ if (r >= (ssize_t)chunks[i].iov_len) {
+ /* written */
+ r -= chunks[i].iov_len;
+ tc->offset += chunks[i].iov_len;
+
+ if (chunk_finished) {
+ /* skip the chunks from further touches */
+ chunks_written++;
+ c = c->next;
+ } else {
+ /* chunks_written + c = c->next is done in the for()*/
+ chunk_finished++;
+ }
+ } else {
+ /* partially written */
+
+ tc->offset += r;
+ chunk_finished = 0;
+
+ break;
+ }
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ ssize_t r;
+ off_t abs_offset;
+ off_t toSend;
+ stat_cache_entry *sce = NULL;
+
+#define KByte * 1024
+#define MByte * 1024 KByte
+#define GByte * 1024 MByte
+ const off_t we_want_to_mmap = 512 KByte;
+ char *start = NULL;
+
+ if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->file.name);
+ return -1;
+ }
+
+ abs_offset = c->file.start + c->offset;
+
+ if (abs_offset > sce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "file was shrinked:", c->file.name);
+
+ return -1;
+ }
+
+ /* mmap the buffer
+ * - first mmap
+ * - new mmap as the we are at the end of the last one */
+ if (c->file.mmap.start == MAP_FAILED ||
+ abs_offset == (off_t)(c->file.mmap.offset + c->file.mmap.length)) {
+
+ /* Optimizations for the future:
+ *
+ * adaptive mem-mapping
+ * the problem:
+ * we mmap() the whole file. If someone has alot large files and 32bit
+ * machine the virtual address area will be unrun and we will have a failing
+ * mmap() call.
+ * solution:
+ * only mmap 16M in one chunk and move the window as soon as we have finished
+ * the first 8M
+ *
+ * read-ahead buffering
+ * the problem:
+ * sending out several large files in parallel trashes the read-ahead of the
+ * kernel leading to long wait-for-seek times.
+ * solutions: (increasing complexity)
+ * 1. use madvise
+ * 2. use a internal read-ahead buffer in the chunk-structure
+ * 3. use non-blocking IO for file-transfers
+ * */
+
+ /* all mmap()ed areas are 512kb expect the last which might be smaller */
+ off_t we_want_to_send;
+ size_t to_mmap;
+
+ /* this is a remap, move the mmap-offset */
+ if (c->file.mmap.start != MAP_FAILED) {
+ munmap(c->file.mmap.start, c->file.mmap.length);
+ c->file.mmap.offset += we_want_to_mmap;
+ } else {
+ /* in case the range-offset is after the first mmap()ed area we skip the area */
+ c->file.mmap.offset = 0;
+
+ while (c->file.mmap.offset + we_want_to_mmap < c->file.start) {
+ c->file.mmap.offset += we_want_to_mmap;
+ }
+ }
+
+ /* length is rel, c->offset too, assume there is no limit at the mmap-boundaries */
+ we_want_to_send = c->file.length - c->offset;
+ to_mmap = (c->file.start + c->file.length) - c->file.mmap.offset;
+
+ /* we have more to send than we can mmap() at once */
+ if (abs_offset + we_want_to_send > c->file.mmap.offset + we_want_to_mmap) {
+ we_want_to_send = (c->file.mmap.offset + we_want_to_mmap) - abs_offset;
+ to_mmap = we_want_to_mmap;
+ }
+
+ if (-1 == c->file.fd) { /* open the file if not already open */
+ if (-1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "open failed for:", c->file.name, strerror(errno));
+
+ return -1;
+ }
+#ifdef FD_CLOEXEC
+ fcntl(c->file.fd, F_SETFD, FD_CLOEXEC);
+#endif
+ }
+
+ if (MAP_FAILED == (c->file.mmap.start = mmap(0, to_mmap, PROT_READ, MAP_SHARED, c->file.fd, c->file.mmap.offset))) {
+ /* close it here, otherwise we'd have to set FD_CLOEXEC */
+
+ log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed:",
+ strerror(errno), c->file.name, c->file.fd);
+
+ return -1;
+ }
+
+ c->file.mmap.length = to_mmap;
+#ifdef LOCAL_BUFFERING
+ buffer_copy_string_len(c->mem, c->file.mmap.start, c->file.mmap.length);
+#else
+#ifdef HAVE_POSIX_MADVISE
+ /* don't advise files < 64Kb */
+ if (c->file.mmap.length > (64 KByte)) {
+ /* darwin 7 is returning EINVAL all the time and I don't know how to
+ * detect this at runtime.i
+ *
+ * ignore the return value for now */
+ posix_madvise(c->file.mmap.start, c->file.mmap.length, POSIX_MADV_WILLNEED);
+ }
+#endif
+#endif
+
+ /* chunk_reset() or chunk_free() will cleanup for us */
+ }
+
+ /* to_send = abs_mmap_end - abs_offset */
+ toSend = (c->file.mmap.offset + c->file.mmap.length) - (abs_offset);
+
+ if (toSend < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "soooo",
+ "toSend is negative:",
+ toSend,
+ c->file.mmap.length,
+ abs_offset,
+ c->file.mmap.offset);
+ assert(toSend < 0);
+ }
+
+#ifdef LOCAL_BUFFERING
+ start = c->mem->ptr;
+#else
+ start = c->file.mmap.start;
+#endif
+
+ if ((r = write(fd, start + (abs_offset - c->file.mmap.offset), toSend)) < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ r = 0;
+ break;
+ case EPIPE:
+ case ECONNRESET:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "write failed:", strerror(errno), fd);
+
+ return -1;
+ }
+ }
+
+ c->offset += r;
+ cq->bytes_out += r;
+
+ if (c->offset == c->file.length) {
+ chunk_finished = 1;
+
+ /* we don't need the mmaping anymore */
+ if (c->file.mmap.start != MAP_FAILED) {
+ munmap(c->file.mmap.start, c->file.mmap.length);
+ c->file.mmap.start = MAP_FAILED;
+ }
+ }
+
+ break;
+ }
+ default:
+
+ log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known");
+
+ return -1;
+ }
+
+ if (!chunk_finished) {
+ /* not finished yet */
+
+ break;
+ }
+
+ chunks_written++;
+ }
+
+ return chunks_written;
+}
+
+#endif
diff --git a/src/plugin.c b/src/plugin.c
new file mode 100644
index 0000000..e74d8b0
--- /dev/null
+++ b/src/plugin.c
@@ -0,0 +1,451 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include <stdio.h>
+
+#include "plugin.h"
+#include "log.h"
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_VALGRIND_VALGRIND_H
+#include <valgrind/valgrind.h>
+#endif
+
+#ifndef __WIN32
+#include <dlfcn.h>
+#endif
+/*
+ *
+ * if you change this enum to add a new callback, be sure
+ * - that PLUGIN_FUNC_SIZEOF is the last entry
+ * - that you add PLUGIN_TO_SLOT twice:
+ * 1. as callback-dispatcher
+ * 2. in plugins_call_init()
+ *
+ */
+
+typedef struct {
+ PLUGIN_DATA;
+} plugin_data;
+
+typedef enum {
+ PLUGIN_FUNC_UNSET,
+ PLUGIN_FUNC_HANDLE_URI_CLEAN,
+ PLUGIN_FUNC_HANDLE_URI_RAW,
+ PLUGIN_FUNC_HANDLE_REQUEST_DONE,
+ PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE,
+ PLUGIN_FUNC_HANDLE_TRIGGER,
+ PLUGIN_FUNC_HANDLE_SIGHUP,
+ PLUGIN_FUNC_HANDLE_SUBREQUEST,
+ PLUGIN_FUNC_HANDLE_SUBREQUEST_START,
+ PLUGIN_FUNC_HANDLE_JOBLIST,
+ PLUGIN_FUNC_HANDLE_DOCROOT,
+ PLUGIN_FUNC_HANDLE_PHYSICAL,
+ PLUGIN_FUNC_CONNECTION_RESET,
+ PLUGIN_FUNC_INIT,
+ PLUGIN_FUNC_CLEANUP,
+ PLUGIN_FUNC_SET_DEFAULTS,
+
+ PLUGIN_FUNC_SIZEOF
+} plugin_t;
+
+static plugin *plugin_init(void) {
+ plugin *p;
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+static void plugin_free(plugin *p) {
+ int use_dlclose = 1;
+ if (p->name) buffer_free(p->name);
+#ifdef HAVE_VALGRIND_VALGRIND_H
+ /*if (RUNNING_ON_VALGRIND) use_dlclose = 0;*/
+#endif
+
+#ifndef LIGHTTPD_STATIC
+ if (use_dlclose && p->lib) {
+#ifdef __WIN32
+ FreeLibrary(p->lib);
+#else
+ dlclose(p->lib);
+#endif
+ }
+#endif
+
+ free(p);
+}
+
+static int plugins_register(server *srv, plugin *p) {
+ plugin **ps;
+ if (0 == srv->plugins.size) {
+ srv->plugins.size = 4;
+ srv->plugins.ptr = malloc(srv->plugins.size * sizeof(*ps));
+ srv->plugins.used = 0;
+ } else if (srv->plugins.used == srv->plugins.size) {
+ srv->plugins.size += 4;
+ srv->plugins.ptr = realloc(srv->plugins.ptr, srv->plugins.size * sizeof(*ps));
+ }
+
+ ps = srv->plugins.ptr;
+ ps[srv->plugins.used++] = p;
+
+ return 0;
+}
+
+/**
+ *
+ *
+ *
+ */
+
+#ifdef LIGHTTPD_STATIC
+int plugins_load(server *srv) {
+ plugin *p;
+#define PLUGIN_INIT(x)\
+ p = plugin_init(); \
+ if (x ## _plugin_init(p)) { \
+ log_error_write(srv, __FILE__, __LINE__, "ss", #x, "plugin init failed" ); \
+ plugin_free(p); \
+ return -1;\
+ }\
+ plugins_register(srv, p);
+
+#include "plugin-static.h"
+
+ return 0;
+}
+#else
+int plugins_load(server *srv) {
+ plugin *p;
+ int (*init)(plugin *pl);
+ const char *error;
+ size_t i;
+
+ for (i = 0; i < srv->srvconf.modules->used; i++) {
+ data_string *d = (data_string *)srv->srvconf.modules->data[i];
+ char *modules = d->value->ptr;
+
+ buffer_copy_string_buffer(srv->tmp_buf, srv->srvconf.modules_dir);
+
+ buffer_append_string(srv->tmp_buf, "/");
+ buffer_append_string(srv->tmp_buf, modules);
+#if defined(__WIN32) || defined(__CYGWIN__)
+ buffer_append_string(srv->tmp_buf, ".dll");
+#else
+ buffer_append_string(srv->tmp_buf, ".so");
+#endif
+
+ p = plugin_init();
+#ifdef __WIN32
+ if (NULL == (p->lib = LoadLibrary(srv->tmp_buf->ptr))) {
+ LPVOID lpMsgBuf;
+ FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &lpMsgBuf,
+ 0, NULL );
+
+ log_error_write(srv, __FILE__, __LINE__, "ssb", "LoadLibrary() failed",
+ lpMsgBuf, srv->tmp_buf);
+
+ plugin_free(p);
+
+ return -1;
+
+ }
+#else
+ if (NULL == (p->lib = dlopen(srv->tmp_buf->ptr, RTLD_LAZY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "dlopen() failed for:",
+ srv->tmp_buf, dlerror());
+
+ plugin_free(p);
+
+ return -1;
+ }
+
+#endif
+ buffer_reset(srv->tmp_buf);
+ buffer_copy_string(srv->tmp_buf, modules);
+ buffer_append_string(srv->tmp_buf, "_plugin_init");
+
+#ifdef __WIN32
+ init = GetProcAddress(p->lib, srv->tmp_buf->ptr);
+
+ if (init == NULL) {
+ LPVOID lpMsgBuf;
+ FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &lpMsgBuf,
+ 0, NULL );
+
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "getprocaddress failed:", srv->tmp_buf, lpMsgBuf);
+
+ plugin_free(p);
+ return -1;
+ }
+
+#else
+#if 1
+ init = (int (*)(plugin *))dlsym(p->lib, srv->tmp_buf->ptr);
+#else
+ *(void **)(&init) = dlsym(p->lib, srv->tmp_buf->ptr);
+#endif
+ if ((error = dlerror()) != NULL) {
+ log_error_write(srv, __FILE__, __LINE__, "s", error);
+
+ plugin_free(p);
+ return -1;
+ }
+
+#endif
+ if ((*init)(p)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", modules, "plugin init failed" );
+
+ plugin_free(p);
+ return -1;
+ }
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "ss", modules, "plugin loaded" );
+#endif
+ plugins_register(srv, p);
+ }
+
+ return 0;
+}
+#endif
+
+#define PLUGIN_TO_SLOT(x, y) \
+ handler_t plugins_call_##y(server *srv, connection *con) {\
+ plugin **slot;\
+ size_t j;\
+ if (!srv->plugin_slots) return HANDLER_GO_ON;\
+ slot = ((plugin ***)(srv->plugin_slots))[x];\
+ if (!slot) return HANDLER_GO_ON;\
+ for (j = 0; j < srv->plugins.used && slot[j]; j++) { \
+ plugin *p = slot[j];\
+ handler_t r;\
+ switch(r = p->y(srv, con, p->data)) {\
+ case HANDLER_GO_ON:\
+ break;\
+ case HANDLER_FINISHED:\
+ case HANDLER_COMEBACK:\
+ case HANDLER_WAIT_FOR_EVENT:\
+ case HANDLER_WAIT_FOR_FD:\
+ case HANDLER_ERROR:\
+ return r;\
+ default:\
+ log_error_write(srv, __FILE__, __LINE__, "sbs", #x, p->name, "unknown state");\
+ return HANDLER_ERROR;\
+ }\
+ }\
+ return HANDLER_GO_ON;\
+ }
+
+/**
+ * plugins that use
+ *
+ * - server *srv
+ * - connection *con
+ * - void *p_d (plugin_data *)
+ */
+
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, handle_connection_close)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL, handle_physical)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset)
+
+#undef PLUGIN_TO_SLOT
+
+#define PLUGIN_TO_SLOT(x, y) \
+ handler_t plugins_call_##y(server *srv) {\
+ plugin **slot;\
+ size_t j;\
+ if (!srv->plugin_slots) return HANDLER_GO_ON;\
+ slot = ((plugin ***)(srv->plugin_slots))[x];\
+ if (!slot) return HANDLER_GO_ON;\
+ for (j = 0; j < srv->plugins.used && slot[j]; j++) { \
+ plugin *p = slot[j];\
+ handler_t r;\
+ switch(r = p->y(srv, p->data)) {\
+ case HANDLER_GO_ON:\
+ break;\
+ case HANDLER_FINISHED:\
+ case HANDLER_COMEBACK:\
+ case HANDLER_WAIT_FOR_EVENT:\
+ case HANDLER_WAIT_FOR_FD:\
+ case HANDLER_ERROR:\
+ return r;\
+ default:\
+ log_error_write(srv, __FILE__, __LINE__, "sbsd", #x, p->name, "unknown state:", r);\
+ return HANDLER_ERROR;\
+ }\
+ }\
+ return HANDLER_GO_ON;\
+ }
+
+/**
+ * plugins that use
+ *
+ * - server *srv
+ * - void *p_d (plugin_data *)
+ */
+
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults)
+
+#undef PLUGIN_TO_SLOT
+
+#if 0
+/**
+ *
+ * special handler
+ *
+ */
+handler_t plugins_call_handle_fdevent(server *srv, const fd_conn *fdc) {
+ size_t i;
+ plugin **ps;
+
+ ps = srv->plugins.ptr;
+
+ for (i = 0; i < srv->plugins.used; i++) {
+ plugin *p = ps[i];
+ if (p->handle_fdevent) {
+ handler_t r;
+ switch(r = p->handle_fdevent(srv, fdc, p->data)) {
+ case HANDLER_GO_ON:
+ break;
+ case HANDLER_FINISHED:
+ case HANDLER_COMEBACK:
+ case HANDLER_WAIT_FOR_EVENT:
+ case HANDLER_ERROR:
+ return r;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "d", r);
+ break;
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+#endif
+/**
+ *
+ * - call init function of all plugins to init the plugin-internals
+ * - added each plugin that supports has callback to the corresponding slot
+ *
+ * - is only called once.
+ */
+
+handler_t plugins_call_init(server *srv) {
+ size_t i;
+ plugin **ps;
+
+ ps = srv->plugins.ptr;
+
+ /* fill slots */
+
+ srv->plugin_slots = calloc(PLUGIN_FUNC_SIZEOF, sizeof(ps));
+
+ for (i = 0; i < srv->plugins.used; i++) {
+ size_t j;
+ /* check which calls are supported */
+
+ plugin *p = ps[i];
+
+#define PLUGIN_TO_SLOT(x, y) \
+ if (p->y) { \
+ plugin **slot = ((plugin ***)(srv->plugin_slots))[x]; \
+ if (!slot) { \
+ slot = calloc(srv->plugins.used, sizeof(*slot));\
+ ((plugin ***)(srv->plugin_slots))[x] = slot; \
+ } \
+ for (j = 0; j < srv->plugins.used; j++) { \
+ if (slot[j]) continue;\
+ slot[j] = p;\
+ break;\
+ }\
+ }
+
+
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, handle_connection_close);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL, handle_physical);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults);
+#undef PLUGIN_TO_SLOT
+
+ if (p->init) {
+ if (NULL == (p->data = p->init())) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "plugin-init failed for module", p->name);
+ return HANDLER_ERROR;
+ }
+
+ /* used for con->mode, DIRECT == 0, plugins above that */
+ ((plugin_data *)(p->data))->id = i + 1;
+
+ if (p->version != LIGHTTPD_VERSION_ID) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "plugin-version doesn't match lighttpd-version for", p->name);
+ return HANDLER_ERROR;
+ }
+ } else {
+ p->data = NULL;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+void plugins_free(server *srv) {
+ size_t i;
+ plugins_call_cleanup(srv);
+
+ for (i = 0; i < srv->plugins.used; i++) {
+ plugin *p = ((plugin **)srv->plugins.ptr)[i];
+
+ plugin_free(p);
+ }
+
+ for (i = 0; srv->plugin_slots && i < PLUGIN_FUNC_SIZEOF; i++) {
+ plugin **slot = ((plugin ***)(srv->plugin_slots))[i];
+
+ if (slot) free(slot);
+ }
+
+ free(srv->plugin_slots);
+ srv->plugin_slots = NULL;
+
+ free(srv->plugins.ptr);
+ srv->plugins.ptr = NULL;
+ srv->plugins.used = 0;
+}
diff --git a/src/plugin.h b/src/plugin.h
new file mode 100644
index 0000000..b43129a
--- /dev/null
+++ b/src/plugin.h
@@ -0,0 +1,92 @@
+#ifndef _PLUGIN_H_
+#define _PLUGIN_H_
+
+#include "base.h"
+#include "buffer.h"
+
+#define SERVER_FUNC(x) \
+ static handler_t x(server *srv, void *p_d)
+
+#define CONNECTION_FUNC(x) \
+ static handler_t x(server *srv, connection *con, void *p_d)
+
+#define INIT_FUNC(x) \
+ static void *x()
+
+#define FREE_FUNC SERVER_FUNC
+#define TRIGGER_FUNC SERVER_FUNC
+#define SETDEFAULTS_FUNC SERVER_FUNC
+#define SIGHUP_FUNC SERVER_FUNC
+
+#define SUBREQUEST_FUNC CONNECTION_FUNC
+#define JOBLIST_FUNC CONNECTION_FUNC
+#define PHYSICALPATH_FUNC CONNECTION_FUNC
+#define REQUESTDONE_FUNC CONNECTION_FUNC
+#define URIHANDLER_FUNC CONNECTION_FUNC
+
+#define PLUGIN_DATA size_t id
+
+typedef struct {
+ size_t version;
+
+ buffer *name; /* name of the plugin */
+
+ void *(* init) ();
+ handler_t (* set_defaults) (server *srv, void *p_d);
+ handler_t (* cleanup) (server *srv, void *p_d);
+ /* is called ... */
+ handler_t (* handle_trigger) (server *srv, void *p_d); /* once a second */
+ handler_t (* handle_sighup) (server *srv, void *p_d); /* at a signup */
+
+ handler_t (* handle_uri_raw) (server *srv, connection *con, void *p_d); /* after uri_raw is set */
+ handler_t (* handle_uri_clean) (server *srv, connection *con, void *p_d); /* after uri is set */
+ handler_t (* handle_docroot) (server *srv, connection *con, void *p_d); /* getting the document-root */
+ handler_t (* handle_physical) (server *srv, connection *con, void *p_d); /* mapping url to physical path */
+ handler_t (* handle_request_done) (server *srv, connection *con, void *p_d); /* at the end of a request */
+ handler_t (* handle_connection_close)(server *srv, connection *con, void *p_d); /* at the end of a connection */
+ handler_t (* handle_joblist) (server *srv, connection *con, void *p_d); /* after all events are handled */
+
+
+
+ handler_t (* handle_subrequest_start)(server *srv, connection *con, void *p_d);
+
+ /* when a handler for the request
+ * has to be found
+ */
+ handler_t (* handle_subrequest) (server *srv, connection *con, void *p_d); /* */
+ handler_t (* connection_reset) (server *srv, connection *con, void *p_d); /* */
+ void *data;
+
+ /* dlopen handle */
+ void *lib;
+} plugin;
+
+int plugins_load(server *srv);
+void plugins_free(server *srv);
+
+handler_t plugins_call_handle_uri_raw(server *srv, connection *con);
+handler_t plugins_call_handle_uri_clean(server *srv, connection *con);
+handler_t plugins_call_handle_subrequest_start(server *srv, connection *con);
+handler_t plugins_call_handle_subrequest(server *srv, connection *con);
+handler_t plugins_call_handle_request_done(server *srv, connection *con);
+handler_t plugins_call_handle_docroot(server *srv, connection *con);
+handler_t plugins_call_handle_physical(server *srv, connection *con);
+handler_t plugins_call_handle_connection_close(server *srv, connection *con);
+handler_t plugins_call_handle_joblist(server *srv, connection *con);
+handler_t plugins_call_connection_reset(server *srv, connection *con);
+
+handler_t plugins_call_handle_trigger(server *srv);
+handler_t plugins_call_handle_sighup(server *srv);
+
+handler_t plugins_call_init(server *srv);
+handler_t plugins_call_set_defaults(server *srv);
+handler_t plugins_call_cleanup(server *srv);
+
+int config_insert_values_global(server *srv, array *ca, const config_values_t *cv);
+int config_insert_values_internal(server *srv, array *ca, const config_values_t *cv);
+int config_setup_connection(server *srv, connection *con);
+int config_patch_connection(server *srv, connection *con, comp_key_t comp);
+int config_check_cond(server *srv, connection *con, data_config *dc);
+int config_append_cond_match_buffer(connection *con, data_config *dc, buffer *buf, int n);
+
+#endif
diff --git a/src/proc_open.c b/src/proc_open.c
new file mode 100644
index 0000000..ecea815
--- /dev/null
+++ b/src/proc_open.c
@@ -0,0 +1,386 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include "proc_open.h"
+
+#ifdef WIN32
+#include <io.h>
+#include <fcntl.h>
+#else
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+
+
+#ifdef WIN32
+/* {{{ win32 stuff */
+# define SHELLENV "ComSpec"
+# define SECURITY_DC , SECURITY_ATTRIBUTES *security
+# define SECURITY_CC , security
+# define pipe(pair) (CreatePipe(&pair[0], &pair[1], security, 2048L) ? 0 : -1)
+static inline HANDLE dup_handle(HANDLE src, BOOL inherit, BOOL closeorig)
+{
+ HANDLE copy, self = GetCurrentProcess();
+
+ if (!DuplicateHandle(self, src, self, &copy, 0, inherit, DUPLICATE_SAME_ACCESS |
+ (closeorig ? DUPLICATE_CLOSE_SOURCE : 0)))
+ return NULL;
+ return copy;
+}
+# define close_descriptor(fd) CloseHandle(fd)
+static void pipe_close_parent(pipe_t *p) {
+ /* don't let the child inherit the parent side of the pipe */
+ p->parent = dup_handle(p->parent, FALSE, TRUE);
+}
+static void pipe_close_child(pipe_t *p) {
+ close_descriptor(p->child);
+ p->fd = _open_osfhandle((long)p->parent,
+ (p->fd == 0 ? O_RDONLY : O_WRONLY)|O_BINARY);
+}
+/* }}} */
+#else /* WIN32 */
+/* {{{ unix way */
+# define SHELLENV "SHELL"
+# define SECURITY_DC
+# define SECURITY_CC
+# define close_descriptor(fd) close(fd)
+static void pipe_close_parent(pipe_t *p) {
+ /* don't close stdin */
+ close_descriptor(p->parent);
+ if (dup2(p->child, p->fd) != p->fd) {
+ perror("pipe_child dup2");
+ } else {
+ close_descriptor(p->child);
+ p->child = p->fd;
+ }
+}
+static void pipe_close_child(pipe_t *p) {
+ close_descriptor(p->child);
+ p->fd = p->parent;
+}
+/* }}} */
+#endif /* WIN32 */
+
+/* {{{ pipe_close */
+static void pipe_close(pipe_t *p) {
+ close_descriptor(p->parent);
+ close_descriptor(p->child);
+#ifdef WIN32
+ close(p->fd);
+#endif
+}
+/* }}} */
+/* {{{ pipe_open */
+static int pipe_open(pipe_t *p, int fd SECURITY_DC) {
+ descriptor_t newpipe[2];
+
+ if (0 != pipe(newpipe)) {
+ fprintf(stderr, "can't open pipe");
+ return -1;
+ }
+ if (0 == fd) {
+ p->parent = newpipe[1]; /* write */
+ p->child = newpipe[0]; /* read */
+ } else {
+ p->parent = newpipe[0]; /* read */
+ p->child = newpipe[1]; /* write */
+ }
+ p->fd = fd;
+
+ return 0;
+}
+/* }}} */
+
+/* {{{ proc_open_pipes */
+static int proc_open_pipes(proc_handler_t *proc SECURITY_DC) {
+ if (pipe_open(&(proc->in), 0 SECURITY_CC) != 0) {
+ return -1;
+ }
+ if (pipe_open(&(proc->out), 1 SECURITY_CC) != 0) {
+ return -1;
+ }
+ if (pipe_open(&(proc->err), 2 SECURITY_CC) != 0) {
+ return -1;
+ }
+ return 0;
+}
+/* }}} */
+/* {{{ proc_close_pipes */
+static void proc_close_pipes(proc_handler_t *proc) {
+ pipe_close(&proc->in);
+ pipe_close(&proc->out);
+ pipe_close(&proc->err);
+}
+/* }}} */
+/* {{{ proc_close_parents */
+static void proc_close_parents(proc_handler_t *proc) {
+ pipe_close_parent(&proc->in);
+ pipe_close_parent(&proc->out);
+ pipe_close_parent(&proc->err);
+}
+/* }}} */
+/* {{{ proc_close_childs */
+static void proc_close_childs(proc_handler_t *proc) {
+ pipe_close_child(&proc->in);
+ pipe_close_child(&proc->out);
+ pipe_close_child(&proc->err);
+}
+/* }}} */
+
+#ifdef WIN32
+/* {{{ proc_close */
+int proc_close(proc_handler_t *proc) {
+ proc_pid_t child = proc->child;
+ DWORD wstatus;
+
+ proc_close_pipes(proc);
+ WaitForSingleObject(child, INFINITE);
+ GetExitCodeProcess(child, &wstatus);
+ CloseHandle(child);
+
+ return wstatus;
+}
+/* }}} */
+/* {{{ proc_open */
+int proc_open(proc_handler_t *proc, const char *command) {
+ PROCESS_INFORMATION pi;
+ STARTUPINFO si;
+ BOOL procok;
+ SECURITY_ATTRIBUTES security;
+ const char *shell;
+ buffer *cmdline;
+
+ if (NULL == (shell = getenv(SHELLENV))) {
+ fprintf(stderr, "env %s is required", SHELLENV);
+ return -1;
+ }
+
+ /* we use this to allow the child to inherit handles */
+ memset(&security, 0, sizeof(security));
+ security.nLength = sizeof(security);
+ security.bInheritHandle = TRUE;
+ security.lpSecurityDescriptor = NULL;
+
+ if (proc_open_pipes(proc, &security) != 0) {
+ return -1;
+ }
+ proc_close_parents(proc);
+
+ memset(&si, 0, sizeof(si));
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESTDHANDLES;
+ si.hStdInput = proc->in.child;
+ si.hStdOutput = proc->out.child;
+ si.hStdError = proc->err.child;
+
+ memset(&pi, 0, sizeof(pi));
+
+ cmdline = buffer_init();
+ buffer_append_string(cmdline, shell);
+ buffer_append_string_len(cmdline, CONST_STR_LEN(" /c "));
+ buffer_append_string(cmdline, command);
+ procok = CreateProcess(NULL, cmdline->ptr, &security, &security, TRUE,
+ NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
+ buffer_free(cmdline);
+
+ if (FALSE == procok) {
+ fprintf(stderr, "failed to CreateProcess");
+ return -1;
+ }
+
+ proc->child = pi.hProcess;
+ CloseHandle(pi.hThread);
+
+ proc_close_childs(proc);
+
+ return 0;
+}
+/* }}} */
+#else /* WIN32 */
+/* {{{ proc_close */
+int proc_close(proc_handler_t *proc) {
+ pid_t child = proc->child;
+ int wstatus;
+ pid_t wait_pid;
+
+ proc_close_pipes(proc);
+
+ do {
+ wait_pid = waitpid(child, &wstatus, 0);
+ } while (wait_pid == -1 && errno == EINTR);
+
+ if (wait_pid == -1) {
+ return -1;
+ } else {
+ if (WIFEXITED(wstatus))
+ wstatus = WEXITSTATUS(wstatus);
+ }
+
+ return wstatus;
+}
+/* }}} */
+/* {{{ proc_open */
+int proc_open(proc_handler_t *proc, const char *command) {
+ pid_t child;
+ const char *shell;
+
+ if (NULL == (shell = getenv(SHELLENV))) {
+ fprintf(stderr, "env %s is required", SHELLENV);
+ return -1;
+ }
+
+ if (proc_open_pipes(proc) != 0) {
+ return -1;
+ }
+
+ /* the unix way */
+
+ child = fork();
+
+ if (child == 0) {
+ /* this is the child process */
+
+ /* close those descriptors that we just opened for the parent stuff,
+ * dup new descriptors into required descriptors and close the original
+ * cruft
+ */
+ proc_close_parents(proc);
+
+ execl(shell, shell, "-c", command, NULL);
+ _exit(127);
+
+ } else if (child < 0) {
+ fprintf(stderr, "failed to forking");
+ proc_close(proc);
+ return -1;
+
+ } else {
+ proc->child = child;
+ proc_close_childs(proc);
+ return 0;
+ }
+}
+/* }}} */
+#endif /* WIN32 */
+
+/* {{{ proc_read_fd_to_buffer */
+static void proc_read_fd_to_buffer(int fd, buffer *b) {
+ ssize_t s;
+
+ for (;;) {
+ buffer_prepare_append(b, 512);
+ if ((s = read(fd, (void *)(b->ptr + b->used), 512 - 1)) <= 0) {
+ break;
+ }
+ b->used += s;
+ }
+ b->ptr[b->used] = '\0';
+}
+/* }}} */
+/* {{{ proc_open_buffer */
+int proc_open_buffer(proc_handler_t *proc, const char *command, buffer *in, buffer *out, buffer *err) {
+
+ UNUSED(err);
+
+ if (proc_open(proc, command) != 0) {
+ return -1;
+ }
+
+ if (in) {
+ if (write(proc->in.fd, (void *)in->ptr, in->used) < 0) {
+ perror("error writing pipe");
+ return -1;
+ }
+ }
+ pipe_close(&proc->in);
+
+ if (out) {
+ proc_read_fd_to_buffer(proc->out.fd, out);
+ }
+ pipe_close(&proc->out);
+
+ if (err) {
+ proc_read_fd_to_buffer(proc->err.fd, err);
+ }
+ pipe_close(&proc->err);
+
+ return 0;
+}
+/* }}} */
+
+/* {{{ test */
+#ifdef DEBUG_PROC_OPEN
+int main() {
+ proc_handler_t proc;
+ buffer *in = buffer_init(), *out = buffer_init(), *err = buffer_init();
+ int wstatus;
+
+#define FREE() do { \
+ buffer_free(in); \
+ buffer_free(out); \
+ buffer_free(err); \
+} while (0)
+
+#define RESET() do { \
+ buffer_reset(in); \
+ buffer_reset(out); \
+ buffer_reset(err); \
+ wstatus = proc_close(&proc); \
+ if (0&&wstatus != 0) { \
+ fprintf(stdout, "exitstatus %d\n", wstatus); \
+ return __LINE__ - 200; \
+ } \
+} while (0)
+
+#define ERROR_OUT() do { \
+ fprintf(stdout, "failed opening proc\n"); \
+ wstatus = proc_close(&proc); \
+ fprintf(stdout, "exitstatus %d\n", wstatus); \
+ FREE(); \
+ return __LINE__ - 300; \
+} while (0)
+
+#ifdef WIN32
+#define CMD_CAT "pause"
+#else
+#define CMD_CAT "cat"
+#endif
+
+ do {
+ fprintf(stdout, "test: echo 123 without read\n");
+ if (proc_open(&proc, "echo 321") != 0) {
+ ERROR_OUT();
+ }
+ close_descriptor(proc.in.parent);
+ close_descriptor(proc.out.parent);
+ close_descriptor(proc.err.parent);
+ RESET();
+
+ fprintf(stdout, "test: echo 321 with read\n"); fflush(stdout);
+ if (proc_open_buffer(&proc, "echo 321", NULL, out, err) != 0) {
+ ERROR_OUT();
+ }
+ fprintf(stdout, "result: ->%s<-\n\n", out->ptr); fflush(stdout);
+ RESET();
+
+ fprintf(stdout, "test: echo 123 | " CMD_CAT "\n"); fflush(stdout);
+ buffer_copy_string_len(in, CONST_STR_LEN("123\n"));
+ if (proc_open_buffer(&proc, CMD_CAT, in, out, err) != 0) {
+ ERROR_OUT();
+ }
+ fprintf(stdout, "result: ->%s<-\n\n", out->ptr); fflush(stdout);
+ RESET();
+ } while (0);
+
+#undef RESET
+#undef ERROR_OUT
+
+ fprintf(stdout, "ok\n");
+
+ FREE();
+ return 0;
+}
+#endif /* DEBUG_PROC_OPEN */
+/* }}} */
+
diff --git a/src/proc_open.h b/src/proc_open.h
new file mode 100644
index 0000000..e07421a
--- /dev/null
+++ b/src/proc_open.h
@@ -0,0 +1,25 @@
+
+#include "buffer.h"
+
+#ifdef WIN32
+#include <windows.h>
+typedef HANDLE descriptor_t;
+typedef HANDLE proc_pid_t;
+#else
+typedef int descriptor_t;
+typedef pid_t proc_pid_t;
+#endif
+
+typedef struct {
+ descriptor_t parent, child;
+ int fd;
+} pipe_t;
+
+typedef struct {
+ pipe_t in, out, err;
+ proc_pid_t child;
+} proc_handler_t;
+
+int proc_close(proc_handler_t *ht);
+int proc_open(proc_handler_t *ht, const char *command);
+int proc_open_buffer(proc_handler_t *ht, const char *command, buffer *in, buffer *out, buffer *err);
diff --git a/src/request.c b/src/request.c
new file mode 100644
index 0000000..020333e
--- /dev/null
+++ b/src/request.c
@@ -0,0 +1,1083 @@
+#include <sys/stat.h>
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "request.h"
+#include "keyvalue.h"
+#include "log.h"
+
+static int request_check_hostname(server *srv, connection *con, buffer *host) {
+ enum { DOMAINLABEL, TOPLABEL } stage = TOPLABEL;
+ size_t i;
+ int label_len = 0;
+ size_t host_len;
+ char *colon;
+ int is_ip = -1; /* -1 don't know yet, 0 no, 1 yes */
+ int level = 0;
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ /*
+ * hostport = host [ ":" port ]
+ * host = hostname | IPv4address | IPv6address
+ * hostname = *( domainlabel "." ) toplabel [ "." ]
+ * domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+ * toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+ * IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
+ * IPv6address = "[" ... "]"
+ * port = *digit
+ */
+
+ /* no Host: */
+ if (!host || host->used == 0) return 0;
+
+ host_len = host->used - 1;
+
+ /* IPv6 adress */
+ if (host->ptr[0] == '[') {
+ char *c = host->ptr + 1;
+ int colon_cnt = 0;
+
+ /* check portnumber */
+ for (; *c && *c != ']'; c++) {
+ if (*c == ':') {
+ if (++colon_cnt > 7) {
+ return -1;
+ }
+ } else if (!light_isxdigit(*c)) {
+ return -1;
+ }
+ }
+
+ /* missing ] */
+ if (!*c) {
+ return -1;
+ }
+
+ /* check port */
+ if (*(c+1) == ':') {
+ for (c += 2; *c; c++) {
+ if (!light_isdigit(*c)) {
+ return -1;
+ }
+ }
+ }
+ return 0;
+ }
+
+ if (NULL != (colon = memchr(host->ptr, ':', host_len))) {
+ char *c = colon + 1;
+
+ /* check portnumber */
+ for (; *c; c++) {
+ if (!light_isdigit(*c)) return -1;
+ }
+
+ /* remove the port from the host-len */
+ host_len = colon - host->ptr;
+ }
+
+ /* Host is empty */
+ if (host_len == 0) return -1;
+
+ /* scan from the right and skip the \0 */
+ for (i = host_len - 1; i + 1 > 0; i--) {
+ const char c = host->ptr[i];
+
+ switch (stage) {
+ case TOPLABEL:
+ if (c == '.') {
+ /* only switch stage, if this is not the last character */
+ if (i != host_len - 1) {
+ if (label_len == 0) {
+ return -1;
+ }
+
+ /* check the first character at right of the dot */
+ if (is_ip == 0) {
+ if (!light_isalpha(host->ptr[i+1])) {
+ return -1;
+ }
+ } else if (!light_isdigit(host->ptr[i+1])) {
+ is_ip = 0;
+ } else if ('-' == host->ptr[i+1]) {
+ return -1;
+ } else {
+ /* just digits */
+ is_ip = 1;
+ }
+
+ stage = DOMAINLABEL;
+
+ label_len = 0;
+ level++;
+ } else if (i == 0) {
+ /* just a dot and nothing else is evil */
+ return -1;
+ }
+ } else if (i == 0) {
+ /* the first character of the hostname */
+ if (!light_isalpha(c)) {
+ return -1;
+ }
+ label_len++;
+ } else {
+ if (c != '-' && !light_isalnum(c)) {
+ return -1;
+ }
+ if (is_ip == -1) {
+ if (!light_isdigit(c)) is_ip = 0;
+ }
+ label_len++;
+ }
+
+ break;
+ case DOMAINLABEL:
+ if (is_ip == 1) {
+ if (c == '.') {
+ if (label_len == 0) {
+ return -1;
+ }
+
+ label_len = 0;
+ level++;
+ } else if (!light_isdigit(c)) {
+ return -1;
+ } else {
+ label_len++;
+ }
+ } else {
+ if (c == '.') {
+ if (label_len == 0) {
+ return -1;
+ }
+
+ /* c is either - or alphanum here */
+ if ('-' == host->ptr[i+1]) {
+ return -1;
+ }
+
+ label_len = 0;
+ level++;
+ } else if (i == 0) {
+ if (!light_isalnum(c)) {
+ return -1;
+ }
+ label_len++;
+ } else {
+ if (c != '-' && !light_isalnum(c)) {
+ return -1;
+ }
+ label_len++;
+ }
+ }
+
+ break;
+ }
+ }
+
+ /* a IP has to consist of 4 parts */
+ if (is_ip == 1 && level != 3) {
+ return -1;
+ }
+
+ if (label_len == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+#if 0
+#define DUMP_HEADER
+#endif
+
+int http_request_split_value(array *vals, buffer *b) {
+ char *s;
+ size_t i;
+ int state = 0;
+ /*
+ * parse
+ *
+ * val1, val2, val3, val4
+ *
+ * into a array (more or less a explode() incl. striping of whitespaces
+ */
+
+ if (b->used == 0) return 0;
+
+ s = b->ptr;
+
+ for (i =0; i < b->used - 1; ) {
+ char *start = NULL, *end = NULL;
+ data_string *ds;
+
+ switch (state) {
+ case 0: /* ws */
+
+ /* skip ws */
+ for (; (*s == ' ' || *s == '\t') && i < b->used - 1; i++, s++);
+
+
+ state = 1;
+ break;
+ case 1: /* value */
+ start = s;
+
+ for (; *s != ',' && i < b->used - 1; i++, s++);
+ end = s - 1;
+
+ for (; (*end == ' ' || *end == '\t') && end > start; end--);
+
+ if (NULL == (ds = (data_string *)array_get_unused_element(vals, TYPE_STRING))) {
+ ds = data_string_init();
+ }
+
+ buffer_copy_string_len(ds->value, start, end-start+1);
+ array_insert_unique(vals, (data_unset *)ds);
+
+ if (*s == ',') {
+ state = 0;
+ i++;
+ s++;
+ } else {
+ /* end of string */
+
+ state = 2;
+ }
+ break;
+ default:
+ i++;
+ break;
+ }
+ }
+ return 0;
+}
+
+int request_uri_is_valid_char(unsigned char c) {
+ if (c <= 32) return 0;
+ if (c == 127) return 0;
+ if (c == 255) return 0;
+
+ return 1;
+}
+
+int http_request_parse(server *srv, connection *con) {
+ char *uri = NULL, *proto = NULL, *method = NULL, con_length_set;
+ int is_key = 1, key_len = 0, is_ws_after_key = 0, in_folding;
+ char *value = NULL, *key = NULL;
+
+ enum { HTTP_CONNECTION_UNSET, HTTP_CONNECTION_KEEPALIVE, HTTP_CONNECTION_CLOSE } keep_alive_set = HTTP_CONNECTION_UNSET;
+
+ int line = 0;
+
+ int request_line_stage = 0;
+ size_t i, first;
+
+ int done = 0;
+
+ data_string *ds = NULL;
+
+ /*
+ * Request: "^(GET|POST|HEAD) ([^ ]+(\\?[^ ]+|)) (HTTP/1\\.[01])$"
+ * Option : "^([-a-zA-Z]+): (.+)$"
+ * End : "^$"
+ */
+
+ if (con->conf.log_request_header) {
+ log_error_write(srv, __FILE__, __LINE__, "sdsdSb",
+ "fd:", con->fd,
+ "request-len:", con->request.request->used,
+ "\n", con->request.request);
+ }
+
+ if (con->request_count > 1 &&
+ con->request.request->ptr[0] == '\r' &&
+ con->request.request->ptr[1] == '\n') {
+ /* we are in keep-alive and might get \r\n after a previous POST request.*/
+
+ buffer_copy_string_len(con->parse_request, con->request.request->ptr + 2, con->request.request->used - 1 - 2);
+ } else {
+ /* fill the local request buffer */
+ buffer_copy_string_buffer(con->parse_request, con->request.request);
+ }
+
+ keep_alive_set = 0;
+ con_length_set = 0;
+
+ /* parse the first line of the request
+ *
+ * should be:
+ *
+ * <method> <uri> <protocol>\r\n
+ * */
+ for (i = 0, first = 0; i < con->parse_request->used && line == 0; i++) {
+ char *cur = con->parse_request->ptr + i;
+
+ switch(*cur) {
+ case '\r':
+ if (con->parse_request->ptr[i+1] == '\n') {
+ http_method_t r;
+ char *nuri = NULL;
+ size_t j;
+
+ /* \r\n -> \0\0 */
+ con->parse_request->ptr[i] = '\0';
+ con->parse_request->ptr[i+1] = '\0';
+
+ buffer_copy_string_len(con->request.request_line, con->parse_request->ptr, i);
+
+ if (request_line_stage != 2) {
+ con->http_status = 400;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "incomplete request line -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+
+ proto = con->parse_request->ptr + first;
+
+ *(uri - 1) = '\0';
+ *(proto - 1) = '\0';
+
+ /* we got the first one :) */
+ if (-1 == (r = get_http_method_key(method))) {
+ con->http_status = 501;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown http-method -> 501");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+
+ return 0;
+ }
+
+ con->request.http_method = r;
+
+ if (0 == strncmp(proto, "HTTP/1.", sizeof("HTTP/1.") - 1)) {
+ if (proto[7] == '1') {
+ con->request.http_version = con->conf.allow_http11 ? HTTP_VERSION_1_1 : HTTP_VERSION_1_0;
+ } else if (proto[7] == '0') {
+ con->request.http_version = HTTP_VERSION_1_0;
+ } else {
+ con->http_status = 505;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown HTTP version -> 505");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+ } else {
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown protocol -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+
+ if (0 == strncmp(uri, "http://", 7) &&
+ NULL != (nuri = strchr(uri + 7, '/'))) {
+ /* ignore the host-part */
+
+ buffer_copy_string_len(con->request.uri, nuri, proto - nuri - 1);
+ } else {
+ /* everything looks good so far */
+ buffer_copy_string_len(con->request.uri, uri, proto - uri - 1);
+ }
+
+ /* check uri for invalid characters */
+ for (j = 0; j < con->request.uri->used - 1; j++) {
+ if (!request_uri_is_valid_char(con->request.uri->ptr[j])) {
+ unsigned char buf[2];
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ buf[0] = con->request.uri->ptr[j];
+ buf[1] = '\0';
+
+ if (con->request.uri->ptr[j] > 32 &&
+ con->request.uri->ptr[j] != 127) {
+ /* the character is printable -> print it */
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "invalid character in URI -> 400",
+ buf);
+ } else {
+ /* a control-character, print ascii-code */
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "invalid character in URI -> 400",
+ con->request.uri->ptr[j]);
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+
+ return 0;
+ }
+ }
+
+ buffer_copy_string_buffer(con->request.orig_uri, con->request.uri);
+
+ con->http_status = 0;
+
+ i++;
+ line++;
+ first = i+1;
+ }
+ break;
+ case ' ':
+ switch(request_line_stage) {
+ case 0:
+ /* GET|POST|... */
+ method = con->parse_request->ptr + first;
+ first = i + 1;
+ break;
+ case 1:
+ /* /foobar/... */
+ uri = con->parse_request->ptr + first;
+ first = i + 1;
+ break;
+ default:
+ /* ERROR, one space to much */
+ con->http_status = 400;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "overlong request line -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+
+ request_line_stage++;
+ break;
+ }
+ }
+
+ in_folding = 0;
+
+ if (con->request.uri->used == 1) {
+ con->http_status = 400;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "no uri specified -> 400");
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+
+
+ for (; i < con->parse_request->used && !done; i++) {
+ char *cur = con->parse_request->ptr + i;
+
+ if (is_key) {
+ size_t j;
+ int got_colon = 0;
+
+ /**
+ * 1*<any CHAR except CTLs or separators>
+ * CTLs == 0-31 + 127
+ *
+ */
+ switch(*cur) {
+ case ':':
+ is_key = 0;
+
+ value = cur + 1;
+
+ if (is_ws_after_key == 0) {
+ key_len = i - first;
+ }
+ is_ws_after_key = 0;
+
+ break;
+ case '(':
+ case ')':
+ case '<':
+ case '>':
+ case '@':
+ case ',':
+ case ';':
+ case '\\':
+ case '\"':
+ case '/':
+ case '[':
+ case ']':
+ case '?':
+ case '=':
+ case '{':
+ case '}':
+ con->http_status = 400;
+ con->keep_alive = 0;
+ con->response.keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbsds",
+ "invalid character in key", con->request.request, cur, *cur, "-> 400");
+ return 0;
+ case ' ':
+ case '\t':
+ if (i == first) {
+ is_key = 0;
+ in_folding = 1;
+ value = cur;
+
+ break;
+ }
+
+
+ key_len = i - first;
+
+ /* skip every thing up to the : */
+ for (j = 1; !got_colon; j++) {
+ switch(con->parse_request->ptr[j + i]) {
+ case ' ':
+ case '\t':
+ /* skip WS */
+ continue;
+ case ':':
+ /* ok, done */
+
+ i += j - 1;
+ got_colon = 1;
+
+ break;
+ default:
+ /* error */
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "WS character in key -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+
+ con->http_status = 400;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ return 0;
+ }
+ }
+
+ break;
+ case '\r':
+ if (con->parse_request->ptr[i+1] == '\n' && i == first) {
+ /* End of Header */
+ con->parse_request->ptr[i] = '\0';
+ con->parse_request->ptr[i+1] = '\0';
+
+ i++;
+
+ done = 1;
+
+ break;
+ } else {
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "CR without LF -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+
+ con->http_status = 400;
+ con->keep_alive = 0;
+ con->response.keep_alive = 0;
+ return 0;
+ }
+ /* fall thru */
+ case 0: /* illegal characters (faster than a if () :) */
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 10:
+ case 11:
+ case 12:
+ case 14:
+ case 15:
+ case 16:
+ case 17:
+ case 18:
+ case 19:
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ case 24:
+ case 25:
+ case 26:
+ case 27:
+ case 28:
+ case 29:
+ case 30:
+ case 31:
+ case 127:
+ con->http_status = 400;
+ con->keep_alive = 0;
+ con->response.keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsds",
+ "CTL character in key", con->request.request, cur, *cur, "-> 400");
+
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+
+ return 0;
+ default:
+ /* ok */
+ break;
+ }
+ } else {
+ switch(*cur) {
+ case '\r':
+ if (con->parse_request->ptr[i+1] == '\n') {
+ /* End of Headerline */
+ con->parse_request->ptr[i] = '\0';
+ con->parse_request->ptr[i+1] = '\0';
+
+ if (in_folding) {
+ if (!ds) {
+ /* 400 */
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "WS at the start of first line -> 400");
+
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+
+
+ con->http_status = 400;
+ con->keep_alive = 0;
+ con->response.keep_alive = 0;
+ return 0;
+ }
+ buffer_append_string(ds->value, value);
+ } else {
+ int s_len;
+ key = con->parse_request->ptr + first;
+
+ s_len = cur - value;
+
+ if (s_len > 0) {
+ int cmp = 0;
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
+ ds = data_string_init();
+ }
+ buffer_copy_string_len(ds->key, key, key_len);
+ buffer_copy_string_len(ds->value, value, s_len);
+
+ /* retreive values
+ *
+ *
+ * the list of options is sorted to simplify the search
+ */
+
+ if (0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Connection")))) {
+ array *vals;
+ size_t vi;
+
+ /* split on , */
+
+ vals = srv->split_vals;
+
+ array_reset(vals);
+
+ http_request_split_value(vals, ds->value);
+
+ for (vi = 0; vi < vals->used; vi++) {
+ data_string *dsv = (data_string *)vals->data[vi];
+
+ if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("keep-alive"))) {
+ keep_alive_set = HTTP_CONNECTION_KEEPALIVE;
+
+ break;
+ } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("close"))) {
+ keep_alive_set = HTTP_CONNECTION_CLOSE;
+
+ break;
+ }
+ }
+
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Length")))) {
+ char *err;
+ unsigned long int r;
+ size_t j;
+
+ if (con_length_set) {
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "duplicate Content-Length-header -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+
+ if (ds->value->used == 0) SEGFAULT();
+
+ for (j = 0; j < ds->value->used - 1; j++) {
+ char c = ds->value->ptr[j];
+ if (!isdigit((unsigned char)c)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "content-length broken:", ds->value, "-> 400");
+
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ array_insert_unique(con->request.headers, (data_unset *)ds);
+ return 0;
+ }
+ }
+
+ r = strtoul(ds->value->ptr, &err, 10);
+
+ if (*err == '\0') {
+ con_length_set = 1;
+ con->request.content_length = r;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "content-length broken:", ds->value, "-> 400");
+
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ array_insert_unique(con->request.headers, (data_unset *)ds);
+ return 0;
+ }
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Type")))) {
+ /* if dup, only the first one will survive */
+ if (!con->request.http_content_type) {
+ con->request.http_content_type = ds->value->ptr;
+ } else {
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "duplicate Content-Type-header -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Expect")))) {
+ /* HTTP 2616 8.2.3
+ * Expect: 100-continue
+ *
+ * -> (10.1.1) 100 (read content, process request, send final status-code)
+ * -> (10.4.18) 417 (close)
+ *
+ * (not handled at all yet, we always send 417 here)
+ *
+ * What has to be added ?
+ * 1. handling of chunked request body
+ * 2. out-of-order sending from the HTTP/1.1 100 Continue
+ * header
+ *
+ */
+
+ con->http_status = 417;
+ con->keep_alive = 0;
+
+ array_insert_unique(con->request.headers, (data_unset *)ds);
+ return 0;
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Host")))) {
+ if (!con->request.http_host) {
+ con->request.http_host = ds->value;
+ } else {
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "duplicate Host-header -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-Modified-Since")))) {
+ /* Proxies sometimes send dup headers
+ * if they are the same we ignore the second
+ * if not, we raise an error */
+ if (!con->request.http_if_modified_since) {
+ con->request.http_if_modified_since = ds->value->ptr;
+ } else if (0 == strcasecmp(con->request.http_if_modified_since,
+ ds->value->ptr)) {
+ /* ignore it if they are the same */
+ } else {
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "duplicate If-Modified-Since header -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-None-Match")))) {
+ /* if dup, only the first one will survive */
+ if (!con->request.http_if_none_match) {
+ con->request.http_if_none_match = ds->value->ptr;
+ } else {
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "duplicate If-None-Match-header -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Range")))) {
+ if (!con->request.http_range) {
+ /* bytes=.*-.* */
+
+ if (0 == strncasecmp(ds->value->ptr, "bytes=", 6) &&
+ NULL != strchr(ds->value->ptr+6, '-')) {
+
+ /* if dup, only the first one will survive */
+ con->request.http_range = ds->value->ptr + 6;
+ }
+ } else {
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "duplicate Range-header -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+ }
+
+ array_insert_unique(con->request.headers, (data_unset *)ds);
+ } else {
+ /* empty header-fields are not allowed by HTTP-RFC, we just ignore them */
+ }
+ }
+
+ i++;
+ first = i+1;
+ is_key = 1;
+ value = 0;
+ key_len = 0;
+ in_folding = 0;
+ } else {
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "CR without LF", con->request.request, "-> 400");
+ }
+
+ con->http_status = 400;
+ con->keep_alive = 0;
+ con->response.keep_alive = 0;
+ return 0;
+ }
+ break;
+ case ' ':
+ case '\t':
+ /* strip leading WS */
+ if (value == cur) value = cur+1;
+ default:
+ break;
+ }
+ }
+ }
+
+ con->header_len = i;
+
+ /* do some post-processing */
+
+ if (con->request.http_version == HTTP_VERSION_1_1) {
+ if (keep_alive_set != HTTP_CONNECTION_CLOSE) {
+ /* no Connection-Header sent */
+
+ /* HTTP/1.1 -> keep-alive default TRUE */
+ con->keep_alive = 1;
+ } else {
+ con->keep_alive = 0;
+ }
+
+ /* RFC 2616, 14.23 */
+ if (con->request.http_host == NULL ||
+ buffer_is_empty(con->request.http_host)) {
+ con->http_status = 400;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "HTTP/1.1 but Host missing -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+ } else {
+ if (keep_alive_set == HTTP_CONNECTION_KEEPALIVE) {
+ /* no Connection-Header sent */
+
+ /* HTTP/1.0 -> keep-alive default FALSE */
+ con->keep_alive = 1;
+ } else {
+ con->keep_alive = 0;
+ }
+ }
+
+ /* check hostname field if it is set */
+ if (NULL != con->request.http_host &&
+ 0 != request_check_hostname(srv, con, con->request.http_host)) {
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "Invalid Hostname -> 400");
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+
+ con->http_status = 400;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ return 0;
+ }
+
+ switch(con->request.http_method) {
+ case HTTP_METHOD_GET:
+ case HTTP_METHOD_HEAD:
+ case HTTP_METHOD_OPTIONS:
+ /* content-length is forbidden for those */
+ if (con_length_set && con->request.content_length != 0) {
+ /* content-length is missing */
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "GET/HEAD/OPTIONS with content-length -> 400");
+
+ con->keep_alive = 0;
+ con->http_status = 400;
+ return 0;
+ }
+ break;
+ case HTTP_METHOD_POST:
+ /* content-length is required for them */
+ if (!con_length_set) {
+ /* content-length is missing */
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "POST-request, but content-length missing -> 411");
+
+ con->keep_alive = 0;
+ con->http_status = 411;
+ return 0;
+
+ }
+ break;
+ default:
+ /* the may have a content-length */
+ break;
+ }
+
+
+ /* check if we have read post data */
+ if (con_length_set) {
+ /* don't handle more the SSIZE_MAX bytes in content-length */
+ if (con->request.content_length > SSIZE_MAX) {
+ con->http_status = 413;
+ con->keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "request-size too long:", con->request.content_length, "-> 413");
+ return 0;
+ }
+
+ /* divide by 1024 as srvconf.max_request_size is in kBytes */
+ if (srv->srvconf.max_request_size != 0 &&
+ (con->request.content_length >> 10) > srv->srvconf.max_request_size) {
+ /* the request body itself is larger then
+ * our our max_request_size
+ */
+
+ con->http_status = 413;
+ con->keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "request-size too long:", con->request.content_length, "-> 413");
+ return 0;
+ }
+
+
+ /* we have content */
+ if (con->request.content_length != 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int http_request_header_finished(server *srv, connection *con) {
+ UNUSED(srv);
+
+ if (con->request.request->used < 5) return 0;
+
+ if (0 == memcmp(con->request.request->ptr + con->request.request->used - 5, "\r\n\r\n", 4)) return 1;
+ if (NULL != strstr(con->request.request->ptr, "\r\n\r\n")) return 1;
+
+ return 0;
+}
diff --git a/src/request.h b/src/request.h
new file mode 100644
index 0000000..cf2b07d
--- /dev/null
+++ b/src/request.h
@@ -0,0 +1,9 @@
+#ifndef _REQUEST_H_
+#define _REQUEST_H_
+
+#include "server.h"
+
+int http_request_parse(server *srv, connection *con);
+int http_request_header_finished(server *srv, connection *con);
+
+#endif
diff --git a/src/response.c b/src/response.c
new file mode 100644
index 0000000..d955cec
--- /dev/null
+++ b/src/response.c
@@ -0,0 +1,591 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <limits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <stdio.h>
+
+#include "response.h"
+#include "keyvalue.h"
+#include "log.h"
+#include "stat_cache.h"
+#include "chunk.h"
+
+#include "connections.h"
+
+#include "plugin.h"
+
+#include "sys-socket.h"
+
+int http_response_write_header(server *srv, connection *con) {
+ buffer *b;
+ size_t i;
+ int have_date = 0;
+ int have_server = 0;
+
+ b = chunkqueue_get_prepend_buffer(con->write_queue);
+
+ if (con->request.http_version == HTTP_VERSION_1_1) {
+ BUFFER_COPY_STRING_CONST(b, "HTTP/1.1 ");
+ } else {
+ BUFFER_COPY_STRING_CONST(b, "HTTP/1.0 ");
+ }
+ buffer_append_long(b, con->http_status);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ buffer_append_string(b, get_http_status_name(con->http_status));
+
+ if (con->request.http_version != HTTP_VERSION_1_1 || con->keep_alive == 0) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nConnection: ");
+ buffer_append_string(b, con->keep_alive ? "keep-alive" : "close");
+ }
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nTransfer-Encoding: chunked");
+ }
+
+
+ /* add all headers */
+ for (i = 0; i < con->response.headers->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->response.headers->data[i];
+
+ if (ds->value->used && ds->key->used &&
+ 0 != strncmp(ds->key->ptr, "X-LIGHTTPD-", sizeof("X-LIGHTTPD-") - 1)) {
+ if (buffer_is_equal_string(ds->key, CONST_STR_LEN("Date"))) have_date = 1;
+ if (buffer_is_equal_string(ds->key, CONST_STR_LEN("Server"))) have_server = 1;
+
+ BUFFER_APPEND_STRING_CONST(b, "\r\n");
+ buffer_append_string_buffer(b, ds->key);
+ BUFFER_APPEND_STRING_CONST(b, ": ");
+ buffer_append_string_buffer(b, ds->value);
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "bb",
+ ds->key, ds->value);
+#endif
+ }
+ }
+
+ if (!have_date) {
+ /* HTTP/1.1 requires a Date: header */
+ BUFFER_APPEND_STRING_CONST(b, "\r\nDate: ");
+
+ /* cache the generated timestamp */
+ if (srv->cur_ts != srv->last_generated_date_ts) {
+ buffer_prepare_copy(srv->ts_date_str, 255);
+
+ strftime(srv->ts_date_str->ptr, srv->ts_date_str->size - 1,
+ "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(srv->cur_ts)));
+
+ srv->ts_date_str->used = strlen(srv->ts_date_str->ptr) + 1;
+
+ srv->last_generated_date_ts = srv->cur_ts;
+ }
+
+ buffer_append_string_buffer(b, srv->ts_date_str);
+ }
+
+ if (!have_server) {
+ if (buffer_is_empty(con->conf.server_tag)) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nServer: " PACKAGE_NAME "/" PACKAGE_VERSION);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nServer: ");
+ buffer_append_string_buffer(b, con->conf.server_tag);
+ }
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "\r\n\r\n");
+
+
+ con->bytes_header = b->used - 1;
+
+ if (con->conf.log_response_header) {
+ log_error_write(srv, __FILE__, __LINE__, "sSb", "Response-Header:", "\n", b);
+ }
+
+ return 0;
+}
+
+
+
+handler_t http_response_prepare(server *srv, connection *con) {
+ handler_t r;
+
+ /* looks like someone has already done a decision */
+ if (con->mode == DIRECT &&
+ (con->http_status != 0 && con->http_status != 200)) {
+ /* remove a packets in the queue */
+ if (con->file_finished == 0) {
+ chunkqueue_reset(con->write_queue);
+ }
+
+ return HANDLER_FINISHED;
+ }
+
+ /* no decision yet, build conf->filename */
+ if (con->mode == DIRECT && con->physical.path->used == 0) {
+ char *qstr;
+
+ /* we only come here when we have the parse the full request again
+ *
+ * a HANDLER_COMEBACK from mod_rewrite and mod_fastcgi might be a
+ * problem here as mod_setenv might get called multiple times
+ *
+ * fastcgi-auth might lead to a COMEBACK too
+ * fastcgi again dead server too
+ *
+ * mod_compress might add headers twice too
+ *
+ * */
+
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "run condition");
+ }
+ config_patch_connection(srv, con, COMP_SERVER_SOCKET); /* SERVERsocket */
+
+ /**
+ * prepare strings
+ *
+ * - uri.path_raw
+ * - uri.path (secure)
+ * - uri.query
+ *
+ */
+
+ /**
+ * Name according to RFC 2396
+ *
+ * - scheme
+ * - authority
+ * - path
+ * - query
+ *
+ * (scheme)://(authority)(path)?(query)
+ *
+ *
+ */
+
+ buffer_copy_string(con->uri.scheme, con->conf.is_ssl ? "https" : "http");
+ buffer_copy_string_buffer(con->uri.authority, con->request.http_host);
+ buffer_to_lower(con->uri.authority);
+
+ config_patch_connection(srv, con, COMP_HTTP_HOST); /* Host: */
+ config_patch_connection(srv, con, COMP_HTTP_REMOTEIP); /* Client-IP */
+ config_patch_connection(srv, con, COMP_HTTP_REFERER); /* Referer: */
+ config_patch_connection(srv, con, COMP_HTTP_USERAGENT); /* User-Agent: */
+ config_patch_connection(srv, con, COMP_HTTP_COOKIE); /* Cookie: */
+
+ /** extract query string from request.uri */
+ if (NULL != (qstr = strchr(con->request.uri->ptr, '?'))) {
+ buffer_copy_string (con->uri.query, qstr + 1);
+ buffer_copy_string_len(con->uri.path_raw, con->request.uri->ptr, qstr - con->request.uri->ptr);
+ } else {
+ buffer_reset (con->uri.query);
+ buffer_copy_string_buffer(con->uri.path_raw, con->request.uri);
+ }
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- splitting Request-URI");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Request-URI : ", con->request.uri);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI-scheme : ", con->uri.scheme);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI-authority: ", con->uri.authority);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI-path : ", con->uri.path_raw);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI-query : ", con->uri.query);
+ }
+
+ /* disable keep-alive if requested */
+
+ if (con->request_count > con->conf.max_keep_alive_requests) {
+ con->keep_alive = 0;
+ }
+
+
+ /**
+ *
+ * call plugins
+ *
+ * - based on the raw URL
+ *
+ */
+
+ switch(r = plugins_call_handle_uri_raw(srv, con)) {
+ case HANDLER_GO_ON:
+ break;
+ case HANDLER_FINISHED:
+ case HANDLER_COMEBACK:
+ case HANDLER_WAIT_FOR_EVENT:
+ case HANDLER_ERROR:
+ return r;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "handle_uri_raw: unknown return value", r);
+ break;
+ }
+
+ /* build filename
+ *
+ * - decode url-encodings (e.g. %20 -> ' ')
+ * - remove path-modifiers (e.g. /../)
+ */
+
+
+
+ if (con->request.http_method == HTTP_METHOD_OPTIONS &&
+ con->uri.path_raw->ptr[0] == '*' && con->uri.path_raw->ptr[1] == '\0') {
+ /* OPTIONS * ... */
+ buffer_copy_string_buffer(con->uri.path, con->uri.path_raw);
+ } else {
+ buffer_copy_string_buffer(srv->tmp_buf, con->uri.path_raw);
+ buffer_urldecode_path(srv->tmp_buf);
+ buffer_path_simplify(con->uri.path, srv->tmp_buf);
+ }
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- sanatising URI");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI-path : ", con->uri.path);
+ }
+
+ /**
+ *
+ * call plugins
+ *
+ * - based on the clean URL
+ *
+ */
+
+ config_patch_connection(srv, con, COMP_HTTP_URL); /* HTTPurl */
+
+ switch(r = plugins_call_handle_uri_clean(srv, con)) {
+ case HANDLER_GO_ON:
+ break;
+ case HANDLER_FINISHED:
+ case HANDLER_COMEBACK:
+ case HANDLER_WAIT_FOR_EVENT:
+ case HANDLER_ERROR:
+ return r;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "");
+ break;
+ }
+
+ if (con->request.http_method == HTTP_METHOD_OPTIONS &&
+ con->uri.path->ptr[0] == '*' && con->uri.path_raw->ptr[1] == '\0') {
+ /* option requests are handled directly without checking of the path */
+
+ response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("OPTIONS, GET, HEAD, POST"));
+
+ con->http_status = 200;
+ con->file_finished = 1;
+
+ return HANDLER_FINISHED;
+ }
+
+ /***
+ *
+ * border
+ *
+ * logical filename (URI) becomes a physical filename here
+ *
+ *
+ *
+ */
+
+
+
+
+ /* 1. stat()
+ * ... ISREG() -> ok, go on
+ * ... ISDIR() -> index-file -> redirect
+ *
+ * 2. pathinfo()
+ * ... ISREG()
+ *
+ * 3. -> 404
+ *
+ */
+
+ /*
+ * SEARCH DOCUMENT ROOT
+ */
+
+ /* set a default */
+
+ buffer_copy_string_buffer(con->physical.doc_root, con->conf.document_root);
+ buffer_copy_string_buffer(con->physical.rel_path, con->uri.path);
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- before doc_root");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Doc-Root :", con->physical.doc_root);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Rel-Path :", con->physical.rel_path);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+ /* the docroot plugin should set the doc_root and might also set the physical.path
+ * for us (all vhost-plugins are supposed to set the doc_root)
+ * */
+ switch(r = plugins_call_handle_docroot(srv, con)) {
+ case HANDLER_GO_ON:
+ break;
+ case HANDLER_FINISHED:
+ case HANDLER_COMEBACK:
+ case HANDLER_WAIT_FOR_EVENT:
+ case HANDLER_ERROR:
+ return r;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "");
+ break;
+ }
+
+ /* MacOS X and Windows can't distiguish between upper and lower-case
+ *
+ * convert to lower-case
+ */
+ if (con->conf.force_lowercase_filenames) {
+ buffer_to_lower(con->physical.rel_path);
+ }
+
+ /* the docroot plugins might set the servername, if they don't we take http-host */
+ if (buffer_is_empty(con->server_name)) {
+ buffer_copy_string_buffer(con->server_name, con->uri.authority);
+ }
+
+ /**
+ * create physical filename
+ * -> physical.path = docroot + rel_path
+ *
+ */
+
+ buffer_copy_string_buffer(con->physical.path, con->physical.doc_root);
+ BUFFER_APPEND_SLASH(con->physical.path);
+ buffer_copy_string_buffer(con->physical.basedir, con->physical.path);
+ if (con->physical.rel_path->used &&
+ con->physical.rel_path->ptr[0] == '/') {
+ buffer_append_string_len(con->physical.path, con->physical.rel_path->ptr + 1, con->physical.rel_path->used - 2);
+ } else {
+ buffer_append_string_buffer(con->physical.path, con->physical.rel_path);
+ }
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- after doc_root");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Doc-Root :", con->physical.doc_root);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Rel-Path :", con->physical.rel_path);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+
+ switch(r = plugins_call_handle_physical(srv, con)) {
+ case HANDLER_GO_ON:
+ break;
+ case HANDLER_FINISHED:
+ case HANDLER_COMEBACK:
+ case HANDLER_WAIT_FOR_EVENT:
+ case HANDLER_ERROR:
+ return r;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "");
+ break;
+ }
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- logical -> physical");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Doc-Root :", con->physical.doc_root);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Rel-Path :", con->physical.rel_path);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+ }
+
+ /*
+ * Noone catched away the file from normal path of execution yet (like mod_access)
+ *
+ * Go on and check of the file exists at all
+ */
+
+ if (con->mode == DIRECT) {
+ char *slash = NULL;
+ char *pathinfo = NULL;
+ int found = 0;
+ stat_cache_entry *sce = NULL;
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- handling physical path");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+
+ if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
+ /* file exists */
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- file found");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+
+ if (S_ISDIR(sce->st.st_mode)) {
+ if (con->physical.path->ptr[con->physical.path->used - 2] != '/') {
+ /* redirect to .../ */
+
+ http_response_redirect_to_directory(srv, con);
+
+ return HANDLER_FINISHED;
+ }
+ } else if (!S_ISREG(sce->st.st_mode)) {
+ /* any special handling of non-reg files ?*/
+
+
+ }
+ } else {
+ switch (errno) {
+ case EACCES:
+ con->http_status = 403;
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- access denied");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+
+ buffer_reset(con->physical.path);
+ return HANDLER_FINISHED;
+ case ENOENT:
+ con->http_status = 404;
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- file not found");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+
+ buffer_reset(con->physical.path);
+ return HANDLER_FINISHED;
+ case ENOTDIR:
+ /* PATH_INFO ! :) */
+ break;
+ default:
+ /* we have no idea what happend. let's tell the user so. */
+ con->http_status = 500;
+ buffer_reset(con->physical.path);
+
+ log_error_write(srv, __FILE__, __LINE__, "ssbsb",
+ "file not found ... or so: ", strerror(errno),
+ con->uri.path,
+ "->", con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+
+ /* not found, perhaps PATHINFO */
+
+ buffer_copy_string_buffer(srv->tmp_buf, con->physical.path);
+
+ do {
+ struct stat st;
+
+ if (slash) {
+ buffer_copy_string_len(con->physical.path, srv->tmp_buf->ptr, slash - srv->tmp_buf->ptr);
+ } else {
+ buffer_copy_string_buffer(con->physical.path, srv->tmp_buf);
+ }
+
+ if (0 == stat(con->physical.path->ptr, &(st)) &&
+ S_ISREG(st.st_mode)) {
+ found = 1;
+ break;
+ }
+
+ if (pathinfo != NULL) {
+ *pathinfo = '\0';
+ }
+ slash = strrchr(srv->tmp_buf->ptr, '/');
+
+ if (pathinfo != NULL) {
+ /* restore '/' */
+ *pathinfo = '/';
+ }
+
+ if (slash) pathinfo = slash;
+ } while ((found == 0) && (slash != NULL) && (slash - srv->tmp_buf->ptr > con->physical.basedir->used - 2));
+
+ if (found == 0) {
+ /* no it really doesn't exists */
+ con->http_status = 404;
+
+ if (con->conf.log_file_not_found) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsb",
+ "file not found:", con->uri.path,
+ "->", con->physical.path);
+ }
+
+ buffer_reset(con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+
+ /* we have a PATHINFO */
+ if (pathinfo) {
+ buffer_copy_string(con->request.pathinfo, pathinfo);
+
+ /*
+ * shorten uri.path
+ */
+
+ con->uri.path->used -= strlen(pathinfo);
+ con->uri.path->ptr[con->uri.path->used - 1] = '\0';
+ }
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- after pathinfo check");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI :", con->uri.path);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Pathinfo :", con->request.pathinfo);
+ }
+ }
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- handling subrequest");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+
+ /* call the handlers */
+ switch(r = plugins_call_handle_subrequest_start(srv, con)) {
+ case HANDLER_GO_ON:
+ /* request was not handled */
+ break;
+ case HANDLER_FINISHED:
+ default:
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- subrequest finished");
+ }
+
+ /* something strange happend */
+ return r;
+ }
+
+ /* if we are still here, no one wanted the file, status 403 is ok I think */
+
+ if (con->mode == DIRECT) {
+ con->http_status = 403;
+
+ return HANDLER_FINISHED;
+ }
+
+ }
+
+ switch(r = plugins_call_handle_subrequest(srv, con)) {
+ case HANDLER_GO_ON:
+ /* request was not handled, looks like we are done */
+ return HANDLER_FINISHED;
+ case HANDLER_FINISHED:
+ /* request is finished */
+ default:
+ /* something strange happend */
+ return r;
+ }
+
+ /* can't happen */
+ return HANDLER_COMEBACK;
+}
+
+
+
diff --git a/src/response.h b/src/response.h
new file mode 100644
index 0000000..c9ff234
--- /dev/null
+++ b/src/response.h
@@ -0,0 +1,19 @@
+#ifndef _RESPONSE_H_
+#define _RESPONSE_H_
+
+#include <time.h>
+
+#include "server.h"
+
+int http_response_parse(server *srv, connection *con);
+int http_response_write_header(server *srv, connection *con);
+
+int response_header_insert(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen);
+int response_header_overwrite(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen);
+
+handler_t http_response_prepare(server *srv, connection *con);
+int http_response_redirect_to_directory(server *srv, connection *con);
+int http_response_handle_cachable(server *srv, connection *con, buffer * mtime);
+
+buffer * strftime_cache_get(server *srv, time_t last_mod);
+#endif
diff --git a/src/server.c b/src/server.c
new file mode 100644
index 0000000..f33100c
--- /dev/null
+++ b/src/server.c
@@ -0,0 +1,1336 @@
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <signal.h>
+#include <assert.h>
+#include <locale.h>
+
+#include <stdio.h>
+
+#include "server.h"
+#include "buffer.h"
+#include "network.h"
+#include "log.h"
+#include "keyvalue.h"
+#include "response.h"
+#include "request.h"
+#include "chunk.h"
+#include "http_chunk.h"
+#include "fdevent.h"
+#include "connections.h"
+#include "stat_cache.h"
+#include "plugin.h"
+#include "joblist.h"
+#include "network_backends.h"
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#ifdef HAVE_VALGRIND_VALGRIND_H
+#include <valgrind/valgrind.h>
+#endif
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#ifdef HAVE_PWD_H
+#include <grp.h>
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+
+#ifndef __sgi
+/* IRIX doesn't like the alarm based time() optimization */
+/* #define USE_ALARM */
+#endif
+
+static volatile sig_atomic_t srv_shutdown = 0;
+static volatile sig_atomic_t graceful_shutdown = 0;
+static volatile sig_atomic_t handle_sig_alarm = 1;
+static volatile sig_atomic_t handle_sig_hup = 0;
+
+#if defined(HAVE_SIGACTION) && defined(SA_SIGINFO)
+static void sigaction_handler(int sig, siginfo_t *si, void *context) {
+ UNUSED(si);
+ UNUSED(context);
+
+ switch (sig) {
+ case SIGTERM: srv_shutdown = 1; break;
+ case SIGINT:
+ if (graceful_shutdown) srv_shutdown = 1;
+ else graceful_shutdown = 1;
+
+ break;
+ case SIGALRM: handle_sig_alarm = 1; break;
+ case SIGHUP: handle_sig_hup = 1; break;
+ case SIGCHLD: break;
+ }
+}
+#elif defined(HAVE_SIGNAL) || defined(HAVE_SIGACTION)
+static void signal_handler(int sig) {
+ switch (sig) {
+ case SIGTERM: srv_shutdown = 1; break;
+ case SIGINT:
+ if (graceful_shutdown) srv_shutdown = 1;
+ else graceful_shutdown = 1;
+
+ break;
+ case SIGALRM: handle_sig_alarm = 1; break;
+ case SIGHUP: handle_sig_hup = 1; break;
+ case SIGCHLD: break;
+ }
+}
+#endif
+
+#ifdef HAVE_FORK
+static void daemonize(void) {
+#ifdef SIGTTOU
+ signal(SIGTTOU, SIG_IGN);
+#endif
+#ifdef SIGTTIN
+ signal(SIGTTIN, SIG_IGN);
+#endif
+#ifdef SIGTSTP
+ signal(SIGTSTP, SIG_IGN);
+#endif
+ if (0 != fork()) exit(0);
+
+ if (-1 == setsid()) exit(0);
+
+ signal(SIGHUP, SIG_IGN);
+
+ if (0 != fork()) exit(0);
+
+ if (0 != chdir("/")) exit(0);
+
+ umask(0);
+}
+#endif
+
+static server *server_init(void) {
+ int i;
+
+ server *srv = calloc(1, sizeof(*srv));
+ assert(srv);
+#define CLEAN(x) \
+ srv->x = buffer_init();
+
+ CLEAN(response_header);
+ CLEAN(parse_full_path);
+ CLEAN(ts_debug_str);
+ CLEAN(ts_date_str);
+ CLEAN(errorlog_buf);
+ CLEAN(response_range);
+ CLEAN(tmp_buf);
+ srv->empty_string = buffer_init_string("");
+ CLEAN(cond_check_buf);
+
+ CLEAN(srvconf.errorlog_file);
+ CLEAN(srvconf.groupname);
+ CLEAN(srvconf.username);
+ CLEAN(srvconf.changeroot);
+ CLEAN(srvconf.bindhost);
+ CLEAN(srvconf.event_handler);
+ CLEAN(srvconf.pid_file);
+
+ CLEAN(tmp_chunk_len);
+#undef CLEAN
+
+#define CLEAN(x) \
+ srv->x = array_init();
+
+ CLEAN(config_context);
+ CLEAN(config_touched);
+ CLEAN(status);
+#undef CLEAN
+
+ for (i = 0; i < FILE_CACHE_MAX; i++) {
+ srv->mtime_cache[i].str = buffer_init();
+ }
+
+ srv->cur_ts = time(NULL);
+ srv->startup_ts = srv->cur_ts;
+
+ srv->conns = calloc(1, sizeof(*srv->conns));
+ assert(srv->conns);
+
+ srv->joblist = calloc(1, sizeof(*srv->joblist));
+ assert(srv->joblist);
+
+ srv->fdwaitqueue = calloc(1, sizeof(*srv->fdwaitqueue));
+ assert(srv->fdwaitqueue);
+
+ srv->srvconf.modules = array_init();
+ srv->srvconf.modules_dir = buffer_init_string(LIBRARY_DIR);
+ srv->srvconf.network_backend = buffer_init();
+ srv->srvconf.upload_tempdirs = array_init();
+
+ /* use syslog */
+ srv->errorlog_fd = -1;
+ srv->errorlog_mode = ERRORLOG_STDERR;
+
+ srv->split_vals = array_init();
+
+ return srv;
+}
+
+static void server_free(server *srv) {
+ size_t i;
+
+ for (i = 0; i < FILE_CACHE_MAX; i++) {
+ buffer_free(srv->mtime_cache[i].str);
+ }
+
+#define CLEAN(x) \
+ buffer_free(srv->x);
+
+ CLEAN(response_header);
+ CLEAN(parse_full_path);
+ CLEAN(ts_debug_str);
+ CLEAN(ts_date_str);
+ CLEAN(errorlog_buf);
+ CLEAN(response_range);
+ CLEAN(tmp_buf);
+ CLEAN(empty_string);
+ CLEAN(cond_check_buf);
+
+ CLEAN(srvconf.errorlog_file);
+ CLEAN(srvconf.groupname);
+ CLEAN(srvconf.username);
+ CLEAN(srvconf.changeroot);
+ CLEAN(srvconf.bindhost);
+ CLEAN(srvconf.event_handler);
+ CLEAN(srvconf.pid_file);
+ CLEAN(srvconf.modules_dir);
+
+ CLEAN(tmp_chunk_len);
+#undef CLEAN
+
+#if 0
+ fdevent_unregister(srv->ev, srv->fd);
+#endif
+ fdevent_free(srv->ev);
+
+ free(srv->conns);
+
+ if (srv->config_storage) {
+ for (i = 0; i < srv->config_context->used; i++) {
+ specific_config *s = srv->config_storage[i];
+
+ if (!s) continue;
+
+ buffer_free(s->document_root);
+ buffer_free(s->server_name);
+ buffer_free(s->server_tag);
+ buffer_free(s->ssl_pemfile);
+ buffer_free(s->ssl_ca_file);
+ buffer_free(s->error_handler);
+ buffer_free(s->errorfile_prefix);
+ array_free(s->mimetypes);
+
+ free(s);
+ }
+ free(srv->config_storage);
+ srv->config_storage = NULL;
+ }
+
+#define CLEAN(x) \
+ array_free(srv->x);
+
+ CLEAN(config_context);
+ CLEAN(config_touched);
+ CLEAN(status);
+#undef CLEAN
+
+ joblist_free(srv, srv->joblist);
+ fdwaitqueue_free(srv, srv->fdwaitqueue);
+
+ if (srv->stat_cache) {
+ stat_cache_free(srv->stat_cache);
+ }
+
+ array_free(srv->srvconf.modules);
+ array_free(srv->split_vals);
+
+ free(srv);
+}
+
+static void show_version (void) {
+#ifdef USE_OPENSSL
+# define TEXT_SSL " (ssl)"
+#else
+# define TEXT_SSL
+#endif
+ char *b = PACKAGE_NAME "-" PACKAGE_VERSION TEXT_SSL \
+" - a light and fast webserver\n" \
+"Build-Date: " __DATE__ " " __TIME__ "\n";
+;
+#undef TEXT_SSL
+ write(STDOUT_FILENO, b, strlen(b));
+}
+
+static void show_features (void) {
+ show_version();
+ printf("\nEvent Handlers:\n\n%s",
+
+#ifdef USE_SELECT
+ "\t+ select (generic)\n"
+#else
+ "\t- select (generic)\n"
+#endif
+#ifdef USE_POLL
+ "\t+ poll (Unix)\n"
+#else
+ "\t- poll (Unix)\n"
+#endif
+#ifdef USE_LINUX_SIGIO
+ "\t+ rt-signals (Linux 2.4+)\n"
+#else
+ "\t- rt-signals (Linux 2.4+)\n"
+#endif
+#ifdef USE_LINUX_EPOLL
+ "\t+ epoll (Linux 2.6)\n"
+#else
+ "\t- epoll (Linux 2.6)\n"
+#endif
+#ifdef USE_SOLARIS_DEVPOLL
+ "\t+ /dev/poll (Solaris)\n"
+#else
+ "\t- /dev/poll (Solaris)\n"
+#endif
+#ifdef USE_FREEBSD_KQUEUE
+ "\t+ kqueue (FreeBSD)\n"
+#else
+ "\t- kqueue (FreeBSD)\n"
+#endif
+ "\nNetwork handler:\n\n"
+#if defined(USE_LINUX_SENDFILE) || defined(USE_FREEBSD_SENDFILE) || defined(USE_SOLARIS_SENDFILEV) || defined(USE_AIX_SENDFILE)
+ "\t+ sendfile\n"
+#else
+ #ifdef USE_WRITEV
+ "\t+ writev\n"
+ #else
+ "\t+ write\n"
+ #endif
+ #ifdef USE_MMAP
+ "\t+ mmap support\n"
+ #else
+ "\t- mmap support\n"
+ #endif
+#endif
+ "\nFeatures:\n\n"
+#ifdef HAVE_IPV6
+ "\t+ IPv6 support\n"
+#else
+ "\t- IPv6 support\n"
+#endif
+#if defined HAVE_ZLIB_H && defined HAVE_LIBZ
+ "\t+ zlib support\n"
+#else
+ "\t- zlib support\n"
+#endif
+#if defined HAVE_BZLIB_H && defined HAVE_LIBBZ2
+ "\t+ bzip2 support\n"
+#else
+ "\t- bzip2 support\n"
+#endif
+#ifdef HAVE_LIBCRYPT
+ "\t+ crypt support\n"
+#else
+ "\t- crypt support\n"
+#endif
+#ifdef USE_PAM
+ "\t+ PAM support\n"
+#else
+ "\t- PAM support\n"
+#endif
+#ifdef USE_OPENSSL
+ "\t+ SSL Support\n"
+#else
+ "\t- SSL Support\n"
+#endif
+#ifdef HAVE_LIBPCRE
+ "\t+ PCRE support\n"
+#else
+ "\t- PCRE support\n"
+#endif
+#ifdef HAVE_MYSQL
+ "\t+ mySQL support\n"
+#else
+ "\t- mySQL support\n"
+#endif
+#if defined(HAVE_LDAP_H) && defined(HAVE_LBER_H) && defined(HAVE_LIBLDAP) && defined(HAVE_LIBLBER)
+ "\t+ LDAP support\n"
+#else
+ "\t- LDAP support\n"
+#endif
+#ifdef HAVE_MEMCACHE_H
+ "\t+ memcached support\n"
+#else
+ "\t- memcached support\n"
+#endif
+#ifdef HAVE_FAM_H
+ "\t+ FAM support\n"
+#else
+ "\t- FAM support\n"
+#endif
+#ifdef HAVE_LUA_H
+ "\t+ LUA support\n"
+#else
+ "\t- LUA support\n"
+#endif
+#ifdef HAVE_LIBXML_H
+ "\t+ xml support\n"
+#else
+ "\t- xml support\n"
+#endif
+#ifdef HAVE_SQLITE3_H
+ "\t+ SQLite support\n"
+#else
+ "\t- SQLite support\n"
+#endif
+#ifdef HAVE_GDBM_H
+ "\t+ GDBM support\n"
+#else
+ "\t- GDBM support\n"
+#endif
+ "\n"
+ );
+}
+
+static void show_help (void) {
+#ifdef USE_OPENSSL
+# define TEXT_SSL " (ssl)"
+#else
+# define TEXT_SSL
+#endif
+ char *b = PACKAGE_NAME "-" PACKAGE_VERSION TEXT_SSL " ("__DATE__ " " __TIME__ ")" \
+" - a light and fast webserver\n" \
+"usage:\n" \
+" -f <name> filename of the config-file\n" \
+" -m <name> module directory (default: "LIBRARY_DIR")\n" \
+" -p print the parsed config-file in internal form, and exit\n" \
+" -t test the config-file, and exit\n" \
+" -D don't go to background (default: go to background)\n" \
+" -v show version\n" \
+" -V show compile-time features\n" \
+" -h show this help\n" \
+"\n"
+;
+#undef TEXT_SSL
+#undef TEXT_IPV6
+ write(STDOUT_FILENO, b, strlen(b));
+}
+
+int main (int argc, char **argv) {
+ server *srv = NULL;
+ int print_config = 0;
+ int test_config = 0;
+ int i_am_root;
+ int o;
+ int num_childs = 0;
+ int pid_fd = -1, fd;
+ size_t i;
+#ifdef HAVE_SIGACTION
+ struct sigaction act;
+#endif
+#ifdef HAVE_GETRLIMIT
+ struct rlimit rlim;
+#endif
+
+#ifdef USE_ALARM
+ struct itimerval interval;
+
+ interval.it_interval.tv_sec = 1;
+ interval.it_interval.tv_usec = 0;
+ interval.it_value.tv_sec = 1;
+ interval.it_value.tv_usec = 0;
+#endif
+
+
+ /* for nice %b handling in strfime() */
+ setlocale(LC_TIME, "C");
+
+ if (NULL == (srv = server_init())) {
+ fprintf(stderr, "did this really happen?\n");
+ return -1;
+ }
+
+ /* init structs done */
+
+ srv->srvconf.port = 0;
+#ifdef HAVE_GETUID
+ i_am_root = (getuid() == 0);
+#else
+ i_am_root = 0;
+#endif
+ srv->srvconf.dont_daemonize = 0;
+
+ while(-1 != (o = getopt(argc, argv, "f:m:hvVDpt"))) {
+ switch(o) {
+ case 'f':
+ if (config_read(srv, optarg)) {
+ server_free(srv);
+ return -1;
+ }
+ break;
+ case 'm':
+ buffer_copy_string(srv->srvconf.modules_dir, optarg);
+ break;
+ case 'p': print_config = 1; break;
+ case 't': test_config = 1; break;
+ case 'D': srv->srvconf.dont_daemonize = 1; break;
+ case 'v': show_version(); return 0;
+ case 'V': show_features(); return 0;
+ case 'h': show_help(); return 0;
+ default:
+ show_help();
+ server_free(srv);
+ return -1;
+ }
+ }
+
+ if (!srv->config_storage) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "No configuration available. Try using -f option.");
+
+ server_free(srv);
+ return -1;
+ }
+
+ if (print_config) {
+ data_unset *dc = srv->config_context->data[0];
+ if (dc) {
+ dc->print(dc, 0);
+ fprintf(stderr, "\n");
+ } else {
+ /* shouldn't happend */
+ fprintf(stderr, "global config not found\n");
+ }
+ }
+
+ if (test_config) {
+ printf("Syntax OK\n");
+ }
+
+ if (test_config || print_config) {
+ server_free(srv);
+ return 0;
+ }
+
+ /* close stdin and stdout, as they are not needed */
+ /* move stdin to /dev/null */
+ if (-1 != (fd = open("/dev/null", O_RDONLY))) {
+ close(STDIN_FILENO);
+ dup2(fd, STDIN_FILENO);
+ close(fd);
+ }
+
+ /* move stdout to /dev/null */
+ if (-1 != (fd = open("/dev/null", O_WRONLY))) {
+ close(STDOUT_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ close(fd);
+ }
+
+ if (0 != config_set_defaults(srv)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "setting default values failed");
+ server_free(srv);
+ return -1;
+ }
+
+ /* UID handling */
+#ifdef HAVE_GETUID
+ if (!i_am_root && (geteuid() == 0 || getegid() == 0)) {
+ /* we are setuid-root */
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "Are you nuts ? Don't apply a SUID bit to this binary");
+
+ server_free(srv);
+ return -1;
+ }
+#endif
+
+ /* check document-root */
+ if (srv->config_storage[0]->document_root->used <= 1) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "document-root is not set\n");
+
+ server_free(srv);
+
+ return -1;
+ }
+
+ if (plugins_load(srv)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "loading plugins finally failed");
+
+ plugins_free(srv);
+ server_free(srv);
+
+ return -1;
+ }
+
+ /* open pid file BEFORE chroot */
+ if (srv->srvconf.pid_file->used) {
+ if (-1 == (pid_fd = open(srv->srvconf.pid_file->ptr, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) {
+ struct stat st;
+ if (errno != EEXIST) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "opening pid-file failed:", srv->srvconf.pid_file, strerror(errno));
+ return -1;
+ }
+
+ if (0 != stat(srv->srvconf.pid_file->ptr, &st)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "stating existing pid-file failed:", srv->srvconf.pid_file, strerror(errno));
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "pid-file exists and isn't regular file:", srv->srvconf.pid_file);
+ return -1;
+ }
+
+ if (-1 == (pid_fd = open(srv->srvconf.pid_file->ptr, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "opening pid-file failed:", srv->srvconf.pid_file, strerror(errno));
+ return -1;
+ }
+ }
+ }
+
+ if (srv->event_handler == FDEVENT_HANDLER_SELECT) {
+ /* select limits itself
+ *
+ * as it is a hard limit and will lead to a segfault we add some safety
+ * */
+ srv->max_fds = FD_SETSIZE - 200;
+ } else {
+ srv->max_fds = 4096;
+ }
+
+ if (i_am_root) {
+ struct group *grp = NULL;
+ struct passwd *pwd = NULL;
+ int use_rlimit = 1;
+
+#ifdef HAVE_VALGRIND_VALGRIND_H
+ if (RUNNING_ON_VALGRIND) use_rlimit = 0;
+#endif
+
+#ifdef HAVE_GETRLIMIT
+ if (0 != getrlimit(RLIMIT_NOFILE, &rlim)) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "ss", "couldn't get 'max filedescriptors'",
+ strerror(errno));
+ return -1;
+ }
+
+ if (use_rlimit && srv->srvconf.max_fds) {
+ /* set rlimits */
+
+ rlim.rlim_cur = srv->srvconf.max_fds;
+ rlim.rlim_max = srv->srvconf.max_fds;
+
+ if (0 != setrlimit(RLIMIT_NOFILE, &rlim)) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "ss", "couldn't set 'max filedescriptors'",
+ strerror(errno));
+ return -1;
+ }
+ }
+
+ /* #372: solaris need some fds extra for devpoll */
+ if (rlim.rlim_cur > 10) rlim.rlim_cur -= 10;
+
+ if (srv->event_handler == FDEVENT_HANDLER_SELECT) {
+ srv->max_fds = rlim.rlim_cur < FD_SETSIZE - 200 ? rlim.rlim_cur : FD_SETSIZE - 200;
+ } else {
+ srv->max_fds = rlim.rlim_cur;
+ }
+
+ /* set core file rlimit, if enable_cores is set */
+ if (use_rlimit && srv->srvconf.enable_cores && getrlimit(RLIMIT_CORE, &rlim) == 0) {
+ rlim.rlim_cur = rlim.rlim_max;
+ setrlimit(RLIMIT_CORE, &rlim);
+ }
+#endif
+ if (srv->event_handler == FDEVENT_HANDLER_SELECT) {
+ /* don't raise the limit above FD_SET_SIZE */
+ if (srv->max_fds > FD_SETSIZE - 200) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "can't raise max filedescriptors above", FD_SETSIZE - 200,
+ "if event-handler is 'select'. Use 'poll' or something else or reduce server.max-fds.");
+ return -1;
+ }
+ }
+
+
+#ifdef HAVE_PWD_H
+ /* set user and group */
+ if (srv->srvconf.username->used) {
+ if (NULL == (pwd = getpwnam(srv->srvconf.username->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "can't find username", srv->srvconf.username);
+ return -1;
+ }
+
+ if (pwd->pw_uid == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "I will not set uid to 0\n");
+ return -1;
+ }
+ }
+
+ if (srv->srvconf.groupname->used) {
+ if (NULL == (grp = getgrnam(srv->srvconf.groupname->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "can't find groupname", srv->srvconf.groupname);
+ return -1;
+ }
+ if (grp->gr_gid == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "I will not set gid to 0\n");
+ return -1;
+ }
+ }
+#endif
+ /* we need root-perms for port < 1024 */
+ if (0 != network_init(srv)) {
+ plugins_free(srv);
+ server_free(srv);
+
+ return -1;
+ }
+#ifdef HAVE_CHROOT
+ if (srv->srvconf.changeroot->used) {
+ tzset();
+
+ if (-1 == chroot(srv->srvconf.changeroot->ptr)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "chroot failed: ", strerror(errno));
+ return -1;
+ }
+ if (-1 == chdir("/")) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "chdir failed: ", strerror(errno));
+ return -1;
+ }
+ }
+#endif
+#ifdef HAVE_PWD_H
+ /* drop root privs */
+ if (srv->srvconf.groupname->used) {
+ setgid(grp->gr_gid);
+ setgroups(0, NULL);
+ }
+ if (srv->srvconf.username->used && srv->srvconf.groupname->used)
+ initgroups(srv->srvconf.username->ptr, grp->gr_gid);
+ if (srv->srvconf.username->used) setuid(pwd->pw_uid);
+#endif
+#ifdef HAVE_PRCTL
+ if (srv->srvconf.enable_cores) {
+ prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
+ }
+#endif
+ } else {
+
+#ifdef HAVE_GETRLIMIT
+ if (0 != getrlimit(RLIMIT_NOFILE, &rlim)) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "ss", "couldn't get 'max filedescriptors'",
+ strerror(errno));
+ return -1;
+ }
+
+ if (srv->event_handler == FDEVENT_HANDLER_SELECT) {
+ srv->max_fds = rlim.rlim_cur < FD_SETSIZE - 200 ? rlim.rlim_cur : FD_SETSIZE - 200;
+ } else {
+ srv->max_fds = rlim.rlim_cur;
+ }
+
+ /* set core file rlimit, if enable_cores is set */
+ if (srv->srvconf.enable_cores && getrlimit(RLIMIT_CORE, &rlim) == 0) {
+ rlim.rlim_cur = rlim.rlim_max;
+ setrlimit(RLIMIT_CORE, &rlim);
+ }
+
+#endif
+ if (srv->event_handler == FDEVENT_HANDLER_SELECT) {
+ /* don't raise the limit above FD_SET_SIZE */
+ if (srv->max_fds > FD_SETSIZE - 200) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "can't raise max filedescriptors above", FD_SETSIZE - 200,
+ "if event-handler is 'select'. Use 'poll' or something else or reduce server.max-fds.");
+ return -1;
+ }
+ }
+
+ if (0 != network_init(srv)) {
+ plugins_free(srv);
+ server_free(srv);
+
+ return -1;
+ }
+ }
+
+ /* set max-conns */
+ if (srv->srvconf.max_conns > srv->max_fds) {
+ /* we can't have more connections than max-fds */
+ srv->max_conns = srv->max_fds;
+ } else if (srv->srvconf.max_conns) {
+ /* otherwise respect the wishes of the user */
+ srv->max_conns = srv->srvconf.max_conns;
+ } else {
+ /* or use the default */
+ srv->max_conns = srv->max_fds;
+ }
+
+ if (HANDLER_GO_ON != plugins_call_init(srv)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "Initialization of plugins failed. Going down.");
+
+ plugins_free(srv);
+ network_close(srv);
+ server_free(srv);
+
+ return -1;
+ }
+
+#ifdef HAVE_FORK
+ /* network is up, let's deamonize ourself */
+ if (srv->srvconf.dont_daemonize == 0) daemonize();
+#endif
+
+ srv->gid = getgid();
+ srv->uid = getuid();
+
+ /* write pid file */
+ if (pid_fd != -1) {
+ buffer_copy_long(srv->tmp_buf, getpid());
+ buffer_append_string(srv->tmp_buf, "\n");
+ write(pid_fd, srv->tmp_buf->ptr, srv->tmp_buf->used - 1);
+ close(pid_fd);
+ pid_fd = -1;
+ }
+
+ if (HANDLER_GO_ON != plugins_call_set_defaults(srv)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "Configuration of plugins failed. Going down.");
+
+ plugins_free(srv);
+ network_close(srv);
+ server_free(srv);
+
+ return -1;
+ }
+
+ /* dump unused config-keys */
+ for (i = 0; i < srv->config_context->used; i++) {
+ array *config = ((data_config *)srv->config_context->data[i])->value;
+ size_t j;
+
+ for (j = 0; config && j < config->used; j++) {
+ data_unset *du = config->data[j];
+
+ /* all var.* is known as user defined variable */
+ if (strncmp(du->key->ptr, "var.", sizeof("var.") - 1) == 0) {
+ continue;
+ }
+
+ if (NULL == array_get_element(srv->config_touched, du->key->ptr)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "WARNING: unknown config-key:",
+ du->key,
+ "(ignored)");
+ }
+ }
+ }
+
+ if (srv->config_deprecated) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "Configuration contains deprecated keys. Going down.");
+
+ plugins_free(srv);
+ network_close(srv);
+ server_free(srv);
+
+ return -1;
+ }
+
+ if (-1 == log_error_open(srv)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "opening errorlog failed, dying");
+
+ plugins_free(srv);
+ network_close(srv);
+ server_free(srv);
+ return -1;
+ }
+
+
+#ifdef HAVE_SIGACTION
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, NULL);
+ sigaction(SIGUSR1, &act, NULL);
+# if defined(SA_SIGINFO)
+ act.sa_sigaction = sigaction_handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_SIGINFO;
+# else
+ act.sa_handler = signal_handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+# endif
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGALRM, &act, NULL);
+ sigaction(SIGCHLD, &act, NULL);
+
+#elif defined(HAVE_SIGNAL)
+ /* ignore the SIGPIPE from sendfile() */
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGUSR1, SIG_IGN);
+ signal(SIGALRM, signal_handler);
+ signal(SIGTERM, signal_handler);
+ signal(SIGHUP, signal_handler);
+ signal(SIGCHLD, signal_handler);
+ signal(SIGINT, signal_handler);
+#endif
+
+#ifdef USE_ALARM
+ signal(SIGALRM, signal_handler);
+
+ /* setup periodic timer (1 second) */
+ if (setitimer(ITIMER_REAL, &interval, NULL)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "setting timer failed");
+ return -1;
+ }
+
+ getitimer(ITIMER_REAL, &interval);
+#endif
+
+#ifdef HAVE_FORK
+ /* start watcher and workers */
+ num_childs = srv->srvconf.max_worker;
+ if (num_childs > 0) {
+ int child = 0;
+ while (!child && !srv_shutdown) {
+ if (num_childs > 0) {
+ switch (fork()) {
+ case -1:
+ return -1;
+ case 0:
+ child = 1;
+ break;
+ default:
+ num_childs--;
+ break;
+ }
+ } else {
+ int status;
+ wait(&status);
+ num_childs++;
+ }
+ }
+ if (srv_shutdown) {
+ kill(0, SIGTERM);
+ }
+ if (!child) return 0;
+ }
+#endif
+
+ if (NULL == (srv->ev = fdevent_init(srv->max_fds + 1, srv->event_handler))) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "s", "fdevent_init failed");
+ return -1;
+ }
+ /*
+ * kqueue() is called here, select resets its internals,
+ * all server sockets get their handlers
+ *
+ * */
+ if (0 != network_register_fdevents(srv)) {
+ plugins_free(srv);
+ network_close(srv);
+ server_free(srv);
+
+ return -1;
+ }
+
+ /* might fail if user is using fam (not gamin) and famd isn't running */
+ if (NULL == (srv->stat_cache = stat_cache_init())) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "stat-cache could not be setup, dieing.");
+ return -1;
+ }
+
+#ifdef HAVE_FAM_H
+ /* setup FAM */
+ if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) {
+ if (0 != FAMOpen2(srv->stat_cache->fam, "lighttpd")) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "could not open a fam connection, dieing.");
+ return -1;
+ }
+#ifdef HAVE_FAMNOEXISTS
+ FAMNoExists(srv->stat_cache->fam);
+#endif
+
+ srv->stat_cache->fam_fcce_ndx = -1;
+ fdevent_register(srv->ev, FAMCONNECTION_GETFD(srv->stat_cache->fam), stat_cache_handle_fdevent, NULL);
+ fdevent_event_add(srv->ev, &(srv->stat_cache->fam_fcce_ndx), FAMCONNECTION_GETFD(srv->stat_cache->fam), FDEVENT_IN);
+ }
+#endif
+
+
+ /* get the current number of FDs */
+ srv->cur_fds = open("/dev/null", O_RDONLY);
+ close(srv->cur_fds);
+
+ for (i = 0; i < srv->srv_sockets.used; i++) {
+ server_socket *srv_socket = srv->srv_sockets.ptr[i];
+ if (-1 == fdevent_fcntl_set(srv->ev, srv_socket->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed:", strerror(errno));
+ return -1;
+ }
+ }
+
+ /* main-loop */
+ while (!srv_shutdown) {
+ int n;
+ size_t ndx;
+ time_t min_ts;
+
+ if (handle_sig_hup) {
+ handler_t r;
+
+ /* reset notification */
+ handle_sig_hup = 0;
+
+
+ /* cycle logfiles */
+
+ switch(r = plugins_call_handle_sighup(srv)) {
+ case HANDLER_GO_ON:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "sighup-handler return with an error", r);
+ break;
+ }
+
+ if (-1 == log_error_cycle(srv)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "cycling errorlog failed, dying");
+
+ return -1;
+ }
+ }
+
+ if (handle_sig_alarm) {
+ /* a new second */
+
+#ifdef USE_ALARM
+ /* reset notification */
+ handle_sig_alarm = 0;
+#endif
+
+ /* get current time */
+ min_ts = time(NULL);
+
+ if (min_ts != srv->cur_ts) {
+ int cs = 0;
+ connections *conns = srv->conns;
+ handler_t r;
+
+ switch(r = plugins_call_handle_trigger(srv)) {
+ case HANDLER_GO_ON:
+ break;
+ case HANDLER_ERROR:
+ log_error_write(srv, __FILE__, __LINE__, "s", "one of the triggers failed");
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "d", r);
+ break;
+ }
+
+ /* trigger waitpid */
+ srv->cur_ts = min_ts;
+
+ /* cleanup stat-cache */
+ stat_cache_trigger_cleanup(srv);
+ /**
+ * check all connections for timeouts
+ *
+ */
+ for (ndx = 0; ndx < conns->used; ndx++) {
+ int changed = 0;
+ connection *con;
+ int t_diff;
+
+ con = conns->ptr[ndx];
+
+ if (con->state == CON_STATE_READ ||
+ con->state == CON_STATE_READ_POST) {
+ if (con->request_count == 1) {
+ if (srv->cur_ts - con->read_idle_ts > con->conf.max_read_idle) {
+ /* time - out */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed - read-timeout:", con->fd);
+#endif
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ changed = 1;
+ }
+ } else {
+ if (srv->cur_ts - con->read_idle_ts > con->conf.max_keep_alive_idle) {
+ /* time - out */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed - read-timeout:", con->fd);
+#endif
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ changed = 1;
+ }
+ }
+ }
+
+ if ((con->state == CON_STATE_WRITE) &&
+ (con->write_request_ts != 0)) {
+#if 0
+ if (srv->cur_ts - con->write_request_ts > 60) {
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "connection closed - pre-write-request-timeout:", con->fd, srv->cur_ts - con->write_request_ts);
+ }
+#endif
+
+ if (srv->cur_ts - con->write_request_ts > con->conf.max_write_idle) {
+ /* time - out */
+#if 1
+ log_error_write(srv, __FILE__, __LINE__, "sbsosds",
+ "NOTE: a request for",
+ con->request.uri,
+ "timed out after writing",
+ con->bytes_written,
+ "bytes. We waited",
+ (int)con->conf.max_write_idle,
+ "seconds. If this a problem increase server.max-write-idle");
+#endif
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ changed = 1;
+ }
+ }
+ /* we don't like div by zero */
+ if (0 == (t_diff = srv->cur_ts - con->connection_start)) t_diff = 1;
+
+ if (con->traffic_limit_reached &&
+ (con->conf.kbytes_per_second == 0 ||
+ ((con->bytes_written / t_diff) < con->conf.kbytes_per_second * 1024))) {
+ /* enable connection again */
+ con->traffic_limit_reached = 0;
+
+ changed = 1;
+ }
+
+ if (changed) {
+ connection_state_machine(srv, con);
+ }
+ con->bytes_written_cur_second = 0;
+ *(con->conf.global_bytes_per_second_cnt_ptr) = 0;
+
+#if 0
+ if (cs == 0) {
+ fprintf(stderr, "connection-state: ");
+ cs = 1;
+ }
+
+ fprintf(stderr, "c[%d,%d]: %s ",
+ con->fd,
+ con->fcgi.fd,
+ connection_get_state(con->state));
+#endif
+ }
+
+ if (cs == 1) fprintf(stderr, "\n");
+ }
+ }
+
+ if (srv->sockets_disabled) {
+ /* our server sockets are disabled, why ? */
+
+ if ((srv->cur_fds + srv->want_fds < srv->max_fds * 0.8) && /* we have enough unused fds */
+ (srv->conns->used < srv->max_conns * 0.9) &&
+ (0 == graceful_shutdown)) {
+ for (i = 0; i < srv->srv_sockets.used; i++) {
+ server_socket *srv_socket = srv->srv_sockets.ptr[i];
+ fdevent_event_add(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN);
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets enabled again");
+
+ srv->sockets_disabled = 0;
+ }
+ } else {
+ if ((srv->cur_fds + srv->want_fds > srv->max_fds * 0.9) || /* out of fds */
+ (srv->conns->used > srv->max_conns) || /* out of connections */
+ (graceful_shutdown)) { /* graceful_shutdown */
+
+ /* disable server-fds */
+
+ for (i = 0; i < srv->srv_sockets.used; i++) {
+ server_socket *srv_socket = srv->srv_sockets.ptr[i];
+ fdevent_event_del(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd);
+
+ if (graceful_shutdown) {
+ /* we don't want this socket anymore,
+ *
+ * closing it right away will make it possible for
+ * the next lighttpd to take over (graceful restart)
+ * */
+
+ fdevent_unregister(srv->ev, srv_socket->fd);
+ close(srv_socket->fd);
+ srv_socket->fd = -1;
+
+ /* network_close() will cleanup after us */
+ }
+ }
+
+ if (graceful_shutdown) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "[note] graceful shutdown started");
+ } else if (srv->conns->used > srv->max_conns) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets disabled, connection limit reached");
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets disabled, out-of-fds");
+ }
+
+ srv->sockets_disabled = 1;
+ }
+ }
+
+ if (graceful_shutdown && srv->conns->used == 0) {
+ /* we are in graceful shutdown phase and all connections are closed
+ * we are ready to terminate without harming anyone */
+ srv_shutdown = 1;
+ }
+
+ /* we still have some fds to share */
+ if (srv->want_fds) {
+ /* check the fdwaitqueue for waiting fds */
+ int free_fds = srv->max_fds - srv->cur_fds - 16;
+ connection *con;
+
+ for (; free_fds > 0 && NULL != (con = fdwaitqueue_unshift(srv, srv->fdwaitqueue)); free_fds--) {
+ connection_state_machine(srv, con);
+
+ srv->want_fds--;
+ }
+ }
+
+ if ((n = fdevent_poll(srv->ev, 1000)) > 0) {
+ /* n is the number of events */
+ int revents;
+ int fd_ndx;
+#if 0
+ if (n > 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "polls:", n);
+ }
+#endif
+ fd_ndx = -1;
+ do {
+ fdevent_handler handler;
+ void *context;
+ handler_t r;
+
+ fd_ndx = fdevent_event_next_fdndx (srv->ev, fd_ndx);
+ revents = fdevent_event_get_revent (srv->ev, fd_ndx);
+ fd = fdevent_event_get_fd (srv->ev, fd_ndx);
+ handler = fdevent_get_handler(srv->ev, fd);
+ context = fdevent_get_context(srv->ev, fd);
+
+ /* connection_handle_fdevent needs a joblist_append */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "event for", fd, revents);
+#endif
+ switch (r = (*handler)(srv, context, revents)) {
+ case HANDLER_FINISHED:
+ case HANDLER_GO_ON:
+ case HANDLER_WAIT_FOR_EVENT:
+ case HANDLER_WAIT_FOR_FD:
+ break;
+ case HANDLER_ERROR:
+ /* should never happen */
+ SEGFAULT();
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "d", r);
+ break;
+ }
+ } while (--n > 0);
+ } else if (n < 0 && errno != EINTR) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "fdevent_poll failed:",
+ strerror(errno));
+ }
+
+ for (ndx = 0; ndx < srv->joblist->used; ndx++) {
+ connection *con = srv->joblist->ptr[ndx];
+ handler_t r;
+
+ connection_state_machine(srv, con);
+
+ switch(r = plugins_call_handle_joblist(srv, con)) {
+ case HANDLER_FINISHED:
+ case HANDLER_GO_ON:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "d", r);
+ break;
+ }
+
+ con->in_joblist = 0;
+ }
+
+ srv->joblist->used = 0;
+ }
+
+ if (srv->srvconf.pid_file->used &&
+ srv->srvconf.changeroot->used == 0) {
+ if (0 != unlink(srv->srvconf.pid_file->ptr)) {
+ if (errno != EACCES && errno != EPERM) {
+ log_error_write(srv, __FILE__, __LINE__, "sbds",
+ "unlink failed for:",
+ srv->srvconf.pid_file,
+ errno,
+ strerror(errno));
+ }
+ }
+ }
+
+ /* clean-up */
+ log_error_close(srv);
+ network_close(srv);
+ connections_free(srv);
+ plugins_free(srv);
+ server_free(srv);
+
+ return 0;
+}
diff --git a/src/server.h b/src/server.h
new file mode 100644
index 0000000..bca2d52
--- /dev/null
+++ b/src/server.h
@@ -0,0 +1,17 @@
+#ifndef _SERVER_H_
+#define _SERVER_H_
+
+#include "base.h"
+
+typedef struct {
+ char *key;
+ char *value;
+} two_strings;
+
+typedef enum { CONFIG_UNSET, CONFIG_DOCUMENT_ROOT } config_var_t;
+
+int config_read(server *srv, const char *fn);
+int config_set_defaults(server *srv);
+buffer *config_get_value_buffer(server *srv, connection *con, config_var_t field);
+
+#endif
diff --git a/src/settings.h b/src/settings.h
new file mode 100644
index 0000000..f0c6354
--- /dev/null
+++ b/src/settings.h
@@ -0,0 +1,44 @@
+#ifndef _LIGHTTPD_SETTINGS_H_
+#define _LIGHTTPD_SETTINGS_H_
+
+#define BV(x) (1 << x)
+
+#define INET_NTOP_CACHE_MAX 4
+#define FILE_CACHE_MAX 16
+
+/**
+ * max size of a buffer which will just be reset
+ * to ->used = 0 instead of really freeing the buffer
+ *
+ * 64kB (no real reason, just a guess)
+ */
+#define BUFFER_MAX_REUSE_SIZE (4 * 1024)
+
+/**
+ * max size of the HTTP request header
+ *
+ * 32k should be enough for everything (just a guess)
+ *
+ */
+#define MAX_HTTP_REQUEST_HEADER (32 * 1024)
+
+typedef enum { HANDLER_UNSET,
+ HANDLER_GO_ON,
+ HANDLER_FINISHED,
+ HANDLER_COMEBACK,
+ HANDLER_WAIT_FOR_EVENT,
+ HANDLER_ERROR,
+ HANDLER_WAIT_FOR_FD
+} handler_t;
+
+
+/* we use it in a enum */
+#ifdef TRUE
+#undef TRUE
+#endif
+
+#ifdef FALSE
+#undef FALSE
+#endif
+
+#endif
diff --git a/src/spawn-fcgi.c b/src/spawn-fcgi.c
new file mode 100644
index 0000000..99fc31a
--- /dev/null
+++ b/src/spawn-fcgi.c
@@ -0,0 +1,431 @@
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#ifdef HAVE_PWD_H
+#include <grp.h>
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#define FCGI_LISTENSOCK_FILENO 0
+
+#ifndef UNIX_PATH_MAX
+# define UNIX_PATH_MAX 108
+#endif
+
+#include "sys-socket.h"
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+/* for solaris 2.5 and netbsd 1.3.x */
+#ifndef HAVE_SOCKLEN_T
+typedef int socklen_t;
+#endif
+
+#ifdef HAVE_SYS_UN_H
+int fcgi_spawn_connection(char *appPath, unsigned short port, const char *unixsocket, int child_count, int pid_fd, int nofork) {
+ int fcgi_fd;
+ int socket_type, status;
+ struct timeval tv = { 0, 100 * 1000 };
+
+ struct sockaddr_un fcgi_addr_un;
+ struct sockaddr_in fcgi_addr_in;
+ struct sockaddr *fcgi_addr;
+
+ socklen_t servlen;
+
+ if (child_count < 2) {
+ child_count = 5;
+ }
+
+ if (child_count > 256) {
+ child_count = 256;
+ }
+
+
+ if (unixsocket) {
+ memset(&fcgi_addr, 0, sizeof(fcgi_addr));
+
+ fcgi_addr_un.sun_family = AF_UNIX;
+ strcpy(fcgi_addr_un.sun_path, unixsocket);
+
+#ifdef SUN_LEN
+ servlen = SUN_LEN(&fcgi_addr_un);
+#else
+ /* stevens says: */
+ servlen = strlen(fcgi_addr_un.sun_path) + sizeof(fcgi_addr_un.sun_family);
+#endif
+ socket_type = AF_UNIX;
+ fcgi_addr = (struct sockaddr *) &fcgi_addr_un;
+ } else {
+ fcgi_addr_in.sin_family = AF_INET;
+ fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
+ fcgi_addr_in.sin_port = htons(port);
+ servlen = sizeof(fcgi_addr_in);
+
+ socket_type = AF_INET;
+ fcgi_addr = (struct sockaddr *) &fcgi_addr_in;
+ }
+
+ if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
+ fprintf(stderr, "%s.%d\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) {
+ /* server is not up, spawn in */
+ pid_t child;
+ int val;
+
+ if (unixsocket) unlink(unixsocket);
+
+ close(fcgi_fd);
+
+ /* reopen socket */
+ if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
+ fprintf(stderr, "%s.%d\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ val = 1;
+ if (setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
+ fprintf(stderr, "%s.%d\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ /* create socket */
+ if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) {
+ fprintf(stderr, "%s.%d: bind failed: %s\n",
+ __FILE__, __LINE__,
+ strerror(errno));
+ return -1;
+ }
+
+ if (-1 == listen(fcgi_fd, 1024)) {
+ fprintf(stderr, "%s.%d: fd = -1\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ if (!nofork) {
+ child = fork();
+ } else {
+ child = 0;
+ }
+
+ switch (child) {
+ case 0: {
+ char cgi_childs[64];
+ char *b;
+
+ int i = 0;
+
+ /* is save as we limit to 256 childs */
+ sprintf(cgi_childs, "PHP_FCGI_CHILDREN=%d", child_count);
+
+ if(fcgi_fd != FCGI_LISTENSOCK_FILENO) {
+ close(FCGI_LISTENSOCK_FILENO);
+ dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO);
+ close(fcgi_fd);
+ }
+
+ /* we don't need the client socket */
+ for (i = 3; i < 256; i++) {
+ close(i);
+ }
+
+ /* create environment */
+
+ putenv(cgi_childs);
+
+ /* fork and replace shell */
+ b = malloc(strlen("exec ") + strlen(appPath) + 1);
+ strcpy(b, "exec ");
+ strcat(b, appPath);
+
+ /* exec the cgi */
+ execl("/bin/sh", "sh", "-c", b, NULL);
+
+ exit(errno);
+
+ break;
+ }
+ case -1:
+ /* error */
+ break;
+ default:
+ /* father */
+
+ /* wait */
+ select(0, NULL, NULL, NULL, &tv);
+
+ switch (waitpid(child, &status, WNOHANG)) {
+ case 0:
+ fprintf(stderr, "%s.%d: child spawned successfully: PID: %d\n",
+ __FILE__, __LINE__,
+ child);
+
+ /* write pid file */
+ if (pid_fd != -1) {
+ /* assume a 32bit pid_t */
+ char pidbuf[12];
+
+ snprintf(pidbuf, sizeof(pidbuf) - 1, "%d", child);
+
+ write(pid_fd, pidbuf, strlen(pidbuf));
+ close(pid_fd);
+ pid_fd = -1;
+ }
+
+ break;
+ case -1:
+ break;
+ default:
+ if (WIFEXITED(status)) {
+ fprintf(stderr, "%s.%d: child exited with: %d, %s\n",
+ __FILE__, __LINE__,
+ WEXITSTATUS(status), strerror(WEXITSTATUS(status)));
+ } else if (WIFSIGNALED(status)) {
+ fprintf(stderr, "%s.%d: child signaled: %d\n",
+ __FILE__, __LINE__,
+ WTERMSIG(status));
+ } else {
+ fprintf(stderr, "%s.%d: child died somehow: %d\n",
+ __FILE__, __LINE__,
+ status);
+ }
+ }
+
+ break;
+ }
+ } else {
+ fprintf(stderr, "%s.%d: socket is already used, can't spawn\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ close(fcgi_fd);
+
+ return 0;
+}
+
+
+void show_version () {
+ char *b = "spawn-fcgi" "-" PACKAGE_VERSION \
+" - spawns fastcgi processes\n"
+;
+ write(1, b, strlen(b));
+}
+
+void show_help () {
+ char *b = "spawn-fcgi" "-" PACKAGE_VERSION \
+" - spawns fastcgi processes\n" \
+"usage:\n" \
+" -f <fcgiapp> filename of the fcgi-application\n" \
+" -p <port> bind to tcp-port\n" \
+" -s <path> bind to unix-domain socket\n" \
+" -C <childs> (PHP only) numbers of childs to spawn (default 5)\n" \
+" -P <path> name of PID-file for spawed process\n" \
+" -n no fork (for daemontools)\n" \
+" -v show version\n" \
+" -h show this help\n" \
+"(root only)\n" \
+" -c <dir> chroot to directory\n" \
+" -u <user> change to user-id\n" \
+" -g <group> change to group-id\n" \
+;
+ write(1, b, strlen(b));
+}
+
+
+int main(int argc, char **argv) {
+ char *fcgi_app = NULL, *changeroot = NULL, *username = NULL,
+ *groupname = NULL, *unixsocket = NULL, *pid_file = NULL;
+ unsigned short port = 0;
+ int child_count = 5;
+ int i_am_root, o;
+ int pid_fd = -1;
+ int nofork = 0;
+
+ i_am_root = (getuid() == 0);
+
+ while(-1 != (o = getopt(argc, argv, "c:f:g:hnp:u:vC:s:P:"))) {
+ switch(o) {
+ case 'f': fcgi_app = optarg; break;
+ case 'p': port = strtol(optarg, NULL, 10);/* port */ break;
+ case 'C': child_count = strtol(optarg, NULL, 10);/* */ break;
+ case 's': unixsocket = optarg; /* unix-domain socket */ break;
+ case 'c': if (i_am_root) { changeroot = optarg; }/* chroot() */ break;
+ case 'u': if (i_am_root) { username = optarg; } /* set user */ break;
+ case 'g': if (i_am_root) { groupname = optarg; } /* set group */ break;
+ case 'n': nofork = 1; break;
+ case 'P': pid_file = optarg; /* PID file */ break;
+ case 'v': show_version(); return 0;
+ case 'h': show_help(); return 0;
+ default:
+ show_help();
+ return -1;
+ }
+ }
+
+ if (fcgi_app == NULL || (port == 0 && unixsocket == NULL)) {
+ show_help();
+ return -1;
+ }
+
+ if (unixsocket && port) {
+ fprintf(stderr, "%s.%d: %s\n",
+ __FILE__, __LINE__,
+ "either a unix domain socket or a tcp-port, but not both\n");
+
+ return -1;
+ }
+
+ if (unixsocket && strlen(unixsocket) > UNIX_PATH_MAX - 1) {
+ fprintf(stderr, "%s.%d: %s\n",
+ __FILE__, __LINE__,
+ "path of the unix socket is too long\n");
+
+ return -1;
+ }
+
+ /* UID handling */
+ if (!i_am_root && (geteuid() == 0 || getegid() == 0)) {
+ /* we are setuid-root */
+
+ fprintf(stderr, "%s.%d: %s\n",
+ __FILE__, __LINE__,
+ "Are you nuts ? Don't apply a SUID bit to this binary\n");
+
+ return -1;
+ }
+
+ if (pid_file &&
+ (-1 == (pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)))) {
+ struct stat st;
+ if (errno != EEXIST) {
+ fprintf(stderr, "%s.%d: opening pid-file '%s' failed: %s\n",
+ __FILE__, __LINE__,
+ pid_file, strerror(errno));
+
+ return -1;
+ }
+
+ /* ok, file exists */
+
+ if (0 != stat(pid_file, &st)) {
+ fprintf(stderr, "%s.%d: stating pid-file '%s' failed: %s\n",
+ __FILE__, __LINE__,
+ pid_file, strerror(errno));
+
+ return -1;
+ }
+
+ /* is it a regular file ? */
+
+ if (!S_ISREG(st.st_mode)) {
+ fprintf(stderr, "%s.%d: pid-file exists and isn't regular file: '%s'\n",
+ __FILE__, __LINE__,
+ pid_file);
+
+ return -1;
+ }
+
+ if (-1 == (pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) {
+ fprintf(stderr, "%s.%d: opening pid-file '%s' failed: %s\n",
+ __FILE__, __LINE__,
+ pid_file, strerror(errno));
+
+ return -1;
+ }
+ }
+
+ if (i_am_root) {
+ struct group *grp = NULL;
+ struct passwd *pwd = NULL;
+
+ /* set user and group */
+
+ if (username) {
+ if (NULL == (pwd = getpwnam(username))) {
+ fprintf(stderr, "%s.%d: %s, %s\n",
+ __FILE__, __LINE__,
+ "can't find username", username);
+ return -1;
+ }
+
+ if (pwd->pw_uid == 0) {
+ fprintf(stderr, "%s.%d: %s\n",
+ __FILE__, __LINE__,
+ "I will not set uid to 0\n");
+ return -1;
+ }
+ }
+
+ if (groupname) {
+ if (NULL == (grp = getgrnam(groupname))) {
+ fprintf(stderr, "%s.%d: %s %s\n",
+ __FILE__, __LINE__,
+ "can't find groupname",
+ groupname);
+ return -1;
+ }
+ if (grp->gr_gid == 0) {
+ fprintf(stderr, "%s.%d: %s\n",
+ __FILE__, __LINE__,
+ "I will not set gid to 0\n");
+ return -1;
+ }
+ }
+
+ if (changeroot) {
+ if (-1 == chroot(changeroot)) {
+ fprintf(stderr, "%s.%d: %s %s\n",
+ __FILE__, __LINE__,
+ "chroot failed: ", strerror(errno));
+ return -1;
+ }
+ if (-1 == chdir("/")) {
+ fprintf(stderr, "%s.%d: %s %s\n",
+ __FILE__, __LINE__,
+ "chdir failed: ", strerror(errno));
+ return -1;
+ }
+ }
+
+ /* drop root privs */
+ if (groupname) {
+ setgid(grp->gr_gid);
+ setgroups(0, NULL);
+ }
+ if (username) setuid(pwd->pw_uid);
+ }
+
+ return fcgi_spawn_connection(fcgi_app, port, unixsocket, child_count, pid_fd, nofork);
+}
+#else
+int main() {
+ return -1;
+}
+#endif
diff --git a/src/splaytree.c b/src/splaytree.c
new file mode 100644
index 0000000..3a80910
--- /dev/null
+++ b/src/splaytree.c
@@ -0,0 +1,210 @@
+/*
+ An implementation of top-down splaying with sizes
+ D. Sleator <sleator@cs.cmu.edu>, January 1994.
+
+ This extends top-down-splay.c to maintain a size field in each node.
+ This is the number of nodes in the subtree rooted there. This makes
+ it possible to efficiently compute the rank of a key. (The rank is
+ the number of nodes to the left of the given key.) It it also
+ possible to quickly find the node of a given rank. Both of these
+ operations are illustrated in the code below. The remainder of this
+ introduction is taken from top-down-splay.c.
+
+ "Splay trees", or "self-adjusting search trees" are a simple and
+ efficient data structure for storing an ordered set. The data
+ structure consists of a binary tree, with no additional fields. It
+ allows searching, insertion, deletion, deletemin, deletemax,
+ splitting, joining, and many other operations, all with amortized
+ logarithmic performance. Since the trees adapt to the sequence of
+ requests, their performance on real access patterns is typically even
+ better. Splay trees are described in a number of texts and papers
+ [1,2,3,4].
+
+ The code here is adapted from simple top-down splay, at the bottom of
+ page 669 of [2]. It can be obtained via anonymous ftp from
+ spade.pc.cs.cmu.edu in directory /usr/sleator/public.
+
+ The chief modification here is that the splay operation works even if the
+ item being splayed is not in the tree, and even if the tree root of the
+ tree is NULL. So the line:
+
+ t = splay(i, t);
+
+ causes it to search for item with key i in the tree rooted at t. If it's
+ there, it is splayed to the root. If it isn't there, then the node put
+ at the root is the last one before NULL that would have been reached in a
+ normal binary search for i. (It's a neighbor of i in the tree.) This
+ allows many other operations to be easily implemented, as shown below.
+
+ [1] "Data Structures and Their Algorithms", Lewis and Denenberg,
+ Harper Collins, 1991, pp 243-251.
+ [2] "Self-adjusting Binary Search Trees" Sleator and Tarjan,
+ JACM Volume 32, No 3, July 1985, pp 652-686.
+ [3] "Data Structure and Algorithm Analysis", Mark Weiss,
+ Benjamin Cummins, 1992, pp 119-130.
+ [4] "Data Structures, Algorithms, and Performance", Derick Wood,
+ Addison-Wesley, 1993, pp 367-375
+*/
+
+#include <stdlib.h>
+#include <assert.h>
+#include "splaytree.h"
+
+#define compare(i,j) ((i)-(j))
+/* This is the comparison. */
+/* Returns <0 if i<j, =0 if i=j, and >0 if i>j */
+
+#define node_size splaytree_size
+
+/* Splay using the key i (which may or may not be in the tree.)
+ * The starting root is t, and the tree used is defined by rat
+ * size fields are maintained */
+splay_tree * splaytree_splay (splay_tree *t, int i) {
+ splay_tree N, *l, *r, *y;
+ int comp, root_size, l_size, r_size;
+
+ if (t == NULL) return t;
+ N.left = N.right = NULL;
+ l = r = &N;
+ root_size = node_size(t);
+ l_size = r_size = 0;
+
+ for (;;) {
+ comp = compare(i, t->key);
+ if (comp < 0) {
+ if (t->left == NULL) break;
+ if (compare(i, t->left->key) < 0) {
+ y = t->left; /* rotate right */
+ t->left = y->right;
+ y->right = t;
+ t->size = node_size(t->left) + node_size(t->right) + 1;
+ t = y;
+ if (t->left == NULL) break;
+ }
+ r->left = t; /* link right */
+ r = t;
+ t = t->left;
+ r_size += 1+node_size(r->right);
+ } else if (comp > 0) {
+ if (t->right == NULL) break;
+ if (compare(i, t->right->key) > 0) {
+ y = t->right; /* rotate left */
+ t->right = y->left;
+ y->left = t;
+ t->size = node_size(t->left) + node_size(t->right) + 1;
+ t = y;
+ if (t->right == NULL) break;
+ }
+ l->right = t; /* link left */
+ l = t;
+ t = t->right;
+ l_size += 1+node_size(l->left);
+ } else {
+ break;
+ }
+ }
+ l_size += node_size(t->left); /* Now l_size and r_size are the sizes of */
+ r_size += node_size(t->right); /* the left and right trees we just built.*/
+ t->size = l_size + r_size + 1;
+
+ l->right = r->left = NULL;
+
+ /* The following two loops correct the size fields of the right path */
+ /* from the left child of the root and the right path from the left */
+ /* child of the root. */
+ for (y = N.right; y != NULL; y = y->right) {
+ y->size = l_size;
+ l_size -= 1+node_size(y->left);
+ }
+ for (y = N.left; y != NULL; y = y->left) {
+ y->size = r_size;
+ r_size -= 1+node_size(y->right);
+ }
+
+ l->right = t->left; /* assemble */
+ r->left = t->right;
+ t->left = N.right;
+ t->right = N.left;
+
+ return t;
+}
+
+splay_tree * splaytree_insert(splay_tree * t, int i, void *data) {
+/* Insert key i into the tree t, if it is not already there. */
+/* Return a pointer to the resulting tree. */
+ splay_tree * new;
+
+ if (t != NULL) {
+ t = splaytree_splay(t, i);
+ if (compare(i, t->key)==0) {
+ return t; /* it's already there */
+ }
+ }
+ new = (splay_tree *) malloc (sizeof (splay_tree));
+ assert(new);
+ if (t == NULL) {
+ new->left = new->right = NULL;
+ } else if (compare(i, t->key) < 0) {
+ new->left = t->left;
+ new->right = t;
+ t->left = NULL;
+ t->size = 1+node_size(t->right);
+ } else {
+ new->right = t->right;
+ new->left = t;
+ t->right = NULL;
+ t->size = 1+node_size(t->left);
+ }
+ new->key = i;
+ new->data = data;
+ new->size = 1 + node_size(new->left) + node_size(new->right);
+ return new;
+}
+
+splay_tree * splaytree_delete(splay_tree *t, int i) {
+/* Deletes i from the tree if it's there. */
+/* Return a pointer to the resulting tree. */
+ splay_tree * x;
+ int tsize;
+
+ if (t==NULL) return NULL;
+ tsize = t->size;
+ t = splaytree_splay(t, i);
+ if (compare(i, t->key) == 0) { /* found it */
+ if (t->left == NULL) {
+ x = t->right;
+ } else {
+ x = splaytree_splay(t->left, i);
+ x->right = t->right;
+ }
+ free(t);
+ if (x != NULL) {
+ x->size = tsize-1;
+ }
+ return x;
+ } else {
+ return t; /* It wasn't there */
+ }
+}
+
+splay_tree *find_rank(int r, splay_tree *t) {
+/* Returns a pointer to the node in the tree with the given rank. */
+/* Returns NULL if there is no such node. */
+/* Does not change the tree. To guarantee logarithmic behavior, */
+/* the node found here should be splayed to the root. */
+ int lsize;
+ if ((r < 0) || (r >= node_size(t))) return NULL;
+ for (;;) {
+ lsize = node_size(t->left);
+ if (r < lsize) {
+ t = t->left;
+ } else if (r > lsize) {
+ r = r - lsize -1;
+ t = t->right;
+ } else {
+ return t;
+ }
+ }
+}
+
+
diff --git a/src/splaytree.h b/src/splaytree.h
new file mode 100644
index 0000000..98e4234
--- /dev/null
+++ b/src/splaytree.h
@@ -0,0 +1,24 @@
+#ifndef _SPLAY_TREE_H_
+#define _SPLAY_TREE_H_
+
+typedef struct tree_node {
+ struct tree_node * left, * right;
+ int key;
+ int size; /* maintained to be the number of nodes rooted here */
+
+ void *data;
+} splay_tree;
+
+
+splay_tree * splaytree_splay (splay_tree *t, int key);
+splay_tree * splaytree_insert(splay_tree *t, int key, void *data);
+splay_tree * splaytree_delete(splay_tree *t, int key);
+splay_tree * splaytree_size(splay_tree *t);
+
+#define splaytree_size(x) (((x)==NULL) ? 0 : ((x)->size))
+/* This macro returns the size of a node. Unlike "x->size", */
+/* it works even if x=NULL. The test could be avoided by using */
+/* a special version of NULL which was a real node with size 0. */
+
+
+#endif
diff --git a/src/stat_cache.c b/src/stat_cache.c
new file mode 100644
index 0000000..148f4c8
--- /dev/null
+++ b/src/stat_cache.c
@@ -0,0 +1,668 @@
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "log.h"
+#include "stat_cache.h"
+#include "fdevent.h"
+#include "etag.h"
+
+#ifdef HAVE_ATTR_ATTRIBUTES_H
+#include <attr/attributes.h>
+#endif
+
+#ifdef HAVE_FAM_H
+# include <fam.h>
+#endif
+
+#include "sys-mmap.h"
+
+/* NetBSD 1.3.x needs it */
+#ifndef MAP_FAILED
+# define MAP_FAILED -1
+#endif
+
+#ifndef O_LARGEFILE
+# define O_LARGEFILE 0
+#endif
+
+#ifndef HAVE_LSTAT
+#define lstat stat
+#endif
+
+#if 0
+/* enables debug code for testing if all nodes in the stat-cache as accessable */
+#define DEBUG_STAT_CACHE
+#endif
+
+/*
+ * stat-cache
+ *
+ * we cache the stat() calls in our own storage
+ * the directories are cached in FAM
+ *
+ * if we get a change-event from FAM, we increment the version in the FAM->dir mapping
+ *
+ * if the stat()-cache is queried we check if the version id for the directory is the
+ * same and return immediatly.
+ *
+ *
+ * What we need:
+ *
+ * - for each stat-cache entry we need a fast indirect lookup on the directory name
+ * - for each FAMRequest we have to find the version in the directory cache (index as userdata)
+ *
+ * stat <<-> directory <-> FAMRequest
+ *
+ * if file is deleted, directory is dirty, file is rechecked ...
+ * if directory is deleted, directory mapping is removed
+ *
+ * */
+
+#ifdef HAVE_FAM_H
+typedef struct {
+ FAMRequest *req;
+ FAMConnection *fc;
+
+ buffer *name;
+
+ int version;
+} fam_dir_entry;
+#endif
+
+/* the directory name is too long to always compare on it
+ * - we need a hash
+ * - the hash-key is used as sorting criteria for a tree
+ * - a splay-tree is used as we can use the caching effect of it
+ */
+
+/* we want to cleanup the stat-cache every few seconds, let's say 10
+ *
+ * - remove entries which are outdated since 30s
+ * - remove entries which are fresh but havn't been used since 60s
+ * - if we don't have a stat-cache entry for a directory, release it from the monitor
+ */
+
+#ifdef DEBUG_STAT_CACHE
+typedef struct {
+ int *ptr;
+
+ size_t used;
+ size_t size;
+} fake_keys;
+
+static fake_keys ctrl;
+#endif
+
+stat_cache *stat_cache_init(void) {
+ stat_cache *fc = NULL;
+
+ fc = calloc(1, sizeof(*fc));
+
+ fc->dir_name = buffer_init();
+#ifdef HAVE_FAM_H
+ fc->fam = calloc(1, sizeof(*fc->fam));
+#endif
+
+#ifdef DEBUG_STAT_CACHE
+ ctrl.size = 0;
+#endif
+
+ return fc;
+}
+
+static stat_cache_entry * stat_cache_entry_init(void) {
+ stat_cache_entry *sce = NULL;
+
+ sce = calloc(1, sizeof(*sce));
+
+ sce->name = buffer_init();
+ sce->etag = buffer_init();
+ sce->content_type = buffer_init();
+
+ return sce;
+}
+
+static void stat_cache_entry_free(void *data) {
+ stat_cache_entry *sce = data;
+ if (!sce) return;
+
+ buffer_free(sce->etag);
+ buffer_free(sce->name);
+ buffer_free(sce->content_type);
+
+ free(sce);
+}
+
+#ifdef HAVE_FAM_H
+static fam_dir_entry * fam_dir_entry_init(void) {
+ fam_dir_entry *fam_dir = NULL;
+
+ fam_dir = calloc(1, sizeof(*fam_dir));
+
+ fam_dir->name = buffer_init();
+
+ return fam_dir;
+}
+
+static void fam_dir_entry_free(void *data) {
+ fam_dir_entry *fam_dir = data;
+
+ if (!fam_dir) return;
+
+ FAMCancelMonitor(fam_dir->fc, fam_dir->req);
+
+ buffer_free(fam_dir->name);
+ free(fam_dir->req);
+
+ free(fam_dir);
+}
+#endif
+
+void stat_cache_free(stat_cache *sc) {
+ while (sc->files) {
+ int osize;
+ splay_tree *node = sc->files;
+
+ osize = sc->files->size;
+
+ stat_cache_entry_free(node->data);
+ sc->files = splaytree_delete(sc->files, node->key);
+
+ assert(osize - 1 == splaytree_size(sc->files));
+ }
+
+ buffer_free(sc->dir_name);
+
+#ifdef HAVE_FAM_H
+ while (sc->dirs) {
+ int osize;
+ splay_tree *node = sc->dirs;
+
+ osize = sc->dirs->size;
+
+ fam_dir_entry_free(node->data);
+ sc->dirs = splaytree_delete(sc->dirs, node->key);
+
+ if (osize == 1) {
+ assert(NULL == sc->dirs);
+ } else {
+ assert(osize == (sc->dirs->size + 1));
+ }
+ }
+
+ if (sc->fam) {
+ FAMClose(sc->fam);
+ free(sc->fam);
+ }
+#endif
+ free(sc);
+}
+
+#ifdef HAVE_XATTR
+static int stat_cache_attr_get(buffer *buf, char *name) {
+ int attrlen;
+ int ret;
+
+ attrlen = 1024;
+ buffer_prepare_copy(buf, attrlen);
+ attrlen--;
+ if(0 == (ret = attr_get(name, "Content-Type", buf->ptr, &attrlen, 0))) {
+ buf->used = attrlen + 1;
+ buf->ptr[attrlen] = '\0';
+ }
+ return ret;
+}
+#endif
+
+/* the famous DJB hash function for strings */
+static uint32_t hashme(buffer *str) {
+ uint32_t hash = 5381;
+ const char *s;
+ for (s = str->ptr; *s; s++) {
+ hash = ((hash << 5) + hash) + *s;
+ }
+
+ hash &= ~(1 << 31); /* strip the highest bit */
+
+ return hash;
+}
+
+#ifdef HAVE_FAM_H
+handler_t stat_cache_handle_fdevent(void *_srv, void *_fce, int revent) {
+ size_t i;
+ server *srv = _srv;
+ stat_cache *sc = srv->stat_cache;
+ size_t events;
+
+ UNUSED(_fce);
+ /* */
+
+ if ((revent & FDEVENT_IN) &&
+ sc->fam) {
+
+ events = FAMPending(sc->fam);
+
+ for (i = 0; i < events; i++) {
+ FAMEvent fe;
+ fam_dir_entry *fam_dir;
+ splay_tree *node;
+ int ndx;
+
+ FAMNextEvent(sc->fam, &fe);
+
+ /* handle event */
+
+ switch(fe.code) {
+ case FAMChanged:
+ case FAMDeleted:
+ case FAMMoved:
+ /* if the filename is a directory remove the entry */
+
+ fam_dir = fe.userdata;
+ fam_dir->version++;
+
+ /* file/dir is still here */
+ if (fe.code == FAMChanged) break;
+
+ buffer_copy_string(sc->dir_name, fe.filename);
+
+ ndx = hashme(sc->dir_name);
+
+ sc->dirs = splaytree_splay(sc->dirs, ndx);
+ node = sc->dirs;
+
+ if (node && (node->key == ndx)) {
+ int osize = splaytree_size(sc->dirs);
+
+ fam_dir_entry_free(node->data);
+ sc->dirs = splaytree_delete(sc->dirs, ndx);
+
+ assert(osize - 1 == splaytree_size(sc->dirs));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (revent & FDEVENT_HUP) {
+ /* fam closed the connection */
+ srv->stat_cache->fam_fcce_ndx = -1;
+
+ fdevent_event_del(srv->ev, &(sc->fam_fcce_ndx), FAMCONNECTION_GETFD(sc->fam));
+ fdevent_unregister(srv->ev, FAMCONNECTION_GETFD(sc->fam));
+
+ FAMClose(sc->fam);
+ free(sc->fam);
+
+ sc->fam = NULL;
+ }
+
+ return HANDLER_GO_ON;
+}
+
+static int buffer_copy_dirname(buffer *dst, buffer *file) {
+ size_t i;
+
+ if (buffer_is_empty(file)) return -1;
+
+ for (i = file->used - 1; i+1 > 0; i--) {
+ if (file->ptr[i] == '/') {
+ buffer_copy_string_len(dst, file->ptr, i);
+ return 0;
+ }
+ }
+
+ return -1;
+}
+#endif
+
+/***
+ *
+ *
+ *
+ * returns:
+ * - HANDLER_FINISHED on cache-miss (don't forget to reopen the file)
+ * - HANDLER_ERROR on stat() failed -> see errno for problem
+ */
+
+handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_cache_entry **ret_sce) {
+#ifdef HAVE_FAM_H
+ fam_dir_entry *fam_dir = NULL;
+ int dir_ndx = -1;
+ splay_tree *dir_node = NULL;
+#endif
+ stat_cache_entry *sce = NULL;
+ stat_cache *sc;
+ struct stat st;
+ size_t k;
+ int fd;
+#ifdef DEBUG_STAT_CACHE
+ size_t i;
+#endif
+
+ int file_ndx;
+ splay_tree *file_node = NULL;
+
+ *ret_sce = NULL;
+
+ /*
+ * check if the directory for this file has changed
+ */
+
+ sc = srv->stat_cache;
+
+ file_ndx = hashme(name);
+ sc->files = splaytree_splay(sc->files, file_ndx);
+
+#ifdef DEBUG_STAT_CACHE
+ for (i = 0; i < ctrl.used; i++) {
+ if (ctrl.ptr[i] == file_ndx) break;
+ }
+#endif
+
+ if (sc->files && (sc->files->key == file_ndx)) {
+#ifdef DEBUG_STAT_CACHE
+ /* it was in the cache */
+ assert(i < ctrl.used);
+#endif
+
+ /* we have seen this file already and
+ * don't stat() it again in the same second */
+
+ file_node = sc->files;
+
+ sce = file_node->data;
+
+ /* check if the name is the same, we might have a collision */
+
+ if (buffer_is_equal(name, sce->name)) {
+ if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) {
+ if (sce->stat_ts == srv->cur_ts) {
+ *ret_sce = sce;
+ return HANDLER_GO_ON;
+ }
+ }
+ } else {
+ /* oops, a collision,
+ *
+ * file_node is used by the FAM check below to see if we know this file
+ * and if we can save a stat().
+ *
+ * BUT, the sce is not reset here as the entry into the cache is ok, we
+ * it is just not pointing to our requested file.
+ *
+ * */
+
+ file_node = NULL;
+ }
+ } else {
+#ifdef DEBUG_STAT_CACHE
+ if (i != ctrl.used) {
+ fprintf(stderr, "%s.%d: %08x was already inserted but not found in cache, %s\n", __FILE__, __LINE__, file_ndx, name->ptr);
+ }
+ assert(i == ctrl.used);
+#endif
+ }
+
+#ifdef HAVE_FAM_H
+ /* dir-check */
+ if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) {
+ if (0 != buffer_copy_dirname(sc->dir_name, name)) {
+ SEGFAULT();
+ }
+
+ dir_ndx = hashme(sc->dir_name);
+
+ sc->dirs = splaytree_splay(sc->dirs, dir_ndx);
+
+ if (sc->dirs && (sc->dirs->key == dir_ndx)) {
+ dir_node = sc->dirs;
+ }
+
+ if (dir_node && file_node) {
+ /* we found a file */
+
+ sce = file_node->data;
+ fam_dir = dir_node->data;
+
+ if (fam_dir->version == sce->dir_version) {
+ /* the stat()-cache entry is still ok */
+
+ *ret_sce = sce;
+ return HANDLER_GO_ON;
+ }
+ }
+ }
+#endif
+
+ /*
+ * *lol*
+ * - open() + fstat() on a named-pipe results in a (intended) hang.
+ * - stat() if regualar file + open() to see if we can read from it is better
+ *
+ * */
+
+ if (-1 == stat(name->ptr, &st)) {
+ return HANDLER_ERROR;
+ }
+
+
+ if (S_ISREG(st.st_mode)) {
+ /* try to open the file to check if we can read it */
+ if (-1 == (fd = open(name->ptr, O_RDONLY))) {
+ return HANDLER_ERROR;
+ }
+ close(fd);
+ }
+
+ if (NULL == sce) {
+ int osize = 0;
+
+ if (sc->files) {
+ osize = sc->files->size;
+ }
+
+ sce = stat_cache_entry_init();
+ buffer_copy_string_buffer(sce->name, name);
+
+ sc->files = splaytree_insert(sc->files, file_ndx, sce);
+#ifdef DEBUG_STAT_CACHE
+ if (ctrl.size == 0) {
+ ctrl.size = 16;
+ ctrl.used = 0;
+ ctrl.ptr = malloc(ctrl.size * sizeof(*ctrl.ptr));
+ } else if (ctrl.size == ctrl.used) {
+ ctrl.size += 16;
+ ctrl.ptr = realloc(ctrl.ptr, ctrl.size * sizeof(*ctrl.ptr));
+ }
+
+ ctrl.ptr[ctrl.used++] = file_ndx;
+
+ assert(sc->files);
+ assert(sc->files->data == sce);
+ assert(osize + 1 == splaytree_size(sc->files));
+#endif
+ }
+
+ sce->st = st;
+ sce->stat_ts = srv->cur_ts;
+
+ /* catch the obvious symlinks
+ *
+ * this is not a secure check as we still have a race-condition between
+ * the stat() and the open. We can only solve this by
+ * 1. open() the file
+ * 2. fstat() the fd
+ *
+ * and keeping the file open for the rest of the time. But this can
+ * only be done at network level.
+ *
+ * */
+ if (S_ISLNK(st.st_mode) && !con->conf.follow_symlink) {
+ return HANDLER_ERROR;
+ }
+
+ if (S_ISREG(st.st_mode)) {
+ /* determine mimetype */
+ buffer_reset(sce->content_type);
+
+ for (k = 0; k < con->conf.mimetypes->used; k++) {
+ data_string *ds = (data_string *)con->conf.mimetypes->data[k];
+ buffer *type = ds->key;
+
+ if (type->used == 0) continue;
+
+ /* check if the right side is the same */
+ if (type->used > name->used) continue;
+
+ if (0 == strncasecmp(name->ptr + name->used - type->used, type->ptr, type->used - 1)) {
+ buffer_copy_string_buffer(sce->content_type, ds->value);
+ break;
+ }
+ }
+ etag_create(sce->etag, &(sce->st));
+#ifdef HAVE_XATTR
+ if (buffer_is_empty(sce->content_type)) {
+ stat_cache_attr_get(sce->content_type, name->ptr);
+ }
+#endif
+ }
+
+#ifdef HAVE_FAM_H
+ if (sc->fam &&
+ (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM)) {
+ /* is this directory already registered ? */
+ if (!dir_node) {
+ fam_dir = fam_dir_entry_init();
+ fam_dir->fc = sc->fam;
+
+ buffer_copy_string_buffer(fam_dir->name, sc->dir_name);
+
+ fam_dir->version = 1;
+
+ fam_dir->req = calloc(1, sizeof(FAMRequest));
+
+ if (0 != FAMMonitorDirectory(sc->fam, fam_dir->name->ptr,
+ fam_dir->req, fam_dir)) {
+
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "monitoring dir failed:",
+ fam_dir->name,
+ FamErrlist[FAMErrno]);
+
+ fam_dir_entry_free(fam_dir);
+ } else {
+ int osize = 0;
+
+ if (sc->dirs) {
+ osize = sc->dirs->size;
+ }
+
+ sc->dirs = splaytree_insert(sc->dirs, dir_ndx, fam_dir);
+ assert(sc->dirs);
+ assert(sc->dirs->data == fam_dir);
+ assert(osize == (sc->dirs->size - 1));
+ }
+ } else {
+ fam_dir = dir_node->data;
+ }
+
+ /* bind the fam_fc to the stat() cache entry */
+
+ if (fam_dir) {
+ sce->dir_version = fam_dir->version;
+ sce->dir_ndx = dir_ndx;
+ }
+ }
+#endif
+
+ *ret_sce = sce;
+
+ return HANDLER_GO_ON;
+}
+
+/**
+ * remove stat() from cache which havn't been stat()ed for
+ * more than 10 seconds
+ *
+ *
+ * walk though the stat-cache, collect the ids which are too old
+ * and remove them in a second loop
+ */
+
+static int stat_cache_tag_old_entries(server *srv, splay_tree *t, int *keys, size_t *ndx) {
+ stat_cache_entry *sce;
+
+ if (!t) return 0;
+
+ stat_cache_tag_old_entries(srv, t->left, keys, ndx);
+ stat_cache_tag_old_entries(srv, t->right, keys, ndx);
+
+ sce = t->data;
+
+ if (srv->cur_ts - sce->stat_ts > 2) {
+ keys[(*ndx)++] = t->key;
+ }
+
+ return 0;
+}
+
+int stat_cache_trigger_cleanup(server *srv) {
+ stat_cache *sc;
+ size_t max_ndx = 0, i;
+ int *keys;
+
+ sc = srv->stat_cache;
+
+ if (!sc->files) return 0;
+
+ keys = calloc(1, sizeof(size_t) * sc->files->size);
+
+ stat_cache_tag_old_entries(srv, sc->files, keys, &max_ndx);
+
+ for (i = 0; i < max_ndx; i++) {
+ int ndx = keys[i];
+ splay_tree *node;
+
+ sc->files = splaytree_splay(sc->files, ndx);
+
+ node = sc->files;
+
+ if (node && (node->key == ndx)) {
+#ifdef DEBUG_STAT_CACHE
+ size_t j;
+ int osize = splaytree_size(sc->files);
+ stat_cache_entry *sce = node->data;
+#endif
+ stat_cache_entry_free(node->data);
+ sc->files = splaytree_delete(sc->files, ndx);
+
+#ifdef DEBUG_STAT_CACHE
+ for (j = 0; j < ctrl.used; j++) {
+ if (ctrl.ptr[j] == ndx) {
+ ctrl.ptr[j] = ctrl.ptr[--ctrl.used];
+ break;
+ }
+ }
+
+ assert(osize - 1 == splaytree_size(sc->files));
+#endif
+ }
+ }
+
+ free(keys);
+
+ return 0;
+}
diff --git a/src/stat_cache.h b/src/stat_cache.h
new file mode 100644
index 0000000..e6f7796
--- /dev/null
+++ b/src/stat_cache.h
@@ -0,0 +1,13 @@
+#ifndef _FILE_CACHE_H_
+#define _FILE_CACHE_H_
+
+#include "base.h"
+
+stat_cache *stat_cache_init(void);
+void stat_cache_free(stat_cache *fc);
+
+handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_cache_entry **fce);
+handler_t stat_cache_handle_fdevent(void *_srv, void *_fce, int revent);
+
+int stat_cache_trigger_cleanup(server *srv);
+#endif
diff --git a/src/stream.c b/src/stream.c
new file mode 100644
index 0000000..ecaadc1
--- /dev/null
+++ b/src/stream.c
@@ -0,0 +1,106 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "stream.h"
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sys-mmap.h"
+
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+int stream_open(stream *f, buffer *fn) {
+ struct stat st;
+#ifdef HAVE_MMAP
+ int fd;
+#elif defined __WIN32
+ HANDLE *fh, *mh;
+ void *p;
+#endif
+
+ f->start = NULL;
+
+ if (-1 == stat(fn->ptr, &st)) {
+ return -1;
+ }
+
+ f->size = st.st_size;
+
+#ifdef HAVE_MMAP
+ if (-1 == (fd = open(fn->ptr, O_RDONLY | O_BINARY))) {
+ return -1;
+ }
+
+ f->start = mmap(0, f->size, PROT_READ, MAP_SHARED, fd, 0);
+
+ close(fd);
+
+ if (MAP_FAILED == f->start) {
+ return -1;
+ }
+
+#elif defined __WIN32
+ fh = CreateFile(fn->ptr,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_READONLY,
+ NULL);
+
+ if (!fh) return -1;
+
+ mh = CreateFileMapping( fh,
+ NULL,
+ PAGE_READONLY,
+ (sizeof(off_t) > 4) ? f->size >> 32 : 0,
+ f->size & 0xffffffff,
+ NULL);
+
+ if (!mh) {
+ LPVOID lpMsgBuf;
+ FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &lpMsgBuf,
+ 0, NULL );
+
+ return -1;
+ }
+
+ p = MapViewOfFile(mh,
+ FILE_MAP_READ,
+ 0,
+ 0,
+ 0);
+ CloseHandle(mh);
+ CloseHandle(fh);
+
+ f->start = p;
+#else
+# error no mmap found
+#endif
+
+ return 0;
+}
+
+int stream_close(stream *f) {
+#ifdef HAVE_MMAP
+ if (f->start) munmap(f->start, f->size);
+#elif defined(__WIN32)
+ if (f->start) UnmapViewOfFile(f->start);
+#endif
+
+ f->start = NULL;
+
+ return 0;
+}
diff --git a/src/stream.h b/src/stream.h
new file mode 100644
index 0000000..d4c9049
--- /dev/null
+++ b/src/stream.h
@@ -0,0 +1,14 @@
+#ifndef _STREAM_H_
+#define _STREAM_H_
+
+#include "buffer.h"
+
+typedef struct {
+ char *start;
+ off_t size;
+} stream;
+
+int stream_open(stream *f, buffer *fn);
+int stream_close(stream *f);
+
+#endif
diff --git a/src/sys-mmap.h b/src/sys-mmap.h
new file mode 100644
index 0000000..94aaa19
--- /dev/null
+++ b/src/sys-mmap.h
@@ -0,0 +1,24 @@
+#ifndef WIN32_MMAP_H
+#define WIN32_MMAP_H
+
+#ifdef __WIN32
+
+#define MAP_FAILED -1
+#define PROT_SHARED 0
+#define MAP_SHARED 0
+#define PROT_READ 0
+
+#define mmap(a, b, c, d, e, f) (-1)
+#define munmap(a, b) (-1)
+
+#include <windows.h>
+
+#else
+#include <sys/mman.h>
+
+#ifndef MAP_FAILED
+#define MAP_FAILED -1
+#endif
+#endif
+
+#endif
diff --git a/src/sys-socket.h b/src/sys-socket.h
new file mode 100644
index 0000000..cc6e649
--- /dev/null
+++ b/src/sys-socket.h
@@ -0,0 +1,24 @@
+#ifndef WIN32_SOCKET_H
+#define WIN32_SOCKET_H
+
+#ifdef __WIN32
+
+#include <winsock2.h>
+
+#define ECONNRESET WSAECONNRESET
+#define EINPROGRESS WSAEINPROGRESS
+#define EALREADY WSAEALREADY
+#define ioctl ioctlsocket
+#define hstrerror(x) ""
+#else
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+
+#include <netdb.h>
+#endif
+
+#endif