summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Kneschke <jan@kneschke.de>2005-02-20 14:27:00 +0000
committerJan Kneschke <jan@kneschke.de>2005-02-20 14:27:00 +0000
commitbcdc6a3bbcde8e66da41aa2311642e53f4fc7c9b (patch)
treea0536d23ba17a40c236fc3cd2a4a133110ae7501
downloadlighttpd-git-bcdc6a3bbcde8e66da41aa2311642e53f4fc7c9b.tar.gz
moved everything below trunk/ and added branches/ and tags/
git-svn-id: svn://svn.lighttpd.net/lighttpd/trunk@30 152afb58-edef-0310-8abb-c4023f1b3aa9
-rw-r--r--AUTHORS1
-rw-r--r--COPYING31
-rw-r--r--ChangeLog3839
-rw-r--r--INSTALL32
-rw-r--r--Makefile.am3
-rw-r--r--NEWS316
-rw-r--r--README156
-rw-r--r--config.h.in374
-rw-r--r--configure.in421
-rw-r--r--cygwin/.cvsignore2
-rw-r--r--cygwin/Makefile.am1
-rw-r--r--cygwin/lighttpd.README114
-rw-r--r--cygwin/lighttpd.README.in114
-rw-r--r--cygwin/setup.hint4
-rw-r--r--debian/.cvsignore2
-rw-r--r--debian/Makefile.am27
-rw-r--r--debian/README.Debian.ex6
-rw-r--r--debian/changelog144
-rw-r--r--debian/compat1
-rw-r--r--debian/conffiles0
-rw-r--r--debian/control15
-rw-r--r--debian/copyright38
-rw-r--r--debian/cron.d.ex4
-rw-r--r--debian/dirs2
-rw-r--r--debian/docs3
-rw-r--r--debian/emacsen-install.ex45
-rw-r--r--debian/emacsen-remove.ex15
-rw-r--r--debian/emacsen-startup.ex19
-rw-r--r--debian/init.d78
-rw-r--r--debian/lighttpd-default.ex10
-rw-r--r--debian/lighttpd.conf232
-rw-r--r--debian/lighttpd.doc-base.EX22
-rw-r--r--debian/lighttpd.logrotate18
-rw-r--r--debian/lighttpd.postinst42
-rw-r--r--debian/manpage.1.ex60
-rw-r--r--debian/manpage.sgml.ex156
-rw-r--r--debian/manpage.xml.ex148
-rw-r--r--debian/menu.ex2
-rw-r--r--debian/postrm.ex38
-rw-r--r--debian/preinst.ex44
-rw-r--r--debian/prerm.ex39
-rwxr-xr-xdebian/rules117
-rw-r--r--debian/watch.ex6
-rw-r--r--distribute.sh.in92
-rw-r--r--doc/.cvsignore2
-rw-r--r--doc/Makefile.am84
-rw-r--r--doc/access.txt41
-rw-r--r--doc/accesslog.txt126
-rw-r--r--doc/alias.txt36
-rw-r--r--doc/authentification.txt191
-rw-r--r--doc/cgi.txt50
-rw-r--r--doc/compress.txt66
-rw-r--r--doc/configuration.txt351
-rw-r--r--doc/fastcgi-state.dot6
-rw-r--r--doc/fastcgi-state.txt51
-rw-r--r--doc/fastcgi.txt600
-rw-r--r--doc/features.txt116
-rw-r--r--doc/lighttpd.118
-rw-r--r--doc/lighttpd.conf264
-rw-r--r--doc/lighttpd.user1
-rw-r--r--doc/mysqlvhost.txt86
-rw-r--r--doc/performance.txt198
-rw-r--r--doc/plugins.txt260
-rw-r--r--doc/proxy.txt72
-rwxr-xr-xdoc/rc.lighttpd169
-rwxr-xr-xdoc/rc.lighttpd.redhat87
-rw-r--r--doc/redirect.txt36
-rw-r--r--doc/rewrite.txt42
-rwxr-xr-xdoc/rrdtool-graph.sh38
-rw-r--r--doc/rrdtool.txt108
-rw-r--r--doc/secdownload.txt142
-rw-r--r--doc/security.txt60
-rw-r--r--doc/setenv.txt37
-rw-r--r--doc/simple-vhost.txt73
-rw-r--r--doc/skeleton.txt29
-rwxr-xr-xdoc/spawn-php.sh54
-rw-r--r--doc/ssi.txt76
-rw-r--r--doc/ssl.txt58
-rw-r--r--doc/state.dot18
-rw-r--r--doc/state.txt170
-rw-r--r--doc/status.txt35
-rw-r--r--doc/sysconfig.lighttpd1
-rw-r--r--doc/traffic-shaping.txt55
-rw-r--r--doc/userdir.txt54
-rw-r--r--lighttpd.spec.in84
-rw-r--r--openwrt/.cvsignore2
-rw-r--r--openwrt/Makefile.am1
-rwxr-xr-xopenwrt/S51lighttpd4
-rw-r--r--openwrt/conffiles1
-rw-r--r--openwrt/control9
-rw-r--r--openwrt/control.in9
-rw-r--r--openwrt/lighttpd.conf231
-rw-r--r--openwrt/lighttpd.mk72
-rw-r--r--openwrt/lighttpd.mk.in72
-rw-r--r--src/.cvsignore11
-rw-r--r--src/Makefile.am221
-rw-r--r--src/array.c241
-rw-r--r--src/array.h120
-rw-r--r--src/base.h522
-rw-r--r--src/bitset.c66
-rw-r--r--src/bitset.h19
-rw-r--r--src/buffer.c1106
-rw-r--r--src/buffer.h122
-rw-r--r--src/chunk.c312
-rw-r--r--src/chunk.h56
-rw-r--r--src/config.c1025
-rw-r--r--src/configfile.h18
-rw-r--r--src/configparser.h19
-rw-r--r--src/configparser.y164
-rw-r--r--src/connections.c1585
-rw-r--r--src/connections.h19
-rw-r--r--src/crc32.c84
-rw-r--r--src/crc32.h6
-rw-r--r--src/data_array.c56
-rw-r--r--src/data_config.c69
-rw-r--r--src/data_count.c56
-rw-r--r--src/data_fastcgi.c56
-rw-r--r--src/data_integer.c53
-rw-r--r--src/data_string.c92
-rw-r--r--src/etag.c30
-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.c194
-rw-r--r--src/fdevent_linux_rtsig.c260
-rw-r--r--src/fdevent_linux_sysepoll.c152
-rw-r--r--src/fdevent_poll.c182
-rw-r--r--src/fdevent_select.c127
-rw-r--r--src/fdevent_solaris_devpoll.c141
-rw-r--r--src/file_cache.c483
-rw-r--r--src/file_cache.h12
-rw-r--r--src/http_auth.c939
-rw-r--r--src/http_auth.h64
-rw-r--r--src/http_auth_digest.c103
-rw-r--r--src/http_auth_digest.h46
-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.c352
-rw-r--r--src/keyvalue.h80
-rw-r--r--src/lemon.c4397
-rw-r--r--src/lempar.c687
-rw-r--r--src/log.c238
-rw-r--r--src/log.h13
-rw-r--r--src/md5.c356
-rw-r--r--src/md5.h37
-rw-r--r--src/md5_global.h31
-rw-r--r--src/mod_access.c177
-rw-r--r--src/mod_accesslog.c840
-rw-r--r--src/mod_alias.c185
-rw-r--r--src/mod_auth.c565
-rw-r--r--src/mod_auth.h0
-rw-r--r--src/mod_cgi.c1191
-rw-r--r--src/mod_compress.c744
-rw-r--r--src/mod_evhost.c340
-rw-r--r--src/mod_expire.c356
-rw-r--r--src/mod_fastcgi.c3209
-rw-r--r--src/mod_mysql_vhost.c425
-rw-r--r--src/mod_proxy.c1093
-rw-r--r--src/mod_redirect.c271
-rw-r--r--src/mod_rewrite.c453
-rw-r--r--src/mod_rrdtool.c457
-rw-r--r--src/mod_secure_download.c323
-rw-r--r--src/mod_setenv.c215
-rw-r--r--src/mod_simple_vhost.c284
-rw-r--r--src/mod_skeleton.c220
-rw-r--r--src/mod_ssi.c1073
-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.h12
-rw-r--r--src/mod_ssi_exprparser.y119
-rw-r--r--src/mod_status.c653
-rw-r--r--src/mod_userdir.c248
-rw-r--r--src/mod_usertrack.c247
-rw-r--r--src/network.c517
-rw-r--r--src/network.h13
-rw-r--r--src/network_backends.h54
-rw-r--r--src/network_freebsd_sendfile.c192
-rw-r--r--src/network_linux_sendfile.c205
-rw-r--r--src/network_openssl.c176
-rw-r--r--src/network_solaris_sendfilev.c208
-rw-r--r--src/network_write.c188
-rw-r--r--src/network_writev.c288
-rw-r--r--src/plugin.c433
-rw-r--r--src/plugin.h91
-rw-r--r--src/request.c937
-rw-r--r--src/request.h9
-rw-r--r--src/response.c1432
-rw-r--r--src/response.h18
-rw-r--r--src/server.c1021
-rw-r--r--src/server.h17
-rw-r--r--src/settings.h46
-rw-r--r--src/spawn-fcgi.c346
-rw-r--r--src/stream.c100
-rw-r--r--src/stream.h14
-rw-r--r--src/sys-mmap.h24
-rw-r--r--src/sys-socket.h24
-rw-r--r--tests/.cvsignore5
-rw-r--r--tests/Makefile.am128
-rwxr-xr-xtests/accessdeny-01.sh16
-rwxr-xr-xtests/auth-01.sh16
-rwxr-xr-xtests/auth-02.sh17
-rwxr-xr-xtests/auth-03.sh17
-rwxr-xr-xtests/basic-01.sh16
-rwxr-xr-xtests/basic-02.sh18
-rwxr-xr-xtests/basic-03.sh17
-rwxr-xr-xtests/basic-05.sh17
-rwxr-xr-xtests/basic-06.sh16
-rwxr-xr-xtests/basic-07.sh23
-rwxr-xr-xtests/basic-08.sh16
-rwxr-xr-xtests/basic-09.sh16
-rwxr-xr-xtests/basic-10.sh21
-rwxr-xr-xtests/basic-11.sh21
-rwxr-xr-xtests/broken-header-01.sh18
-rwxr-xr-xtests/broken-key-01.sh17
-rwxr-xr-xtests/broken-key-02.sh17
-rwxr-xr-xtests/broken-key-03.sh17
-rwxr-xr-xtests/broken-key-04.sh18
-rwxr-xr-xtests/bug-03.sh26
-rw-r--r--tests/bug-06.conf164
-rwxr-xr-xtests/bug-06.sh26
-rw-r--r--tests/bug-12.conf166
-rwxr-xr-xtests/bug-12.sh27
-rwxr-xr-xtests/bug-14.sh18
-rwxr-xr-xtests/bug-15-2.sh27
-rwxr-xr-xtests/bug-15-3.sh26
-rwxr-xr-xtests/bug-15.sh27
-rwxr-xr-xtests/bug-urldecode-00.sh18
-rwxr-xr-xtests/cgi-01.sh16
-rwxr-xr-xtests/cgi-02.sh17
-rwxr-xr-xtests/cgi-03.sh17
-rwxr-xr-xtests/cleanup.sh14
-rwxr-xr-xtests/compress-01.sh18
-rwxr-xr-xtests/compress-02.sh19
-rwxr-xr-xtests/compress-03.sh18
-rwxr-xr-xtests/compress-04.sh19
-rwxr-xr-xtests/conformance.pl164
-rwxr-xr-xtests/content-01.sh32
-rwxr-xr-xtests/content-02.sh32
-rwxr-xr-xtests/content-03.sh32
-rwxr-xr-xtests/content-04.sh36
-rwxr-xr-xtests/content-length-01.sh18
-rwxr-xr-xtests/content-length-02.sh18
-rwxr-xr-xtests/content-length-03.sh18
-rwxr-xr-xtests/content-length-04.sh18
-rwxr-xr-xtests/content-length-05.sh18
-rwxr-xr-xtests/continue-01.sh18
-rw-r--r--tests/docroot/123/12345.html1
-rw-r--r--tests/docroot/123/12345.txt1
-rw-r--r--tests/docroot/123/Makefile.am1
-rw-r--r--tests/docroot/123/dummyfile.bla1
-rw-r--r--tests/docroot/123/phpinfo.php1
-rw-r--r--tests/docroot/Makefile.am1
-rw-r--r--tests/docroot/www/Makefile.am4
-rw-r--r--tests/docroot/www/cgi-pathinfo.pl7
-rwxr-xr-xtests/docroot/www/cgi.php9
-rw-r--r--tests/docroot/www/cgi.pl7
-rw-r--r--tests/docroot/www/go/Makefile.am1
-rwxr-xr-xtests/docroot/www/go/cgi.php9
-rw-r--r--tests/docroot/www/index.html113
-rw-r--r--tests/docroot/www/index.txt113
-rw-r--r--tests/docroot/www/indexfile/Makefile.am1
-rw-r--r--tests/docroot/www/indexfile/index.php1
-rw-r--r--tests/docroot/www/indexfile/return-404.php5
-rwxr-xr-xtests/docroot/www/nph-status.pl4
-rw-r--r--tests/docroot/www/phphost.php3
-rw-r--r--tests/docroot/www/phpinfo.php1
-rw-r--r--tests/docroot/www/phpself.php3
-rw-r--r--tests/docroot/www/redirect.php4
-rwxr-xr-xtests/fastcgi-01.sh25
-rwxr-xr-xtests/fastcgi-02.sh24
-rwxr-xr-xtests/fastcgi-03.sh25
-rwxr-xr-xtests/fastcgi-04.sh27
-rwxr-xr-xtests/fastcgi-05.sh27
-rwxr-xr-xtests/fastcgi-06.sh27
-rwxr-xr-xtests/fastcgi-07.sh27
-rwxr-xr-xtests/fastcgi-08.sh27
-rwxr-xr-xtests/fastcgi-09.sh27
-rw-r--r--tests/fastcgi-10.conf156
-rwxr-xr-xtests/fastcgi-10.sh27
-rw-r--r--tests/fastcgi-11.conf164
-rwxr-xr-xtests/fastcgi-11.sh19
-rw-r--r--tests/fastcgi-12.conf164
-rwxr-xr-xtests/fastcgi-12.sh19
-rw-r--r--tests/fastcgi-13.conf156
-rwxr-xr-xtests/fastcgi-13.sh26
-rw-r--r--tests/fcgi-auth.c26
-rwxr-xr-xtests/head-01.sh26
-rwxr-xr-xtests/host-01.sh17
-rwxr-xr-xtests/host-02.sh16
-rwxr-xr-xtests/host-03.sh17
-rwxr-xr-xtests/host-04.sh17
-rwxr-xr-xtests/host-05.sh17
-rwxr-xr-xtests/http11-01.sh17
-rwxr-xr-xtests/http11-02.sh18
-rwxr-xr-xtests/http11-03.sh16
-rwxr-xr-xtests/large-header-01.sh25
-rwxr-xr-xtests/leak-01.sh17
-rwxr-xr-xtests/leak-02.sh17
-rwxr-xr-xtests/leak-03.sh17
-rwxr-xr-xtests/leak-04.sh17
-rwxr-xr-xtests/leak-05.sh17
-rwxr-xr-xtests/leak-06.sh17
-rwxr-xr-xtests/leak-07.sh17
-rwxr-xr-xtests/leak-08.sh17
-rwxr-xr-xtests/leak-09.sh17
-rwxr-xr-xtests/leak-10.sh17
-rwxr-xr-xtests/leak-11.sh17
-rwxr-xr-xtests/leak-12.sh17
-rwxr-xr-xtests/leak-13.sh17
-rwxr-xr-xtests/leak-14.sh17
-rwxr-xr-xtests/leak-15.sh17
-rwxr-xr-xtests/leak-16.sh17
-rwxr-xr-xtests/leak-17.sh17
-rw-r--r--tests/lighttpd.conf166
-rw-r--r--tests/lighttpd.user1
-rwxr-xr-xtests/missing-01.sh16
-rwxr-xr-xtests/missing-02.sh16
-rwxr-xr-xtests/pathinfo-01.sh24
-rwxr-xr-xtests/pathinfo-02.sh24
-rwxr-xr-xtests/post-01.sh16
-rwxr-xr-xtests/post-02.sh18
-rwxr-xr-xtests/prepare.sh35
-rwxr-xr-xtests/redirect-01.sh17
-rwxr-xr-xtests/redirect-02.sh17
-rwxr-xr-xtests/redirect-03.sh17
-rw-r--r--tests/testbase.sh63
-rwxr-xr-xtests/wrapper.sh8
333 files changed, 51339 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 00000000..80ea411a
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+jan kneschke <jan@kneschke.de>
diff --git a/COPYING b/COPYING
new file mode 100644
index 00000000..3f699760
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,31 @@
+
+
+Copyright (c) 2004, Jan Kneschke, incremental
+ All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+- Neither the name of the 'incremental' nor the names of its contributors may
+ be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 00000000..9f29ce13
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,3839 @@
+CVS-Version: $Id: ChangeLog,v 1.18 2004/04/09 22:08:19 weigon Exp $
+
+Feature Requests:
+* complex
+- nested conditionals
+- config file per host
+- compressing dynamic content (filters)
+- limit read-queue to 64k and use traffic-shaping cap
+ - if fastcgi/cgi are not ready ignore incoming FDEVENT_IN for a while
+ -> reduce memory usage
+- userdir + suphp
+ -> internal config-hash
+- don't strip // from PATHINFO
+- TLS
+
+* moderate
+- errorlog piping
+- access and auth with host, net
+- fastcgi-spawn
+ - suid spawn
+- gracefull restart
+- mod_accesslog_mysql
+- mod_auth_mysql
+- IPv6 for FastCGI
+- man-page for spawn-fcgi
+- server.base-directory
+
+* simple
+- file-mapping for alias
+- add header for broken requests if debug.request-header-on-error
+- IPv6 for FastCGI
+- hostnames for remote-FastCGI
+- fix remote-fastcgi on freebsd + kqueue
+ - crash FDEVENT_OUT on FreeBSD
+- EPERM on unlink
+- ignore blank lines between requests
+ - handle \r\n at start of request
+- cgi exec via shell
+- added REMOTE_PORT, SERVER_ADDR to cgi/fastcgi
+- REQUEST_URI to cgi
+- don't set physical-file on 416-errors
+- crash in read-post
+- same config option twice -> aaa, aaa
+- remove default port from Host:
+
+13.02.2005 17:33
+- mod_auth
+
+ fix crash if require, realm or method are empty (Bug #5)
+
+13.02.2005 14:52
+- network
+
+ handle EPIPE and ECONNRESET as 'client has closed connection' in writev()
+ (Bug #1)
+
+- macosx
+
+ compile error on MacOS X due to missing environ (patch by Johan Sörensen)
+ (Bug #2)
+
+- indexfiles
+
+ append the detected indexfile only once to uri.path (reported by Thomas
+ Seifert)
+ (Bug #3)
+
+06.02.2005 15:16 - 1.3.10
+- fastcgi
+
+ display a error-message if a hostname if specified in fastcgi.server->host
+ we need an IP here
+
+- debug
+
+ added debug.log-state-handling
+
+- spawn-fcgi
+
+ accept a full commandline for spawning
+
+06.02.2005 12:50
+- fastcgi
+
+ fixed openssl handling
+
+- network_freebsd_sendfilev
+
+ gracefull handling of connections closed on client-side
+ removed debug-message
+
+06.02.2005 01:44 - 1.3.9
+- documentation
+
+ added docs for SSL setup and mod_status
+
+- fastcgi
+
+ fixed config handling on PowerPC for local-spawning
+
+05.02.2005 15:14
+- fastcgi
+
+ added bin-environment to setup the environment of the spawned process
+
+ added bin-copy-environment to copy only the specified set of options
+ from the old environment
+
+ added handling of cmd-line options to bin-path
+
+- setenv
+
+ fixed crashed in setenv.add-response-header
+
+04.02.2005 18:09
+- configure
+
+ fixed docs for --with-mysql
+
+- fastcgi
+
+ improved performance of building the header (drop strlen())
+
+04.02.2005 01:59
+- cgi
+
+ don't send file on error
+
+ check if cgi-handler exists before executing it
+
+ added support for nph-...
+
+02.02.2005 21:18 - pre-release
+- request parsing
+
+ handle invalid characters in URI
+
+02.02.2005 15:12
+- makefiles
+
+ dropped unused header files from the distribution
+
+02.02.2005 14:18
+- fastcgi
+
+ delete sockets on shutdown
+
+- http/1.1
+
+ adding option to disable http/1.1
+
+01.02.2005 12:03
+- cygwin
+
+ fixed plugins_load to use the right extensions again
+ removed mmap check
+ added ssl support
+
+01.02.2005 01:49
+- configure
+
+ make check for valgrind.h covered by --with-valgrind
+
+- mod_localizer, mod_maps
+
+ remove both plugins from the distribution
+
+- file-not-found
+
+ handle file not found again
+
+30.01.2005 16:44
+- HEAD requests
+
+ don't send content on dynamic HEAD requests with status 200
+
+30.01.2005 15:16 - 1.3.8
+- network-handler
+
+ remove debug output on writev() if the remote side closed the connection
+
+- directory index
+
+ handle EACCES correctly
+
+29.01.2005 15:16 - pre-release
+- mod_alias
+
+ fixed mod_alias + pathinfo handling
+
+- mod_accesslog
+
+ added access-log to syslog patch from allan
+
+28.01.2005 17:30
+- directory redirect without Host-header
+
+ use server-ip instead of client-ip for the Location:
+
+- fastcgi + pathinfo
+
+ if fastcgi-auth redirects to a directory which doesn't exist handle it
+ correctly (bug introduced in 1.3.8)
+
+- requesting directories
+
+ clean physical.path if directory is requested and dir-listing is disabled
+ send 403 again (buf introduced in 1.3.8)
+
+28.01.2005 12:08
+- fastcgi
+
+ ignore FDEVENT_HUP for unix-sockets as a simple read + timeout will do
+ the job anyway
+
+22.01.2005 20:28 - pre-release
+- fastcgi
+
+ send content and headers if authorizer mode is used
+
+ use a new connection if connection is died to fastcgi
+ and we have not used it yet
+
+18.01.2005 21:21 - pre-release
+- plugins
+
+ added version-id to plugins to detect plugins which are not up-to-date
+
+16.01.2005 23:11
+- fastcgi
+
+ fixed write-failed after crash of fastcgi-child
+
+16.01.2005 20:43
+- setenv
+
+ fixed setenv.add-environment
+
+- fastcgi
+
+ fixed authorizer + added testcases
+
+16.01.2005 17:40 - pre-release
+- mod_status
+
+ beautified mod_status
+
+- mod_setenv
+
+ added setenv.add-environment
+
+- timeouts
+
+ add timeout to read-post
+
+15.01.2005 12:57
+- debug
+
+ added debug options to log
+ - missing files
+ - request header
+ - response header
+ - request handling
+
+ added a more usefull error message for the status-code changes in the
+ request parser
+
+- server announcement
+
+ set Server: header for dynamic content too
+
+- fastcgi
+
+ fixed double free
+
+ don't crash on FDEVENT_ERR
+
+ added a comment for EAGAIN on connect()
+
+08.01.2005 17:45
+- ssl
+
+ report an error if ssl.engine is enable but no ssl support compiled in
+
+08.01.2005 12:23
+- mod_status
+
+ added request time to the output
+
+ (late changelog) added host and filename to the output (fobax)
+ (late changelog) HTMLalized the output (fobax)
+
+06.01.2005 19:51 - pre-release
+- error-handler
+
+ let the error-handler handle 403 requests too
+
+ make the error-handler setable by a module
+
+- error-pages
+
+ reworked the error-page handling
+
+05.01.2005 13:10
+- keep-alive handling
+
+ made sure that keep-alive is really handled correctly
+
+04.01.2005 17:02
+- mod_setenv
+
+ added a module to added request and response headers on the fly
+
+- error-log
+
+ send error log to syslog() if no errorlogfile is specified (again)
+
+02.01.2005 22:44 - pre-release
+- response handling
+
+ cut of body for status 301, 304 and 205
+
+- buffer
+
+ optimized all _hex functions (Silvan Minghetti)
+
+02.01.2005 20:32
+- fastcgi
+
+ if bin-path is not specified, don't die (bug introduced in the last pre-rel)
+
+- auth
+
+ if userfile is empty don't auth.
+
+02.01.2005 19:06
+- mod_compress
+
+ fixed off by one if cache-dir is not set
+
+02.01.2005 16:10
+- conditional config
+
+ fixed !~ and !=
+
+- buffer
+
+ copy empty buffers correctly
+
+31.12.2004 17:45
+- ipv6 + pidfile
+
+ don't complain if we can't remove the pidfile (Silvan Minghetti)
+
+ remove ipv6 option from the commandline of lighttpd doesn't support
+ ipv6 (Silvan Minghetti)
+
+31.12.2004 15:41 - pre-release
+- kqueue
+
+ simplified event handling (adam)
+
+- fastcgi
+
+ fixed div-by-zero bugs in the adaptive process spawning
+
+- mysql-vhost
+
+ added mysql-vhost (Christer Holgersson)
+
+30.12.2004 19:09
+- fastcgi
+
+ added adaptive spawning of FastCGI processes
+
+- traffic shaping
+
+ added traffic shaping per virtual server
+
+28.12.2004 23:26
+- traffic shaping
+
+ added traffic shaping per connection
+
+25.12.2004 22:58
+- mod_status
+
+ fixed status.url again (Timo)
+
+21.12.2004 11:29
+- configure
+
+ added check for signal and select (compile fix for netbsd 1.4 and 1.5)
+
+11.12.2004 12:38 - 1.3.7
+- fastcgi + php
+
+ retry to connect to another PHP child if one of them dies after
+ connect
+
+- cgi + multipart
+
+ don't transform CONTENT_TYPE to HTTP_CONTENT_TYPE
+
+- debian
+
+ more cleanup, updated changelog, added more deps and suggests
+ (Chris Brown)
+
+10.12.2004 22:33
+- event handler
+
+ fixed crashes in kqueue
+
+10.12.2004 13:57 - pre-release
+- mod_status
+
+ fixed wraparound in total requests and total traffic
+
+- debian
+
+ updated licence and packaging
+
+- security
+
+ call setgroups() to get rid of all groups
+
+- ssl
+
+ handle SSL_shutdown() == 0 correctly
+
+ fixed openssl detection in configure
+
+ fixed handling of chunked encoding
+
+- request handling
+
+ handle Connection: keep-alive correctly (case as not ignored)
+
+21.11.2004 02:39
+- windows
+
+ merged basic native windows port (compiles with mingw)
+
+20.11.2004 18:43
+- conditional
+
+ ported
+ - cgi
+ - secdownload
+ - expire
+ - localizer
+ - usertrack
+ - status
+ - proxy
+
+- server-tag
+
+ Server: ... can now be specified by server.tag = "..."
+
+- spawn-fcgi
+
+ fixed typo in usage text
+
+- ssl
+
+ fixed detection of libs and headers
+
+05.11.2004 16:01
+- fastcgi
+
+ added more usefull error messages
+
+04.11.2004 23:01
+- ssi
+
+ added support for ${...}
+
+03.11.2004 14:51 - 1.3.6
+- fastcgi
+
+ added spawn-fcgi to the distribution
+ added spawn-local-fastcgi yourself ( bin-path )
+
+03.11.2004 11:22
+- accesslog
+
+ don't cycle accesslogs of external processes are used
+
+02.11.2004 15:34
+- fastcgi
+
+ handle END-OF-REQUEST correctly if chunk-encoding is not used
+
+02.11.2004 10:53
+- internal redirects
+
+ fixed handling of query strings in internal redirects for directories
+
+02.11.2004 09:54 - pre-release
+- cgi
+
+ add REMOTE_USER, suppress AUTHORIZATION
+ handle payloads > 4k
+
+- mod_alias
+
+ fixed url checking
+
+- follow-symlink
+
+ fixed config
+
+31.10.2004 11:30 - 1.3.5
+- writev
+
+ fixed seg-fault in debug-message if write() fails and LFS is enabled
+ handle EINTR
+
+- sendfile linux
+
+ handle EINTR
+
+31.10.2004 09:09
+- freebsd
+
+ added missing header in joblist.c
+ fixed test-scripts for zsh
+
+30.10.2004 22:26
+- modules
+
+ added mod_userdir and mod_alias
+ added docs for the new modules
+
+30.10.2004 19:52
+- porting
+
+ added defines for MAP_FAILED for NetBSD 1.3.x
+
+30.10.2004 18:54 - pre-release
+- pipelining
+
+ fixed offset calculations
+
+- ipv6
+
+ IPv6 might be disabled at compile-time
+
+- rewrite
+
+ close mem-leak
+
+- auth
+
+ forgot to reset the global-config handler
+
+- symlink
+
+ add option to disable follow-symlink
+
+- ssi
+
+ added support for exec-cmd
+
+23.10.2004 - 1.3.4
+- max-fds
+
+ set the upper limit of fds only if server.max-fds is set
+
+23.10.2004 13:49
+- accesslog
+
+ use a shell to handle accesslog-pipes
+
+22.10.2004 17:00
+- accesslog
+
+ added logging of user-supplied data via %{...}o and
+ X-LIGHTTPD-* header
+
+22.10.2004 14:57 - pre-release
+- openwrt
+
+ fixed configure-checks and Makefile.am's to build cleanly with a
+ cross-compiler
+
+ builds cleanly for openwrt
+
+22.10.2004 13:03
+- out-of-fd
+
+ improved the out-of-fd handler
+
+- cgi, fastcgi
+
+ set SERVER_NAME to server.name or the value submitted by Host:
+
+- error-handler
+
+ only set old status code if it wasn't set by a handler
+
+21.10.2004 22:36 - pre-release
+- fastcgi
+
+ don't crash on out-of-fd condition
+
+- out-of-fd
+
+ try handle the out-of-fd condition in a sane way
+
+21.10.2004 15:03
+- mod_auth
+
+ seperated auth.backend.*.userfile for plain, htpasswd and htdigest
+
+ added 'digest-auth' against 'plain-backend'
+
+ added auth.debug for debugging
+
+16.10.2004 10:18 - 1.3.3
+- mod_simple_vhost, mod_evhost
+
+ conditional-ized
+
+- mod_rrdtool
+
+ maintain the request-counter for each conditional-config (adam)
+
+14.10.2004 11:30
+- accesslogs
+
+ cycle all access-logs
+
+- mod_rewrite
+
+ tell the user to install pcre.h if he wants to use mod_rewrite
+
+10.10.2004 10:11 - pre-release
+- error-handler
+
+ added a error-handler for status 404 (server.error-handler-404)
+
+09.10.2004 16:28 - pre-release
+- cgi
+
+ added support for \n in headers
+
+- mod_auth
+
+ added conditional auth
+
+01.10.2004 09:28
+- plugins
+
+ fixed off by one error in plugin initialization (Mike)
+ related into a segfault on AMD64
+
+30.09.2004 21:44 - 1.3.2
+- file-cache
+
+ disabled the file-cache it was taken the wrong files from the cache
+
+30.09.2004 08:39 - 1.3.1
+- file-cache
+
+ drop a unused file-cache entry after 10 seconds
+ reuse unused entries
+
+- request-parser
+
+ accept IPv6 adresses in Host header
+
+- tests
+
+ modified the scripts to work with zsh (check on Linux, Irix and FreeBSD)
+
+26.09.2004 12:28
+- comparission function
+
+ file-cache has delivering the wrong entry if only the last character of
+ the filename differed and the filesize was the same.
+
+- cgi + cygwin
+
+ cgi need s SYSTEMROOT environment
+
+
+22.09.2004 08:55
+- network
+
+ detect of file has been shrinked while we are sending it out and terminate
+ the connection if would run over the edge
+
+22.09.2004 07:56
+- mod rewrite, fastcgi, ...
+
+ keep REQUEST_URI after rewrite
+
+21.09.2004 22:49
+- fastcgi authorizer
+
+ fixed cleanup code (matt)
+
+21.09.2004 20:08
+- rrdtool
+
+ rrdtool.db-name is now conditional
+
+ fixed check if write() failed (adam)
+
+17.09.2004 17:50 - 1.3.0
+- rewrite
+
+ added url.rewrite-final = ...
+
+17.09.2004 15:55
+- code cleanup
+
+ integrated the fixes from cygwin into the main tree
+
+- kqueue
+
+ init kqueue after daemonizing (broken since 12.09.2004 14:02)
+
+16.09.2004 21:00
+- cygwin + macosx
+
+ finished the cygwin port
+ this port seems to fix the problems on macosx too
+
+12.09.2004 14:02
+- socket handling
+
+ added support to handle more than one server socket
+
+11.09.2004 12:23 - 1.2.8
+- EINTR
+
+ handle EINTR for linux-sendfile
+
+- configfile
+
+ ignore an extra comma at the end of the array declaration
+
+11.09.2004 09:46
+- mod_proxy
+
+ pass remote-addr as X-Forwarded-For to the real server behind the proxy
+
+- code cleanup
+
+ moved all cut'n'paste versions of the inet_ntop cache to inet_ntop_cache.c
+
+- fcgi
+
+ don't overwrite the fd in fcgi_establish connection if connect fails. this
+ results in various problem in other places.
+
+05.09.2004 09:46
+- file-cache
+
+ cache the mimetype
+
+- last-modified
+
+ don't complain if the If-Modified-Since contains a valid comment/option
+ like <timestamp>; length = ...
+
+05.09.2004 09:13
+- expires
+
+ overwrite the Expire if it is set by a previous plugin
+
+- conditional config
+
+ conditional config as disabled in 1.2.7 by accident
+
+04.09.2004 10:02 - 1.2.7
+- mod-proxy
+
+ remove the \0 before the post content
+
+- cgi
+
+ fixed hanging process if cgi-crash terminates to fast (before we read its
+ response)
+
+- extented attributes
+
+ added xattr support, submitted by Ari
+
+29.08.2004 16:00
+- rrdtool
+
+ moved the rrdtool support from mod_status into its own module mod_rrdtool
+
+ rrdtool.binary = "/usr/bin/rrdtool"
+ rrdtool.db-name = "/var/www/lighttpd.rrd"
+
+29.08.2004 11:00 - pre-release
+- timeouts
+
+ server.max-keep-alive-requests = 0 replaces
+ server.use-keep-alive = "disable"
+
+ added
+ server.max-keep-alive-idle
+ server.max-read-idle
+ server.max-write-idle
+
+- docs
+
+ added a entry for each config-value into configuration.txt
+ added simple docs for
+ rewrite
+ redirect
+ compress
+ cgi
+ simple-vhost
+
+29.08.2004 10:05
+- config options
+
+ complain if no configfile is specified
+
+- fastcgi
+
+ removed stupid allocation bug which might cause a problem in really rare
+ cases
+
+26.08.2004 22:06 - 1.2.6
+- optimize
+
+ use array_strcasecmp() in favour of strcasecmp() as it is slightly
+ faster.
+
+ apply the case-insentive conversion also on the last character. (adam)
+
+ sort the checked elements in request.c and filter apply the logic to
+ compare some less fields, if the header is not used.
+
+ improved the config-patch function to use our internal buffer-compare
+ functions instead of strcmp
+
+22.08.2004 16:09 - pre-release
+- cgi
+
+ added missing cleanup code
+
+- fastcgi
+
+ remove double-free
+ added handling of EINTR in some places
+
+- leaks
+
+ fixed some leaks in the new config code
+
+- array_strcasecmp
+
+ fixed alignment in the improved array_strcasecmp function (adam)
+
+20.08.2004 14:46 - pre-release
+- performance
+
+ optimized a few useless strlen() away as we either know the length from
+ buffer->used - 1 or by sizeof(str) - 1 if it is constant.
+
+ optimized the 'find the \r\n\r\n' function.
+
+ improved the array_strcasecmp() based on another idea from (ralf)
+
+- accesslog
+
+ enabled the strftime cache again
+
+15.08.2004 23:41
+- accesslog
+
+ added apache-like CustomLog handling in accesslog.format
+
+ accesslog.format = "..."
+
+15.08.2004 21:08
+- test-cases
+
+ remove testdir
+
+- configfiles
+
+ handle escaping of " in strings properly
+
+13.08.2004 12:07
+- array
+
+ improved inner-loop of array_strcasecmp() (ralf)
+
+11.08.2004 14:14
+- fcgi socket
+
+ use SUN_LEN if available
+
+- keep-alive
+
+ disable keep-alive on request
+
+ server.use-keep-alive = "disable"
+
+10.08.2004 15:59 - 1.2.5
+- conditional config
+
+ mod_fastcgi
+ mod_rewrite
+ mod_redirect
+ mod_access
+ mod_compress
+ mod_accesslog
+
+ are ported
+
+10.08.2004 13:05
+- pipelining
+
+ fixed very stupid pipelining bug
+
+09.08.2004 22:07 - pre-release
+- conditional config
+
+ first code for conditional config
+
+09.08.2004 14:21
+- fcgi
+
+ fixed access to free()'d memory (doesn't create any harm)
+
+- isdigit, warings
+
+ signed -> unsigned for 2 more isdigit() calls (adam)
+ removed some unused var's if pcre is not available (adam)
+
+08.08.2004 20:57 - pre-release
+- debian
+
+ added a chmod to /var/log/lighttpd/ (allan)
+
+08.08.2004 12:05
+- kqueue
+
+ use EV_SET() instead of setting the ev-struct by hand (adam)
+
+- fcgi
+
+ fixed the EINPROGRESS handling to use getsockopt (er)
+ fixed a leak of server is disabled (er)
+
+- solaris 10 port-api
+
+ added a skeleton for the sol10 port api
+
+06.08.2004 10:18
+- mod_ssi
+
+ fix DATE_LOCAL so it displays the correct time zone (Jeremy Hinegardner)
+
+04.08.2004 11:43
+- openbsd fixes
+
+ dropped usage of MAX() in buffer.c
+ added prober includes for md5.h if openssl is enabled (brad)
+
+- memory usage
+
+ documented the way how lighttpd caches memory blocks
+ reset the buffers after they have been written by the network-layer
+
+- kqueue
+
+ modify fd-bitmask only if kevent succeeded (adam)
+
+
+03.08.2004 15:09
+- mod_compress
+
+ compress even if you have no cachedir set
+
+03.08.2004 13:26 - pre-release
+- Makefile
+
+ fixed dependencies for parallel build in mod_ssi_expr.c
+
+- combo patch
+
+ * Tinker with kqueue(). Add a reset method so that the kqueue file
+ descriptor can be re-enabled after a fork(). Emulate the devpoll driver
+ in that adds and deletes are sent to the notification mechanism im-
+ mediately, which should cut down on phantom events. Use
+ ev->kq_results as a sliding window.
+
+ * Change F_SETFD calls to use the preferred FD_CLOEXEC instead of 1.
+
+ * Remove unnecessary fdevent fcntl handlers. It appears that the only
+ driver that needs one is Linux RT signals.
+
+ * Quiet compiler warning about unused parameter.
+
+ * Set the close-on-exec flag for the /dev/poll and epoll_create() file
+ descriptors.
+
+ * Return failure if /dev/poll could not be opened instead of logging
+ and continuing.
+
+ * Detect EAGAIN after writev() failures. FreeBSD sendfile() doesn't need
+ protection, as the man page says:
+
+ When using a socket marked for non-blocking I/O, sendfile() may send
+ fewer bytes than requested. In this case, the number of bytes success-
+ fully written is returned in *sbytes (if specified), and the error
+ EAGAIN is returned.
+
+ (adam, georg, matt)
+
+
+02.08.2004 18:08
+- mod_ssi
+
+ check for pcre before compiling the module
+
+- fdevents
+
+ dropped fdevent_fcntl added by the last patch (adam)
+ kqueue: events == FDEVENT_IN -> events & FDEVENT_IN (adam)
+
+31.07.2004 22:07 - 1.2.4
+- fdevents
+
+ * Test at configure time for kqueue() and <sys/event.h>
+ * Remove various hard-coded constants from event handlers
+ * Move maxfds into the event structure, and out of the
+ fdevent_*_init handlers. Event handlers can use the maxfds
+ member to size arrays.
+ * Various event structure renames to discourage clashes
+ * Remove extra (ignored) call to fdevent_event_next_fdndx() in
+ the main server loop.
+ * Wrestle with kqueue(). The implementation has to deal with
+ phantom events (for fds which have been deleted/closed), similar
+ to the Linux RT signals code. Like the RT code, it maintains a
+ bitmask of active fds. After a successful call to kevent(), the
+ code will compress/overwrite dead events. The other annoyance is
+ that the handler must track the event filter for each fd, even
+ though you cannot support both read and write filters for the same
+ fd in one kqueue. The handler maintains a separate bitmask for fd
+ filters (1 == EVFILT_IN, 0 == EVFILT_OUT).
+ (adam)
+
+- server side includes
+
+ added native server-side includes based on the docs from apache:
+ http://httpd.apache.org/docs/mod/mod_include.html
+
+ not supported are:
+ - exec
+ - nested virtual
+ - config.errmsg
+ - echo.encoding
+
+24.07.2004
+- fdevents
+
+ added a bitset to figure out if we received a event for an unregistered fd
+ in rt-signal (adam)
+
+- kqueue
+
+ added kqueue support (Matt Levine)
+
+13.07.2004 08:58
+- configfile
+
+ parse keys correctly that contain a digit (Geoff Adams)
+
+- fcgi
+
+ fixed large post uploads (Geoff Adams)
+ fixed uri if docroot is set (Geoff Adams)
+
+03.07.2004 22:50 - 1.2.3
+- index-files
+
+ rewrite uri.path to the index-file instead of keeping it at .../
+ this fixes index-file handling in FastCGI/CGI docroot is used
+
+- close-on-exec
+
+ enable close-on-exec handling to simplify FD handling in CGI code
+
+- cgi
+
+ keep error-log-fd open to catch the error handling for execve()
+
+ report error if cgi-exec file doesn't exist
+
+- proxy
+
+ pass page-content on error to the user (E.R.)
+ code cleanup (E.R.)
+
+- ssi
+
+ first skeleton of a plugin for ServerSideIncludes
+
+- security
+
+ limit the headerlength again to 64k max
+
+03.07.2004 14:23
+- configure
+
+ fixed compile-check for libpcre if pcre-config doesn't point to /usr/lib
+
+02.07.2004 18:17
+- buffers
+
+ always allocate a multiply of 64bytes. this should reduce the number of
+ realloc()s and still doesn't has a too high overhead.
+
+02.07.2004 11:07
+- fds
+
+ connect stdin, stdout and stderr to /dev/null instead of just closing it
+ use dup2() instead of dup()
+
+- accesslog
+
+ if accesslog.filename starts with a | spawn a process which will get data
+ in one chunk once in a while
+
+01.07.2004 11:00
+- sample config
+
+ added text/css and text/javascript mimetypes
+
+28.06.2004 12:18
+- proxy module
+
+ added a proxy module (based on the fastcgi module) and added some
+ documentation
+
+25.06.2004 21:41
+- last-modified handling
+
+ replaced %Z by GMT. otherwise the last-modified check will most often fail.
+
+24.06.2004 20:20
+- relax http-parser
+
+ don't reply 400 in case of an empty header-field, just ignore it
+
+23.06.2004 22:10
+- file-cache
+
+ don't cache mmap() for files larger than 64k as we run out of RAM otherwise
+ too fast (check with some 200mb files)
+
+- 64bit fixes
+
+ fixed buffer_equal on sparc64
+
+15.06.2004 19:09 - 1.2.2
+- mmap cache
+
+ fixed mmap-caching in network_write.c and network_writev.c after a direct
+ hint by E.Rodichev
+
+- sendfile + linux
+
+ check at config-time if sendfile() works on Linux
+
+11.06.2004 15:09
+- fcgi + unix sockets
+
+ added support for unix domain sockets (spawn-fcgi 1.2.0 required)
+
+10.06.2004 11:49
+- configure
+
+ use pcre-config to determine the position of the pcre headers
+
+05.06.2004 22:06
+- filehandle-cache
+
+ remove mmap-segment if filecache gets invalidated
+
+30.05.2004 14:13 - lighttpd 1.2.1
+- response headers
+
+ request headers that appear twice are grouped together like expected by
+ the CGI spec (concat with a ", ")
+ response headers behaved the same way but are not grouped anymore. They
+ stay seperated. Actually they are concated by \r\n<key>: <value> which is
+ the same in the end.
+
+- file uploads
+
+ the handling of longer post requests is fixed now.
+
+28.05.2004 09:13
+- cgi
+
+ added support for direct calls to cgi-binary
+
+22.05.2004 21:58
+- pipeling
+
+ the code cleanup is finished successfully. Now all cases of pipelining are
+ handle the right way. POST pipelining was still not working up to now.
+
+22.05.2004 12:55
+- code cleanup
+
+ use the well-tested code from the write-queue as the base for the
+ read-queues and simplify the pipeline handling alot that way.
+
+20.05.2004 15:08
+- network backends
+
+ enabled sendfile support again (__FreeBSD__ instead of __freebsd__)
+
+ added a mmap cache as part of the filedescriptor cache
+
+ added AIX specific send_file() support (untested)
+
+20.05.2004 10:40
+- segfaults
+
+ fixed some minor segfaults on startup when no config file is used.
+
+17.05.2004 10:58 - lighttpd 1.2.0
+- documentation
+
+ reformated the documentation the doc/ directory
+
+15.05.2004 14:45
+- localizer
+
+ fixed build of localizer extension
+
+15.05.2004 12:35
+- POST requests
+
+ there is no need to die if we spot a simple POST request on a static file
+
+- pipelining
+
+ fixed HTTP/1.1 pipelining which caused the problems with opera
+
+- array handling
+
+ how did that bug survive such a long time ? a wrong compare function was
+ used in one case, but not the other.
+
+15.05.2004 03:20
+- secure and fast downloads
+
+ added a module which allows secure and fast downloading of files:
+ 1. the application (.php, ...) controls the access to the files
+ 2. the webserver is handling the transfer (and check the app generated
+ tokens)
+
+ the token is based on
+ - a secret
+ - a timestamp
+ - the filename
+ this means even if the token is is distributed by the user it will get
+ invalid after a given timeout (default 60 seconds)
+
+- errorfiles
+
+ check for errorfiles before using them
+
+- code cleanup
+
+ applied cleanup fixes from adam
+
+14.05.2004 18:47
+- fdevent handling
+
+ added a more generic callback interface to the fdevent structures which
+ simplifies the writing of plugins. this might destabilize lighttpd for a
+ while
+
+- cgi
+
+ fixed header parsing of the header is sent in chunks and the terminator is
+ sent in a single chunk
+
+- EINTR
+
+ fixed some occurences of EINTR which read()
+
+03.05.2004 23:55
+- portability
+
+ E.R.:
+ * portability fixes for Solaris 2.5
+
+02.05.2004 10:15
+- Expect: Handling
+
+ added incomplete support for Expect: 100-continue (RFC 2616 8.2.3) by
+ sending always 417 for every Expect-request (see 14.20)
+
+ we have been blamed for not supporting it:
+ http://lists.w3.org/Archives/Public/ietf-http-wg/2004JanMar/0059.html
+
+29.04.2004 23:07 - 1.1.9
+- usertracking
+
+ added a very basic usertracking cookie handler
+
+29.04.2004 19:37
+- network-writev
+
+ Adam:
+ * call munmap() on error in write()
+
+- docs
+
+ E.R:
+ * documented 'check-local'
+
+- test-env
+
+ made the shell scripts more portable (checked with zsh, bash and ksh)
+
+ fixed compilation on NetBSD
+
+28.04.2004 22:22
+- FastCGI
+
+ E.Rodichev:
+ * added "Authorizer" mode for FastCGI
+
+27.04.2004 18:08
+- ssl
+
+ Alexey Slynko:
+ * handle SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE in SSL_write the
+ right way.
+
+- FastCGI
+
+ add 'check-local' instead of the implicit 'if-docroot-is-set' handling
+ implemented at 24.04.2004 14:34
+
+ E.Rodichev:
+ * remove useless extra-/ in before uri.path
+ * add 'prefix' notation for FastCGI processes
+
+26.04.2004 16:52
+- code cleanup
+
+ patches from Alexey Slynko:
+ * remove the pidfile if lighttpd terminates the normal way (if not in chroot)
+ * init SSL before getting daemonized
+
+25.04.2004 19.15
+- mem-leak
+
+ fixed mem-leak on broken HTTP-headers
+
+- FastCGI
+
+ patches from E.Rodichev:
+
+ 1. CONTENT_LENGTH
+ "If no data are attached, then this metavariable is either NULL or not
+ defined".
+
+ 2. QUERY_STRING
+ If the Script-URI does not include a query component, the QUERY_STRING
+ metavariable MUST be defined as an empty string ("").
+
+ 3. Doubling of REMOTE_ADDR removed.
+
+ patches from adam:
+ * fixed bug in the sizeof() patches from yesterday.
+ * some more *_long to *_off_t
+
+24.04.2004 14:34
+- FastCGI
+
+ don't check for localfile if 'docroot' for a FastCGI host is specified.
+
+24.04.2004 12:13
+- POST
+
+ fixed POST request handling
+
+- chunk-encoding
+
+ the generated HEX strings where broken since the 1.1.8
+ (this effected all HTTP/1.1 requests without Content-Length like FastCGI-PHP)
+
+- code cleanup
+
+ patches from adam:
+ * malloc + memset -> calloc
+ * sizeof(int) -> sizeof(<variable>)
+ * assign fd_set instead of memcpy()
+ * init fd -> connection fd pointers to -1
+
+16.04.2004 08:48 - 1.1.8
+- code cleanup
+
+ don't reuse buffer > 64k (see settings.h)
+
+ added server.max-request-size to limit the maximum request-body size
+ (in kBytes)
+
+ don't accept HTTP-request headers larger then 32kBytes (see settings.h)
+
+ minor speed improvements in the request-parser
+
+ More cleanup patches from adam:
+
+ * change pre-ANSI C/valid C++ syntax for function declarations/definitions
+ from using () to (void). Ex: int foo(); --> int foo(void);
+ * use static linkage as much as possible, to limit possible symbol
+ collisions
+ * whack more unneeded variables
+ * try and prevent any errno clobbering by storing the old errno value before
+ any subsequent system calls, and restoring before function exit.
+ * change printf syntax for unsigned variables from %d to %u
+
+15.04.2004 18:41
+- code cleanup
+
+ handle all int != size_t cases in fcgi.c correctly
+
+ check headerfields to have a value
+
+ handle both EINVAL cases of writev() before the can occur
+
+ limit content-length to SSIZE_MAX
+ disallow negative content-length
+
+ the usage of ltostr() has been reduced to the minimum in favour of
+ buffer_/append|copy)_(long|off_t)
+
+ dropped ultostr() and ultohex() in favour of buffer_*
+
+15.04.2004 16:35
+- portablity
+
+ more patches from adam:
+
+ * remove warnings for unused parameters and variables
+ * remove warnings for mismatched pointer assignments
+ * change "gtime_r" to "gmtime_r"
+
+13.04.2004 20:48
+- test-cases
+
+ made the 'make check' target self-contained
+
+ In our case we have to call the targets in the following order:
+
+ $ ./configure ...
+ $ make
+ $ make install
+ $ make check
+
+ because the path to the plugins is hardcoded in the binary itself
+
+ using
+
+ $ ./configure --prefix=/tmp/lighttpd-1.1.x/
+ ...
+
+ will help if you don't really want to install before testing.
+
+13.04.2004 00:05
+- portability
+
+ adam sent another patchset:
+
+ * Wrap PCRE-specific data member access with an #ifdef
+ * Add const to pointer using return value from dlerror()
+ * Explicitly initialize pointer in the lemon parser to 0,
+ in order to catch missing else { } clause
+ * Use a time_t rather than an int for gmtime() call. On some
+ systems (including 32- and 64-bit SPARC) time_t is a long.
+
+12.04.2004 17:00 - 1.1.7
+- fastcgi
+
+ strip WS after HTTP-response headers coming from the FastCGI process
+
+ added REMOTE_USER to the Server->FastCGI headers
+ removed HTTP_AUTHORIZATION from the Server->FastCGI headers
+
+12.04.2004 10:24
+- cgi
+
+ if we don't get a partial HTTP-response-header send the content out as soon
+ as the cgi script is finished
+
+12.04.2004 01:23
+- compression
+
+ added bzip2 compression (supported by w3m)
+
+12.04.2004 00:12
+- configfile
+
+ add some usefull error messages if the tokenizer or the parser fail to
+ read the configfile
+
+11.04.2004 22:04
+- configure
+
+ added --with-ldap and --disable-lfs to the configure options
+
+11.04.2004 20:28
+- 64bit offset size
+
+ disable linux-sendfile support for linux 2.4.x for now as it don't
+ support 64bit transfers
+
+ fixed all assignments on the path from the stat() to the Content-Length
+ HTTP-header
+
+- head requests
+ set content-length in HEAD requests
+
+- accesslog
+
+ write accesslog entry on network error
+
+ write the correct amount of byte written to the accesslog
+
+11.04.2004 11:48
+- code cleanup
+
+ moved the config for the cgi-plugin from config.c to the plugin.
+
+ moved some buffers which were only used by a one or two plugin from
+ the server-structure to the plugins
+
+ keeping the plugins independent from the server-core is a 'good thing'
+
+10.04.2004 19:06
+- configfile parser
+
+ removed the leaks from the configfile parser
+
+09.04.2004 23:15 - 1.1.6
+- stricter http-parser
+
+ added line-folding although noone really seems to use it.
+
+09.04.2004 18:42
+- configfile parser
+
+ the hand-written configfile parser has been replaced by a LALR(1) one.
+ 'lemon' from the sqlite guys has been used to generate the parser.
+
+- by-host, by-url, by-file, by ...
+
+ $HTTP["url"] =~ "~$" {
+ access.deny = "all"
+ }
+
+ $HTTP["host"] =~ "." {
+ simple-vhost.server-root = "/home/weigon/wwwroot/servers/"
+ simple-vhost.default-host = "grisu.home.kneschke.de"
+ simple-vhost.document-root = "pages"
+ }
+
+ $HTTP["host"] == "incremental.home.kneschke.de" {
+ server.docroot = "/hasdasd"
+ }
+
+ at least the parser can handle it now. Currently there is no real support
+ for this context-based config-option. But the syntax and the parser are
+ done.
+
+09.04.2004 10:58
+- ssl support
+
+ enable ssl support again
+
+- mmap
+
+ enabled mmap+write again
+
+08.04.2004 12:34
+- stricter http-parser
+
+ based on a thread at
+
+ http://lists.w3.org/Archives/Public/ietf-http-wg/2004JanMar/0050.html
+
+ the HTTP-parser has been adjusted to be more correct when it comes to
+ request-header fieldnames
+
+ the whitespace handling around the ':' has been relaxed as requested
+
+07.04.2004 17:06
+- sigaction
+
+ use sigaction instead of signal if possible
+
+07.04.2004 13:55
+- accesslog
+
+ use localtime-timestamps in accesslogs if struct tm has a tm_gmtoff field
+
+07.04.2004 10:41 - 1.1.5
+- -D_REENTRANT
+
+ solaris + localtime_r() needs it
+
+07.04.2004 02:54
+- mod_auth + ldap
+
+ added a ldap backend to the auth plugin.
+
+06.04.2004 13:37
+- pidfile
+
+ fixed the permissions of the pidfile (Matthijs van der Klip)
+
+- specfile
+
+ merge the RedHat and SuSE specfile with Matthijs
+
+- 64bit file-offsets
+
+ moved the FILE_OFFSET_BITS settings from the config.h the Makefile to enable
+ 64bit offsets the right way
+
+06.04.2004 12:32
+- mod_expire
+
+ added an apache compatible mod_expire which adds Expires: headers to the
+ request
+
+ expire.url = ( "/buggy/" => "access 2 hours" )
+
+05.04.2004 22:34
+- solaris devpoll
+
+ forgot to provide the infrastructure to actually enable the devpoll event
+ handler. the same has been done for the freebsd-kqueue handler (which
+ doesn't work yet)
+
+ fixed the devpoll support while testing it on a real solaris box
+
+05.04.2004 09:49
+- debian
+
+ added debian packaging support written by Vincent Wagelaar
+
+- solaris
+
+ Another set of patches for Solaris from Adam
+ * Detect <sys/devpoll.h>
+ * Detect and include <sys/filio.h> for definition of FIONREAD
+ * Detect and link against the library exporting hstrerror
+ * Correct typo in SENDFILE_LIB
+ * Use__sun instead of __solaris__ for detecting operating system.
+ Please see http://predef.sourceforge.net/preos.html for more
+ detail
+ * Explicitly cast arguments to isalpha() and toupper() to unsigned
+ char. The man page says that the functions support the range of
+ an unsigned char, and EOF.
+ * Include <limits.h> and define UIO_MAXIOV as IOV_MAX for Solaris.
+
+04.04.2004 18:05 - 1.1.4
+- pidfile
+
+ added pidfile writing after deamonizing
+
+04.04.2004 01:05
+- fdevent
+
+ added framework for freebsd_kqueue and solaris_devpoll
+
+ the solaris_devpoll one might event work (untested)
+
+03.04.2004 16:41
+- network
+
+ added framework for filebased chunks
+ - read-write + mmap-write
+ - linux-sendfile
+ - freebsd-sendfile
+ - solaris-sendfilev (untested)
+
+ and memorybased chunks
+ - write
+ - writev
+
+ made TCP_CORK a 'global' flag around the write_chunkqueue-calls
+
+ the writev() support should improve the performance for all non-static
+ pages.
+
+ 170 req/s against 158 req/s for the following script if writev() is used
+ instead of write()
+
+ <?php
+
+ for ($i = 0; $i < 1000; $i++) {
+ print $i."<br />\n";
+ flush();
+ }
+
+ ?>
+
+28.03.2004 13:42
+- cleanup
+
+ applied some cleanup patches submitted by Adam:
+ * variables modified in signal handlers should be sig_atomic_t
+ * assert statements should not have side effects
+ * STD{IN,OUT,ERR}_FILENO preferred instead of {0,1,2}
+ * dieing --> dying
+ * SEGFAULT calls abort directly, instead of derefencing a NULL pointer
+
+- mod_accesslog
+
+ modified the accesslog format to be CLF compatible
+ set locale for LC_TIME to C
+
+26.03.2004 16:13
+- path info
+
+ enabled the pathinfo code again
+
+
+25.03.2004 13:30 - 1.1.3
+- portability
+
+ compiles fine now without any patches on IRIX
+
+- hostname detection
+
+ reworked "get the hostname for HTTP/1.0 requests which don't specify a
+ Host: ..." to only query the name in this single case for the server side
+ of the connection
+
+- errorlog handling
+
+ stderr is only used until the errorlog is init'ed
+ if no error-log is specified, syslog() is used
+ if cycling error-log fails, syslog() is used
+
+- accesslog cycling
+
+ don't fall back to stdout anymore
+
+- event-handler
+
+ use poll() as the default event-handler again
+
+24.03.2004 01:37 - 1.1.2a
+- error messages
+
+ added some hints to the failing error-messages
+
+22.03.2004 01:58 - 1.1.2
+- configure
+
+ some protability changes to get the 'inline' working with the MIPS CC
+
+21.03.2004 22:00
+- mod_rewrite, mod_redirect
+
+ reading the config for those two plugins was not working
+
+- fdevents
+
+ changed the compile time setting for the event handling into a run-time
+ setting.
+
+ server.event-handler = "select" # poll, linux-rtsig, linux-sysepoll
+
+ added sys_epoll() for linux 2.6
+
+ select - all systems
+ poll - Linux 2.1.23+, all XPG4-UNIX
+ sigio - linux 2.4.0+
+ sysepoll - linux 2.5.66+
+
+ 1000-4k-nok 1000-100k-nok
+ select 1776.99 296.52
+ poll 678.02 607.28
+ sigio 3754.46 1411.23
+ sysepoll 3817.67 1431.02
+
+21.03.2004 00:10
+- configure script
+
+ rewrote large parts of the lib/header detection of the configure script
+
+20.03.2004 01:39
+- fastcgi
+
+ as the docroot on external hosts might be different than the webserver
+ docroot it can now be specified in the config:
+
+ fastcgi.server = ( ".php" =>
+ ( "grisu" =>
+ (
+ "host" => "192.168.2.41",
+ "docroot" => "/home/jan/servers/",
+ "port" => 1026
+ )
+ )
+ )
+
+ a huge internal cleanup in the config handling made the code more readable.
+ some more warnings and error checking should track most of the config
+ errors for the fastcgi plugin
+
+19.03.2004 12:34
+- external patches
+
+ Matthijs van der Klip submitted three nice patches:
+ - turn off writing in mod_status if status.rrd-reports is set to disable
+ - fix for a debug message
+ - get the hostname for HTTP/1.0 requests which don't specify a Host: ...
+ - rc-script for RedHat
+
+- documentation
+
+ added a documentation section about authentification
+ - doc/authentification.txt
+
+19.03.2004 05:11
+- optimizations
+
+ implemented special versions of
+ - strcasecmp (array_strcasecmp)
+ - isdigit, isalpha, isalnum (light_*)
+
+ added a faster check for a finished header
+
+ after disabling all modules it is still possible to get 20 kreq/s.
+
+15.03.2004 19:45 - 1.1.1
+- localizer server
+
+ added the localizer-server module to the code
+
+- chunked-encoding
+
+ Apple's Safari or HTTP-Handler doesn't handle chunked-extensions defined by
+ RFC 2616 correctly and doesn't ignore them. Disabled the chunked-extension
+ which were just used for debugging purposes.
+
+07.03.2004 12:20
+- optimization
+
+ moved the checks if a plugins support a given function from the dispatcher
+ (plugins_call) to plugins_call_init() to do the check only once.
+
+ equalized the plugins functions to only two types:
+ SERVER_FUNC()
+ CONNECTION_FUNC()
+
+ replaced all handwritten plugin_call-handlers with macros
+
+ made all plugin-functions 'static'
+
+ with all plugins loaded we are at 15kreq/s gain
+ without any plugin loaded at 16kreq/s
+
+ before the change we were at 13kreq/s
+
+06.03.2004 21:13
+- compilation fixes
+
+ fixed some warnings on FreeBSD and NetBSD by adding
+ #include <netinet/in.h>
+
+ ifdef'ed a pcre-entry in base.h
+
+ remove #define _XOPEN_SOURCE from http_auth.c for IRIX
+ crypt() on Linux needs _XOPEN_SOURCE
+
+06.03.2004 19:18 - 1.1.0
+- authentification
+
+ added htpasswd and htdigest backends to work against user-files generated
+ by htpasswd and htdigest.
+
+ for basic auth:
+ - plain
+ - htpasswd (crypt only)
+ - htdigest
+
+ for digest auth:
+ - plain
+ - htdigest
+
+06.03.2004 17:35
+- authentification
+
+ check the method in the authorization header againt the configured method
+
+06.03.2004 14:54
+- hostname parsing
+
+ added a RFC-2396 conforming "Host:" parser
+ added 17 checks for the parser
+
+06.03.2004 10:25
+- configuration
+
+ added a warning for unknown configuration variables.
+
+ dropped the 'specific-for.url' ideas for now as it is not known yet how to
+ implement it the right way
+
+ renamed some config-variables to reflect there actual meaning
+ - server.host -> server.bind
+ - server.virtual-* -> simple-vhost.*
+ - server.userid -> server.username
+ - server.groupid -> server.groupname
+ - server.docroot -> server.document-root
+
+
+05.03.2004 15:36
+- fastcgi
+
+ internals: moved all mod_fastcgi settings from the global struct to the
+ plugin itself
+
+- vhosting
+
+ got a patch for enhanced virtualhosting from christian kruse -> mod_evhost
+
+ moved the virtual hosting config (server.virtual-*) to it's own module
+ called mod_simple_vhost
+
+23.02.2004 10:06
+- configfile
+
+ rearragned the config-file structure again to be able to add settings for
+ a given URL, Host, Filename, ...
+ This change affects some config-options directly (access.deny,
+ url.rewrite, url.redirect, virtual-hosting, ...)
+
+ added 'specific-for.url' for url specific config settings
+
+- digest auth
+
+ FIX: md5-sess type
+
+ seperate the auth-backend stuff
+
+13.02.2004 22:23 - lighttpd 1.0.3
+- content-length + POST
+
+ FIX: If header and content didn't fit into one single packet the rest of
+ the content was not read correctly
+
+13.02.2004 01:07
+- content-length + POST
+
+ the check for content-length on a POST request vanished somehow in one of
+ the previous releases.
+
+- header search
+
+ FIX: the search for header fields was up to now case-sensitive. Now it is
+ like required by the standard case-in-sensitive.
+
+- browser bugs
+
+ w3m 0.2.5 adds an additional \r\n at the end of the POST requests which is
+ handled now
+
+10.02.2004 10:12
+- start script
+
+ took the suse rc-skeleton which states to be LSB compliant and modified it
+ for lighttpd needs
+
+09.02.2004 11:48
+- HEAD requests
+
+ FIX: HEAD requests for static files delived the content from the GET
+ request. (test case is added)
+
+08.02.2004 15:53
+- directory listings
+
+ FIX: the pathname has not encoded at all before it was transfered to the
+ browser. a proper url and html-encoding has been added.
+
+ added modification-time and filesize to the output
+
+ added a basic css for the virtual listings
+
+07.02.2004 22:15 - lighttpd 1.0.2
+- sample configfile
+
+ rearranged the config-file to have all the important options at the top
+
+- docs
+
+ added a mod-rewrite options
+
+- mod_accesslog
+
+ stdout is no longer used a default for the accesslog
+
+- error-messages
+
+ the 404 is now reported in the error-log
+
+07.02.2004 17:30
+- configfile handling
+
+ if a key is used twice like
+
+ url.rewrite = ( "url1" => "url")
+ url.rewrite = ( "url2" => "url")
+
+ you get an error now. You have to write:
+
+ url.rewrite = ( "url1" => "url",
+ "url2" => "url")
+
+31.01.2004 12:22 - lighttpd 1.0.1
+- log cycling
+
+ added a sighup-handler to the plugin interface and fixed the log-cycling
+ for access-logs
+
+- portability
+
+ disabled the interval-time optimization on IRIX
+
+- documentation
+
+ added a lot of new documentation to README
+
+31.01.2004 10:59
+- status module
+
+ added two new options rrd-dir and rrd-reports:
+
+ rrd-reports = (boolean) # enables RRD-reports
+ rrd-dir = (string) # path for the daily status-files
+
+ fixed the "status-files in /"-bug this way
+
+
+22.01.2004 13:38 - lighttpd 1.0.0
+- simple docroot
+
+ fixed handling of docroots if virtual-host is disabled
+
+27.12.2003 11:12
+- license handling
+
+ added the first interface to license handling.
+
+25.12.2003 23:48
+- protability
+
+ Verified again that the code compiles and runs cleanly on Linux, FreeBSD,
+ NetBSD and IRIX
+
+ compiling with gcc and the option -pedantic works fine
+ compiling with mipspro cc works fine, too
+
+- tests
+
+ added some more tests: 39 tests
+
+25.12.2003 16:01
+- protability
+
+ some compile fixes for FreeBSD have been applied and a new switch has been
+ added to choose between IPv4 and IPv6 on FreeBSD.
+ (cmdline: -6, configfile: server.use-ipv6)
+
+- packaging
+
+ cleaned up the specfile for building RPMs
+
+21.12.2003 01:00
+- authorization
+
+ brought basic and digest auth back to life. this module as the last one
+ which had to be updated after the config-file changes
+
+- test harness framework
+
+ add 3 tests for basic auth
+
+20.12.2003 22:10
+- compression
+
+ added gzip compression (gzip-header + deflate + crc)
+
+- test harness framework
+
+ added a 22 tests to verify the correct behaviour of lighttpd
+
+- request parsing
+
+ GET http://www.yahoo.com/ HTTP/1.0
+
+ is handled now as
+
+ GET / HTTP/1.0
+
+- plugins
+
+ moved FastCGI and CGI handling into modules which can be loaded at run-time
+
+17.12.2003 13:18
+- compression
+
+ the directory structure is now build automaticly
+
+15.12.2003 01:00
+- compression
+
+ added a compression cache to the compression module (mod_compress)
+
+ Vary: Accept-Encoding is set now as it influences to delivered stream of
+ bytes. This is important for caches.
+
+10.12.2003 00:24
+- config files
+
+ a new config-file format is ready for the final release of lighttpd.
+
+ it supports:
+
+ server.docroot = "string"
+ server.host = integer
+ server.modules = ( "string", "string" )
+ server.mimetypes = ( "key" => "value" )
+ server.complex = ( "key" => ( "string", integer ),
+ "string",
+ integer )
+
+ the syntax should look familar to all who worked with Perl or PHP.
+
+ config-file handling has been seperated into a parser and a lexer. Both
+ are currently written by hand and will propably be rewritten into bison +
+ flex if time permits. But that would be a cosmetical change.
+
+05.12.2003 03:07
+- status-page
+
+ improved the status page to display
+ - the current connection-stati
+ - average throughput over 5 seconds
+ (requests/s and output-bound traffic)
+
+ now you can see what is going on in the server
+
+- access-log
+
+ the accesslog is now a module and can be disabled by just removing the
+ plugin from the list of loaded plugins
+
+04.12.2003 16:18
+- chroot-ing
+
+ how-to: using chroot
+
+ - chroot to /home/www/
+ - docroot at /servers/<hostname>/pages/
+ - defaulthost www.example.org
+
+ e.g. (external view)
+ /home/www/servers/www.example.org/pages/index.html
+
+ (in chroot)
+ /servers/www.example.org/pages/index.html
+
+ config:
+
+ chroot /home/www/
+ userid wwwrun
+ groupid nogroup
+
+ virtual-server-root /servers/
+ virtual-server-default-host www.example.org
+ virtual-server-docroot /pages/
+
+ The FastCGI process is living outside this chroot definition as it is
+ started seperatly.
+
+01.12.2003 02:06
+- cleanup
+
+ in preparation for the first stable release some internals had to be
+ cleaned up. Basicly it was a cleanup of workflow of the creation of the
+ response-header. All modules can use a clean interface for this purpose
+ now. This is espacially usefull for all modules which have to pass some
+ HTTP-headers to the client.
+
+29.11.2003 22:22
+- modules
+
+ finally moved the modules to shared libraries and cleaned up some code
+ path to become more readable.
+
+ So far we have:
+
+ mod_rewrite
+ mod_redirect
+ mod_access
+ mod_auth
+ mod_cache
+ mod_chat
+ mod_status
+ mod_maps
+
+28.11.2003 18:16
+- redirects
+
+ as lighttpd supports url-rewriting redirection was a few lines of new code.
+
+ redirect ^/wishlist/(.+) http://jan.kneschke.de/wishlist/$1
+
+ rewrite ^/wishlist/(.+) /new/wishlist/$1
+
+28.11.2003 17:00
+- signal stuff
+
+ setitimer is used to send a event every second to call time() only once a
+ second. another system call which has been remove from the main-loop.
+
+ sending HUP to lighttpd will close and re-open the logfiles. this is used
+ for cycling logfiles.
+
+#! /bin/sh
+
+###
+#
+# a simple logfile rotator for lighttpd
+#
+
+DATE=`date +"%Y%m%d-%H%M"`
+LPID=`pidof lighttpd`
+mv access.log access.log.${DATE}
+kill -HUP ${LPID}
+gzip access.log.${DATE}
+
+27.11.2003 01:07
+- native win32 port
+
+ a first attempt for a native win32 has been done. For now mingw is the
+ base for the development as it provides a basic unix-like framework for
+ building native win32 applications.
+
+ the most internal files have been ported and the over all progress is
+ going well. At the end this will just be a prove of concept.
+
+26.11.2003 01:17
+- access denied
+
+ added a access-deny filter to block specific urls like
+
+ access-deny ~
+ access-deny .inc
+
+17.11.2003 01:06
+- bug fixing
+
+ a fstat() on a opened fd which has changed reports wrong the file-info.
+ Using stat() again helps to solve this problem.
+
+ the sig-io version doesn't suffer from this problem.
+
+ the etags are now used for verifing file-cache-entries.
+
+- chat
+
+ enabled the internal login mechanism again
+
+ added support transfering session-infos over MySQL.
+
+15.11.2003 00:19
+- optimizing
+
+ added a buffer_equal_reverse function which is optimized version of strcmp
+ which is going backwards as pathnames are often the same for in the first
+ bytes.
+
+ wrote a one-pass parser for the request-header. The combination of
+ strstr(..., "\r\n") to seperate lines and strchr(..., ':') to seperate
+ keys from values more or less touched every byte twice.
+
+ we are still at 18.000 req/s for 4kb keep-alive requests even with etags
+ and handling all header fields.
+
+14.11.2003 17:26
+- fcgi
+
+ Content-Type wasn't passed correctly to the FastCGI app. HTTP_CONTENT_TYPE
+ was sent instead.
+
+- cache
+
+ provided access to the Session-ID
+
+- error-log
+
+ the timestamp is now written in a human readable form
+
+22.10.2003 00:06
+- fcgi, cgi
+
+ added a special set of array-functions which are optimised for the "insert
+ only once" case.
+
+ this provides access to the headers which are now forwarded to the external
+ interfaces. Before this change only a limited set of request-headers were
+ forwarded.
+
+21.10.2003 11:58
+- modules
+
+ added a new module-hook after the basic-init of the module for handling
+ config-settings and prepare the overall operartion (like building
+ db-connections, compiling regexes, ...)
+
+- cache
+
+ use turckmm-cache 2.4.3 to get some numbers for the php-latency:
+
+ /usr/sbin/ab -n 10000 -c 10 http://alba.home.kneschke.de:1025/index.php
+
+ handling the cache-decision and the cache-hit in php:
+
+ cache-miss: 100% (-&gt; $version = 0)
+
+ plain : 108.13 req/s
+ turckmm-cache: 218.39 req/s
+
+ cache-hit: 100% (-&gt; $version = 1)
+
+ plain : 164.45 req/s
+ turckmm-cache: 653.98 req/s
+
+ handling the cache-decision and the cache-hit in the server:
+
+ cache-hit: 100%, but using index.cml
+
+ cml: 4918.84 req/s (no keep-alive)
+ cml: 6901.31 req/s (keep-alive)
+
+ cache-miss: 100%, but using index.cml
+
+ plain : 108.39 req/s
+ turckmm-cache: 217.84 req/s
+
+ Conclusion:
+ - there is no loss in the cache-miss case through the cml-handling
+ - the cache-hit case can be improved dramaticly with lighttpd-cache
+ - turckmm-cache improves the cache-miss case alot
+
+20.10.2003 00:40
+- cache
+
+ the first 'real-life' test showed dramatic improvements in the req/s
+ handling.
+
+ The basic idea was to move the decision if a php-page can be taken from
+ the cache from the php-code to the webserver.
+
+ See here why this is a good thing:
+
+ the quite common code which works for http://jan.kneschke.de/ is using
+ templates and is quite static, but depends on 4 external files (the
+ menu-structure, the template, the current content, the class-file).
+
+ the index-file is always:
+
+ include_once "jk.inc";
+
+ $v = new view();
+ print $v->get(array(array ("file" => "content.html")));
+
+ It is more or less the same for all pages.
+
+ This basic setup can deliver 100 requests/s.
+
+ The next step has:
+ - application bases caching
+
+ as we know that each pages depends on those 4 files, you can check if they
+ have been modified since the last request and deliver the content from the
+ cache otherwise.
+
+ this increased the throughput to 150 req/s. (cache-hit ratio 100%)
+
+ The next logic step is to move the decision-making process out of the PHP
+ code as PHP is to slow for the cache-hit path:
+
+ a CML (Cache-Markup-Language) has been written which describes the whole
+ decision process which has been written in PHP-code before:
+
+
+output.content-type text/html
+
+output.include _cache.html
+
+trigger.handler index.php
+
+trigger.if file.mtime("../lib/php/menu.csv") > file.mtime("_cache.html")
+trigger.if file.mtime("templates/jk.tmpl") > file.mtime("_cache.html")
+trigger.if file.mtime("content.html") > file.mtime("_cache.html")
+
+ if one of the 'trigger.if' statements is true the 'trigger.handler' is
+ called to generate the pages.
+
+ if none of the them is trigger the files from 'output.include' are sent to
+ the browser with content-type specified in the first line:
+
+ The result was very 'promissing':
+
+ 5900 req/s with keep-alive
+ 3800 req/s without keep-alive
+
+ (both for a cache-hit ratio of 100%)
+
+ for keep-alive this is factor <b>59</b> against the plain un-cached
+ version and still <b>39</b> againt the php-cache-version which is doing
+ exactly the same.
+
+ Time for party. :)
+
+19.10.2003 - 18:55
+- cache
+
+ the handling of functions has been improved. they are now 'plugable'. just
+ the dlopen() stuff is missing.
+
+ a new datatype has been added: the string
+
+ this makes it possible to evaluate something like:
+
+trigger.if unix.time.now() - file.mtime("head.html") > 30
+trigger.if mysql.query("SELECT count(*) " + \
+ " FROM structure AS struc, session AS sess " + \
+ " WHERE struct.user = sess.user" + \
+ " AND sess.id = \"" + mysql.escape("ab\"c") + "\"")
+
+ you see:
+ - string operations (concat)
+ - handing of escape-sequences
+ - functions
+ - comparisions
+
+18.10.2003 - 13:39
+- cvs
+
+ imported everything into the cvs server which makes the whole revision
+ handler a lot easier. The CVS server was up all the time but not used.
+ Importing required 5 minutes of work which included merge the freebsd and
+ the linux tree.
+
+- compilers + platforms
+
+ on a regular various compilers and platform are check to compile with with
+ the current code base:
+
+ platform | os | compiler | state
+ ---------+---------------------+--------------+---------
+ ia32 | Linux 2.4.22 | gcc 2.95.3 | ok
+ ia32 | FreeBSD 5.1-CURRENT | gcc 3.3.1 | ok
+ mips64 | IRIX 6.5 | gcc 3.2.2 | ok
+ misp64 | IRIX 6.5 | MIPSpro 7.41 | ok
+
+ the mipspro compiler revealed some warning which resulted in a nice
+ code-cleanup that made the code more readable.
+
+18.10.2003 - 03:00
+- e-tags and friends
+
+ in preparation for the php-conference at the begin of november in
+ frankfurt/main the server has the support some more caching/proxy tags
+ like:
+ - E-Tag (14.19) [done]
+ - If-Match (14.24)
+ - If-None-Match (14.26) [done]
+ - If-Range (14.27)
+
+ Section (13.3.3) binds them together. [RFC 2616]
+
+ using lxr.kde.org and lxr.mozilla.org revealed that
+ - konqui only uses if-none-match
+ - mozilla uses if-none-match and if-range
+
+ it looks like it isn't that easy to trigger the if-range case.
+
+ As ulf suggested the etag is a hash of file-size, inode-number and mtime.
+
+- fd-caching
+
+ ulf just phoned me ask proposed the free the cache more agressivly in case
+ of fd-shortage. increasing the the fd-limit is the better idea. :)
+
+17.10.2003 12:45
+- chat
+
+ finished the mysql-support for storing the sessions
+
+12.10.2003 20:56
+- valgrind
+
+ used valgrind again to verify that the code a free of mem-leaks and found
+ a 'leak generator' in the chunk-api.
+
+ the last few leaks were just some missing free()'s at the end of the
+ program run which would have been freed anyway.
+
+ at the end valgrind couldn't find any missing free()'s.
+
+11.10.2003 12:09
+- FastCGI
+
+ reduced the number of system calls for FastCGI to WebServer to 2 calls per
+ fd-event. (ioctl() + read())
+
+ this has no direct effect of the performance of the server, but improves
+ the possible througput of the load-balancer.
+
+10.10.2003 21:09
+- FastCGI - load-balancing
+
+ a brown paper bug has been fixed which caused to decreasing throughput if
+ load-balancing was enabled.
+
+
+ benchmarking the req/s with load-balancing shows really nice results:
+
+
+ server : req/s comment
+ ---------+--------------------------------------------------
+ ulf : 764.06 (php)
+ lappi : 800.06 (php)
+
+ ulf+lappi: 1526.95 (2 * php)
+
+ grisu : 1265.66 (php + ab + lighttpd)
+
+
+ all : 1647.72 (3 * php + ab + lighttpd)
+ all(nice): 1904.40 (same as all, but the local php on grisu
+ has been 'nice -20'd)
+
+
+ if a php is run on the load-balancer it has to get a lower priority than
+ the load-balancer itself as to handle the work of (here) 3 php-servers.
+
+
+10.10.2003 15:11
+- java ?
+
+ http://jakarta.apache.org/tomcat/tomcat-4.1-doc/jk2/common/AJPv13.html
+
+08.10.2003 21:08
+- gigE
+
+ Last week 3 RTL8169S gigE cards arrived and were installed in the
+ test-framework.
+
+ They are very cheap (20 Euros) and are a good start for a low-level
+ benchmark network.
+
+ First result show us:
+
+ 48 Mb/s with ab.
+
+ The webserver and the test-server are equipped with a 2000+ AMD CPU. The
+ system load 95%, user load is 3%, the rest is idle on both plattforms.
+
+ After some calculations at gets clear that there are various bottlenecks:
+
+ 1. The PCI-Bus (32bit/33Mhz) can only transfer 133Mb/s
+ - 48 Mb/s TCP-Traffic results in 55Mb/s Ethernet-Traffic (verified with
+ slurm) [outgoing]
+ - there is about 10Mb/s incomming traffic at the PCI bus which the
+ requests
+ - the rest of the devices at the PCI bus are eating the last few mb/s
+ 2. IRQ-Handling
+ - There are reasons why the RTL8169S cards are so cheap
+ - they can't send jumbo-frames
+ - only 8k/64k buffers which results in a interrupt every 3 packets
+ - they are at least handling checksum offloading for ip, udp and tcp
+
+ In the end there is a need for new hardware to limit the throughput by the
+ Ethernet again.
+ - PCI-X, 64bit-PCI, ...
+ - 'real' gigE-Network cards
+ - ...
+
+
+ BTW: 2.4.22 + the r8139 driver are very flacky and resulted in 3 lock-ups
+ for today.
+
+ In another test the dual-PPro-FreeBSD 5.1-CURRENT machine has been used as
+ server with a gigE interface (re0) at has shown that the CPU is the limit
+ for this combination. The maximum thoughput was 7Mb/s.
+
+ Another small benchmark:
+ $ ab -n 1000 -c 10 http://192.168.2.41:&lt;port&gt;/lighttpd-20030925.tar
+
+ port | server | CPU Idle
+ ------+-------­---------+-----------
+ 1025 | lighttpd | 75%
+ 1026 | thttpd 2.23b1 | 75%
+ 1027 | boa-0.94.14rc17 | 69% *
+ 1028 | apache 1.3.x | 77%
+
+
+ In all 4 cases the thoughput was 8600 kbytes/s.
+
+ * boa had 9 failed transfers.
+
+- FreeBSD
+
+ The problems with FreeBSD 5.0-RELEASE vanished after updating to
+ 5.1-CURRENT.
+
+26.09.2003 18:22
+- FreeBSD
+
+ A SMP-machine has been added to the test-farm. It is running FreeBSD
+ 5.1-RELEASE and will help to the improve the scalability.
+
+ fixed sendfile() handling.
+
+- FreeBSD problems
+
+ FreeBSD-5.1-RELEASE-SMP
+ 2 * Pentium Pro 200 MHz
+ 192.168.2.38 (doubleheart)
+ (webserver [lighttpd at port 1025, thttpd at port 1027])
+
+ Linux 2.4.20
+ 1 * AMD 2000+
+ 192.168.2.10 (grisu)
+ (ab)
+
+ Problem:
+ Connections are reset by the kernel without any application intervention.
+
+ Calling 'ab' (apachebench) at grisu with the following paramters:
+
+ /usr/sbin/ab -n 10000 -c 10 http://192.168.2.38:1025/index.html
+
+ results in the following output:
+
+ ...
+ Time taken for tests: 40.610 seconds
+ Complete requests: 10000
+ Failed requests: 5980
+ (Connect: 0, Length: 5980, Exceptions: 0)
+ ...
+
+
+ This is reproducable and the number of failed requests is always
+ 5980 +/- 50 requests. In other words: after 4000 requests tcpdump shows
+ the following output:
+
+ tcpdump shows:
+
+ 00:53:48.923029 192.168.2.10.39774 &gt; 192.168.2.38.1025: S [tcp sum ok]
+ 1013737315:1013737315(0) win 5840 &lt;mss 1460,sackOK,timestamp 5208461
+ 0,nop,wscale0&gt; (DF) (ttl 64, id 7918, len 60)
+0x0000 4500 003c 1eee 4000 4006 964d c0a8 020a E..&lt;..@.@..M....
+0x0010 c0a8 0226 9b5e 0401 3c6c 6763 0000 0000 ...&amp;.^..&lt;lgc....
+0x0020 a002 16d0 eeaa 0000 0204 05b4 0402 080a ................
+0x0030 004f 798d 0000 0000 0103 0300 .Oy.........
+
+ 00:53:48.923330 192.168.2.38.1025 &gt; 192.168.2.10.39774: S [tcp sum ok]
+ 1803860672:1803860672(0) ack 1013737316 win 65535 &lt;mss 1460,nop,wscale
+ 1,nop,nop,timestamp 4459794 5208461&gt; (DF) (ttl 64, id 6821, len 60)
+0x0000 4500 003c 1aa5 4000 4006 9a96 c0a8 0226 E..&lt;..@.@......&amp;
+0x0010 c0a8 020a 0401 9b5e 6b84 bac0 3c6c 6764 .......^k...&lt;lgd
+0x0020 a012 ffff d4ce 0000 0204 05b4 0103 0301 ................
+0x0030 0101 080a 0044 0d12 004f 798d .....D...Oy.
+
+ 00:53:48.924009 192.168.2.10.39774 &gt; 192.168.2.38.1025: . [tcp sum ok] ack 1
+ win 5840 &lt;nop,nop,timestamp 5208461 4459794&gt; (DF)
+ (ttl 64, id 7919, len 52)
+0x0000 4500 0034 1eef 4000 4006 9654 c0a8 020a E..4..@.@..T....
+0x0010 c0a8 0226 9b5e 0401 3c6c 6764 6b84 bac1 ...&amp;.^..&lt;lgdk...
+0x0020 8010 16d0 e9c3 0000 0101 080a 004f 798d .............Oy.
+0x0030 0044 0d12
+
+ 00:53:48.924150 192.168.2.10.39774 &gt; 192.168.2.38.1025: P [tcp sum ok]
+ 1:29(28) ack 1 win 5840 &lt;nop,nop,timestamp 5208461 4459794&gt; (DF)
+ (ttl 64, id 7920, len 80)
+ 0x0000 4500 0050 1ef0 4000 4006 9637 c0a8 020a E..P..@.@..7....
+ 0x0010 c0a8 0226 9b5e 0401 3c6c 6764 6b84 bac1 ...&amp;.^..&lt;lgdk...
+ 0x0020 8018 16d0 27e4 0000 0101 080a 004f 798d ....'........Oy.
+ 0x0030 0044 0d12 4745 5420 2f69 6e64 6578 2e68 .D..GET./index.h
+ 0x0040 746d 6c20 4854 5450 2f31 2e30 0d0a 0d0a tml.HTTP/1.0....
+
+ 00:53:48.924728 192.168.2.38.1025 &gt; 192.168.2.10.39774: R [tcp sum ok]
+ 1803860673:1803860673(0) win 0 (ttl 64, id 6831, len 40)
+0x0000 4500 0028 1aaf 0000 4006 daa0 c0a8 0226 E..(....@......&amp;
+0x0010 c0a8 020a 0401 9b5e 6b84 bac1 0000 0000 .......^k.......
+0x0020 5004 0000 64ba 0000
+
+ SYN, SYN+ACK, ACK, DATA, RST
+
+ strace shows that no connection attempt has been reported to the
+ application which is poll()'ing the server socket.
+
+ /* the common loop without any block attempts */
+
+ accept(3, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, [0]) = 5
+ fcntl(5, F_SETFL, O_RDWR|O_NONBLOCK) = 0
+ read(5, "GET /index.html HTTP/1.0\r\n\r\n", 4095) = 28
+ stat("/home/jan/lighttpd-0.1.0/servers/", {st_mode=S_IFDIR|0755, st_size=512, ...}) = 0
+ fstat(6, {st_mode=S_IFREG|0644, st_size=4348, ...}) = 0
+ write(5, "HTTP/1.0 200 OK\r\nConnection: clo"..., 235) = 235
+ write(2, "1064875136: (network.c.210) 235 "..., 33) = 33
+ syscall_393(0x6, 0x5, 0, 0, 0x10fc, 0, 0xbfbff2c0, 0) = 0
+ close(5) = 0
+
+ /* no futher waiting connections */
+
+ accept(3, 0xbfbff700, [1852702730]) = -1 EAGAIN (Resource temporarily unavailable)
+
+ /* enter the main-loop */
+
+ gettimeofday({1769235301, 1663069807}, NULL) = 0
+ poll([{fd=3, events=POLLIN}], 1, 1000) = 0
+ gettimeofday({4294967295, 65537}, NULL) = 0
+ poll([{fd=3, events=POLLIN}], 1, 1000) = 0
+ gettimeofday({4294967295, 65537}, NULL) = 0
+ poll([{fd=3, events=POLLIN}], 1, 1000) = 0
+ ...
+
+ (strace is broken for accept() and gettimeofday(), syscall_393() is sendfile())
+
+ after entering the main-loop the webserver doesn't receive any new POLLIN
+ events at all for the next 10-15 seconds. Any connection-attempt within
+ the period is, as you can see in the tcpdump output, accepted by the
+ kernel and the received data is thrown away as the kernel sends a RST.
+ After those 10-15 seconds the application gets a POLLIN event for the
+ server socket and the normal data-transfer taken place for the next 4000
+ requests.
+
+ This behaviour is reproducable with thttpd 2.20c and the current lighttpd.
+
+
+26.09.2003 11:42
+- localizer
+
+ ported the localizer-server application into a module for lighttpd. After
+ 30 minutes the code was ported and funtional. This modules provides access
+ to the localizer-db by a HTTP-Interface and generates the response as
+ HTML, CSV and plain-text.
+
+ So far, the module-interface looks good and flexible enough.
+
+ 7000 req/s is good enough too. Generating the HTML and querying the DB
+ needs some time.
+
+22.09.2003 08:40
+- modules
+
+ introduced a simple module interface which allow to hook into the process
+ of handling the requests. Basicly it allows to move the url-rewriter, the
+ auth-sub-system and the cache out of the main-code. The module-interface
+ will be extended to allow the cgi and the fastcgi sub-processes to be
+ moved into a module.
+
+15.09.2003 09:36
+- error-handling
+
+ fastcgi and cgi connections are now closed correctly if the corresponding
+ client-connection has died.
+
+14.09.2003 10:40
+- cgi
+
+ finally streaming works with CGI, too
+
+ this enable support for cgiirc.sf.net and friends which use streaming as
+ there transport mechanism.
+
+ streaming has been verified with cgiirc-0.5.2.
+
+- fdevents
+
+ there where some reports that sigio didn't work as expected. It just
+ reported no events at all. Looks like a known bug in the glibc on
+ those platforms.
+
+ Using poll() instead solve the problems.
+
+27.08.2003 22:12
+- rewrite
+
+ a pcre based rewrite engine has been integrated
+
+- cgi
+
+ the file-based cgi interface has been replace by two pipes.
+
+23.07.2003 13:29
+- fdevents
+
+ the whole fd-event handling has been reworked and several smaller bugs
+ and design-errors have fixed.
+
+ sigio, poll() and select() are working fine again.
+
+ On IRIX 6.5 SIGIO could be used, but without further testing poll() is used.
+
+- cgi
+ CGI-support is still broken.
+
+21.07.2003 18:46
+- dnotify
+
+ using the F_NOTIFY feautre of the Linux 2.4.x kernel gives anothre nice
+ performance boost as lighttpd can cache the stat()'s the right way, now.
+
+ 23009.66 @ 5-4k-k which means +10% against 30.06.2003 14:03
+
+ for 1000-4k-nok the performace nearly doubles: 3730.23 goes up to 6144.39
+
+17.07.2003 13:21
+- FreeBSD
+
+ a small patch (#include &lt;&gt;) to compile lighttpd on FreeBSD again.
+
+08.07.2003 10:48
+- fileinfo-cache
+
+ A reallife test showed that the cache wasn't perfect at all as it made
+ lighttpd crash. This is fixed now.
+
+30.06.2003 17:40
+- lighttpd-bench
+
+ After some problems with other benchmarking tools for webservers the first
+ version of lighttpd-bench has been written.
+
+ It a revealed a nasty strange behaviour which was fixed by increasing the
+ listen-backlog from 5 to 1024.
+
+30.06.2003 14:03
+- fileinfo-cache
+
+ the fileinfo-cache has been relaxed a little bit and there are always 2
+ fstat()'s per file-request now. This isn't that dramatic:
+
+ 21800.74 req/s is still a very good result (7-4k-k).
+
+29.06.2003 03:29
+- fileinfo-cache
+
+ a fileinfo cache has been added to reduce the number of system-calls to
+ stat and open a file.
+
+ in the releases before the same file was stat'ed at least 2 times plus a
+ stat on the docroot for each request. Now the stat()'s and the
+ corresponding open() + close() calls are cached and the number of system
+ calls has been reduced to the minimum:
+
+ $ strace -eopen,stat64,read,write,sendfile,accept,shutdown,close \
+ -p `pidof lighttpd`
+
+/* first connection */
+accept(3, {sin_family=AF_INET6, sin6_port=htons(56211),
+ inet_pton(AF_INET6, "::ffff:192.168.2.10", &amp;sin6_addr), sin6_flowinfo=0,
+ sin6_scope_id=0}, [28]) = 5
+accept(3, 0xbffff470, [28]) = -1 EAGAIN
+read(5, "GET /index.html HTTP/1.0\r\nUser-A"..., 4095) = 91
+stat64("/home/weigon/projects/lighttpd/servers/grisu.home.kneschke.de:1025/pages/",
+ {st_mode=S_IFDIR|0755, st_size=3656, ...}) = 0
+stat64("/home/weigon/projects/lighttpd/servers/grisu.home.kneschke.de:1025/pages//index.html",
+ {st_mode=S_IFREG|0644, st_size=4348, ...}) = 0
+open("/home/weigon/projects/lighttpd/servers/grisu.home.kneschke.de:1025/pages//index.html",
+ O_RDONLY) = 6
+write(5, "HTTP/1.0 200 OK\r\nConnection: clo"..., 235) = 235
+sendfile(5, 6, [0], 4348) = 4348
+shutdown(5, 1 /* send */) = 0
+close(5) = 0
+
+/* second connection */
+accept(3, {sin_family=AF_INET6, sin6_port=htons(56212), inet_pton(AF_INET6,
+ "::ffff:192.168.2.10", &amp;sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28])
+ = 5
+accept(3, 0xbffff470, [28]) = -1 EAGAIN
+read(5, "GET /index.html HTTP/1.0\r\nUser-A"..., 4095) = 91
+write(5, "HTTP/1.0 200 OK\r\nConnection: clo"..., 235) = 235
+sendfile(5, 6, [0], 4348) = 4348
+shutdown(5, 1 /* send */) = 0
+close(5) = 0
+
+
+ In the end we have a new speed record:
+
+ (ab -c 8 -n 100000 http://192.168.2.10:1025/index.html with poll())
+ Requests per second: 23435.67 [#/sec] (mean)
+
+ This is a speed increasement of 20-30% against the last internal benchmark.
+
+
+28.06.2003 02:30
+- caching framework
+
+ the trigger.if directive is working fine. the basic algebra is done ( +,
+ -, *, / and comperations =, &lt;=, &gt;=, &gt;, &lt; and the boolean logic
+ &amp;&amp; and || including braces)
+
+ two basic function are available:
+ - unix.time.now
+ - file.mtime(...)
+
+25.06.2003 17:33
+- caching framework
+
+ added output.include, output.content-type for a cache-hit and
+ trigger.handler for a cache-miss.
+
+ the actual decision is made be trigger.if which will be added tomorrow.
+
+15.06.2003 15:33
+- sig-io
+
+ After several other smaller optimisations lighttpd performs better with
+ sig-io under high load:
+
+ sigio+poll poll
+ c1000-4k-nok : 2635.95 1643.39
+ c1000-4k-k : 7335.68 6788.87
+ c1000-100k-nok: 2353.49 1217.73
+ c1000-100k-k : 3097.89 2080.73
+
+
+ The user-space part has been optimized a lot. Now it is time to optimized
+ the number of context-switches between user and kernel-mode.
+
+ user 0m0.180s
+ sys 0m1.160s
+
+ a fileinfo-cache is the way to go.
+
+ struct {
+ buffer *name;
+ struct stat st;
+ int handler;
+ ...
+
+ int fd;
+ void *mmap_p;
+ }
+
+11.06.2003 14:57
+- sig-io is back again
+
+ and it works fine.
+
+ sigio (Realtime-Signals under Linux 2.4.x) sends one signal per event and
+ buffers the rest of the events in a kernel queue. If the is full a SIGIO
+ is sent and poll() gets all events at once for further processing.
+
+
+ Currently the behaviour is a little bit strange:
+ - sig-io + poll() is good for non-keep-alive connections
+ - poll() for keep-alive connections
+
+ c1000-4k-nok: (1000 concurrent request, 4k filesize, no keepalive)
+ poll : 1521.38
+ sigio+poll: 2124.00
+
+ c1000-4k-k: (1000 concurrent request, 4k filesize, keepalive)
+ poll : 5882.35
+ sigio+poll: 1239.46
+
+ Very strange for now.
+
+09.06.2003 23:59
+- code-cleanup
+
+ the event-handling code has been rewritten to handle single events better
+ as they are expected from sig-io.
+
+ the fallback-mode of sig-io is broken now, but the normal poll() mode got
+ a 10% increasement in speed. This means the we are back the speed level of
+ 20030308-0155 are as fast as zeus again.
+
+ Especially under higher load the current lighttpd performes better.
+
+09.06.2003 11:51
+- sig-io benchmark
+
+ 20030609-1151 20030608-2110 20030308-0155
+-c 10 sigio select() poll() poll()
+4k : 7870.92 7937.77 8035.36 9443.76
+4k (keep-alive) : 14098.41 14590.02 14275.52 17985.61
+100k : 3366.32 3382.03 3261.15 3722.32
+100k (keep-alive) : 5544.77 5576.00 5573.20 5975.86
+-c 100
+4k : 6144.77 5821.40 5714.29 6724.95
+4k (keep-alive) : 9097.53 9213.19 8979.08 10833.06
+100k : 2549.33 2495.94 2318.95 2607.36
+100k (keep-alive) : 4267.67 4283.94 4094.17 4314.06
+
+
+For -c 100 4k and 100k sig-io gives a small increasment.
+
+09.06.2003 01:00
+- sig-io
+
+ writing large files works now as expected. After removing the limit for
+ the chunks of sendfile the write-buffer-is-empty-again signal is
+ generated. that was missing.
+
+ 321 of 10000 connections still have the wrong length. After that is fixed
+ it is time to start some benchmarks again.
+
+08.06.2003 21:10
+- sig-io
+
+ first attempts in getting SIGIO support running which were not very
+ successfull yet.
+
+- poll()/select() benchmark
+
+ 20030608-2110 20030525-1623 20030308-0155
+-c 10 select() poll() poll() poll()
+4k : 7937.77 8035.36 8166.60 9443.76
+4k (keep-alive) : 14590.02 14275.52 14781.97 17985.61
+100k : 3382.03 3261.15 3176.42 3722.32
+100k (keep-alive) : 5576.00 5573.20 5809.56 5975.86
+-c 100
+4k : 5821.40 5714.29 5669.26 6724.95
+4k (keep-alive) : 9213.19 8979.08 8418.22 10833.06
+100k : 2495.94 2318.95 2314.28 2607.36
+100k (keep-alive) : 4283.94 4094.17 4456.92 4314.06
+
+
+ as the overall processing cycle has been rearranged the overall figures
+ changed in minor ranges. c100-4k-k increased, *-100k-k decreased.
+
+ At least it still works.
+
+08.06.2003 12:45
+- select()/poll()
+
+ implementate an abstration layer for fd-events (like eevry other webserver).
+ currently we support poll() and select().
+
+ This should bring us support for Mac OS X and propably Windows.
+
+04.06.2003 18:35
+- configure
+
+ lighttpd can now be build without ssl-support even if openssl is installed
+
+- protability
+
+ - on IRIX IPv6 is supported now out of the box
+ - for FreeBSD some missing haeders have been added
+
+04.06.2003 14:34
+- pipelining
+
+ adding support for pipeling introduces a problem if the request-header
+ was broken. this has been fixed now.
+
+- unneccesary slashes
+
+ when building pathes from different portions of a string (e.g. docroot +
+ virt-server-docroot + request-uri) slashes where added for security and
+ simplicity. This resulted in up to 5 adjacent slashes which caused no harm
+ but looked strange. (fixed)
+
+04.06.2003 09:57
+- start/stop messages
+
+ the error-log contains the start and end-times of the lighttpd process,
+ now.
+
+- configfile
+
+ the config-file parser has been relaxed to accepts tabs instead of spaces.
+
+- better error-handling
+
+ fixed a <a
+ href="http://bugs.php.net/?id=24009">bug in the FastCGI-SAPI of PHP</a>
+
+ if the fastcgi process dies or closes the connection unexpectedly we
+ return 500 now instead of closing the connection to the client.
+
+- Location
+
+ the CGI/1.1-rev-03 specification requires us to send Status 302 if a
+ Location-header is sent by the client and a Status-header is missing.
+
+- PATH_TRANSLATED
+
+ if PHP is compiled without --discard-path PATH_TRANSLATED has to be
+ provided.
+
+27.05.2003 15:54
+- directory listings
+
+ if a directory is requested and the directory doesn't contain a index-file
+ a directory-listing can be displayed. You have to enable directory-listings
+ in the config-file (directory-listings on)
+
+- url-decoding
+
+ up to know urls where not decoded at all (%26 -> . and so on). This has
+ been added. Unicode isn't supported as we use 8-bit chars internally.
+
+26.05.2003 00:44
+- pipelining
+
+ as Sascha required pipelining for his benchmarking tool it has been
+ implemented.
+
+ pipelining allows sending a bunch of requests at once without waiting for
+ the actual responses. This reduces the network-overhead and the
+ round-trip-time.
+
+- non-free()d memory
+
+ dmalloc helped to close some non-free()d memory. For the normal operation
+ this isn't important as only memory chunks which had to be free()d at the
+ end of the live-time of the lighttpd-process were not de-allocated.
+
+- partly-initialized variables
+
+ lighttpd wasn't initializing the main-structure which resulted in strange
+ behaviour in rare circumstances.
+
+
+25.05.2003 16:23
+- benchmarks
+
+ after removing some useless internal copies we are more or less at the old
+ speed levels.
+
+ after adding virtual-hosts 10% of the performance were lost. Using less
+ memcpy() operations might add several other boosts.
+
+ I've just checked how lighttpd compares to Zeus.
+
+ lighttpd (current) (old)
+-c 10 20030525-1623 20030308-0155 Zeus 4_2
+4k : 8166.60 9443.76 7278.55
+4k (keep-alive) : 14781.97 17985.61 16496.21
+100k : 3176.42 3722.32 3156.37
+100k (keep-alive) : 5809.56 5975.86 5460.30
+-c 100
+4k : 5669.26 6724.95 5134.26
+4k (keep-alive) : 8418.22 10833.06 8010.25
+100k : 2314.28 2607.36 2688.32
+100k (keep-alive) : 4456.92 4314.06 4240.70
+
+23.05.2003 14:38
+- cgi-variables
+
+ HTTP_HOST was missing for the cgi-module while the fcgi-module passed it
+ through to the handler. Fixed that.
+
+- fcgi-errors
+
+ the connection to the fcgi was dropped and poll() reported an error, the
+ error wasn't reported to the client the right way.
+
+22.05.2003 23:02
+- authorization
+
+ the first password-storage has been added:
+
+ [auth]
+ backend plan
+ plain-userfile &lt;filename&gt;
+
+ require /download/ user=jan|user=anom
+ http-auth /download/ "download archiv" digest
+
+ groups are prepared but not implemented. basic and digest are working fine.
+
+20.05.2003 17:53
+- authentification
+
+ The auth-methods from RFC 2617 have been added.
+ - auth basic
+ - auth digest
+
+ The only source for accounts is currently only the config-file.
+
+ auth-digest needs the plain-text passwort. Are there any source which
+ provide a plain-text password ?
+
+12.05.2003 14:33
+- virtual hosts
+
+ added very basic virtual-host support
+
+ virtual-server-root /home/weigon/projects/lighttpd/servers/
+ virtual-server-default-host grisu.home.kneschke.de:1025
+ virtual-server-docroot /pages/
+
+ docroot is
+
+ - if http-host exists
+ &lt;virtual-server-root&gt; + &lt;http-host&gt; + &lt;virtual-server-docroot&gt;
+
+ - otherwise
+ &lt;virtual-server-root&gt; + &lt;virtual-server-default-host&gt; +
+ &lt;virtual-server-docroot&gt;
+
+ - if even virtual-server-default-host does not exist, 500 is sent
+
+
+12.05.2003 13:02
+- code cleanup
+
+ After two month of development it was time clean-up the internal
+ structures. It looks like every went fine as lighttpd works es expected
+ like before.
+
+- deflate
+ the on-the-fly compression has been verified to work fine with opera,
+ konqui, mozilla and the IE.
+
+12.05.2003 02:10
+- on-the-fly compression: deflate
+
+ Why the hell are the defining a "deflate" encoding in the form of
+
+ _deflate_ The "zlib" format defined in RFC 1950 [31] in combination
+ with the "deflate" compression mechanism described in RFC 1951 [29].
+ (RFC 2616)
+
+ and noone implements it that way ? Konqui and Mozilla expect a plain
+ deflate() package without the zlib-header.
+
+ Konqui is using "inflate2(..., -MAX_WBITS); " which is noted in the zlib
+ source as
+
+ /* handle undocumented nowrap option (no zlib header or check) */
+
+ Funny. Very, very funny.
+
+
+ Anyway. We have mimetype-depended compression support now.
+
+11.05.2003 21:56
+- logging
+ re-arranged the logfile structure to write CLF + useragent + referrer.
+
+11.05.2003 10:23
+- POST file-upload
+ added the missing functionality to send more than a single packet to the
+ FCGI-Server (or Client ? ... the PHP).
+
+ This gives us the file-upload thing for eg. PHP and large user-forms
+ (&gt;16kb).
+
+05.05.2003 15:21
+- PATH_INFO
+
+ added support for PATH_INFO. PHP is a little strange and doesn't trust the
+ passed PATH_INFO setting. Works fine now.
+
+30.04.2003 15:25
+- bug-fixing day
+
+ While testing the FastCGI interface with the MSIE Björn Schotte discovered
+ that sometimes the output repeats itself from the start in an endless loop.
+ This bug has been fixed.
+
+ The read-write-fallback for ancient systems seeked the wrong FDs.
+
+ The FastCGI handler is now separting the HTTP-Header from the
+ response-body what results in a cleaner interface. The "header too long
+ for caching" message is gone now.
+
+28.04.2003 18:18
+- chunked transfer-encoding
+
+ The FastCGI part is now using Chunked-Transfer-Encoding if HTTP/1.1 is
+ used and no content-length is specified.
+
+27.04.2003 23:10
+- chunked transfer-encoding
+ added "Transfer-Encoding: chunked" which is currently used in the web-chat
+ for the endless stream. Perhaps it helps some browsers.
+
+ The FastCGI interface will get a the chunked-support too, as it will
+ enable keep-alive even if no content-length is returned from the FastCGI
+ process. We know the size of the chunks and will report it to the browser.
+
+16.04.2003 12:02
+- gigE deatchmatch
+
+ sascha compared the current lighttpd to his premium thttpd in his
+ gigabit-Ethernet-Network.
+
+ <a
+href="http://schumann.cx/gbit_deathmatch.txt">http://schumann.cx/gbit_deathmatch.txt</a>
+
+ small comment:
+ lighttpd provides the same performance (req/s and thoughput), but uses more
+ CPU-Time.
+
+10.04.2003 17:22
+- works on IRIX
+
+ Jörg Behrens provided me login to his SGI Origin and after fixing two small
+ typos it worked fine on IRIX. (#if define HAVE... was missing a 'd' and
+ getopt returns a 'int' and not a 'char')
+
+ IPv6 support is currently disabled for IRIX a gethostbyname2() isn't
+ available.
+
+10.04.2003 15:56
+- another bug-fixing day
+ The upper limit of open connections was enforced and a caused a seg-fault.
+ The current limit is set to 4096 parallel connections.
+
+ As sascha benchmarked lighttpd and his premium thttpd I tried to reproduce
+ his reported results and had to fix 2 flaws in the async-io handling of
+ httpd_load which was used for the testing.
+
+ If sascha starts another benchmark session I'll put a link to it here.
+
+09.04.2003 00:08
+- bug-fixing time
+ Date: and Last-Modified: where sending a timezone != GMT what was invalid.
+ The timestamp itself was correct, just the timezone use the wrong
+ characters.
+
+ the fcgi-code had an buffer-overflow for larger POST-Requests.
+
+ The fcgi-code still has problems with POST-Request larger than 16kb as the
+ the write buffer is full. Currently we don't handle this case except from
+ reporting it in the error-log.
+
+02.04.2003 01:17
+- cgi is back
+
+ The CGI Interface wasn't tested for a longer time. It don't really
+ survived the introduction of the config-file handling and the internal
+ changes that were part of it.
+
+ A small test with
+
+#! /usr/bin/perl
+print "Content-Type: text/html\r\n\r\n";
+print time()."\n";
+0;
+
+ produced 219 req/s.
+
+ Calling PHP via the CGI interface results in 100 req/s. Same script, same
+ parameters for 'ab' and same PHP result in 1400 req/s if we use the
+ FastCGI interface.
+
+ <b>Don't forget</b>: these benchmarks only represent figures for scenarios which
+ are not very realistic. They are just usefull for comparisions of the
+ internals. We don't want to benchmark applications.
+
+01.04.2003 23:04
+- new benchmarks
+
+ This time we wanted to see if we can get the fastcgi-Interface a little bit
+ faster. We use this small script for the testing the interface.
+
+&lt;?php
+
+ob_start(/*"ob_gzhandler"*/);
+print "12345&lt;br /&gt;\n";
+header("Content-Length: ".ob_get_length());
+ob_end_flush();
+
+?&gt;
+
+ It generate a small network load and is the best case for benchmarking the
+ overhead of the call to the fastcgi-php.
+
+ Using the chunk-API instead auf tmp-files increase the req/s vom 1200req/s
+ to 1600 req/s and the IO-load went down as expected. Waiting for the
+ filesystem and creating 1200 files per second required some time. The
+ CGI-Interface is still using tempfiles.
+
+01.04.2003 19:28 (no april fools joke)
+- added SSL support
+ I took -lssl and -lcrypto and added SSLv2/SSLv3/TLS support to lighttpd.
+
+ After some reading I realisized that libcrypto contained some of my code.
+ They have a similar buffer-struct with similar functions and they use the
+ same MD5-code from the RFC. :)
+
+ Adding basic SSL-support was quite esay: replacing all write/read-calls by
+ SSL_write/read, initializing the ssl-context the right way and telling
+ openssl where to get the data from (SSL_set_fd). It fits really well into
+ lighttpd.
+
+- sidenote
+ sooner or later the con->filename handler will vanish in favour of the
+ direct interface to the chunk-API which will simplifiy the design a little
+ bit.
+
+31.03.2003 20:50
+- added a web-chat module
+ 1400 lines of C-code are neccesary to add a web-chat to lighttpd.
+
+ Features:
+ - bb-code for text-layouting
+ - auto-highlight for URLs and Email-adresses
+ - unlimited number of channels
+ - max. 32.000 users per channel
+ - fast as it is directly integrated into the web-server
+ - easy to install (as easy as lighttpd)
+ - IRC-like (/msg, /kick, /me, ...)
+ - works with MS IE, Mozilla, Konqueror, ...
+ - uses as little JavaScript as possible to stay compatible with all browsers
+ - flood-protection (2 Levels: ignore and kick)
+ - uses CSS for customized look-and-feel
+
+25.03.2003 00:36
+- finished the config-file support
+ now we have config-sections
+
+ [fastcgi]
+ fastcgi .php 192.168.2.76 1025
+
+ The only this missing is -HUP handling to re-read the config. But that
+ will follow when I'm up again.
+
+20.03.2003 01:04
+- adding config-file support
+ the most boring part of the writing programs after writing documentation.
+ The format is quite simple and good enough for lighttpd.
+
+19.03.2003 03:05
+- more on sendfile support
+ added support for freebsd's version of sendfile().
+
+ Linux:
+ ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
+
+ FreeBSD:
+ int sendfile(int fd, int s, off_t offset, size_t nbytes,
+ struct sf_hdtr *hdtr, off_t *sbytes, int flags);
+
+ the first 2 params are swap, the next 2 are the same and the last 3 are
+ set to NULL or 0.
+
+ Solaris 9 is providing a direct interface to the chunk-API with the
+ sendfilev() system call:
+
+ ssize_t sendfilev(int fildes,
+ const struct sendfilevec *vec, int sfvcnt, size_t *xferred);
+
+ recovering from a failure is a little bit more complex, but it should be
+ fast. I just need a system to test it. Anyone ?
+
+18.03.2003 17:32
+- in the news
+ what a surprise: lighttpd is announced in entwickler.com.
+
+ <a href="http://www.entwickler.com/itr/news/psecom,id,9483,nodeid,82.html">http://www.entwickler.com/itr/news/psecom,id,9483,nodeid,82.html</a>
+
+ this was not planned.
+
+
+- implemented the chunked-API
+ rather writing the content of multi-range'd requests to a temp-file we
+ just register the parts in a chunk-queue.
+
+ chunk 1 [mem] - part-header
+ chunk 2 [file] - source-file (offset, len)
+ chunk 3 [mem] - part-header
+ chunk 4 [file] - source-file (offset, len)
+ chunk 5 [mem] - part-footer
+
+ If everything is prepared, the content-length is calculated and the
+ http-header is generated and prepended to the chunk-queue.
+
+ the handle_write() function which handles the write-process just sends the
+ chunks to the network without only further modifications.
+
+ the chunk-api even simplifies the
+ 'my-fastcgi-process-needs-some-time-to-create-the-response-
+ and-sends-the-data-in-chunks' case. Just add the chunk to the queue if it
+ is received.
+
+13.03.2003 10:49
+- another set of real-life benchmarks
+
+ the chairman was benchmarked a little bit. Chairman is full-flegded WCMS.
+ As every CMS it needs some time to generate pages. To speed the page
+ generating the static-parts of the page can be cached internally.
+
+ without caching with caching
+apache + mod_php : 2.50 10.64 req/s
+lighttpd + fcgi-php: 2.50 22.74 req/s
+
+ Looks like lighttpd is the way to go :)
+
+ Please, don't compare these numbers with the other benchmarks:
+ We used a different test-machine which is a little bit under-powered
+ for this test-case.
+
+12.03.2003 13:43
+- another large application works with lighttpd
+
+ <a
+href="http://www.thinkphp.de/content/content2.php?CatID=44&amp;NewsID=95">Chairman</a> from <a href="http://www.thinkphp.de/">thinkphp</a> has been verified to work with lighttpd.
+
+ Three things are worth to note:
+
+ 1. ALWAYS use log_error in the php.ini if you use FastCGI
+ otherwise it will put the errormessages directly into fastcgi-socket
+ without any FastCGI Header. This will result in strange errors.
+ (its a PHP bug)
+ 2. DOCUMENT_ROOT has been added to the environment variables
+ 3. a small bug for sending larger output from a fastcgi client has been
+ fixed.
+
+11.03.2003 11:52
+- added two neccesary features
+ - redirect _dir_ to _dir_/
+ - append index-file to _dir_/ if they exist
+
+11.03.2003 00:13
+- another target
+ after adding some headers lighttpd compiles without any warnings
+ under cygwin on Windows.
+
+ A native windows port will take some time as I have to figure out the
+ changes on the winsock.h to the unix headers.
+
+10.03.2003 11:52
+- added Solaris 8 and 9
+ lighttpd has been confirmed to compile and work on
+
+ - linux
+ - FreeBSD, NetBSD
+ - Solaris 8, 9
+
+ Perhaps I can get lcc to compile it on windows.
+
+10.03.2003 01:30
+- another platform was confirmed
+ lighttpd works on NetBSD
+
+- another webserver benchmark
+ mathopd (http://www.mathopd.org/)
+
+ Mathopd/1.4
+-c 10
+4k : 6329.11 [#/sec]
+4k (keep-alive) : 10235.41 [#/sec]
+100k : 1168.50 [#/sec]
+100k (keep-alive) : 268.82 [#/sec] (99% idle)
+-c 100
+4k : ---- (connections dropped)
+4k (keep-alive) : ----
+100k : ----
+100k (keep-alive) : ----
+
+Very good values for small files, but the rest ?
+
+10.03.2003 00:06
+- added more write-handlers
+ 1. sendfile (linux only)
+ 2. mmap + write
+ 3. read + write
+
+ This means that lighttpd runs on some more systems. It has been verified
+ to compile and work on linux 2.4.x and FreeBSD.
+
+
+-c 100
+ sendfile mmap read
+4k 6476.68 5698.01 5363.08 [#/sec]
+100k 2312.35 841.54 783.09 [#/sec]
+
+ The mmap() numbers could be better with a fd+mmap-cache. The zero-copy thing
+ is good for our performance.
+
+- tested another small httpd - BOA
+ just to get a better view about the performance of lighttpd i've tested
+ another httpd: Boa/0.94.14rc16 (http://www.boa.org/)
+
+ Boa/0.94.14rc16
+-c 10
+4k : 5659.63 [#/sec]
+4k (keep-alive) : 250.23 [#/sec]
+100k : 1104.29 [#/sec]
+100k (keep-alive) : 1363.14 [#/sec]
+-c 100
+4k : 4319.65 [#/sec]
+4k (keep-alive) : 2490.66 [#/sec]
+100k : 815.93 [#/sec]
+100k (keep-alive) : 1007.05 [#/sec]
+
+ The results are a little bit strange. Keep-Alive for small files seems to be
+ broken.
+
+ It looks like lighttpd has enough power for now. Time the port and add more
+ features.
+
+09.03.2003 13:26
+- load-balancing works great
+
+ I used the framework from http://framework.netuse.de/ as a
+ real-life example again to test the load-balancer.
+
+ setup:
+ - grisu
+ - AMD athlon XP 2000+
+ - runs lighttpd + ab + a 'nice -15'ed FastCGI-PHP
+ - generates 153 req/s alone
+
+ - laptop
+ - Intel PIII 850
+ - runs a FastCGI-PHP
+ - generates 88 req/s
+
+ using the internal load-balancer of lighttpd which balances the
+ php-requests over grisu and laptop generate:
+ 221 req/s
+
+ An apache with mod_php running on grisu gives 117.04 req/s only.
+
+09.03.2003 12:46
+- building the connect connection to the fcgi-php is not non-blocking
+ socket() -> connect() -> fcntl(non-blocking) resulted in some problems as
+ the connect() call blocked sometimes for 1 seconds.
+
+ socket() -> fcntl(non-blocking) -> connect() solves this problem, but
+ addes more overhead. The first connect-attempt seems always to
+ return EINPROGRESS.
+
+08.03.2003 15:06
+- the first 'real-life' PHP test showed 2 bugs in the POST handling.
+ 1. the Content-Type header was not forwarded via fastcgi
+ 2. the internal content_length handler wasn't reset after the request
+
+ now lighttpd + php work with
+
+ http://framework.netuse.de/
+
+ the basic application gives us 145 req/s
+
+08.03.2003 11:06
+- use diet to 'test' your code
+ diet gives some use full warnings which should be followed. I took the
+ chance and removed the last few fprintf() and sprintf() from the code.
+ now, we don't need stdio.h anymore.
+
+ a staticly linked, stripped lighttpd is 42884 bytes large.
+
+08.03.2003 01:55
+- buffer_strcat() uses strlen() to get the length of the 'to-be-catted-string'
+ if the string is constant or has a known length you can pass that function
+ which gives use buffer_strcat_len() which passes the string length as a
+ parameter.
+
+- using a ramdisk as the base for the tempfiles generated mkstemp() is a
+ very cood idea
+ it reduces the io-load on the system and gives as more cpu-time for
+ load-balancing the php-requests.
+
+- cache the output of strftime(), gmtime() and localtime
+ we generate more then 10.000 req/s which results in using the same
+ timestamp over and over again. It is sufficient to generate the timestamp
+ once a second and give us a bunch of CPU-cycles for sending files.
+
+ the same applies for other timestamps like Last-modified which sends a
+ string version of st.st_mtime. Cache it.
+
+- don't try to overoptimize for code for the sake of clean code
+ the range support require some hack in the first versions like setting the
+ write_offset to the start of the range and keeping the rest of the
+ "code 200" send code as usual. Only a few lines added this first range
+ feature. fast, but a hack. It made things more complicated then neccesary
+ to add multi-range support in this scenario.
+
+ removing the hack doing small clean preprocessing helps to clean up the
+ whole 'write a chunk on the wire' code. At the end we use less code and
+ have a cleaner design.
+
+ Sascha proposed a chunked layer which just takes the chunks (http-header,
+ part-header, part-message, ... and the last boundary) and passes them to
+ the write-to-the-wire-code. This is far more elegant and will perhaps same
+ us from other problems, too. Currently I think that it is not necessary.
+ First we have to fix some protability issues.
+
+Ok, today benches:
+
+ today last
+-c 10
+4k : 9443.76 7739.94 + 22%
+4k (keep-alive) : 17985.61 13885.03 + 29%
+100k : 3722.32 3349.97 + 11%
+100k (keep-alive) : 5975.86 4965.49 + 20%
+-c 100
+4k : 6724.95 5918.56 + 13%
+4k (keep-alive) : 10833.06 8405.48 + 28%
+100k : 2607.36 2393.60 + 9%
+100k (keep-alive) : 4314.06 4035.35 + 7%
+
+Top Transfer Rate:
+
+ 86365.77 [Kbytes/sec] received
+
+Too much for a GigE-link ?
+
+
+so far:
+
+- use state-engines
+
+ a non-forking webserver like described in
+
+ http://www.kegel.com/c10k.html
+
+- don't use fprintf() for logging.
+ writing one line to log file takes more time that sending responsing the a
+ HTTP-request
+
+- take care of your memory
+
+ typedef struct {
+ char *ptr;
+ size_t used;
+ size_t size;
+ } buffer;
+
+ buffer* buffer_init();
+ void buffer_free(buffer *b);
+ int buffer_prepare_copy(buffer *b, size_t size);
+ int buffer_prepare_append(buffer *b, size_t size);
+ int buffer_strcpy(buffer *b, const char *s);
+ int buffer_strcpy_len(buffer *b, const char *s, int s_len);
+ int buffer_strcat(buffer *b, const char *s);
+ int buffer_strcat_len(buffer *b, const char *s, int s_len);
+
+ buffer_init() is only called once per buffer. If you don't need the
+ buffer, set 'used' to zero and reused it afterward.
+ buffer_strcpy() + buffer_strcat() check if the 'used' + the new strlen()
+ fit in to the 'size' of the buffer. If not the realloc() the buffer.
+ buffer_strcat() uses memcpy(ptr + used - 1, s, s_len + 1); which should
+ be faster than the original strcat().
+
+ Using those buffers keep the memory usage at 2Mb. malloc() and free() are
+ only called at the beginnig of the programm and at the end.
+
+- don't used sprintf() to convert a integer into a string as like
+ sprintf(buf, "%d", l);
+
+ write your own ltostr function. sprintf() is to general for the case and
+ is slow.
+
+
+Speed:
+------
+
+Let's assume that <a
+href="http://www.acme.com/software/thttpd/">thttpd</a> is (one of) the
+fasted webservers out there.
+
+all httpds were started with:
+
+$ ./thttpd -p 1026 -D \
+ -d /home/weigon/projects/localizer/src/lighttpd/docroot/ \
+ -l thttpd.access.log
+
+(lighttpd uses the same parameters).
+
+We used ApacheBench for testing the RPS (requests per second).
+
+$ /usr/sbin/ab -dS -c 10 -n 100000 http://192.168.2.10:80/dummy.out
+
+ lighttpd thttpd/2.21b+php thttpd/2.20c thttpd/2.23b1
+(concurrency 10)
+4k : 7739.94 6040.84 5078.20 5888.24 [#/sec]
+4k (keep-alive): 13885.03 10349.82 5034.49 5853.09 [#/sec]
+100k : 3349.97 1176.29[1] 1188.74 1198.29 [#/sec]
+100k (keep-a.) : 4965.49 2513.38[2] 1100.65 1130.51 [#/sec]
+(concurrency 100)
+4k : 5918.56 4907.01 4987.53 4886.87 [#/sec]
+4k (keep-alive): 8405.48 6379.99 4938.76 4816.26 [#/sec]
+100k : 2393.60 972.73 958.27 961.09 [#/sec]
+100k (keep-a.) : 4035.35 893.51[3] 970.21 955.05 [#/sec]
+
+thttpd/2.21b+php has been patched with the keep-alive + php patches from PHP4
+sapi/thttpd/thttpd_patch.
+
+[1] ab reported:
+Failed requests: 1
+ (Connect: 0, Length: 1, Exceptions: 0)
+[2] ab reported:
+Failed requests: 5
+ (Connect: 0, Length: 5, Exceptions: 0)
+[3] ab reported:
+Failed requests: 24
+ (Connect: 0, Length: 24, Exceptions: 0)
+
+Tunning the thttpd:
+-------------------
+- disable symlink checking (chroot() or -nos)
+- log to /dev/shm/logfile, a ramdisk or disable logging
+
+
+$ ./thttpd -p 1026 -D \
+ -d /home/weigon/projects/localizer/src/lighttpd/docroot/ \
+ -l /dev/null
+
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 00000000..0f78d7f8
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,32 @@
+
+============
+Installation
+============
+
+:author: Jan Kneschke
+:Date: $Date: $
+:Revision: $Revision: $
+
+Installation
+------------
+
+Get the source from
+
+http://www.incremental.de/products/lighttpd/download/
+
+unpack it by ::
+
+ $ gzip -cd lighttpd-1.0.0.tar.gz | tar xf -
+
+compile and install it with ::
+
+ $ cd lighttpd-1.0.0
+ $ ./configure
+ $ make
+ $ su -
+ # make install
+ # exit
+
+take look at the configfile in ./doc/lighttpd.conf,
+make your own copy of that file and modify it for your needs.
+
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 00000000..a92affeb
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS=src doc tests debian cygwin openwrt
+
+EXTRA_DIST=lighttpd.spec
diff --git a/NEWS b/NEWS
new file mode 100644
index 00000000..bc5882d0
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,316 @@
+
+====
+NEWS
+====
+
+- 1.3.10 - 2005-02-06
+
+ * added support for full commandline in spawn-fcgi
+ * fixed missing check for IP-address in mod_fastcgi
+ * fixed compile error with openssl in mod_fastcgi
+ * removed a debug-message from network_freebsd_...
+
+- 1.3.9 - 2005-02-06
+
+ * added a stricter URI parser
+ * added a check to the CGI spawner if the cgi-handler exists
+ * added documentation for SSL and mod_status
+ * added handling of startup environment to FastCGI
+ * improved performance in FastCGI in buildind the FastCGI header
+ * fixed min-procs and max-procs in FastCGI on PowerPC
+ * fixed crash in setenv.add-response-header
+ * fixed handling of nph-scripts in CGI
+ * fixed accidently sending out physical file in CGI on error
+ * fixed cygwin support
+ * fixed handling of missing files
+ * fixed HEAD requests for dynamic requests
+
+- 1.3.8 - 2005-01-30
+
+ * added traffic shaping by remote host and virtual server
+ * added auto-spawning of FastCGI process on demand
+ * added virtual host based on MySQL
+ * added mod_setenv to add envirnoment and http headers on the fly
+ * added support for syslog in mod_accesslog
+ * improved output of mod_status
+ * improved debug output in request handling
+ * fixed build problems on netbsd 1.4.x and 1.5.x
+ * fixed status.url configuration
+ * fixed handling of != and !~ in configutation
+ * fixed special cases in keep-alive handling
+ * fixed timeout handling in handling POST requests
+ * fixed mode AUTHORIZER in FastCGI
+ * fixed handling if internal redirects if no Host: is supplied
+ * fixed mod_alias + pathinfo
+ * fixed directory indexes and permissions
+ * enabled sending errorlog to syslog again
+
+- 1.3.7 - 2004-12-11
+
+ * added retries for a fastcgi connect if a php-childs
+ dies at startup
+ * update the debian directory
+ * added setgroups() to drop all group-privs
+ * added native port to windows via mingw32
+ * added server.tag = '...'
+ * added support for ${...} in mod_ssi
+ * ported all plugins to conditional support
+ * fixed multipart handling in cgi
+ * fixed kqueue event-handler
+ * fixed wrap-around in mod_status
+ * fixed crash with SSL + FastCGI
+ * fixed detection of SSL headers
+ * fixed handling of dangling SSL_shutdown
+ * fixed detection of keep-alive of Firefox
+
+- 1.3.6 - 2004-11-03
+
+ * added spawn-fcgi to the distribution
+ * added support in fastcgi module to spawn fastcgi
+ processes itself
+ * fixed logfile cycling if external logging is used
+ * fixed connection handling in fastcgi if no chunk
+ encoding is used
+ * fixed internal redirects on directories if a query
+ string is supplied
+ * fixed cgi-module for POST request above 4k
+ * fixed mod_alias and follow-symlink
+
+- 1.3.5 - 2004-10-31
+
+ * added mod_alias
+ * added mod_userdir
+ * added the exec command to the SSI handler
+ * added a switch to disable follow-symlinks
+ * added a switch to disable IPv6 at compile-time
+ * fixed compilation on FreeBSD and NetBSD 1.3.x
+ * fixed segfault in pipelining
+ * fixed a segfault in writev() handler if LFS is used
+
+- 1.3.4 - 2004-10-24
+
+ * added limiter for open files
+ * added logging of user supplied data to accesslogs
+ * added build target for OpenWRT
+ * added plain backend support for auth-digest
+ * fixed handling the external accesslog processes
+ * fixed SERVER_NAME in CGI and FastCGI
+
+- 1.3.3 - 2004-10-16
+
+ * added support for NL terminators in CGI-scripts
+ * added support for conditionals in mod_auth,
+ mod_simple_vhost and mod_evhost
+ * added a error-handler for 404 codes
+ * fixed request counter in the rrdtool module
+ * fixed log-file cycling
+ * fixed seg-fault
+
+- 1.3.2 - 2004-09-30
+
+ * fixed file-cache
+
+- 1.3.1 - 2004-09-30
+
+ * fixed file-cache
+ * fixed parsing of IPv6 adresses
+ * fixed cgi for cygwin
+ * fixed test-suite for FreeBSD and IRIX
+ * fixed handling of shrinked files
+ * fixed handling of REQUEST_URI after rewrite
+
+- 1.3.0 - 2004-09-17
+
+ * added build for MacOS X and Cygwin
+ * added handling of more than one socket
+ * added config-conditions for User-Agent and Referer
+ * added final rewrite-rules
+
+- 1.2.8 - 2004-09-11
+
+ * added a cache for mimetypes
+ * added X-Forwarded-For for mod_proxy
+ * fixed handling of comments in If-Modified-Since
+ * fixed error handling in FastCGI code
+ * fixed expire plugin for second Expire header
+
+- 1.2.7 - 2004-09-04
+
+ * added mod_rrdtool for internal statistics
+ * added xattr support
+ * added user-controlable timeouts
+ * improved documentation for many plugins
+ * fixed POST requests for mod_proxy
+ * fixed rare hang with CGI
+ * fixed seg-fault if no configfile is specified
+ * fixed rare problem in FastCGI header generation
+
+- 1.2.6 - 2004-08-26
+
+ * added apache-like accesslog definition
+ * enabled timestamp cache again
+ * improved performance in the string compare functions
+ * fixed double-free in fastcgi handler
+ * fixed error-handling in cgi handler
+
+- 1.2.5 - 2004-08-10
+
+ * added skeleton for solaris 10 port-API
+ * added compression support even if no cachedir is set
+ * added conditional configoptions
+ * fixed compilation on OpenBSD
+ * fixed kqueue support
+ * fixed pipelining bug
+ * fixed parallel build (triggered by Gentoo)
+ * updated debian postinst
+
+- 1.2.4 - 2004-07-31
+
+ * added kqueue support
+ * added server-side includes (mod_ssi)
+ * fixed large post uploads in fastcgi
+ * fixed rt-signals handling of delayed events
+
+- 1.2.3 - 2004-07-10
+
+ * added a proxy module for Java and friends
+ * added support to pass accesslog through an external programm
+ * added mimetypes for text/css and text/javascript
+ * fixed index-files for FastCGI if webserver is in chroot
+ * fixed error messages of CGI process fails to exec()
+ * fixed detection of pcre on IRIX and FreeBSD
+ * fixed timestamps in Last-Modified checks
+ * fixed 64bit builds
+ * fixed mmap-caching of large files
+ * relaxed the HTTP parser on empty headerfields
+
+- 1.2.2 - 2004-06-15
+
+ * added support for unix domain sockets in FastCGI
+ * fixed mmap caching
+ * fixed compile-time check for linux sendfile()
+ * fixed check for pcre.h on Fedora Core 2
+
+- 1.2.1 - 2004-05-30
+
+ * added experimental support for AIX send_file()
+ * added an mmap cache to the filehandle cache
+ * enabled FreeBSD sendfile support again
+ * added support for calling CGI binaries directly
+ * fixed pipelining for POST requests
+ * fixed some seg-faults if no configfile is used
+
+- 1.2.0 - 2004-05-17
+
+ * added conforming Expect: handling
+ * added a module for secure and fast downloading
+ * rewrote the event handling interface
+ * fixed array handling which might lead to 'missing header'
+ * fixed pipelining support
+ * fixed build of the localizer extension
+ * fixed cgi handling for headers which are flushed to often
+ * fixed compilation on Solaris 2.5
+
+- 1.1.9 - 2004-04-29
+
+ * added AUTHORIZER mode to the FastCGI module
+ * added 'check-local' option to disable local stat() in the FastCGI module
+ * added prefix-notation for FastCGI module
+ * added 'mod_usertrack'
+ * improved CGI/FastCGI spec conformance
+ * more code cleanup
+ * fixed HTTP/1.1 chunk headers
+ * fixed POST handling
+ * fixed SSL network handler
+ * fixed writev() network handler
+
+- 1.1.8 - 2004-04-16
+
+ * code cleanup
+ * limiting the size of the request-body and the request-header
+ * minor speed improvements
+ * tightend the HTTP-Parser again
+
+- 1.1.7 - 2004-04-12
+
+ * added REMOTE_USER to the Server->FastCGI parameters
+ * added bzip2 compression
+ * improved the error-messages from the new configfile parser
+ * fixed accesslog writing for errornous requests
+ * fixed LFS (64bit filesizes) handling
+ * fixed Content-Length for HEAD requests
+ * fixed some memory leaks in the configfile parser
+
+- 1.1.6 - 2004-04-10
+
+ * tightend the HTTP-Parser
+ * rewrote the configfile parser (based on lemon)
+ * fixed openssl support
+ * fixed mmap+write support
+ * use localtime in accesslog if possible
+
+- 1.1.5 - 2004-04-07
+
+ * added ldap backend to the auth
+ * added a mod_expire
+ * added debian packaging structure
+ * merged redhat and suse spec-file
+ * fixed eventhandler for solaris
+ * fixed 64bit fileoffsets
+ * fixed permissions of the PID-file
+
+- 1.1.4 - 2004-04-04
+
+ * added server.pid-file
+ * added support for solaris /dev/poll and solaris sendfilev()
+ * added support for writev()
+ * added PATHINFO support (again)
+ * fixed CLF logfile writing
+
+- 1.1.3 - 2004-03-25
+
+ * set default event-handler to 'poll'
+ * fixed logcycling in chroot()
+ * fixed hostname detection
+ * added syslog() as fallback for error-logging
+
+- 1.1.2 - 2004-03-22
+
+ * added a "docroot" setting for fastcgi processes
+ * performance improvements
+ * improved configure script
+ * rewrote the fastcgi config parser
+ * added a rc-script for RedHat
+ * added epoll() support for Linux 2.6.x
+
+- 1.1.1 - 2004-03-15
+
+ * added localizer module
+ * performance improvements
+ * code cleanup
+
+- 1.1.0 - 2004-03-06
+
+ * changed some configuration keys for better readability
+ * moved the virtual-host code to mod_simple_vhost
+ * added enhanced virtual host plugin from Christian Kruse
+ * added two new auth-backends (htpasswd, htdigest)
+ * fixed and improved authentification
+ * stricter parsing of the Host: field
+ * added a warning for unused configuration keys
+ * improved FastCGI documentation
+
+- 1.0.3 - 2004-02-13
+
+ * a startup script has been added (LSB compliant)
+ * HEAD requests were submitting the content like a GET request
+ * the virtual directory listing got a face-lifting and fixes
+ * request-headers are now handled case-in-sensitive as required
+ by the standard. this fixes POST requests for w3m and some Proxies.
+
+- 1.0.2 - 2004-02-07
+
+ * rearrangement of the default configfile
+ * some updates in the documentation
+ * a entry in the error-log for a 404
+ * stdout is no longer the default for the accesslog
diff --git a/README b/README
new file mode 100644
index 00000000..5a701554
--- /dev/null
+++ b/README
@@ -0,0 +1,156 @@
+
+========
+lighttpd
+========
+
+-------------
+a light httpd
+-------------
+
+:author: Jan Kneschke
+:Date: $Date: 2004/11/03 22:25:54 $
+:Revision: $Revision: 1.8 $
+
+:abstract:
+ lighttpd a secure, fast, compliant and very flexible web-server
+ which has been optimized for high-performance environments. It has a very
+ low memory footprint compared to other webservers and takes care of cpu-load.
+ Its advanced feature-set (FastCGI, CGI, Auth, Output-Compression,
+ URL-Rewriting and many more) make lighttpd the perfect webserver-software
+ for every server that is suffering load problems.
+
+the naming
+----------
+
+lighttpd is a __httpd__ which is
+
+- fast as __light__ning and
+- __light__ when it comes to memory consumption and system requirements
+
+Features
+--------
+
+Network
+```````
+
+- IPv4, IPv6
+
+Protocols
+`````````
+
+- HTTP/1.0 (http://www.ietf.org/rfc/rfc1945.txt)
+- HTTP/1.1 (http://www.ietf.org/rfc/rfc2616.txt)
+- HTTPS (provided by openssl)
+- CGI/1.1 (http://CGI-Spec.Golux.Com/)
+- FastCGI (http://www.fastcgi.com/devkit/doc/fcgi-spec.html)
+
+Advanced Features
+`````````````````
+
+- load-balanced FastCGI
+ (one webserver distributes requests to multiple PHP-servers via FastCGI)
+- custom error pages (for Response-Code 400-599)
+- virtual hosts
+- directory listings
+- streaming CGI and FastCGI
+- URL-Rewriting
+- HTTP-Redirection
+- output-compression with transparent caching
+
+FastCGI-Support
+```````````````
+
+- parses the Response-header and completes the HTTP-header accordingly
+- Keep-Alive handling based on Content-Length header
+
+PHP-Support
+```````````
+
+- same speed as or faster than apache + mod_php4
+- handles various PHP bugs in the FastCGI SAPI
+- includes a utility to spawn FastCGI processes (necessary for PHP 4.3.x)
+
+Security features
+`````````````````
+
+- chroot(), set UID, set GID
+- protecting docroot
+
+HTTP/1.1 features
+`````````````````
+
+- Ranges (start-end, start-, -end, multiple ranges)
+- HTTP/1.0 Keep-Alive + HTTP/1.1 persistent Connections
+- methods: GET, HEAD, POST
+- Last-Modified + If-Modified handling
+- sends Content-Length if possible
+- sends Transfer-Encoding: chunk, if Content-Length is not possible
+- sends Content-Type
+- on-the-fly output compression (deflate, gzip)
+- authentication: basic and digest
+ (http://www.ietf.org/rfc/rfc2617.txt)
+
+HTTP/1.1 compliance
+```````````````````
+
+- Sends 206 for Range Requests
+- Sends 304 for If-Modified Requests
+- Sends 400 for missing Host on HTTP/1.1 requests
+- Sends 400 for broken Request-Line
+- Sends 411 for missing Content-Length on POST requests
+- Sends 416 for "out-of-range" on Range: Header
+- Sends 501 for request-method != (GET|POST|HEAD)
+- Sends 505 for protocol != HTTP/1.0 or HTTP/1.1
+- Sends Date: on every requests
+
+Intended Audience
+-----------------
+
+- Ad-Server Front-Ends ("Banner-Schleuder")
+ - delivering small files rapidly
+- php-servers under high load
+ (load-balancing the php-request over multiple PHP-servers)
+
+Works with
+----------
+
+It has been tested to work with
+
+- IE 6.0
+- Mozilla 1.x
+- Konqueror 3.1
+ (for Keep-Alive/Persistent Connections, Accept-Encoding for PHP + gzip)
+- wget
+ (for Resuming)
+- acrobat plugin
+ (for multiple ranges)
+
+
+Works on
+--------
+
+lighttpd has been verified to compile and work on
+
+- Linux
+- FreeBSD
+- NetBSD
+- Solaris 8 + 9
+- SGI IRIX 6.5
+
+missing for HTTP/1.1 compliance
+-------------------------------
+- parsing chunked POST request
+
+-----------------
+Starting lighttpd
+-----------------
+
+As daemon in the background: ::
+
+ $ lighttpd -f <configfile>
+
+or without detaching from the console: ::
+
+ $ lighttpd -D -f <configfile>
+
+
diff --git a/config.h.in b/config.h.in
new file mode 100644
index 00000000..19b5d7c4
--- /dev/null
+++ b/config.h.in
@@ -0,0 +1,374 @@
+/* config.h.in. Generated from configure.in by autoheader. */
+
+/* Define to 1 if you have the <arpa/inet.h> header file. */
+#undef HAVE_ARPA_INET_H
+
+/* Define to 1 if you have the <attr/attributes.h> header file. */
+#undef HAVE_ATTR_ATTRIBUTES_H
+
+/* Define to 1 if you have the <bzlib.h> header file. */
+#undef HAVE_BZLIB_H
+
+/* Define to 1 if you have the `chroot' function. */
+#undef HAVE_CHROOT
+
+/* Define to 1 if you have the <crypt.h> header file. */
+#undef HAVE_CRYPT_H
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* Define to 1 if you have the `dup2' function. */
+#undef HAVE_DUP2
+
+/* Define to 1 if you have the `epoll_ctl' function. */
+#undef HAVE_EPOLL_CTL
+
+/* Define to 1 if you have the <errmsg.h> header file. */
+#undef HAVE_ERRMSG_H
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#undef HAVE_FCNTL_H
+
+/* Define to 1 if you have the `fork' function. */
+#undef HAVE_FORK
+
+/* Define to 1 if you have the `getcwd' function. */
+#undef HAVE_GETCWD
+
+/* Define to 1 if you have the `gethostbyname' function. */
+#undef HAVE_GETHOSTBYNAME
+
+/* Define to 1 if you have the `getopt' function. */
+#undef HAVE_GETOPT
+
+/* Define to 1 if you have the <getopt.h> header file. */
+#undef HAVE_GETOPT_H
+
+/* Define to 1 if you have the `getrlimit' function. */
+#undef HAVE_GETRLIMIT
+
+/* Define to 1 if you have the `getuid' function. */
+#undef HAVE_GETUID
+
+/* Define to 1 if you have the `inet_ntoa' function. */
+#undef HAVE_INET_NTOA
+
+/* Define to 1 if you have the `inet_ntop' function. */
+#undef HAVE_INET_NTOP
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Whether to enable IPv6 support */
+#undef HAVE_IPV6
+
+/* Define to 1 if you have the `kqueue' function. */
+#undef HAVE_KQUEUE
+
+/* Define to 1 if you have the <lber.h> header file. */
+#undef HAVE_LBER_H
+
+/* Define to 1 if you have the <ldap.h> header file. */
+#undef HAVE_LDAP_H
+
+/* libbz2 */
+#undef HAVE_LIBBZ2
+
+/* libcrypt */
+#undef HAVE_LIBCRYPT
+
+/* libdl */
+#undef HAVE_LIBDL
+
+/* liblber */
+#undef HAVE_LIBLBER
+
+/* libldap */
+#undef HAVE_LIBLDAP
+
+/* libpcre */
+#undef HAVE_LIBPCRE
+
+/* Have libssl */
+#undef HAVE_LIBSSL
+
+/* libz */
+#undef HAVE_LIBZ
+
+/* Define to 1 if you have the `localtime_r' function. */
+#undef HAVE_LOCALTIME_R
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the `memset' function. */
+#undef HAVE_MEMSET
+
+/* Define to 1 if you have the `mmap' function. */
+#undef HAVE_MMAP
+
+/* Define to 1 if you have the `munmap' function. */
+#undef HAVE_MUNMAP
+
+/* mysql support */
+#undef HAVE_MYSQL
+
+/* Define to 1 if you have the <mysql.h> header file. */
+#undef HAVE_MYSQL_H
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#undef HAVE_NETINET_IN_H
+
+/* Define to 1 if you have the <openssl/ssl.h> header file. */
+#undef HAVE_OPENSSL_SSL_H
+
+/* Define to 1 if you have the <pcre.h> header file. */
+#undef HAVE_PCRE_H
+
+/* Define to 1 if you have the `poll' function. */
+#undef HAVE_POLL
+
+/* Define to 1 if you have the <poll.h> header file. */
+#undef HAVE_POLL_H
+
+/* Define to 1 if you have the `port_create' function. */
+#undef HAVE_PORT_CREATE
+
+/* Define to 1 if you have the <pwd.h> header file. */
+#undef HAVE_PWD_H
+
+/* Define to 1 if you have the `select' function. */
+#undef HAVE_SELECT
+
+/* Define to 1 if you have the `sendfile' function. */
+#undef HAVE_SENDFILE
+
+/* Define to 1 if you have the `sendfile64' function. */
+#undef HAVE_SENDFILE64
+
+/* solaris sendfilev */
+#undef HAVE_SENDFILEV
+
+/* broken sendfile */
+#undef HAVE_SENDFILE_BROKEN
+
+/* Define to 1 if you have the `send_file' function. */
+#undef HAVE_SEND_FILE
+
+/* Define to 1 if you have the `sigaction' function. */
+#undef HAVE_SIGACTION
+
+/* Define to 1 if you have the `signal' function. */
+#undef HAVE_SIGNAL
+
+/* Define to 1 if you have the `sigtimedwait' function. */
+#undef HAVE_SIGTIMEDWAIT
+
+/* Define to 1 if you have the `socket' function. */
+#undef HAVE_SOCKET
+
+/* Define to 1 if the system has the type `socklen_t'. */
+#undef HAVE_SOCKLEN_T
+
+/* Define to 1 if `stat' has the bug that it succeeds when given the
+ zero-length file name argument. */
+#undef HAVE_STAT_EMPTY_STRING_BUG
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the `strchr' function. */
+#undef HAVE_STRCHR
+
+/* Define to 1 if you have the `strdup' function. */
+#undef HAVE_STRDUP
+
+/* Define to 1 if you have the `strerror' function. */
+#undef HAVE_STRERROR
+
+/* Define to 1 if you have the `strftime' function. */
+#undef HAVE_STRFTIME
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the `strstr' function. */
+#undef HAVE_STRSTR
+
+/* Define to 1 if you have the `strtol' function. */
+#undef HAVE_STRTOL
+
+/* Define to 1 if the system has the type `struct sockaddr_storage'. */
+#undef HAVE_STRUCT_SOCKADDR_STORAGE
+
+/* gmtoff in struct tm */
+#undef HAVE_STRUCT_TM_GMTOFF
+
+/* Define to 1 if you have the <syslog.h> header file. */
+#undef HAVE_SYSLOG_H
+
+/* Define to 1 if you have the <sys/devpoll.h> header file. */
+#undef HAVE_SYS_DEVPOLL_H
+
+/* Define to 1 if you have the <sys/epoll.h> header file. */
+#undef HAVE_SYS_EPOLL_H
+
+/* Define to 1 if you have the <sys/event.h> header file. */
+#undef HAVE_SYS_EVENT_H
+
+/* Define to 1 if you have the <sys/filio.h> header file. */
+#undef HAVE_SYS_FILIO_H
+
+/* Define to 1 if you have the <sys/mman.h> header file. */
+#undef HAVE_SYS_MMAN_H
+
+/* Define to 1 if you have the <sys/poll.h> header file. */
+#undef HAVE_SYS_POLL_H
+
+/* Define to 1 if you have the <sys/port.h> header file. */
+#undef HAVE_SYS_PORT_H
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#undef HAVE_SYS_RESOURCE_H
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#undef HAVE_SYS_SELECT_H
+
+/* Define to 1 if you have the <sys/sendfile.h> header file. */
+#undef HAVE_SYS_SENDFILE_H
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#undef HAVE_SYS_SOCKET_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#undef HAVE_SYS_TIME_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <sys/uio.h> header file. */
+#undef HAVE_SYS_UIO_H
+
+/* Define to 1 if you have the <sys/un.h> header file. */
+#undef HAVE_SYS_UN_H
+
+/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
+#undef HAVE_SYS_WAIT_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to 1 if you have the <valgrind/valgrind.h> header file. */
+#undef HAVE_VALGRIND_VALGRIND_H
+
+/* Define to 1 if you have the `vfork' function. */
+#undef HAVE_VFORK
+
+/* Define to 1 if you have the <vfork.h> header file. */
+#undef HAVE_VFORK_H
+
+/* Define to 1 if you have the <winsock2.h> header file. */
+#undef HAVE_WINSOCK2_H
+
+/* Define to 1 if `fork' works. */
+#undef HAVE_WORKING_FORK
+
+/* Define to 1 if `vfork' works. */
+#undef HAVE_WORKING_VFORK
+
+/* Define to 1 if you have the `writev' function. */
+#undef HAVE_WRITEV
+
+/* libattr */
+#undef HAVE_XATTR
+
+/* Define to 1 if you have the <zlib.h> header file. */
+#undef HAVE_ZLIB_H
+
+/* lighttpd-version-id */
+#undef LIGHTTPD_VERSION_ID
+
+/* Define to 1 if `lstat' dereferences a symlink specified with a trailing
+ slash. */
+#undef LSTAT_FOLLOWS_SLASHED_SYMLINK
+
+/* Name of package */
+#undef PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define if compiler has function prototypes */
+#undef PROTOTYPES
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#undef RETSIGTYPE
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Version number of package */
+#undef VERSION
+
+/* Define to 1 if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+# undef _ALL_SOURCE
+#endif
+
+/* Define to 1 if on MINIX. */
+#undef _MINIX
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+#undef _POSIX_1_SOURCE
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+#undef _POSIX_SOURCE
+
+/* Define to 1 if type `char' is unsigned and you are not using gcc. */
+#ifndef __CHAR_UNSIGNED__
+# undef __CHAR_UNSIGNED__
+#endif
+
+/* Define to empty if `const' does not conform to ANSI C. */
+#undef const
+
+/* Define as `__inline' if that's what the C compiler calls it, or to nothing
+ if it is not supported. */
+#undef inline
+
+/* Define to `long' if <sys/types.h> does not define. */
+#undef off_t
+
+/* Define to `int' if <sys/types.h> does not define. */
+#undef pid_t
+
+/* Define to `unsigned' if <sys/types.h> does not define. */
+#undef size_t
+
+/* Define as `fork' if `vfork' does not work. */
+#undef vfork
diff --git a/configure.in b/configure.in
new file mode 100644
index 00000000..1e13a03a
--- /dev/null
+++ b/configure.in
@@ -0,0 +1,421 @@
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.57)
+AC_INIT(lighttpd, 1.3.11, jan@kneschke.de)
+AC_CONFIG_SRCDIR([src/server.c])
+
+AC_CANONICAL_TARGET
+
+AM_INIT_AUTOMAKE
+
+AM_CONFIG_HEADER([config.h])
+
+AM_MAINTAINER_MODE
+
+# Checks for programs.
+AC_PROG_CC
+AC_PROG_LD
+AC_PROG_INSTALL
+AC_PROG_AWK
+AC_PROG_CPP
+dnl AC_PROG_CXX
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+
+dnl check environment
+AC_AIX
+AC_ISC_POSIX
+AC_MINIX
+
+dnl AC_CANONICAL_HOST
+case $host_os in
+ *darwin*|*cygwin*|*aix*|*mingw* ) NO_RDYNAMIC=yes;;
+ * ) NO_RDYNAMIC=no;;
+esac
+AM_CONDITIONAL(NO_RDYNAMIC, test x$NO_RDYNAMIC = xyes)
+
+AC_EXEEXT
+
+dnl more automake stuff
+AM_C_PROTOTYPES
+
+dnl libtool
+AC_DISABLE_STATIC
+AC_ENABLE_SHARED
+
+AC_LIBTOOL_DLOPEN
+AC_PROG_LIBTOOL
+
+dnl for solaris and localtime_r
+CPPFLAGS="${CPPFLAGS} -D_REENTRANT"
+
+# Checks for header files.
+AC_HEADER_STDC
+AC_HEADER_SYS_WAIT
+AC_CHECK_HEADERS([arpa/inet.h fcntl.h netinet/in.h stdlib.h string.h \
+sys/socket.h sys/time.h unistd.h sys/sendfile.h sys/uio.h \
+getopt.h sys/epoll.h sys/select.h poll.h sys/poll.h sys/devpoll.h sys/filio.h \
+sys/mman.h sys/event.h sys/port.h winsock2.h pwd.h \
+sys/resource.h sys/un.h syslog.h])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_C_INLINE
+AC_C_CHAR_UNSIGNED
+AC_TYPE_OFF_T
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+
+AC_CHECK_MEMBER(struct tm.tm_gmtoff,AC_DEFINE([HAVE_STRUCT_TM_GMTOFF],[1],[gmtoff in struct tm]),,[#include <time.h>])
+AC_CHECK_TYPES(struct sockaddr_storage,,,[#include <sys/socket.h>])
+AC_CHECK_TYPES(socklen_t,,,[#include <sys/types.h>
+#include <sys/socket.h>])
+
+# Checks for library functions.
+AC_FUNC_FORK
+dnl AC_FUNC_MALLOC
+#AC_FUNC_MMAP
+dnl AC_FUNC_REALLOC
+AC_TYPE_SIGNAL
+AC_FUNC_STAT
+AC_FUNC_STRFTIME
+
+dnl Checks for database.
+MYSQL_INCLUDE=""
+
+AC_PATH_PROG(MYSQLCONFIG, mysql_config)
+AC_MSG_CHECKING(for MySQL support)
+AC_ARG_WITH(mysql,
+ AC_HELP_STRING([--with-mysql@<:@=PATH@:>@],[Include MySQL support. PATH is the path to 'mysql_config']),
+ [
+ if test "$withval" != "no"; then
+ if test "$withval" = "yes"; then
+ withval=$MYSQLCONFIG
+ fi
+
+ if test \! -x $withval; then
+ echo "--with-mysql=path-to-mysql_config"
+ fi
+ MYSQL_INCLUDE="`$withval --cflags | sed s/\'//g`"
+ MYSQL_LIBS="`$withval --libs | sed s/\'//g`"
+
+ AC_MSG_RESULT(yes)
+
+ AC_MSG_CHECKING(for MySQL includes at)
+ AC_MSG_RESULT($MYSQL_INCLUDE)
+
+ AC_MSG_CHECKING(for MySQL libraries at)
+ AC_MSG_RESULT($MYSQL_LIBS)
+ dnl check for errmsg.h, which isn't installed by some versions of 3.21
+ old_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $MYSQL_INCLUDE"
+ AC_CHECK_HEADERS(errmsg.h mysql.h)
+ CPPFLAGS="$old_CPPFLAGS"
+
+ AC_DEFINE([HAVE_MYSQL], [1], [mysql support])
+ else
+ AC_MSG_RESULT(no)
+ fi
+],[AC_MSG_RESULT(no)])
+
+AC_SUBST(MYSQL_LIBS)
+AC_SUBST(MYSQL_INCLUDE)
+
+
+AC_MSG_CHECKING(for LDAP support)
+AC_ARG_WITH(ldap, AC_HELP_STRING([--with-ldap],[enable LDAP support]),
+[AC_MSG_RESULT(yes)
+ AC_CHECK_LIB(ldap, ldap_bind, [
+ AC_CHECK_HEADERS([ldap.h],[
+ LDAP_LIB=-lldap
+ AC_DEFINE([HAVE_LIBLDAP], [1], [libldap])
+ AC_DEFINE([HAVE_LDAP_H], [1])
+ ])
+ ])
+ AC_SUBST(LDAP_LIB)
+ AC_CHECK_LIB(lber, ber_printf, [
+ AC_CHECK_HEADERS([lber.h],[
+ LBER_LIB=-llber
+ AC_DEFINE([HAVE_LIBLBER], [1], [liblber])
+ AC_DEFINE([HAVE_LBER_H], [1])
+ ])
+ ])
+ AC_SUBST(LBER_LIB)
+
+],[AC_MSG_RESULT(no)])
+
+AC_MSG_CHECKING(for extended attributes support)
+AC_ARG_WITH(attr, AC_HELP_STRING([--with-attr],[enable extended attribute support]),
+[AC_MSG_RESULT(yes)
+ AC_CHECK_LIB(attr, attr_get, [
+ AC_CHECK_HEADERS([attr/attributes.h],[
+ ATTR_LIB=-lattr
+ AC_DEFINE([HAVE_XATTR], [1], [libattr])
+ AC_DEFINE([HAVE_ATTR_ATTRIBUTES_H], [1])
+ ])
+ ])
+],[AC_MSG_RESULT(no)])
+AC_SUBST(ATTR_LIB)
+
+AC_MSG_CHECKING(for valgrind)
+AC_ARG_WITH(valgrind, AC_HELP_STRING([--with-valgrind],[enable internal
+support for valgrind]),
+[AC_MSG_RESULT(yes)
+ AC_CHECK_HEADERS([valgrind/valgrind.h])
+],[AC_MSG_RESULT(no)])
+
+AC_MSG_CHECKING(for OpenSSL)
+dnl check for openssl
+ AC_ARG_WITH(openssl,
+ AC_HELP_STRING([--with-openssl@<:@=DIR@:>@],[Include openssl support (default no)]),
+[
+ if test "$withval" != "no"; then
+ if test "$withval" != "yes"; then
+ CPPFLAGS="$CPPFLAGS -I$withval/include"
+ LDFLAGS="$LDFLAGS -L$withval/lib"
+ fi
+
+ use_openssl=yes
+ AC_MSG_RESULT($withval)
+ else
+ use_openssl=no
+ AC_MSG_RESULT(no)
+ fi
+],[
+ use_openssl=no
+ AC_MSG_RESULT(no)
+])
+
+AC_ARG_WITH(openssl-includes,
+ AC_HELP_STRING([--with-openssl-includes=DIR],[OpenSSL includes]),
+ [ use_openssl=yes CPPFLAGS="$CPPFLAGS -I$withval" ]
+)
+
+AC_ARG_WITH(openssl-libs,
+ AC_HELP_STRING([--with-openssl-libs=DIR],[OpenSSL libraries]),
+ [ use_openssl=yes LDFLAGS="$LDFLAGS -L$withval" ]
+)
+
+if test "x$use_openssl" = "xyes"; then
+ AC_CHECK_HEADERS([openssl/ssl.h])
+ OLDLIBS="$LIBS"
+ AC_CHECK_LIB(crypto, BIO_f_base64, [
+ AC_CHECK_LIB(ssl, SSL_new, [ SSL_LIB="-lssl -lcrypto"
+ AC_DEFINE(HAVE_LIBSSL, [], [Have libssl]) ], [], [ -lcrypto ])
+ ], [], [])
+ LIBS="$OLDLIBS"
+ AC_SUBST(SSL_LIB)
+fi
+
+if test "x$cross_compiling" = xno; then
+ AC_PATH_PROG(PCRECONFIG, pcre-config)
+
+ if test x"$PCRECONFIG" != x; then
+ PCRE_LIB=`$PCRECONFIG --libs`
+ CPPFLAGS="$CPPFLAGS `$PCRECONFIG --cflags`"
+ OLDLIBS="$LIBS"
+ LIBS="$LIBS $PCRE_LIB"
+ AC_CHECK_LIB(pcre, pcre_compile, [
+ AC_CHECK_HEADERS([pcre.h], [
+ AC_DEFINE([HAVE_LIBPCRE], [1], [libpcre])
+ AC_DEFINE([HAVE_PCRE_H], [1])
+ ])
+ ])
+ LIBS="$OLDLIBS"
+ fi
+fi
+
+AC_SUBST(PCRE_LIB)
+
+AC_CHECK_LIB(z, deflate, [
+ AC_CHECK_HEADERS([zlib.h],[
+ Z_LIB=-lz
+ AC_DEFINE([HAVE_LIBZ], [1], [libz])
+ AC_DEFINE([HAVE_ZLIB_H], [1])
+ ])
+])
+AC_SUBST(Z_LIB)
+
+AC_CHECK_LIB(bz2, BZ2_bzCompress, [
+ AC_CHECK_HEADERS([bzlib.h],[
+ BZ_LIB=-lbz2
+ AC_DEFINE([HAVE_LIBBZ2], [1], [libbz2])
+ AC_DEFINE([HAVE_BZLIB_H], [1])
+ ])
+])
+AC_SUBST(BZ_LIB)
+
+AC_SEARCH_LIBS(socket,socket)
+AC_SEARCH_LIBS(gethostbyname,nsl socket)
+AC_SEARCH_LIBS(hstrerror,resolv)
+
+save_LIBS=$LIBS
+AC_SEARCH_LIBS(dlopen,dl,[
+ AC_CHECK_HEADERS([dlfcn.h],[
+ if test "$ac_cv_search_dlopen" != no; then
+ test "$ac_cv_search_dlopen" = "none required" || DL_LIB="$ac_cv_search_dlopen"
+ fi
+
+ AC_DEFINE([HAVE_LIBDL], [1], [libdl])
+ AC_DEFINE([HAVE_DLFCN_H], [1])
+ ])
+])
+LIBS=$save_LIBS
+AC_SUBST(DL_LIB)
+
+save_LIBS=$LIBS
+AC_SEARCH_LIBS(crypt,crypt,[
+ AC_CHECK_HEADERS([crypt.h],[
+ AC_DEFINE([HAVE_CRYPT_H], [1])
+ ])
+
+ AC_DEFINE([HAVE_LIBCRYPT], [1], [libcrypt])
+ if test "$ac_cv_search_crypt" != no; then
+ test "$ac_cv_search_crypt" = "none required" || CRYPT_LIB="$ac_cv_search_crypt"
+ fi
+])
+LIBS=$save_LIBS
+AC_SUBST(CRYPT_LIB)
+
+save_LIBS=$LIBS
+AC_SEARCH_LIBS(sendfilev,sendfile,[
+ if test "$ac_cv_search_sendfilev" != no; then
+ test "$ac_cv_search_sendfilev" = "none required" || SENDFILE_LIB="$ac_cv_search_sendfilev"
+ AC_DEFINE([HAVE_SENDFILEV], [1], [solaris sendfilev])
+ fi
+])
+LIBS=$save_LIBS
+AC_SUBST(SENDFILE_LIB)
+
+case $host_os in
+ *mingw* ) LIBS="$LIBS -lwsock32";;
+ * ) ;;
+esac
+
+AC_CHECK_FUNCS([dup2 getcwd inet_ntoa inet_ntop memset mmap munmap strchr \
+ strdup strerror strstr strtol sendfile getopt socket \
+ gethostbyname poll sigtimedwait epoll_ctl getrlimit chroot \
+ getuid select signal\
+ writev sigaction sendfile64 send_file kqueue port_create localtime_r])
+
+if test "x$ac_cv_func_sendfile" = xyes; then
+ # check if sendfile works
+ AC_MSG_CHECKING(if sendfile works)
+ if test "x$cross_compiling" = xno; then
+ AC_TRY_RUN([
+ #include <errno.h>
+ int main() {
+ int o = 0;
+ if (-1 == sendfile(0, 0, &o, 0) && errno == ENOSYS) return -1;
+ return 0;
+ } ],
+ AC_MSG_RESULT(yes),
+ [ AC_MSG_RESULT(no)
+ AC_DEFINE([HAVE_SENDFILE_BROKEN], [1], [broken sendfile]) ] )
+ else
+ AC_MSG_RESULT(no, cross-compiling)
+ AC_DEFINE([HAVE_SENDFILE_BROKEN], [1], [broken sendfile])
+ fi
+fi
+
+dnl Check for IPv6 support
+
+AC_ARG_ENABLE(ipv6,
+ AC_HELP_STRING([--disable-ipv6],[disable IPv6 support]),
+ [case "${enableval}" in
+ yes) ipv6=true ;;
+ no) ipv6=false ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-ipv6) ;;
+ esac],[ipv6=true])
+
+if test x$ipv6 = xtrue; then
+ AC_CACHE_CHECK([for IPv6 support], ac_cv_ipv6_support,
+ [AC_TRY_LINK([ #include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>], [struct sockaddr_in6 s; struct in6_addr t=in6addr_any; int i=AF_INET6; s; t.s6_addr[0] = 0; ],
+ [ac_cv_ipv6_support=yes], [ac_cv_ipv6_support=no])])
+
+ if test "$ac_cv_ipv6_support" = yes; then
+ AC_DEFINE(HAVE_IPV6,1,[Whether to enable IPv6 support])
+ fi
+fi
+
+
+AC_MSG_CHECKING(for Large File System support)
+AC_ARG_ENABLE(lfs,
+ AC_HELP_STRING([--enable-lfs],[Turn on Large File System (default)]),
+ [case "${enableval}" in
+ yes) CPPFLAGS="${CPPFLAGS} -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGE_FILES" ;;
+ no) ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-lfs) ;;
+ esac],[CPPFLAGS="${CPPFLAGS} -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGE_FILES"])
+AC_MSG_RESULT($enableval)
+
+AM_CONDITIONAL(CROSS_COMPILING, test "x$cross_compiling" = xyes)
+
+if test "${GCC}" = "yes"; then
+ CFLAGS="${CFLAGS} -Wall -W -Wshadow -pedantic"
+fi
+
+dnl build version-id
+LIGHTTPD_VERSION_ID=`echo $PACKAGE_VERSION | $AWK -F '.' '{print "(" $1 " << 16 | " $2 " << 8 | " $3 ")"}'`
+AC_DEFINE_UNQUOTED([LIGHTTPD_VERSION_ID], [$LIGHTTPD_VERSION_ID], [lighttpd-version-id])
+
+AC_CONFIG_FILES([Makefile debian/Makefile src/Makefile doc/Makefile tests/Makefile \
+ tests/docroot/Makefile \
+ tests/docroot/123/Makefile \
+ tests/docroot/www/Makefile \
+ tests/docroot/www/go/Makefile \
+ tests/docroot/www/indexfile/Makefile \
+ lighttpd.spec distribute.sh cygwin/Makefile cygwin/lighttpd.README
+ openwrt/Makefile openwrt/control openwrt/lighttpd.mk])
+AC_OUTPUT
+
+$ECHO
+$ECHO "Plugins:"
+$ECHO
+if test ! "x$PCRE_LIB" = x; then
+ $ECHO "mod_rewrite : enabled"
+ $ECHO "mod_redirect : enabled"
+ $ECHO "mod_ssi : enabled"
+else
+ $ECHO "mod_rewrite : disabled (libpcre missing)"
+ $ECHO "mod_redirect : disabled (libpcre missing)"
+ $ECHO "mod_ssi : disabled (libpcre missing)"
+fi
+
+$ECHO "mod_cgi : enabled"
+$ECHO "mod_fastcgi : enabled"
+$ECHO "mod_proxy : enabled"
+$ECHO "mod_evhost : enabled"
+$ECHO "mod_simple_vhost: enabled"
+
+if test "x$MYSQL_LIBS" = x; then
+ $ECHO "mod_mysql_vhost : disabled (libmysqlclient missing or mysql support disabled)"
+else
+ $ECHO "mod_mysql_vhost : enabled"
+fi
+
+$ECHO "mod_access : enabled"
+$ECHO "mod_alias : enabled"
+$ECHO "mod_setenv : enabled"
+$ECHO "mod_usertrack : enabled"
+if test "x$Z_LIB" = x; then
+ $ECHO "mod_compress : disabled (libz missing)"
+else
+ $ECHO "mod_compress : enabled"
+fi
+
+# no crypt call
+if test "$ac_cv_search_crypt" = no; then
+ $ECHO "mod_auth : enabled, crypt() support disabled"
+else
+ $ECHO "mod_auth : enabled"
+fi
+$ECHO "mod_status : enabled"
+$ECHO "mod_accesslog : enabled"
+$ECHO "mod_rrdtool : enabled"
+$ECHO "mod_secdownload : enabled"
+$ECHO "mod_expire : enabled"
+
+$ECHO
diff --git a/cygwin/.cvsignore b/cygwin/.cvsignore
new file mode 100644
index 00000000..3dda7298
--- /dev/null
+++ b/cygwin/.cvsignore
@@ -0,0 +1,2 @@
+Makefile.in
+Makefile
diff --git a/cygwin/Makefile.am b/cygwin/Makefile.am
new file mode 100644
index 00000000..8892f643
--- /dev/null
+++ b/cygwin/Makefile.am
@@ -0,0 +1 @@
+EXTRA_DIST=lighttpd.README setup.hint
diff --git a/cygwin/lighttpd.README b/cygwin/lighttpd.README
new file mode 100644
index 00000000..5873d01e
--- /dev/null
+++ b/cygwin/lighttpd.README
@@ -0,0 +1,114 @@
+lighttpd
+------------------------------------------
+A fast, secure and flexible webserver
+
+Runtime requirements:
+ cygwin-1.5.10 or newer
+ crypt-1.1 or newer
+ libbz2_1-1.0.2 or newer
+ libpcre0-4.5 or newer
+ openssl-0.9.7d or newer
+ zlib-1.2.1 or newer
+
+Build requirements:
+ cygwin-1.5.10 or newer
+ gcc-3.3.1-3 or newer
+ binutils-20030901-1 or newer
+ crypt
+ openssl-devel
+ openssl
+ openldap
+ openldap-devel
+ zlib
+ bzip2
+
+Canonical homepage:
+ http://jan.kneschke.de/projects/lighttpd/
+
+Canonical download:
+ http://jan.kneschke.de/projects/lighttpd/download
+
+------------------------------------
+
+Build instructions:
+ unpack lighttpd-1.3.11-<REL>-src.tar.bz2
+ if you use setup to install this src package, it will be
+ unpacked under /usr/src automatically
+ cd /usr/src
+ ./lighttpd-1.3.11-<REL>.sh all
+
+This will create:
+ /usr/src/lighttpd-1.3.11-<REL>.tar.bz2
+ /usr/src/lighttpd-1.3.11-<REL>-src.tar.bz2
+
+Or use './lighttpd-1.3.11-<REL>.sh prep' to get a patched source directory
+
+-------------------------------------------
+
+Files included in the binary distribution:
+
+ /etc/lighttpd/lighttpd.conf.default
+ /usr/lib/cyglightcomp.dll
+ /usr/lib/lighttpd/mod_access.dll
+ /usr/lib/lighttpd/mod_accesslog.dll
+ /usr/lib/lighttpd/mod_auth.dll
+ /usr/lib/lighttpd/mod_cgi.dll
+ /usr/lib/lighttpd/mod_compress.dll
+ /usr/lib/lighttpd/mod_evhost.dll
+ /usr/lib/lighttpd/mod_expire.dll
+ /usr/lib/lighttpd/mod_fastcgi.dll
+ /usr/lib/lighttpd/mod_httptls.dll
+ /usr/lib/lighttpd/mod_maps.dll
+ /usr/lib/lighttpd/mod_proxy.dll
+ /usr/lib/lighttpd/mod_redirect.dll
+ /usr/lib/lighttpd/mod_rewrite.dll
+ /usr/lib/lighttpd/mod_rrdtool.dll
+ /usr/lib/lighttpd/mod_secdownload.dll
+ /usr/lib/lighttpd/mod_simple_vhost.dll
+ /usr/lib/lighttpd/mod_ssi.dll
+ /usr/lib/lighttpd/mod_status.dll
+ /usr/lib/lighttpd/mod_usertrack.dll
+ /usr/sbin/lighttpd.exe
+ /usr/share/doc/Cygwin/lighttpd-1.3.0.README
+ /usr/share/doc/lighttpd-1.3.0/accesslog.txt
+ /usr/share/doc/lighttpd-1.3.0/authentification.txt
+ /usr/share/doc/lighttpd-1.3.0/AUTHORS
+ /usr/share/doc/lighttpd-1.3.0/cgi.txt
+ /usr/share/doc/lighttpd-1.3.0/ChangeLog
+ /usr/share/doc/lighttpd-1.3.0/compress.txt
+ /usr/share/doc/lighttpd-1.3.0/configuration.txt
+ /usr/share/doc/lighttpd-1.3.0/COPYING
+ /usr/share/doc/lighttpd-1.3.0/fastcgi-state.txt
+ /usr/share/doc/lighttpd-1.3.0/fastcgi.txt
+ /usr/share/doc/lighttpd-1.3.0/features.txt
+ /usr/share/doc/lighttpd-1.3.0/INSTALL
+ /usr/share/doc/lighttpd-1.3.0/NEWS
+ /usr/share/doc/lighttpd-1.3.0/performance.txt
+ /usr/share/doc/lighttpd-1.3.0/plugins.txt
+ /usr/share/doc/lighttpd-1.3.0/proxy.txt
+ /usr/share/doc/lighttpd-1.3.0/README
+ /usr/share/doc/lighttpd-1.3.0/redirect.txt
+ /usr/share/doc/lighttpd-1.3.0/rewrite.txt
+ /usr/share/doc/lighttpd-1.3.0/rrdtool.txt
+ /usr/share/doc/lighttpd-1.3.0/secdownload.txt
+ /usr/share/doc/lighttpd-1.3.0/security.txt
+ /usr/share/doc/lighttpd-1.3.0/simple-vhost.txt
+ /usr/share/doc/lighttpd-1.3.0/skeleton.txt
+ /usr/share/doc/lighttpd-1.3.0/ssi.txt
+ /usr/share/doc/lighttpd-1.3.0/state.txt
+ /usr/share/man/man1/lighttpd.1.gz
+
+------------------
+
+Port Notes:
+
+---------- lighttpd-1.3.1-1 -----------
+
+Updated to 1.3.1
+
+---------- lighttpd-1.3.0-1 -----------
+Initial release
+
+Cygwin port maintained by: Jan Kneschke <jan@kneschke.de>
+Please address all questions to the Cygwin mailing list at <cygwin@cygwin.com>
+
diff --git a/cygwin/lighttpd.README.in b/cygwin/lighttpd.README.in
new file mode 100644
index 00000000..2b455462
--- /dev/null
+++ b/cygwin/lighttpd.README.in
@@ -0,0 +1,114 @@
+lighttpd
+------------------------------------------
+A fast, secure and flexible webserver
+
+Runtime requirements:
+ cygwin-1.5.10 or newer
+ crypt-1.1 or newer
+ libbz2_1-1.0.2 or newer
+ libpcre0-4.5 or newer
+ openssl-0.9.7d or newer
+ zlib-1.2.1 or newer
+
+Build requirements:
+ cygwin-1.5.10 or newer
+ gcc-3.3.1-3 or newer
+ binutils-20030901-1 or newer
+ crypt
+ openssl-devel
+ openssl
+ openldap
+ openldap-devel
+ zlib
+ bzip2
+
+Canonical homepage:
+ http://jan.kneschke.de/projects/lighttpd/
+
+Canonical download:
+ http://jan.kneschke.de/projects/lighttpd/download
+
+------------------------------------
+
+Build instructions:
+ unpack lighttpd-@VERSION@-<REL>-src.tar.bz2
+ if you use setup to install this src package, it will be
+ unpacked under /usr/src automatically
+ cd /usr/src
+ ./lighttpd-@VERSION@-<REL>.sh all
+
+This will create:
+ /usr/src/lighttpd-@VERSION@-<REL>.tar.bz2
+ /usr/src/lighttpd-@VERSION@-<REL>-src.tar.bz2
+
+Or use './lighttpd-@VERSION@-<REL>.sh prep' to get a patched source directory
+
+-------------------------------------------
+
+Files included in the binary distribution:
+
+ /etc/lighttpd/lighttpd.conf.default
+ /usr/lib/cyglightcomp.dll
+ /usr/lib/lighttpd/mod_access.dll
+ /usr/lib/lighttpd/mod_accesslog.dll
+ /usr/lib/lighttpd/mod_auth.dll
+ /usr/lib/lighttpd/mod_cgi.dll
+ /usr/lib/lighttpd/mod_compress.dll
+ /usr/lib/lighttpd/mod_evhost.dll
+ /usr/lib/lighttpd/mod_expire.dll
+ /usr/lib/lighttpd/mod_fastcgi.dll
+ /usr/lib/lighttpd/mod_httptls.dll
+ /usr/lib/lighttpd/mod_maps.dll
+ /usr/lib/lighttpd/mod_proxy.dll
+ /usr/lib/lighttpd/mod_redirect.dll
+ /usr/lib/lighttpd/mod_rewrite.dll
+ /usr/lib/lighttpd/mod_rrdtool.dll
+ /usr/lib/lighttpd/mod_secdownload.dll
+ /usr/lib/lighttpd/mod_simple_vhost.dll
+ /usr/lib/lighttpd/mod_ssi.dll
+ /usr/lib/lighttpd/mod_status.dll
+ /usr/lib/lighttpd/mod_usertrack.dll
+ /usr/sbin/lighttpd.exe
+ /usr/share/doc/Cygwin/lighttpd-1.3.0.README
+ /usr/share/doc/lighttpd-1.3.0/accesslog.txt
+ /usr/share/doc/lighttpd-1.3.0/authentification.txt
+ /usr/share/doc/lighttpd-1.3.0/AUTHORS
+ /usr/share/doc/lighttpd-1.3.0/cgi.txt
+ /usr/share/doc/lighttpd-1.3.0/ChangeLog
+ /usr/share/doc/lighttpd-1.3.0/compress.txt
+ /usr/share/doc/lighttpd-1.3.0/configuration.txt
+ /usr/share/doc/lighttpd-1.3.0/COPYING
+ /usr/share/doc/lighttpd-1.3.0/fastcgi-state.txt
+ /usr/share/doc/lighttpd-1.3.0/fastcgi.txt
+ /usr/share/doc/lighttpd-1.3.0/features.txt
+ /usr/share/doc/lighttpd-1.3.0/INSTALL
+ /usr/share/doc/lighttpd-1.3.0/NEWS
+ /usr/share/doc/lighttpd-1.3.0/performance.txt
+ /usr/share/doc/lighttpd-1.3.0/plugins.txt
+ /usr/share/doc/lighttpd-1.3.0/proxy.txt
+ /usr/share/doc/lighttpd-1.3.0/README
+ /usr/share/doc/lighttpd-1.3.0/redirect.txt
+ /usr/share/doc/lighttpd-1.3.0/rewrite.txt
+ /usr/share/doc/lighttpd-1.3.0/rrdtool.txt
+ /usr/share/doc/lighttpd-1.3.0/secdownload.txt
+ /usr/share/doc/lighttpd-1.3.0/security.txt
+ /usr/share/doc/lighttpd-1.3.0/simple-vhost.txt
+ /usr/share/doc/lighttpd-1.3.0/skeleton.txt
+ /usr/share/doc/lighttpd-1.3.0/ssi.txt
+ /usr/share/doc/lighttpd-1.3.0/state.txt
+ /usr/share/man/man1/lighttpd.1.gz
+
+------------------
+
+Port Notes:
+
+---------- lighttpd-1.3.1-1 -----------
+
+Updated to 1.3.1
+
+---------- lighttpd-1.3.0-1 -----------
+Initial release
+
+Cygwin port maintained by: Jan Kneschke <jan@kneschke.de>
+Please address all questions to the Cygwin mailing list at <cygwin@cygwin.com>
+
diff --git a/cygwin/setup.hint b/cygwin/setup.hint
new file mode 100644
index 00000000..c50b615c
--- /dev/null
+++ b/cygwin/setup.hint
@@ -0,0 +1,4 @@
+sdesc: "a light-weight and flexible webserver"
+ldesc: "lighttpd a secure, fast, compliant and very flexible web-server which has been optimized for high-performance environments. It has a very low memory footprint compared to other webservers and takes care of cpu-load. Its advanced feature-set (FastCGI, CGI, Auth, Output-Compression, URL-Rewriting and many more) make lighttpd the perfect webserver-software for every server that is suffering load problems. "
+category: Net Web
+requires: libpcre0 cygwin zlib openssl libbzip2_1 crypt
diff --git a/debian/.cvsignore b/debian/.cvsignore
new file mode 100644
index 00000000..3dda7298
--- /dev/null
+++ b/debian/.cvsignore
@@ -0,0 +1,2 @@
+Makefile.in
+Makefile
diff --git a/debian/Makefile.am b/debian/Makefile.am
new file mode 100644
index 00000000..f37933df
--- /dev/null
+++ b/debian/Makefile.am
@@ -0,0 +1,27 @@
+EXTRA_DIST=README.Debian.ex \
+changelog \
+compat \
+conffiles \
+control \
+copyright \
+cron.d.ex \
+dirs \
+docs \
+emacsen-install.ex \
+emacsen-remove.ex \
+emacsen-startup.ex \
+init.d \
+lighttpd-default.ex \
+lighttpd.conf \
+lighttpd.doc-base.EX \
+lighttpd.logrotate \
+lighttpd.postinst \
+manpage.1.ex \
+manpage.sgml.ex \
+manpage.xml.ex \
+menu.ex \
+postrm.ex \
+preinst.ex \
+prerm.ex \
+rules \
+watch.ex
diff --git a/debian/README.Debian.ex b/debian/README.Debian.ex
new file mode 100644
index 00000000..48cb2c71
--- /dev/null
+++ b/debian/README.Debian.ex
@@ -0,0 +1,6 @@
+lighttpd for Debian
+-------------------
+
+<possible notes regarding this package - if none, delete this file>
+
+ -- Vincent Wagelaar <vincent@hannibal.lr-s.tudelft.nl>, Wed, 24 Mar 2004 08:20:58 +0100
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 00000000..d3dde99d
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,144 @@
+lighttpd (1.3.10-1) unstable; urgency=low
+
+ * updated to 1.3.10
+
+ -- Jan Kneschke <jan@kneschke.de> Sun, 06 Feb 2005 15:41:00 +0000
+
+lighttpd (1.3.9-1) unstable; urgency=low
+
+ * updated to 1.3.9
+
+ -- Jan Kneschke <jan@kneschke.de> Sun, 06 Feb 2005 02:30:00 +0000
+
+lighttpd (1.3.8-1) unstable; urgency=low
+
+ * updated to 1.3.8
+
+ -- Jan Kneschke <jan@kneschke.de> Sun, 30 Jan 2005 00:49:00 +0000
+
+lighttpd (1.3.7-1) unstable; urgency=low
+
+ * updated to 1.3.7
+ * turned on SSL by default in debian packages
+
+ -- Jan Kneschke <jan@kneschke.de> Tue, 10 Dec 2004 19:09:15 +0000
+
+lighttpd (1.3.6-1) unstable; urgency=low
+
+ * updated to 1.3.6
+
+ -- Jan Kneschke <jan@kneschke.de> Tue, 03 Nov 2004 19:09:15 +0000
+
+lighttpd (1.3.5-1) unstable; urgency=low
+
+ * updated to 1.3.5
+ * added mod_alias
+ * added mod_userdir
+ * added the exec command to the SSI handler
+ * added a switch to disable follow-symlinks
+ * added a switch to disable IPv6 at compile-time
+ * fixed compilation on FreeBSD and NetBSD 1.3.x
+ * fixed segfault in pipelining
+ * fixed a segfault in writev() handler if LFS is used
+
+ -- Jan Kneschke <jan@kneschke.de> Sun, 31 Oct 2004 19:09:15 +0000
+
+lighttpd (1.3.4-1) unstable; urgency=low
+
+ * updated to 1.3.4
+
+ -- Jan Kneschke <jan@kneschke.de> Sun, 24 Oct 2004 19:09:15 +0000
+
+lighttpd (1.3.1-1) unstable; urgency=low
+
+ * updated to 1.3.1
+
+ -- Jan Kneschke <jan@kneschke.de> Sat, 30 Sep 2004 19:09:15 +0000
+
+lighttpd (1.2.8-1) unstable; urgency=low
+
+ * updated to 1.2.8
+
+ -- Jan Kneschke <jan@kneschke.de> Sat, 11 Sep 2004 19:09:15 +0000
+
+lighttpd (1.2.7-1) unstable; urgency=low
+
+ * added mod_rrdtool for internal statistics
+ * added xattr support
+ * added user-controlable timeouts
+ * improved documentation for many plugins
+ * fixed POST requests for mod_proxy
+ * fixed rare hang with CGI
+ * fixed seg-fault if no configfile is specified
+ * fixed rare problem in FastCGI header generation
+
+ -- Jan Kneschke <jan@kneschke.de> Sat, 04 Sep 2004 19:09:15 +0000
+
+lighttpd (1.2.5-1) unstable; urgency=low
+
+ * added skeleton for solaris 10 port-API
+ * added compression support even if no cachedir is set
+ * added conditional configoptions
+ * fixed compilation on OpenBSD
+ * fixed kqueue support
+ * fixed pipelining bug
+ * fixed parallel build (triggered by Gentoo)
+ * updated debian postinst
+
+ -- Jan Kneschke <jan@kneschke.de> Sun, 10 Aug 2004 19:09:15 +0000
+
+lighttpd (1.2.4-1) unstable; urgency=low
+
+ * added kqueue support
+ * added server-side includes (mod_ssi)
+ * fixed large post uploads in fastcgi
+ * fixed rt-signals handling of delayed events
+
+ -- Jan Kneschke <jan@kneschke.de> Sat, 31 Jul 2004 10:09:15 +0000
+
+lighttpd (1.2.3-1) unstable; urgency=low
+
+ * added a proxy module for Java and friends
+ * added support to pass accesslog through an external programm
+ * added mimetypes for text/css and text/javascript
+ * fixed index-files for FastCGI if webserver is in chroot
+ * fixed error messages of CGI process fails to exec()
+ * fixed detection of pcre on IRIX and FreeBSD
+ * fixed timestamps in Last-Modified checks
+ * fixed 64bit builds
+ * fixed mmap-caching of large files
+ * relaxed the HTTP parser on empty headerfields
+
+ -- Jan Kneschke <jan@kneschke.de> Sat, 10 Jul 2004 20:37:01 +0200
+
+lighttpd (1.2.2-1) unstable; urgency=low
+
+ * added support for unix domain sockets in FastCGI
+ * fixed mmap caching
+ * fixed compile-time check for linux sendfile()
+ * fixed check for pcre.h on Fedora Core 2
+
+ -- Jan Kneschke <jan@kneschke.de> Tue, 15 Jun 2004 20:37:01 +0100
+
+
+lighttpd (1.1.4-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Vincent Wagelaar <vincent@ricardis.tudelft.nl> Sun, 04 Apr 2004 21:44:58 +0100
+
+
+lighttpd (1.1.3-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Vincent Wagelaar <vincent@ricardis.tudelft.nl> Wed, 24 Mar 2004 08:20:58 +0100
+
+
+
+lighttpd (1.1.2-1) unstable; urgency=low
+
+ * Initial Release.
+
+ -- Vincent Wagelaar <vincent@ricardis.tudelft.nl> Wed, 24 Mar 2004 08:20:58 +0100
+
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 00000000..b8626c4c
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+4
diff --git a/debian/conffiles b/debian/conffiles
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/debian/conffiles
diff --git a/debian/control b/debian/control
new file mode 100644
index 00000000..bfeccb70
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,15 @@
+Source: lighttpd
+Section: web
+Priority: optional
+Maintainer: Jan Kneschke <jan@kneschke.de>
+Build-Depends: debhelper (>= 4.0.0), libssl-dev, zlib1g-dev, libbz2-dev, libpcre3-dev
+Standards-Version: 3.6.0
+
+Package: lighttpd
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Provides: httpd, httpd-cgi
+Suggests: openssl, rrdtool
+Description: A fast webserver with minimal memory footprint
+ lighttpd is intended to be a frontend for ad-servers which have to deliver
+ small files concurrently to many connections.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 00000000..7cdb5b38
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,38 @@
+This package was debianized by Vincent Wagelaar <vincent@ricardis.tudelft.nl> on
+Wed, 24 Mar 2004 08:20:58 +0100.
+
+It was downloaded from http://www.incremental.de/products/lighttpd/download/
+
+Upstream Author: Jan Kneschke <jan@kneschke.de>
+
+Copyright:
+
+Copyright (c) 2004, Jan Kneschke, incremental
+ All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+- Neither the name of the 'incremental' nor the names of its contributors may
+ be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/debian/cron.d.ex b/debian/cron.d.ex
new file mode 100644
index 00000000..59a20abd
--- /dev/null
+++ b/debian/cron.d.ex
@@ -0,0 +1,4 @@
+#
+# Regular cron jobs for the lighttpd package
+#
+0 4 * * * root lighttpd_maintenance
diff --git a/debian/dirs b/debian/dirs
new file mode 100644
index 00000000..ca882bbb
--- /dev/null
+++ b/debian/dirs
@@ -0,0 +1,2 @@
+usr/bin
+usr/sbin
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 00000000..35f23816
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,3 @@
+NEWS
+README
+doc
diff --git a/debian/emacsen-install.ex b/debian/emacsen-install.ex
new file mode 100644
index 00000000..3a32ca03
--- /dev/null
+++ b/debian/emacsen-install.ex
@@ -0,0 +1,45 @@
+#! /bin/sh -e
+# /usr/lib/emacsen-common/packages/install/lighttpd
+
+# Written by Jim Van Zandt <jrv@vanzandt.mv.com>, borrowing heavily
+# from the install scripts for gettext by Santiago Vila
+# <sanvila@ctv.es> and octave by Dirk Eddelbuettel <edd@debian.org>.
+
+FLAVOR=$1
+PACKAGE=lighttpd
+
+if [ ${FLAVOR} = emacs ]; then exit 0; fi
+
+echo install/${PACKAGE}: Handling install for emacsen flavor ${FLAVOR}
+
+#FLAVORTEST=`echo $FLAVOR | cut -c-6`
+#if [ ${FLAVORTEST} = xemacs ] ; then
+# SITEFLAG="-no-site-file"
+#else
+# SITEFLAG="--no-site-file"
+#fi
+FLAGS="${SITEFLAG} -q -batch -l path.el -f batch-byte-compile"
+
+ELDIR=/usr/share/emacs/site-lisp/${PACKAGE}
+ELCDIR=/usr/share/${FLAVOR}/site-lisp/${PACKAGE}
+
+# Install-info-altdir does not actually exist.
+# Maybe somebody will write it.
+if test -x /usr/sbin/install-info-altdir; then
+ echo install/${PACKAGE}: install Info links for ${FLAVOR}
+ install-info-altdir --quiet --section "" "" --dirname=${FLAVOR} /usr/info/${PACKAGE}.info.gz
+fi
+
+install -m 755 -d ${ELCDIR}
+cd ${ELDIR}
+FILES=`echo *.el`
+cp ${FILES} ${ELCDIR}
+cd ${ELCDIR}
+
+cat << EOF > path.el
+(setq load-path (cons "." load-path) byte-compile-warnings nil)
+EOF
+${FLAVOR} ${FLAGS} ${FILES}
+rm -f *.el path.el
+
+exit 0
diff --git a/debian/emacsen-remove.ex b/debian/emacsen-remove.ex
new file mode 100644
index 00000000..e049ae32
--- /dev/null
+++ b/debian/emacsen-remove.ex
@@ -0,0 +1,15 @@
+#!/bin/sh -e
+# /usr/lib/emacsen-common/packages/remove/lighttpd
+
+FLAVOR=$1
+PACKAGE=lighttpd
+
+if [ ${FLAVOR} != emacs ]; then
+ if test -x /usr/sbin/install-info-altdir; then
+ echo remove/${PACKAGE}: removing Info links for ${FLAVOR}
+ install-info-altdir --quiet --remove --dirname=${FLAVOR} /usr/info/lighttpd.info.gz
+ fi
+
+ echo remove/${PACKAGE}: purging byte-compiled files for ${FLAVOR}
+ rm -rf /usr/share/${FLAVOR}/site-lisp/${PACKAGE}
+fi
diff --git a/debian/emacsen-startup.ex b/debian/emacsen-startup.ex
new file mode 100644
index 00000000..75980033
--- /dev/null
+++ b/debian/emacsen-startup.ex
@@ -0,0 +1,19 @@
+;; -*-emacs-lisp-*-
+;;
+;; Emacs startup file for the Debian lighttpd package
+;;
+;; Originally contributed by Nils Naumann <naumann@unileoben.ac.at>
+;; Modified by Dirk Eddelbuettel <edd@debian.org>
+;; Adapted for dh-make by Jim Van Zandt <jrv@vanzandt.mv.com>
+
+;; The lighttpd package follows the Debian/GNU Linux 'emacsen' policy and
+;; byte-compiles its elisp files for each 'emacs flavor' (emacs19,
+;; xemacs19, emacs20, xemacs20...). The compiled code is then
+;; installed in a subdirectory of the respective site-lisp directory.
+;; We have to add this to the load-path:
+(let ((package-dir (concat "/usr/share/"
+ (symbol-name flavor)
+ "/site-lisp/lighttpd")))
+ (when (file-directory-p package-dir)
+ (setq load-path (cons package-dir load-path))))
+
diff --git a/debian/init.d b/debian/init.d
new file mode 100644
index 00000000..9822eeb0
--- /dev/null
+++ b/debian/init.d
@@ -0,0 +1,78 @@
+#! /bin/sh
+#
+# skeleton example file to build /etc/init.d/ scripts.
+# This file should be used to construct scripts for /etc/init.d.
+#
+# Written by Miquel van Smoorenburg <miquels@cistron.nl>.
+# Modified for Debian
+# by Ian Murdock <imurdock@gnu.ai.mit.edu>.
+#
+# Version: @(#)skeleton 1.9 26-Feb-2001 miquels@cistron.nl
+#
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/usr/sbin/lighttpd
+OPTS="-f /etc/lighttpd/lighttpd.conf"
+NAME=lighttpd
+DESC=lighttpd
+
+test -x $DAEMON || exit 0
+
+# Include lighttpd defaults if available
+if [ -f /etc/default/lighttpd ] ; then
+ . /etc/default/lighttpd
+fi
+
+set -e
+
+case "$1" in
+ start)
+ echo -n "Starting $DESC: "
+ start-stop-daemon --start --quiet \
+ --pidfile /var/run/$NAME.pid --exec $DAEMON -- $OPTS
+ echo "$NAME."
+ ;;
+ stop)
+ echo -n "Stopping $DESC: "
+ if start-stop-daemon --stop --pidfile /var/run/$NAME.pid \
+ --exec $DAEMON; then
+ rm -f /var/run/$NAME.pid
+ echo "$NAME."
+ fi
+ ;;
+ reload)
+ #
+ # If the daemon can reload its config files on the fly
+ # for example by sending it SIGHUP, do it here.
+ #
+ # If the daemon responds to changes in its config file
+ # directly anyway, make this a do-nothing entry.
+ #
+ echo "Reloading $DESC configuration files."
+ start-stop-daemon --stop --signal 1 --quiet --pidfile /var/run/$NAME.pid \
+ --exec $DAEMON
+ ;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented, move the "force-reload"
+ # option to the "reload" entry above. If not, "force-reload" is
+ # just the same as "restart".
+ #
+ echo -n "Restarting $DESC: "
+ start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/$NAME.pid \
+ --exec $DAEMON
+ rm -f /var/run/$NAME.pid
+ sleep 1
+ start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
+ --exec $DAEMON -- $OPTS
+ echo "$NAME."
+ ;;
+ *)
+ N=/etc/init.d/$NAME
+ echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
+ #echo "Usage: $N {start|stop|restart|force-reload}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/debian/lighttpd-default.ex b/debian/lighttpd-default.ex
new file mode 100644
index 00000000..4866702b
--- /dev/null
+++ b/debian/lighttpd-default.ex
@@ -0,0 +1,10 @@
+# Defaults for lighttpd initscript
+# sourced by /etc/init.d/lighttpd
+# installed at /etc/default/lighttpd by the maintainer scripts
+
+#
+# This is a POSIX shell fragment
+#
+
+# Additional options that are passed to the Daemon.
+DAEMON_OPTS=""
diff --git a/debian/lighttpd.conf b/debian/lighttpd.conf
new file mode 100644
index 00000000..389181ec
--- /dev/null
+++ b/debian/lighttpd.conf
@@ -0,0 +1,232 @@
+# lighttpd configuration file
+#
+# use a it as base for lighttpd 1.0.0 and above
+#
+# $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $
+
+############ Options you really have to take care of ####################
+
+## modules to load
+# at least mod_access and mod_accesslog should be loaded
+# all other module should only be loaded if really neccesary
+# - saves some time
+# - saves memory
+server.modules = (
+# "mod_rewrite",
+# "mod_redirect",
+ "mod_access",
+# "mod_auth",
+# "mod_status",
+# "mod_fastcgi",
+# "mod_simple_vhost",
+# "mod_evhost",
+# "mod_cgi",
+# "mod_compress",
+# "mod_ssi",
+# "mod_usertrack",
+# "mod_rrdtool",
+ "mod_accesslog" )
+
+## a static document-root, for virtual-hosting take look at the
+## server.virtual-* options
+server.document-root = "/var/www/"
+
+## where to send error-messages to
+server.errorlog = "/var/log/lighttpd/error.log"
+
+# files to check for if .../ is requested
+server.indexfiles = ( "index.php", "index.html",
+ "index.htm", "default.htm" )
+
+# mimetype mapping
+mimetype.assign = (
+ ".pdf" => "application/pdf",
+ ".sig" => "application/pgp-signature",
+ ".spl" => "application/futuresplash",
+ ".class" => "application/octet-stream",
+ ".ps" => "application/postscript",
+ ".torrent" => "application/x-bittorrent",
+ ".dvi" => "application/x-dvi",
+ ".gz" => "application/x-gzip",
+ ".pac" => "application/x-ns-proxy-autoconfig",
+ ".swf" => "application/x-shockwave-flash",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".tar" => "application/x-tar",
+ ".zip" => "application/zip",
+ ".mp3" => "audio/mpeg",
+ ".m3u" => "audio/x-mpegurl",
+ ".wma" => "audio/x-ms-wma",
+ ".wax" => "audio/x-ms-wax",
+ ".ogg" => "audio/x-wav",
+ ".wav" => "audio/x-wav",
+ ".gif" => "image/gif",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".png" => "image/png",
+ ".xbm" => "image/x-xbitmap",
+ ".xpm" => "image/x-xpixmap",
+ ".xwd" => "image/x-xwindowdump",
+ ".css" => "text/css",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".js" => "text/javascript",
+ ".asc" => "text/plain",
+ ".c" => "text/plain",
+ ".conf" => "text/plain",
+ ".text" => "text/plain",
+ ".txt" => "text/plain",
+ ".dtd" => "text/xml",
+ ".xml" => "text/xml",
+ ".mpeg" => "video/mpeg",
+ ".mpg" => "video/mpeg",
+ ".mov" => "video/quicktime",
+ ".qt" => "video/quicktime",
+ ".avi" => "video/x-msvideo",
+ ".asf" => "video/x-ms-asf",
+ ".asx" => "video/x-ms-asf",
+ ".wmv" => "video/x-ms-wmv"
+ )
+
+# Use the "Content-Type" extended attribute to obtain mime type if possible
+# mimetypes.use-xattr = "enable"
+
+#### accesslog module
+accesslog.filename = "/var/log/lighttpd/access.log"
+
+## deny access the file-extensions
+#
+# ~ is for backupfiles from vi, emacs, joe, ...
+# .inc is often used for code includes which should in general not be part
+# of the document-root
+url.access-deny = ( "~", ".inc" )
+
+
+
+######### Options that are good to be but not neccesary to be changed #######
+
+## bind to port (default: 80)
+#server.port = 81
+
+## bind to localhost (default: all interfaces)
+#server.bind = "grisu.home.kneschke.de"
+
+## error-handler for status 404
+#server.error-handler-404 = "/error-handler.html"
+#server.error-handler-404 = "/error-handler.php"
+
+## to help the rc.scripts
+server.pid-file = "/var/run/lighttpd.pid"
+
+
+###### virtual hosts
+##
+## If you want name-based virtual hosting add the next three settings and load
+## mod_simple_vhost
+##
+## document-root =
+## virtual-server-root + virtual-server-default-host + virtual-server-docroot or
+## virtual-server-root + http-host + virtual-server-docroot
+##
+#simple-vhost.server-root = "/home/weigon/wwwroot/servers/"
+#simple-vhost.default-host = "grisu.home.kneschke.de"
+#simple-vhost.document-root = "/pages/"
+
+
+##
+## Format: <errorfile-prefix><status>.html
+## -> ..../status-404.html for 'File not found'
+#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-"
+
+## virtual directory listings
+#server.dir-listing = "enable"
+
+## send unhandled HTTP-header headers to error-log
+#debug.dump-unknown-headers = "enable"
+
+### only root can use these options
+#
+# chroot() to directory (default: no chroot() )
+#server.chroot = "/"
+
+## change uid to <uid> (default: don't care)
+server.username = "www-data"
+
+## change uid to <uid> (default: don't care)
+server.groupname = "www-data"
+
+#### compress module
+#compress.cache-dir = "/var/tmp/lighttpd/cache/compress/"
+#compress.filetype = ("text/plain", "text/html")
+
+#### fastcgi module
+## read fastcgi.txt for more info
+#fastcgi.server = ( ".php" =>
+# ( "localhost" =>
+# (
+# "socket" => "/tmp/php-fastcgi.socket",
+# "bin-path" => "/usr/local/bin/php"
+# )
+# )
+# )
+
+#### CGI module
+#cgi.assign = ( ".pl" => "/usr/bin/perl",
+# ".cgi" => "/usr/bin/perl" )
+
+#### SSL engine
+#ssl.engine = "enable"
+#ssl.pemfile = "server.pem"
+
+#### status module
+# status.status-url = "/server-status"
+# status.config-url = "/server-config"
+
+#### auth module
+## read authentification.txt for more info
+# auth.backend = "plain"
+# auth.backend.plain.userfile = "lighttpd.user"
+# auth.backend.plain.groupfile = "lighttpd.group"
+
+# auth.backend.ldap.hostname = "localhost"
+# auth.backend.ldap.base-dn = "dc=my-domain,dc=com"
+# auth.backend.ldap.filter = "(uid=$)"
+
+# auth.require = ( "/server-status" =>
+# (
+# "method" => "digest",
+# "realm" => "download archiv",
+# "require" => "group=www|user=jan|host=192.168.2.10"
+# ),
+# "/server-info" =>
+# (
+# "method" => "digest",
+# "realm" => "download archiv",
+# "require" => "group=www|user=jan|host=192.168.2.10"
+# )
+# )
+
+#### url handling modules (rewrite, redirect, access)
+# url.rewrite = ( "^/$" => "/server-status" )
+# url.redirect = ( "^/wishlist/(.+)" => "http://www.123.org/$1" )
+
+#
+# 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/storage/dev/www/%3/htdocs/"
+
+#### expire module
+# expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes")
+
+#### ssi
+# ssi.extension = ( ".shtml" )
+
+#### rrdtool
+# rrdtool.binary = "/usr/bin/rrdtool"
+# rrdtool.db-name = "/var/www/lighttpd.rrd"
diff --git a/debian/lighttpd.doc-base.EX b/debian/lighttpd.doc-base.EX
new file mode 100644
index 00000000..edfecd13
--- /dev/null
+++ b/debian/lighttpd.doc-base.EX
@@ -0,0 +1,22 @@
+Document: lighttpd
+Title: Debian lighttpd Manual
+Author: <insert document author here>
+Abstract: This manual describes what lighttpd is
+ and how it can be used to
+ manage online manuals on Debian systems.
+Section: unknown
+
+Format: debiandoc-sgml
+Files: /usr/share/doc/lighttpd/lighttpd.sgml.gz
+
+Format: postscript
+Files: /usr/share/doc/lighttpd/lighttpd.ps.gz
+
+Format: text
+Files: /usr/share/doc/lighttpd/lighttpd.text.gz
+
+Format: HTML
+Index: /usr/share/doc/lighttpd/html/index.html
+Files: /usr/share/doc/lighttpd/html/*.html
+
+
diff --git a/debian/lighttpd.logrotate b/debian/lighttpd.logrotate
new file mode 100644
index 00000000..e2fb5571
--- /dev/null
+++ b/debian/lighttpd.logrotate
@@ -0,0 +1,18 @@
+/var/log/lighttpd/*.log {
+ daily
+ missingok
+ copytruncate
+ rotate 7
+ compress
+ notifempty
+ sharedscripts
+ postrotate
+ if [ -f /var/run/lighttpd.pid ]; then \
+ if [ -x /usr/sbin/invoke-rc.d ]; then \
+ invoke-rc.d lighttpd reload > /dev/null; \
+ else \
+ /etc/init.d/lighttpd reload > /dev/null; \
+ fi; \
+ fi;
+ endscript
+}
diff --git a/debian/lighttpd.postinst b/debian/lighttpd.postinst
new file mode 100644
index 00000000..f3ee3ed9
--- /dev/null
+++ b/debian/lighttpd.postinst
@@ -0,0 +1,42 @@
+#! /bin/sh
+# postinst script for lighttpd
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postinst> `configure' <most-recently-configured-version>
+# * <old-postinst> `abort-upgrade' <new version>
+# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+# <new-version>
+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+# <failed-install-package> <version> `removing'
+# <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+#
+
+case "$1" in
+ configure)
+ chown www-data:www-data /var/log/lighttpd
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/manpage.1.ex b/debian/manpage.1.ex
new file mode 100644
index 00000000..1396b394
--- /dev/null
+++ b/debian/manpage.1.ex
@@ -0,0 +1,60 @@
+.\" Hey, EMACS: -*- nroff -*-
+.\" First parameter, NAME, should be all caps
+.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
+.\" other parameters are allowed: see man(7), man(1)
+.TH LIGHTTPD SECTION "March 24, 2004"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh disable hyphenation
+.\" .hy enable hyphenation
+.\" .ad l left justify
+.\" .ad b justify to both left and right margins
+.\" .nf disable filling
+.\" .fi enable filling
+.\" .br insert line break
+.\" .sp <n> insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.SH NAME
+lighttpd \- program to do something
+.SH SYNOPSIS
+.B lighttpd
+.RI [ options ] " files" ...
+.br
+.B bar
+.RI [ options ] " files" ...
+.SH DESCRIPTION
+This manual page documents briefly the
+.B lighttpd
+and
+.B bar
+commands.
+This manual page was written for the Debian distribution
+because the original program does not have a manual page.
+Instead, it has documentation in the GNU Info format; see below.
+.PP
+.\" TeX users may be more comfortable with the \fB<whatever>\fP and
+.\" \fI<whatever>\fP escape sequences to invode bold face and italics,
+.\" respectively.
+\fBlighttpd\fP is a program that...
+.SH OPTIONS
+These programs follow the usual GNU command line syntax, with long
+options starting with two dashes (`-').
+A summary of options is included below.
+For a complete description, see the Info files.
+.TP
+.B \-h, \-\-help
+Show summary of options.
+.TP
+.B \-v, \-\-version
+Show version of program.
+.SH SEE ALSO
+.BR bar (1),
+.BR baz (1).
+.br
+The programs are documented fully by
+.IR "The Rise and Fall of a Fooish Bar" ,
+available via the Info system.
+.SH AUTHOR
+This manual page was written by Vincent Wagelaar <vincent@hannibal.lr-s.tudelft.nl>,
+for the Debian project (but may be used by others).
diff --git a/debian/manpage.sgml.ex b/debian/manpage.sgml.ex
new file mode 100644
index 00000000..4efa3da4
--- /dev/null
+++ b/debian/manpage.sgml.ex
@@ -0,0 +1,156 @@
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
+
+<!-- Process this file with docbook-to-man to generate an nroff manual
+ page: `docbook-to-man manpage.sgml > manpage.1'. You may view
+ the manual page with: `docbook-to-man manpage.sgml | nroff -man |
+ less'. A typical entry in a Makefile or Makefile.am is:
+
+manpage.1: manpage.sgml
+ docbook-to-man $< > $@
+
+
+ The docbook-to-man binary is found in the docbook-to-man package.
+ Please remember that if you create the nroff version in one of the
+ debian/rules file targets (such as build), you will need to include
+ docbook-to-man in your Build-Depends control field.
+
+ -->
+
+ <!-- Fill in your name for FIRSTNAME and SURNAME. -->
+ <!ENTITY dhfirstname "<firstname>FIRSTNAME</firstname>">
+ <!ENTITY dhsurname "<surname>SURNAME</surname>">
+ <!-- Please adjust the date whenever revising the manpage. -->
+ <!ENTITY dhdate "<date>March 24, 2004</date>">
+ <!-- SECTION should be 1-8, maybe w/ subsection other parameters are
+ allowed: see man(7), man(1). -->
+ <!ENTITY dhsection "<manvolnum>SECTION</manvolnum>">
+ <!ENTITY dhemail "<email>vincent@hannibal.lr-s.tudelft.nl</email>">
+ <!ENTITY dhusername "Vincent Wagelaar">
+ <!ENTITY dhucpackage "<refentrytitle>LIGHTTPD</refentrytitle>">
+ <!ENTITY dhpackage "lighttpd">
+
+ <!ENTITY debian "<productname>Debian</productname>">
+ <!ENTITY gnu "<acronym>GNU</acronym>">
+ <!ENTITY gpl "&gnu; <acronym>GPL</acronym>">
+]>
+
+<refentry>
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ <copyright>
+ <year>2003</year>
+ <holder>&dhusername;</holder>
+ </copyright>
+ &dhdate;
+ </refentryinfo>
+ <refmeta>
+ &dhucpackage;
+
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>&dhpackage;</refname>
+
+ <refpurpose>program to do something</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>&dhpackage;</command>
+
+ <arg><option>-e <replaceable>this</replaceable></option></arg>
+
+ <arg><option>--example <replaceable>that</replaceable></option></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>This manual page documents briefly the
+ <command>&dhpackage;</command> and <command>bar</command>
+ commands.</para>
+
+ <para>This manual page was written for the &debian; distribution
+ because the original program does not have a manual page.
+ Instead, it has documentation in the &gnu;
+ <application>Info</application> format; see below.</para>
+
+ <para><command>&dhpackage;</command> is a program that...</para>
+
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <para>These programs follow the usual &gnu; command line syntax,
+ with long options starting with two dashes (`-'). A summary of
+ options is included below. For a complete description, see the
+ <application>Info</application> files.</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>-h</option>
+ <option>--help</option>
+ </term>
+ <listitem>
+ <para>Show summary of options.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-v</option>
+ <option>--version</option>
+ </term>
+ <listitem>
+ <para>Show version of program.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>bar (1), baz (1).</para>
+
+ <para>The programs are documented fully by <citetitle>The Rise and
+ Fall of a Fooish Bar</citetitle> available via the
+ <application>Info</application> system.</para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>This manual page was written by &dhusername; &dhemail; for
+ the &debian; system (but may be used by others). Permission is
+ granted to copy, distribute and/or modify this document under
+ the terms of the &gnu; General Public License, Version 2 any
+ later version published by the Free Software Foundation.
+ </para>
+ <para>
+ On Debian systems, the complete text of the GNU General Public
+ License can be found in /usr/share/common-licenses/GPL.
+ </para>
+
+ </refsect1>
+</refentry>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-omittag:t
+sgml-shorttag:t
+sgml-minimize-attributes:nil
+sgml-always-quote-attributes:t
+sgml-indent-step:2
+sgml-indent-data:t
+sgml-parent-document:nil
+sgml-default-dtd-file:nil
+sgml-exposed-tags:nil
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+End:
+-->
+
+
diff --git a/debian/manpage.xml.ex b/debian/manpage.xml.ex
new file mode 100644
index 00000000..c2c58ee9
--- /dev/null
+++ b/debian/manpage.xml.ex
@@ -0,0 +1,148 @@
+<?xml version='1.0' encoding='ISO-8859-1'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
+
+<!--
+
+Process this file with an XSLT processor: `xsltproc \
+-''-nonet /usr/share/sgml/docbook/stylesheet/xsl/nwalsh/\
+manpages/docbook.xsl manpage.dbk'. A manual page
+<package>.<section> will be generated. You may view the
+manual page with: nroff -man <package>.<section> | less'. A
+typical entry in a Makefile or Makefile.am is:
+
+DB2MAN=/usr/share/sgml/docbook/stylesheet/xsl/nwalsh/\
+manpages/docbook.xsl
+XP=xsltproc -''-nonet
+
+manpage.1: manpage.dbk
+ $(XP) $(DB2MAN) $<
+
+The xsltproc binary is found in the xsltproc package. The
+XSL files are in docbook-xsl. Please remember that if you
+create the nroff version in one of the debian/rules file
+targets (such as build), you will need to include xsltproc
+and docbook-xsl in your Build-Depends control field.
+
+-->
+
+ <!-- Fill in your name for FIRSTNAME and SURNAME. -->
+ <!ENTITY dhfirstname "<firstname>FIRSTNAME</firstname>">
+ <!ENTITY dhsurname "<surname>SURNAME</surname>">
+ <!-- Please adjust the date whenever revising the manpage. -->
+ <!ENTITY dhdate "<date>March 24, 2004</date>">
+ <!-- SECTION should be 1-8, maybe w/ subsection other parameters are
+ allowed: see man(7), man(1). -->
+ <!ENTITY dhsection "<manvolnum>SECTION</manvolnum>">
+ <!ENTITY dhemail "<email>vincent@hannibal.lr-s.tudelft.nl</email>">
+ <!ENTITY dhusername "Vincent Wagelaar">
+ <!ENTITY dhucpackage "<refentrytitle>LIGHTTPD</refentrytitle>">
+ <!ENTITY dhpackage "lighttpd">
+
+ <!ENTITY debian "<productname>Debian</productname>">
+ <!ENTITY gnu "<acronym>GNU</acronym>">
+ <!ENTITY gpl "&gnu; <acronym>GPL</acronym>">
+]>
+
+<refentry>
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ <copyright>
+ <year>2003</year>
+ <holder>&dhusername;</holder>
+ </copyright>
+ &dhdate;
+ </refentryinfo>
+ <refmeta>
+ &dhucpackage;
+
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>&dhpackage;</refname>
+
+ <refpurpose>program to do something</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>&dhpackage;</command>
+
+ <arg><option>-e <replaceable>this</replaceable></option></arg>
+
+ <arg><option>--example <replaceable>that</replaceable></option></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>This manual page documents briefly the
+ <command>&dhpackage;</command> and <command>bar</command>
+ commands.</para>
+
+ <para>This manual page was written for the &debian; distribution
+ because the original program does not have a manual page.
+ Instead, it has documentation in the &gnu;
+ <application>Info</application> format; see below.</para>
+
+ <para><command>&dhpackage;</command> is a program that...</para>
+
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <para>These programs follow the usual &gnu; command line syntax,
+ with long options starting with two dashes (`-'). A summary of
+ options is included below. For a complete description, see the
+ <application>Info</application> files.</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>-h</option>
+ <option>--help</option>
+ </term>
+ <listitem>
+ <para>Show summary of options.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-v</option>
+ <option>--version</option>
+ </term>
+ <listitem>
+ <para>Show version of program.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>bar (1), baz (1).</para>
+
+ <para>The programs are documented fully by <citetitle>The Rise and
+ Fall of a Fooish Bar</citetitle> available via the
+ <application>Info</application> system.</para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>This manual page was written by &dhusername; &dhemail; for
+ the &debian; system (but may be used by others). Permission is
+ granted to copy, distribute and/or modify this document under
+ the terms of the &gnu; General Public License, Version 2 any
+ later version published by the Free Software Foundation.
+ </para>
+ <para>
+ On Debian systems, the complete text of the GNU General Public
+ License can be found in /usr/share/common-licenses/GPL.
+ </para>
+
+ </refsect1>
+</refentry>
+
diff --git a/debian/menu.ex b/debian/menu.ex
new file mode 100644
index 00000000..07134b69
--- /dev/null
+++ b/debian/menu.ex
@@ -0,0 +1,2 @@
+?package(lighttpd):needs=X11|text|vc|wm section=Apps/see-menu-manual\
+ title="lighttpd" command="/usr/bin/lighttpd"
diff --git a/debian/postrm.ex b/debian/postrm.ex
new file mode 100644
index 00000000..3645f893
--- /dev/null
+++ b/debian/postrm.ex
@@ -0,0 +1,38 @@
+#! /bin/sh
+# postrm script for lighttpd
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postrm> `remove'
+# * <postrm> `purge'
+# * <old-postrm> `upgrade' <new-version>
+# * <new-postrm> `failed-upgrade' <old-version>
+# * <new-postrm> `abort-install'
+# * <new-postrm> `abort-install' <old-version>
+# * <new-postrm> `abort-upgrade' <old-version>
+# * <disappearer's-postrm> `disappear' <r>overwrit>r> <new-version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+
+
+ ;;
+
+ *)
+ echo "postrm called with unknown argument \`$1'" >&2
+ exit 1
+
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/preinst.ex b/debian/preinst.ex
new file mode 100644
index 00000000..6e888f3f
--- /dev/null
+++ b/debian/preinst.ex
@@ -0,0 +1,44 @@
+#! /bin/sh
+# preinst script for lighttpd
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <new-preinst> `install'
+# * <new-preinst> `install' <old-version>
+# * <new-preinst> `upgrade' <old-version>
+# * <old-preinst> `abort-upgrade' <new-version>
+#
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ install|upgrade)
+# if [ "$1" = "upgrade" ]
+# then
+# start-stop-daemon --stop --quiet --oknodo \
+# --pidfile /var/run/lighttpd.pid \
+# --exec /usr/sbin/lighttpd 2>/dev/null || true
+# fi
+ ;;
+
+ abort-upgrade)
+ ;;
+
+ *)
+ echo "preinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/prerm.ex b/debian/prerm.ex
new file mode 100644
index 00000000..538ea1c8
--- /dev/null
+++ b/debian/prerm.ex
@@ -0,0 +1,39 @@
+#! /bin/sh
+# prerm script for lighttpd
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <prerm> `remove'
+# * <old-prerm> `upgrade' <new-version>
+# * <new-prerm> `failed-upgrade' <old-version>
+# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
+# * <deconfigured's-prerm> `deconfigure' `in-favour'
+# <package-being-installed> <version> `removing'
+# <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ remove|upgrade|deconfigure)
+# install-info --quiet --remove /usr/info/lighttpd.info.gz
+ ;;
+ failed-upgrade)
+ ;;
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 00000000..5e3d0476
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,117 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+
+# These are used for cross-compiling and for saving the configure script
+# from having to guess our platform (since we know it already)
+DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE)
+DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE)
+
+
+CFLAGS = -Wall -g
+
+ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
+ CFLAGS += -O0
+else
+ CFLAGS += -O2
+endif
+ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS)))
+ INSTALL_PROGRAM += -s
+endif
+
+config.status: configure
+ dh_testdir
+ # Add here commands to configure the package.
+ CFLAGS="$(CFLAGS)" ./configure --host=$(DEB_HOST_GNU_TYPE) \
+ --build=$(DEB_BUILD_GNU_TYPE)\
+ --prefix=/usr \
+ --mandir=\$${prefix}/share/man \
+ --infodir=\$${prefix}/share/info \
+ --sysconfdir=/etc/lighttpd \
+ --libdir=/usr/lib/lighttpd \
+ --with-openssl
+
+
+
+build: build-stamp
+
+build-stamp: config.status
+ dh_testdir
+
+ # Add here commands to compile the package.
+ $(MAKE)
+ #/usr/bin/docbook-to-man debian/lighttpd.sgml > lighttpd.1
+
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp
+
+ # Add here commands to clean up after the build process.
+ -$(MAKE) distclean
+ifneq "$(wildcard /usr/share/misc/config.sub)" ""
+ cp -f /usr/share/misc/config.sub config.sub
+endif
+ifneq "$(wildcard /usr/share/misc/config.guess)" ""
+ cp -f /usr/share/misc/config.guess config.guess
+endif
+
+
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ # Add here commands to install the package into debian/lighttpd.
+ $(MAKE) install DESTDIR=$(CURDIR)/debian/lighttpd
+ install -d debian/lighttpd/etc/{lighttpd,init.d,logrotate.d}
+ install -d debian/lighttpd/var/{www,log/lighttpd}
+ install -m 644 debian/*conf debian/lighttpd/etc/lighttpd/
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs ChangeLog
+ dh_installdocs
+ dh_installexamples
+# dh_install
+# dh_installmenu
+# dh_installdebconf
+ dh_installlogrotate
+# dh_installemacsen
+# dh_installpam
+# dh_installmime
+ dh_installinit
+# dh_installcron
+# dh_installinfo
+ dh_installman
+ dh_link
+ dh_strip
+ dh_compress
+ dh_fixperms
+# dh_perl
+# dh_python
+# dh_makeshlibs
+ dh_installdeb
+ dh_shlibdeps
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install
diff --git a/debian/watch.ex b/debian/watch.ex
new file mode 100644
index 00000000..87e1b9fd
--- /dev/null
+++ b/debian/watch.ex
@@ -0,0 +1,6 @@
+# Example watch control file for uscan
+# Rename this file to "watch" and then you can run the "uscan" command
+# to check for upstream updates and more.
+# Site Directory Pattern Version Script
+version=2
+sunsite.unc.edu /pub/Linux/Incoming lighttpd-(.*)\.tar\.gz debian uupdate
diff --git a/distribute.sh.in b/distribute.sh.in
new file mode 100644
index 00000000..8db25094
--- /dev/null
+++ b/distribute.sh.in
@@ -0,0 +1,92 @@
+#!/bin/sh
+PACKAGE=@PACKAGE_TARNAME@
+VERSION=@VERSION@
+NAME=@PACKAGE_TARNAME@-@VERSION@
+
+DISTDIR="/home/weigon/wwwroot/servers/www.lighttpd.net/pages/download/"
+FILES="content-3 content-4 content-5 \
+ /usr/src/packages/RPMS/i586/${NAME}-1.i586.rpm \
+ /usr/src/packages/SRPMS/${NAME}-1.src.rpm \
+ ${NAME}.tar.gz \
+ NEWS.html \
+ ChangeLog \
+ release-news.${VERSION}.txt \
+ ${NAME}.tar.gz.sig"
+DLURL="http://www.lighttpd.net/download"
+pack=0
+echo $1
+case "$1" in
+ --pack) pack=1;;
+esac
+
+echo ${nopack}
+
+if test x${pack} = x1; then
+ make distcheck && rpmbuild -ta --nodeps ${NAME}.tar.gz
+ gpg --detach-sign ${NAME}.tar.gz
+ rpm --addsign /usr/src/packages/RPMS/i586/${NAME}-1.i586.rpm /usr/src/packages/SRPMS/${NAME}-1.src.rpm
+fi
+
+MD5RPM=`md5sum /usr/src/packages/RPMS/i586/${NAME}-1.i586.rpm| cut -b 1-32`
+MD5SRPM=`md5sum /usr/src/packages/SRPMS/${NAME}-1.src.rpm| cut -b 1-32`
+MD5TGZ=`md5sum ${NAME}.tar.gz| cut -b 1-32`
+DATE=`date +'%Y-%m-%d %H:%M'`
+NEWS=`cat NEWS | sed "/^- ${VERSION}/,/^-/p;d" | sed "/^- /d;/^$/d"`
+DLNAME="${DLURL}/${NAME}"
+
+cat > release-news.${VERSION} <<EOF
+!${PACKAGE} ${VERSION} - ${DATE}
+- *Changes*
+++QUOTED++
+<pre>${NEWS}
+</pre><br />
+--QUOTED--
+- *Download*
+- (${NAME}-1.i586.rpm|${NAME}-1.i586.rpm) [built on SuSE 9.0]
+ MD5: ${MD5RPM}
+- (${NAME}-1.src.rpm|${NAME}-1.src.rpm)
+ MD5: ${MD5SRPM}
+- (${NAME}.tar.gz|${NAME}.tar.gz)
+ MD5: ${MD5TGZ} (${NAME}.tar.gz.sig|signature)
+
+EOF
+
+cat > release-news.${VERSION}.txt <<EOF
+${PACKAGE} ${VERSION} - ${DATE}
+
+Changes
+-------
+${NEWS}
+
+Download
+- ${NAME}-1.i586.rpm [built on SuSE 9.0]
+ ${DLNAME}-1.i586.rpm
+ MD5: ${MD5RPM}
+- ${NAME}-1.src.rpm
+ ${DLNAME}-1.src.rpm
+ MD5: ${MD5SRPM}
+- ${NAME}.tar.gz
+ ${DLNAME}.tar.gz
+ MD5: ${MD5TGZ}
+ Signature: ${DLNAME}.tar.gz.sig
+
+EOF
+
+rst2html.py NEWS > NEWS.html
+
+cat content-4.foot | sed "/^\!${PACKAGE} ${VERSION}/,/^$/d" > content-4.foot.new
+cat release-news.${VERSION} content-4.foot.new > content-4.foot
+rm content-4.foot.new
+cat content-4.head content-4.foot > content-4
+
+for i in ${DISTDIR}; do
+ cp -u ${FILES} $i
+done
+
+curdir=`pwd`
+cd ~/wwwroot/servers/www.lighttpd.net/
+make put
+cd ${curdir}
+
+kmail -s "ANNOUNCE: ${NAME}" --msg `pwd`/release-news.${VERSION}.txt lighttpd-announce@lists.kneschke.de
+
diff --git a/doc/.cvsignore b/doc/.cvsignore
new file mode 100644
index 00000000..3dda7298
--- /dev/null
+++ b/doc/.cvsignore
@@ -0,0 +1,2 @@
+Makefile.in
+Makefile
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644
index 00000000..d515024a
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,84 @@
+dist_man1_MANS=lighttpd.1
+
+
+DOCS=accesslog.txt \
+authentification.txt \
+cgi.txt \
+compress.txt \
+configuration.txt \
+fastcgi-state.txt \
+fastcgi.txt \
+features.txt \
+performance.txt \
+plugins.txt \
+proxy.txt \
+redirect.txt \
+rewrite.txt \
+secdownload.txt \
+security.txt \
+simple-vhost.txt \
+skeleton.txt \
+ssi.txt \
+ssl.txt \
+state.txt \
+rrdtool.txt \
+alias.txt \
+userdir.txt \
+mysqlvhost.txt \
+access.txt \
+traffic-shaping.txt \
+setenv.txt \
+status.txt
+
+HTMLDOCS=accesslog.html \
+ authentification.html \
+ cgi.html \
+ compress.html \
+ configuration.html \
+ fastcgi-state.html \
+ fastcgi.html \
+ features.html \
+ performance.html \
+ plugins.html \
+ proxy.html \
+ redirect.html \
+ rewrite.html \
+ secdownload.html \
+ security.html \
+ simple-vhost.html \
+ skeleton.html \
+ ssi.html \
+ ssl.html \
+ state.html \
+ rrdtool.html \
+ alias.html \
+ userdir.html \
+ mysqlvhost.html \
+ access.html \
+ traffic-shaping.html \
+ setenv.html \
+ status.html
+
+EXTRA_DIST=lighttpd.conf lighttpd.user \
+ rc.lighttpd rc.lighttpd.redhat sysconfig.lighttpd \
+ state.ps.gz rrdtool-graph.sh \
+ state.dot fastcgi-state.dot fastcgi-state.ps.gz \
+ spawn-php.sh \
+ $(DOCS)
+
+%.html: %.txt
+ rst2html.py $^ > $@
+
+
+html: $(HTMLDOCS)
+
+%.ps.gz: %.ps
+ gzip $^
+
+%.ps: %.dot
+ dot -Tps -o $@ $^
+
+clean-local:
+ rm -f *.html
+
+
diff --git a/doc/access.txt b/doc/access.txt
new file mode 100644
index 00000000..acfdcb72
--- /dev/null
+++ b/doc/access.txt
@@ -0,0 +1,41 @@
+======
+Access
+======
+
+------------------
+Module: mod_access
+------------------
+
+:Author: Allan Wind
+:Date: $Date: 2005/01/30 11:34:32 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ The access module is used to deny access to files with given trailing path names.
+
+.. meta::
+ :keywords: lighttpd, trailing path access control
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+The access module is used to deny access to files with given trailing path names.
+
+Options
+=======
+
+url.access-deny
+ Denies access to all files with any of given trailing path names.
+
+ Default: empty
+
+ Example: ::
+
+ url.access-deny = ( "~", ".inc")
+
+ will deny access to all files ended with a diacritical mark (~) or .inc
+ such as example~ or example.inc. A trailing diacritical mark is often
+ used by editors for backup files. And the .inc extension is often used
+ for include files with code.
diff --git a/doc/accesslog.txt b/doc/accesslog.txt
new file mode 100644
index 00000000..453b3ea8
--- /dev/null
+++ b/doc/accesslog.txt
@@ -0,0 +1,126 @@
+=========
+Accesslog
+=========
+
+---------------------
+Module: mod_accesslog
+---------------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/11/03 22:26:05 $
+:Revision: $Revision: 1.2 $
+
+:abstract:
+ The accesslog module ...
+
+.. meta::
+ :keywords: lighttpd, accesslog, CLF
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+CLF like by default, flexible like apache
+
+Options
+=======
+
+accesslog.use-syslog
+ send the accesslog to syslog
+
+ Default: disabled
+
+accesslog.filename
+ name of the file where the accesslog should be written too if syslog
+ is not used.
+
+ if the name starts with a '|' the rest of the name is taken
+ as the name of a process which will be spawn and will get the
+ output
+
+ e.g.: ::
+
+ accesslog.filename = "/var/log/lighttpd.log"
+
+ $HTTP["host"] == "mail.example.org" {
+ accesslog.filename = "|/usr/bin/cronolog"
+ }
+
+ Default: disabled
+
+accesslog.format
+ the format of the logfile
+
+ ====== ================================
+ Option Description
+ ====== ================================
+ %% a percent sign
+ %h name or address of remote-host
+ %l ident name (not supported)
+ %u authenticated user
+ %t timestamp for the request-start
+ %r request-line
+ %s status code
+ %b bytes sent for the body
+ %i HTTP-header field
+ %a remote address
+ %A local address
+ %B same as %b
+ %C cookie field (not supported)
+ %D time used in ms (not supported)
+ %e environment (not supported)
+ %f phyiscal filename
+ %H request protocol (HTTP/1.0, ...)
+ %m request method (GET, POST, ...)
+ %n (not supported)
+ %o `response header`_
+ %p server port
+ %P (not supported)
+ %q query string
+ %T time used in seconds
+ %U request URL
+ %v server-name
+ %V (not supported)
+ %X connection status
+ %I bytes incomming
+ %O bytes outgoing
+ ====== ================================
+
+ If %s is written %>s or %<s the < and the > are ignored. They are support
+ for compat with apache.
+
+ %i and %o expect the name of the field which should be written in curly brackets.
+
+ e.g.: ::
+
+ accesslog.format = "%h %l %u %t \"%r\" %b %>s \"%{User-Agent}i\" \"%{Referer}i\""
+
+ Default: CLF compatible output
+
+response header
+---------------
+
+The accesslog module provides a special way to log content from the
+application in a accesslog file. It can be used to log the session id into a
+logfile.
+
+If you want to log it into the accesslog just specify the field-name within
+a %{...}o like ::
+
+ accesslog.format = "%h %l %u %t \"%r\" %b %>s \"%{User-Agent}i\" \"%{Referer}i\" \"%{X-LIGHTTPD-SID}o\""
+
+The prefix ``X-LIGHTTPD-`` is special as every response header starting with
+this prefix is assumed to be special for lighttpd and won't be sent out
+to the client.
+
+An example the use this functionality is provided below: ::
+
+ <?php
+
+ session_start();
+
+ header("X-LIGHTTPD-SID: ".session_id());
+
+ ?>
+
diff --git a/doc/alias.txt b/doc/alias.txt
new file mode 100644
index 00000000..15012e31
--- /dev/null
+++ b/doc/alias.txt
@@ -0,0 +1,36 @@
+=====
+Alias
+=====
+
+-----------------
+Module: mod_alias
+-----------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/29 09:43:49 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ The alias module ...
+
+.. meta::
+ :keywords: lighttpd, alias
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+The alias module is used to specify a special document-root for a given url-subset.
+
+Options
+=======
+
+alias.url
+ rewrites the document-root for a URL-subset
+
+ Default: empty
+
+ Example: ::
+
+ alias.url = ( "/cgi-bin/" => "/var/www/servers/www.example.org/cgi-bin/" )
diff --git a/doc/authentification.txt b/doc/authentification.txt
new file mode 100644
index 00000000..2528589f
--- /dev/null
+++ b/doc/authentification.txt
@@ -0,0 +1,191 @@
+======================
+Using Authentification
+======================
+
+----------------
+Module: mod_auth
+----------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/11/03 22:26:05 $
+:Revision: $Revision: 1.3 $
+
+:abstract:
+ The auth module provides ...
+
+.. meta::
+ :keywords: lighttpd, authentification
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+Supported Methods
+-----------------
+
+lighttpd supportes both authentification method described by
+RFC 2617:
+
+basic
+`````
+
+The Basic method transfers the username and the password in
+cleartext over the network (base64 encoded) and might result
+in security problems if not used in conjunction with a crypted
+channel between client and server.
+
+digest
+``````
+
+The Digest method only transfers a hashed value over the
+network which is performes a lot of work to harden the
+authentification process in insecure networks.
+
+Backends
+--------
+
+Depending on the method lighttpd provides various way to store
+the credentials used for the authentification.
+
+for basic auth:
+
+- plain_
+- htpasswd_ (crypt only)
+- htdigest_
+- ldap_
+
+for digest auth:
+
+- plain_
+- htdigest_
+
+
+plain
+`````
+
+A file which contains username and the cleartext password
+seperated by a colon. Each entry is terminated by a single
+newline.::
+
+ e.g.:
+ agent007:secret
+
+
+htpasswd
+````````
+
+A file which contains username and the crypt()'ed password
+seperated by a colon. Each entry is terminated by a single
+newline. ::
+
+ e.g.:
+ agent007:XWY5JwrAVBXsQ
+
+You can use htpasswd from the apache distribution to manage
+those files. ::
+
+ $ htpasswd lighttpd.user.digest agent007
+
+
+htdigest
+````````
+
+A file which contains username, realm and the md5()'ed
+password seperated by a colon. Each entry is terminated
+by a single newline. ::
+
+ e.g.:
+ agent007:download area:8364d0044ef57b3defcfa141e8f77b65
+
+You can use htdigest from the apache distribution to manage
+those files. ::
+
+ $ htdigest src/lighttpd.user.digest 'download area' agent007
+
+Using md5sum can also generate the password-hash: ::
+
+ $ echo -n "agent007:download area:secret" | md5sum -
+ 8364d0044ef57b3defcfa141e8f77b65 -
+
+
+ldap
+````
+
+the ldap backend is basicly performing the following steps
+to authenticate a user
+
+1. connect anonymously (at plugin init)
+2. get DN for filter = username
+3. auth against ldap server
+4. disconnect
+
+if step 4 is performs without any error the user is
+authenticated
+
+Configuration
+=============
+
+::
+
+ ## debugging
+ # 0 for off, 1 for 'auth-ok' messages, 2 for verbose debugging
+ auth.debug = 0
+
+ ## type of backend
+ # plain, htpasswd, ldap or htdigest
+ auth.backend = "htpasswd"
+
+ # filename of the password storage for
+ # plain
+ auth.backend.plain.userfile = "lighttpd-plain.user"
+
+ ## for htpasswd
+ auth.backend.htpasswd.userfile = "lighttpd-htpasswd.user"
+
+ ## for htdigest
+ auth.backend.htdigest.userfile = "lighttpd-htdigest.user"
+
+ ## for ldap
+ # the $ in auth.backend.ldap.filter is replaced by the
+ # 'username' from the login dialog
+ auth.backend.ldap.hostname = "localhost"
+ auth.backend.ldap.base-dn = "dc=my-domain,dc=com"
+ auth.backend.ldap.filter = "(uid=$)"
+
+ ## restrictions
+ # set restrictions:
+ #
+ # ( <left-part-of-the-url> =>
+ # ( "method" => "digest"/"basic",
+ # "realm" => <realm>,
+ # "require" => "user=<username>" )
+ # )
+ #
+ # <realm> is a string that is should be display in the dialog
+ # presented to the user and is also used for the
+ # digest-algorithm and has to match the realm in the
+ # htdigest file (if used)
+ #
+
+ auth.require = ( "/download/" =>
+ (
+ "method" => "digest",
+ "realm" => "download archiv",
+ "require" => "user=agent007|user=agent008"
+ ),
+ "/server-info" =>
+ (
+ "method" => "digest",
+ "realm" => "download archiv",
+ "require" => "user=jan"
+ )
+ )
+
+Limitiations
+============
+
+- The implementation of digest method is currently not
+ completely conforming to the standard as it is still allowing
+ a replay attack.
+
diff --git a/doc/cgi.txt b/doc/cgi.txt
new file mode 100644
index 00000000..6642feaf
--- /dev/null
+++ b/doc/cgi.txt
@@ -0,0 +1,50 @@
+===
+CGI
+===
+
+---------------
+Module: mod_cgi
+---------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/11/03 22:26:05 $
+:Revision: $Revision: 1.2 $
+
+:abstract:
+ cgi module provide a CGI conforming interface
+
+.. meta::
+ :keywords: lighttpd, cgi
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+CGI programs allow you to enhance the functionality of the server in a very
+straight and simple wait. ...
+
+Options
+=======
+
+cgi.assign
+
+ file-extensions that are handled by a CGI program
+
+ e.g.: ::
+
+ cgi.assign = ( ".pl" => "/usr/bin/perl",
+ ".cgi" => "/usr/bin/perl" )
+
+Examples
+========
+
+To setup a executable which doesn't need the help of a external program you
+just don't specify a handler for the extension. ::
+
+ cgi.assign = ( ".sh" => "" )
+
+If the file has no extension keep in mind that lighttpd matches not the
+extension itself but the right part of the URL: ::
+
+ cgi.assign = ( "/testfile" => "" )
diff --git a/doc/compress.txt b/doc/compress.txt
new file mode 100644
index 00000000..7b083e94
--- /dev/null
+++ b/doc/compress.txt
@@ -0,0 +1,66 @@
+==================
+Output Compression
+==================
+
+--------------------
+Module: mod_compress
+--------------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/29 09:43:49 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ a nice, short abstrace about the module
+
+.. meta::
+ :keywords: lighttpd, compress
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+Output compression reduces the network load and can improve the overall
+throughput of the webserver.
+
+Only static content is supported up to now.
+
+The server negotiates automaticly which compression method is used.
+Supported are gzip, deflate, bzip.
+
+Options
+=======
+
+compress.cache-dir
+ name of the directory where compressed content will be cached
+
+ e.g.: ::
+
+ compress.cache-dir = "/var/www/cache/"
+
+ # even better with virt-hosting
+ $HTTP["host"] == "docs.example.org" {
+ compress.cache-dir = "/var/www/cache/docs.example.org/"
+ }
+
+ Default: not set, compress the file for every request
+
+compress.filetype
+ mimetypes where might get compressed
+
+ e.g.: ::
+
+ compress.filetype = ("text/plain", "text/html")
+
+ Default: not set
+
+
+Compressing Dynamic Content
+===========================
+
+To compress dynamic content with PHP please enable ::
+
+ zlib.output_compression = 1
+
+in the php.ini as PHP provides compression support by itself.
diff --git a/doc/configuration.txt b/doc/configuration.txt
new file mode 100644
index 00000000..b3a7a61e
--- /dev/null
+++ b/doc/configuration.txt
@@ -0,0 +1,351 @@
+=================
+Configuation File
+=================
+
+------------
+Module: core
+------------
+
+:Author: Jan Kneschke
+:Date: $Date$
+:Revision: $Revision$
+
+:abstract:
+ the layout of the configuration file
+
+.. meta::
+ :keywords: lighttpd, configuration
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+Basic Syntax
+------------
+
+A BNF like notation: ::
+
+ option : NAME = VARIABLE
+ NAME : modulename.key
+ VARIABLE : ( <string> | <integer> | <boolean> | <array> )
+ <string> : "text"
+ <integer>: digit*
+ <boolean>: ( "enable" | "disable" )
+ <array> : "(" [ <string> "=>" ] <variable> [, [ <string> "=>" ] <variable> ]* ")"
+
+Example
+-------
+
+::
+
+ # default document-root
+ server.document-root = "/var/www/example.org/pages/"
+
+ # TCP port
+ server.port = 80
+
+ # selecting modules
+ server.modules = ( "mod_access", "mod_rewrite" )
+
+ # enable directory listings
+ server.dir-listing = "enable"
+
+
+
+Conditional Configuration
+=========================
+
+Most options can be configured conditionally by using the following syntax
+(including nesting).
+
+::
+
+ <field> <operator> <value> {
+ ...
+ }
+
+where <field> is one of one of the following:
+
+$HTTP["cookie"]
+ match on cookie
+$HTTP["host"]
+ match on host
+$HTTP["useragent"]
+ match on useragent
+$HTTP["referer"]
+ match on referer
+$HTTP["url"]
+ match on url
+$SERVER["socket"]
+ match on socket. Value must be on the format "$ip:$port" where $ip is an IP
+ address and $port a port number. Only equal match (==) is supported.
+
+<operator> is one of:
+
+==
+ string equal match
+!=
+ string not equal match
+=~
+ perl style regular expression match
+!~
+ perl style regular expression not match
+
+and <value> is either a quoted ("") literal string or regular expression.
+
+
+Example
+-------
+
+::
+
+ # disable directory-listings for /download/*
+ server.dir-listing = "enable"
+ $HTTP["url"] =~ "^/download/" {
+ server.dir-listing = "disable"
+ }
+
+ # handish virtual hosting
+ # map all subdomains to a single document-root
+ $HTTP["host"] =~ "\.example\.org$" {
+ server.document-root = "/var/www/htdocs/example.org/pages/"
+ }
+
+ # multiple sockets
+ $SERVER["socket"] == "127.0.0.1:81" {
+ server.document-root = "..."
+ }
+
+ $SERVER["socket"] == "127.0.0.1:443" {
+ ssl.pemfile = "/var/www/certs/localhost.pem"
+ ssl.engine = "enable"
+
+ server.document-root = "/var/www/htdocs/secure.example.org/pages/"
+ }
+
+ # deny access for all googlebot
+ $HTTP["useragent"] =~ "Google" {
+ url.access-deny = ( "" )
+ }
+
+ # deny access for all image stealers
+ $HTTP["referer"] !~ "^($|http://www\.example\.org)" {
+ url.access-deny = ( ".jpg", ".jpeg", ".png" )
+ }
+
+Options
+=======
+
+server module
+-------------
+
+main sections
+`````````````
+
+server.document-root
+ document-root of the webserver
+
+server.bind
+ hostname of the server
+
+server.port
+ tcp-port to bind the server to
+ if nothing is specified port 80 is used
+ NOTE: port belows 1024 require root-permissions
+
+server.use-ipv6
+ bind to the IPv6 socket
+
+server.errorlog
+ pathname of the error-log
+ if nothing is specified STDERR is used
+
+server.chroot
+ root-directory of the server
+
+server.username
+ username used to run the server
+ NOTE: requires root-permissions
+
+server.groupname
+ groupname used to run the server
+ NOTE: requires root-permissions
+
+server.dir-listing
+ enables virtual directory listings if a directory is requested no
+ index-file was found
+
+server.follow-symlink
+ allow to follow-symlinks
+
+ default: enabled
+
+server.indexfiles
+ list of files to search for if a directory is requested
+ e.g.: ::
+
+ server.indexfiles = ( "index.php", "index.html",
+ "index.htm", "default.htm" )
+
+server.modules
+ modules to load
+
+..note: the order of the modules is somewhat important as the modules are
+ handled in the way they are specified. mod_rewrite should always be
+ the first module, mod_accesslog always the last.
+
+ e.g.: ::
+
+ server.modules = ( "mod_rewrite",
+ "mod_redirect",
+ "mod_alias",
+ "mod_access",
+ "mod_auth",
+ "mod_status",
+ "mod_fastcgi",
+ "mod_proxy",
+ "mod_simple_vhost",
+ "mod_evhost",
+ "mod_userdir",
+ "mod_cgi",
+ "mod_compress",
+ "mod_ssi",
+ "mod_usertrack",
+ "mod_expire",
+ "mod_secdownload",
+ "mod_rrdtool",
+ "mod_accesslog" )
+
+server.event-handler
+ set the event handler
+
+ Default: "poll"
+
+server.pid-file
+ set the name of the .pid-file where the PID of the server should be placed.
+ This option is used in combination with a start-script and the deamon mode
+
+ Default: not set
+
+server.max-request-size
+ maximum size in kbytes of the request (header + body)
+
+ Default: 2Gb
+
+server.max-worker
+ number of worker processes to spawn (works but has no benefit)
+
+ Default: 0
+
+server.name
+ name of the server/virtual server
+
+ Default: hostname
+
+server.max-keep-alive-requests
+ maximum number of request within a keep-alive session before the server
+ terminates the connection
+
+ Default: 128
+
+server.max-keep-alive-idle
+ maximum number of seconds until a idling keep-alive connection is droped
+
+ Default: 30
+
+server.max-read-idle
+ maximum number of seconds until a waiting, non keep-alive read times out
+ and closes the connection
+
+ Default: 60
+
+server.max-write-idle
+ maximum number of seconds until a waiting write call times out and closes
+ the connection
+
+ Default: 360
+
+server.error-handler-404
+ uri to call if the requested file results in a 404
+
+ Default: not set
+
+ Example: ::
+
+ server.error-handler-404 = "/error-404.php"
+
+server.allow_http11
+ defines if HTTP/1.1 is allowed or not.
+
+ Default: enabled
+
+SSL engine
+``````````
+
+ssl.pemfile
+ path to the PEM file for SSL support
+
+debugging
+`````````
+
+debug.dump-unknown-headers
+ enables listing of internally unhandled HTTP-headers
+
+ e.g. ::
+
+ debug.dump-unknown-headers = "enable"
+
+mimetypes
+`````````
+
+mimetype.assign
+ list of known mimetype mappings
+ NOTE: if no mapping is given "application/octet-stream" is used
+
+ e.g.: ::
+
+ mimetype.assign = ( ".png" => "image/png",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".html" => "text/html",
+ ".txt" => "text/plain" )
+
+
+mimetype.use-xattr
+ If available, use the XFS-style extended attribute interface to
+ retrieve the "Content-Type" attribute on each file, and use that as the
+ mime type. If it's not defined or not available, fall back to the
+ mimetype.assign assignment.
+
+ e.g.: ::
+
+ mimetype.use-xattr = "enable"
+
+ on shell use:
+
+ $ attr -s Content-Type -V image/svg svgfile.svg
+
+ or
+
+ $ attr -s Content-Type -V text/html indexfile
+
+
+debugging
+`````````
+
+debug.log-request-header
+
+ default: disabled
+
+debug.log-response-header
+
+ default: disabled
+
+debug.log-file-not-found
+
+ default: disabled
+
+debug.log-request-handler
+
+ default: disabled
diff --git a/doc/fastcgi-state.dot b/doc/fastcgi-state.dot
new file mode 100644
index 00000000..8e3068cd
--- /dev/null
+++ b/doc/fastcgi-state.dot
@@ -0,0 +1,6 @@
+digraph fcgistate {
+ init -> connect -> prepwrite -> write -> read -> close
+ write -> write
+ read -> read
+ connect -> connect
+}
diff --git a/doc/fastcgi-state.txt b/doc/fastcgi-state.txt
new file mode 100644
index 00000000..9e76a6f6
--- /dev/null
+++ b/doc/fastcgi-state.txt
@@ -0,0 +1,51 @@
+=================
+FastCGI Internals
+=================
+
+---------------
+Module: fastcgi
+---------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/01 07:01:29 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ This is a short summary of the state-engine which is driving the FastCGI
+ module. It describes the basic concepts and the way the different parts
+ of the module are connected.
+
+.. meta::
+ :keywords: lighttpd, state-engine, fastcgi
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+States
+------
+
+The state-engine is currently made of 6 states which are walk-through on
+the way each connection.
+
+:init:
+ prepare fastcgi-connection
+:connect:
+ waiting for a connection
+:prepwrite:
+ build the fastcgi-request
+:write:
+ write the fastcgi-request to the network
+:read:
+ read fastcgi-response from network and push it to the write-queue
+:close:
+ terminate the connection
+
+.. image:: fastcgi-state.png
+
+Delays
+------
+
+connect, write and read may need to wait for an fdevent. That's the reason
+for the loop in the state-diagram.
diff --git a/doc/fastcgi.txt b/doc/fastcgi.txt
new file mode 100644
index 00000000..7f2422cf
--- /dev/null
+++ b/doc/fastcgi.txt
@@ -0,0 +1,600 @@
+=====================
+the FastCGI Interface
+=====================
+
+-------------------
+Module: mod_fastcgi
+-------------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/11/03 22:26:05 $
+:Revision: $Revision: 1.3 $
+
+:abstract:
+ The FastCGI interface is the fastest and most secure way
+ to interface external process-handlers like Perl, PHP and
+ you self-written applications.
+
+.. meta::
+ :keywords: lighttpd, FastCGI
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+lighttpd provides an interface to a external programs that
+support the FastCGI interface. The FastCGI Interface is
+defined by http://www.fastcgi.com/ and is a
+platform-indepentant and server independant interface between
+a web-application and a webserver.
+
+This means that FastCGI programs that run with the Apache
+Webserver will run seamlessly with lighttpd and vice versa.
+
+
+FastCGI
+-------
+
+FastCGI is removes a lot of the limitations of CGI programs.
+CGI programs have the problem that they have to be restarted
+by the webserver for every request which leads to really bad
+performance values.
+
+FastCGI removes this limitation by keeping the process running
+and handling the requests by this always running process. This
+removes the time used for the fork() and the overall startup
+and cleanup time which is necessary to create and destroy a
+process.
+
+While CGI programs communicate to the server over pipes,
+FastCGI processes use Unix-Domain-Sockets or TCP/IP to talk
+with the webserver. This gives you the second advantage over
+simple CGI programs: FastCGI don't have to run on the Webserver
+itself but everywhere in the network.
+
+lighttpd takes it a little bit further by providing a internal
+FastCGI load-balancer which can be used to balance the load
+over multiple FastCGI Servers. In contrast to other solutions
+only the FastCGI process has to be on the cluster and not the
+whole webserver. That gives the FastCGI process more resources
+than a e.g. load-balancer+apache+mod_php solution.
+
+If you compare FastCGI against a apache+mod_php solution you
+should note that FastCGI provides additional security as the
+FastCGI process can be run under different permissions that
+the webserver and can also live a chroot which might be
+different than the one the webserver is running in.
+
+Options
+=======
+
+lighttpd provides the FastCGI support via the fastcgi-module
+(mod_fastcgi) which provides 2 options in the config-file:
+
+fastcgi.debug
+ a value between 0 and 65535 to set the debug-level in the
+ FastCGI module. Currently only 0 and 1 are used. Use 1 to
+ enable some debug output, 0 to disable it.
+
+fastcgi.server
+ tell the module where to send FastCGI requests to. Every
+ file-extension can have it own handler. Load-Balancing is
+ done by specifying multiple handles for the same extension.
+
+ structure of fastcgi.server section: ::
+
+ ( <extension> =>
+ ( <handle> =>
+ ( "host" => <string> ,
+ "port" => <integer> ,
+ "socket" => <string>, # either socket
+ # or host+port
+ "bin-path" => <string>, # OPTIONAL
+ "bin-environment" => <array>, # OPTIONAL
+ "bin-copy-environment" => <array>, # OPTIONAL
+ "mode" => <string>, # OPTIONAL
+ "docroot" => <string> , # OPTIONAL if "mode"
+ # is not "authorizer"
+ "check-local" => <string>, # OPTIONAL
+ "min-procs" => <integer>, # OPTIONAL
+ "max-procs" => <integer>, # OPTIONAL
+ "max-load-per-proc" => <integer>, # OPTIONAL
+ "idle-timeout" => <integer> # OPTIONAL
+ )
+ ),
+ ( <handle> => ...
+ )
+ )
+
+ :<extension>: is the file-extension or prefix
+ (if started with "/")
+ :<handle>: is just a unique handle name
+ :"host": is hostname/ip of the FastCGI process
+ :"port": is tcp-port on the "host" used by the FastCGI
+ process
+ :"bin-path": path to the local FastCGI binary which should be
+ started if no local FastCGI is running
+ :"socket": path to the unix-domain socket
+ :"mode": is the FastCGI protocol mode.
+ Default is "responder", also "authorizer"
+ mode is implemented.
+ :"docroot": is optional and is the docroot on the remote
+ host for default "responder" mode. For
+ "authorizer" mode it is MANDATORY and it points
+ to docroot for authorized requests. For security
+ reasons it is recommended to keep this docroot
+ outside of server.document-root tree.
+ :"check-local": is optional and may be "enable" (default) or
+ "disable". If enabled the server first check
+ for a file in local server.document-root tree
+ and return 404 (Not Found) if no such file.
+ If disabled, the server forward request to
+ FastCGI interface without this check.
+
+ If bin-path is set:
+
+ :"min-procs": sets the minium processes to start
+ :"max-procs": the upper limit of the processess to start
+ :"max-load-per-proc": maximum number of waiting processes on
+ average per process before a new process is
+ spawned
+ :"idle-timeout": number of seconds before a unused process
+ gets terminated
+ :"bin-environment": put an entry into the environment of
+ the started process
+ :"bin-copy-environement": clean up the environment and copy
+ only the specified entries into the fresh
+ environment of the spawn process
+
+
+Examples
+--------
+
+ Multiple extensions for the same host ::
+
+ fastcgi.server = ( ".php" =>
+ ( "grisu" =>
+ (
+ "host" => "127.0.0.1",
+ "port" => 1026,
+ "bin-path" => "/usr/local/bin/php"
+ )
+ ),
+ ".php4" =>
+ ( "grisu" =>
+ (
+ "host" => "127.0.0.1",
+ "port" => 1026
+ )
+ )
+ )
+
+ Example with prefix: ::
+
+ fastcgi.server = ( "/remote_scripts" =>
+ ( "fcg" =>
+ (
+ "host" => "192.168.0.3",
+ "port" => 9000,
+ "check-local" => "disable",
+ "docroot" => "/" # remote server may use
+ # it's own docroot
+ )
+ )
+ )
+
+ The request `http://my.host.com/remote_scripts/test.cgi` will
+ be forwarded to fastcgi server at 192.168.0.3 and the value
+ "/remote_scripts/test.cgi" will be used for the SCRIPT_NAME
+ variable. Remote server may prepend it with its own
+ document root. The handling of index files si also the
+ resposibility of remote server for this case.
+
+
+ Example for "authorizer" mode: ::
+
+ fastcgi.server = ( "/remote_scripts" =>
+ ( "auth" =>
+ (
+ "host" => "10.0.0.2",
+ "port" => 9000,
+ "docroot" => "/path_to_private_docs",
+ "mode" => "authorizer"
+ )
+ )
+ )
+
+ Note that if "docroot" is specified then its value will be
+ used in DOCUMENT_ROOT and SCRIPT_FILENAME variables passed
+ to FastCGI server.
+
+Load-Balancing
+==============
+
+The FastCGI plugin provides automaticly a load-balancing between
+multiple FastCGI servers. ::
+
+ fastcgi.server = ( ".php" =>
+ ( "server1" =>
+ ( "host" => "10.0.0.3",
+ "port" => 1030 ),
+ "server2" =>
+ ( "host" => "10.0.0.3",
+ "port" => 1030 )
+ )
+ )
+
+
+
+
+To understand how the load-balancing works you can enable the
+fastcgi.debug option and will get a similar output as here: ::
+
+ proc: 127.0.0.1 1031 1 1 1 31454
+ proc: 127.0.0.1 1028 1 1 1 31442
+ proc: 127.0.0.1 1030 1 1 1 31449
+ proc: 127.0.0.1 1029 1 1 2 31447
+ proc: 127.0.0.1 1026 1 1 2 31438
+ got proc: 34 31454
+ release proc: 40 31438
+ proc: 127.0.0.1 1026 1 1 1 31438
+ proc: 127.0.0.1 1028 1 1 1 31442
+ proc: 127.0.0.1 1030 1 1 1 31449
+ proc: 127.0.0.1 1031 1 1 2 31454
+ proc: 127.0.0.1 1029 1 1 2 31447
+
+Even if this for multiple FastCGI children on the local machine
+the following explaination is valid for remote connections too.
+
+The output shows:
+
+- IP, port, unix-socket (is empty here)
+- is-local, state (0 - unset, 1 - running, ... )
+- active connections (load)
+- PID
+
+As you can see the list is always sorted by the load field.
+
+When ever a new connection is requested, the first entry (the one
+with the lowest load) is selected, the load is increase (got proc: ...)
+and the list is sorted again.
+
+If a FastCGI request is done or the connection is drop, the load on the
+FastCGI proc decreases and the list is sorted again (release proc: ...)
+
+This behaviour is very light-weight in code and still very efficient
+as it keeps the fastcgi-servers equally loaded even if they have different
+CPUs.
+
+Adaptive Process Spawning
+=========================
+
+Starting with 1.3.8 lighttpd can spawn processes on demand if
+a bin-path is specified and the FastCGI process runs locally.
+
+If you want to have a least one FastCGI process running and
+more of the number of requests increases you can use min-procs
+and max-procs.
+
+A new process is spawned as soon as the average number of
+requests waiting to be handle by a single process increases the
+max-load-per-proc setting.
+
+The idle-timeout specifies how long a fastcgi-process should wait
+for a new request before it kills itself.
+
+Example
+-------
+::
+
+ fastcgi.server = ( ".php" => ( "localhost" =>
+ ( "socket" => "/tmp/php.socket",
+ "bin-path" => "/usr/local/bin/php",
+ "min-procs" => 1,
+ "max-procs" => 32,
+ "max-load-per-proc" => 4,
+ "idle-timeout" => 20 )
+ ) )
+
+Disabling Adaptive Spawning
+---------------------------
+
+Adaptive Spawning is a quite new feature and it might misbehave
+for your setup. There are several ways to control how the spawing
+is done:
+
+1. ``"max-load-per-proc" => 1``
+ if that works for you, great.
+
+2. If not set ``min-procs == max-procs``.
+
+3. For PHP you can also use: ::
+
+ $ PHP_FCGI_CHILDREN=384 ./lighttpd -f ./lighttpd.conf
+
+ fastcgi.server = ( ".php" => ( "localhost" =>
+ ( "socket" => "/tmp/php.socket",
+ "bin-path" => "/usr/local/bin/php",
+ "min-procs" => 1,
+ "max-procs" => 1,
+ "max-load-per-proc" => 4,
+ "idle-timeout" => 20 )
+ ) )
+
+ It will create one socket and let's PHP create the 384 processes itself.
+
+4. If you don't want lighttpd to manage the fastcgi processes, remove the
+ bin-path and use spawn-fcgi to spawn them itself.
+
+
+FastCGI and Programming Languages
+=================================
+
+Preparing PHP as a FastCGI program
+----------------------------------
+
+One of the most important application that has a FastCGI
+interface is php which can be downloaded from
+http://www.php.net/ . You have to recompile the php from
+source to enable the FastCGI interface as it is normally
+not enabled by default in the distributions.
+
+If you already have a working installation of PHP on a
+webserver execute a small script which just contains ::
+
+ <?php phpinfo(); ?>
+
+and search for the line in that contains the configure call.
+You can use it as the base for the compilation.
+
+You have to remove all occurences of `--with-apxs`, `--with-apxs2`
+and the like which would build PHP with Apache support. Add the
+next three switches to compile PHP with FastCGI support::
+
+ $ ./configure \
+ --enable-fastcgi \
+ --enable-discard-path \
+ --enable-force-cgi-redirect \
+ ...
+
+After compilation and installation check that your PHP
+binary contains FastCGI support by calling: ::
+
+ $ php -v
+ PHP 4.3.3RC2-dev (cgi-fcgi) (built: Oct 19 2003 23:19:17)
+
+The important part is the (cgi-fcgi).
+
+
+Configuring PHP
+---------------
+
+It is important that the php.ini contains: ::
+
+ cgi.fix_pathinfo = 1
+
+Otherwise PHP_SELF won't be set correctly.
+
+Starting a FastCGI-PHP
+----------------------
+
+Starting with version 1.3.6 lighttpd can spawn the FastCGI
+processes locally itself if necessary: ::
+
+ fastcgi.server = ( ".php" =>
+ ( "localhost" =>
+ ( "socket" => "/tmp/php-fastcgi.socket",
+ "bin-path" => "/usr/local/bin/php"
+ )
+ )
+ )
+
+PHP provides 2 special environment variables which control the number of
+spawned workes under the control of a single watching process
+(PHP_FCGI_CHILDREN) and the number of requests what a single worker
+handles before it kills itself. ::
+
+ fastcgi.server = ( ".php" =>
+ ( "localhost" =>
+ ( "socket" => "/tmp/php-fastcgi.socket",
+ "bin-path" => "/usr/local/bin/php",
+ "bin-environment" => (
+ "PHP_FCGI_CHILDREN" => "16",
+ "PHP_FCGI_MAX_REQUESTS" => "10000"
+ )
+ )
+ )
+ )
+
+To increase the security of the started process you should only pass
+the necessary environment variables to the FastCGI process. ::
+
+ fastcgi.server = ( ".php" =>
+ ( "localhost" =>
+ ( "socket" => "/tmp/php-fastcgi.socket",
+ "bin-path" => "/usr/local/bin/php",
+ "bin-environment" => (
+ "PHP_FCGI_CHILDREN" => "16",
+ "PHP_FCGI_MAX_REQUESTS" => "10000"
+ ),
+ "bin-copy-environment" => (
+ "PATH", "SHELL", "USER"
+ )
+ )
+ )
+ )
+
+
+External Spawning
+-----------------
+
+Spawning FastCGI processes directly in the webserver has some
+disadvantages like
+
+- FastCGI process can only run locally
+- has the same permissions as the webserver
+- has the same base-dir as the webserver
+
+As soon as you are using a seperate FastCGI Server to
+take off some load from the webserver you have to control
+the FastCGI process by a external program like spawn-fcgi.
+
+spawn-fcgi is used to start a FastCGI process in its own
+environment and set the user-id, group-id and change to
+another root-directory (chroot).
+
+For convenience a wrapper script should be used which takes
+care of all the necessary option. Such a script in included
+in the lighttpd distribution and is call spawn-php.sh.
+
+The script has a set of config variables you should take
+a look at: ::
+
+ ## ABSOLUTE path to the spawn-fcgi binary
+ SPAWNFCGI="/usr/local/sbin/spawn-fcgi"
+
+ ## ABSOLUTE path to the PHP binary
+ FCGIPROGRAM="/usr/local/bin/php"
+
+ ## bind to tcp-port on localhost
+ FCGIPORT="1026"
+
+ ## bind to unix domain socket
+ # FCGISOCKET="/tmp/php.sock"
+
+ ## number of PHP childs to spawn
+ PHP_FCGI_CHILDREN=10
+
+ ## number of request server by a single php-process until
+ ## is will be restarted
+ PHP_FCGI_MAX_REQUESTS=1000
+
+ ## IP adresses where PHP should access server connections
+ ## from
+ FCGI_WEB_SERVER_ADDRS="127.0.0.1,192.168.0.1"
+
+ # allowed environment variables sperated by spaces
+ ALLOWED_ENV="ORACLE_HOME PATH USER"
+
+ ## if this script is run as root switch to the following user
+ USERID=wwwrun
+ GROUPID=wwwrun
+
+If you have set the variables to values that fit to your
+setup you can start it by calling: ::
+
+ $ spawn-php.sh
+ spawn-fcgi.c.136: child spawned successfully: PID: 6925
+
+If you get "child spawned successfully: PID:" the php
+processes could be started successfully. You should see them
+in your processlist: ::
+
+ $ ps ax | grep php
+ 6925 ? S 0:00 /usr/local/bin/php
+ 6928 ? S 0:00 /usr/local/bin/php
+ ...
+
+The number of processes should be PHP_FCGI_CHILDREN + 1.
+Here the process 6925 is the master of the slaves which
+handle the work in parallel. Number of parallel workers can
+be set by PHP_FCGI_CHILDREN. A worker dies automaticly of
+handling PHP_FCGI_MAX_REQUESTS requests as PHP might have
+memory leaks.
+
+If you start the script as user root php processes will be
+running as the user USERID and group GROUPID to drop the
+root permissions. Otherwise the php processes will run as
+the user you started script as.
+
+As the script might be started from a unknown stage or even
+directly from the command-line it cleans the environment
+before starting the processes. ALLOWED_ENV contains all
+the external environement variables that should be available
+to the php-process.
+
+
+
+
+Perl
+----
+
+For Perl you have to install the FCGI module from CPAN.
+
+TCL
+---
+
+For TCL ...
+
+
+
+Skeleton for remote authorizer
+==============================
+
+The basic functionality of authorizer is as follows (see
+http://www.fastcgi.com/devkit/doc/fcgi-spec.html, 6.3 for
+details). ::
+
+ #include <fcgi_stdio.h>
+ #include <stdlib.h>
+ #include <unistd.h>
+ int main () {
+ char* p;
+
+ while (FCGI_Accept() >= 0) {
+ /* wait for fastcgi authorizer request */
+
+ printf("Content-type: text/html\r\n");
+
+ if ((p = getenv("QUERY_STRING")) == NULL) ||
+ <QUERY_STRING is unauthorized>)
+ printf("Status: 403 Forbidden\r\n\r\n");
+
+ else printf("\r\n");
+ /* default Status is 200 - allow access */
+ }
+
+ return 0;
+ }
+
+It is possible to use any other variables provided by
+FastCGI interface for authorization check. Here is only an
+example.
+
+
+Troubleshooting
+===============
+
+fastcgi.debug should be enabled for troubleshooting.
+
+If you get: ::
+
+ (fcgi.c.274) connect delayed: 8
+ (fcgi.c.289) connect succeeded: 8
+ (fcgi.c.745) unexpected end-of-file (perhaps the fastcgi
+ process died): 8
+
+the fastcgi process accepted the connection but closed it
+right away. This happens if FCGI_WEB_SERVER_ADDRS doesn't
+include the host where you are connection from.
+
+If you get ::
+
+ (fcgi.c.274) connect delayed: 7
+ (fcgi.c.1107) error: unexpected close of fastcgi connection
+ for /peterp/seite1.php (no fastcgi process on host/port ?)
+ (fcgi.c.1015) emergency exit: fastcgi: connection-fd: 5
+ fcgi-fd: 7
+
+the fastcgi process is not running on the host/port you are
+connection to. Check your configuration.
+
+If you get ::
+
+ (fcgi.c.274) connect delayed: 7
+ (fcgi.c.289) connect succeeded: 7
+
+everything is fine. The connect() call just was delayed a
+little bit and is completly normal.
+
diff --git a/doc/features.txt b/doc/features.txt
new file mode 100644
index 00000000..b5885463
--- /dev/null
+++ b/doc/features.txt
@@ -0,0 +1,116 @@
+===============
+progress report
+===============
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/11/03 22:26:05 $
+:Revision: $Revision: 1.2 $
+
+:abstract:
+ This document tries to track the requested features and
+ the release when they have been implemented.
+
+.. meta::
+ :keywords: lighttpd, features
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+The document was inspired by a mail from David Phillips:
+
+http://marc.theaimsgroup.com/?l=thttpd&m=108051453226692&w=2
+
+It is used to see what is still missing and what is already done. ::
+
+ zell@zell.best.vwh.net writes:
+ > Now that the author has made the source code available, I am
+ > considering installing and testing the latest version. From a
+ > quick glance, it seems to support most/all of the features of
+ > Premium thttpd and Zeus.
+
+ If you think it compares to Zeus, then you've obviously never used Zeus.
+
+ lighttpd is currently the only non-blocking open source web server to
+ support FastCGI responders and that's worthwhile.
+
+ The documentation is lacking. Comments in the configuration file do not
+ make up for a complete manual.
+
+Constantly improving. ::
+
+ The configuration syntax is overly complex, like Apache. There no .htaccess
+ support.
+
+.htaccess support is not planed yet. ::
+
+ There is only one server. You cannot have a separate configuration for each
+ virtual server. This would seem to be especially problamatic when doing
+ SSL.
+
+Works since 1.3.0. ::
+
+ There is no SSI support. Zeus has full recursive SSI support. Output from
+ a FastCGI program can get run through the SSI interpreter. SSI can also do
+ virtual includes recursively.
+
+SSI works since 1.2.4. ::
+
+ Request logging is not configurable. Zeus supports fully configurable
+ access logging, plus a binary version of CLF that save space.
+
+1.2.6 adds Apache-like logfile config. ::
+
+ Access control only allows authentication via username and password. There
+ is no way to allow or deny based in IP address.
+
+planed for 1.3.x ::
+
+ The request rewriting appears to only allow regex substitutions. Zeus has a
+ simple, yet powerful, request rewrite language.
+
+
+
+ There is no support for FastCGI authorizers. These are very useful for high
+ traffic sites that require complex authentication schemes or that store
+ authorization information in a central database.
+
+since 1.1.9. ::
+
+ There is no bandwidth throttling support. Zeus does bandwidth throttling
+ correctly (i.e. unlike past versions of thttpd) and can throttle on a
+ per-subserver (thttpd-style virtual hosts) basis.
+
+since 1.3.8. ::
+
+ There is no ISAPI support. ISAPI is an elegant, open API that allows
+ modification of web server behavior. While it isn't strictly necessary for
+ an open source web server, it nice to have a documented, consistent API,
+ rather than having to manually patch the server.
+
+If someone requests it it might be implemented. ::
+
+ There is no web based interface. Zeus has a complete web based interface
+ for everything, including a powerful feature of configuring multiple virtual
+ servers at once.
+
+That is something that should be a special feature of Zeus. :) ::
+
+ There is no support for mapping certain URLs to specific filesystem paths.
+
+since 1.2.6 ::
+
+ There is no referring checking. This is incredibly important to prevent
+ hotlinking of bandwidth intensive media types (images, movies, etc.).
+
+we have something better: mod_secdownload. And if someone wants referer
+checking we have a condition in the config for it since 1.2.9 ::
+
+ Zeus has a lot of features that lighttpd doesn't have, but I only mentioned
+ the ones I care about and use.
+
+ --
+ David Phillips <david@acz.org>
+ http://david.acz.org/
+
diff --git a/doc/lighttpd.1 b/doc/lighttpd.1
new file mode 100644
index 00000000..1bfd2c54
--- /dev/null
+++ b/doc/lighttpd.1
@@ -0,0 +1,18 @@
+.TH LIGHTTPD 1 2003-12-21
+.SH NAME
+lighttpd - a fast, secure and flexible webserver
+.SH SYNOPSIS
+lighttpd -D -f <configfile>
+.SH DESCRIPTION
+.SH FILES
+/etc/lighttpd/lighttpd.conf
+.SH CONFORMING TO
+HTTP/1.0
+HTTP/1.0
+HTTP-Authentification - Basic, Digest
+FastCGI
+CGI/1.1
+.SH SEE ALSO
+spawn-fcgi(1)
+.SH AUTHOR
+jan.kneschke@incremental.de
diff --git a/doc/lighttpd.conf b/doc/lighttpd.conf
new file mode 100644
index 00000000..e5c29b98
--- /dev/null
+++ b/doc/lighttpd.conf
@@ -0,0 +1,264 @@
+# lighttpd configuration file
+#
+# use a it as base for lighttpd 1.0.0 and above
+#
+# $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $
+
+############ Options you really have to take care of ####################
+
+## modules to load
+# at least mod_access and mod_accesslog should be loaded
+# all other module should only be loaded if really neccesary
+# - saves some time
+# - saves memory
+server.modules = (
+# "mod_rewrite",
+# "mod_redirect",
+# "mod_alias",
+ "mod_access",
+# "mod_auth",
+# "mod_status",
+# "mod_setenv",
+# "mod_fastcgi",
+# "mod_proxy",
+# "mod_simple_vhost",
+# "mod_evhost",
+# "mod_userdir",
+# "mod_cgi",
+# "mod_compress",
+# "mod_ssi",
+# "mod_usertrack",
+# "mod_expire",
+# "mod_secdownload",
+# "mod_rrdtool",
+ "mod_accesslog" )
+
+## a static document-root, for virtual-hosting take look at the
+## server.virtual-* options
+server.document-root = "/www/pages/"
+
+## where to send error-messages to
+server.errorlog = "/www/logs/lighttpd.error.log"
+
+# files to check for if .../ is requested
+server.indexfiles = ( "index.php", "index.html",
+ "index.htm", "default.htm" )
+
+# mimetype mapping
+mimetype.assign = (
+ ".pdf" => "application/pdf",
+ ".sig" => "application/pgp-signature",
+ ".spl" => "application/futuresplash",
+ ".class" => "application/octet-stream",
+ ".ps" => "application/postscript",
+ ".torrent" => "application/x-bittorrent",
+ ".dvi" => "application/x-dvi",
+ ".gz" => "application/x-gzip",
+ ".pac" => "application/x-ns-proxy-autoconfig",
+ ".swf" => "application/x-shockwave-flash",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".tar" => "application/x-tar",
+ ".zip" => "application/zip",
+ ".mp3" => "audio/mpeg",
+ ".m3u" => "audio/x-mpegurl",
+ ".wma" => "audio/x-ms-wma",
+ ".wax" => "audio/x-ms-wax",
+ ".ogg" => "audio/x-wav",
+ ".wav" => "audio/x-wav",
+ ".gif" => "image/gif",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".png" => "image/png",
+ ".xbm" => "image/x-xbitmap",
+ ".xpm" => "image/x-xpixmap",
+ ".xwd" => "image/x-xwindowdump",
+ ".css" => "text/css",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".js" => "text/javascript",
+ ".asc" => "text/plain",
+ ".c" => "text/plain",
+ ".conf" => "text/plain",
+ ".text" => "text/plain",
+ ".txt" => "text/plain",
+ ".dtd" => "text/xml",
+ ".xml" => "text/xml",
+ ".mpeg" => "video/mpeg",
+ ".mpg" => "video/mpeg",
+ ".mov" => "video/quicktime",
+ ".qt" => "video/quicktime",
+ ".avi" => "video/x-msvideo",
+ ".asf" => "video/x-ms-asf",
+ ".asx" => "video/x-ms-asf",
+ ".wmv" => "video/x-ms-wmv"
+ )
+
+# Use the "Content-Type" extended attribute to obtain mime type if possible
+#mimetypes.use-xattr = "enable"
+
+
+## send a different Server: header
+## be nice and keep it at lighttpd
+#server.tag = "lighttpd"
+
+#### accesslog module
+accesslog.filename = "/www/logs/access.log"
+
+## deny access the file-extensions
+#
+# ~ is for backupfiles from vi, emacs, joe, ...
+# .inc is often used for code includes which should in general not be part
+# of the document-root
+url.access-deny = ( "~", ".inc" )
+
+
+
+######### Options that are good to be but not neccesary to be changed #######
+
+## bind to port (default: 80)
+#server.port = 81
+
+## bind to localhost (default: all interfaces)
+#server.bind = "grisu.home.kneschke.de"
+
+## error-handler for status 404
+#server.error-handler-404 = "/error-handler.html"
+#server.error-handler-404 = "/error-handler.php"
+
+## to help the rc.scripts
+#server.pid-file = "/var/run/lighttpd.pid"
+
+
+###### virtual hosts
+##
+## If you want name-based virtual hosting add the next three settings and load
+## mod_simple_vhost
+##
+## document-root =
+## virtual-server-root + virtual-server-default-host + virtual-server-docroot
+## or
+## virtual-server-root + http-host + virtual-server-docroot
+##
+#simple-vhost.server-root = "/home/weigon/wwwroot/servers/"
+#simple-vhost.default-host = "grisu.home.kneschke.de"
+#simple-vhost.document-root = "/pages/"
+
+
+##
+## Format: <errorfile-prefix><status-code>.html
+## -> ..../status-404.html for 'File not found'
+#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-"
+
+## virtual directory listings
+#server.dir-listing = "enable"
+
+## enable debugging
+#debug.log-request-header = "enable"
+#debug.log-response-header = "enable"
+#debug.log-request-handling = "enable"
+#debug.log-file-not-found = "enable"
+
+### only root can use these options
+#
+# chroot() to directory (default: no chroot() )
+#server.chroot = "/"
+
+## change uid to <uid> (default: don't care)
+#server.username = "wwwrun"
+
+## change uid to <uid> (default: don't care)
+#server.groupname = "wwwrun"
+
+#### compress module
+#compress.cache-dir = "/tmp/lighttpd/cache/compress/"
+#compress.filetype = ("text/plain", "text/html")
+
+#### proxy module
+## read proxy.txt for more info
+#proxy.server = ( ".php" =>
+# ( "localhost" =>
+# (
+# "host" => "192.168.0.101",
+# "port" => 80
+# )
+# )
+# )
+
+#### fastcgi module
+## read fastcgi.txt for more info
+#fastcgi.server = ( ".php" =>
+# ( "localhost" =>
+# (
+# "socket" => "/tmp/php-fastcgi.socket",
+# "bin-path" => "/usr/local/bin/php"
+# )
+# )
+# )
+
+#### CGI module
+#cgi.assign = ( ".pl" => "/usr/bin/perl",
+# ".cgi" => "/usr/bin/perl" )
+#
+
+#### SSL engine
+#ssl.engine = "enable"
+#ssl.pemfile = "server.pem"
+
+#### status module
+#status.status-url = "/server-status"
+#status.config-url = "/server-config"
+
+#### auth module
+## read authentification.txt for more info
+#auth.backend = "plain"
+#auth.backend.plain.userfile = "lighttpd.user"
+#auth.backend.plain.groupfile = "lighttpd.group"
+
+#auth.backend.ldap.hostname = "localhost"
+#auth.backend.ldap.base-dn = "dc=my-domain,dc=com"
+#auth.backend.ldap.filter = "(uid=$)"
+
+#auth.require = ( "/server-status" =>
+# (
+# "method" => "digest",
+# "realm" => "download archiv",
+# "require" => "group=www|user=jan|host=192.168.2.10"
+# ),
+# "/server-info" =>
+# (
+# "method" => "digest",
+# "realm" => "download archiv",
+# "require" => "group=www|user=jan|host=192.168.2.10"
+# )
+# )
+
+#### url handling modules (rewrite, redirect, access)
+#url.rewrite = ( "^/$" => "/server-status" )
+#url.redirect = ( "^/wishlist/(.+)" => "http://www.123.org/$1" )
+
+#
+# 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/storage/dev/www/%3/htdocs/"
+
+#### expire module
+#expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes")
+
+#### ssi
+#ssi.extension = ( ".shtml" )
+
+#### rrdtool
+#rrdtool.binary = "/usr/bin/rrdtool"
+#rrdtool.db-name = "/var/www/lighttpd.rrd"
+
+#### setenv
+#setenv.add-request-header = ( "TRAV_ENV" => "mysql://user@host/db" )
+#setenv.add-response-header = ( "X-Secret-Message" => "42" )
+
diff --git a/doc/lighttpd.user b/doc/lighttpd.user
new file mode 100644
index 00000000..727e9c33
--- /dev/null
+++ b/doc/lighttpd.user
@@ -0,0 +1 @@
+dummy:test123
diff --git a/doc/mysqlvhost.txt b/doc/mysqlvhost.txt
new file mode 100644
index 00000000..db7edd0c
--- /dev/null
+++ b/doc/mysqlvhost.txt
@@ -0,0 +1,86 @@
+
+====================
+MySQL bases vhosting
+====================
+
+-----------------------
+Module: mod_mysql_vhost
+-----------------------
+
+:Author: ada@riksnet.se
+:Date: $Date: 2004/08/29 09:43:49 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ This module provides virtual hosts (vhosts) based on a MySQL table, and
+ (optionally) prepares mod_fastcgi for sub-chroot FastCGI deployment.
+
+.. meta::
+ :keywords: lighttpd, mysql, vhost
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+vhost via mysql
+
+Options
+=======
+
+Example: ::
+
+ mysql-vhost.db = "lighttpd"
+ mysql-vhost.user = "lighttpd"
+ mysql-vhost.pass = "secret"
+ mysql-vhost.sock = "/var/mysql.lighttpd.sock"
+ mysql-vhost.sql = "SELECT docroot,fcgioffset,fcgiarg FROM domains WHERE domain='?'"
+
+
+MySQL setup: ::
+
+ GRANT SELECT ON lighttpd.* TO lighttpd@localhost IDENTIFIED BY 'secret';
+
+ CREATE DATABASE lighttpd;
+
+ USE lighttpd;
+
+ CREATE TABLE domains (
+ domain char(64) not null primary key,
+ docroot char(128) not null,
+ fcgioffset tinyint unsigned not null,
+ fcgiarg smallint unsigned not null
+ );
+
+ INSERT INTO domains VALUES ('host.dom.ain','/http/host.dom.ain/',5,10001);
+
+
+Extra stuff: ::
+
+ fastcgi.server = ( ".php" => ( "php-fcgi" => (
+ "socket" => "../php/socket",
+ "spawn" => "/php/php-spawn"
+ )))
+
+
+The example above will get the docroot for a virtual host from the
+lighttpd.domains table, and also change the docroot for FastCGI to
+be 5 (=fcgioffset) chars below the web docroot (in the example above
+this means that the web docroot is /http/host.dom.ain/, while the
+FastCGI docroot will be /host.dom.ain/).
+
+The fastcgi.server "socket" is (patched to be) relative to docroot,
+and the new "spawn" argument specifies a command to run to dynamically
+create a new FastCGI process in case none is running (as opposed to
+"bin-path" which does not dynamically create/restart FastCGI processes).
+
+The example above will also add the argument "10001" (=fcgiarg) when
+invoking the /php/php-spawn (=spawn) FastCGI spawn program. Tf the
+spawn program is setuid this can be used to chroot and setgid/setuid
+to the right user before exec:ing the actual FastCGI program.
+
+NOTE: both fcgioffset and fcgiarg are optional. Just remove every
+mention of them in the example above for a pure vhost server.
+You can also use just fcgioffset and not fcgiarg if you like.
+
+
diff --git a/doc/performance.txt b/doc/performance.txt
new file mode 100644
index 00000000..3d0eff90
--- /dev/null
+++ b/doc/performance.txt
@@ -0,0 +1,198 @@
+========================
+Performance Improvements
+========================
+
+------------
+Module: core
+------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/11/03 22:26:05 $
+:Revision: $Revision: 1.3 $
+
+:abstract:
+ handling performance issues in lighttpd
+
+.. meta::
+ :keywords: lighttpd, performance
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+Performance Issues
+------------------
+
+lighttpd is optimized into various directions. The most important is
+performance. The operation system has two major facalities to help lighttpd
+a deliver it best performance.
+
+HTTP Keep-Alive
+---------------
+
+Disabling keep-alive might help your server if you suffer from a large
+number of open file-descriptors.
+
+::
+
+ server.max-keep-alive-requests = 128
+ server.max-keep-alive-idle = 30
+ server.max-read-idle = 60
+ server.max-write-idle = 360
+
+Event Handlers
+--------------
+
+The first one is the Event Handler which cares about notifying the server
+that one of the connections is ready to send or to recieve. As you can see
+every OS has at least the select() call which has some limitations.
+
+============ ========== ===============
+OS Method Config-Value
+============ ========== ===============
+all select select
+Unix poll poll
+Linux 2.4+ rt-signals linux-rtsig
+Linux 2.6+ epoll linux-sysepoll
+Solaris /dev/poll solaris-devpoll
+FreeBSD, ... kqueue freebsd-kqueue
+============ ========== ===============
+
+
+For more infomation in this topic take a look at http://www.kegel.com/c10k.html
+
+Configuration
+`````````````
+
+The event-handler can be set by specifying the 'Config-Value' from above
+in the ``server.event-handler`` variable
+
+e.g.: ::
+
+ server.event-handler = "linux-sysepoll"
+
+Network Handlers
+----------------
+
+The basic network interface for all platforms at the syscalls read() and
+write(). Each modern OS provides its own syscall to help network servers
+to transfer files as fast as possible.
+
+If you want to send out a file from the webserver it does make any sense
+to copy the file into the webserver just to write() it back into a socket
+in the next step.
+
+sendfile() minimizes the work in the application and pushes a file directly
+into the network card (idealy spoken).
+
+lighttpd supports all major platform specific calls:
+
+========== ==========
+OS Method
+========== ==========
+all write
+Unix writev
+Linux 2.4+ sendfile
+Linux 2.6+ sendfile64
+Solaris sendfilev
+FreeBSD sendfile
+========== ==========
+
+They are selected automaticly on compile-time. If you have problems check
+./src/network_backend.h and disable the corresponding USE\_... define.
+
+Max Connections
+---------------
+
+As lighttpd is a single-threaded server its main resource limit is the
+number of file-descriptors which is (on most systems) set to 1024 by default.
+
+If you are running a high-traffic site you might want to increase this limit
+by setting ::
+
+ server.max-fds = 2048
+
+This only works if lighttpd is started as root.
+
+Out-of-fd condition
+-------------------
+
+As fds are used for tcp/ip sockets, files, directories, ... a simple request
+for a PHP page might result in using 3 fds:
+
+1. the TCP/IP socket to the client
+2. the TCP/IP and Unix domain socket to the FastCGI process
+3. the filehandle to the file in the document-root to check if it is really existing
+
+If lighttpd runs out of file-descriptors it will stop accepting new
+connections for while to use the currently available fds (file-descriptors)
+to handle the currently running requests.
+
+If more than 90% of the fds are used the the handling of new connections is
+disabled, if it dropes below 80% again new connection will accepted again.
+
+Under some circumstances you will see ::
+
+ ... accept() failed: Too many open files
+
+in the error-log. This tells you the you had to many new requests at once
+and lighttpd could not disable the incomming connections soon enough. The
+connection is drop and the client will get a error-message like 'connection
+failed'. This is very rare and might only occur in test-setups.
+
+Increasing the ``server.max-fds`` limit will reduce the propability of this
+problem.
+
+
+
+
+Plattform Specific Notes
+========================
+
+Linux
+-----
+
+For Linux 2.4.x should should think about compiling lighttpd with the option
+``--disable-lfs`` to disable the support for files larger than 2Gb. lighttpd will
+fall back to the ``writev() + mmap()`` network calls which is ok, but not as
+fast as possible but support files larger than 2Gb.
+
+Disabling the TCP options reduces the overhead of each TCP packet and might
+help to get the last few percent of performance out of the server.
+
+- net.ipv4.tcp_sack = 0
+- net.ipv4.tcp_timestamps = 0
+
+Increasing the TCP send and receive buffers will increase the performance a
+lot if (and only if) you have a lot large files to send.
+
+- net.ipv4.tcp_wmem = 4096 65536 524288
+- net.core.wmem_max = 1048576
+
+If you have a lot large file uploads increasing the receive buffers will help.
+
+- net.ipv4.tcp_rmem = 4096 87380 524288
+- net.core.rmem_max = 1048576
+
+Keep in mind that the buffers have to multiplied by server.max-fds and be
+allocated in the Kernel area. Be carefull with that.
+
+FreeBSD
+-------
+
+On FreeBSD you might gain some performance by enabling accept-filters. Just
+compile your kernel with: ::
+
+ options ACCEPT_FILTER_HTTP
+
+For more ideas in tuning FreeBSD read: tuning(7)
+
+Reducing the recvspace should always be ok if the server only handles HTTP
+requests without large uploads. Increasing the sendspace would reduce the
+system-load if you have a lot large files to be sent, but keep in mind that
+you to provide the memory in kernel for each connection. 1024 * 64k would mean
+64M of kernel-ram. Keep this in mind.
+
+- net.inet.tcp.recvspace = 4096
+
diff --git a/doc/plugins.txt b/doc/plugins.txt
new file mode 100644
index 00000000..8a755fea
--- /dev/null
+++ b/doc/plugins.txt
@@ -0,0 +1,260 @@
+================
+Plugin Interface
+================
+
+------------
+Module: core
+------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/01 07:01:29 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ The plugin interface is the integral part of lighttpd provide
+ a flexible way to add specific functionality to lighttpd.
+
+.. meta::
+ :keywords: lighttpd, plugins
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+Plugins allow you to enhance to functionality of lighttpd without
+changing the core of the webserver. They can be loaded at startup-time
+and can change hardly any aspect of the behaviour of the webserver.
+
+Plugin Entry Points
+-------------------
+
+lighttpd has 16 hooks which are used in different states of the
+execution of the request:
+
+Serverwide hooks
+````````````````
+
+:init_:
+ called when the plugin is loaded
+:cleanup_:
+ called when the plugin is unloaded
+:set_defaults_:
+ called when the configuration has to be processed
+:handle_trigger_:
+ called once a second
+:handle_sighup_:
+ called when the server received a SIGHUP
+
+Connectionwide hooks
+````````````````````
+
+Most of these hooks are call in ``http_response_prepare()`` after some
+field in the connection structure are set.
+
+:handle_uri_raw_:
+ called after uri.path_raw, uri.authority and uri.scheme are set
+:handle_uri_clean_:
+ called after uri.path (a clean uri without .. and %20) is set
+:handle_docroot_:
+ called at the end of the logical path handle to get a docroot
+:handle_subrequest_start_:
+ called if the physical path is setup and checked
+:handle_subrequest_:
+ called at the end of ``http_response_prepare()``
+:handle_physical_path_:
+ called after the physical path is created and no other handler is
+ found for this request
+:handle_request_done_:
+ called when the request is done
+:handle_connection_close_:
+ called if the connection has to be closed
+:handle_joblist_:
+ called after the connection_state_engine is left again and plugin
+ internal handles have to be called
+:connection_reset_:
+ called if the connection structure has to be cleaned up
+
+
+Plugin Interface
+----------------
+
+\*_plugin_init
+``````````````
+
+Every plugin has a uniquely named function which is called after the
+plugin is loaded. It is used to setup the ``plugin`` structure with
+some usefull data:
+
+- name of the plugin ``name``
+- all hooks
+
+The field ``data`` and ``lib`` should not be touched in the init function.
+``lib`` is the library handler from dlopen and ``data`` will be the storage
+of the internal plugin data.
+
+:returns:
+ 0 (not handled)
+
+init
+````
+
+The first real call of a plugin function is the init-hook which is used
+to set up the internal plugin data. The internal plugin is assigned the
+``data`` field mentioned in the \*_plugin_init description.
+
+:returns:
+ a pointer to the internal plugin data.
+
+cleanup
+```````
+
+The cleanup hook is called just before the plugin is unloaded. It is meant
+to free all buffers allocated in ``init`` or somewhere else in the plugin
+which are still not freed and to close all handles which were opened and
+are not closed yet.
+
+:returns:
+ HANDLER_GO_ON if ok (not handled)
+
+set_defaults
+````````````
+
+set_defaults is your entry point into the configfile parsing. It should
+pass a list of options to ``config_insert_values`` and check if
+the plugin configuration is valid. If it is not valid yet, it should
+set usefull defaults or return with HANDLER_ERROR and an error message.
+
+:returns:
+ HANDLER_GO_ON if ok
+
+ HANDLER_ERROR will terminated lighttpd
+
+connection_reset
+````````````````
+
+called at the end of each request
+
+:returns:
+ HANDLER_GO_ON if ok
+
+ HANDLER_ERROR on error
+
+handle_trigger
+``````````````
+
+called once a second
+
+:returns:
+ HANDLER_GO_ON if ok
+
+ HANDLER_ERROR on error
+
+handle_sighup
+`````````````
+
+called if a SIGHUP is received (cycling logfiles, ...)
+
+:returns:
+ HANDLER_GO_ON if ok
+
+ HANDLER_ERROR on error
+
+handle_uri_raw
+``````````````
+
+called after uri_raw is set
+
+:returns:
+ HANDLER_GO_ON if ok
+ HANDLER_FINISHED if the final output is prepared
+
+ HANDLER_ERROR on error
+
+handle_uri_clean
+````````````````
+
+called after uri.path is set
+
+:returns:
+ HANDLER_GO_ON if ok
+ HANDLER_FINISHED if the final output is prepared
+
+ HANDLER_ERROR on error
+
+handle_docroot
+``````````````
+
+called when a docroot is needed
+
+:returns:
+ HANDLER_GO_ON if ok
+ HANDLER_FINISHED if the final output is prepared
+
+ HANDLER_ERROR on error
+
+handle_subrequest_start
+```````````````````````
+
+called after physical.path is set
+
+:returns:
+ HANDLER_GO_ON if ok
+ HANDLER_FINISHED if the final output is prepared
+
+ HANDLER_ERROR on error
+
+handle_subrequest
+`````````````````
+
+called if subrequest_start requested a COMEBACK or a WAIT_FOR_EVENT
+
+:returns:
+ HANDLER_GO_ON if ok
+ HANDLER_FINISHED if the final output is prepared
+
+ HANDLER_ERROR on error
+
+handle_physical_path
+````````````````````
+
+called after physical.path is set
+
+:returns:
+ HANDLER_GO_ON if ok
+ HANDLER_FINISHED if the final output is prepared
+
+ HANDLER_ERROR on error
+
+
+handle_request_done
+```````````````````
+
+called at the end of the request (logging, statistics, ...)
+
+:returns:
+ HANDLER_GO_ON if ok
+
+ HANDLER_ERROR on error
+
+handle_connection_close
+```````````````````````
+
+called if the connection is terminated
+
+:returns:
+ HANDLER_GO_ON if ok
+
+ HANDLER_ERROR on error
+
+handle_joblist
+``````````````
+
+called if the state of the connection has changed
+
+:returns:
+ HANDLER_GO_ON if ok
+
+ HANDLER_ERROR on error
+
+
diff --git a/doc/proxy.txt b/doc/proxy.txt
new file mode 100644
index 00000000..6621cf97
--- /dev/null
+++ b/doc/proxy.txt
@@ -0,0 +1,72 @@
+===================
+the Proxy Interface
+===================
+
+-----------------
+Module: mod_proxy
+-----------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/01 07:01:29 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ The proxy module a simplest way to connect lighttpd to
+ java servers which have a HTTP-interface.
+
+.. meta::
+ :keywords: lighttpd, Proxy
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+...
+
+Options
+=======
+
+lighttpd provides the Proxy support via the proxy-module
+(mod_proxy) which provides 2 options in the config-file:
+
+:proxy.debug:
+ a value between 0 and 65535 to set the debug-level in the
+ Proxy module. Currently only 0 and 1 are used. Use 1 to
+ enable some debug output, 0 to disable it.
+
+:proxy.server:
+ tell the module where to send Proxy requests to. Every
+ file-extension can have its own handler. Load-Balancing is
+ done by specifying multiple handles for the same extension.
+
+ structure of proxy.server section: ::
+
+ ( <extension> =>
+ ( <handle> =>
+ ( "host" => <string> ,
+ "port" => <integer>
+ )
+ ),
+ ( <handle> => ...
+ )
+ )
+
+ :<extension>: is the file-extension or prefix
+ (if started with "/")
+ :<handle>: is just a unique handle name
+ :"host": is ip of the proxy server
+ :"port": is tcp-port on the "host" used by the proxy
+ server
+
+ e.g.: ::
+
+ proxy.server = ( ".php" =>
+ ( "grisu" =>
+ (
+ "host" => "192.168.0.2",
+ "port" => 1026
+ )
+ )
+ )
+
diff --git a/doc/rc.lighttpd b/doc/rc.lighttpd
new file mode 100755
index 00000000..7b0255ce
--- /dev/null
+++ b/doc/rc.lighttpd
@@ -0,0 +1,169 @@
+#! /bin/sh
+# Copyright (c) 1995-2002 SuSE Linux AG, Nuernberg, Germany.
+# All rights reserved.
+#
+# Author: Kurt Garloff <feedback@suse.de>
+#
+# /etc/init.d/FOO
+#
+# and symbolic its link
+#
+# /(usr/)sbin/rcFOO
+#
+# LSB compliant service control script; see http://www.linuxbase.org/spec/
+#
+# System startup script for some example service or daemon FOO (template)
+#
+### BEGIN INIT INFO
+# Provides: FOO
+# Required-Start: $remote_fs $syslog
+# Required-Stop: $remote_fs $syslog
+# Default-Start: 3 5
+# Default-Stop: 0 1 2 6
+# Description: Start FOO to allow XY and provide YZ
+# continued on second line by '#<TAB>'
+### END INIT INFO
+#
+# Note on Required-Start: It does specify the init script ordering,
+# not real dependencies. Depencies have to be handled by admin
+# resp. the configuration tools (s)he uses.
+
+# Source SuSE config (if still necessary, most info has been moved)
+test -r /etc/rc.config && . /etc/rc.config
+
+# Check for missing binaries (stale symlinks should not happen)
+LIGHTTPD_BIN=/usr/sbin/lighttpd
+test -x $LIGHTTPD_BIN || exit 5
+
+# Check for existence of needed config file and read it
+LIGHTTPD_CONFIG=/etc/sysconfig/lighttpd
+test -r $LIGHTTPD_CONFIG || exit 6
+. $LIGHTTPD_CONFIG
+
+# Shell functions sourced from /etc/rc.status:
+# rc_check check and set local and overall rc status
+# rc_status check and set local and overall rc status
+# rc_status -v ditto but be verbose in local rc status
+# rc_status -v -r ditto and clear the local rc status
+# rc_failed set local and overall rc status to failed
+# rc_failed <num> set local and overall rc status to <num><num>
+# rc_reset clear local rc status (overall remains)
+# rc_exit exit appropriate to overall rc status
+# rc_active checks whether a service is activated by symlinks
+. /etc/rc.status
+
+# First reset status of this service
+rc_reset
+
+# Return values acc. to LSB for all commands but status:
+# 0 - success
+# 1 - generic or unspecified error
+# 2 - invalid or excess argument(s)
+# 3 - unimplemented feature (e.g. "reload")
+# 4 - insufficient privilege
+# 5 - program is not installed
+# 6 - program is not configured
+# 7 - program is not running
+#
+# Note that starting an already running service, stopping
+# or restarting a not-running service as well as the restart
+# with force-reload (in case signalling is not supported) are
+# considered a success.
+
+case "$1" in
+ start)
+ echo -n "Starting lighttpd"
+ ## Start daemon with startproc(8). If this fails
+ ## the echo return value is set appropriate.
+
+ # NOTE: startproc returns 0, even if service is
+ # already running to match LSB spec.
+ startproc $LIGHTTPD_BIN -f $LIGHTTPD_CONF_PATH
+
+ # Remember status and be verbose
+ rc_status -v
+ ;;
+ stop)
+ echo -n "Shutting down lighttpd"
+ ## Stop daemon with killproc(8) and if this fails
+ ## set echo the echo return value.
+
+ killproc -TERM $LIGHTTPD_BIN
+
+ # Remember status and be verbose
+ rc_status -v
+ ;;
+ try-restart)
+ ## Stop the service and if this succeeds (i.e. the
+ ## service was running before), start it again.
+ ## Note: try-restart is not (yet) part of LSB (as of 0.7.5)
+ $0 status >/dev/null && $0 restart
+
+ # Remember status and be quiet
+ rc_status
+ ;;
+ restart)
+ ## Stop the service and regardless of whether it was
+ ## running or not, start it again.
+ $0 stop
+ $0 start
+
+ # Remember status and be quiet
+ rc_status
+ ;;
+ force-reload)
+ ## Signal the daemon to reload its config. Most daemons
+ ## do this on signal 1 (SIGHUP).
+ ## If it does not support it, restart.
+
+ echo -n "Reload service LIGHTTPD"
+ ## if it supports it:
+ killproc -HUP $LIGHTTPD_BIN
+ #touch /var/run/LIGHTTPD.pid
+ rc_status -v
+
+ ## Otherwise:
+ #$0 stop && $0 start
+ #rc_status
+ ;;
+ reload)
+ ## Like force-reload, but if daemon does not support
+ ## signalling, do nothing (!)
+
+ # If it supports signalling:
+ echo -n "Reload service LIGHTTPD"
+ killproc -HUP $LIGHTTPD_BIN
+ #touch /var/run/LIGHTTPD.pid
+ rc_status -v
+
+ ## Otherwise if it does not support reload:
+ #rc_failed 3
+ #rc_status -v
+ ;;
+ status)
+ echo -n "Checking for service LIGHTTPD: "
+ ## Check status with checkproc(8), if process is running
+ ## checkproc will return with exit status 0.
+
+ # Return value is slightly different for the status command:
+ # 0 - service running
+ # 1 - service dead, but /var/run/ pid file exists
+ # 2 - service dead, but /var/lock/ lock file exists
+ # 3 - service not running
+
+ # NOTE: checkproc returns LSB compliant status values.
+ checkproc $LIGHTTPD_BIN
+ rc_status -v
+ ;;
+ probe)
+ ## Optional: Probe for the necessity of a reload,
+ ## print out the argument which is required for a reload.
+
+ test /etc/lighttpd/lighttpd.conf -nt /var/run/lighttpd.pid && echo reload
+ ;;
+ *)
+ echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}"
+ exit 1
+ ;;
+esac
+rc_exit
diff --git a/doc/rc.lighttpd.redhat b/doc/rc.lighttpd.redhat
new file mode 100755
index 00000000..e22d551d
--- /dev/null
+++ b/doc/rc.lighttpd.redhat
@@ -0,0 +1,87 @@
+#!/bin/sh
+#
+# lighttpd Startup script for the lighttpd server
+#
+# chkconfig: - 85 15
+# description: Lightning fast webserver with light system requirements
+#
+# processname: lighttpd
+# config: /etc/lighttpd/lighttpd.conf
+# config: /etc/sysconfig/lighttpd
+# pidfile: /var/run/lighttpd.pid
+#
+# Note: pidfile is assumed to be created
+# by lighttpd (config: server.pid-file).
+# If not, uncomment 'pidof' line.
+
+# Source function library
+. /etc/rc.d/init.d/functions
+
+if [ -f /etc/sysconfig/lighttpd ]; then
+ . /etc/sysconfig/lighttpd
+fi
+
+if [ -z "$LIGHTTPD_CONF_PATH" ]; then
+ LIGHTTPD_CONF_PATH="/etc/lighttpd/lighttpd.conf"
+fi
+
+prog="lighttpd"
+lighttpd="/usr/sbin/lighttpd"
+RETVAL=0
+
+start() {
+ echo -n $"Starting $prog: "
+ daemon $lighttpd -f $LIGHTTPD_CONF_PATH
+ RETVAL=$?
+ echo
+ [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog
+ return $RETVAL
+}
+
+stop() {
+ echo -n $"Stopping $prog: "
+ killproc $lighttpd
+ RETVAL=$?
+ echo
+ [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog
+ return $RETVAL
+}
+
+reload() {
+ echo -n $"Reloading $prog: "
+ killproc $lighttpd -HUP
+ RETVAL=$?
+ echo
+ return $RETVAL
+}
+
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ restart)
+ stop
+ start
+ ;;
+ condrestart)
+ if [ -f /var/lock/subsys/$prog ]; then
+ stop
+ start
+ fi
+ ;;
+ reload)
+ reload
+ ;;
+ status)
+ status $lighttpd
+ RETVAL=$?
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|restart|condrestart|reload|status}"
+ RETVAL=1
+esac
+
+exit $RETVAL
diff --git a/doc/redirect.txt b/doc/redirect.txt
new file mode 100644
index 00000000..10c6a16a
--- /dev/null
+++ b/doc/redirect.txt
@@ -0,0 +1,36 @@
+===============
+URL Redirection
+===============
+
+--------------------
+Module: mod_redirect
+--------------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/29 09:43:49 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ url redirection
+
+.. meta::
+ :keywords: lighttpd, redirect
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+...
+
+Options
+=======
+
+url.redirect
+ redirects a set of URLs externally
+
+ e.g. ::
+
+ url.redirect = ( "^/show/([0-9]+)/([0-9]+)$" => "http://www.example.org/show.php?isdn=$1&page$2",
+ "^/get/([0-9]+)/([0-9]+)$" => "http://www.example.org/get.php?isdn=$1&page$2" )
+
diff --git a/doc/rewrite.txt b/doc/rewrite.txt
new file mode 100644
index 00000000..f9102642
--- /dev/null
+++ b/doc/rewrite.txt
@@ -0,0 +1,42 @@
+============
+URL Rewrites
+============
+
+-------------------
+Module: mod_rewrite
+-------------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/11/03 22:26:05 $
+:Revision: $Revision: 1.2 $
+
+:abstract:
+ url rewrite
+
+.. meta::
+ :keywords: lighttpd, rewrite
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+internal redirects, url rewrite
+
+Options
+=======
+
+url.rewrite
+ rewrites a set of URLs interally in the webserver BEFORE they are handled.
+
+ e.g. ::
+
+ url.rewrite = ( "^/show/([0-9]+)/([0-9]+)$" => "/show.php?isdn=$1&page$2" )
+
+url.rewrite-final
+ rewrites a set of URLs interally in the webserver BEFORE they are handled
+ and rewrite the urls in the second round
+
+ e.g. ::
+
+ url.rewrite-final = ( "^/get/([0-9]+)/([0-9]+)$" => "/get.php?isdn=$1&page$2" )
diff --git a/doc/rrdtool-graph.sh b/doc/rrdtool-graph.sh
new file mode 100755
index 00000000..1157a285
--- /dev/null
+++ b/doc/rrdtool-graph.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+RRDTOOL=/usr/bin/rrdtool
+OUTDIR=/var/www/servers/www.example.org/pages/rrd/
+INFILE=/var/www/lighttpd.rrd
+OUTPRE=lighttpd-traffic
+
+DISP="DEF:bin=$INFILE:InOctets:AVERAGE \
+ DEF:binmin=$INFILE:InOctets:MIN \
+ DEF:binmax=$INFILE:InOctets:MAX \
+ DEF:bout=$INFILE:OutOctets:AVERAGE \
+ DEF:boutmin=$INFILE:OutOctets:MIN \
+ DEF:boutmax=$INFILE:OutOctets:MAX \
+ LINE1:bin#0000FF:in \
+ LINE1:binmin#2222FF: \
+ STACK:binmax#2222FF: \
+ LINE1:bout#FF0000:out \
+ LINE1:boutmin#FF2222: \
+ STACK:boutmax#FF2222: \
+ -v bytes/s"
+
+$RRDTOOL graph $OUTDIR/$OUTPRE-hour.png -a PNG --start -14400 $DISP
+$RRDTOOL graph $OUTDIR/$OUTPRE-day.png -a PNG --start -86400 $DISP
+$RRDTOOL graph $OUTDIR/$OUTPRE-month.png -a PNG --start -2592000 $DISP
+
+OUTPRE=lighttpd-requests
+
+DISP="DEF:req=$INFILE:Requests:AVERAGE \
+ DEF:reqmin=$INFILE:Requests:MIN \
+ DEF:reqmax=$INFILE:Requests:MAX \
+ LINE1:req#0000FF:requests \
+ LINE1:reqmin#2222FF: \
+ STACK:reqmax#2222FF: \
+ -v req/s"
+
+$RRDTOOL graph $OUTDIR/$OUTPRE-hour.png -a PNG --start -14400 $DISP
+$RRDTOOL graph $OUTDIR/$OUTPRE-day.png -a PNG --start -86400 $DISP
+$RRDTOOL graph $OUTDIR/$OUTPRE-month.png -a PNG --start -2592000 $DISP
diff --git a/doc/rrdtool.txt b/doc/rrdtool.txt
new file mode 100644
index 00000000..ac2d0e5d
--- /dev/null
+++ b/doc/rrdtool.txt
@@ -0,0 +1,108 @@
+=======
+rrdtool
+=======
+
+-------------------
+Module: mod_rrdtool
+-------------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/29 09:43:49 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ mod_rrdtool is used to monitor the traffic and load on the webserver
+
+.. meta::
+ :keywords: lighttpd, skeleton
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+RRD_ is a system to store and display time-series data (i.e. network
+bandwidth, machine-room temperature, server load average).
+
+.. _RRD: http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/
+
+Options
+=======
+
+rrdtool.binary
+ path to the rrdtool binary
+
+ e.g.: ::
+
+ rrdtool.binary = "/usr/bin/rrdtool"
+
+rrdtool.db-name
+ filename of the rrd-database. Make sure that <rrdtool.db-name> doesn't exists
+ before the first run as lighttpd has to create the DB for you.
+
+ e.g.: ::
+
+ rrdtool.db-name = "/var/www/lighttpd.rrd"
+
+Generating Graphs
+=================
+
+::
+
+ #!/bin/sh
+
+ RRDTOOL=/usr/bin/rrdtool
+ OUTDIR=/var/www/servers/www.example.org/pages/rrd/
+ INFILE=/var/www/lighttpd.rrd
+ OUTPRE=lighttpd-traffic
+
+ DISP="-v bytes --title TrafficWebserver \
+ DEF:binraw=$INFILE:InOctets:AVERAGE \
+ DEF:binmaxraw=$INFILE:InOctets:MAX \
+ DEF:binminraw=$INFILE:InOctets:MIN \
+ DEF:bout=$INFILE:OutOctets:AVERAGE \
+ DEF:boutmax=$INFILE:OutOctets:MAX \
+ DEF:boutmin=$INFILE:OutOctets:MIN \
+ CDEF:bin=binraw,-1,* \
+ CDEF:binmax=binmaxraw,-1,* \
+ CDEF:binmin=binminraw,-1,* \
+ AREA:binmin#ffffff: \
+ STACK:binmax#f00000: \
+ LINE1:binmin#a0a0a0: \
+ LINE1:binmax#a0a0a0: \
+ LINE2:bin#a00000:incoming \
+ GPRINT:bin:MIN:%.2lf \
+ GPRINT:bin:AVERAGE:%.2lf \
+ GPRINT:bin:MAX:%.2lf \
+ AREA:boutmin#ffffff: \
+ STACK:boutmax#00f000: \
+ LINE1:boutmin#a0a0a0: \
+ LINE1:boutmax#a0a0a0: \
+ LINE2:bout#00a000:outgoing \
+ GPRINT:bout:MIN:%.2lf \
+ GPRINT:bout:AVERAGE:%.2lf \
+ GPRINT:bout:MAX:%.2lf \
+ "
+
+
+ $RRDTOOL graph $OUTDIR/$OUTPRE-hour.png -a PNG --start -14400 $DISP
+ $RRDTOOL graph $OUTDIR/$OUTPRE-day.png -a PNG --start -86400 $DISP
+ $RRDTOOL graph $OUTDIR/$OUTPRE-month.png -a PNG --start -2592000 $DISP
+
+ OUTPRE=lighttpd-requests
+
+ DISP="-v req --title RequestsperSecond -u 1 \
+ DEF:req=$INFILE:Requests:AVERAGE \
+ DEF:reqmax=$INFILE:Requests:MAX \
+ DEF:reqmin=$INFILE:Requests:MIN \
+ AREA:reqmin#ffffff: \
+ STACK:reqmax#00f000: \
+ LINE1:reqmin#a0a0a0: \
+ LINE1:reqmax#a0a0a0: \
+ LINE2:req#006000:requests"
+
+
+ $RRDTOOL graph $OUTDIR/$OUTPRE-hour.png -a PNG --start -14400 $DISP
+ $RRDTOOL graph $OUTDIR/$OUTPRE-day.png -a PNG --start -86400 $DISP
+ $RRDTOOL graph $OUTDIR/$OUTPRE-month.png -a PNG --start -2592000 $DISP
+
diff --git a/doc/secdownload.txt b/doc/secdownload.txt
new file mode 100644
index 00000000..5cd8f315
--- /dev/null
+++ b/doc/secdownload.txt
@@ -0,0 +1,142 @@
+===========================
+Secure and Fast Downloading
+===========================
+
+-----------------------
+Module: mod_secdownload
+-----------------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/01 07:01:29 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ authenticated file requests and a counter measurement against
+ deep-linking can be achieved easily by using mod_secdownload
+
+.. meta::
+ :keywords: lighttpd, secure, fast, downloads
+
+.. contents:: Table of Contents
+
+Options
+=======
+
+::
+
+ secdownload.secret = <string>
+ secdownload.document-root = <string>
+ secdownload.uri-prefix = <string> (default: /)
+ secdownload.timeout = <short> (default: 60s)
+
+Description
+===========
+
+there are multiple way to handle secured download mechanisms:
+
+1. use the webserver and the internal HTTP-authentification
+2. use the application to authenticate and send the file
+ through the application
+
+Both way have limitations:
+
+webserver:
+
+- ``+`` fast download
+- ``+`` no additional system load
+- ``-`` unflexible authentification handling
+
+application:
+
+- ``+`` integrated into the overall layout
+- ``+`` very flexible permission management
+- ``-`` the download occupies a application thread/process
+
+A simple way to combine the two way could be:
+
+1. app authenticates user and checks permissions to
+ download the file.
+2. app redirectes user the file accessable by the webserver
+ for further downloading
+3. the webserver transfers the file to the user
+
+As the webserver doesn't know anything about the permissions
+used in the app the resulting URL would be available to every
+user who knows the URL.
+
+mod_secdownload removes this problem by introducing a way to
+authenticate a URL for a specified time. The application has
+to generate a token and a timestamp which are checked by the
+webserver before it allows the file to be downloaded by the
+webserver.
+
+The generated URL has to have the format:
+
+<uri-prefix><token>/<timestamp-in-hex><rel-path>
+
+<token> is a MD5 of
+
+1. a secret string (user supplied)
+2. <rel-path> (startes with /)
+3. <timestamp-in-hex>
+
+
+As you can see the token is not bound to the user at all. The
+only limiting factor is the timestamp which is used to
+invalidate the URL after a given timeout (secdownload.timeout).
+
+.. Note::
+ Be sure to choose a another secret then used in the examples
+ as this is the only part of the token that is not known to
+ the user.
+
+
+
+If the user tries to fake the URL by choosing a random token
+status 403 'Forbidden' will be sent out.
+
+If the timeout is reached status 408 'Request Timeout' will be
+sent (this not really standard conforming but should do the
+trick).
+
+If token and timeout are valid the <rel-path> is taken and
+appended at the configured (secdownload.document-root) and
+passed to the normal internal file transfer functionality.
+This might lead to status 200 or 404.
+
+Example
+=======
+
+Application
+-----------
+::
+
+ <?php
+
+ $secret = "verysecret";
+ $uri_prefix = "/dl/";
+
+ # filename
+ $f = "/secret-file.txt";
+
+ # current timestamp
+ $t = time();
+
+ $t_hex = sprintf("%08x", $t);
+ $m = md5($secret.$f.$t_hex);
+
+ # generate link
+ printf('<a href="%s%s/%s%s">%s</a>',
+ $uri_prefix, $m, $t_hex, $f, $f);
+ ?>
+
+Webserver
+---------
+::
+
+ server.modules = ( ..., "mod_secdownload", ... )
+
+ secdownload.secret = "verysecret"
+ secdownload.document-root = "/home/www/servers/download-area/"
+ secdownload.uri-prefix = "/dl/"
+
diff --git a/doc/security.txt b/doc/security.txt
new file mode 100644
index 00000000..d4e9147c
--- /dev/null
+++ b/doc/security.txt
@@ -0,0 +1,60 @@
+=================
+Security Features
+=================
+
+------------
+Module: core
+------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/29 09:44:53 $
+:Revision: $Revision: 1.2 $
+
+:abstract:
+ lighttpd was developed with security in mind ...
+
+.. meta::
+ :keywords: lighttpd, security
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+Limiting POST requests
+----------------------
+
+
+
+::
+
+ server.max-request-size = <kbyte>
+
+System Security
+---------------
+
+Running daemons as root will full privileges is a bad idea in general.
+lighttpd runs best without any extra privileges and runs perfectly in chroot.
+
+Change Root
+```````````
+
+server.chroot = "..."
+
+Drop root-privileges
+````````````````````
+
+server.username = "..."
+server.groupname = "..."
+
+FastCGI
+```````
+
+fastcgi + chroot
+
+Permissions
+```````````
+
+::
+
+ $ useradd wwwrun ...
diff --git a/doc/setenv.txt b/doc/setenv.txt
new file mode 100644
index 00000000..b04b0838
--- /dev/null
+++ b/doc/setenv.txt
@@ -0,0 +1,37 @@
+===========================
+Conditional Request Headers
+===========================
+
+------------------
+Module: mod_setenv
+------------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/11/03 22:26:05 $
+:Revision: $Revision: 1.2 $
+
+:abstract:
+ mod_setenv is used to add request
+
+.. meta::
+ :keywords: lighttpd, skeleton
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+...
+
+Options
+=======
+
+setenv.add-environment
+ adds a value to the process environment that is passed to the external applications
+
+
+setenv.add-response-header
+ add a header to the HTTP-response sent to the client
+
+setenv.add-request-header
+ add a header to the HTTP-request that was received by the client
diff --git a/doc/simple-vhost.txt b/doc/simple-vhost.txt
new file mode 100644
index 00000000..b679296e
--- /dev/null
+++ b/doc/simple-vhost.txt
@@ -0,0 +1,73 @@
+======================
+Simple Virtual-Hosting
+======================
+
+------------------------
+Module: mod_simple_vhost
+------------------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/29 09:43:49 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ virtual hosting
+
+.. meta::
+ :keywords: lighttpd, virtual hosting
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+Simple assumption:
+
+Every virtual host is in a direction below a base directory in a path that
+is the same as the name of the vhost. Below this vhost-path might be a
+extra directory which is the document-root of the vhost.
+
+The document-root for each vhost is build from three values:
+
+- server-root
+- hostname
+- document-root
+
+Either the absolute documentroot is build by ::
+
+ server-root + hostname + document-root
+
+or if this path does not exist by ::
+
+ server-root + default-host + document-root
+
+A small example should make this thinking clean: ::
+
+ /var/www/
+ /var/www/logs/
+ /var/www/servers/
+ /var/www/servers/www.example.org/
+ /var/www/servers/www.example.org/lib/
+ /var/www/servers/www.example.org/pages/
+ /var/www/servers/mail.example.org/
+ /var/www/servers/mail.example.org/lib/
+ /var/www/servers/mail.example.org/pages/
+
+ simple-vhost.server-root = "/var/www/servers/"
+ simple-vhost.default-host = "www.example.org"
+ simple-vhost.document-root = "pages"
+
+You can use symbolic links to map several hostnames to the same directory.
+
+Options
+=======
+
+simple-vhost.server-root
+ root of the virtual hosting
+
+simple-vhost.default-host
+ use this hostname if the
+
+simple-vhost.document-root
+ path below the vhost-directory
+
diff --git a/doc/skeleton.txt b/doc/skeleton.txt
new file mode 100644
index 00000000..13c6881d
--- /dev/null
+++ b/doc/skeleton.txt
@@ -0,0 +1,29 @@
+===================
+headline
+===================
+
+--------------------
+Module: mod_skeleton
+--------------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/11/03 22:26:05 $
+:Revision: $Revision: 1.2 $
+
+:abstract:
+ a nice, short abstrace about the module
+
+.. meta::
+ :keywords: lighttpd, skeleton
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+...
+
+Options
+=======
+
+...
diff --git a/doc/spawn-php.sh b/doc/spawn-php.sh
new file mode 100755
index 00000000..73abf671
--- /dev/null
+++ b/doc/spawn-php.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+## ABSOLUTE path to the spawn-fcgi binary
+SPAWNFCGI="/home/weigon/projects/spawn-fcgi/src/spawn-fcgi"
+
+## ABSOLUTE path to the PHP binary
+FCGIPROGRAM="/usr/local/bin/php"
+
+## bind to tcp-port on localhost
+FCGIPORT="1026"
+
+## number of PHP childs to spawn
+PHP_FCGI_CHILDREN=10
+
+## number of request server by a single php-process until is will be restarted
+PHP_FCGI_MAX_REQUESTS=1000
+
+## IP adresses where PHP should access server connections from
+FCGI_WEB_SERVER_ADDRS="127.0.0.1,192.168.2.10"
+
+# allowed environment variables sperated by spaces
+ALLOWED_ENV="ORACLE_HOME PATH USER"
+
+## if this script is run as root switch to the following user
+USERID=wwwrun
+GROUPID=wwwrun
+
+
+################## no config below this line
+
+if test x$PHP_FCGI_CHILDREN = x; then
+ PHP_FCGI_CHILDREN=5
+fi
+
+export PHP_FCGI_MAX_REQUESTS
+export FCGI_WEB_SERVER_ADDRS
+
+ALLOWED_ENV="$ALLOWED_ENV PHP_FCGI_MAX_REQUESTS FCGI_WEB_SERVER_ADDRS"
+
+if test x$UID = x0; then
+ EX="$SPAWNFCGI -p $FCGIPORT -f $FCGIPROGRAM -u $USERID -g $GROUPID -C $PHP_FCGI_CHILDREN"
+else
+ EX="$SPAWNFCGI -p $FCGIPORT -f $FCGIPROGRAM -C $PHP_FCGI_CHILDREN"
+fi
+
+# copy the allowed environment variables
+E=
+
+for i in $ALLOWED_ENV; do
+ E="$E $i=${!i}"
+done
+
+# clean environment and set up a new one
+env - $E $EX
diff --git a/doc/ssi.txt b/doc/ssi.txt
new file mode 100644
index 00000000..c5791a24
--- /dev/null
+++ b/doc/ssi.txt
@@ -0,0 +1,76 @@
+====================
+Server-Side Includes
+====================
+
+---------------
+Module: mod_ssi
+---------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/29 09:44:53 $
+:Revision: $Revision: 1.2 $
+
+:abstract:
+ The module for server-side includes provides a compat layer for
+ NSCA/Apache SSI.
+
+.. meta::
+ :keywords: lighttpd, ssi, Server-Side Includes
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+Configuration
+-------------
+
+::
+
+ server.modules = ( ..., "mod_ssi", ... )
+ ssi.extension = ( ".shtml" )
+
+Supported Options
+-----------------
+
+- ``<!--#echo var="..." -->``
+- ``<!--#include (file="..."\|virtual="...") -->``
+- ``<!--#flastmod (file="..."\|virtual="...") -->``
+- ``<!--#fsize (file="..."\|virtual="...") -->``
+- ``<!--#config timefmt="..." sizefmt="(bytes|abbrev)" -->``
+- ``<!--#printenv -->``
+- ``<!--#set var="..." value="..." -->``
+- ``<!--#if expr="..." -->``
+- ``<!--#elif expr="..." -->``
+- ``<!--#else -->``
+- ``<!--#endif -->``
+
+Expression Handling
+-------------------
+
+Every ''expr'' is interpreted:
+
+- logical: AND, OR, !
+- compare: =, <, <=, >, =>, !=
+- precedence: (, )
+- quoted strings: 'string with a dollar: $FOO'
+- variable substitution: $REMOTE_ADDR
+- unquoted strings: string
+
+Flow Control
+------------
+
+if, elif, else and endif can be used the insert content only under special
+conditions.
+
+Unsupported Features
+--------------------
+
+The original SSI module from NCSA and Apache provided some more options
+which are not supported by this module for various reasons:
+
+- exec
+- nested virtual
+- config.errmsg
+- echo.encoding
+
diff --git a/doc/ssl.txt b/doc/ssl.txt
new file mode 100644
index 00000000..8225822e
--- /dev/null
+++ b/doc/ssl.txt
@@ -0,0 +1,58 @@
+===========
+Secure HTTP
+===========
+
+------------
+Module: core
+------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/29 09:44:53 $
+:Revision: $Revision: 1.2 $
+
+:abstract:
+ How to setup SSL in lighttpd
+
+.. meta::
+ :keywords: lighttpd, ssl
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+lighttpd support SSLv2 and SSLv3 if it compiled against openssl.
+
+Configuration
+-------------
+
+To enable SSL for the whole server you have to provide a valid
+certificate and have to enable the SSL engine.::
+
+ ssl.engine = "enable"
+ ssl.pemfile = "/path/to/server.pem"
+
+As SSL and named-based virtual hosting can not work together you
+have to use IP-based virtual hosting if you want to run multiple
+SSL-servers with one lighttpd: ::
+
+ SERVER["socket"] == "10.0.0.1:443" {
+ ssl.engine = "enable"
+ ssl.pemfile = "www.example.org.pem"
+ server.name = "www.example.org"
+
+ server.document-root = "/www/servers/www.example.org/pages/"
+ }
+
+
+
+
+Self-Signed Certificates
+------------------------
+
+A self-signed SSL cerifitcate can be generated with: ::
+
+ $ openssl req -new -x509 \
+ -keyout server.pem -out server.pem \
+ -days 365 -nodes
+
diff --git a/doc/state.dot b/doc/state.dot
new file mode 100644
index 00000000..551b2323
--- /dev/null
+++ b/doc/state.dot
@@ -0,0 +1,18 @@
+digraph state {
+ edge [color=green];
+ connect -> reqstart -> read -> reqend -> handlereq -> respstart -> write -> respend -> connect;
+ edge [color=grey];
+ reqend -> readpost -> handlereq [ label="POST" ];
+ edge [ color=blue]
+ respend -> reqstart [ label="keep-alive" ];
+ edge [ color=lightblue]
+ handlereq -> handlereq [ label="sub-request" ];
+ edge [style=dashed, color=red];
+ error -> close -> connect;
+ error -> connect;
+ handlereq -> error;
+ read -> error;
+ readpost -> error;
+ write -> error;
+ connect [shape=box];
+}
diff --git a/doc/state.txt b/doc/state.txt
new file mode 100644
index 00000000..d0768bc9
--- /dev/null
+++ b/doc/state.txt
@@ -0,0 +1,170 @@
+============================
+The State Engine of lighttpd
+============================
+
+------------
+Module: core
+------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/01 07:01:29 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ This is a short summary of the state-engine which is driving the lighttpd
+ webserver. It describes the basic concepts and the way the different parts
+ of the server are connected.
+
+.. meta::
+ :keywords: lighttpd, state-engine
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+States
+------
+
+The state-engine is currently made of 11 states which are walk-through on
+the way each connection. Some of them are specific for a special operation
+and some may never be hit at all.
+
+:connect:
+ waiting for a connection
+:reqstart:
+ init the read-idle timer
+:read:
+ read http-request-header from network
+:reqend:
+ parse request
+:readpost:
+ read http-request-content from network
+:handlereq:
+ handle the request internally (might result in sub-requests)
+:respstart:
+ prepare response header
+:write:
+ write response-header + content to network
+:respend:
+ cleanup environment, log request
+:error:
+ reset connection (incl. close())
+:close:
+ close connection (handle lingering close)
+
+.. image:: state.png
+
+A simple GET request (green path)
+---------------------------------
+
+The connection is idling in the 'connect' state waiting for a connection.
+As soon as the connection is set up we init the read-timer in 'reqstart'
+and start to read data from the network. As soon as we get the
+HTTP-request terminator (CRLFCRLF) we forward the header to the parser.
+
+The parsed request is handled by 'handlereq' and as soon as a decision out
+the request is made it is sent to 'respstart' to prepare the
+HTTP-response header. In the 'write' state the prepare content is sent out
+to the network. When everything is sent 'respend' is entered to log the
+request and cleanup the environment. After the close() call the connection
+is set back to the 'connect' state again.
+
+Keep-Alive (blue path)
+----------------------
+
+The Keep-Alive handling is implemented by going from the 'respend'
+directly to 'reqstart' without the close() and the accept() calls.
+
+POST requests (grey path)
+-------------------------
+
+As requests might contain a request-body the state 'readpost' entered as
+soon as the header is parsed and we know how much data we expect.
+
+Pipelining
+----------
+
+HTTP/1.1 supportes pipelining (sending multiple requests without waiting
+for the response of the first request). This is handled transparently by
+the 'read' state.
+
+Unexpected errors (red path)
+----------------------------
+
+For really hard errors we use the 'error' state which resets the
+connection and can be call from every state. It is only use if there is no
+other way to handle the issue (e.g. client-side close of the connection).
+If possible we should use http-status 500 ('internal server error') and
+log the issue in the errorlog.
+
+If we have to take care of some data which is coming in after we ran into
+the error condition the 'close' state is used the init a half-close and
+read all the delay packet from the network.
+
+Sub-Requests (lightblue)
+------------------------
+
+The FastCGI, CGI, ... intergration is done by introducing a loop in
+'handlereq' to handle all aspect which are neccesary to find out what has
+to be sent back to the client.
+
+Functions
+=========
+
+Important functions used by the state-engine
+
+:state-engine:
+
+- ``connection_state_machine()``
+
+:connect:
+
+- (nothing)
+
+:reqstart:
+
+- (nothing)
+
+:read:
+
+- ``connection_handle_read_state()``
+- ``connection_handle_read()``
+
+:reqend:
+
+- ``http_request_parse()``
+
+:readpost:
+
+- ``connection_handle_read_state()``
+- ``connection_handle_read()``
+
+:handlereq:
+
+- ``http_response_prepare()``
+
+:respstart:
+
+- ``connection_handle_write_prepare()``
+
+:write:
+
+- ``connection_handle_write()``
+
+:respend:
+
+- ``plugins_call_handle_request_done()``
+- ``plugins_call_handle_connection_close()``
+- ``connection_close()`` (if not keep-alive)
+- ``connection_reset()``
+
+:error:
+
+- ``plugins_call_handle_request_done()``
+- ``plugins_call_handle_connection_close()``
+- ``connection_reset()``
+
+:close:
+
+- ``connection_close()``
diff --git a/doc/status.txt b/doc/status.txt
new file mode 100644
index 00000000..3e0acab4
--- /dev/null
+++ b/doc/status.txt
@@ -0,0 +1,35 @@
+=============
+Server Status
+=============
+
+------------------
+Module: mod_status
+------------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/11/03 22:26:05 $
+:Revision: $Revision: 1.2 $
+
+:abstract:
+ mod_status displays server-status and server-config
+
+.. meta::
+ :keywords: lighttpd, server status
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+The server status module generates the ...
+
+Options
+=======
+
+status.status-url
+
+ Default: unset
+
+status.config-url
+
+ Default: unset
diff --git a/doc/sysconfig.lighttpd b/doc/sysconfig.lighttpd
new file mode 100644
index 00000000..c8154c96
--- /dev/null
+++ b/doc/sysconfig.lighttpd
@@ -0,0 +1 @@
+LIGHTTPD_CONF_PATH=/etc/lighttpd/lighttpd.conf
diff --git a/doc/traffic-shaping.txt b/doc/traffic-shaping.txt
new file mode 100644
index 00000000..7d16638d
--- /dev/null
+++ b/doc/traffic-shaping.txt
@@ -0,0 +1,55 @@
+===============
+Traffic Shaping
+===============
+
+------------
+Module: core
+------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/11/03 22:26:05 $
+:Revision: $Revision: 1.2 $
+
+:abstract:
+ limiting the bandwith usage
+
+.. meta::
+ :keywords: lighttpd, bandwidth limit, traffic shaping
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+Starting with 1.3.8 lighttpd supports limiting the bandwith for a single connection
+or config-context like virtual-host or URL.
+
+Options
+=======
+
+:connection.kbytes-per-second:
+ limit the through-put for each single connection to the given
+ limit in kbyte/s
+
+ default: 0 (no limit)
+
+:server.kbytes-per-second:
+ limit the through-put for all connections to the given limit
+ in kbyte/s
+
+ if you want to specify a limit for a special virtual server
+ use: ::
+
+ $HTTP["host"] == "www.example.org" {
+ server.kbytes-per-second = 128
+ }
+
+ which will overwrite the default for this host.
+
+ default: 0 (no limit)
+
+Additional Notes
+================
+
+Keep in mind that a limit below 32kb/s might actually limit the traffic to 32kb/s. This
+is caused by by the size of the TCP-sendbuffer.
diff --git a/doc/userdir.txt b/doc/userdir.txt
new file mode 100644
index 00000000..e0983fb3
--- /dev/null
+++ b/doc/userdir.txt
@@ -0,0 +1,54 @@
+=======
+userdir
+=======
+
+-------------------
+Module: mod_userdir
+-------------------
+
+:Author: Jan Kneschke
+:Date: $Date: 2004/08/29 09:43:49 $
+:Revision: $Revision: 1.1 $
+
+:abstract:
+ The userdir module ...
+
+.. meta::
+ :keywords: lighttpd, userdir
+
+.. contents:: Table of Contents
+
+Description
+===========
+
+The userdir module provides a simple way to link user-based directories into the global namespace of the webserver.
+
+Requests in the form ``/~user/page.html`` are rewritten to take the file ``page.html`` from the home-directory of the user.
+If userdir.path is set, the path will be appended at the home-directory.
+
+To control which users should be able to use this feature you can set a include- or a exclude list for username.
+
+Options
+=======
+
+userdir.path
+ usually it should set the "public_html" to take ~/public_html/ as the document-root
+
+ Default: empty (document-root is the home-directory)
+ Example: ::
+
+ userdir.path = "public_html"
+
+userdir.exclude-user
+ list of usernames which should not be able to use this feature
+
+ Default: empty (all users may use it)
+ Example: ::
+
+ userdir.exclude-user = ( "root", "postmaster" )
+
+
+userdir.include-user
+ if set, only users from this list may use the feature
+
+ Default: empty (all user may use it)
diff --git a/lighttpd.spec.in b/lighttpd.spec.in
new file mode 100644
index 00000000..aaed41a8
--- /dev/null
+++ b/lighttpd.spec.in
@@ -0,0 +1,84 @@
+Summary: A fast webserver with minimal memory-footprint (lighttpd)
+Name: lighttpd
+Version: @VERSION@
+Release: 1
+Source: http://jan.kneschke.de/projects/lighttpd/download/lighttpd-%version.tar.gz
+Packager: Jan Kneschke <jan@kneschke.de>
+License: BSD
+Group: Networking/Daemons
+URL: http://jan.kneschke.de/projects/lighttpd/
+Requires: pcre >= 3.1 zlib
+BuildPrereq: libtool zlib-devel
+BuildRoot: %{_tmppath}/%{name}-root
+
+
+%description
+lighttpd is intented to be a frontend for ad-servers which have to deliver
+small files concurrently to many connections.
+
+Available rpmbuild rebuild options :
+--with : ssl mysql
+
+%prep
+
+%setup -q
+
+%build
+rm -rf %{buildroot}
+%configure \
+ %{?_with_mysql: --with-mysql} \
+ %{?_with_ssl: --with-openssl}
+make
+
+%install
+
+%makeinstall
+
+mkdir -p %{buildroot}%{_sysconfdir}/{init.d,sysconfig}
+if test -f /etc/redhat-release -o -f /etc/fedora-release; then
+ install -m 755 doc/rc.lighttpd.redhat %{buildroot}%{_sysconfdir}/init.d/lighttpd
+else
+ install -m 755 doc/rc.lighttpd %{buildroot}%{_sysconfdir}/init.d/lighttpd
+fi
+install -m 644 doc/sysconfig.lighttpd %{buildroot}%{_sysconfdir}/sysconfig/lighttpd
+
+%clean
+rm -rf %{buildroot}
+
+%post
+
+if test "$1" = "0"; then
+ # real install, not upgrade
+ /sbin/chkconfig --add lighttpd
+fi
+
+%preun
+if test "$1" = "0"; then
+ # real uninstall, not upgrade
+ %{_sysconfdir}/init.d/lighttpd stop
+ /sbin/chkconfig --del lighttpd
+fi
+
+%files
+%defattr(-,root,root)
+%doc doc/lighttpd.conf doc/lighttpd.user README INSTALL ChangeLog COPYING AUTHORS
+%doc doc/*.txt
+%config(noreplace) %attr(0755,root,root) %{_sysconfdir}/init.d/lighttpd
+%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/sysconfig/lighttpd
+%{_mandir}/*
+%{_libdir}/*
+%{_sbindir}/*
+%{_bindir}/*
+
+%changelog
+* Thu Sep 30 2004 12:41 <jan@kneschke.de> 1.3.1
+- upgraded to 1.3.1
+
+* Tue Jun 29 2004 17:26 <jan@kneschke.de> 1.2.3
+- rpmlint'ed the package
+- added URL
+- added (noreplace) to start-script
+- change group to Networking/Daemon (like apache)
+
+* Sun Feb 23 2003 15:04 <jan@kneschke.de>
+- initial version
diff --git a/openwrt/.cvsignore b/openwrt/.cvsignore
new file mode 100644
index 00000000..3dda7298
--- /dev/null
+++ b/openwrt/.cvsignore
@@ -0,0 +1,2 @@
+Makefile.in
+Makefile
diff --git a/openwrt/Makefile.am b/openwrt/Makefile.am
new file mode 100644
index 00000000..6bb5b8cd
--- /dev/null
+++ b/openwrt/Makefile.am
@@ -0,0 +1 @@
+EXTRA_DIST=conffiles control lighttpd.conf S51lighttpd lighttpd.mk
diff --git a/openwrt/S51lighttpd b/openwrt/S51lighttpd
new file mode 100755
index 00000000..c9aa1f9f
--- /dev/null
+++ b/openwrt/S51lighttpd
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+/usr/sbin/lighttpd -f /etc/lighttpd.conf
+exit $?
diff --git a/openwrt/conffiles b/openwrt/conffiles
new file mode 100644
index 00000000..e90f8e19
--- /dev/null
+++ b/openwrt/conffiles
@@ -0,0 +1 @@
+/etc/lighttpd.conf
diff --git a/openwrt/control b/openwrt/control
new file mode 100644
index 00000000..3c085e49
--- /dev/null
+++ b/openwrt/control
@@ -0,0 +1,9 @@
+Package: lighttpd
+Version: 1.3.11
+Architecture: mipsel
+Maintainer: Jan Kneschke <jan@kneschke.de>
+Source: http://jan.kneschke.de/projects/lighttpd/download/lighttpd-1.3.11.tar.gz
+Section: net
+Priority: optional
+Depends:
+Description: Flexible and Lightweight Webserver
diff --git a/openwrt/control.in b/openwrt/control.in
new file mode 100644
index 00000000..3a4c5715
--- /dev/null
+++ b/openwrt/control.in
@@ -0,0 +1,9 @@
+Package: lighttpd
+Version: @VERSION@
+Architecture: mipsel
+Maintainer: Jan Kneschke <jan@kneschke.de>
+Source: http://jan.kneschke.de/projects/lighttpd/download/lighttpd-@VERSION@.tar.gz
+Section: net
+Priority: optional
+Depends:
+Description: Flexible and Lightweight Webserver
diff --git a/openwrt/lighttpd.conf b/openwrt/lighttpd.conf
new file mode 100644
index 00000000..42d2f769
--- /dev/null
+++ b/openwrt/lighttpd.conf
@@ -0,0 +1,231 @@
+# lighttpd configuration file
+#
+# use a it as base for lighttpd 1.0.0 and above
+#
+# $Id: lighttpd.conf,v 1.6 2004/08/29 09:44:53 weigon Exp $
+
+############ Options you really have to take care of ####################
+
+## modules to load
+# at least mod_access and mod_accesslog should be loaded
+# all other module should only be loaded if really neccesary
+# - saves some time
+# - saves memory
+server.modules = (
+# "mod_rewrite",
+# "mod_redirect",
+ "mod_access",
+# "mod_auth",
+# "mod_status",
+# "mod_fastcgi",
+# "mod_simple_vhost",
+# "mod_evhost",
+# "mod_cgi",
+# "mod_compress",
+# "mod_ssi",
+# "mod_usertrack",
+# "mod_rrdtool",
+# "mod_accesslog"
+)
+
+## a static document-root, for virtual-hosting take look at the
+## server.virtual-* options
+server.document-root = "/www/"
+
+## where to send error-messages to
+# server.errorlog = ""
+
+# files to check for if .../ is requested
+server.indexfiles = ( "index.php", "index.html",
+ "index.htm", "default.htm" )
+
+# mimetype mapping
+mimetype.assign = (
+ ".pdf" => "application/pdf",
+ ".sig" => "application/pgp-signature",
+ ".spl" => "application/futuresplash",
+ ".class" => "application/octet-stream",
+ ".ps" => "application/postscript",
+ ".torrent" => "application/x-bittorrent",
+ ".dvi" => "application/x-dvi",
+ ".gz" => "application/x-gzip",
+ ".pac" => "application/x-ns-proxy-autoconfig",
+ ".swf" => "application/x-shockwave-flash",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".tar" => "application/x-tar",
+ ".zip" => "application/zip",
+ ".mp3" => "audio/mpeg",
+ ".m3u" => "audio/x-mpegurl",
+ ".wma" => "audio/x-ms-wma",
+ ".wax" => "audio/x-ms-wax",
+ ".ogg" => "audio/x-wav",
+ ".wav" => "audio/x-wav",
+ ".gif" => "image/gif",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".png" => "image/png",
+ ".xbm" => "image/x-xbitmap",
+ ".xpm" => "image/x-xpixmap",
+ ".xwd" => "image/x-xwindowdump",
+ ".css" => "text/css",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".js" => "text/javascript",
+ ".asc" => "text/plain",
+ ".c" => "text/plain",
+ ".conf" => "text/plain",
+ ".text" => "text/plain",
+ ".txt" => "text/plain",
+ ".dtd" => "text/xml",
+ ".xml" => "text/xml",
+ ".mpeg" => "video/mpeg",
+ ".mpg" => "video/mpeg",
+ ".mov" => "video/quicktime",
+ ".qt" => "video/quicktime",
+ ".avi" => "video/x-msvideo",
+ ".asf" => "video/x-ms-asf",
+ ".asx" => "video/x-ms-asf",
+ ".wmv" => "video/x-ms-wmv"
+ )
+
+# Use the "Content-Type" extended attribute to obtain mime type if possible
+# mimetypes.use-xattr = "enable"
+
+#### accesslog module
+# accesslog.filename = "/www/logs/access.log"
+
+## deny access the file-extensions
+#
+# ~ is for backupfiles from vi, emacs, joe, ...
+# .inc is often used for code includes which should in general not be part
+# of the document-root
+url.access-deny = ( "~", ".inc" )
+
+
+
+######### Options that are good to be but not neccesary to be changed #######
+
+## bind to port (default: 80)
+#server.port = 81
+
+## bind to localhost (default: all interfaces)
+#server.bind = "grisu.home.kneschke.de"
+
+## error-handler for status 404
+#server.error-handler-404 = "/error-handler.html"
+#server.error-handler-404 = "/error-handler.php"
+
+
+###### virtual hosts
+##
+## If you want name-based virtual hosting add the next three settings and load
+## mod_simple_vhost
+##
+## document-root =
+## virtual-server-root + virtual-server-default-host + virtual-server-docroot or
+## virtual-server-root + http-host + virtual-server-docroot
+##
+#simple-vhost.server-root = "/home/weigon/wwwroot/servers/"
+#simple-vhost.default-host = "grisu.home.kneschke.de"
+#simple-vhost.document-root = "/pages/"
+
+
+##
+## Format: <errorfile-prefix><status>.html
+## -> ..../status-404.html for 'File not found'
+#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-"
+
+## virtual directory listings
+#server.dir-listing = "enable"
+
+## send unhandled HTTP-header headers to error-log
+#debug.dump-unknown-headers = "enable"
+
+### only root can use these options
+#
+# chroot() to directory (default: no chroot() )
+#server.chroot = "/"
+
+## change uid to <uid> (default: don't care)
+#server.username = "wwwrun"
+
+## change uid to <uid> (default: don't care)
+#server.groupname = "wwwrun"
+
+#### compress module
+#compress.cache-dir = "/tmp/lighttpd/cache/compress/"
+#compress.filetype = ("text/plain", "text/html")
+
+#### fastcgi module
+## read fastcgi.txt for more info
+#fastcgi.server = ( ".php" =>
+# ( "grisu" =>
+# (
+# "host" => "192.168.2.10",
+# "port" => 1026
+# )
+# )
+# )
+
+#### CGI module
+#cgi.assign = ( ".pl" => "/usr/bin/perl",
+# ".cgi" => "/usr/bin/perl" )
+#
+
+#### SSL engine
+#ssl.engine = "enable"
+#ssl.pemfile = "server.pem"
+
+#### status module
+# status.status-url = "/server-status"
+# status.config-url = "/server-config"
+
+#### auth module
+## read authentification.txt for more info
+# auth.backend = "plain"
+# auth.backend.plain.userfile = "lighttpd.user"
+# auth.backend.plain.groupfile = "lighttpd.group"
+
+# auth.backend.ldap.hostname = "localhost"
+# auth.backend.ldap.base-dn = "dc=my-domain,dc=com"
+# auth.backend.ldap.filter = "(uid=$)"
+
+# auth.require = ( "/server-status" =>
+# (
+# "method" => "digest",
+# "realm" => "download archiv",
+# "require" => "group=www|user=jan|host=192.168.2.10"
+# ),
+# "/server-info" =>
+# (
+# "method" => "digest",
+# "realm" => "download archiv",
+# "require" => "group=www|user=jan|host=192.168.2.10"
+# )
+# )
+
+#### url handling modules (rewrite, redirect, access)
+# url.rewrite = ( "^/$" => "/server-status" )
+# url.redirect = ( "^/wishlist/(.+)" => "http://www.123.org/$1" )
+
+#
+# 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/storage/dev/www/%3/htdocs/"
+
+#### expire module
+# expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes")
+
+#### ssi
+# ssi.extension = ( ".shtml" )
+
+#### rrdtool
+# rrdtool.binary = "/usr/bin/rrdtool"
+# rrdtool.db-name = "/var/www/lighttpd.rrd"
diff --git a/openwrt/lighttpd.mk b/openwrt/lighttpd.mk
new file mode 100644
index 00000000..e70972c1
--- /dev/null
+++ b/openwrt/lighttpd.mk
@@ -0,0 +1,72 @@
+######################################################
+#
+# An example makefile to fetch a package from sources
+# then fetch the ipkg updates required to the base package
+# extract the archives into the build tree
+# and then build the source
+#
+######################################################
+
+
+# For this example we'll use a fairly simple package that compiles easily
+# and has sources available for download at sourceforge
+LIGHTTPD=lighttpd-1.3.11
+LIGHTTPD_TARGET=.built
+LIGHTTPD_DIR=$(BUILD_DIR)/$(LIGHTTPD)
+LIGHTTPD_IPK=$(BUILD_DIR)/$(LIGHTTPD)_mipsel.ipk
+LIGHTTPD_IPK_DIR=$(BUILD_DIR)/$(LIGHTTPD)-ipk
+
+LIGHTTPD_SITE=http://jan.kneschke.de/projects/lighttpd/download/
+LIGHTTPD_SOURCE=$(LIGHTTPD).tar.gz
+
+
+# We need to download sources if we dont have them
+$(DL_DIR)/$(LIGHTTPD_SOURCE) :
+ $(WGET) -P $(DL_DIR) $(LIGHTTPD_SITE)/$(LIGHTTPD_SOURCE)
+
+# if we have the sources, they do no good unless they are unpacked
+$(LIGHTTPD_DIR)/.unpacked: $(DL_DIR)/$(LIGHTTPD_SOURCE)
+ gzip -cd $(DL_DIR)/$(LIGHTTPD_SOURCE) | tar -C $(BUILD_DIR) -xvf -
+ touch $(LIGHTTPD_DIR)/.unpacked
+
+# if we have the sources unpacked, we need to configure them
+$(LIGHTTPD_DIR)/.configured: $(LIGHTTPD_DIR)/.unpacked
+ (cd $(LIGHTTPD_DIR); rm -rf config.cache; \
+ $(TARGET_CONFIGURE_OPTS) \
+ CFLAGS="$(TARGET_CFLAGS)" \
+ LD=$(TARGET_CROSS)ld \
+ ./configure \
+ --target=$(GNU_TARGET_NAME) \
+ --host=$(GNU_TARGET_NAME) \
+ --build=$(GNU_HOST_NAME) \
+ --prefix=/usr \
+ --exec-prefix=/usr \
+ --bindir=/usr/bin \
+ --sbindir=/usr/sbin \
+ --disable-zlib \
+ --sysconfdir=/etc \
+ --program-transform-name="s,y,y," \
+ );
+ touch $(LIGHTTPD_DIR)/.configured
+
+
+# now that we have it all in place, just build it
+$(LIGHTTPD_DIR)/$(LIGHTTPD_TARGET): $(LIGHTTPD_DIR)/.configured
+ cd $(LIGHTTPD_DIR) && $(MAKE) CC=$(TARGET_CC) DESTDIR="$(LIGHTTPD_IPK_DIR)" install
+ $(STAGING_DIR)/bin/sstrip $(LIGHTTPD_IPK_DIR)/usr/sbin/lighttpd
+ touch $(LIGHTTPD_DIR)/$(LIGHTTPD_TARGET)
+
+$(LIGHTTPD_IPK): uclibc $(LIGHTTPD_DIR)/$(LIGHTTPD_TARGET)
+ mkdir -p $(LIGHTTPD_IPK_DIR)/CONTROL
+ mkdir -p $(LIGHTTPD_IPK_DIR)/etc/init.d
+ cp $(LIGHTTPD_DIR)/openwrt/conffiles $(LIGHTTPD_IPK_DIR)/CONTROL
+ cp $(LIGHTTPD_DIR)/openwrt/control $(LIGHTTPD_IPK_DIR)/CONTROL
+ cp $(LIGHTTPD_DIR)/openwrt/S51lighttpd $(LIGHTTPD_IPK_DIR)/etc/init.d/
+ cp $(LIGHTTPD_DIR)/openwrt/lighttpd.conf $(LIGHTTPD_IPK_DIR)/etc/
+
+ rm -f $(LIGHTTPD_IPK_DIR)/usr/lib/*.a
+ rm -f $(LIGHTTPD_IPK_DIR)/usr/lib/*.la
+
+ cd $(BUILD_DIR); $(IPKG_BUILD) $(LIGHTTPD_IPK_DIR)
+
+lighttpd-ipk: $(LIGHTTPD_IPK)
diff --git a/openwrt/lighttpd.mk.in b/openwrt/lighttpd.mk.in
new file mode 100644
index 00000000..9ee61705
--- /dev/null
+++ b/openwrt/lighttpd.mk.in
@@ -0,0 +1,72 @@
+######################################################
+#
+# An example makefile to fetch a package from sources
+# then fetch the ipkg updates required to the base package
+# extract the archives into the build tree
+# and then build the source
+#
+######################################################
+
+
+# For this example we'll use a fairly simple package that compiles easily
+# and has sources available for download at sourceforge
+LIGHTTPD=lighttpd-@VERSION@
+LIGHTTPD_TARGET=.built
+LIGHTTPD_DIR=$(BUILD_DIR)/$(LIGHTTPD)
+LIGHTTPD_IPK=$(BUILD_DIR)/$(LIGHTTPD)_mipsel.ipk
+LIGHTTPD_IPK_DIR=$(BUILD_DIR)/$(LIGHTTPD)-ipk
+
+LIGHTTPD_SITE=http://jan.kneschke.de/projects/lighttpd/download/
+LIGHTTPD_SOURCE=$(LIGHTTPD).tar.gz
+
+
+# We need to download sources if we dont have them
+$(DL_DIR)/$(LIGHTTPD_SOURCE) :
+ $(WGET) -P $(DL_DIR) $(LIGHTTPD_SITE)/$(LIGHTTPD_SOURCE)
+
+# if we have the sources, they do no good unless they are unpacked
+$(LIGHTTPD_DIR)/.unpacked: $(DL_DIR)/$(LIGHTTPD_SOURCE)
+ gzip -cd $(DL_DIR)/$(LIGHTTPD_SOURCE) | tar -C $(BUILD_DIR) -xvf -
+ touch $(LIGHTTPD_DIR)/.unpacked
+
+# if we have the sources unpacked, we need to configure them
+$(LIGHTTPD_DIR)/.configured: $(LIGHTTPD_DIR)/.unpacked
+ (cd $(LIGHTTPD_DIR); rm -rf config.cache; \
+ $(TARGET_CONFIGURE_OPTS) \
+ CFLAGS="$(TARGET_CFLAGS)" \
+ LD=$(TARGET_CROSS)ld \
+ ./configure \
+ --target=$(GNU_TARGET_NAME) \
+ --host=$(GNU_TARGET_NAME) \
+ --build=$(GNU_HOST_NAME) \
+ --prefix=/usr \
+ --exec-prefix=/usr \
+ --bindir=/usr/bin \
+ --sbindir=/usr/sbin \
+ --disable-zlib \
+ --sysconfdir=/etc \
+ --program-transform-name="s,y,y," \
+ );
+ touch $(LIGHTTPD_DIR)/.configured
+
+
+# now that we have it all in place, just build it
+$(LIGHTTPD_DIR)/$(LIGHTTPD_TARGET): $(LIGHTTPD_DIR)/.configured
+ cd $(LIGHTTPD_DIR) && $(MAKE) CC=$(TARGET_CC) DESTDIR="$(LIGHTTPD_IPK_DIR)" install
+ $(STAGING_DIR)/bin/sstrip $(LIGHTTPD_IPK_DIR)/usr/sbin/lighttpd
+ touch $(LIGHTTPD_DIR)/$(LIGHTTPD_TARGET)
+
+$(LIGHTTPD_IPK): uclibc $(LIGHTTPD_DIR)/$(LIGHTTPD_TARGET)
+ mkdir -p $(LIGHTTPD_IPK_DIR)/CONTROL
+ mkdir -p $(LIGHTTPD_IPK_DIR)/etc/init.d
+ cp $(LIGHTTPD_DIR)/openwrt/conffiles $(LIGHTTPD_IPK_DIR)/CONTROL
+ cp $(LIGHTTPD_DIR)/openwrt/control $(LIGHTTPD_IPK_DIR)/CONTROL
+ cp $(LIGHTTPD_DIR)/openwrt/S51lighttpd $(LIGHTTPD_IPK_DIR)/etc/init.d/
+ cp $(LIGHTTPD_DIR)/openwrt/lighttpd.conf $(LIGHTTPD_IPK_DIR)/etc/
+
+ rm -f $(LIGHTTPD_IPK_DIR)/usr/lib/*.a
+ rm -f $(LIGHTTPD_IPK_DIR)/usr/lib/*.la
+
+ cd $(BUILD_DIR); $(IPKG_BUILD) $(LIGHTTPD_IPK_DIR)
+
+lighttpd-ipk: $(LIGHTTPD_IPK)
diff --git a/src/.cvsignore b/src/.cvsignore
new file mode 100644
index 00000000..82d670f2
--- /dev/null
+++ b/src/.cvsignore
@@ -0,0 +1,11 @@
+lemon
+lighttpd
+Makefile.in
+Makefile
+spawn-fcgi
+chunk
+.deps
+.libs
+array
+mod_ssi_exprparser.c
+configparser.c
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..c4d275fc
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,221 @@
+noinst_PROGRAMS=array chunk 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
+
+config.c: configparser.h
+mod_ssi_expr.c: mod_ssi_exprparser.h
+
+common_src=buffer.c log.c \
+ keyvalue.c response.c request.c chunk.c \
+ http_chunk.c stream.c fdevent.c connections.c \
+ file_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 \
+ network_write.c network_linux_sendfile.c \
+ network_freebsd_sendfile.c network_writev.c \
+ network_solaris_sendfilev.c network_openssl.c \
+ data_config.c bitset.c configparser.c config.c \
+ inet_ntop_cache.c network.c
+
+src = server.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)
+liblightcomp_la_LDFLAGS = -avoid-version -no-undefined
+liblightcomp_la_LIBADD = $(PCRE_LIB) $(SSL_LIB)
+common_libadd = liblightcomp.la
+else
+src += $(common_src)
+common_libadd =
+endif
+
+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)
+
+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_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 crc32.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 md5_global.h http_auth.h stream.h \
+ fdevent.h connections.h base.h file_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
+ # license.h
+
+#hdr += chat.h chat_misc.h chat_endec.h chat_user.h \
+# chat_channel.h
+
+DEFS= @DEFS@ -DLIBRARY_DIR="\"$(libdir)\""
+
+lighttpd_SOURCES = $(src)
+lighttpd_LDADD = $(PCRE_LIB) $(DL_LIB) $(SENDFILE_LIB) $(ATTR_LIB) $(common_libadd) $(SSL_LIB)
+lighttpd_LDFLAGS = -export-dynamic
+lighttpd_CCPFLAGS = $(MYSQL_INCLUDES)
+
+array_SOURCES = array.c buffer.c data_string.c data_count.c
+array_CPPFLAGS= -DDEBUG_ARRAY
+
+chunk_SOURCES = buffer.c chunk.c
+chunk_CPPFLAGS= -DDEBUG_CHUNK
+
+#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/array.c b/src/array.c
new file mode 100644
index 00000000..a57abb36
--- /dev/null
+++ b/src/array.c
@@ -0,0 +1,241 @@
+#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;
+}
+
+void array_free(array *a) {
+ size_t i;
+ if (!a) return;
+
+ 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;
+
+ for (i = 0; i < a->used; i++) {
+ a->data[i]->reset(a->data[i]);
+ }
+
+ a->used = 0;
+}
+
+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;
+}
+
+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) {
+ buffer_copy_long(str->key, a->unique_ndx++);
+ }
+
+ /* 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;
+}
+
+int array_print(array *a) {
+ size_t i;
+
+ for (i = 0; i < a->used; i++) {
+ fprintf(stderr, "%d: ", i);
+ a->data[i]->print(a->data[i]);
+ fprintf(stderr, "\n");
+ }
+
+ 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);
+
+ 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 00000000..8ac2bdcc
--- /dev/null
+++ b/src/array.h
@@ -0,0 +1,120 @@
+#ifndef ARRAY_H
+#define ARRAY_H
+
+#include <stdlib.h>
+#include "config.h"
+#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; \
+ 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)(struct data_unset *p)
+
+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;
+} 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;
+
+/* $HTTP["host"] == "incremental.home.kneschke.de" { ... }
+ * comp_key cond string/regex
+ */
+
+typedef struct {
+ DATA_UNSET;
+
+ array *value;
+
+ buffer *comp_key;
+
+ config_cond_t cond;
+
+ union {
+ buffer *string;
+#ifdef HAVE_PCRE_H
+ pcre *regex;
+#endif
+ } match;
+} data_config;
+
+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;
+ int usage;
+
+ time_t disable_ts;
+
+} data_fastcgi;
+
+data_fastcgi *data_fastcgi_init(void);
+
+array *array_init(void);
+void array_free(array *a);
+void array_reset(array *a);
+int array_insert_unique(array *a, data_unset *str);
+int array_print(array *a);
+data_unset *array_get_unused_element(array *a, data_type_t t);
+data_unset *array_get_element(array *a, const char *key);
+int array_strcasecmp(const char *a, size_t a_len, const char *b, size_t b_len);
+
+#endif
diff --git a/src/base.h b/src/base.h
new file mode 100644
index 00000000..a9b483d7
--- /dev/null
+++ b/src/base.h
@@ -0,0 +1,522 @@
+#ifndef _BASE_H_
+#define _BASE_H_
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include "config.h"
+
+#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"
+
+
+#if defined HAVE_LIBSSL && defined HAVE_OPENSSL_SSL_H
+# define USE_OPENSSL
+# include <openssl/ssl.h>
+#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;
+ 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 */
+ buffer *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 *name;
+ buffer *etag;
+
+ struct stat st;
+
+ int fd;
+ int fde_ndx;
+
+ char *mmap_p;
+ size_t mmap_length;
+ off_t mmap_offset;
+
+ size_t in_use;
+ size_t is_dirty;
+
+ time_t stat_ts;
+ buffer *content_type;
+} file_cache_entry;
+
+typedef struct {
+ buffer *scheme;
+ buffer *authority;
+ buffer *path;
+ buffer *path_raw;
+ buffer *query;
+} request_uri;
+
+typedef struct {
+ buffer *path;
+
+ buffer *doc_root; /* path = doc_root + rel_path */
+ buffer *rel_path;
+
+ buffer *etag;
+} physical;
+
+typedef struct {
+ file_cache_entry **ptr;
+
+ size_t size;
+ size_t used;
+
+ buffer *dir_name;
+} file_cache;
+
+typedef struct {
+ array *indexfiles;
+ array *mimetypes;
+
+ /* virtual-servers */
+ buffer *document_root;
+ buffer *server_name;
+ buffer *error_handler;
+ buffer *server_tag;
+
+ unsigned short dir_listing;
+ 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;
+
+ /* debug */
+
+ unsigned short log_file_not_found;
+ unsigned short log_request_header;
+ unsigned short log_request_handling;
+ unsigned short log_response_header;
+
+
+ /* server wide */
+ buffer *ssl_pemfile;
+ unsigned short use_ipv6;
+ unsigned short is_ssl;
+ unsigned short allow_http11;
+ 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;
+
+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 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 */
+
+ 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;
+ chunkqueue *read_queue;
+
+ 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;
+
+ /* 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;
+
+ file_cache_entry *fce; /* filecache entry for the selected file */
+
+ void **plugin_ctx; /* plugin connection specific config */
+
+ specific_config conf; /* global connection specific config */
+
+ 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 *error_logfile;
+ unsigned short dont_daemonize;
+ buffer *changeroot;
+ buffer *username;
+ buffer *groupname;
+
+ buffer *errorfile_prefix;
+ buffer *license;
+ buffer *pid_file;
+
+ buffer *event_handler;
+
+ array *modules;
+
+ unsigned short max_worker;
+ unsigned short max_fds;
+
+ unsigned short log_request_header_on_error;
+ unsigned short log_state_handling;
+} server_config;
+
+typedef struct {
+ sock_addr addr;
+ int fd;
+ int fde_ndx;
+
+ buffer *ssl_pemfile;
+ unsigned short use_ipv6;
+ unsigned short is_ssl;
+ unsigned short max_request_size;
+
+ 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_socket_array srv_sockets;
+
+ int log_error_fd;
+ int log_using_syslog;
+ fdevents *ev, *ev_ins;
+
+ buffer_plugin plugins;
+ void *plugin_slots;
+
+ 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;
+
+ /* buffers */
+ buffer *parse_full_path;
+ buffer *response_header;
+ buffer *error_log;
+ buffer *response_range;
+ buffer *tmp_buf;
+
+ buffer *tmp_chunk_len;
+
+ buffer *range_buf;
+
+ buffer *empty_string; /* is necessary for cond_match */
+
+ /* 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;
+
+ file_cache *file_cache;
+ buffer *file_cache_etag;
+ dot_stack dot_stack;
+
+ buffer_array *config_patches;
+
+ fdevent_handler_t event_handler;
+} server;
+
+
+#endif
diff --git a/src/bitset.c b/src/bitset.c
new file mode 100644
index 00000000..e40bea04
--- /dev/null
+++ b/src/bitset.c
@@ -0,0 +1,66 @@
+#include <limits.h>
+#include <stdlib.h>
+#include <string.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 00000000..467e13fd
--- /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 00000000..35f4c33d
--- /dev/null
+++ b/src/buffer.c
@@ -0,0 +1,1106 @@
+#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;
+}
+
+/**
+ * free the buffer
+ *
+ */
+
+void buffer_free(buffer *b) {
+ if (!b) return;
+
+ if (b->size) {
+ free(b->ptr);
+ b->size = 0;
+ b->used = 0;
+ }
+ 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' coutner 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);
+ if (buffer_prepare_copy(b, s_len + 1)) return -1;
+
+ memcpy(b->ptr, s, s_len + 1);
+ b->used = s_len + 1;
+
+ 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
+ if (buffer_prepare_copy(b, s_len + 1)) return -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 0;
+
+ 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;
+
+ /* the buffer is empty, fallback to copy */
+ if (b->used == 0) {
+ return buffer_copy_string(b, s);
+ }
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+ s_len = strlen(s);
+ if (buffer_prepare_append(b, s_len)) return -1;
+
+ 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;
+ size_t m;
+ ssize_t fill_len;
+
+ if (!s || !b) return -1;
+
+ /* the buffer is empty, fallback to copy */
+ if (b->used == 0) {
+ return buffer_copy_string(b, s);
+ }
+
+ if (b->ptr[b->used - 1] != '\0') {
+ /* seg-fault */
+ SEGFAULT();
+ }
+
+ s_len = strlen(s);
+
+ m = s_len > maxlen + 1 ? s_len : maxlen + 1;
+
+ if (buffer_prepare_append(b, m)) return -1;
+
+ fill_len = maxlen - s_len;
+
+ if (fill_len > 0) {
+ memcpy(b->ptr + b->used - 1, s, s_len);
+ memset(b->ptr + b->used + s_len - 1, ' ', fill_len);
+ *(b->ptr + b->used + s_len + fill_len - 1) = '\0';
+ b->used += s_len + fill_len;
+ } else {
+ memcpy(b->ptr + b->used - 1, s, s_len + 1);
+ b->used += s_len;
+ }
+
+ 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;
+
+ /* the buffer is empty, fallback to copy */
+ if (b->used == 0) {
+ return buffer_copy_string_len(b, s, s_len);
+ }
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+ if (buffer_prepare_append(b, s_len)) return -1;
+
+ memcpy(b->ptr + b->used - 1, s, s_len);
+ b->ptr[b->used + s_len - 1] = '\0';
+ b->used += s_len;
+
+ return 0;
+}
+
+int buffer_append_string_buffer(buffer *b, const buffer *src) {
+ if (!src) return 0;
+ 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;
+
+ if (buffer_prepare_append(b, s_len)) return -1;
+
+ 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_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);
+ buf = b->ptr + b->used;
+ b->used += shift + 1;
+
+ shift <<= 2;
+ while (shift > 0) {
+ shift -= 4;
+ *(buf++) = hex_chars[(value >> shift) & 0x0F];
+ }
+ *buf = '\0';
+
+ return 0;
+}
+
+
+int ltostr(char *s, long l) {
+ int i, sign = 0;
+
+ if (l < 0) {
+ sign = 1;
+ l = -l;
+ }
+
+ for (i = 0; l > 9; l /= 10, i++) {
+ s[i] = '0' + (l % 10);
+ }
+
+ s[i] = '0' + l;
+ if (sign) {
+ s[++i] = '-';
+ }
+ s[i+1] = '\0';
+
+ /* swap bytes again :) */
+ if (i > 0) {
+ int li = i;
+ for (; i > li / 2; i--) {
+ char c;
+
+ c = s[i];
+ s[i] = s[li - i];
+ s[li - i] = c;
+ }
+ }
+
+ return 0;
+}
+
+int buffer_copy_long(buffer *b, long l) {
+ int i, sign = 0;
+ char *s;
+
+ if (!b) return -1;
+
+ b->used = 0;
+
+ if (buffer_prepare_append(b, 32)) return -1;
+
+ s = b->ptr + b->used;
+
+ if (l < 0) {
+ sign = 1;
+ l = -l;
+ }
+
+ for (i = 0; l > 9; l /= 10, i++) {
+ s[i] = '0' + (l % 10);
+ }
+
+ s[i] = '0' + l;
+ if (sign) {
+ s[++i] = '-';
+ }
+ s[i+1] = '\0';
+ b->used = i + 2;
+
+ /* swap bytes again :) */
+ if (i > 0) {
+ int li = i;
+ for (; i > li / 2; i--) {
+ char c;
+
+ c = s[i];
+ s[i] = s[li - i];
+ s[li - i] = c;
+ }
+ }
+
+ return 0;
+}
+
+int buffer_append_long(buffer *b, long l) {
+ int i, sign = 0;
+ char *s;
+
+ if (!b) return -1;
+
+ /* the buffer is empty, fallback to copy */
+ if (b->used == 0) {
+ SEGFAULT();
+ }
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+ if (buffer_prepare_append(b, 32)) return -1;
+
+ s = b->ptr + b->used - 1;
+
+ if (l < 0) {
+ sign = 1;
+ l = -l;
+ }
+
+ for (i = 0; l > 9; l /= 10, i++) {
+ s[i] = '0' + (l % 10);
+ }
+
+ s[i] = '0' + l;
+ if (sign) {
+ s[++i] = '-';
+ }
+ s[i+1] = '\0';
+ b->used += i + 1;
+
+ /* swap bytes again :) */
+ if (i > 0) {
+ int li = i;
+ for (; i > li / 2; i--) {
+ char c;
+
+ c = s[i];
+ s[i] = s[li - i];
+ s[li - i] = c;
+ }
+ }
+
+ return 0;
+}
+
+
+int buffer_copy_off_t(buffer *b, off_t l) {
+ int i, sign = 0;
+ char *s;
+
+ /* a 32bit off_t is handled by _long directly */
+ if (sizeof(l) == 4) return buffer_copy_long(b, l);
+
+ if (!b) return -1;
+
+ b->used = 0;
+
+ if (buffer_prepare_append(b, 32)) return -1;
+
+ s = b->ptr + b->used;
+
+ if (l < 0) {
+ sign = 1;
+ l = -l;
+ }
+
+ for (i = 0; l > 9; l /= 10, i++) {
+ s[i] = '0' + (l % 10);
+ }
+
+ s[i] = '0' + l;
+ if (sign) {
+ s[++i] = '-';
+ }
+ s[i+1] = '\0';
+ b->used = i + 2;
+
+ /* swap bytes again :) */
+ if (i > 0) {
+ int li = i;
+ for (; i > li / 2; i--) {
+ char c;
+
+ c = s[i];
+ s[i] = s[li - i];
+ s[li - i] = c;
+ }
+ }
+
+ return 0;
+}
+
+int buffer_append_off_t(buffer *b, off_t l) {
+ int i, sign = 0;
+ char *s;
+
+ /* a 32bit off_t is handled by _long directly */
+ if (sizeof(l) == 4) return buffer_append_long(b, l);
+
+ if (!b) return -1;
+
+ /* the buffer is empty, fallback to copy */
+ if (b->used == 0) {
+ SEGFAULT();
+ }
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+ if (buffer_prepare_append(b, 32)) return -1;
+
+ s = b->ptr + b->used - 1;
+
+ if (l < 0) {
+ sign = 1;
+ l = -l;
+ }
+
+ for (i = 0; l > 9; l /= 10, i++) {
+ s[i] = '0' + (l % 10);
+ }
+
+ s[i] = '0' + l;
+ if (sign) {
+ s[++i] = '-';
+ }
+ s[i+1] = '\0';
+ b->used += i + 1;
+
+ /* swap bytes again :) */
+ if (i > 0) {
+ int li = i;
+ for (; i > li / 2; i--) {
+ char c;
+
+ c = s[i];
+ s[i] = s[li - i];
+ s[li - i] = c;
+ }
+ }
+
+ return 0;
+}
+
+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;
+}
+
+/**
+ * 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
+ *
+ * this is a optimized 32/64bit compare function.
+ *
+ * it is assumed that the leftmost byte have the most equality.
+ * That why the comparision is done right to left
+ *
+ */
+
+int buffer_is_equal(buffer *a, buffer *b) {
+ size_t i;
+
+ if (a->used != b->used) return 0;
+ if (a->used == 0) return 1;
+
+ for (i = a->used - 1; i < a->used && i % (sizeof(size_t)); i --) {
+ if (a->ptr[i] != b->ptr[i]) return 0;
+ }
+
+ for (i -= (sizeof(size_t)); i < a->used; i -= (sizeof(size_t))) {
+ if (*((size_t *)(a->ptr + i)) !=
+ *((size_t *)(b->ptr + i))) return 0;
+ }
+
+ return 1;
+}
+
+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;
+}
+
+
+int buffer_append_string_hex(buffer *b, const char *in, size_t in_len) {
+ size_t i;
+
+ /* BO protection */
+ if (in_len * 2 < in_len) return -1;
+
+ if (b->used > 0) {
+ if (b->ptr[b->used-1] == '\0') b->used--;
+ }
+
+ buffer_prepare_append(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;
+}
+
+int buffer_append_string_url_encoded(buffer *b, const char *s) {
+ unsigned char *ds, *d;
+ size_t d_len;
+
+ if (!s || !b) return -1;
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+
+ /* count to-be-encoded-characters */
+ for (ds = (unsigned char *)s, d_len = 0; *ds; ds++) {
+ if (*ds < 32 || *ds > 126) {
+ d_len += 3;
+ } else {
+ switch (*ds) {
+ case '$':
+ case '&':
+ case '+':
+ case ',':
+ case '/':
+ case ':':
+ case ';':
+ case '=':
+ case '?':
+ case '@':
+ case ' ':
+ case '#':
+ case '%':
+ case '<':
+ case '>':
+ case '"':
+ case '\'':
+ d_len += 3;
+ break;
+ default:
+ d_len ++;
+ break;
+ }
+ }
+ }
+
+ if (buffer_prepare_append(b, d_len)) return -1;
+
+ for (ds = (unsigned char *)s, d = (unsigned char *)b->ptr + b->used - 1, d_len = 0; *ds; ds++) {
+ if (*ds < 32 || *ds > 126) {
+ d[d_len++] = '%';
+ d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
+ d[d_len++] = hex_chars[(*ds) & 0x0F];
+ } else {
+ switch (*ds) {
+ case '$':
+ case '&':
+ case '+':
+ case ',':
+ case '/':
+ case ':':
+ case ';':
+ case '=':
+ case '?':
+ case '@':
+ case ' ':
+ case '#':
+ case '%':
+ case '<':
+ case '>':
+ case '"':
+ case '\'':
+ d[d_len++] = '%';
+ d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
+ d[d_len++] = hex_chars[(*ds) & 0x0F];
+ break;
+ default:
+ d[d_len++] = *ds;
+ break;
+ }
+ }
+ }
+
+ b->ptr[b->used + d_len - 1] = '\0';
+
+ b->used += d_len;
+
+ return 0;
+}
+
+int buffer_append_string_html_encoded(buffer *b, const char *s) {
+ unsigned char *ds, *d;
+ size_t d_len;
+
+ if (!s || !b) return -1;
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+
+ /* count to-be-encoded-characters */
+ for (ds = (unsigned char *)s, d_len = 0; *ds; ds++) {
+ switch (*ds) {
+ case '>':
+ case '<':
+ d_len += 4;
+ break;
+ case '&':
+ d_len += 5;
+ break;
+ default:
+ d_len++;
+ break;
+ }
+ }
+
+ if (buffer_prepare_append(b, d_len)) return -1;
+
+ for (ds = (unsigned char *)s, d = (unsigned char *)b->ptr + b->used - 1, d_len = 0; *ds; ds++) {
+ switch (*ds) {
+ case '>':
+ d[d_len++] = '&';
+ d[d_len++] = 'g';
+ d[d_len++] = 't';
+ d[d_len++] = ';';
+
+ break;
+ case '<':
+ d[d_len++] = '&';
+ d[d_len++] = 'l';
+ d[d_len++] = 't';
+ d[d_len++] = ';';
+
+ break;
+ case '&':
+ d[d_len++] = '&';
+ d[d_len++] = 'a';
+ d[d_len++] = 'm';
+ d[d_len++] = 'p';
+ d[d_len++] = ';';
+
+ break;
+
+ default:
+ d[d_len++] = *ds;
+ break;
+ }
+ }
+
+ b->ptr[b->used + d_len - 1] = '\0';
+ b->used += d_len;
+
+ return 0;
+}
+
+/* decodes url-special-chars inplace.
+ * ignores %00 (null-byte).
+ */
+int buffer_urldecode(buffer *url) {
+ 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 1
+ if (*src == '+') {
+ *dst = ' ';
+ } else
+#endif
+ 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_path_simplify(dot_stack *stack, buffer *out, buffer *in) {
+ char *last_slash, *slash;
+ size_t i;
+
+ /*
+ * /./ -> /
+ * ^/../ -> /
+ * /abc/../ -> /
+ */
+
+ stack->used = 0;
+
+ for (last_slash = in->ptr; NULL != (slash = strchr(last_slash, '/')); last_slash = slash + 1) {
+ int n;
+
+ n = slash - last_slash;
+
+ if ((n == 0) || /* // */
+ (n == 1 && *last_slash == '.') || /* /./ */
+ (n == 2 && *last_slash == '.' && *(last_slash+1) == '.')) /* /../ */ {
+ if (n == 2 && stack->used > 0) stack->used--;
+ } else {
+ if (stack->size == 0) {
+ stack->size = 16;
+ stack->ptr = malloc(stack->size * sizeof(*stack->ptr));
+ assert(stack->ptr);
+
+ stack->used = 0;
+ for (i = 0; i < stack->size; i++) {
+ stack->ptr[i] = malloc(sizeof(**stack->ptr));
+ assert(stack->ptr[i]);
+ }
+ } else if (stack->size == stack->used) {
+ stack->size += 16;
+ stack->ptr = realloc(stack->ptr, stack->size * sizeof(*stack->ptr));
+ assert(stack->ptr);
+
+ for (i = stack->used; i < stack->size; i++) {
+ stack->ptr[i] = malloc(sizeof(**stack->ptr));
+ assert(stack->ptr[i]);
+ }
+ }
+
+ stack->ptr[stack->used]->start = last_slash;
+ stack->ptr[stack->used]->len = n + 1;
+
+ stack->used++;
+ }
+ }
+
+ if (stack->size == 0) {
+ stack->size = 16;
+ stack->ptr = malloc(stack->size * sizeof(*stack->ptr));
+ assert(stack->ptr);
+
+ stack->used = 0;
+ for (i = 0; i < stack->size; i++) {
+ stack->ptr[i] = malloc(sizeof(**stack->ptr));
+ assert(stack->ptr[i]);
+ }
+ } else if (stack->size == stack->used) {
+ stack->size += 16;
+ stack->ptr = realloc(stack->ptr, stack->size * sizeof(*stack->ptr));
+ assert(stack->ptr);
+
+ for (i = stack->used; i < stack->size; i++) {
+ stack->ptr[i] = malloc(sizeof(**stack->ptr));
+ assert(stack->ptr[i]);
+ }
+ }
+
+ stack->ptr[stack->used]->start = last_slash;
+ stack->ptr[stack->used]->len = in->used - (last_slash - in->ptr) - 1;
+
+ stack->used++;
+
+ BUFFER_COPY_STRING_CONST(out, "/");
+
+ for (i = 0; i < stack->used; i++) {
+ buffer_append_string_len(out, stack->ptr[i]->start, stack->ptr[i]->len);
+ }
+
+ return 0;
+}
+
+inline int light_isdigit(int c) {
+ return (c >= '0' && c <= '9');
+}
+
+inline int light_isxdigit(int c) {
+ if (light_isdigit(c)) return 1;
+
+ c |= 32;
+ return (c >= 'a' && c <= 'f');
+}
+
+inline int light_isalpha(int c) {
+ c |= 32;
+ return (c >= 'a' && c <= 'z');
+}
+
+inline int light_isalnum(int c) {
+ return light_isdigit(c) || light_isalpha(c);
+}
diff --git a/src/buffer.h b/src/buffer.h
new file mode 100644
index 00000000..53febed6
--- /dev/null
+++ b/src/buffer.h
@@ -0,0 +1,122 @@
+#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;
+
+typedef struct {
+ const char *start;
+ size_t len;
+} dot;
+
+typedef struct {
+ dot **ptr;
+ size_t used;
+ size_t size;
+} dot_stack;
+
+buffer_array* buffer_array_init(void);
+void buffer_array_free(buffer_array *b);
+buffer *buffer_array_append_get_buffer(buffer_array *b);
+
+buffer* buffer_init(void);
+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 l);
+int buffer_copy_off_t(buffer *b, off_t l);
+
+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_hex(buffer *b, unsigned long len);
+int buffer_append_long(buffer *b, long l);
+int buffer_append_off_t(buffer *b, off_t l);
+
+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);
+
+int buffer_append_string_hex(buffer *b, const char *in, size_t in_len);
+int buffer_append_string_url_encoded(buffer *b, const char *s);
+int buffer_append_string_html_encoded(buffer *b, const char *s);
+
+int buffer_urldecode(buffer *url);
+int buffer_path_simplify(dot_stack *stack, buffer *out, buffer *in);
+
+/** deprecated */
+int ltostr(char *s, long l);
+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, sizeof(x) - 1
+#define CONST_BUF_LEN(x) x->ptr, x->used - 1
+
+
+#define SEGFAULT() abort()
+#define UNUSED(x) ( (void)(x) )
+
+#endif
diff --git a/src/chunk.c b/src/chunk.c
new file mode 100644
index 00000000..ee631c60
--- /dev/null
+++ b/src/chunk.c
@@ -0,0 +1,312 @@
+/**
+ * the network 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 "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 overlaps with c->data.file.name */
+ c->data.mem = buffer_init();
+ c->next = NULL;
+
+ return c;
+}
+
+static void chunk_free(chunk *c) {
+ if (!c) return;
+
+ /* c->data.mem overlaps with c->data.file.name */
+ switch (c->type) {
+ case MEM_CHUNK: buffer_free(c->data.mem); break;
+ case FILE_CHUNK: buffer_free(c->data.file.name); break;
+ default: break;
+ }
+
+ free(c);
+}
+
+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;
+ }
+
+ 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) {
+ /* move everything to the unused queue */
+
+ if (cq->last == NULL) return;
+
+ cq->last->next = cq->unused;
+ cq->unused = cq->first;
+
+ /* disconnect active chain */
+ cq->first = cq->last = NULL;
+}
+
+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->data.file.name, fn);
+ c->data.file.offset = offset;
+ c->data.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->data.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->data.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->data.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->data.mem);
+
+ chunkqueue_prepend_chunk(cq, c);
+
+ return c->data.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->data.mem);
+
+ chunkqueue_append_chunk(cq, c);
+
+ return c->data.mem;
+}
+
+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->data.mem->used ? c->data.mem->used - 1 : 0;
+ break;
+ case FILE_CHUNK:
+ len += c->data.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;
+}
+
+#ifdef DEBUG_CHUNK
+
+static int write_chunkqueue(int fd, chunkqueue *c) {
+ UNUSED(fd);
+ UNUSED(c);
+
+ return 0;
+}
+
+int main(int argc, char **argv) {
+ chunkqueue *c;
+ buffer *b, *fn;
+
+ UNUSED(argc);
+ UNUSED(argv);
+
+ c = chunkqueue_init();
+
+ fn = buffer_init_string("server.c");
+
+ chunkqueue_append_file(c, fn, 0, 10);
+ chunkqueue_append_file(c, fn, 10, 10);
+ chunkqueue_append_file(c, fn, 20, 10);
+
+ write_chunkqueue(STDERR_FILENO, c);
+ chunkqueue_reset(c);
+
+ b = buffer_init();
+ buffer_copy_string(b, "\ntest string mit vielen Zeichen\n");
+ chunkqueue_append_buffer(c, b);
+
+ write_chunkqueue(STDERR_FILENO, c);
+ chunkqueue_reset(c);
+
+ chunkqueue_append_file(c, fn, 0, 10);
+ buffer_copy_string(b, "\ntest string mit vielen Zeichen\n");
+ chunkqueue_append_buffer(c, b);
+ chunkqueue_append_file(c, fn, 10, 10);
+ chunkqueue_append_file(c, fn, 20, 10);
+ chunkqueue_append_file(c, fn, 50, 40);
+
+ write_chunkqueue(STDERR_FILENO, c);
+ chunkqueue_reset(c);
+
+ chunkqueue_free(c);
+
+ return 0;
+}
+#endif
diff --git a/src/chunk.h b/src/chunk.h
new file mode 100644
index 00000000..9d5ed25a
--- /dev/null
+++ b/src/chunk.h
@@ -0,0 +1,56 @@
+#ifndef _CHUNK_H_
+#define _CHUNK_H_
+
+#include "buffer.h"
+
+typedef struct chunk {
+ /* ok, this one is tricky:
+ *
+ * MEM_CHUNK
+ * b: the chunk it self
+ * FILE_CHUNK
+ * b: a buffer for the filename
+ */
+
+ enum { UNUSED_CHUNK, MEM_CHUNK, FILE_CHUNK } type;
+
+ union {
+ buffer *mem;
+ struct {
+ buffer *name;
+ off_t offset;
+ off_t length;
+ } file;
+ } data;
+
+ /* how many bytes are already handled */
+
+ off_t offset;
+
+ struct chunk *next;
+} chunk;
+
+typedef struct {
+ chunk *first;
+ chunk *last;
+
+ chunk *unused;
+} chunkqueue;
+
+chunkqueue *chunkqueue_init(void);
+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);
+
+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/config.c b/src/config.c
new file mode 100644
index 00000000..27feb6ee
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,1025 @@
+#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"
+
+/* handle global options */
+
+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);
+}
+
+/* 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);
+ 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__, "sssbs", "unexpected type for key: ", cv[i].key, "[", da->value->data[i]->key, "](string)");
+
+ 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__, "ssbss", "unexpected type for key: ", cv[i].key, ds->value, "(short)", "0 ... 65535");
+
+ return -1;
+ }
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssdss", "unexpected type for key: ", cv[i].key, du->type, "(short)", "0 ... 65535");
+ return -1;
+ }
+ break;
+ case T_CONFIG_BOOLEAN:
+ if (du->type == TYPE_STRING) {
+ data_string *ds = (data_string *)du;
+
+ if (0 == strcmp(ds->value->ptr, "enable")) {
+ *((unsigned short *)(cv[i].destination)) = 1;
+ } else if (0 == strcmp(ds->value->ptr, "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;
+}
+
+static int config_insert(server *srv) {
+ size_t i;
+ int ret = 0;
+
+ 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.dir-listing", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */
+ { "server.indexfiles", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 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 */
+
+
+ { "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 },
+
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+
+ /* 0 */
+ cv[0].destination = srv->srvconf.bindhost;
+ cv[1].destination = srv->srvconf.error_logfile;
+ cv[2].destination = srv->srvconf.errorfile_prefix;
+ 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);
+
+ srv->config_storage = malloc(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->dir_listing = 0;
+ s->indexfiles = array_init();
+ s->mimetypes = array_init();
+ s->server_name = buffer_init();
+ s->ssl_pemfile = buffer_init();
+ s->error_handler = buffer_init();
+ s->server_tag = buffer_init();
+ s->max_keep_alive_requests = 128;
+ s->max_keep_alive_idle = 30;
+ 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->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[7].destination = s->server_tag;
+ cv[8].destination = &(s->use_ipv6);
+
+
+ cv[12].destination = &(s->max_request_size);
+ /* 13 max-worker */
+ cv[14].destination = s->document_root;
+ cv[15].destination = &(s->dir_listing);
+ cv[16].destination = s->indexfiles;
+ 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);
+
+ srv->config_storage[i] = s;
+
+ if (0 != (ret = config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv))) {
+ break;
+ }
+ }
+
+ return ret;
+
+}
+
+int config_check_cond(server *srv, connection *con, data_config *dc) {
+ buffer *l;
+ server_socket *srv_sock = con->srv_socket;
+ /* pass the rules */
+
+ l = srv->empty_string;
+
+ if (0 == strcmp(dc->comp_key->ptr, "HTTPhost")) {
+ l = con->uri.authority;
+ } else if (0 == strcmp(dc->comp_key->ptr, "HTTPurl")) {
+ l = con->uri.path;
+ } else if (0 == strcmp(dc->comp_key->ptr, "SERVERsocket")) {
+ l = srv_sock->srv_token;
+ } else if (0 == strcmp(dc->comp_key->ptr, "HTTPreferer")) {
+ data_string *ds;
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Referer"))) {
+ l = ds->value;
+ }
+ } else if (0 == strcmp(dc->comp_key->ptr, "HTTPcookie")) {
+ data_string *ds;
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Cookie"))) {
+ l = ds->value;
+ }
+ } else if (0 == strcmp(dc->comp_key->ptr, "HTTPuseragent")) {
+ data_string *ds;
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "User-Agent"))) {
+ l = ds->value;
+ }
+ } else {
+ return 0;
+ }
+
+ switch(dc->cond) {
+ case CONFIG_COND_NE:
+ case CONFIG_COND_EQ:
+ if (buffer_is_equal(l, dc->match.string)) {
+ return (dc->cond == CONFIG_COND_EQ) ? 1 : 0;
+ } else {
+ return (dc->cond == CONFIG_COND_EQ) ? 0 : 1;
+ }
+ break;
+#ifdef HAVE_PCRE_H
+ case CONFIG_COND_NOMATCH:
+ case CONFIG_COND_MATCH: {
+#define N 10
+ int ovec[N * 3];
+ int n;
+
+ n = pcre_exec(dc->match.regex, NULL, l->ptr, l->used - 1, 0, 0, ovec, N * 3);
+
+ if (n > 0) {
+ return (dc->cond == CONFIG_COND_MATCH) ? 1 : 0;
+ } else {
+ return (dc->cond == CONFIG_COND_MATCH) ? 0 : 1;
+ }
+
+ break;
+ }
+#endif
+ default:
+ /* no way */
+ break;
+ }
+
+ return 0;
+}
+
+#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(dir_listing);
+ PATCH(indexfiles);
+ 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(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_file_not_found);
+
+ return 0;
+}
+
+int config_patch_connection(server *srv, connection *con, const char *stage, size_t stage_len) {
+ 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 (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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.dir-listing"))) {
+ PATCH(dir_listing);
+ } 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.indexfiles"))) {
+ PATCH(indexfiles);
+ } 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.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-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.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;
+
+ char *input;
+ size_t offset;
+ size_t size;
+
+ int line_pos;
+ int line;
+
+ int in_key;
+ int in_brace;
+ int in_cond;
+} tokenizer_t;
+
+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];
+ 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__, "sdsds",
+ "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__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "only =~ and == are allow in the condition");
+ return -1;
+ }
+ } else if (t->in_key) {
+ tid = TK_ASSIGN;
+
+ buffer_copy_string_len(token, t->input + t->offset, 1);
+
+ t->offset++;
+ t->line_pos++;
+ t->in_key = 0;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "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__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "only !~ and != are allow in the condition");
+ return -1;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "unexpected exclamation-marks: !");
+ return -1;
+ }
+
+ break;
+ case '\t':
+ case ' ':
+ t->offset++;
+ t->line_pos++;
+ break;
+ case '\r':
+ if (t->in_brace == 0) {
+ if (t->input[t->offset + 1] == '\n') {
+ t->in_key = 1;
+ t->offset += 2;
+
+ tid = TK_EOL;
+ t->line++;
+ t->line_pos = 1;
+
+ buffer_copy_string(token, "(EOL)");
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "CR without LF");
+ return 0;
+ }
+ } else {
+ t->offset++;
+ t->line_pos++;
+ }
+ break;
+ case '\n':
+ if (t->in_brace == 0) {
+ t->in_key = 1;
+
+ tid = TK_EOL;
+
+ buffer_copy_string(token, "(EOL)");
+ }
+ t->line++;
+ t->line_pos = 1;
+ t->offset++;
+ 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__, "sdsds",
+ "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 '{':
+ t->offset++;
+
+ tid = TK_LCURLY;
+ t->in_key = 1;
+ t->in_cond = 0;
+
+ 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 '#':
+ for (i = 1; t->input[t->offset + i] &&
+ (t->input[t->offset + i] != '\n' && t->input[t->offset + i] != '\r');
+ i++);
+
+ t->offset += i;
+
+ break;
+ default:
+ if (t->in_key) {
+ /* 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] == '-'
+ ); i++);
+
+ if (i && t->input[t->offset + i]) {
+ tid = TK_LKEY;
+ 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__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "invalid character in lvalue");
+ return -1;
+ }
+ } else 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__, "sdsds",
+ "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__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "unexpected EOF");
+
+ return -1;
+ }
+ } else {
+ /* ERROR */
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "invalid value field");
+
+ return -1;
+ }
+ }
+ break;
+ }
+ }
+
+ if (tid) {
+ *token_id = tid;
+
+ return 1;
+ } else if (t->offset < t->size) {
+ fprintf(stderr, "%s.%d: %d, %s\n",
+ __FILE__, __LINE__,
+ tid, token->ptr);
+ }
+ return 0;
+}
+
+int config_read(server *srv, const char *fn) {
+ stream s;
+ tokenizer_t t;
+ void *pParser;
+ int token_id;
+ buffer *token;
+ config_t context;
+ data_config *dc;
+ int ret;
+ buffer *bfn = buffer_init_string(fn);
+
+ if (0 != stream_open(&s, bfn)) {
+ buffer_free(bfn);
+
+ log_error_write(srv, __FILE__, __LINE__, "ssss",
+ "opening configfile ", fn, "failed:", strerror(errno));
+ return -1;
+ }
+
+ buffer_free(bfn);
+
+ t.input = s.start;
+ t.offset = 0;
+ t.size = s.size;
+ t.line = 1;
+ t.line_pos = 1;
+
+ t.in_key = 1;
+ t.in_brace = 0;
+ t.in_cond = 0;
+
+ context.ok = 1;
+ context.config = srv->config_context;
+
+ dc = data_config_init();
+ buffer_copy_string(dc->key, "global");
+ array_insert_unique(srv->config_context, (data_unset *)dc);
+
+ context.ctx_name = dc->key;
+ context.ctx_config = dc->value;
+
+ /* default context */
+ srv->config = dc->value;
+
+ pParser = configparserAlloc( malloc );
+ token = buffer_init();
+ while((1 == (ret = config_tokenizer(srv, &t, &token_id, token))) && context.ok) {
+ configparser(pParser, token_id, token, &context);
+
+ token = buffer_init();
+ }
+ configparser(pParser, 0, token, &context);
+ configparserFree(pParser, free );
+
+ buffer_free(token);
+
+ stream_close(&s);
+
+ if (ret == -1) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "configfile parser failed");
+ return -1;
+ }
+
+ if (context.ok == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t.line, "pos:", t.line_pos,
+ "parser failed somehow near here");
+ return -1;
+ }
+
+ if (0 != config_insert(srv)) {
+ return -1;
+ }
+
+ if (NULL != (dc = (data_config *)array_get_element(srv->config_context, "global"))) {
+ srv->config = dc->value;
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+int config_set_defaults(server *srv) {
+ size_t i;
+ specific_config *s = srv->config_storage[0];
+
+ 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 }
+ };
+
+#ifdef USE_LICENSE
+ license_t *l;
+
+ if (srv->srvconf.license->used == 0) {
+ /* license is missing */
+ return -1;
+ }
+
+ l = license_init();
+
+ if (0 != license_parse(l, srv->srvconf.license)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "parsing license information failed", srv->srvconf.license);
+
+ license_free(l);
+ return -1;
+ }
+ if (!license_is_valid(l)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "license is not valid");
+
+ license_free(l);
+ return -1;
+ }
+ license_free(l);
+#endif
+ 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 (s->ssl_pemfile->used == 0) {
+ /* 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 00000000..55a13d5e
--- /dev/null
+++ b/src/configfile.h
@@ -0,0 +1,18 @@
+#ifndef _CONFIG_PARSER_H_
+#define _CONFIG_PARSER_H_
+
+#include "array.h"
+#include "buffer.h"
+
+typedef struct {
+ int ok;
+ array *config;
+ buffer *ctx_name;
+ array *ctx_config;
+} 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);
+
+#endif
diff --git a/src/configparser.h b/src/configparser.h
new file mode 100644
index 00000000..c7e86ae1
--- /dev/null
+++ b/src/configparser.h
@@ -0,0 +1,19 @@
+#define TK_EOL 1
+#define TK_ASSIGN 2
+#define TK_LKEY 3
+#define TK_STRING 4
+#define TK_INTEGER 5
+#define TK_LPARAN 6
+#define TK_RPARAN 7
+#define TK_COMMA 8
+#define TK_ARRAY_ASSIGN 9
+#define TK_LCURLY 10
+#define TK_RCURLY 11
+#define TK_DOLLAR 12
+#define TK_SRVVARNAME 13
+#define TK_LBRACKET 14
+#define TK_RBRACKET 15
+#define TK_EQ 16
+#define TK_MATCH 17
+#define TK_NE 18
+#define TK_NOMATCH 19
diff --git a/src/configparser.y b/src/configparser.y
new file mode 100644
index 00000000..3d424ce3
--- /dev/null
+++ b/src/configparser.y
@@ -0,0 +1,164 @@
+%token_prefix TK_
+%token_type {buffer *}
+%extra_argument {config_t *ctx}
+%name configparser
+
+%include {
+#include <assert.h>
+#include "config.h"
+#include "configfile.h"
+#include "buffer.h"
+#include "array.h"
+}
+
+%parse_failure {
+ ctx->ok = 0;
+}
+
+input ::= metalines.
+metalines ::= metalines metaline.
+metalines ::= .
+metaline ::= varline.
+metaline ::= condline.
+metaline ::= EOL.
+
+%type value {data_unset *}
+%type aelement {data_unset *}
+%type aelements {array *}
+%type array {array *}
+%type cond {config_cond_t }
+%token_destructor { buffer_free($$); }
+
+varline ::= key(A) ASSIGN value(B). {
+ buffer_copy_string_buffer(B->key, A);
+ array_insert_unique(ctx->ctx_config, B);
+ buffer_free(A);
+}
+
+key(A) ::= LKEY(B). {
+ A = 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);
+}
+
+value(A) ::= INTEGER(B). {
+ A = (data_unset *)data_integer_init();
+ ((data_integer *)(A))->value = strtol(B->ptr, NULL, 10);
+ buffer_free(B);
+}
+value(A) ::= array(B). {
+ A = (data_unset *)data_array_init();
+ array_free(((data_array *)(A))->value);
+ ((data_array *)(A))->value = B;
+}
+array(A) ::= LPARAN aelements(B) RPARAN. {
+ A = B;
+ B = NULL;
+}
+
+aelements(A) ::= aelements(C) COMMA aelement(B). {
+ array_insert_unique(C, B);
+
+ A = C;
+}
+
+aelements(A) ::= aelements(C) COMMA. {
+ A = C;
+}
+
+aelements(A) ::= aelement(B). {
+ A = array_init();
+ array_insert_unique(A, B);
+}
+
+aelement(A) ::= value(B). {
+ A = B;
+ B = NULL;
+}
+aelement(A) ::= STRING(B) ARRAY_ASSIGN value(C). {
+ buffer_copy_string_buffer(C->key, B);
+ buffer_free(B);
+
+ A = C;
+ C = NULL;
+}
+condline ::= context LCURLY metalines RCURLY EOL. {
+ data_config *dc;
+
+ dc = (data_config *)array_get_element(ctx->config, "global");
+ assert(dc);
+ ctx->ctx_name = dc->key;
+ ctx->ctx_config = dc->value;
+}
+
+context ::= DOLLAR SRVVARNAME(B) LBRACKET STRING(C) RBRACKET cond(E) STRING(D). {
+ data_config *dc;
+ buffer *b;
+
+ b = buffer_init();
+ buffer_copy_string_buffer(b, B);
+ buffer_append_string_buffer(b, C);
+ buffer_append_string_buffer(b, D);
+ buffer_append_long(b, E);
+
+ if (NULL != (dc = (data_config *)array_get_element(ctx->config, b->ptr))) {
+ ctx->ctx_name = dc->key;
+ ctx->ctx_config = dc->value;
+ } else {
+ dc = data_config_init();
+
+ buffer_copy_string_buffer(dc->key, b);
+ buffer_copy_string_buffer(dc->comp_key, B);
+ buffer_append_string_buffer(dc->comp_key, C);
+ dc->cond = E;
+
+ switch(E) {
+ case CONFIG_COND_NE:
+ case CONFIG_COND_EQ:
+ dc->match.string = buffer_init_string(D->ptr);
+ break;
+#ifdef HAVE_PCRE_H
+ case CONFIG_COND_NOMATCH:
+ case CONFIG_COND_MATCH: {
+ const char *errptr;
+ int erroff;
+
+ if (NULL == (dc->match.regex =
+ pcre_compile(D->ptr, 0, &errptr, &erroff, NULL))) {
+ dc->match.string = buffer_init_string(errptr);
+ dc->cond = CONFIG_COND_UNSET;
+ }
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+
+ array_insert_unique(ctx->config, (data_unset *)dc);
+
+ ctx->ctx_name = dc->key;
+ ctx->ctx_config = dc->value;
+ }
+ buffer_free(b);
+ buffer_free(B);
+ buffer_free(C);
+ buffer_free(D);
+}
+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;
+}
diff --git a/src/connections.c b/src/connections.c
new file mode 100644
index 00000000..5f1ff544
--- /dev/null
+++ b/src/connections.c
@@ -0,0 +1,1585 @@
+#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 "file_cache.h"
+#include "joblist.h"
+
+#include "plugin.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"
+
+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";
+ }
+}
+
+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;
+}
+
+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");
+ }
+}
+
+static int connection_handle_read(server *srv, connection *con) {
+ int len;
+ buffer *b;
+#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 {
+ len = read(con->fd, b->ptr, b->size - 1);
+ }
+#elif defined(__WIN32)
+ len = recv(con->fd, b->ptr, b->size - 1, 0);
+#else
+ 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;
+
+ switch ((r = SSL_get_error(con->ssl, len))) {
+ case SSL_ERROR_WANT_READ:
+ return 0;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:",
+ r, ERR_error_string(ERR_get_error(), 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 */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "connection closed: remote site closed unexpectedly");
+#endif
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ return -1;
+ } 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) {
+ struct stat st;
+ int s_len;
+
+ switch(con->mode) {
+ case DIRECT:
+ 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: {
+ con->file_finished = 1;
+
+ /* rewrite the filename */
+
+ /* FIXME: use con.physical.errorfile
+ *
+ *
+ */
+ buffer_reset(con->physical.path);
+
+ if (srv->srvconf.errorfile_prefix->used) {
+ buffer_copy_string_buffer(con->physical.path, srv->srvconf.errorfile_prefix);
+ buffer_append_string(con->physical.path, get_http_status_body_name(con->http_status));
+ }
+
+ if ((con->physical.path->used <= 1) ||
+ (-1 == (stat(con->physical.path->ptr, &st)))) {
+ buffer *b;
+
+ buffer_reset(con->physical.path);
+
+ 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"));
+ } else {
+ /* get content-type */
+ size_t k;
+ s_len = con->physical.path->used - 1;
+
+ for (k = 0; k < con->conf.mimetypes->used; k++) {
+ data_string *ds = (data_string *)con->conf.mimetypes->data[k];
+ int ct_len = ds->key->used - 1;
+
+ if (s_len < ct_len ||
+ ds->key->used == 0) continue;
+
+ if (0 == strncmp(con->physical.path->ptr + s_len - ct_len, ds->key->ptr, ct_len)) {
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(ds->value));
+ break;
+ }
+ }
+
+ if (k == con->conf.mimetypes->used) {
+ /* the error message should be HTML */
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+ }
+ }
+ }
+ /* fall through */
+
+ case 200: /* class: header + body */
+ if (con->physical.path->used) {
+ con->file_finished = 1;
+
+ if (HANDLER_GO_ON != file_cache_get_entry(srv, con, con->physical.path, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), con->physical.path);
+
+ connection_set_state(srv, con, CON_STATE_ERROR);
+
+ return -1;
+ }
+
+ if (S_ISREG(con->fce->st.st_mode)) {
+ if (con->request.http_method == HTTP_METHOD_GET ||
+ con->request.http_method == HTTP_METHOD_POST) {
+ http_chunk_append_file(srv, con, con->physical.path, 0, con->fce->st.st_size);
+ con->response.content_length = http_chunkqueue_length(srv, con);
+ } else if (con->request.http_method == HTTP_METHOD_HEAD) {
+ con->response.content_length = con->fce->st.st_size;
+ } else {
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ return -1;
+ }
+
+ http_response_write_header(srv, con,
+ con->response.content_length,
+ con->fce->st.st_mtime);
+
+
+ } else {
+ /* why the heck ? */
+
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "connection closed: no regular-file to send:",
+ con->physical.path);
+
+ con->file_finished = 1;
+ }
+ } else {
+ if (con->file_finished) {
+ con->response.content_length = (ssize_t)http_chunkqueue_length(srv, con);
+ }
+
+ /* 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 (con->request.http_method == HTTP_METHOD_HEAD) {
+ chunkqueue_reset(con->write_queue);
+ }
+
+ http_response_write_header(srv, con,
+ con->response.content_length,
+ 0);
+
+ }
+ break;
+
+ case 206: /* write_queue is already prepared */
+ http_response_write_header(srv, con,
+ con->response.content_length,
+ 0);
+ con->file_finished = 1;
+ break;
+ case 302:
+ con->file_finished = 1;
+
+ con->response.content_length = (ssize_t)http_chunkqueue_length(srv, con);
+
+ http_response_write_header(srv, con,
+ con->response.content_length,
+ 0);
+
+ break;
+ case 205: /* class: header only */
+ case 301:
+ 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);
+
+ http_response_write_header(srv, con, 0, 0);
+ con->file_finished = 1;
+ break;
+ }
+
+ break;
+ default:
+ if (con->request.http_method == HTTP_METHOD_HEAD ||
+ con->http_status == 301 ||
+ con->http_status == 304 ||
+ con->http_status == 205) {
+ /* remove possible chunks */
+ con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
+ chunkqueue_reset(con->write_queue);
+ }
+
+ 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 */
+ }
+ }
+
+ con->response.content_length = (ssize_t)http_chunkqueue_length(srv, con);
+ http_response_write_basic_header(srv, con);
+
+ break;
+ }
+
+ 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;
+
+
+#define CLEAN(x) \
+ con->x = buffer_init();
+
+ CLEAN(request.uri);
+ CLEAN(request.request_line);
+ CLEAN(request.request);
+ CLEAN(request.pathinfo);
+ CLEAN(request.content);
+
+ 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.rel_path);
+ CLEAN(physical.etag);
+ CLEAN(parse_request);
+
+ CLEAN(authed_user);
+ CLEAN(server_name);
+ CLEAN(error_handler);
+
+#undef CLEAN
+ con->write_queue = chunkqueue_init();
+ con->read_queue = chunkqueue_init();
+ con->request.headers = array_init();
+ con->response.headers = array_init();
+ con->environment = array_init();
+
+ /* init plugin specific connection structures */
+
+ con->plugin_ctx = calloc(srv->plugins.used + 1, sizeof(void *));
+
+ 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);
+ 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.content);
+
+ 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.etag);
+ CLEAN(physical.rel_path);
+ CLEAN(parse_request);
+
+ CLEAN(authed_user);
+ CLEAN(server_name);
+ CLEAN(error_handler);
+#undef CLEAN
+ free(con->plugin_ctx);
+
+ 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->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.content);
+ 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.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);
+
+ if (con->fce) {
+ file_cache_entry_release(srv, con, con->fce);
+ con->fce = NULL;
+ }
+
+ for (i = 0; i < srv->plugins.used; i++) {
+ con->plugin_ctx[0] = NULL;
+ }
+
+ 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;
+}
+
+int connection_handle_read_state(server *srv, connection *con) {
+ int ostate = con->state;
+ char *h_term = NULL;
+ chunk *c;
+ chunkqueue *cq = con->read_queue;
+
+ if (con->is_readable) {
+ con->read_idle_ts = srv->cur_ts;
+
+ if (0 != connection_handle_read(srv, con)) {
+ return -1;
+ }
+ }
+
+ /* move the empty chunks out of the way */
+ for (c = cq->first; c; c = cq->first) {
+ assert(c != c->next);
+
+ if (c->data.mem->used == 0) {
+ cq->first = c->next;
+ c->next = cq->unused;
+ cq->unused = c;
+
+ if (cq->first == NULL) cq->last = NULL;
+
+ c = cq->first;
+ } else {
+ break;
+ }
+ }
+
+ /* 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->data.mem->ptr + c->offset;
+ b.used = c->data.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->data.mem->ptr + c->offset, c->data.mem->used - c->offset - 1);
+ c->offset = c->data.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->data.mem->ptr + c->offset;
+ b.used = c->data.mem->used - c->offset;
+
+ if (con->request.request->used - 1 > 3 &&
+ c->data.mem->used > 1 &&
+ s[l-2] == '\r' &&
+ s[l-1] == '\n' &&
+ s[l-0] == '\r' &&
+ c->data.mem->ptr[0] == '\n') {
+ buffer_append_string_len(con->request.request, c->data.mem->ptr + c->offset, 1);
+ c->offset += 1;
+
+ h_term = con->request.request->ptr;
+ } else if (con->request.request->used - 1 > 2 &&
+ c->data.mem->used > 2 &&
+ s[l-1] == '\r' &&
+ s[l-0] == '\n' &&
+ c->data.mem->ptr[0] == '\r' &&
+ c->data.mem->ptr[1] == '\n') {
+ buffer_append_string_len(con->request.request, c->data.mem->ptr + c->offset, 2);
+ c->offset += 2;
+
+ h_term = con->request.request->ptr;
+ } else if (con->request.request->used - 1 > 1 &&
+ c->data.mem->used > 3 &&
+ s[l-0] == '\r' &&
+ c->data.mem->ptr[0] == '\n' &&
+ c->data.mem->ptr[1] == '\r' &&
+ c->data.mem->ptr[2] == '\n') {
+ buffer_append_string_len(con->request.request, c->data.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->data.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->data.mem->ptr + c->offset, c->data.mem->used - c->offset - 1);
+ c->offset = c->data.mem->used - 1;
+ }
+ }
+
+ if (c->offset + 1 == c->data.mem->used) {
+ /* chunk is empty, move it to unused */
+ cq->first = c->next;
+ c->next = cq->unused;
+ cq->unused = c;
+
+ if (cq->first == NULL) cq->last = NULL;
+
+ assert(c != c->next);
+ }
+
+ /* con->request.request is setup up */
+ if (h_term) {
+ connection_set_state(srv, con, CON_STATE_REQUEST_END);
+ } else if (chunkqueue_length(cq) > 64 * 1024) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "http-header larger then 64k -> disconnected", chunkqueue_length(cq));
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ }
+ break;
+ case CON_STATE_READ_POST:
+ for (c = cq->first; c && (con->request.content->used != con->request.content_length + 1); c = cq->first) {
+ off_t weWant, weHave, toRead;
+
+ weWant = con->request.content_length - (con->request.content->used ? con->request.content->used - 1 : 0);
+ /* without the terminating \0 */
+
+ assert(c->data.mem->used);
+
+ weHave = c->data.mem->used - c->offset - 1;
+
+ toRead = weHave > weWant ? weWant : weHave;
+
+ buffer_append_string_len(con->request.content, c->data.mem->ptr + c->offset, toRead);
+
+ c->offset += toRead;
+
+ if (c->offset + 1 >= c->data.mem->used) {
+ /* chunk is empty, move it to unused */
+
+ cq->first = c->next;
+ c->next = cq->unused;
+ cq->unused = c;
+
+ if (cq->first == NULL) cq->last = NULL;
+
+ assert(c != c->next);
+ } else {
+ assert(toRead);
+ }
+ }
+
+ /* Content is ready */
+ if (con->request.content->used == con->request.content_length + 1) {
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ }
+
+ break;
+ }
+
+ 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) {
+ int accepted_requests = 0;
+ /* 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__, "ss", "accept failed: ", strerror(errno));
+ }
+ return NULL;
+ } else {
+ connection *con;
+
+ srv->cur_fds++;
+
+ accepted_requests++;
+ /* 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;
+ 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;
+ } else {
+ SSL_set_accept_state(con->ssl);
+ }
+
+ 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_set_state(server *srv, connection *con, connection_state_t state) {
+ UNUSED(srv);
+
+ con->state = state;
+
+ return 0;
+}
+
+
+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++;
+
+ 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", "error-handler not found:", con->conf.error_handler);
+
+ con->http_status = con->error_handler_saved_status;
+ }
+ } else if (con->in_error_handler) {
+ /* error-handler is back and has generated content */
+ /* reset the old status */
+ if (con->http_status == 0) {
+ con->http_status = con->error_handler_saved_status;
+ }
+ }
+
+ 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));
+ }
+
+ connection_handle_write_prepare(srv, con);
+
+ 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 00000000..1fcfc365
--- /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 00000000..2b2053e1
--- /dev/null
+++ b/src/crc32.c
@@ -0,0 +1,84 @@
+#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,
+};
+
+
+unsigned long
+generate_crc32c(unsigned char *buffer, unsigned int length)
+{
+ unsigned int i;
+ unsigned long crc32 = ~0L;
+
+ for (i = 0; i < length; i++){
+ CRC32C(crc32, buffer[i]);
+ }
+ return ~crc32;
+}
+
diff --git a/src/crc32.h b/src/crc32.h
new file mode 100644
index 00000000..e5b1c2fd
--- /dev/null
+++ b/src/crc32.h
@@ -0,0 +1,6 @@
+#ifndef __crc32cr_table_h__
+#define __crc32cr_table_h__
+
+unsigned long generate_crc32c(unsigned char *string, unsigned int length);
+
+#endif
diff --git a/src/data_array.c b/src/data_array.c
new file mode 100644
index 00000000..6cd740c4
--- /dev/null
+++ b/src/data_array.c
@@ -0,0 +1,56 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "array.h"
+
+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(data_unset *d) {
+ data_array *ds = (data_array *)d;
+
+ printf("{%s:\n", ds->key->ptr);
+ array_print(ds->value);
+ printf("}");
+}
+
+
+data_array *data_array_init(void) {
+ data_array *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->value = array_init();
+
+ 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 00000000..6a01af04
--- /dev/null
+++ b/src/data_config.c
@@ -0,0 +1,69 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "array.h"
+
+static void data_config_free(data_unset *d) {
+ data_config *ds = (data_config *)d;
+
+ buffer_free(ds->key);
+ buffer_free(ds->comp_key);
+
+ array_free(ds->value);
+
+ switch(ds->cond) {
+ case CONFIG_COND_EQ: buffer_free(ds->match.string); break;
+#ifdef HAVE_PCRE_H
+ case CONFIG_COND_MATCH: pcre_free(ds->match.regex); break;
+#endif
+ default:
+ break;
+ }
+
+ 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(data_unset *d) {
+ data_config *ds = (data_config *)d;
+
+ printf("{%s:\n", ds->key->ptr);
+ array_print(ds->value);
+ printf("}");
+}
+
+
+data_config *data_config_init(void) {
+ data_config *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->comp_key = buffer_init();
+ ds->value = array_init();
+
+ 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 00000000..843e3bb1
--- /dev/null
+++ b/src/data_count.c
@@ -0,0 +1,56 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "array.h"
+
+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(data_unset *d) {
+ data_count *ds = (data_count *)d;
+
+ printf("{%s: %d}", ds->key->ptr, ds->count);
+}
+
+
+data_count *data_count_init(void) {
+ data_count *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->count = 1;
+
+ 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 00000000..28273752
--- /dev/null
+++ b/src/data_fastcgi.c
@@ -0,0 +1,56 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "array.h"
+#include "fastcgi.h"
+
+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(data_unset *d) {
+ data_fastcgi *ds = (data_fastcgi *)d;
+
+ printf("{%s: %s}", ds->key->ptr, 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->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 00000000..af6ad221
--- /dev/null
+++ b/src/data_integer.c
@@ -0,0 +1,53 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "array.h"
+
+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(data_unset *d) {
+ data_integer *ds = (data_integer *)d;
+
+ printf("{%s: %d}", ds->key->ptr, ds->value);
+}
+
+
+data_integer *data_integer_init(void) {
+ data_integer *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->value = 0;
+
+ 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 00000000..5ce160a3
--- /dev/null
+++ b/src/data_string.c
@@ -0,0 +1,92 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "array.h"
+
+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(data_unset *d) {
+ data_string *ds = (data_string *)d;
+
+ fprintf(stderr, "{%s: %s}", ds->key->ptr, 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->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 00000000..eb1b2f41
--- /dev/null
+++ b/src/etag.c
@@ -0,0 +1,30 @@
+#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, "-", 1);
+ buffer_append_off_t(etag, st->st_size);
+ buffer_append_string_len(etag, "-", 1);
+ 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_long(mut, h);
+
+ return 0;
+}
diff --git a/src/etag.h b/src/etag.h
new file mode 100644
index 00000000..53fae00a
--- /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 00000000..15f1deae
--- /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 00000000..7deda395
--- /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->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 00000000..5197713b
--- /dev/null
+++ b/src/fdevent.h
@@ -0,0 +1,222 @@
+#ifndef _FDEVENT_H_
+#define _FDEVENT_H_
+
+#include "config.h"
+#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
+#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 {
+ fdnode *first, *last;
+} fdlist;
+
+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;
+
+ fdlist fdlist;
+ 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 00000000..0585cfbf
--- /dev/null
+++ b/src/fdevent_freebsd_kqueue.c
@@ -0,0 +1,194 @@
+#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) {
+ fprintf(stderr, "%s.%d: kqueue failed polling: %s\n",
+ __FILE__, __LINE__, strerror(errno));
+ }
+
+ 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) {
+ close(ev->kq_fd);
+
+ 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);
+
+ 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;
+ }
+ ev->kq_results = calloc(ev->maxfds, sizeof(*ev->kq_results));
+ ev->kq_bevents = bitset_init(ev->maxfds);
+
+ 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 00000000..8bedaf0d
--- /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 %u\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: %u %u\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 00000000..4175fc65
--- /dev/null
+++ b/src/fdevent_linux_sysepoll.c
@@ -0,0 +1,152 @@
+#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;
+
+ 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 00000000..07a67a2f
--- /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 %u\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: %u >= %u\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 00000000..fe9d7236
--- /dev/null
+++ b/src/fdevent_select.c
@@ -0,0 +1,127 @@
+#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 "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);
+
+ 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 00000000..7e6680c1
--- /dev/null
+++ b/src/fdevent_solaris_devpoll.c
@@ -0,0 +1,141 @@
+#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_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_SOLARIS_DEVPOLL;
+#define SET(x) \
+ ev->x = fdevent_solaris_devpoll_##x;
+
+ SET(free);
+ SET(poll);
+
+ 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;
+ }
+
+ 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;
+}
+
+#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/file_cache.c b/src/file_cache.c
new file mode 100644
index 00000000..ee16c77a
--- /dev/null
+++ b/src/file_cache.c
@@ -0,0 +1,483 @@
+#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 "log.h"
+#include "file_cache.h"
+#include "fdevent.h"
+#include "etag.h"
+
+#ifdef HAVE_ATTR_ATTRIBUTES_H
+#include <attr/attributes.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
+
+/* don't enable the dir-cache
+ *
+ * F_NOTIFY would be nice but only works with linux-rtsig
+ */
+#undef USE_LINUX_SIGIO
+
+file_cache *file_cache_init(void) {
+ file_cache *fc = NULL;
+
+ fc = calloc(1, sizeof(*fc));
+
+ fc->dir_name = buffer_init();
+
+ return fc;
+}
+
+static file_cache_entry * file_cache_entry_init(void) {
+ file_cache_entry *fce = NULL;
+
+ fce = calloc(1, sizeof(*fce));
+
+ fce->fd = -1;
+ fce->fde_ndx = -1;
+ fce->name = buffer_init();
+ fce->etag = buffer_init();
+ fce->content_type = buffer_init();
+
+ return fce;
+}
+
+static void file_cache_entry_free(server *srv, file_cache_entry *fce) {
+ if (!fce) return;
+
+ if (fce->fd >= 0) {
+ close(fce->fd);
+ srv->cur_fds--;
+ }
+
+ buffer_free(fce->etag);
+ buffer_free(fce->name);
+ buffer_free(fce->content_type);
+
+ if (fce->mmap_p) munmap(fce->mmap_p, fce->mmap_length);
+
+ free(fce);
+}
+
+static int file_cache_entry_reset(server *srv, file_cache_entry *fce) {
+ if (fce->fd < 0) return 0;
+
+ close(fce->fd);
+ srv->cur_fds--;
+
+#ifdef USE_LINUX_SIGIO
+ /* doesn't work anymore */
+ if (fce->fde_ndx != -1) {
+ fdevent_event_del(srv->ev, &(fce->fde_ndx), fce->fd);
+ }
+#else
+ UNUSED(srv);
+#endif
+
+ if (fce->mmap_p) {
+ munmap(fce->mmap_p, fce->mmap_length);
+ fce->mmap_p = NULL;
+ }
+ fce->fd = -1;
+
+ buffer_reset(fce->etag);
+ buffer_reset(fce->name);
+ buffer_reset(fce->content_type);
+
+ return 0;
+}
+
+void file_cache_free(server *srv, file_cache *fc) {
+ size_t i;
+ for (i = 0; i < fc->used; i++) {
+ file_cache_entry_free(srv, fc->ptr[i]);
+ }
+
+ free(fc->ptr);
+
+ buffer_free(fc->dir_name);
+
+ free(fc);
+
+}
+
+#ifdef HAVE_XATTR
+int fce_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
+
+file_cache_entry * file_cache_get_unused_entry(server *srv) {
+ file_cache_entry *fce = NULL;
+ file_cache *fc = srv->file_cache;
+ size_t i;
+
+ if (fc->size == 0) {
+ fc->size = 16;
+ fc->ptr = calloc(fc->size, sizeof(*fc->ptr));
+ fc->used = 0;
+ }
+ for (i = 0; i < fc->used; i++) {
+ file_cache_entry *f = fc->ptr[i];
+
+ if (f->fd == -1) {
+ return f;
+ }
+ }
+
+ if (fc->used < fc->size) {
+ fce = file_cache_entry_init();
+ fc->ptr[fc->used++] = fce;
+ } else {
+ /* the cache is full, time to resize */
+
+ fc->size += 16;
+ fc->ptr = realloc(fc->ptr, sizeof(*fc->ptr) * fc->size);
+
+ fce = file_cache_entry_init();
+ fc->ptr[fc->used++] = fce;
+ }
+
+ return fce;
+}
+
+handler_t file_cache_handle_fdevent(void *_srv, void *_fce, int revent) {
+ size_t i;
+ server *srv = _srv;
+ file_cache_entry *fce = _fce;
+ file_cache *fc = srv->file_cache;;
+
+ UNUSED(revent);
+ /* */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sds", "dir has changed: ", fce->fd, fce->name->ptr);
+#endif
+ /* touch all files below this directory */
+
+ for (i = 0; i < fc->used; i++) {
+ file_cache_entry *f = fc->ptr[i];
+
+ if (fce == f) continue;
+
+ if (0 == strncmp(fce->name->ptr, f->name->ptr, fce->name->used - 1)) {
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "ss", "file hit: ", f->name->ptr);
+#endif
+ f->is_dirty = 1;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+#if 0
+/* dead code, might be reused somewhere again */
+
+int file_cache_check_cache() {
+ file_cache_entry *first_unused_fce = NULL;
+ file_cache *fc = srv->file_cache;
+ size_t i;
+
+ /* check the cache */
+ for (i = 0; i < fc->used; i++) {
+ fce = fc->ptr[i];
+
+ if (buffer_is_equal(name, fce->name)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "cache hit:", name);
+
+#ifdef USE_LINUX_SIGIO
+ if (fce->is_dirty == 0) {
+ fce->in_use++;
+ return fce;
+ }
+#endif
+
+ /* get the etag information */
+ if (-1 == (stat(name->ptr, &(fce->st)))) {
+ fce->in_use = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "stat failed: ", name, strerror(errno));
+
+ file_cache_entry_reset(srv, fce);
+
+ return NULL;
+ }
+ fce->stat_ts = srv->cur_ts;
+
+ /* create etag */
+ etag_create(srv->file_cache_etag, &(fce->st));
+
+ if (!buffer_is_equal(srv->file_cache_etag, fce->etag)) {
+ size_t s_len = 0, k;
+ /* etag has changed: reopen file */
+
+ file_cache_entry_reset(srv, fce);
+
+ if (-1 == (fce->fd = open(fce->name->ptr, O_RDONLY | O_LARGEFILE))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
+
+ buffer_reset(fce->name);
+ return NULL;
+ }
+
+ srv->cur_fds++;
+
+ buffer_copy_string_buffer(fce->etag, srv->file_cache_etag);
+
+ /* determine mimetype */
+ buffer_reset(fce->content_type);
+
+ s_len = name->used - 1;
+
+ for (k = 0; k < con->conf.mimetypes->used; k++) {
+ data_string *ds = (data_string *)con->conf.mimetypes->data[k];
+ size_t ct_len;
+
+ ct_len = ds->key->used - 1;
+
+ if (buffer_is_equal_right_len(name, ds->key, ct_len)) {
+ buffer_copy_string_buffer(fce->content_type, ds->value);
+ break;
+ }
+ }
+
+#ifdef HAVE_XATTR
+ if (buffer_is_empty(fce->content_type)) {
+ fce_attr_get(fce->content_type, name->ptr);
+ }
+#endif
+ }
+
+#ifdef USE_LINUX_SIGIO
+ fce->is_dirty = 0;
+#endif
+
+ fce->in_use++;
+
+ if (fce->fd == -1) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "fd is still -1 !", fce->name);
+ }
+ if (fce->st.st_size == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "size is still 0 !", fce->name);
+ }
+
+
+
+
+ return fce;
+ }
+
+ if (fce->in_use == 0) {
+ if (!first_unused_fce) first_unused_fce = fce;
+
+ if (srv->cur_ts - fce->stat_ts > 10) {
+ file_cache_entry_reset(srv, fce);
+ }
+ }
+ }
+
+ if (first_unused_fce) {
+ file_cache_entry_reset(srv, fce);
+
+ fce = first_unused_fce;
+ } else {
+ /* not found, insert */
+ fce = file_cache_get_unused_entry(srv);
+ }
+}
+#endif
+
+
+handler_t file_cache_get_entry(server *srv, connection *con, buffer *name, file_cache_entry **o_fce_ptr) {
+ file_cache_entry *fce = NULL;
+ file_cache_entry *o_fce = *o_fce_ptr;
+
+
+ UNUSED(con);
+
+ /* still valid ? */
+ if (o_fce != NULL) {
+ if (buffer_is_equal(name, o_fce->name) &&
+ (o_fce->fd != -1) &&
+ (o_fce->stat_ts == srv->cur_ts)
+ ) {
+ return HANDLER_GO_ON;
+ } else {
+ o_fce->in_use--;
+ }
+ file_cache_entry_reset(srv, o_fce);
+ }
+
+
+ fce = file_cache_get_unused_entry(srv);
+
+ buffer_copy_string_buffer(fce->name, name);
+ fce->in_use = 0;
+ fce->fd = -1;
+
+ if (-1 == (con->conf.follow_symlink ? stat(name->ptr, &(fce->st)) : lstat(name->ptr, &(fce->st)))) {
+ int oerrno = errno;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "stat failed:", name, strerror(errno));
+#endif
+ file_cache_entry_reset(srv, fce);
+
+ buffer_reset(fce->name);
+
+ errno = oerrno;
+ return HANDLER_ERROR;
+ }
+
+ fce->stat_ts = srv->cur_ts;
+
+ if (S_ISREG(fce->st.st_mode)) {
+ size_t k, s_len;
+#ifdef USE_LINUX_SIGIO
+ file_cache_entry *dir_fce;
+ char *slash;
+#endif
+
+ if (-1 == (fce->fd = open(name->ptr, O_RDONLY | O_LARGEFILE))) {
+ int oerrno = errno;
+ if (errno == EMFILE || errno == EINTR) {
+ return HANDLER_WAIT_FOR_FD;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "open failed for:", name,
+ strerror(errno));
+
+ buffer_reset(fce->name);
+
+ errno = oerrno;
+ return HANDLER_ERROR;
+ }
+
+ srv->cur_fds++;
+
+ /* determine mimetype */
+ buffer_reset(fce->content_type);
+
+ s_len = name->used - 1;
+
+ 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 (s_len < ct_len) continue;
+
+ if (0 == strncmp(name->ptr + s_len - ct_len, ds->key->ptr, ct_len)) {
+ buffer_copy_string_buffer(fce->content_type, ds->value);
+ break;
+ }
+ }
+
+#ifdef HAVE_XATTR
+ if (buffer_is_empty(fce->content_type)) {
+ fce_attr_get(fce->content_type, name->ptr);
+ }
+#endif
+
+ etag_create(fce->etag, &(fce->st));
+
+#ifdef USE_LINUX_SIGIO
+ /* register sigio for the directory */
+ dir_fce = file_cache_get_unused_entry(srv);
+
+ buffer_copy_string_buffer(fc->dir_name, name);
+
+ /* get dirname */
+ if (0 == (slash = strrchr(fc->dir_name->ptr, '/'))) {
+ SEGFAULT();
+ }
+ *(slash+1) = '\0';
+
+ if (-1 == (dir_fce->fd = open(fc->dir_name->ptr, O_RDONLY))) {
+ int oerrno = errno;
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "open failed:", fc->dir_name, strerror(errno));
+
+ errno = oerrno;
+ return HANDLER_ERROR;
+ }
+
+ srv->cur_fds++;
+
+ if (fcntl(dir_fce->fd, F_NOTIFY, DN_CREATE|DN_DELETE|DN_MODIFY|DN_MULTISHOT) < 0) {
+ int oerrno = errno;
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "fcntl failed:", strerror(errno));
+
+ close(dir_fce->fd);
+ srv->cur_fds--;
+
+ errno = oerrno;
+ return HANDLER_ERROR;
+ }
+
+ /* ->used is not updated -> no _buffer copy */
+ buffer_copy_string(dir_fce->name, fc->dir_name->ptr);
+
+ /* register fd-handler */
+ fdevent_register(srv->ev, dir_fce->fd, file_cache_handle_fdevent, dir_fce);
+ fdevent_event_add(srv->ev, &(dir_fce->fde_ndx), dir_fce->fd, FDEVENT_IN);
+# if 1
+ log_error_write(srv, __FILE__, __LINE__, "sddb", "fdevent_event_add:", fce->fde_ndx, fce->fd, fce->name);
+# endif
+#endif
+ }
+
+ fce->in_use++;
+
+ *o_fce_ptr = fce;
+
+ return HANDLER_GO_ON;
+}
+
+int file_cache_entry_release(server *srv, connection *con, file_cache_entry *fce) {
+ UNUSED(srv);
+ UNUSED(con);
+
+ if (fce->in_use > 0) fce->in_use--;
+ file_cache_entry_reset(srv, fce);
+
+ return 0;
+}
+
diff --git a/src/file_cache.h b/src/file_cache.h
new file mode 100644
index 00000000..b45a7cea
--- /dev/null
+++ b/src/file_cache.h
@@ -0,0 +1,12 @@
+#ifndef _FILE_CACHE_H_
+#define _FILE_CACHE_H_
+
+#include "base.h"
+
+file_cache *file_cache_init(void);
+void file_cache_free(server *srv, file_cache *fc);
+
+handler_t file_cache_get_entry(server *srv, connection *con, buffer *name, file_cache_entry **o_fce);
+int file_cache_entry_release(server *srv, connection *con, file_cache_entry *fce);
+
+#endif
diff --git a/src/http_auth.c b/src/http_auth.c
new file mode 100644
index 00000000..2aa0e23f
--- /dev/null
+++ b/src/http_auth.c
@@ -0,0 +1,939 @@
+#include "config.h"
+
+#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 <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_global.h"
+# 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
+
+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");
+
+ /* 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 */
+
+ /* search for = in the rules */
+ if (NULL == (eq = strchr(r, '='))) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "= is missing");
+ return -1;
+ }
+
+ /* = out of range */
+ if (eq > r + r_len) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "= out of range");
+
+ 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[3];
+ char *crypted;
+ /*
+ * htpasswd format
+ *
+ * user:crypted password
+ */
+
+ salt[0] = password->ptr[0];
+ salt[1] = password->ptr[1];
+ salt[2] = '\0';
+ crypted = crypt(pw, salt);
+
+ if (0 == strcmp(password->ptr, crypted)) {
+ return 0;
+ }
+#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("login", 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 (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 (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", 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;
+
+ const char *method_get = "GET";
+ const char *method_post = "POST";
+ const char *method_head = "HEAD";
+
+ 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++) {
+ 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;
+ }
+
+ switch(con->request.http_method) {
+ case HTTP_METHOD_GET: m = method_get; break;
+ case HTTP_METHOD_POST: m = method_post; break;
+ case HTTP_METHOD_HEAD: m = method_head; break;
+ case HTTP_METHOD_UNSET: break;
+ }
+
+ /* 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 (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) {
+ 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__, "ssssb",
+ "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 00000000..392678b5
--- /dev/null
+++ b/src/http_auth.h
@@ -0,0 +1,64 @@
+#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_filter;
+
+ 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 00000000..a1dd2d3d
--- /dev/null
+++ b/src/http_auth_digest.c
@@ -0,0 +1,103 @@
+#include <string.h>
+#include "http_auth_digest.h"
+
+#include "config.h"
+#include "buffer.h"
+
+#ifndef USE_OPENSSL
+# include "md5_global.h"
+# 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';
+}
+
+/* calculate H(A1) as per spec */
+void DigestCalcHA1(
+ IN char * pszAlg,
+ IN char * pszUserName,
+ IN char * pszRealm,
+ IN char * pszPassword,
+ IN char * pszNonce,
+ IN char * pszCNonce,
+ OUT HASHHEX SessionKey
+ )
+{
+ MD5_CTX Md5Ctx;
+ HASH HA1;
+
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszUserName, strlen(pszUserName));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszRealm, strlen(pszRealm));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszPassword, strlen(pszPassword));
+ MD5_Final(HA1, &Md5Ctx);
+ if (strcasecmp(pszAlg, "md5-sess") == 0) {
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)HA1, HASHLEN);
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszNonce, strlen(pszNonce));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszCNonce, strlen(pszCNonce));
+ MD5_Final(HA1, &Md5Ctx);
+ }
+ CvtHex(HA1, SessionKey);
+}
+
+/* calculate request-digest/response-digest as per HTTP Digest spec */
+void DigestCalcResponse(
+ IN HASHHEX HA1, /* H(A1) */
+ IN char * pszNonce, /* nonce from server */
+ IN char * pszNonceCount, /* 8 hex digits */
+ IN char * pszCNonce, /* client nonce */
+ IN char * pszQop, /* qop-value: "", "auth", "auth-int" */
+ IN char * pszMethod, /* method from the request */
+ IN char * pszDigestUri, /* requested URL */
+ IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */
+ OUT HASHHEX Response /* request-digest or response-digest */
+ )
+{
+ MD5_CTX Md5Ctx;
+ HASH HA2;
+ HASH RespHash;
+ HASHHEX HA2Hex;
+
+ /* calculate H(A2) */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszMethod, strlen(pszMethod));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszDigestUri, strlen(pszDigestUri));
+ if (strcasecmp(pszQop, "auth-int") == 0) {
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)HEntity, HASHHEXLEN);
+ };
+ MD5_Final(HA2, &Md5Ctx);
+ CvtHex(HA2, HA2Hex);
+
+ /* calculate response */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)HA1, HASHHEXLEN);
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszNonce, strlen(pszNonce));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ if (*pszQop) {
+ MD5_Update(&Md5Ctx, (unsigned char *)pszNonceCount, strlen(pszNonceCount));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszCNonce, strlen(pszCNonce));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszQop, strlen(pszQop));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ }
+ MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN);
+ MD5_Final(RespHash, &Md5Ctx);
+ CvtHex(RespHash, Response);
+}
+
diff --git a/src/http_auth_digest.h b/src/http_auth_digest.h
new file mode 100644
index 00000000..2d4e363f
--- /dev/null
+++ b/src/http_auth_digest.h
@@ -0,0 +1,46 @@
+#ifndef _DIGCALC_H_
+#define _DIGCALC_H_
+
+#include "config.h"
+
+#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
+
+/* calculate H(A1) as per HTTP Digest spec */
+void DigestCalcHA1(
+ IN char * pszAlg,
+ IN char * pszUserName,
+ IN char * pszRealm,
+ IN char * pszPassword,
+ IN char * pszNonce,
+ IN char * pszCNonce,
+ OUT HASHHEX SessionKey
+ );
+
+/* calculate request-digest/response-digest as per HTTP Digest spec */
+void DigestCalcResponse(
+ IN HASHHEX HA1, /* H(A1) */
+ IN char * pszNonce, /* nonce from server */
+ IN char * pszNonceCount, /* 8 hex digits */
+ IN char * pszCNonce, /* client nonce */
+ IN char * pszQop, /* qop-value: "", "auth", "auth-int" */
+ IN char * pszMethod, /* method from the request */
+ IN char * pszDigestUri, /* requested URL */
+ IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */
+ OUT HASHHEX Response /* request-digest or response-digest */
+ );
+
+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 00000000..c128bf1c
--- /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 00000000..4ba24a26
--- /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 00000000..c0b3aa18
--- /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 00000000..fd3c2814
--- /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 00000000..dcab9550
--- /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 00000000..3701e097
--- /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 00000000..1b8eaf8d
--- /dev/null
+++ b/src/keyvalue.c
@@ -0,0 +1,352 @@
+#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_UNSET, NULL }
+};
+
+static keyvalue http_status[] = {
+ { 100, "Continue" },
+ { 101, "Switching Protocols" },
+ { 200, "OK" },
+ { 201, "Created" },
+ { 202, "Accepted" },
+ { 203, "Non-Authoritative Information" },
+ { 204, "No Content" },
+ { 205, "Reset Content" },
+ { 206, "Partial Content" },
+ { 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" },
+ { 426, "Upgrade Required" },
+ { 500, "Internal Server Error" },
+ { 501, "Not Implemented" },
+ { 502, "Bad Gateway" },
+ { 503, "Service Not Available" },
+ { 504, "Gateway Timeout" },
+ { 505, "HTTP Version Not Supported" },
+
+ { -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;
+#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));
+ }
+ }
+
+ if (NULL == (kvb->kv[kvb->used]->key = pcre_compile(key,
+ 0, &errptr, &erroff, NULL))) {
+
+ fprintf(stderr, "%s.%d: rexexp compilation error at %s\n", __FILE__, __LINE__, errptr);
+ return -1;
+ }
+
+ kvb->kv[kvb->used]->value = strdup(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;
+
+ for (i = 0; i < kvb->size; i++) {
+ if (kvb->kv[i]->key) pcre_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);
+#endif
+
+ free(kvb);
+}
diff --git a/src/keyvalue.h b/src/keyvalue.h
new file mode 100644
index 00000000..3c1f0519
--- /dev/null
+++ b/src/keyvalue.h
@@ -0,0 +1,80 @@
+#ifndef _KEY_VALUE_H_
+#define _KEY_VALUE_H_
+
+#include "config.h"
+
+#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_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;
+#endif
+
+ char *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 00000000..55e558db
--- /dev/null
+++ b/src/lemon.c
@@ -0,0 +1,4397 @@
+/*
+** 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 {B_FALSE=0, B_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 = B_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==B_FALSE ) break;
+ }
+ if( i==rp->nrhs ){
+ rp->lhs->lambda = B_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==B_FALSE ) break;
+ }else{
+ progress += SetUnion(s1->firstset,s2->firstset);
+ if( s2->lambda==B_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 state *stp;
+ 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 */
+ 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 = B_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 = B_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==B_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;
+ int 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;
+ char *name;
+ int mnTknOfst, mxTknOfst;
+ int mnNtOfst, mxNtOfst;
+ struct axset *ax;
+
+ 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 ){
+ char *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] ){
+ int i;
+ 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 size = 0;
+
+/* Set the set size */
+void SetSize(n)
+int n;
+{
+ size = n+1;
+}
+
+/* Allocate a new set */
+char *SetNew(){
+ char *s;
+ int i;
+ s = (char*)malloc( size );
+ if( s==0 ){
+ extern void memory_error();
+ memory_error();
+ }
+ for(i=0; i<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<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 = B_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 00000000..ee1edbfa
--- /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 00000000..7d7a3331
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,238 @@
+#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>
+
+#include "config.h"
+
+#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
+ *
+ * if the open failed, report to the user and die
+ * if no filename is given, use syslog instead
+ *
+ */
+
+int log_error_open(server *srv) {
+ int fd;
+ int close_stderr = 1;
+
+ if (srv->srvconf.error_logfile->used) {
+ const char *logfile = srv->srvconf.error_logfile->ptr;
+
+ if (-1 == (srv->log_error_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->log_error_fd, F_SETFD, FD_CLOEXEC);
+#endif
+ } else {
+ srv->log_error_fd = -1;
+#ifdef HAVE_SYSLOG_H
+ /* syslog-mode */
+ srv->log_using_syslog = 1;
+ openlog("lighttpd", LOG_CONS, LOG_LOCAL0);
+#endif
+ }
+
+ 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
+
+ /* 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 (0 == srv->log_using_syslog) {
+ const char *logfile = srv->srvconf.error_logfile->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->log_error_fd);
+ srv->log_error_fd = -1;
+#ifdef HAVE_SYSLOG_H
+ /* fall back to syslog() */
+ srv->log_using_syslog = 1;
+
+ openlog("lighttpd", LOG_CONS, LOG_LOCAL0);
+#endif
+ } else {
+ /* ok, new log is open, close the old one */
+ close(srv->log_error_fd);
+ srv->log_error_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");
+
+ if (srv->log_error_fd >= 0) {
+ close(srv->log_error_fd);
+ } else if(srv->log_using_syslog) {
+#ifdef HAVE_SYSLOG_H
+ closelog();
+#endif
+ }
+
+ return 0;
+}
+
+int log_error_write(server *srv, const char *filename, unsigned int line, const char *fmt, ...) {
+ va_list ap;
+
+ if (srv->log_using_syslog == 0) {
+ /* 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->error_log, srv->ts_debug_str);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, ": (");
+ } else {
+ BUFFER_COPY_STRING_CONST(srv->error_log, "(");
+ }
+ buffer_append_string(srv->error_log, filename);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, ".");
+ buffer_append_long(srv->error_log, line);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, ") ");
+
+
+ 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->error_log, s);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, " ");
+ break;
+ case 'b': /* buffer */
+ b = va_arg(ap, buffer *);
+ buffer_append_string_buffer(srv->error_log, b);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, " ");
+ break;
+ case 'd': /* int */
+ d = va_arg(ap, int);
+ buffer_append_long(srv->error_log, d);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, " ");
+ break;
+ case 'o': /* off_t */
+ o = va_arg(ap, off_t);
+ buffer_append_off_t(srv->error_log, o);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, " ");
+ break;
+ case 'x': /* int (hex) */
+ d = va_arg(ap, int);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, "0x");
+ buffer_append_hex(srv->error_log, d);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, " ");
+ break;
+ case 'S': /* string */
+ s = va_arg(ap, char *);
+ buffer_append_string(srv->error_log, s);
+ break;
+ case 'B': /* buffer */
+ b = va_arg(ap, buffer *);
+ buffer_append_string_buffer(srv->error_log, b);
+ break;
+ case 'D': /* int */
+ d = va_arg(ap, int);
+ buffer_append_long(srv->error_log, d);
+ break;
+ case '(':
+ case ')':
+ case '<':
+ case '>':
+ case ',':
+ case ' ':
+ buffer_append_string_len(srv->error_log, fmt, 1);
+ break;
+ }
+ }
+ va_end(ap);
+
+ BUFFER_APPEND_STRING_CONST(srv->error_log, "\n");
+
+ if (srv->log_error_fd >= 0) {
+ write(srv->log_error_fd, srv->error_log->ptr, srv->error_log->used - 1);
+ } else if (srv->log_using_syslog == 0) {
+ /* only available at startup time */
+ write(STDERR_FILENO, srv->error_log->ptr, srv->error_log->used - 1);
+ } else {
+#ifdef HAVE_SYSLOG_H
+ syslog(LOG_ERR, "%s", srv->error_log->ptr);
+#endif
+ }
+
+ return 0;
+}
+
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 00000000..bffee3a2
--- /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 00000000..5696b71a
--- /dev/null
+++ b/src/md5.c
@@ -0,0 +1,356 @@
+/* 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.
+ */
+
+#include "config.h"
+
+#ifndef USE_OPENSSL
+#include <string.h>
+
+#include "md5_global.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 PROTO_LIST ((UINT4 [4], unsigned char [64]));
+static void Encode PROTO_LIST
+ ((unsigned char *, UINT4 *, unsigned int));
+static void Decode PROTO_LIST
+ ((UINT4 *, unsigned char *, unsigned int));
+
+#ifdef HAVE_MEMCPY
+#define MD5_memcpy(output, input, len) memcpy((output), (input), (len))
+#else
+static void MD5_memcpy PROTO_LIST ((POINTER, POINTER, unsigned int));
+#endif
+#ifdef HAVE_MEMSET
+#define MD5_memset(output, value, len) memset((output), (value), (len))
+#else
+static void MD5_memset PROTO_LIST ((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 00000000..4b601484
--- /dev/null
+++ b/src/md5.h
@@ -0,0 +1,37 @@
+/* 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.
+ */
+
+/* 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 PROTO_LIST ((MD5_CTX *));
+void MD5_Update PROTO_LIST
+ ((MD5_CTX *, unsigned char *, unsigned int));
+void MD5_Final PROTO_LIST ((unsigned char [16], MD5_CTX *));
+
diff --git a/src/md5_global.h b/src/md5_global.h
new file mode 100644
index 00000000..6a5c94ff
--- /dev/null
+++ b/src/md5_global.h
@@ -0,0 +1,31 @@
+/* GLOBAL.H - RSAREF types and constants
+ */
+
+/* PROTOTYPES should be set to one if and only if the compiler supports
+ function argument prototyping.
+The following makes PROTOTYPES default to 0 if it has not already
+
+ been defined with C compiler flags.
+ */
+#ifndef PROTOTYPES
+#define PROTOTYPES 0
+#endif
+
+/* POINTER defines a generic pointer type */
+typedef unsigned char *POINTER;
+
+/* UINT2 defines a two byte word */
+typedef unsigned short int UINT2;
+
+/* UINT4 defines a four byte word */
+typedef unsigned long int UINT4;
+
+/* PROTO_LIST is defined depending on how PROTOTYPES is defined above.
+If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it
+ returns an empty list.
+ */
+#if PROTOTYPES
+#define PROTO_LIST(list) list
+#else
+#define PROTO_LIST(list) ()
+#endif
diff --git a/src/mod_access.c b/src/mod_access.c
new file mode 100644
index 00000000..b2c81cfb
--- /dev/null
+++ b/src/mod_access.c
@@ -0,0 +1,177 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "config.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 = 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->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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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.access-deny"))) {
+ PATCH(access_deny);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_access_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(access_deny);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_access_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_access_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_access_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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 (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 00000000..22574b12
--- /dev/null
+++ b/src/mod_accesslog.c
@@ -0,0 +1,840 @@
+#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_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_UNSUPPORTED },
+ { '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->access_logbuffer->used) {
+ if (s->use_syslog) {
+# ifdef HAVE_SYSLOG_H
+ syslog(LOG_INFO, "%*s", s->access_logbuffer->used - 1, 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 = malloc(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 %l %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__, "s", "config: ", "failed");
+ 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) {
+ if (srv->log_using_syslog == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbs",
+ "accesslog can only be written to syslog if errorlog is also sent to syslog. ABORTING.");
+
+ return HANDLER_ERROR;
+ }
+
+ /* 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 -1;
+ }
+
+ /* 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
+ syslog(LOG_INFO, "%*s", s->access_logbuffer->used - 1, 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);
+ }
+ s->access_logbuffer->used = 0;
+ }
+
+ if (s->use_syslog == 0 &&
+ 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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("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;
+}
+
+static int mod_accesslog_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ 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);
+
+ return 0;
+}
+#undef PATCH
+
+REQUESTDONE_FUNC(log_access_write) {
+ plugin_data *p = p_d;
+ size_t j, i;
+
+ int newts = 0;
+ data_string *ds;
+
+ mod_accesslog_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_accesslog_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ if (p->conf.access_logbuffer->used == 0) {
+ buffer_copy_string(p->conf.access_logbuffer, "");
+ }
+
+ 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(p->conf.access_logbuffer, 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(p->conf.access_logbuffer, p->conf.ts_accesslog_str);
+
+ break;
+ case FORMAT_REMOTE_HOST:
+
+ /* handle inet_ntop cache */
+
+ buffer_append_string(p->conf.access_logbuffer, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
+
+ break;
+ case FORMAT_REMOTE_IDENT:
+ /* ident */
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ break;
+ case FORMAT_REMOTE_USER:
+ if (con->authed_user->used > 1) {
+ buffer_append_string_buffer(p->conf.access_logbuffer, con->authed_user);
+ } else {
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ }
+ break;
+ case FORMAT_REQUEST_LINE:
+ if (con->request.request_line->used) {
+ buffer_append_string_buffer(p->conf.access_logbuffer, con->request.request_line);
+ }
+ break;
+ case FORMAT_STATUS:
+ buffer_append_long(p->conf.access_logbuffer, con->http_status);
+ break;
+
+ case FORMAT_BYTES_OUT_NO_HEADER:
+ if (con->bytes_written > 0) {
+ buffer_append_off_t(p->conf.access_logbuffer,
+ con->bytes_written - con->bytes_header <= 0 ? 0 : con->bytes_written - con->bytes_header);
+ } else {
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ }
+ 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(p->conf.access_logbuffer, ds->value);
+ } else {
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ }
+ 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(p->conf.access_logbuffer, ds->value);
+ } else {
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ }
+ break;
+ case FORMAT_FILENAME:
+ if (con->physical.path->used > 1) {
+ buffer_append_string_buffer(p->conf.access_logbuffer, con->physical.path);
+ } else {
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ }
+ break;
+ case FORMAT_BYTES_OUT:
+ if (con->bytes_written > 0) {
+ buffer_append_off_t(p->conf.access_logbuffer, con->bytes_written);
+ } else {
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ }
+ break;
+ case FORMAT_TIME_USED:
+ buffer_append_long(p->conf.access_logbuffer, srv->cur_ts - con->request_start);
+ break;
+ case FORMAT_SERVER_NAME:
+ buffer_append_string_buffer(p->conf.access_logbuffer, con->server_name);
+ break;
+ case FORMAT_REQUEST_PROTOCOL:
+ buffer_append_string(p->conf.access_logbuffer,
+ con->request.http_version == HTTP_VERSION_1_1 ? "HTTP/1.1" : "HTTP/1.0");
+ break;
+ case FORMAT_REQUEST_METHOD:
+ switch(con->request.http_method) {
+ case HTTP_METHOD_GET: buffer_append_string(p->conf.access_logbuffer, "GET"); break;
+ case HTTP_METHOD_POST: buffer_append_string(p->conf.access_logbuffer, "POST"); break;
+ case HTTP_METHOD_HEAD: buffer_append_string(p->conf.access_logbuffer, "HEAD"); break;
+ default: break;
+ }
+ break;
+ case FORMAT_SERVER_PORT:
+ buffer_append_long(p->conf.access_logbuffer, srv->srvconf.port);
+ break;
+ case FORMAT_QUERY_STRING:
+ buffer_append_string_buffer(p->conf.access_logbuffer, con->uri.query);
+ break;
+ case FORMAT_URL:
+ buffer_append_string_buffer(p->conf.access_logbuffer, con->uri.path_raw);
+ break;
+ case FORMAT_CONNECTION_STATUS:
+ switch(con->keep_alive) {
+ case 0: buffer_append_string(p->conf.access_logbuffer, "-"); break;
+ default: buffer_append_string(p->conf.access_logbuffer, "+"); break;
+ }
+ break;
+ default:
+ /*
+ { 'a', FORMAT_REMOTE_ADDR },
+ { 'A', FORMAT_LOCAL_ADDR },
+ { 'C', FORMAT_COOKIE },
+ { 'D', FORMAT_TIME_USED_MS },
+ { 'e', FORMAT_ENV },
+ { 'I', FORMAT_BYTES_IN },
+ { 'O', FORMAT_BYTES_OUT },
+ */
+
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "\n");
+
+ if (newts || p->conf.access_logbuffer->used > BUFFER_MAX_REUSE_SIZE) {
+ if (p->conf.use_syslog) {
+#ifdef HAVE_SYSLOG_H
+ syslog(LOG_INFO, "%*s", p->conf.access_logbuffer->used - 1, p->conf.access_logbuffer->ptr);
+#endif
+ } else if (p->conf.log_access_fd != -1) {
+ write(p->conf.log_access_fd, p->conf.access_logbuffer->ptr, p->conf.access_logbuffer->used - 1);
+ }
+ buffer_reset(p->conf.access_logbuffer);
+ }
+
+ 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 00000000..501f5fac
--- /dev/null
+++ b/src/mod_alias.c
@@ -0,0 +1,185 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "config.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 = 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->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;
+ }
+ }
+
+ 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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("alias.url"))) {
+ PATCH(alias);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_alias_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(alias);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_alias_docroot_handler) {
+ plugin_data *p = p_d;
+ int uri_len;
+ size_t k, i;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_alias_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_alias_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ uri_len = con->uri.path->used - 1;
+
+ 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(con->uri.path->ptr, ds->key->ptr, alias_len)) {
+ /* matched */
+
+ buffer_copy_string_buffer(con->physical.doc_root, ds->value);
+ buffer_copy_string(con->physical.rel_path, con->uri.path->ptr + alias_len);
+
+ 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_docroot = mod_alias_docroot_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 00000000..824b8f73
--- /dev/null
+++ b/src/mod_auth.c
@@ -0,0 +1,565 @@
+#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"
+
+
+/**
+ * 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];
+
+ 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_filter);
+
+#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, const char *stage, size_t stage_len) {
+ 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];
+ mod_auth_plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("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);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_auth_setup_connection(server *srv, connection *con, mod_auth_plugin_data *p) {
+ mod_auth_plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ 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_filter);
+#ifdef USE_LDAP
+ PATCH(ldap);
+ PATCH(ldap_filter_pre);
+ PATCH(ldap_filter_post);
+#endif
+
+ 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;
+ size_t i;
+
+ /* select the right config */
+ mod_auth_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_auth_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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++) {
+ if (p->conf.auth_require->data[k]->key->used == 0) continue;
+
+ if (0 == strncmp(con->uri.path->ptr, p->conf.auth_require->data[k]->key->ptr, p->conf.auth_require->data[k]->key->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.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 }, /* 9 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = malloc(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 = malloc(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_filter = buffer_init();
+ 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_htdigest_userfile;
+ cv[8].destination = s->auth_htpasswd_userfile;
+ cv[9].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;
+ }
+
+ 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:
+#ifdef USE_LDAP
+#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) {
+ int ret;
+ 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;
+ }
+
+
+ /* 1. */
+ 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
+ break;
+ default:
+ break;
+ }
+ }
+
+ 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 00000000..e69de29b
--- /dev/null
+++ b/src/mod_auth.h
diff --git a/src/mod_cgi.c b/src/mod_cgi.c
new file mode 100644
index 00000000..ff91d5f7
--- /dev/null
+++ b/src/mod_cgi.c
@@ -0,0 +1,1191 @@
+#include <sys/types.h>
+#ifdef __WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#include <sys/wait.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 "server.h"
+#include "keyvalue.h"
+#include "log.h"
+#include "connections.h"
+#include "joblist.h"
+#include "http_chunk.h"
+
+#include "plugin.h"
+
+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 = 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));
+ 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
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "emergency exit: cgi",
+ con->fd,
+ hctx->fd);
+#endif
+
+ /* the connection to the browser went away, but we still have a connection
+ * to the CGI script
+ *
+ * close cgi-connection
+ */
+
+ /* 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_FINISHED;
+
+ 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:", pid);
+#endif
+ pid = 0;
+
+ return HANDLER_FINISHED;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "cgi died, pid:", pid);
+ pid = 0;
+ return HANDLER_FINISHED;
+ }
+ }
+
+
+ kill(pid, SIGTERM);
+
+ /* cgi-script is still alive, queue the PID for removal */
+ cgi_pid_add(srv, p, pid);
+ }
+#endif
+ return HANDLER_FINISHED;
+}
+
+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
+
+ break;
+ 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) {
+ int val_len;
+ char *dst;
+
+ if (!key || !val) return -1;
+
+ val_len = strlen(val);
+
+ 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
+
+ /* 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;
+ 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]);
+
+ /* create environment */
+ env.ptr = NULL;
+ env.size = 0;
+ env.used = 0;
+
+#ifdef PACKAGE_NAME
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), PACKAGE_NAME"/"PACKAGE_VERSION);
+#else
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), PACKAGE"/"VERSION);
+#endif
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"),
+ con->server_name->used ?
+ con->server_name->ptr :
+#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
+ );
+ cgi_env_add(&env, CONST_STR_LEN("GATEWAY_INTERFACE"), "CGI/1.1");
+
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_PROTOCOL"), get_http_version_name(con->request.http_version));
+
+ 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);
+ cgi_env_add(&env, CONST_STR_LEN("REQUEST_METHOD"), get_http_method_name(con->request.http_method));
+ if (con->request.pathinfo->used) {
+ cgi_env_add(&env, CONST_STR_LEN("PATH_INFO"), con->request.pathinfo->ptr);
+ }
+ cgi_env_add(&env, CONST_STR_LEN("REDIRECT_STATUS"), "200");
+ cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), con->uri.query->used ? con->uri.query->ptr : "");
+
+
+ cgi_env_add(&env, CONST_STR_LEN("REMOTE_ADDR"),
+#ifdef HAVE_IPV6
+ 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
+ inet_ntoa(con->dst_addr.ipv4.sin_addr)
+#endif
+ );
+
+ if (con->authed_user->used) {
+ cgi_env_add(&env, CONST_STR_LEN("REMOTE_USER"),
+ con->authed_user->ptr);
+ }
+
+ /* request.content_length < SSIZE_MAX, see request.c */
+ ltostr(buf, con->request.content_length);
+ cgi_env_add(&env, CONST_STR_LEN("CONTENT_LENGTH"), buf);
+ cgi_env_add(&env, CONST_STR_LEN("SCRIPT_FILENAME"), con->physical.path->ptr);
+ cgi_env_add(&env, CONST_STR_LEN("SCRIPT_NAME"), con->uri.path->ptr);
+
+ /* for valgrind */
+ cgi_env_add(&env, CONST_STR_LEN("LD_PRELOAD"), getenv("LD_PRELOAD"));
+ cgi_env_add(&env, CONST_STR_LEN("LD_LIBRARY_PATH"), getenv("LD_LIBRARY_PATH"));
+#ifdef __CYGWIN__
+ /* CYGWIN needs SYSTEMROOT */
+ cgi_env_add(&env, CONST_STR_LEN("SYSTEMROOT"), getenv("SYSTEMROOT"));
+#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++) {
+ 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), ds->value->ptr);
+ }
+ }
+
+ 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), ds->value->ptr);
+ }
+ }
+
+ 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->log_error_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->used) {
+ write(to_cgi_fds[1], con->request.content->ptr, con->request.content_length);
+ }
+
+ 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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("cgi.assign"))) {
+ PATCH(cgi);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_cgi_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(cgi);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(cgi_is_handled) {
+ size_t k, s_len, i;
+ plugin_data *p = p_d;
+ buffer *fn = con->physical.path;
+
+ if (fn->used == 0) return HANDLER_ERROR;
+
+ mod_cgi_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_cgi_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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;
+ }
+ }
+
+ 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:
+ /* not finished yet */
+ if (con->file_started) {
+ return HANDLER_GO_ON;
+ } else {
+ 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->handle_connection_close = 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_compress.c b/src/mod_compress.c
new file mode 100644
index 00000000..726d086d
--- /dev/null
+++ b/src/mod_compress.c
@@ -0,0 +1,744 @@
+#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 "plugin.h"
+
+#include "crc32.h"
+
+#include "config.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];
+
+ 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;
+ int ret = HANDLER_GO_ON;
+
+ 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 = 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->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 != (ret = config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv))) {
+ ret = HANDLER_ERROR;
+
+ break;
+ }
+
+ ret = HANDLER_GO_ON;
+
+ 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 ret;
+
+}
+
+#ifdef USE_ZLIB
+static int deflate_file_to_buffer_gzip(server *srv, connection *con, plugin_data *p, unsigned 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 = 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, file_cache_entry *fce, int type) {
+ int ifd, ofd;
+ int ret = -1;
+ void *start;
+ const char *filename = fn->ptr;
+ ssize_t r;
+
+ /* overflow */
+ if ((off_t)(fce->st.st_size * 1.1) < fce->st.st_size) return -1;
+
+ /* don't mmap files > size_t
+ *
+ * we could use a sliding window, but currently there is no need for it
+ */
+
+ if (fce->st.st_size > SIZE_MAX) 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__, "ssss", "creating cache-directory", p->b->ptr, "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, fce->etag);
+
+ if (-1 == (ofd = open(p->ofn->ptr, O_WRONLY | O_CREAT | O_EXCL, 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))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "opening plain-file", fn, "failed", strerror(errno));
+
+ close(ofd);
+
+ return -1;
+ }
+
+
+ if (MAP_FAILED == (start = mmap(NULL, fce->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, fce->st.st_size, fce->st.st_mtime);
+ break;
+ case HTTP_ACCEPT_ENCODING_DEFLATE:
+ ret = deflate_file_to_buffer_deflate(srv, con, p, start, fce->st.st_size);
+ break;
+#endif
+#ifdef USE_BZ2LIB
+ case HTTP_ACCEPT_ENCODING_BZIP2:
+ ret = deflate_file_to_buffer_bzip2(srv, con, p, start, fce->st.st_size);
+ break;
+#endif
+ default:
+ ret = -1;
+ break;
+ }
+
+ if (-1 == (r = write(ofd, p->b->ptr, p->b->used))) {
+ return -1;
+ }
+
+ if ((size_t)r != p->b->used) {
+
+ }
+
+ munmap(start, fce->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, file_cache_entry *fce, int type) {
+ int ifd;
+ int ret = -1;
+ void *start;
+ buffer *b;
+
+ /* overflow */
+ if ((off_t)(fce->st.st_size * 1.1) < fce->st.st_size) return -1;
+
+ /* don't mmap files > size_t
+ *
+ * we could use a sliding window, but currently there is no need for it
+ */
+
+ if (fce->st.st_size > SIZE_MAX) return -1;
+
+
+ if (-1 == (ifd = open(fn->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "opening plain-file", fn, "failed", strerror(errno));
+
+ return -1;
+ }
+
+
+ if (MAP_FAILED == (start = mmap(NULL, fce->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, fce->st.st_size, fce->st.st_mtime);
+ break;
+ case HTTP_ACCEPT_ENCODING_DEFLATE:
+ ret = deflate_file_to_buffer_deflate(srv, con, p, start, fce->st.st_size);
+ break;
+#endif
+#ifdef USE_BZ2LIB
+ case HTTP_ACCEPT_ENCODING_BZIP2:
+ ret = deflate_file_to_buffer_bzip2(srv, con, p, start, fce->st.st_size);
+ break;
+#endif
+ default:
+ ret = -1;
+ break;
+ }
+
+ munmap(start, fce->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);
+
+ 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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("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;
+}
+
+static int mod_compress_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(compress_cache_dir);
+ PATCH(compress);
+ PATCH(compress_max_filesize);
+
+ return 0;
+}
+#undef PATCH
+
+PHYSICALPATH_FUNC(mod_compress_physical) {
+ plugin_data *p = p_d;
+ data_string *content_ds;
+ size_t m, i;
+ off_t max_fsize;
+
+ /* 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;
+ }
+
+ mod_compress_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_compress_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+
+ max_fsize = p->conf.compress_max_filesize;
+
+ /* don't compress files that are too large as we need to much time to handle them */
+ if (max_fsize && (con->fce->st.st_size >> 10) > max_fsize) return HANDLER_GO_ON;
+
+ if (NULL == (content_ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbb", "Content-Type is not set for", con->physical.path, con->uri.path);
+
+ 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, content_ds->value)) {
+ /* 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, con->fce, compression_type)) {
+ struct tm *tm;
+ time_t last_mod;
+
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Encoding"), compression_name, strlen(compression_name));
+
+ /* Set Last-Modified of ORIGINAL file */
+ last_mod = con->fce->st.st_mtime;
+
+ for (i = 0; i < FILE_CACHE_MAX; i++) {
+ if (srv->mtime_cache[i].mtime == last_mod) break;
+
+ if (srv->mtime_cache[i].mtime == 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++;
+ 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++;
+ }
+
+ response_header_insert(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(srv->mtime_cache[i].str));
+
+ return HANDLER_FINISHED;
+ }
+ } else if (0 == deflate_file_to_buffer(srv, con, p,
+ con->physical.path, con->fce, compression_type)) {
+
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Encoding"), compression_name, strlen(compression_name));
+
+ 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_physical_path = mod_compress_physical;
+ p->cleanup = mod_compress_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_evhost.c b/src/mod_evhost.c
new file mode 100644
index 00000000..4a07a923
--- /dev/null
+++ b/src/mod_evhost.c
@@ -0,0 +1,340 @@
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "plugin.h"
+#include "log.h"
+#include "response.h"
+#include "file_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->path_pieces) {
+ for (i = 0; i < s->len; i++) {
+ buffer_free(s->path_pieces[i]);
+ }
+
+ 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 = malloc(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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("evhost.path-pattern"))) {
+ PATCH(path_pieces);
+ PATCH(len);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_evhost_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ 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;
+
+ /* not authority set */
+ if (con->uri.authority->used == 0) return HANDLER_GO_ON;
+
+ mod_evhost_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_evhost_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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_GO_ON != file_cache_get_entry(srv, con, p->tmp_buf, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf);
+ not_good = 1;
+ } else if(!S_ISDIR(con->fce->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 00000000..e22e2a32
--- /dev/null
+++ b/src/mod_expire.c
@@ -0,0 +1,356 @@
+#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"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/**
+ * 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 = 0;
+ 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 = 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->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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("expire.url"))) {
+ PATCH(expire_url);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_expire_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(expire_url);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_expire_uri_handler) {
+ plugin_data *p = p_d;
+ int s_len;
+ size_t k, i;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_expire_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_expire_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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;
+
+ mod_expire_get_offset(srv, p, ds->value, &ts);
+ t = (ts += srv->cur_ts);
+
+ 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;
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Expires"), 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_uri_clean = mod_expire_uri_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 00000000..5438e6fa
--- /dev/null
+++ b/src/mod_fastcgi.c
@@ -0,0 +1,3209 @@
+#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 <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 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;
+} fcgi_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
+ */
+ 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;
+
+
+ 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 */
+} 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;
+
+ 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;
+
+ 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
+} fcgi_connection_state_t;
+
+typedef struct {
+ buffer *response;
+ size_t response_len;
+ int response_type;
+ int response_padding;
+ size_t response_request_id;
+
+ fcgi_proc *proc;
+ fcgi_extension_host *host;
+
+ fcgi_connection_state_t state;
+ time_t state_timestamp;
+
+ int reconnects; /* number of reconnect attempts */
+
+ buffer *write_buffer;
+ size_t write_offset;
+
+ read_buffer *rb;
+
+ buffer *response_header;
+
+ int delayed; /* flag to mark that the connect() is delayed */
+
+ size_t request_id;
+ int fd; /* fd to the fastcgi process */
+ int fde_ndx; /* index into the fd-event buffer */
+
+ size_t path_info_offset; /* start of path_info in uri.path */
+
+ 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);
+
+
+
+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->write_buffer = 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->response_request_id = 0;
+ hctx->fd = -1;
+
+ hctx->reconnects = 0;
+
+ return hctx;
+}
+
+static void handler_ctx_free(handler_ctx *hctx) {
+ buffer_free(hctx->response);
+ buffer_free(hctx->response_header);
+ buffer_free(hctx->write_buffer);
+
+ if (hctx->rb) {
+ if (hctx->rb->ptr) free(hctx->rb->ptr);
+ free(hctx->rb);
+ }
+
+ 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->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 fastcgi_host_free(fcgi_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);
+
+ 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();
+
+ 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);
+
+ 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);
+
+ 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 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 - 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__,
+ "ssb", "gethostbyname failed: ",
+ hstrerror(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__, "ss",
+ "bind failed:", 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: {
+ buffer *b;
+ size_t i = 0;
+ char_array env;
+
+
+ /* create environment */
+ env.ptr = NULL;
+ env.size = 0;
+ env.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;
+
+ 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 FastCGI 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(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 */
+ { 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;
+
+ 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, "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 *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 */
+ { "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", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
+ { "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 */
+
+ { 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;
+ }
+
+ df = fastcgi_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->mode = FCGI_RESPONDER;
+ df->disable_time = 60;
+
+ fcv[0].destination = df->host;
+ fcv[1].destination = df->docroot;
+ fcv[2].destination = fcgi_mode;
+ fcv[3].destination = df->unixsocket;
+ fcv[4].destination = df->bin_path;
+
+ fcv[5].destination = &(df->check_local);
+ fcv[6].destination = &(df->port);
+ fcv[7].destination = &(df->min_procs);
+ fcv[8].destination = &(df->max_procs);
+ fcv[9].destination = &(df->max_load_per_proc);
+ fcv[10].destination = &(df->idle_timeout);
+ fcv[11].destination = &(df->disable_time);
+
+ fcv[12].destination = df->bin_env;
+ fcv[13].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",
+ "--- fastcgi 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++) {
+ fcgi_proc *proc;
+
+ proc = fastcgi_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",
+ "--- fastcgi spawning",
+ "\n\tport:", df->port,
+ "\n\tsocket", df->unixsocket,
+ "\n\tcurrent:", pno, "/", df->min_procs);
+ }
+
+ if (fcgi_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 {
+ fcgi_proc *fp;
+
+ fp = fastcgi_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 (!buffer_is_empty(fcgi_mode)) {
+ if (strcmp(fcgi_mode->ptr, "responder") == 0) {
+ df->mode = FCGI_RESPONDER;
+ } else if (strcmp(fcgi_mode->ptr, "authorizer") == 0) {
+ df->mode = FCGI_AUTHORIZER;
+ if (buffer_is_empty(df->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, df);
+ }
+ }
+ }
+ }
+
+ 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_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->request_id != 0) {
+ fcgi_requestid_del(srv, p, hctx->request_id);
+ }
+
+ if (hctx->host && hctx->proc) {
+ hctx->host->load--;
+ if (hctx->state != FCGI_STATE_INIT &&
+ hctx->state != FCGI_STATE_CONNECT) {
+ /* 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);
+ }
+ }
+
+ 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
+ *
+ */
+
+ 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);
+ }
+
+ hctx->proc->load--;
+ fcgi_proclist_sort_down(srv, hctx->host, hctx->proc);
+
+ return 0;
+}
+
+
+static handler_t fcgi_connection_reset(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ fcgi_connection_cleanup(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
+ */
+
+static int 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 (INADDR_NONE == (fcgi_addr_in.sin_addr.s_addr = inet_addr(host->host->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "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__, "sd",
+ "connect delayed:", fcgi_fd);
+ }
+
+ return 1;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sdsddb",
+ "connect failed:", fcgi_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: ", fcgi_fd);
+ }
+
+
+
+ return 0;
+}
+
+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++) {
+ 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';
+
+ 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++) {
+ 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';
+
+ 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;
+
+ char buf[32];
+ size_t offset;
+ 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;
+
+ /* 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));
+
+ buffer_copy_memory(hctx->write_buffer, (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));
+
+ 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.
+ */
+
+ if (hctx->path_info_offset == 0) { /* no pathinfo */
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path));
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"), CONST_STR_LEN(""));
+ } else { /* pathinfo */
+ *(con->uri.path->ptr + hctx->path_info_offset) = '\0'; /* get sctipt_name part */
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path));
+
+ *(con->uri.path->ptr + hctx->path_info_offset) = '/'; /* restore uri.path */
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"),
+ con->uri.path->ptr + hctx->path_info_offset,
+ con->uri.path->used - 1 - hctx->path_info_offset);
+
+ if (host->docroot->used) {
+ buffer_copy_string_buffer(p->path, host->docroot);
+ buffer_append_string(p->path, con->uri.path->ptr + hctx->path_info_offset);
+ 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_TRANSLATED"),
+ con->uri.path->ptr + hctx->path_info_offset,
+ con->uri.path->used - 1 - hctx->path_info_offset);
+ }
+ }
+ }
+
+ /*
+ * 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 {
+ if (con->request.pathinfo->used) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo));
+ }
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path));
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root));
+ }
+ 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(hctx->write_buffer, (const char *)&header, sizeof(header));
+ buffer_append_memory(hctx->write_buffer, (const char *)p->fcgi_env->ptr, p->fcgi_env->used);
+
+ fcgi_header(&(header), FCGI_PARAMS, request_id, 0, 0);
+ buffer_append_memory(hctx->write_buffer, (const char *)&header, sizeof(header));
+
+ /* send FCGI_STDIN */
+
+ /* something to send ? */
+ for (offset = 0; offset != con->request.content_length; ) {
+ /* send chunks of 1024 bytes */
+ size_t toWrite = con->request.content_length - offset > 4096 ? 4096 : con->request.content_length - offset;
+
+ fcgi_header(&(header), FCGI_STDIN, request_id, toWrite, 0);
+ buffer_append_memory(hctx->write_buffer, (const char *)&header, sizeof(header));
+ buffer_append_memory(hctx->write_buffer, (const char *)(con->request.content->ptr + offset), toWrite);
+
+ offset += toWrite;
+ }
+
+ /* terminate STDIN */
+ fcgi_header(&(header), FCGI_STDIN, request_id, 0, 0);
+ buffer_append_memory(hctx->write_buffer, (const char *)&header, 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);
+
+ /* \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;
+
+ ns[0] = '\0';
+ ns[1] = '\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 */
+ 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;
+}
+
+
+static int fcgi_demux_response(server *srv, handler_ctx *hctx) {
+ ssize_t len;
+ int fin = 0;
+ int b;
+ 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, &b)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "unexpected end-of-file (perhaps the fastcgi process died):",
+ fcgi_fd);
+ return -1;
+ }
+
+ /* init read-buffer */
+ if (hctx->rb == NULL) {
+ hctx->rb = calloc(1, sizeof(*hctx->rb));
+ }
+
+ if (b > 0) {
+ if (hctx->rb->size == 0) {
+ hctx->rb->size = b;
+ hctx->rb->ptr = malloc(hctx->rb->size * sizeof(*hctx->rb->ptr));
+ } else if (hctx->rb->size < hctx->rb->used + b) {
+ hctx->rb->size += b;
+ hctx->rb->ptr = realloc(hctx->rb->ptr, hctx->rb->size * sizeof(*hctx->rb->ptr));
+ }
+
+ /* append to read-buffer */
+ if (-1 == (r = read(hctx->fd, hctx->rb->ptr + hctx->rb->used, b))) {
+ 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);
+
+ hctx->rb->used += r;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsdsd",
+ "unexpected end-of-file (perhaps the fastcgi process died):",
+ "pid:", proc->pid,
+ "fcgi-fd:", fcgi_fd,
+ "remote-fd:", con->fd);
+
+ return -1;
+ }
+
+ /* parse all fcgi packets
+ *
+ * start: hctx->rb->ptr
+ * end : hctx->rb->ptr + hctx->rb->used
+ *
+ */
+ while (fin == 0) {
+ size_t request_id;
+
+ if (hctx->response_len == 0) {
+ FCGI_Header *header;
+
+ if (hctx->rb->used - hctx->rb->offset < sizeof(*header)) {
+ /* didn't get the full header packet (most often 0),
+ * but didn't recieved the final packet either
+ *
+ * we will come back later and finish everything
+ *
+ */
+
+ hctx->delayed = 1;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sddd", "didn't get the full header: ",
+ hctx->rb->used - hctx->rb->offset, sizeof(*header),
+ fcgi_fd
+ );
+#endif
+ break;
+ }
+#if 0
+ fprintf(stderr, "fcgi-version: %02x\n", hctx->rb->ptr[hctx->rb->offset]);
+#endif
+
+ header = (FCGI_Header *)(hctx->rb->ptr + hctx->rb->offset);
+ hctx->rb->offset += sizeof(*header);
+
+ len = (header->contentLengthB0 | (header->contentLengthB1 << 8)) + header->paddingLength;
+ request_id = (header->requestIdB0 | (header->requestIdB1 << 8));
+
+ hctx->response_len = len;
+ hctx->response_request_id = request_id;
+ hctx->response_type = header->type;
+ hctx->response_padding = header->paddingLength;
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sddd", "offset: ",
+ fcgi_fd, hctx->rb->offset, header->type
+ );
+#endif
+
+ } else {
+ len = hctx->response_len;
+ }
+
+ if (hctx->rb->used - hctx->rb->offset < hctx->response_len) {
+ /* we are not finished yet */
+ break;
+ }
+
+ hctx->response->ptr = hctx->rb->ptr + hctx->rb->offset;
+ hctx->rb->offset += hctx->response_len;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdd", "offset: ",
+ fcgi_fd, hctx->rb->offset
+ );
+#endif
+
+ /* remove padding */
+#if 0
+ hctx->response->ptr[hctx->response_len - hctx->response_padding] = '\0';
+#endif
+ hctx->response->used = hctx->response_len - hctx->response_padding + 1;
+
+ /* mark the fast-cgi packet as finished */
+ hctx->response_len = 0;
+
+ switch(hctx->response_type) {
+ case FCGI_STDOUT:
+ if (len) {
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdb", "len", len, 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__, "ss", "Header:", hctx->response_header->ptr);
+#endif
+ /* parse the response header */
+ fcgi_response_parse(srv, con, p, hctx->response_header);
+
+
+ if (host->mode != FCGI_AUTHORIZER ||
+ !(con->http_status == 0 ||
+ con->http_status == 200)) {
+ /* 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);
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "body-len", blen);
+#endif
+ }
+ }
+ } else {
+ /* copy everything */
+ buffer_append_string_buffer(hctx->response_header, hctx->response);
+ }
+ } else {
+ if (host->mode != FCGI_AUTHORIZER ||
+ !(con->http_status == 0 ||
+ con->http_status == 200)) {
+ http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used);
+ joblist_append(srv, con);
+ }
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "body-len", hctx->response->used);
+#endif
+ }
+ } else {
+ /* finished */
+ }
+
+ break;
+ case FCGI_STDERR:
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "FastCGI-stderr:", hctx->response);
+
+ 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: ", hctx->response_type);
+ break;
+ }
+ }
+
+ hctx->response->ptr = NULL;
+
+ 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 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 r;
+
+ /* sanity check */
+ if (!host ||
+ ((!host->host->used || !host->port) && !host->unixsocket->used)) return HANDLER_ERROR;
+
+ switch(hctx->state) {
+ case FCGI_STATE_INIT:
+ r = host->unixsocket->used ? AF_UNIX : AF_INET;
+
+ if (-1 == (hctx->fd = socket(r, 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;
+ }
+
+ /* 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;
+ }
+
+
+ switch (fcgi_establish_connection(srv, hctx)) {
+ case 1:
+ fcgi_set_state(srv, hctx, FCGI_STATE_CONNECT);
+
+ /* connection is in progress, wait for an event and call getsockopt() below */
+
+ 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) {
+ 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;
+
+ 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);
+ }
+
+ fcgi_set_state(srv, hctx, FCGI_STATE_PREPARE_WRITE);
+ /* fall through */
+ case FCGI_STATE_PREPARE_WRITE:
+ fcgi_create_env(srv, hctx, hctx->request_id);
+
+ fcgi_set_state(srv, hctx, FCGI_STATE_WRITE);
+ hctx->write_offset = 0;
+
+ /* fall through */
+ case FCGI_STATE_WRITE:
+ /* continue with the code after the switch */
+ if (-1 == (r = write(hctx->fd,
+ hctx->write_buffer->ptr + hctx->write_offset,
+ hctx->write_buffer->used - hctx->write_offset))) {
+
+ 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->write_offset == 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->write_offset,
+ "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 {
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+ }
+
+ hctx->write_offset += r;
+
+ if (hctx->write_offset == hctx->write_buffer->used) {
+ fcgi_set_state(srv, hctx, FCGI_STATE_READ);
+ }
+
+ 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;
+}
+
+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 (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 > FCGI_RETRY_TIMEOUT)) {
+ 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)) {
+ 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);
+ }
+
+ 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 (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;
+}
+
+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;
+
+ /* ok, create the request */
+ switch(fcgi_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) {
+ log_error_write(srv, __FILE__, __LINE__, "sbdb", "connect() to fastcgi failed, restarting the request-handling:",
+ host->host,
+ proc->port,
+ proc->socket);
+
+ proc->state = PROC_STATE_DIED_WAIT_FOR_PID;
+ }
+
+ fcgi_restart_dead_procs(srv, p, host);
+
+ fcgi_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 {
+
+ fcgi_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:
+ return HANDLER_ERROR;
+ }
+}
+
+static handler_t fcgi_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: fastcgi:",
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+
+
+ fcgi_connection_cleanup(srv, hctx);
+
+ return HANDLER_FINISHED;
+}
+
+
+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;
+
+ joblist_append(srv, con);
+
+ 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_cleanup(srv, hctx);
+
+ con->mode = DIRECT;
+ con->file_started = 1; /* fcgi_extension won't touch the request afterwards */
+ } else {
+ /* we are done */
+ fcgi_connection_cleanup(srv, hctx);
+ }
+
+ return HANDLER_FINISHED;
+ case -1:
+ if (proc->pid) {
+ int status;
+ 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 (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->write_offset == 0 &&
+ hctx->reconnects < 5) {
+ fcgi_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->write_offset,
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+ fcgi_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 */
+ fcgi_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);
+ }
+
+ /* */
+
+
+ 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_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) {
+ /* 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);
+ }
+ } 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);
+ }
+
+ return HANDLER_FINISHED;
+}
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int fcgi_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("fastcgi.server"))) {
+ PATCH(exts);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("fastcgi.debug"))) {
+ PATCH(debug);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int fcgi_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(exts);
+ PATCH(debug);
+
+ 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;
+ int used = -1;
+ int ndx;
+ size_t k, i;
+ buffer *fn;
+ fcgi_extension *extension = NULL;
+ size_t path_info_offset;
+
+ /* 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;
+
+ /* select the right config */
+ fcgi_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ fcgi_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ path_info_offset = 0;
+
+ /* 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 "/fcgi_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;
+ }
+ }
+
+ /* 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++) {
+ fcgi_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) {
+ fcgi_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;
+ hctx = handler_ctx_init();
+
+ hctx->path_info_offset = path_info_offset;
+ 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;
+ }
+ return HANDLER_GO_ON;
+ } else {
+ 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;
+ 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;
+
+ return HANDLER_FINISHED;
+ }
+ } 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 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:
+ 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;
+
+ return fcgi_connection_close(srv, con->plugin_ctx[p->id]);
+}
+
+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_mysql_vhost.c b/src/mod_mysql_vhost.c
new file mode 100644
index 00000000..76ff96aa
--- /dev/null
+++ b/src/mod_mysql_vhost.c
@@ -0,0 +1,425 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <strings.h>
+
+#include "plugin.h"
+#include "config.h"
+#include "log.h"
+
+#include "file_cache.h"
+#ifdef HAVE_MYSQL
+#include <mysql/mysql.h>
+#endif
+#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
+ */
+
+typedef struct {
+#ifdef HAVE_MYSQL
+ MYSQL *mysql;
+#endif
+ buffer *mydb;
+ buffer *myuser;
+ buffer *mypass;
+ buffer *mysock;
+
+ 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];
+#ifdef HAVE_MYSQL
+ mysql_close(s->mysql);
+#endif
+ 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 },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ buffer *sel;
+
+
+ s = malloc(sizeof(plugin_config));
+ s->mydb = buffer_init();
+ s->myuser = buffer_init();
+ s->mypass = buffer_init();
+ s->mysock = buffer_init();
+ sel = buffer_init();
+#ifdef HAVE_MYSQL
+ s->mysql = NULL;
+#endif
+
+ 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;
+
+ 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);
+ }
+
+ /* all have to be set */
+ if (!(buffer_is_empty(s->myuser) ||
+ buffer_is_empty(s->mypass) ||
+ buffer_is_empty(s->mydb) ||
+ buffer_is_empty(s->mysock))) {
+#ifdef HAVE_MYSQL
+ int fd;
+
+ if (NULL == (s->mysql = mysql_init(NULL))) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "mysql_init() failed, exiting...");
+
+ return HANDLER_ERROR;
+ }
+
+ if (!mysql_real_connect(s->mysql, NULL, s->myuser->ptr, s->mypass->ptr,
+ s->mydb->ptr, 0, s->mysock->ptr, 0)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(s->mysql));
+
+ return HANDLER_ERROR;
+ }
+
+ /* 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);
+ }
+#endif
+ }
+ }
+
+
+
+ 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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("mysql-vhost.sql"))) {
+ PATCH(mysql_pre);
+ PATCH(mysql_post);
+ }
+ }
+
+#ifdef HAVE_MYSQL
+ if (s->mysql) {
+ PATCH(mysql);
+ }
+#endif
+ }
+
+ return 0;
+}
+
+static int mod_mysql_vhost_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(mysql_pre);
+ PATCH(mysql_post);
+#ifdef HAVE_MYSQL
+ PATCH(mysql);
+#endif
+
+ return 0;
+}
+#undef PATCH
+
+
+/* handle document root request */
+CONNECTION_FUNC(mod_mysql_vhost_handle_docroot) {
+#ifdef HAVE_MYSQL
+ plugin_data *p = p_d;
+ plugin_connection_data *c;
+
+ unsigned cols;
+ MYSQL_ROW row;
+ MYSQL_RES *result = NULL;
+ size_t i;
+
+ /* no host specified? */
+ if (!con->uri.authority->used) return HANDLER_GO_ON;
+
+ /* apply conditionals */
+ mod_mysql_vhost_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_mysql_vhost_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ /* 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 (file_cache_get_entry(srv, con, p->tmp_buf, &(con->fce)) != HANDLER_GO_ON) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf);
+ goto ERR500;
+ }
+ if (!S_ISDIR(con->fce->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;
+#else
+ UNUSED(srv);
+ UNUSED(con);
+ UNUSED(p_d);
+
+ return HANDLER_ERROR;
+#endif
+}
+
+/* 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_connection_close = mod_mysql_vhost_handle_connection_close;
+
+ p->set_defaults = mod_mysql_vhost_set_defaults;
+ p->handle_docroot = mod_mysql_vhost_handle_docroot;
+
+ return 0;
+}
+
diff --git a/src/mod_proxy.c b/src/mod_proxy.c
new file mode 100644
index 00000000..48bab9a4
--- /dev/null
+++ b/src/mod_proxy.c
@@ -0,0 +1,1093 @@
+#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 <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 struct {
+ array *extensions;
+ int debug;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *parse_response;
+
+ 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;
+
+ buffer *write_buffer;
+ size_t write_offset;
+
+
+ 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->write_buffer = buffer_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);
+ buffer_free(hctx->write_buffer);
+
+ free(hctx);
+}
+
+INIT_FUNC(mod_proxy_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->parse_response = buffer_init();
+
+ return p;
+}
+
+
+FREE_FUNC(mod_proxy_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ buffer_free(p->parse_response);
+
+ 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 */
+ { 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);
+
+ 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, "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();
+
+ 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;
+ } 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 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_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) return;
+
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+ if (hctx->fd != -1) {
+ close(hctx->fd);
+ srv->cur_fds--;
+ }
+
+ handler_ctx_free(hctx);
+ con->plugin_ctx[p->id] = NULL;
+}
+
+static handler_t mod_proxy_connection_reset(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ proxy_connection_cleanup(srv, con->plugin_ctx[p->id]);
+
+ return HANDLER_GO_ON;
+}
+
+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 -proxy_fd;
+ } else {
+
+ log_error_write(srv, __FILE__, __LINE__, "sdsd",
+ "connect failed:", proxy_fd, strerror(errno), errno);
+
+ proxy_connection_cleanup(srv, hctx);
+
+ return -1;
+ }
+ }
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connect succeeded: ", proxy_fd);
+ }
+ return proxy_fd;
+}
+
+static int proxy_create_env(server *srv, handler_ctx *hctx) {
+ size_t i;
+
+ connection *con = hctx->remote_conn;
+ UNUSED(srv);
+
+ /* build header */
+
+ buffer_reset(hctx->write_buffer);
+
+ /* request line */
+ switch(con->request.http_method) {
+ case HTTP_METHOD_GET:
+ BUFFER_COPY_STRING_CONST(hctx->write_buffer, "GET ");
+ break;
+ case HTTP_METHOD_POST:
+ BUFFER_COPY_STRING_CONST(hctx->write_buffer, "POST ");
+ break;
+ case HTTP_METHOD_HEAD:
+ BUFFER_COPY_STRING_CONST(hctx->write_buffer, "HEAD ");
+ break;
+ default:
+ return -1;
+ }
+
+ buffer_append_string_buffer(hctx->write_buffer, con->request.uri);
+ BUFFER_APPEND_STRING_CONST(hctx->write_buffer, " HTTP/1.0\r\n");
+
+ /* 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 (0 == strcmp(ds->key->ptr, "Connection")) continue;
+
+ buffer_append_string_buffer(hctx->write_buffer, ds->key);
+ BUFFER_APPEND_STRING_CONST(hctx->write_buffer, ": ");
+ buffer_append_string_buffer(hctx->write_buffer, ds->value);
+ BUFFER_APPEND_STRING_CONST(hctx->write_buffer, "\r\n");
+ }
+ }
+
+ BUFFER_APPEND_STRING_CONST(hctx->write_buffer, "X-Forwarded-For: ");
+ buffer_append_string(hctx->write_buffer, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
+ BUFFER_APPEND_STRING_CONST(hctx->write_buffer, "\r\n");
+
+ BUFFER_APPEND_STRING_CONST(hctx->write_buffer, "\r\n");
+
+ /* body */
+
+ if (con->request.http_method == HTTP_METHOD_POST &&
+ con->request.content_length) {
+ /* the buffer-string functions add an extra \0 at the end the memory-function don't */
+ hctx->write_buffer->used--;
+ buffer_append_memory(hctx->write_buffer, con->request.content->ptr, con->request.content_length);
+ }
+
+ 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;
+
+ 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++;
+
+
+ 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 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;
+ }
+ }
+
+ 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 (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 int proxy_write_request(server *srv, handler_ctx *hctx) {
+ data_proxy *host= hctx->host;
+
+ int r;
+
+ if (!host ||
+ (!host->host->used || !host->port)) return -1;
+
+ switch(hctx->state) {
+ case PROXY_STATE_INIT:
+ r = AF_INET;
+
+ if (-1 == (hctx->fd = socket(r, SOCK_STREAM, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed: ", strerror(errno));
+ return -1;
+ }
+ 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));
+
+ proxy_connection_cleanup(srv, hctx);
+
+ return -1;
+ }
+
+ proxy_set_state(srv, hctx, PROXY_STATE_CONNECT);
+ /* fall through */
+
+ case PROXY_STATE_CONNECT:
+ /* try to finish the connect() */
+ hctx->fd = proxy_establish_connection(srv, hctx);
+
+ if (hctx->fd == -1) {
+ hctx->fde_ndx = -1;
+ return -1;
+ }
+
+ if (hctx->fd < 0) {
+ hctx->fd = -hctx->fd;
+ proxy_set_state(srv, hctx, PROXY_STATE_CONNECT);
+ return 0;
+ }
+
+ 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);
+ hctx->write_offset = 0;
+
+ /* fall through */
+ case PROXY_STATE_WRITE:
+ /* continue with the code after the switch */
+ if (-1 == (r = write(hctx->fd,
+ hctx->write_buffer->ptr + hctx->write_offset,
+ hctx->write_buffer->used - hctx->write_offset))) {
+ if (errno != EAGAIN) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed:", strerror(errno), r);
+
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ hctx->write_offset += r;
+
+ if (hctx->write_offset == hctx->write_buffer->used) {
+ proxy_set_state(srv, hctx, PROXY_STATE_READ);
+ }
+
+ break;
+ case PROXY_STATE_READ:
+ /* waiting for a response */
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state");
+ return -1;
+ }
+
+ return 0;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_proxy_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("proxy.server"))) {
+ PATCH(extensions);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.debug"))) {
+ PATCH(debug);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_proxy_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(extensions);
+ PATCH(debug);
+
+ 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;
+ size_t i;
+
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+ /* select the right config */
+ mod_proxy_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_proxy_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ host = hctx->host;
+
+ /* not my job */
+ if (con->mode != p->id) return HANDLER_GO_ON;
+
+ /* ok, create the request */
+ if (-1 == proxy_write_request(srv, hctx)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbdd", "proxy-server disabled:",
+ host->host,
+ host->port,
+ hctx->fd);
+
+ /* disable this server */
+ host->usage = -1;
+ host->disable_ts = srv->cur_ts;
+
+ con->mode = DIRECT;
+ con->http_status = 503;
+ return HANDLER_FINISHED;
+ }
+
+ if (con->file_started == 1) {
+ return HANDLER_FINISHED;
+ } else {
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+}
+
+static handler_t proxy_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: proxy:",
+ "connection-fd:", con->fd,
+ "proxy-fd:", hctx->fd);
+
+
+
+ proxy_connection_cleanup(srv, hctx);
+
+ return HANDLER_FINISHED;
+}
+
+
+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;
+
+ joblist_append(srv, con);
+
+ if ((revents & FDEVENT_IN) &&
+ hctx->state == PROXY_STATE_READ) {
+ switch (proxy_demux_response(srv, hctx)) {
+ case 0:
+ break;
+ case 1:
+ hctx->host->usage--;
+
+ /* we are done */
+
+ if (chunkqueue_is_empty(con->write_queue)) {
+ connection_set_state(srv, con, CON_STATE_RESPONSE_END);
+ }
+
+ proxy_connection_cleanup(srv, hctx);
+
+ 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);
+ }
+
+ return HANDLER_FINISHED;
+ }
+ }
+
+ if (revents & FDEVENT_OUT) {
+ 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) {
+ log_error_write(srv, __FILE__, __LINE__, "sbSBSDS",
+ "error: unexpected close of proxy connection for",
+ con->uri.path,
+ "(no proxy process on host: ",
+ hctx->host->host,
+ ", port: ",
+ hctx->host->port,
+ " ?)" );
+
+#ifndef USE_LINUX_SIGIO
+ proxy_connection_close(srv, hctx);
+# if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "proxy-FDEVENT_HUP", con->fd);
+# endif
+ return HANDLER_ERROR;
+#endif
+ } else if (revents & FDEVENT_ERR) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "proxy: err");
+ /* kill all connections to the proxy process */
+
+ proxy_connection_close(srv, hctx);
+#if 1
+ log_error_write(srv, __FILE__, __LINE__, "s", "proxy-FDEVENT_ERR");
+#endif
+ return HANDLER_ERROR;
+ }
+
+ 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;
+ int used = -1;
+ int ndx;
+ size_t k, i;
+ 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;
+
+ /* select the right config */
+ mod_proxy_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_proxy_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ fn = con->uri.path;
+
+ if (fn->used == 0) {
+ return HANDLER_ERROR;
+ }
+
+ s_len = fn->used - 1;
+
+
+ path_info_offset = 0;
+
+ /* 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;
+ }
+ }
+
+ /* extension doesn't match */
+ if (k == p->conf.extensions->used) {
+ return HANDLER_GO_ON;
+ }
+
+ /* get best server */
+ for (k = 0, ndx = -1; k < extension->value->used; k++) {
+ data_proxy *host = (data_proxy *)extension->value->data[k];
+
+ /* enable the server again, perhaps it is back again */
+ if ((host->usage == -1) &&
+ (srv->cur_ts - host->disable_ts > PROXY_RETRY_TIMEOUT)) {
+ host->usage = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbd", "proxy-server re-enabled:",
+ host->host, host->port);
+ }
+
+ if (used == -1 || host->usage < used) {
+ used = host->usage;
+
+ ndx = k;
+ }
+ }
+
+ /* 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;
+
+ 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;
+}
+
+JOBLIST_FUNC(mod_proxy_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 PROXY_STATE_READ:
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+
+ break;
+ case PROXY_STATE_CONNECT:
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "unhandled proxy.state", hctx->state);
+ break;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+static handler_t mod_proxy_connection_close_callback(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ return proxy_connection_close(srv, con->plugin_ctx[p->id]);
+}
+
+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_reset;
+ p->handle_connection_close = mod_proxy_connection_close_callback;
+ p->handle_uri_clean = mod_proxy_check_extension;
+ p->handle_subrequest = mod_proxy_handle_subrequest;
+ p->handle_joblist = mod_proxy_handle_joblist;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_redirect.c b/src/mod_redirect.c
new file mode 100644
index 00000000..5c5ecf25
--- /dev/null
+++ b/src/mod_redirect.c
@@ -0,0 +1,271 @@
+#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;
+} 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;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ 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 = malloc(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 = malloc(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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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 (0 == strcmp(du->key->ptr, "url.redirect")) {
+ p->conf.redirect = s->redirect;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_redirect_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ p->conf.redirect = s->redirect;
+
+ 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_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_redirect_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ buffer_copy_string_buffer(p->match_buf, con->request.uri);
+
+ for (i = 0; i < p->conf.redirect->used; i++) {
+ pcre *match;
+ const char *pattern;
+ size_t pattern_len;
+ int n;
+# define N 10
+ int ovec[N * 3];
+
+ match = p->conf.redirect->kv[i]->key;
+ pattern = p->conf.redirect->kv[i]->value;
+ pattern_len = strlen(pattern);
+
+ 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(p->location);
+
+ start = 0; end = pattern_len;
+ for (k = 0; k < pattern_len; k++) {
+ if (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);
+
+ /* n is always > 0 */
+ if (num < (size_t)n) {
+ buffer_append_string(p->location, list[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;
+
+ 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 00000000..868abb5f
--- /dev/null
+++ b/src/mod_rewrite.c
@@ -0,0 +1,453 @@
+#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 final;
+} rewrite_rule;
+
+typedef struct {
+ rewrite_rule **ptr;
+
+ size_t used;
+ size_t size;
+} rewrite_rule_buffer;
+
+typedef struct {
+ rewrite_rule_buffer *rewrite;
+} plugin_config;
+
+typedef struct {
+ enum { REWRITE_STATE_UNSET, REWRITE_STATE_FINISHED} state;
+} 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;
+
+ 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 final) {
+#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]->final = final;
+
+ kvb->used++;
+
+ return 0;
+#else
+ UNUSED(kvb);
+ UNUSED(value);
+ UNUSED(final);
+ 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;
+}
+
+SETDEFAULTS_FUNC(mod_rewrite_set_defaults) {
+ plugin_data *p = p_d;
+ data_unset *du;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "url.rewrite", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "url.rewrite-final", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ /* 0 */
+ p->config_storage = malloc(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 = malloc(sizeof(plugin_config));
+ s->rewrite = rewrite_rule_buffer_init();
+
+ cv[0].destination = s->rewrite;
+ cv[1].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;
+ }
+
+ if (NULL != (du = array_get_element(ca, "url.rewrite"))) {
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", "url.rewrite", "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.rewrite",
+ "[", 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,
+ 0)) {
+#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
+ }
+ }
+ }
+
+ if (NULL != (du = array_get_element(ca, "url.rewrite-final"))) {
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", "url.rewrite", "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.rewrite",
+ "[", 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,
+ 1)) {
+
+#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 HANDLER_GO_ON;
+}
+#ifdef HAVE_PCRE_H
+static int mod_rewrite_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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;
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-final"))) {
+ p->conf.rewrite = s->rewrite;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_rewrite_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ p->conf.rewrite = s->rewrite;
+
+ 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->state == REWRITE_STATE_FINISHED) return HANDLER_GO_ON;
+ }
+
+ mod_rewrite_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_rewrite_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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] == '$' &&
+ 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);
+
+ /* n is always larger than 0 */
+ if (num < (size_t)n) {
+ buffer_append_string(con->request.uri, list[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->final) 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;
+ 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 00000000..1719d3ca
--- /dev/null
+++ b/src/mod_rrdtool.c
@@ -0,0 +1,457 @@
+#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 "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]);
+
+ /* 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++) {
+ if (i != srv->log_error_fd) 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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("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;
+}
+
+static int mod_rrd_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ 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);
+
+ 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 = 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->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;
+ size_t i;
+
+ mod_rrd_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_rrd_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ *(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_secure_download.c b/src/mod_secure_download.c
new file mode 100644
index 00000000..eecc230a
--- /dev/null
+++ b/src/mod_secure_download.c
@@ -0,0 +1,323 @@
+#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_global.h"
+# 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;
+
+ time_t 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 = 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->secret = buffer_init();
+ s->doc_root = buffer_init();
+ s->uri_prefix = buffer_init();
+ s->timeout = 0;
+
+ /* set global default */
+ if (i == 0) {
+ s->timeout = 60;
+ buffer_copy_string(s->uri_prefix, "/");
+ }
+
+ 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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("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;
+}
+
+static int mod_secdownload_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(secret);
+ PATCH(doc_root);
+ PATCH(uri_prefix);
+ 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_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_secdownload_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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.path, p->conf.doc_root);
+ buffer_append_string(con->physical.path, rel_uri);
+
+ return HANDLER_COMEBACK;
+}
+
+/* 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_uri_clean = 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 00000000..8cfca42e
--- /dev/null
+++ b/src/mod_setenv.c
@@ -0,0 +1,215 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "config.h"
+
+#include "response.h"
+
+/* plugin config for all request/connections */
+
+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;
+
+/* 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 = 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->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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("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.environment"))) {
+ PATCH(environment);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_setenv_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(request_header);
+ PATCH(response_header);
+ PATCH(environment);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_setenv_uri_handler) {
+ plugin_data *p = p_d;
+ size_t k, i;
+
+ mod_setenv_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_setenv_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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;
+}
+
+/* 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->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_simple_vhost.c b/src/mod_simple_vhost.c
new file mode 100644
index 00000000..9fcef860
--- /dev/null
+++ b/src/mod_simple_vhost.c
@@ -0,0 +1,284 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+#include "file_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;
+} 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 },
+ { 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 = 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();
+
+ cv[0].destination = s->server_root;
+ cv[1].destination = s->default_host;
+ cv[2].destination = s->document_root;
+
+ 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) {
+ 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_GO_ON != file_cache_get_entry(srv, con, out, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), out);
+ return -1;
+ }
+
+ if (!S_ISDIR(con->fce->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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("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);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_simple_vhost_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(server_root);
+ PATCH(default_host);
+ PATCH(document_root);
+
+ PATCH(docroot_cache_key);
+ PATCH(docroot_cache_value);
+ PATCH(docroot_cache_servername);
+
+ return 0;
+}
+#undef PATCH
+
+static handler_t mod_simple_vhost_docroot(server *srv, connection *con, void *p_data) {
+ plugin_data *p = p_data;
+ size_t i;
+
+ /*
+ * cache the last successfull translation from hostname (authority) to docroot
+ * - this saves us a stat() call
+ *
+ */
+
+ mod_simple_vhost_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_simple_vhost_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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 00000000..b5dab829
--- /dev/null
+++ b/src/mod_skeleton.c
@@ -0,0 +1,220 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "config.h"
+
+/**
+ * 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];
+
+ 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 = 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->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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("skeleton.array"))) {
+ PATCH(match);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_skeleton_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ 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_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_skeleton_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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 00000000..038c08e6
--- /dev/null
+++ b/src/mod_ssi.c
@@ -0,0 +1,1073 @@
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.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
+/* 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 = 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->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++) {
+ 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';
+
+ 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);
+
+#ifdef PACKAGE_NAME
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_SOFTWARE"), PACKAGE_NAME"/"PACKAGE_VERSION);
+#else
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_SOFTWARE"), PACKAGE"/"VERSION);
+#endif
+ 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;
+
+ 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;
+ }
+
+ 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(con->fce->st.st_uid))) {
+ buffer_copy_long(b, con->fce->st.st_uid);
+ } else {
+ buffer_copy_string(b, pw->pw_name);
+ }
+#else
+ buffer_copy_long(b, con->fce->st.st_uid);
+#endif
+ break;
+ }
+ case SSI_ECHO_LAST_MODIFIED: {
+ time_t t = con->fce->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(p->stat_fn);
+ buffer_path_simplify(&(srv->dot_stack), 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)) {
+
+ /* read everything from client and paste it into the output */
+
+ for (b = chunkqueue_get_append_buffer(con->write_queue), buffer_prepare_copy(b, 4096);
+ (r = read(from_exec_fds[0], b->ptr, b->size - 1)) > 0;
+ b = chunkqueue_get_append_buffer(con->write_queue), buffer_prepare_copy(b, 4096)) {
+
+ b->used = r;
+ b->ptr[b->used++] = '\0';
+ }
+ } 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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("ssi.extension"))) {
+ PATCH(ssi_extension);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_ssi_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(ssi_extension);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_ssi_physical_path) {
+ plugin_data *p = p_d;
+ size_t k, i;
+
+ if (con->physical.path->used == 0) return HANDLER_GO_ON;
+
+ mod_ssi_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_ssi_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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_physical_path = 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 00000000..80f03ed8
--- /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 00000000..98959abe
--- /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 00000000..b484f788
--- /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.h b/src/mod_ssi_exprparser.h
new file mode 100644
index 00000000..eb55ea5f
--- /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 00000000..1bf229b7
--- /dev/null
+++ b/src/mod_ssi_exprparser.y
@@ -0,0 +1,119 @@
+%token_prefix TK_
+%token_type {buffer *}
+%extra_argument {ssi_ctx_t *ctx}
+%name ssiexprparser
+
+%include {
+#include <assert.h>
+#include <string.h>
+#include "config.h"
+#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_status.c b/src/mod_status.c
new file mode 100644
index 00000000..ed51f329
--- /dev/null
+++ b/src/mod_status.c
@@ -0,0 +1,653 @@
+#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 "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;
+} 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->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 },
+ { 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->config_url = buffer_init();
+ s->status_url = buffer_init();
+
+ cv[0].destination = s->status_url;
+ cv[1].destination = s->config_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 handler_t mod_status_handle_server_status(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"
+ "th.status { background-color: black; color: white; }\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"
+ " </style>\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;
+ multiplier = '\0';
+ if (avg > 1000) { avg /= 1000; multiplier = 'k'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'M'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'G'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'T'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'P'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'E'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'Z'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'Y'; }
+ 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;
+ multiplier = '\0';
+
+ if (avg > 1024) { avg /= 1024; multiplier = 'k'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'M'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'G'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'T'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'P'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'E'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'Z'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'Y'; }
+
+ buffer_append_long(b, avg);
+ 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 (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\">");
+ multiplier = '\0';
+
+ if (avg > 1024) { avg /= 1024; multiplier = 'k'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'M'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'G'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'T'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'P'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'E'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'Z'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'Y'; }
+
+ 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\">");
+ multiplier = '\0';
+
+ if (avg > 1024) { avg /= 1024; multiplier = 'k'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'M'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'G'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'T'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'P'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'E'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'Z'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'Y'; }
+
+ buffer_append_long(b, avg);
+ 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>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Client IP</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Read</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Written</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">State</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Time</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Host</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">URI</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">File</th>");
+ 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->used);
+ 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\">");
+
+ buffer_append_string_buffer(b, c->server_name);
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">");
+
+ buffer_append_string_html_encoded(b, c->uri.path->ptr);
+
+ 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"));
+
+ 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, "Rewrite Engine", "enabled");
+#else
+ mod_status_row_append(b, "Rewrite Engine", "disabled - pcre missing");
+#endif
+#ifdef HAVE_ZLIB_H
+ mod_status_row_append(b, "On-the-Fly Output Compression", "enabled");
+#else
+ mod_status_row_append(b, "On-the-Fly Output Compression", "disabled - zlib 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");
+ mod_status_row_append(b, "Directory Listings", con->conf.dir_listing ? "enabled" : "disabled");
+
+ 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_skeleton_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("status.status-url"))) {
+ PATCH(status_url);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.config-url"))) {
+ PATCH(config_url);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_skeleton_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(status_url);
+ PATCH(config_url);
+
+ return 0;
+}
+#undef PATCH
+
+static handler_t mod_status_handler(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ mod_skeleton_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_skeleton_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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);
+ }
+
+ 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_userdir.c b/src/mod_userdir.c
new file mode 100644
index 00000000..d098fbc2
--- /dev/null
+++ b/src/mod_userdir.c
@@ -0,0 +1,248 @@
+#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"
+
+#include "config.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;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *username;
+
+ 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();
+
+ 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);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->username);
+
+ 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 */
+ { 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->exclude_user = array_init();
+ s->include_user = array_init();
+ s->path = buffer_init();
+
+ cv[0].destination = s->path;
+ cv[1].destination = s->exclude_user;
+ cv[2].destination = s->include_user;
+
+ 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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("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);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_userdir_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(path);
+ PATCH(exclude_user);
+ PATCH(include_user);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_userdir_docroot_handler) {
+ plugin_data *p = p_d;
+ int uri_len;
+ size_t k;
+ char *rel_url;
+ struct passwd *pwd;
+ size_t i;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+#ifdef HAVE_PWD_H
+ mod_userdir_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_userdir_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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;
+ }
+ buffer_copy_string_len(p->username, con->uri.path->ptr + 2, rel_url - (con->uri.path->ptr + 2));
+
+ if (NULL == (pwd = getpwnam(p->username->ptr))) {
+ /* 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 exclude list */
+ found_user = 1;
+ break;
+ }
+ }
+
+ if (!found_user) return HANDLER_GO_ON;
+ }
+
+ buffer_copy_string(con->physical.path, pwd->pw_dir);
+ BUFFER_APPEND_SLASH(con->physical.path);
+ buffer_append_string_buffer(con->physical.path, p->conf.path); /* skip the / */
+ BUFFER_APPEND_SLASH(con->physical.path);
+ buffer_append_string(con->physical.path, rel_url + 1); /* skip the / */
+
+ return HANDLER_GO_ON;
+#else
+ return HANDLER_ERROR;
+#endif
+}
+
+/* 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_docroot = 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 00000000..7b53fddf
--- /dev/null
+++ b/src/mod_usertrack.c
@@ -0,0 +1,247 @@
+#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_global.h"
+# include "md5.h"
+#endif
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ buffer *cookie_name;
+} 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);
+
+ 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.cookiename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { 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->cookie_name = buffer_init();
+
+ cv[0].destination = s->cookie_name;
+
+ 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->cookie_name->used == 0) {
+ 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.cookiename:",
+ s->cookie_name);
+
+ 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, const char *stage, size_t stage_len) {
+ 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];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) 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("usertrack.cookiename"))) {
+ PATCH(cookie_name);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_usertrack_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(cookie_name);
+
+ 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];
+ size_t i;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_usertrack_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_usertrack_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ 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_hex(ds->value, (char *)h, 16);
+ buffer_append_string(ds->value, "; path=/");
+
+ 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/network.c b/src/network.c
new file mode 100644
index 00000000..bcf289fd
--- /dev/null
+++ b/src/network.c
@@ -0,0 +1,517 @@
+#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 "file_cache.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;
+
+ UNUSED(context);
+
+ if (revents != FDEVENT_IN) {
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "strange event for server socket",
+ srv_socket->fd,
+ revents);
+ return -1;
+ }
+
+ while (NULL != (con = connection_accept(srv, srv_socket))) {
+ 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;
+
+#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 (port == 0 || port > 65535) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "port out of range:", port);
+
+ return -1;
+ }
+
+ if (*host == '\0') host = NULL;
+
+#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;
+ }
+
+ if (-1 == fdevent_fcntl_set(srv->ev, srv_socket->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl 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__,
+ "sss", "gethostbyname failed: ",
+ hstrerror(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;
+ default:
+ addr_len = 0;
+
+ return -1;
+ }
+
+ if (0 != bind(srv_socket->fd, (struct sockaddr *) &(srv_socket->addr), addr_len)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds", "can't bind to port", port, strerror(errno));
+ return -1;
+ }
+
+ if (-1 == listen(srv_socket->fd, 128 * 8)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "listen failed: ", strerror(errno));
+ return -1;
+ }
+
+#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
+
+
+ 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 (0 > SSL_CTX_use_certificate_file(s->ssl_ctx, s->ssl_pemfile->ptr, SSL_FILETYPE_PEM)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL));
+ return -1;
+ }
+
+ if (0 > SSL_CTX_use_PrivateKey_file (s->ssl_ctx, s->ssl_pemfile->ptr, SSL_FILETYPE_PEM)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL));
+ return -1;
+ }
+
+ if (!SSL_CTX_check_private_key(s->ssl_ctx)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ "Private key does not match the certificate public key");
+ 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
+ }
+
+ 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) {
+ /* 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;
+}
+
+int network_init(server *srv) {
+ buffer *b;
+ size_t i;
+
+ 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);
+
+ /* 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 (!buffer_is_equal_string(dc->comp_key, CONST_STR_LEN("SERVERsocket"))) 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->match.string, s)) {
+ return -1;
+ }
+ }
+
+
+ return 0;
+}
+
+int network_register_fdevents(server *srv) {
+ size_t i;
+
+ fdevent_reset(srv->ev);
+
+ /* 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, i;
+ off_t written = 0;
+
+ 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 = con->bytes_written;
+
+#ifdef TCP_CORK
+ /* Linux: put a cork into the socket as we want to combine the write() calls */
+ i = 1;
+ setsockopt(con->fd, IPPROTO_TCP, TCP_CORK, &i, sizeof(i));
+#endif
+
+ if (srv_socket->is_ssl) {
+#ifdef USE_OPENSSL
+ ret = network_write_chunkqueue_openssl(srv, con, cq);
+#endif
+ } else {
+ /* dispatch call */
+#if defined USE_LINUX_SENDFILE
+ ret = network_write_chunkqueue_linuxsendfile(srv, con, cq);
+#elif defined USE_FREEBSD_SENDFILE
+ ret = network_write_chunkqueue_freebsdsendfile(srv, con, cq);
+#elif defined USE_SOLARIS_SENDFILEV
+ ret = network_write_chunkqueue_solarissendfilev(srv, con, cq);
+#elif defined USE_WRITEV
+ ret = network_write_chunkqueue_writev(srv, con, cq);
+#else
+ ret = network_write_chunkqueue_write(srv, con, cq);
+#endif
+ }
+
+ if (ret >= 0) {
+ /*
+ * map the return code
+ *
+ * -1 -> -1
+ * >0 -> (everything written) 0
+ * (not finished yet) 1
+ *
+ * ret means:
+ * - <ret> chunks are unused now
+ *
+ */
+
+ chunk *c, *pc = NULL;
+
+ for (i = 0, c = cq->first; i < ret; i++, c = c->next) {
+ buffer_reset(c->data.mem);
+
+ if (i == ret - 1) pc = c;
+ }
+
+ if (c) {
+ /* there is still something to write */
+
+ if (c != cq->first) {
+ /* move the first few buffers to unused */
+
+ assert(pc);
+
+ pc->next = cq->unused;
+ cq->unused = cq->first;
+ cq->first = c;
+ }
+
+ ret = 1;
+ } else {
+ /* everything is written */
+ chunkqueue_reset(cq);
+
+ ret = 0;
+ }
+ }
+
+#ifdef TCP_CORK
+ i = 0;
+ setsockopt(con->fd, IPPROTO_TCP, TCP_CORK, &i, sizeof(i));
+#endif
+
+ 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 00000000..99c75966
--- /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 00000000..8238c72b
--- /dev/null
+++ b/src/network_backends.h
@@ -0,0 +1,54 @@
+#ifndef _NETWORK_BACKENDS_H_
+#define _NETWORK_BACKENDS_H_
+
+#include "config.h"
+
+#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, chunkqueue *cq);
+int network_write_chunkqueue_writev(server *srv, connection *con, chunkqueue *cq);
+int network_write_chunkqueue_linuxsendfile(server *srv, connection *con, chunkqueue *cq);
+int network_write_chunkqueue_freebsdsendfile(server *srv, connection *con, chunkqueue *cq);
+int network_write_chunkqueue_solarissendfilev(server *srv, connection *con, chunkqueue *cq);
+int network_write_chunkqueue_openssl(server *srv, connection *con, chunkqueue *cq);
+
+#endif
diff --git a/src/network_freebsd_sendfile.c b/src/network_freebsd_sendfile.c
new file mode 100644
index 00000000..8953fb68
--- /dev/null
+++ b/src/network_freebsd_sendfile.c
@@ -0,0 +1,192 @@
+#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 "file_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, chunkqueue *cq) {
+ const int fd = con->fd;
+ 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->data.mem->used == 0) {
+ chunks[i].iov_base = tc->data.mem->ptr;
+ chunks[i].iov_len = 0;
+ } else {
+ offset = tc->data.mem->ptr + tc->offset;
+ toSend = tc->data.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 */
+
+ 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;
+ con->bytes_written += 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;
+ con->bytes_written += r;
+ chunk_finished = 0;
+
+ break;
+ }
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ off_t offset, r;
+ size_t toSend;
+
+ if (HANDLER_GO_ON != file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->data.file.name);
+ return -1;
+ }
+
+ offset = c->data.file.offset + c->offset;
+ toSend = c->data.file.length - c->offset;
+
+ if (offset > con->fce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name);
+
+ return -1;
+ }
+
+ r = 0;
+
+ /* FreeBSD sendfile() */
+ if (-1 == sendfile(con->fce->fd, fd, offset, toSend, NULL, &r, 0)) {
+ if (errno != EAGAIN) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile: ", strerror(errno), errno);
+
+ return -1;
+ }
+ }
+
+ c->offset += r;
+ con->bytes_written += r;
+
+ if (c->offset == c->data.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 00000000..829c73b9
--- /dev/null
+++ b/src/network_linux_sendfile.c
@@ -0,0 +1,205 @@
+#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 "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "file_cache.h"
+
+
+int network_write_chunkqueue_linuxsendfile(server *srv, connection *con, chunkqueue *cq) {
+ const int fd = con->fd;
+ 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->data.mem->used == 0) {
+ chunks[i].iov_base = tc->data.mem->ptr;
+ chunks[i].iov_len = 0;
+ } else {
+ offset = tc->data.mem->ptr + tc->offset;
+ toSend = tc->data.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 */
+
+ 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;
+ con->bytes_written += 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;
+ con->bytes_written += r;
+ chunk_finished = 0;
+
+ break;
+ }
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ ssize_t r;
+ off_t offset;
+ size_t toSend;
+
+ switch(file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) {
+ case HANDLER_GO_ON:
+ offset = c->data.file.offset + c->offset;
+ /* limit the toSend to 2^31-1 bytes in a chunk */
+ toSend = c->data.file.length - c->offset > ((1 << 30) - 1) ?
+ ((1 << 30) - 1) : c->data.file.length - c->offset;
+
+ if (offset > con->fce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name);
+
+ return -1;
+ }
+
+ /* Linux sendfile() */
+ if (-1 == (r = sendfile(fd, con->fce->fd, &offset, toSend))) {
+ if (errno != EAGAIN &&
+ errno != EINTR) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile:", strerror(errno), errno);
+
+ return -1;
+ }
+
+ r = 0;
+ }
+
+ break;
+ case HANDLER_WAIT_FOR_FD:
+ /* comeback later */
+
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile (handled):", strerror(errno), errno);
+
+ r = 0;
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->data.file.name);
+
+ return -1;
+ }
+
+ c->offset += r;
+ con->bytes_written += r;
+
+ if (c->offset == c->data.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
+#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 00000000..7e8e2f6a
--- /dev/null
+++ b/src/network_openssl.c
@@ -0,0 +1,176 @@
+#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 "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "file_cache.h"
+
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+
+int network_write_chunkqueue_openssl(server *srv, connection *con, chunkqueue *cq) {
+ int ssl_r;
+ 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->data.mem->used == 0) {
+ chunk_finished = 1;
+ break;
+ }
+
+ offset = c->data.mem->ptr + c->offset;
+ toSend = c->data.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(con->ssl, offset, toSend)) <= 0) {
+ switch ((ssl_r = SSL_get_error(con->ssl, r))) {
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:",
+ ssl_r, r,
+ ERR_error_string(ERR_get_error(), NULL));
+
+ return -1;
+ }
+ } else {
+ c->offset += r;
+ con->bytes_written += r;
+ }
+
+ if (c->offset == (off_t)c->data.mem->used - 1) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ char *s;
+ ssize_t r;
+ off_t offset;
+ size_t toSend;
+# if defined USE_MMAP
+ char *p;
+# endif
+
+ WP();
+ if (HANDLER_GO_ON != file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->data.file.name);
+ return -1;
+ }
+
+ offset = c->data.file.offset + c->offset;
+ toSend = c->data.file.length - c->offset;
+
+
+#if defined USE_MMAP
+ if (MAP_FAILED == (p = mmap(0, con->fce->st.st_size, PROT_READ, MAP_SHARED, con->fce->fd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "mmap failed: ", strerror(errno));
+
+ return -1;
+ }
+
+ s = p + offset;
+#else
+ buffer_prepare_copy(srv->tmp_buf, toSend);
+
+ lseek(con->fce->fd, offset, SEEK_SET);
+ if (-1 == (toSend = read(con->fce->fd, srv->tmp_buf->ptr, toSend))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "read failed: ", strerror(errno));
+
+ return -1;
+ }
+
+ s = srv->tmp_buf->ptr;
+#endif
+
+ if ((r = SSL_write(con->ssl, s, toSend)) <= 0) {
+ switch ((ssl_r = SSL_get_error(con->ssl, r))) {
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:",
+ ssl_r, r,
+ ERR_error_string(ERR_get_error(), NULL));
+
+#if defined USE_MMAP
+ munmap(p, c->data.file.length);
+#endif
+ return -1;
+ }
+ } else {
+ c->offset += r;
+ con->bytes_written += r;
+ }
+
+#if defined USE_MMAP
+ munmap(p, c->data.file.length);
+#endif
+
+ if (c->offset == c->data.file.length) {
+ chunk_finished = 1;
+ }
+
+ 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 00000000..2e0694c1
--- /dev/null
+++ b/src/network_solaris_sendfilev.c
@@ -0,0 +1,208 @@
+#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 "file_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, chunkqueue *cq) {
+ const int fd = con->fd;
+ 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->data.mem->used == 0) {
+ chunks[i].iov_base = tc->data.mem->ptr;
+ chunks[i].iov_len = 0;
+ } else {
+ offset = tc->data.mem->ptr + tc->offset;
+ toSend = tc->data.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 */
+
+ 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;
+ con->bytes_written += 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;
+ con->bytes_written += r;
+ chunk_finished = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "partially write: ", r, fd);
+
+ break;
+ }
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ ssize_t r;
+ off_t offset;
+ size_t toSend, written;
+ sendfilevec_t fvec;
+
+ if (HANDLER_GO_ON != file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->data.file.name);
+ return -1;
+ }
+
+ offset = c->data.file.offset + c->offset;
+ toSend = c->data.file.length - c->offset;
+
+ if (offset > con->fce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name);
+
+ return -1;
+ }
+
+ fvec.sfv_fd = con->fce->fd;
+ 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);
+
+ return -1;
+ }
+
+ r = 0;
+ }
+
+ c->offset += written;
+ con->bytes_written += written;
+
+ if (c->offset == c->data.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 00000000..542120b9
--- /dev/null
+++ b/src/network_write.c
@@ -0,0 +1,188 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+#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 "file_cache.h"
+
+#include "sys-socket.h"
+
+#include "network_backends.h"
+
+int network_write_chunkqueue_write(server *srv, connection *con, chunkqueue *cq) {
+ const int fd = con->fd;
+ 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->data.mem->used == 0) {
+ chunk_finished = 1;
+ break;
+ }
+
+ offset = c->data.mem->ptr + c->offset;
+ toSend = c->data.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;
+ con->bytes_written += r;
+
+ if (c->offset == (off_t)c->data.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;
+
+ if (HANDLER_GO_ON != file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->data.file.name);
+ return -1;
+ }
+
+ offset = c->data.file.offset + c->offset;
+ toSend = c->data.file.length - c->offset;
+
+ if (offset > con->fce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name);
+
+ return -1;
+ }
+
+ if (-1 == con->fce->fd) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "fd is invalid", c->data.file.name);
+
+ return -1;
+ }
+
+#if defined USE_MMAP
+ /* check if the mapping fits */
+ if (con->fce->mmap_p &&
+ con->fce->mmap_length != con->fce->st.st_size &&
+ con->fce->mmap_offset != 0) {
+ munmap(con->fce->mmap_p, con->fce->mmap_length);
+
+ con->fce->mmap_p = NULL;
+ }
+
+ /* build mapping if neccesary */
+ if (con->fce->mmap_p == NULL) {
+ if (MAP_FAILED == (p = mmap(0, con->fce->st.st_size, PROT_READ, MAP_SHARED, con->fce->fd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "mmap failed: ", strerror(errno));
+
+ return -1;
+ }
+ con->fce->mmap_p = p;
+ con->fce->mmap_offset = 0;
+ con->fce->mmap_length = con->fce->st.st_size;
+ } else {
+ p = con->fce->mmap_p;
+ }
+
+ if ((r = write(fd, p + offset, toSend)) <= 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "write failed: ", strerror(errno));
+
+ return -1;
+ }
+
+ /* don't cache mmap()ings for files large then 64k */
+ if (con->fce->mmap_length > 64 * 1024) {
+ munmap(con->fce->mmap_p, con->fce->mmap_length);
+
+ con->fce->mmap_p = NULL;
+ }
+
+#else
+ buffer_prepare_copy(srv->tmp_buf, toSend);
+
+ lseek(con->fce->fd, offset, SEEK_SET);
+ if (-1 == (toSend = read(con->fce->fd, srv->tmp_buf->ptr, toSend))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "read: ", strerror(errno));
+
+ return -1;
+ }
+#ifdef __WIN32
+ if (-1 == (r = send(fd, srv->tmp_buf->ptr, toSend, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "write: ", strerror(errno));
+
+ return -1;
+ }
+#else
+ if (-1 == (r = write(fd, srv->tmp_buf->ptr, toSend))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "write: ", strerror(errno));
+
+ return -1;
+ }
+#endif
+#endif
+ c->offset += r;
+ con->bytes_written += r;
+
+ if (c->offset == c->data.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 00000000..3421241c
--- /dev/null
+++ b/src/network_writev.c
@@ -0,0 +1,288 @@
+#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 "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "file_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
+
+int network_write_chunkqueue_writev(server *srv, connection *con, chunkqueue *cq) {
+ const int fd = con->fd;
+ 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->data.mem->used == 0) {
+ chunks[i].iov_base = tc->data.mem->ptr;
+ chunks[i].iov_len = 0;
+ } else {
+ offset = tc->data.mem->ptr + tc->offset;
+ toSend = tc->data.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 */
+
+ 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;
+ con->bytes_written += 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;
+ con->bytes_written += r;
+ chunk_finished = 0;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "(debug) partially write: ", r, fd);
+#endif
+ break;
+ }
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+#ifdef USE_MMAP
+ char *p = NULL;
+#endif
+ ssize_t r;
+ off_t offset;
+ size_t toSend;
+
+ switch (file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) {
+ case HANDLER_GO_ON:
+ if (con->fce->st.st_size == 0 ||
+ con->fce->fd == -1) {
+
+ log_error_write(srv, __FILE__, __LINE__, "sbdd", "foo", c->data.file.name,
+ con->fce->st.st_size, con->fce->fd);
+ }
+
+ offset = c->data.file.offset + c->offset;
+ toSend = c->data.file.length - c->offset;
+
+ if (offset > con->fce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name);
+
+ return -1;
+ }
+
+#if defined USE_MMAP
+ /* check if the mapping fits */
+ if (con->fce->mmap_p &&
+ con->fce->mmap_length != con->fce->st.st_size &&
+ con->fce->mmap_offset != 0) {
+ munmap(con->fce->mmap_p, con->fce->mmap_length);
+
+ con->fce->mmap_p = NULL;
+ }
+
+ /* build mapping if neccesary */
+ if (con->fce->mmap_p == NULL) {
+ if (MAP_FAILED == (p = mmap(0, con->fce->st.st_size, PROT_READ, MAP_SHARED, con->fce->fd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ",
+ strerror(errno), c->data.file.name, con->fce->fd);
+
+ return -1;
+ }
+ con->fce->mmap_p = p;
+ con->fce->mmap_offset = 0;
+ con->fce->mmap_length = con->fce->st.st_size;
+ } else {
+ p = con->fce->mmap_p;
+ }
+
+ if ((r = write(fd, p + 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;
+ }
+ }
+
+ /* don't cache mmap()ings for files large then 64k */
+ if (con->fce->mmap_length > 64 * 1024) {
+ munmap(con->fce->mmap_p, con->fce->mmap_length);
+
+ con->fce->mmap_p = NULL;
+ }
+
+#else
+ buffer_prepare_copy(srv->tmp_buf, toSend);
+
+ lseek(con->fce->fd, offset, SEEK_SET);
+ if (-1 == (toSend = read(con->fce->fd, srv->tmp_buf->ptr, toSend))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "read:", strerror(errno));
+
+ return -1;
+ }
+
+ if (-1 == (r = write(fd, srv->tmp_buf->ptr, toSend))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "write: ", strerror(errno));
+
+ return -1;
+ }
+#endif
+
+
+ break;
+ case HANDLER_WAIT_FOR_FD:
+
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "writev (handled):", strerror(errno), errno);
+
+ r = 0;
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->data.file.name);
+ return -1;
+ }
+
+ c->offset += r;
+ con->bytes_written += r;
+
+ if (c->offset == c->data.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;
+}
+
+#endif
diff --git a/src/plugin.c b/src/plugin.c
new file mode 100644
index 00000000..c16781b8
--- /dev/null
+++ b/src/plugin.c
@@ -0,0 +1,433 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include <stdio.h>
+
+#include "plugin.h"
+#include "log.h"
+#include "config.h"
+
+#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_PHYSICAL_PATH,
+ 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_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
+
+ if (use_dlclose && p->lib) {
+#ifdef __WIN32
+ FreeLibrary(p->lib);
+#else
+ dlclose(p->lib);
+#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;
+}
+
+/**
+ *
+ *
+ *
+ */
+
+
+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;
+ char *inst;
+
+ buffer_reset(srv->tmp_buf);
+ /* for make distcheck */
+ if (NULL != (inst = strstr(LIBRARY_DIR, "/_inst/lib"))) {
+ buffer_copy_string_len(srv->tmp_buf, LIBRARY_DIR, strlen(LIBRARY_DIR) - strlen(inst));
+ buffer_append_string(srv->tmp_buf, "/_build/src/.libs");
+ buffer_append_string(srv->tmp_buf, inst + strlen("/_inst/lib") );
+ } else {
+ buffer_copy_string(srv->tmp_buf, LIBRARY_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__, "ss", "dlopen() failed",
+ 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
+ *(void **)(&init) = dlsym(p->lib, srv->tmp_buf->ptr);
+ 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;
+}
+
+#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_PHYSICAL_PATH, handle_physical_path)
+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_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_PHYSICAL_PATH, handle_physical_path);
+ 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_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;
+}
diff --git a/src/plugin.h b/src/plugin.h
new file mode 100644
index 00000000..6d33a921
--- /dev/null
+++ b/src/plugin.h
@@ -0,0 +1,91 @@
+#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_path) (server *srv, connection *con, void *p_d); /* after the physical path is set */
+ 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_connection_close(server *srv, connection *con);
+handler_t plugins_call_handle_joblist(server *srv, connection *con);
+handler_t plugins_call_handle_physical_path(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, const char *stage, size_t stage_len);
+int config_check_cond(server *srv, connection *con, data_config *dc);
+
+#endif
diff --git a/src/request.c b/src/request.c
new file mode 100644
index 00000000..bbffd1c9
--- /dev/null
+++ b/src/request.c
@@ -0,0 +1,937 @@
+#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--) {
+ 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(char c) {
+ /* RFC 2396 - Appendix A */
+
+ /* alphanum */
+ if (light_isalnum(c)) return 1;
+
+
+ switch(c) {
+ /* reserved */
+ case ';':
+ case '/':
+ case '?':
+ case ':':
+ case '@':
+ case '&':
+ case '=':
+ case '+':
+ case '$':
+ case ',':
+
+ /* mark */
+ case '-':
+ case '_':
+ case '.':
+ case '!':
+ case '~':
+ case '*':
+ case '\'':
+ case '(':
+ case ')':
+
+ /* escaped */
+ case '%':
+
+ /* fragment, should not be out in the wild $*/
+ case '#':
+
+ /* non RFC */
+ case '[':
+ case ']':
+
+ return 1;
+ }
+
+ return 0;
+}
+
+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;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "incomplete request line -> 400");
+ if (srv->srvconf.log_request_header_on_error) {
+ 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;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown http-method -> 501");
+ if (srv->srvconf.log_request_header_on_error) {
+ 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;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown HTTP version -> 505");
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+ } else {
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown protocol -> 400");
+ if (srv->srvconf.log_request_header_on_error) {
+ 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])) {
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "invalid character in URI -> 400",
+ con->request.uri->ptr[j]);
+
+ if (srv->srvconf.log_request_header_on_error) {
+ 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;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "overlong request line -> 400");
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+
+ request_line_stage++;
+ break;
+ }
+ }
+
+ in_folding = 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 */
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "WS character in key -> 400");
+
+ 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 {
+ log_error_write(srv, __FILE__, __LINE__, "s", "CR without LF -> 400");
+
+ 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;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbsds",
+ "CTL character in key", con->request.request, cur, *cur, "-> 400");
+
+ 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 */
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "WS at the start of first line -> 400");
+
+ 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 (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")))) {
+ con->request.http_content_type = ds->value->ptr;
+ } 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)
+ */
+
+ 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")))) {
+ con->request.http_host = ds->value;
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-Modified-Since")))) {
+ con->request.http_if_modified_since = ds->value->ptr;
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-None-Match")))) {
+ con->request.http_if_none_match = ds->value->ptr;
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Range")))) {
+ /* bytes=.*-.* */
+
+ if (0 == strncasecmp(ds->value->ptr, "bytes=", 6) &&
+ NULL != strchr(ds->value->ptr+6, '-')) {
+
+ con->request.http_range = ds->value->ptr + 6;
+ }
+ }
+
+ 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 {
+ 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;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "HTTP/1.1 but Host missing -> 400");
+ 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 (0 != request_check_hostname(srv, con, con->request.http_host)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "Invalid Hostname:", con->request.http_host, "-> 400");
+
+ con->http_status = 400;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ return 0;
+ }
+
+ /* check if we have read post data */
+ if (con->request.http_method == HTTP_METHOD_POST) {
+ server_socket *srv_socket = con->srv_socket;
+ if (con->request.http_content_type == NULL) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "POST request, but content-type not set");
+ }
+
+ if (con_length_set == 0) {
+ /* content-length is missing */
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "POST-request, but content-length missing -> 411");
+
+ con->http_status = 411;
+ return 0;
+ }
+
+ /* don't handle more the SSIZE_MAX bytes in content-length */
+ if (con->request.content_length > SSIZE_MAX) {
+ con->http_status = 413;
+
+ 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_socket->max_request_size != 0 &&
+ (con->request.content_length >> 10) > srv_socket->max_request_size) {
+ /* the request body itself is larger then
+ * our our max_request_size
+ */
+
+ con->http_status = 413;
+
+ 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 00000000..cf2b07d4
--- /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 00000000..1f67d70e
--- /dev/null
+++ b/src/response.c
@@ -0,0 +1,1432 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <dirent.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 "file_cache.h"
+#include "etag.h"
+
+#include "connections.h"
+
+#include "plugin.h"
+
+#include "sys-socket.h"
+
+#ifdef HAVE_ATTR_ATTRIBUTES_H
+#include <attr/attributes.h>
+#endif
+
+/*
+ * 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_write_basic_header(server *srv, connection *con) {
+ size_t i;
+ buffer *b;
+
+ b = chunkqueue_get_prepend_buffer(con->write_queue);
+
+ if (con->request.http_version == HTTP_VERSION_1_1) {
+ buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.1 "));
+ } else {
+ buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.0 "));
+ }
+ buffer_append_long(b, con->http_status);
+ buffer_append_string_len(b, CONST_STR_LEN(" "));
+ buffer_append_string(b, get_http_status_name(con->http_status));
+
+ /* add the connection header if
+ * HTTP/1.1 -> close
+ * HTTP/1.0 -> keep-alive
+ */
+ if (con->request.http_version != HTTP_VERSION_1_1 || con->keep_alive == 0) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nConnection: ");
+ if (con->keep_alive) {
+ BUFFER_APPEND_STRING_CONST(b, "keep-alive");
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "close");
+ }
+ }
+
+ if (con->request.http_version == HTTP_VERSION_1_1 &&
+ (con->parsed_response & HTTP_DATE) == 0) {
+ /* 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 (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)) {
+ 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 (buffer_is_empty(con->conf.server_tag)) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nServer: "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");
+
+ if (con->conf.log_response_header) {
+ log_error_write(srv, __FILE__, __LINE__, "sdsdSb",
+ "fd:", con->fd,
+ "response-header-len:", b->used - 1,
+ "\n", b);
+ }
+
+ con->bytes_header = b->used - 1;
+
+ return 0;
+}
+
+
+int http_response_write_header(server *srv, connection *con,
+ off_t file_size,
+ time_t last_mod) {
+ buffer *b;
+ size_t i;
+
+ 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");
+ }
+
+ /* 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);
+
+ /* no Last-Modified specified */
+ if (last_mod && NULL == array_get_element(con->response.headers, "Last-Modified")) {
+ struct tm *tm;
+
+ for (i = 0; i < FILE_CACHE_MAX; i++) {
+ if (srv->mtime_cache[i].mtime == last_mod) break;
+
+ if (srv->mtime_cache[i].mtime == 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++;
+ 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++;
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "\r\nLast-Modified: ");
+ buffer_append_string_buffer(b, srv->mtime_cache[i].str);
+ }
+
+ if (file_size >= 0 && con->http_status != 304) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nContent-Length: ");
+ buffer_append_off_t(b, file_size);
+ }
+
+ if (con->physical.path->used && con->physical.etag->used) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nETag: ");
+ buffer_append_string_buffer(b, con->physical.etag);
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "\r\nAccept-Ranges: bytes");
+
+ /* 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)) {
+ 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 (buffer_is_empty(con->conf.server_tag)) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nServer: "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;
+}
+
+static int http_response_parse_range(server *srv, connection *con) {
+ struct stat st;
+ int multipart = 0;
+ int error;
+ off_t start, end;
+ const char *s, *minus;
+ char *boundary = "fkj49sn38dcn3";
+ const char *content_type = NULL;
+ data_string *ds;
+
+ if (-1 == stat(con->physical.path->ptr, &st)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "stat failed: ", strerror(errno));
+ return -1;
+ }
+
+ start = 0;
+ end = 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->ptr;
+ }
+
+ for (s = con->request.http_range, error = 0;
+ !error && *s && NULL != (minus = strchr(s, '-')); ) {
+ char *err;
+ long la, le;
+
+ if (s == minus) {
+ /* -<stop> */
+
+ le = strtol(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 = st.st_size - 1;
+ start = st.st_size + le;
+ } else if (*err == ',') {
+ multipart = 1;
+ s = err + 1;
+
+ end = st.st_size - 1;
+ start = st.st_size + le;
+ } else {
+ error = 1;
+ }
+
+ } else if (*(minus+1) == '\0' || *(minus+1) == ',') {
+ /* <start>- */
+
+ la = strtol(s, &err, 10);
+
+ if (err == minus) {
+ /* ok */
+
+ if (*(err + 1) == '\0') {
+ s = err + 1;
+
+ end = st.st_size - 1;
+ start = la;
+
+ } else if (*(err + 1) == ',') {
+ multipart = 1;
+ s = err + 2;
+
+ end = st.st_size - 1;
+ start = la;
+ } else {
+ error = 1;
+ }
+ } else {
+ /* error */
+ error = 1;
+ }
+ } else {
+ /* <start>-<stop> */
+
+ la = strtol(s, &err, 10);
+
+ if (err == minus) {
+ le = strtol(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 > st.st_size - 1) end = st.st_size - 1;
+
+ if (start > 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, st.st_size);
+
+ buffer_append_string(b, "\r\nContent-Type: ");
+ buffer_append_string(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 0;
+ }
+
+ 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(srv->range_buf, "multipart/byteranges; boundary=");
+ buffer_append_string(srv->range_buf, boundary);
+
+ /* overwrite content-type */
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(srv->range_buf));
+ } else {
+ /* add Content-Range-header */
+
+ buffer_copy_string(srv->range_buf, "bytes ");
+ buffer_append_off_t(srv->range_buf, start);
+ buffer_append_string(srv->range_buf, "-");
+ buffer_append_off_t(srv->range_buf, end);
+ buffer_append_string(srv->range_buf, "/");
+ buffer_append_off_t(srv->range_buf, st.st_size);
+
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(srv->range_buf));
+ }
+
+ /* ok, the file is set-up */
+ con->http_status = 206;
+
+ return 0;
+}
+
+typedef struct {
+ char **ptr;
+ size_t used;
+ size_t size;
+} string_buffer;
+
+static int http_list_directory(server *srv, connection *con, buffer *dir) {
+ DIR *d;
+ struct dirent *dent;
+ buffer *b, *date_buf;
+ string_buffer *sb;
+ size_t i;
+
+ if (NULL == (d = opendir(dir->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "opendir failed:", dir, strerror(errno));
+ return -1;
+ }
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ 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>Directory Listing for ");
+ buffer_append_string_html_encoded(b, con->uri.path->ptr);
+
+ BUFFER_APPEND_STRING_CONST(b,
+ "</title>\n"
+ " <style type=\"text/css\">\n");
+
+ BUFFER_APPEND_STRING_CONST(b,
+ "th.dirlisting { background-color: black; color: white; }\n"
+ "table.dirlisting { border: black solid thin; }\n"
+ "td.dirlisting { background-color: #f0f0f0; }\n"
+ "td.dirlistingnumber { background-color: #f0f0f0; text-align: right }\n"
+ );
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " </style>\n"
+ " </head>\n"
+ " <body>\n"
+ " <h2>Directory Listing for ");
+ buffer_append_string_html_encoded(b, con->uri.path->ptr);
+ BUFFER_APPEND_STRING_CONST(b,"</h2>\n <table class='dirlisting'>\n");
+ BUFFER_APPEND_STRING_CONST(b," <tr class='dirlisting'><th class='dirlisting'>date</th><th class='dirlisting'>size</th><th class='dirlisting'>type</th><th class='dirlisting'>name</th></tr>\n");
+
+
+ /* allocate memory */
+ sb = calloc(1, sizeof(*sb));
+ assert(sb);
+
+ sb->ptr = NULL;
+ sb->used = 0;
+ sb->size = 0;
+
+ while(NULL != (dent = readdir(d))) {
+ size_t j;
+ if (sb->size == 0) {
+ sb->size = 4;
+ sb->ptr = malloc(sizeof(*sb->ptr) * sb->size);
+ assert(sb->ptr);
+ } else if (sb->used == sb->size) {
+ sb->size += 4;
+ sb->ptr = realloc(sb->ptr, sizeof(*sb->ptr) * sb->size);
+ assert(sb->ptr);
+ }
+
+ for (i = 0; i < sb->used; i++) {
+ if (strcmp(dent->d_name, sb->ptr[i]) < 0) {
+ break;
+ }
+ }
+
+ /* <left><right> */
+ /* <left>[i]<right> */
+
+ for (j = sb->used - 1; sb->used && j >= i && (j+1) > 0; j--) {
+ sb->ptr[j + 1] = sb->ptr[j];
+ }
+ sb->ptr[i] = strdup(dent->d_name);
+
+ sb->used++;
+
+ }
+
+ closedir(d);
+
+ date_buf = buffer_init();
+ buffer_prepare_copy(date_buf, 22);
+ for (i = 0; i < sb->used; i++) {
+ struct stat st;
+ struct tm tm;
+ size_t s_len = strlen(sb->ptr[i]);
+
+
+ buffer_copy_string(srv->tmp_buf, dir->ptr);
+ buffer_append_string(srv->tmp_buf, sb->ptr[i]);
+
+ if (0 != stat(srv->tmp_buf->ptr, &st)) {
+ free(sb->ptr[i]);
+ continue;
+ }
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " <tr><td class='dirlisting'>");
+
+#ifdef HAVE_LOCALTIME_R
+ /* localtime_r is faster */
+ localtime_r(&(st.st_mtime), &tm);
+
+ date_buf->used = strftime(date_buf->ptr, date_buf->size - 1, "%Y-%m-%d %H:%M:%S", &tm);
+#else
+ date_buf->used = strftime(date_buf->ptr, date_buf->size - 1, "%Y-%m-%d %H:%M:%S", localtime(&(st.st_mtime)));
+#endif
+ date_buf->used++;
+
+ buffer_append_string_buffer(b, date_buf);
+ BUFFER_APPEND_STRING_CONST(b,
+ "</td><td class='dirlistingnumber'>");
+
+ buffer_append_off_t(b, st.st_size);
+
+ /* mime type */
+ BUFFER_APPEND_STRING_CONST(b,
+ "</td><td class='dirlisting'>");
+
+ if (S_ISDIR(st.st_mode)) {
+ buffer_append_string_rfill(b, "directory", 28);
+ } else {
+ size_t k;
+ unsigned short have_content_type = 0;
+
+#ifdef HAVE_XATTR
+ char attrval[128];
+ int attrlen = sizeof(attrval) - 1;
+
+ if(con->conf.use_xattr && 0 == attr_get(srv->tmp_buf->ptr, "Content-Type", attrval, &attrlen, 0)) {
+ attrval[attrlen] = 0;
+ buffer_append_string_rfill(b, attrval, 28);
+ have_content_type = 1;
+ }
+#endif
+
+ if(!have_content_type) {
+ 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 (s_len < ct_len) continue;
+
+ if (0 == strncmp(sb->ptr[i] + s_len - ct_len, ds->key->ptr, ct_len)) {
+ buffer_append_string_rfill(b, ds->value->ptr, 28);
+ break;
+ }
+ }
+
+ if (k == con->conf.mimetypes->used) {
+ buffer_append_string_rfill(b, "application/octet-stream", 28);
+ }
+ }
+ }
+
+ /* URL */
+ BUFFER_APPEND_STRING_CONST(b,"</td><td class='dirlisting'><a href=\"");
+ /* URL encode */
+ buffer_append_string_url_encoded(b, sb->ptr[i]);
+ if (S_ISDIR(st.st_mode)) {
+ BUFFER_APPEND_STRING_CONST(b,"/");
+ }
+ BUFFER_APPEND_STRING_CONST(b,"\">");
+ /* HTML encode */
+ buffer_append_string_html_encoded(b, sb->ptr[i]);
+ if (S_ISDIR(st.st_mode)) {
+ BUFFER_APPEND_STRING_CONST(b,"/");
+ }
+ BUFFER_APPEND_STRING_CONST(b,"</a></td></tr>\n");
+
+
+ free(sb->ptr[i]);
+ }
+
+ buffer_free(date_buf);
+ free(sb->ptr);
+ free(sb);
+
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " </table>\n"
+ " </body>\n"
+ "</html>\n" );
+
+ con->file_finished = 1;
+
+ return 0;
+}
+
+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__,
+ "SSSS", "NOTICE: gethostbyaddr failed: ",
+ hstrerror(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;
+
+ buffer_free(o);
+
+ 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;
+
+ config_patch_connection(srv, con, CONST_STR_LEN("SERVERsocket")); /* 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 ? "http" : "https");
+ buffer_copy_string_buffer(con->uri.authority, con->request.http_host);
+
+ config_patch_connection(srv, con, CONST_STR_LEN("HTTPhost")); /* Host: */
+ config_patch_connection(srv, con, CONST_STR_LEN("HTTPreferer")); /* Referer: */
+ config_patch_connection(srv, con, CONST_STR_LEN("HTTPuseragent")); /* User-Agent: */
+ config_patch_connection(srv, con, CONST_STR_LEN("HTTPcookie")); /* 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. /../)
+ */
+
+
+
+ buffer_copy_string_buffer(srv->tmp_buf, con->uri.path_raw);
+ buffer_urldecode(srv->tmp_buf);
+ buffer_path_simplify(&(srv->dot_stack), 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, CONST_STR_LEN("HTTPurl")); /* 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;
+ }
+
+ /***
+ *
+ * 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);
+
+ buffer_reset(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, the alias plugin
+ * sets the path too)
+ * */
+ 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;
+ }
+
+ if (buffer_is_empty(con->physical.path)) {
+ /**
+ * 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);
+ if (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", "-- 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);
+ }
+ }
+
+ /*
+ * only if we are still in DIRECT mode we check for the real existence of the file
+ *
+ */
+
+ if (con->mode == DIRECT) {
+ char *slash = NULL;
+ char *pathinfo = NULL;
+ int found = 0;
+ size_t k;
+
+ 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);
+ }
+
+ switch (file_cache_get_entry(srv, con, con->physical.path, &(con->fce))) {
+ case HANDLER_WAIT_FOR_FD:
+ return HANDLER_WAIT_FOR_FD;
+ case HANDLER_ERROR:
+ 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;
+ 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.rel_path);
+
+ /*
+ *
+ * FIXME:
+ *
+ * Check for PATHINFO fall to dir of
+ *
+ * /a is a dir and
+ *
+ * /a/b/c is requested
+ *
+ */
+
+ do {
+ struct stat st;
+
+ buffer_copy_string_buffer(con->physical.path, con->physical.doc_root);
+ BUFFER_APPEND_SLASH(con->physical.path);
+ if (slash) {
+ buffer_append_string_len(con->physical.path, srv->tmp_buf->ptr, slash - srv->tmp_buf->ptr);
+ } else {
+ buffer_append_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));
+
+ 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", "Pathinfo :", con->request.pathinfo);
+ }
+
+ /* setup the right file cache entry (FCE) */
+ switch (file_cache_get_entry(srv, con, con->physical.path, &(con->fce))) {
+ case HANDLER_ERROR:
+ 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);
+ }
+
+ return HANDLER_FINISHED;
+ case HANDLER_WAIT_FOR_FD:
+ return HANDLER_WAIT_FOR_FD;
+ case HANDLER_GO_ON:
+ break;
+ default:
+ break;
+ }
+
+ break;
+ case HANDLER_GO_ON:
+ 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(con->fce->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 {
+ found = 0;
+ /* indexfile */
+
+ for (k = 0; !found && (k < con->conf.indexfiles->used); k++) {
+ data_string *ds = (data_string *)con->conf.indexfiles->data[k];
+
+ buffer_copy_string_buffer(srv->tmp_buf, con->physical.path);
+ buffer_append_string_buffer(srv->tmp_buf, ds->value);
+
+ switch (file_cache_get_entry(srv, con, srv->tmp_buf, &(con->fce))) {
+ case HANDLER_GO_ON:
+ /* rewrite uri.path to the real path (/ -> /index.php) */
+ buffer_append_string_buffer(con->uri.path, ds->value);
+
+ found = 1;
+ break;
+ case HANDLER_ERROR:
+
+ 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;
+ 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;
+ }
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!found &&
+ (k == con->conf.indexfiles->used)) {
+ /* directory listing ? */
+
+ buffer_reset(srv->tmp_buf);
+
+ if (con->conf.dir_listing == 0) {
+ /* dirlisting disabled */
+ con->http_status = 403;
+ } else if (0 != http_list_directory(srv, con, con->physical.path)) {
+ /* dirlisting failed */
+ con->http_status = 403;
+ }
+
+ buffer_reset(con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+
+ buffer_copy_string_buffer(con->physical.path, srv->tmp_buf);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!S_ISREG(con->fce->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,
+ "->", con->fce->name);
+ }
+
+ return HANDLER_FINISHED;
+ }
+
+ /* call the handlers */
+ switch(r = plugins_call_handle_subrequest_start(srv, con)) {
+ case HANDLER_FINISHED:
+ /* request was handled */
+ break;
+ case HANDLER_GO_ON:
+ /* request was not handled */
+ break;
+ default:
+ /* something strange happend */
+ return r;
+ }
+
+ /* ok, noone has handled the file up to now, so we do the fileserver-stuff */
+ if (r == HANDLER_GO_ON) {
+ /* DIRECT */
+
+ /* set response content-type */
+
+ if (buffer_is_empty(con->fce->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(con->fce->content_type));
+ }
+
+ /* generate e-tag */
+ etag_mutate(con->physical.etag, con->fce->etag);
+
+ /*
+ * 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->http_status == 0 &&
+ 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) {
+ char buf[64];
+ struct tm *tm;
+ size_t used_len;
+ char *semicolon;
+
+ tm = gmtime(&(con->fce->st.st_mtime));
+ strftime(buf, sizeof(buf)-1, "%a, %d %b %Y %H:%M:%S GMT", tm);
+
+ 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, buf, used_len)) {
+ con->http_status = 304;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ con->request.http_if_modified_since, buf);
+
+ con->http_status = 412;
+ }
+ } else {
+ con->http_status = 304;
+ }
+ } else {
+ con->http_status = 412;
+ }
+ }
+ } else if (con->http_status == 0 && con->request.http_if_modified_since) {
+ char buf[64];
+ struct tm *tm;
+ size_t used_len;
+ char *semicolon;
+
+ tm = gmtime(&(con->fce->st.st_mtime));
+ strftime(buf, sizeof(buf)-1, "%a, %d %b %Y %H:%M:%S GMT", tm);
+
+ 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, buf, used_len)) {
+ con->http_status = 304;
+ }
+ }
+
+ if (con->http_status == 0 && con->request.http_range) {
+ http_response_parse_range(srv, con);
+ } else if (con->http_status == 0) {
+ switch(r = plugins_call_handle_physical_path(srv, con)) {
+ case HANDLER_GO_ON:
+ break;
+ default:
+ return r;
+ }
+ }
+ }
+ }
+
+ 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 00000000..e7d7dc74
--- /dev/null
+++ b/src/response.h
@@ -0,0 +1,18 @@
+#ifndef _RESPONSE_H_
+#define _RESPONSE_H_
+
+#include "server.h"
+
+int http_response_parse(server *srv, connection *con);
+int http_response_write_basic_header(server *srv, connection *con);
+int http_response_write_header(server *srv, connection *con,
+ off_t file_size,
+ time_t last_mod);
+
+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);
+
+#endif
diff --git a/src/server.c b/src/server.c
new file mode 100644
index 00000000..7573caef
--- /dev/null
+++ b/src/server.c
@@ -0,0 +1,1021 @@
+#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 "file_cache.h"
+#include "plugin.h"
+#include "joblist.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
+
+const char *patches[] = { "SERVERsocket", "HTTPurl", "HTTPhost", "HTTPreferer", "HTTPuseragent", "HTTPcookie", NULL };
+
+
+#ifndef __sgi
+/* IRIX doesn't like the alarm based time() optimization */
+/* #define USE_ALARM */
+#endif
+
+static sig_atomic_t srv_shutdown = 0;
+static sig_atomic_t handle_sig_alarm = 1;
+static 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 SIGALRM: handle_sig_alarm = 1; break;
+ case SIGHUP: handle_sig_hup = 1; break;
+ }
+}
+#elif defined(HAVE_SIGNAL) || defined(HAVE_SIGACTION)
+static void signal_handler(int sig) {
+ switch (sig) {
+ case SIGTERM: srv_shutdown = 1; break;
+ case SIGALRM: handle_sig_alarm = 1; break;
+ case SIGHUP: handle_sig_hup = 1; 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 (fork() != 0) exit(0);
+
+ if (setsid() == -1) exit(0);
+
+ signal(SIGHUP, SIG_IGN);
+
+ if (fork() != 0) exit(0);
+
+ chdir("/");
+
+ 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(error_log);
+ CLEAN(response_range);
+ CLEAN(tmp_buf);
+ CLEAN(file_cache_etag);
+ CLEAN(range_buf);
+ CLEAN(empty_string);
+
+ buffer_copy_string(srv->empty_string, "");
+
+ CLEAN(srvconf.error_logfile);
+ CLEAN(srvconf.groupname);
+ CLEAN(srvconf.username);
+ CLEAN(srvconf.changeroot);
+ CLEAN(srvconf.bindhost);
+ CLEAN(srvconf.errorfile_prefix);
+ CLEAN(srvconf.license);
+ 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);
+#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->file_cache = file_cache_init();
+ assert(srv->file_cache);
+
+ srv->srvconf.modules = array_init();
+
+ /* use syslog */
+ srv->log_error_fd = -1;
+
+ srv->split_vals = array_init();
+
+
+ srv->dot_stack.ptr = NULL;
+ srv->dot_stack.used = 0;
+ srv->dot_stack.size = 0;
+
+ srv->config_patches = buffer_array_init();
+ for (i = 0; patches[i]; i++) {
+ buffer *b;
+
+ b = buffer_array_append_get_buffer(srv->config_patches);
+ buffer_copy_string(b, patches[i]);
+ }
+
+ 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);
+ }
+
+ buffer_array_free(srv->config_patches);
+
+#define CLEAN(x) \
+ buffer_free(srv->x);
+
+ CLEAN(response_header);
+ CLEAN(parse_full_path);
+ CLEAN(ts_debug_str);
+ CLEAN(ts_date_str);
+ CLEAN(error_log);
+ CLEAN(response_range);
+ CLEAN(tmp_buf);
+ CLEAN(file_cache_etag);
+ CLEAN(range_buf);
+ CLEAN(empty_string);
+
+ CLEAN(srvconf.error_logfile);
+ CLEAN(srvconf.groupname);
+ CLEAN(srvconf.username);
+ CLEAN(srvconf.changeroot);
+ CLEAN(srvconf.bindhost);
+ CLEAN(srvconf.errorfile_prefix);
+ CLEAN(srvconf.license);
+ CLEAN(srvconf.event_handler);
+ CLEAN(srvconf.pid_file);
+
+ CLEAN(tmp_chunk_len);
+#undef CLEAN
+
+#if 0
+ fdevent_unregister(srv->ev, srv->fd);
+#endif
+ fdevent_free(srv->ev);
+
+ connections_free(srv);
+ free(srv->conns);
+
+ if (srv->config_storage) {
+ for (i = 0; i < srv->config_context->used; i++) {
+ specific_config *s = srv->config_storage[i];
+ buffer_free(s->document_root);
+ buffer_free(s->server_name);
+ buffer_free(s->server_tag);
+ buffer_free(s->ssl_pemfile);
+ buffer_free(s->error_handler);
+ array_free(s->indexfiles);
+ 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);
+#undef CLEAN
+
+ joblist_free(srv, srv->joblist);
+ fdwaitqueue_free(srv, srv->fdwaitqueue);
+
+ file_cache_free(srv, srv->file_cache);
+
+ array_free(srv->srvconf.modules);
+ array_free(srv->split_vals);
+
+ for (i = 0; i < srv->dot_stack.size; i++) {
+ free(srv->dot_stack.ptr[i]);
+ }
+ free(srv->dot_stack.ptr);
+
+ free(srv);
+}
+
+static void show_version (void) {
+#ifdef USE_OPENSSL
+# define TEXT_SSL " (ssl)"
+#else
+# define TEXT_SSL
+#endif
+ char *b = 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_help (void) {
+#ifdef USE_OPENSSL
+# define TEXT_SSL " (ssl)"
+#else
+# define TEXT_SSL
+#endif
+#ifdef HAVE_IPV6
+# define TEXT_IPV6 " -6 use IPv6\n"
+#else
+# define TEXT_IPV6
+#endif
+ char *b = PACKAGE "-" VERSION TEXT_SSL " ("__DATE__ " " __TIME__ ")" \
+" - a light and fast webserver\n" \
+"usage:\n" \
+" -f <name> filename of the config-file\n" \
+" -D don't go to background (default: go to background)\n" \
+TEXT_IPV6 \
+" -v show version\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 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 happend ?\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:hvD"))) {
+ switch(o) {
+ case 'f':
+ if (config_read(srv, optarg)) {
+ server_free(srv);
+ return -1;
+ }
+ break;
+ case 'D': srv->srvconf.dont_daemonize = 1; break;
+ case 'v': show_version(); 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;
+ }
+
+ /* 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 (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;
+ }
+ }
+
+ srv->max_fds = rlim.rlim_cur;
+#else
+ srv->max_fds = 4096;
+#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;
+ }
+
+#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) setuid(pwd->pw_uid);
+#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;
+ }
+
+ srv->max_fds = rlim.rlim_cur;
+#else
+ srv->max_fds = 4096;
+#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;
+ }
+
+ if (0 != network_init(srv)) {
+ plugins_free(srv);
+ server_free(srv);
+
+ return -1;
+ }
+ }
+
+ 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;
+ }
+
+ 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; srv->config && i < srv->config->used; i++) {
+ data_unset *du = srv->config->data[i];
+
+ 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;
+ }
+#ifdef HAVE_FORK
+ /* network is up, let's deamonize ourself */
+ if (srv->srvconf.dont_daemonize == 0) daemonize();
+#endif
+
+ /* 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 (-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;
+ }
+
+ /* kqueue needs a reset AFTER daemonize() */
+ if (0 != network_register_fdevents(srv)) {
+ plugins_free(srv);
+ network_close(srv);
+ server_free(srv);
+
+ return -1;
+ }
+
+ /* get the current number of FDs */
+ srv->cur_fds = open("/dev/null", O_RDONLY);
+ close(srv->cur_fds);
+
+
+#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(SIGTERM, &act, NULL);
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGALRM, &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);
+#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__, "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 (!child) return 0;
+ }
+#endif
+
+ /* 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;
+
+ /**
+ * 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__, "sd",
+ "connection closed - write-request-timeout:", con->fd);
+#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->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");
+ }
+ }
+
+ /* handle out of fd condition */
+ if (!srv->sockets_disabled &&
+ srv->cur_fds + srv->want_fds > srv->max_fds * 0.9) {
+
+ /* 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);
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets disabled, out-of-fds");
+
+ srv->sockets_disabled = 1;
+ } else if (srv->sockets_disabled &&
+ srv->cur_fds + srv->want_fds < srv->max_fds * 0.8) {
+
+ 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, out-of-fds");
+
+ srv->sockets_disabled = 0;
+ }
+
+ /* 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:
+ 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 != 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);
+ plugins_free(srv);
+ server_free(srv);
+
+ return 0;
+}
diff --git a/src/server.h b/src/server.h
new file mode 100644
index 00000000..bca2d525
--- /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 00000000..959edcc9
--- /dev/null
+++ b/src/settings.h
@@ -0,0 +1,46 @@
+#ifndef _LIGHTTPD_SETTINGS_H_
+#define _LIGHTTPD_SETTINGS_H_
+
+#define BV(x) (1 << x)
+
+#define INET_NTOP_CACHE_MAX 4
+#define FILE_CACHE_MAX 16
+
+#define FCGI_RETRY_TIMEOUT (5 * 60)
+
+/**
+ * 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 00000000..8be65cec
--- /dev/null
+++ b/src/spawn-fcgi.c
@@ -0,0 +1,346 @@
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "config.h"
+
+#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 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;
+
+ 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;
+ }
+
+ /* 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;
+ }
+
+ switch ((child = fork())) {
+ case 0: {
+ char cgi_childs[64];
+
+ 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);
+
+ /* exec the cgi */
+ execl("/bin/sh", "sh", "-c", appPath, 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);
+ 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" "-" VERSION \
+" - spawns fastcgi processes\n" \
+"Build-Date: " __DATE__ " " __TIME__ "\n";
+;
+ write(1, b, strlen(b));
+}
+
+void show_help () {
+ char *b = "spawn-fcgi" "-" 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" \
+" -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;
+ unsigned short port = 0;
+ int child_count = 5;
+ int i_am_root, o;
+
+ i_am_root = (getuid() == 0);
+
+ while(-1 != (o = getopt(argc, argv, "c:f:g:hp:u:vC:s:"))) {
+ 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 '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 (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);
+}
+#else
+int main() {
+ return -1;
+}
+#endif
diff --git a/src/stream.c b/src/stream.c
new file mode 100644
index 00000000..ec228d85
--- /dev/null
+++ b/src/stream.c
@@ -0,0 +1,100 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "stream.h"
+#include "config.h"
+
+#include "sys-mmap.h"
+
+
+int stream_open(stream *f, buffer *fn) {
+ struct stat st;
+#ifdef HAVE_MMAP
+ int fd;
+#elif defined __WIN32
+ HANDLE *fh, *mh;
+ void *p;
+#endif
+
+
+ if (-1 == stat(fn->ptr, &st)) {
+ return -1;
+ }
+
+ f->size = st.st_size;
+
+#ifdef HAVE_MMAP
+ if (-1 == (fd = open(fn->ptr, O_RDONLY))) {
+ 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 00000000..d4c9049b
--- /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 00000000..94aaa19b
--- /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 00000000..cc6e649b
--- /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
diff --git a/tests/.cvsignore b/tests/.cvsignore
new file mode 100644
index 00000000..302e477d
--- /dev/null
+++ b/tests/.cvsignore
@@ -0,0 +1,5 @@
+Makefile.in
+Makefile
+.deps
+.libs
+fcgi-auth
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 00000000..859ab63a
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,128 @@
+# lighttpd.conf and conformance.pl expect this directory
+testdir=/tmp/lighttpd/
+
+check_PROGRAMS=fcgi-auth
+
+fcgi_auth_SOURCES=fcgi-auth.c
+fcgi_auth_LDADD=-lfcgi
+
+TESTS=\
+prepare.sh \
+basic-01.sh \
+basic-02.sh \
+basic-03.sh \
+basic-05.sh \
+basic-06.sh \
+basic-07.sh \
+basic-08.sh \
+basic-09.sh \
+basic-10.sh \
+basic-11.sh \
+bug-urldecode-00.sh \
+bug-03.sh \
+bug-06.sh \
+bug-12.sh \
+bug-14.sh \
+bug-15.sh \
+bug-15-2.sh \
+bug-15-3.sh \
+broken-header-01.sh \
+content-length-01.sh \
+content-length-02.sh \
+content-length-03.sh \
+content-length-04.sh \
+content-length-05.sh \
+head-01.sh \
+post-01.sh \
+post-02.sh \
+host-01.sh \
+host-02.sh \
+host-03.sh \
+host-04.sh \
+host-05.sh \
+http11-01.sh \
+http11-02.sh \
+http11-03.sh \
+missing-01.sh \
+missing-02.sh \
+large-header-01.sh \
+accessdeny-01.sh \
+cgi-01.sh \
+cgi-02.sh \
+cgi-03.sh \
+compress-01.sh \
+compress-02.sh \
+compress-03.sh \
+compress-04.sh \
+fastcgi-01.sh \
+fastcgi-02.sh \
+fastcgi-03.sh \
+fastcgi-04.sh \
+fastcgi-05.sh \
+fastcgi-06.sh \
+fastcgi-07.sh \
+fastcgi-08.sh \
+fastcgi-09.sh \
+fastcgi-10.sh \
+fastcgi-11.sh \
+fastcgi-12.sh \
+fastcgi-13.sh \
+auth-01.sh \
+auth-02.sh \
+auth-03.sh \
+content-01.sh \
+content-02.sh \
+content-03.sh \
+content-04.sh \
+leak-01.sh \
+leak-02.sh \
+leak-03.sh \
+leak-04.sh \
+leak-05.sh \
+leak-06.sh \
+leak-07.sh \
+leak-08.sh \
+leak-09.sh \
+leak-10.sh \
+leak-11.sh \
+leak-12.sh \
+leak-13.sh \
+leak-14.sh \
+leak-15.sh \
+leak-16.sh \
+leak-17.sh \
+redirect-01.sh \
+redirect-02.sh \
+redirect-03.sh \
+pathinfo-01.sh \
+pathinfo-02.sh \
+broken-key-01.sh \
+broken-key-02.sh \
+broken-key-03.sh \
+broken-key-04.sh \
+continue-01.sh \
+cleanup.sh
+
+CONFS=fastcgi-10.conf \
+ fastcgi-11.conf \
+ fastcgi-12.conf \
+ fastcgi-13.conf \
+ bug-06.conf \
+ bug-12.conf
+
+TESTS_ENVIRONMENT=$(srcdir)/wrapper.sh $(srcdir) $(top_builddir)
+
+EXTRA_DIST=conformance.pl wrapper.sh testbase.sh lighttpd.conf \
+ lighttpd.user \
+ $(CONFS) \
+ $(TESTS)
+SUBDIRS=docroot
+
+leak-check:
+ for i in $(TESTS); do \
+ $(srcdir)/$$i; \
+ echo $$?; \
+ done
+
+clean-local:
+ rm -f *.out
diff --git a/tests/accessdeny-01.sh b/tests/accessdeny-01.sh
new file mode 100755
index 00000000..e9a80bc9
--- /dev/null
+++ b/tests/accessdeny-01.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+deny request for index.html~
+GET /index.html~ HTTP/1.0
+
+Status: 403
+EOF
+
+run_test
diff --git a/tests/auth-01.sh b/tests/auth-01.sh
new file mode 100755
index 00000000..59d5e7cd
--- /dev/null
+++ b/tests/auth-01.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+auth required, no token sent
+GET /server-status HTTP/1.0
+
+Status: 401
+EOF
+
+run_test
diff --git a/tests/auth-02.sh b/tests/auth-02.sh
new file mode 100755
index 00000000..ad8241b7
--- /dev/null
+++ b/tests/auth-02.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+auth required, wrong token
+GET /server-status HTTP/1.0
+Authorization: Basic amFuOmphb
+
+Status: 401
+EOF
+
+run_test
diff --git a/tests/auth-03.sh b/tests/auth-03.sh
new file mode 100755
index 00000000..906fcfb4
--- /dev/null
+++ b/tests/auth-03.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+auth required, good token
+GET /server-config HTTP/1.0
+Authorization: Basic amFuOmphbg==
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/basic-01.sh b/tests/basic-01.sh
new file mode 100755
index 00000000..1d24fd82
--- /dev/null
+++ b/tests/basic-01.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+valid request
+GET / HTTP/1.0
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/basic-02.sh b/tests/basic-02.sh
new file mode 100755
index 00000000..671111c0
--- /dev/null
+++ b/tests/basic-02.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Missing Protocol
+GET /
+
+Status: 400
+Protocol: HTTP/0.9
+EOF
+
+run_test
+
diff --git a/tests/basic-03.sh b/tests/basic-03.sh
new file mode 100755
index 00000000..438ca04e
--- /dev/null
+++ b/tests/basic-03.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Missing Protocol + Unknown Method
+ABC /
+
+Status: 400
+Protocol: HTTP/0.9
+EOF
+
+run_test
diff --git a/tests/basic-05.sh b/tests/basic-05.sh
new file mode 100755
index 00000000..4c520d43
--- /dev/null
+++ b/tests/basic-05.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Broken Request Header
+ASd
+
+Status: 400
+Protocol: HTTP/0.9
+EOF
+
+run_test
diff --git a/tests/basic-06.sh b/tests/basic-06.sh
new file mode 100755
index 00000000..b51b8c97
--- /dev/null
+++ b/tests/basic-06.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Unknown Method
+ABC / HTTP/1.0
+
+Status: 501
+EOF
+
+run_test
diff --git a/tests/basic-07.sh b/tests/basic-07.sh
new file mode 100755
index 00000000..d70b78c1
--- /dev/null
+++ b/tests/basic-07.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+##
+# using a higher protocol is always allowed as we can
+# downgrade the protocol on our own in the response
+#
+
+
+cat > $TMPFILE <<EOF
+Protocoll == HTTP/1.3
+GET / HTTP/1.3
+Host: testbase.home.kneschke.de
+
+Status: 505
+EOF
+
+run_test
diff --git a/tests/basic-08.sh b/tests/basic-08.sh
new file mode 100755
index 00000000..a6690499
--- /dev/null
+++ b/tests/basic-08.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+HTTP/1.1, with missing host
+GET / HTTP/1.1
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/basic-09.sh b/tests/basic-09.sh
new file mode 100755
index 00000000..3231a5b4
--- /dev/null
+++ b/tests/basic-09.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+HTTP/1.0, host in URI
+GET http://www.yahoo.com/ HTTP/1.0
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/basic-10.sh b/tests/basic-10.sh
new file mode 100755
index 00000000..3b1bf948
--- /dev/null
+++ b/tests/basic-10.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+##
+# apache excepts broken request headers
+#
+#
+
+cat > $TMPFILE <<EOF
+broken requestline (4 fields)
+GET http://www.yahoo.com/ HTTP/1.0 jsdh
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/basic-11.sh b/tests/basic-11.sh
new file mode 100755
index 00000000..05d19fdf
--- /dev/null
+++ b/tests/basic-11.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+##
+#
+# apache sends 400
+#
+
+cat > $TMPFILE <<EOF
+Docroot protection
+GET /../ HTTP/1.0
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/broken-header-01.sh b/tests/broken-header-01.sh
new file mode 100755
index 00000000..6ccafb7f
--- /dev/null
+++ b/tests/broken-header-01.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Header appears twice
+GET / HTTP/1.0
+Foo: foo
+Foo: foo
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/broken-key-01.sh b/tests/broken-key-01.sh
new file mode 100755
index 00000000..abbd60c6
--- /dev/null
+++ b/tests/broken-key-01.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Broken Key-Value pairs
+GET / HTTP/1.0
+ABC : jsajfsfdg
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/broken-key-02.sh b/tests/broken-key-02.sh
new file mode 100755
index 00000000..dddae9dd
--- /dev/null
+++ b/tests/broken-key-02.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Broken Key-Value pairs
+GET / HTTP/1.0
+ABC a: jsajfsfdg
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/broken-key-03.sh b/tests/broken-key-03.sh
new file mode 100755
index 00000000..af5c0b6c
--- /dev/null
+++ b/tests/broken-key-03.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Broken Key-Value pairs
+GET / HTTP/1.0
+ABC:jsajfsfdg
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/broken-key-04.sh b/tests/broken-key-04.sh
new file mode 100755
index 00000000..38d91d7d
--- /dev/null
+++ b/tests/broken-key-04.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Broken Key-Value pairs
+GET / HTTP/1.0
+ABC : jsajfsfdg
+ kde.org
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/bug-03.sh b/tests/bug-03.sh
new file mode 100755
index 00000000..c46e6735
--- /dev/null
+++ b/tests/bug-03.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "FastCGI PHPinfo"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+PHP_SELF + indexfile
+GET /indexfile/ HTTP/1.0
+Host: www.example.org
+
+Status: 200
+Content: /indexfile/index.php
+EOF
+
+run_test
+
diff --git a/tests/bug-06.conf b/tests/bug-06.conf
new file mode 100644
index 00000000..b286f48d
--- /dev/null
+++ b/tests/bug-06.conf
@@ -0,0 +1,164 @@
+server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+server.pid-file = "/tmp/lighttpd/lighttpd.pid"
+
+## bind to port (default: 80)
+server.port = 2048
+
+# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c"
+
+## bind to localhost (default: all interfaces)
+server.bind = "localhost"
+server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log"
+server.name = "www.example.org"
+server.tag = "Apache 1.3.29"
+
+fastcgi.debug = 1
+
+##
+## Format: <errorfile-prefix><status>.html
+## -> ..../status-404.html for 'File not found'
+#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-"
+
+server.dir-listing = "enable"
+
+#server.event-handler = "linux-sysepoll"
+#server.event-handler = "linux-rtsig"
+
+#server.modules.path = ""
+server.modules = (
+ "mod_rewrite",
+ "mod_setenv",
+ "mod_access",
+ "mod_auth",
+# "mod_httptls",
+ "mod_status",
+ "mod_expire",
+ "mod_simple_vhost",
+ "mod_redirect",
+# "mod_evhost",
+# "mod_localizer",
+ "mod_fastcgi",
+ "mod_cgi",
+ "mod_compress",
+ "mod_accesslog" )
+
+server.indexfiles = ( "index.html",
+ "index.htm", "default.htm", "index.php" )
+
+#,-- only root can use these options
+#|
+#|# chroot() to directory (default: no chroot() )
+#| server.chroot /
+#|# change uid to <uid> (default: don't care)
+#| server.userid wwwrun
+#|# change uid to <uid> (default: don't care)
+#| server.groupid wwwrun
+#|
+#`--
+
+
+######################## MODULE CONFIG ############################
+
+
+accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log"
+
+mimetype.assign = ( ".png" => "image/png",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".gif" => "image/gif",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".pdf" => "application/pdf",
+ ".swf" => "application/x-shockwave-flash",
+ ".spl" => "application/futuresplash",
+ ".txt" => "text/plain",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".gz" => "application/x-gzip",
+ ".c" => "text/plain",
+ ".conf" => "text/plain" )
+
+compress.cache-dir = "/tmp/lighttpd/cache/compress/"
+compress.filetype = ("text/plain", "text/html")
+
+setenv.add-environment = ( "TRAC_ENV" => "foo")
+setenv.add-request-header = ( "FOO" => "foo")
+setenv.add-response-header = ( "BAR" => "foo")
+
+fastcgi.debug = 0
+fastcgi.server = ( ".php" => (
+ "grisu" => (
+ "host" => "192.168.0.2",
+ "port" => 1026,
+# "mode" => "authorizer",
+# "docroot" => "/tmp/lighttpd/servers/www.example.org/pages/",
+ )
+ )
+ )
+
+
+cgi.assign = ( ".pl" => "/usr/bin/perl",
+ ".cgi" => "/usr/bin/perl",
+ ".py" => "/usr/bin/python" )
+
+
+
+ssl.engine = "disable"
+ssl.pemfile = "server.pem"
+
+auth.backend = "plain"
+auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user"
+auth.backend.plain.groupfile = "lighttpd.group"
+
+auth.backend.ldap.hostname = "localhost"
+auth.backend.ldap.base-dn = "dc=my-domain,dc=com"
+auth.backend.ldap.filter = "(uid=$)"
+
+auth.require = ( "/server-status" =>
+ (
+ "method" => "digest",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ ),
+ "/auth.php" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "user=jan"
+ ),
+ "/server-config" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ )
+ )
+
+url.access-deny = ( "~", ".inc")
+
+url.redirect = ( "^/redirect/$" => "http://localhost:2048/" )
+
+expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes")
+
+#cache.cache-dir = "/home/weigon/wwwroot/cache/"
+
+#### status module
+status.status-url = "/server-status"
+status.config-url = "/server-config"
+
+simple-vhost.document-root = "pages"
+simple-vhost.server-root = "/tmp/lighttpd/servers/"
+simple-vhost.default-host = "www.example.org"
+
+$HTTP["host"] == "vvv.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+}
+
+$HTTP["host"] == "zzz.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+ server.name = "zzz.example.org"
+}
+
diff --git a/tests/bug-06.sh b/tests/bug-06.sh
new file mode 100755
index 00000000..336c8919
--- /dev/null
+++ b/tests/bug-06.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "FastCGI PHPinfo"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+PHP_SELF + last indexfile
+GET /indexfile/ HTTP/1.0
+Host: www.example.org
+
+Status: 200
+Content: /indexfile/index.php
+EOF
+
+run_test
+
diff --git a/tests/bug-12.conf b/tests/bug-12.conf
new file mode 100644
index 00000000..cbac1b3f
--- /dev/null
+++ b/tests/bug-12.conf
@@ -0,0 +1,166 @@
+server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+server.pid-file = "/tmp/lighttpd/lighttpd.pid"
+
+## bind to port (default: 80)
+server.port = 2048
+
+# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c"
+
+## bind to localhost (default: all interfaces)
+server.bind = "localhost"
+server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log"
+server.name = "www.example.org"
+server.tag = "Apache 1.3.29"
+
+fastcgi.debug = 1
+
+##
+## Format: <errorfile-prefix><status>.html
+## -> ..../status-404.html for 'File not found'
+#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-"
+
+server.dir-listing = "enable"
+
+#server.event-handler = "linux-sysepoll"
+#server.event-handler = "linux-rtsig"
+
+#server.modules.path = ""
+server.modules = (
+ "mod_rewrite",
+ "mod_setenv",
+ "mod_access",
+ "mod_auth",
+# "mod_httptls",
+ "mod_status",
+ "mod_expire",
+ "mod_simple_vhost",
+ "mod_redirect",
+# "mod_evhost",
+# "mod_localizer",
+ "mod_fastcgi",
+ "mod_cgi",
+ "mod_compress",
+ "mod_accesslog" )
+
+server.indexfiles = ( "index.html",
+ "index.htm", "default.htm", "index.php" )
+
+server.error-handler-404 = "/indexfile/return-404.php"
+
+#,-- only root can use these options
+#|
+#|# chroot() to directory (default: no chroot() )
+#| server.chroot /
+#|# change uid to <uid> (default: don't care)
+#| server.userid wwwrun
+#|# change uid to <uid> (default: don't care)
+#| server.groupid wwwrun
+#|
+#`--
+
+
+######################## MODULE CONFIG ############################
+
+
+accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log"
+
+mimetype.assign = ( ".png" => "image/png",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".gif" => "image/gif",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".pdf" => "application/pdf",
+ ".swf" => "application/x-shockwave-flash",
+ ".spl" => "application/futuresplash",
+ ".txt" => "text/plain",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".gz" => "application/x-gzip",
+ ".c" => "text/plain",
+ ".conf" => "text/plain" )
+
+compress.cache-dir = "/tmp/lighttpd/cache/compress/"
+compress.filetype = ("text/plain", "text/html")
+
+setenv.add-environment = ( "TRAC_ENV" => "foo")
+setenv.add-request-header = ( "FOO" => "foo")
+setenv.add-response-header = ( "BAR" => "foo")
+
+fastcgi.debug = 0
+fastcgi.server = ( ".php" => (
+ "grisu" => (
+ "host" => "192.168.0.2",
+ "port" => 1026,
+# "mode" => "authorizer",
+# "docroot" => "/tmp/lighttpd/servers/www.example.org/pages/",
+ )
+ )
+ )
+
+
+cgi.assign = ( ".pl" => "/usr/bin/perl",
+ ".cgi" => "/usr/bin/perl",
+ ".py" => "/usr/bin/python" )
+
+
+
+ssl.engine = "disable"
+ssl.pemfile = "server.pem"
+
+auth.backend = "plain"
+auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user"
+auth.backend.plain.groupfile = "lighttpd.group"
+
+auth.backend.ldap.hostname = "localhost"
+auth.backend.ldap.base-dn = "dc=my-domain,dc=com"
+auth.backend.ldap.filter = "(uid=$)"
+
+auth.require = ( "/server-status" =>
+ (
+ "method" => "digest",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ ),
+ "/auth.php" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "user=jan"
+ ),
+ "/server-config" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ )
+ )
+
+url.access-deny = ( "~", ".inc")
+
+url.redirect = ( "^/redirect/$" => "http://localhost:2048/" )
+
+expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes")
+
+#cache.cache-dir = "/home/weigon/wwwroot/cache/"
+
+#### status module
+status.status-url = "/server-status"
+status.config-url = "/server-config"
+
+simple-vhost.document-root = "pages"
+simple-vhost.server-root = "/tmp/lighttpd/servers/"
+simple-vhost.default-host = "www.example.org"
+
+$HTTP["host"] == "vvv.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+}
+
+$HTTP["host"] == "zzz.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+ server.name = "zzz.example.org"
+}
+
diff --git a/tests/bug-12.sh b/tests/bug-12.sh
new file mode 100755
index 00000000..7bdb0690
--- /dev/null
+++ b/tests/bug-12.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "FastCGI PHPinfo"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+FastCGI + errorhandler
+POST /indexfile/abc HTTP/1.0
+Host: www.example.org
+Content-Length: 0
+
+Status: 404
+Content: /indexfile/return-404.php
+EOF
+
+run_test
+
diff --git a/tests/bug-14.sh b/tests/bug-14.sh
new file mode 100755
index 00000000..505f15ec
--- /dev/null
+++ b/tests/bug-14.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+CGI + NPH
+GET /nph-status.pl HTTP/1.0
+Host: www.example.org
+
+Status: 200
+EOF
+
+run_test
+
diff --git a/tests/bug-15-2.sh b/tests/bug-15-2.sh
new file mode 100755
index 00000000..371dbc80
--- /dev/null
+++ b/tests/bug-15-2.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+if id weigon > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "userdir"
+ exit 77
+fi
+
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+userdir for ~weigon + redirect
+GET /~weigon HTTP/1.0
+Host: www.example.org
+
+Status: 301
+Location: http://www.example.org/~weigon/
+EOF
+
+run_test
+
diff --git a/tests/bug-15-3.sh b/tests/bug-15-3.sh
new file mode 100755
index 00000000..b51810f7
--- /dev/null
+++ b/tests/bug-15-3.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+if id weigon > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "userdir"
+ exit 77
+fi
+
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+userdir for ~weigon + redirect
+GET /~weigon HTTP/1.0
+
+Status: 301
+Location: http://localhost:2048/~weigon/
+EOF
+
+run_test
+
diff --git a/tests/bug-15.sh b/tests/bug-15.sh
new file mode 100755
index 00000000..eb33eb35
--- /dev/null
+++ b/tests/bug-15.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+
+if id weigon > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "userdir"
+ exit 77
+fi
+
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+userdir for ~weigon
+GET /~weigon/ HTTP/1.0
+Host: www.example.org
+
+Status: 200
+EOF
+
+run_test
+
diff --git a/tests/bug-urldecode-00.sh b/tests/bug-urldecode-00.sh
new file mode 100755
index 00000000..4f34e841
--- /dev/null
+++ b/tests/bug-urldecode-00.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Check that %00 is decoded correctly
+GET /%00 HTTP/1.0
+Foo: foo
+Foo: foo
+
+Status: 404
+EOF
+
+run_test
diff --git a/tests/cgi-01.sh b/tests/cgi-01.sh
new file mode 100755
index 00000000..3406a3ae
--- /dev/null
+++ b/tests/cgi-01.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+CGI
+GET /cgi.pl HTTP/1.0
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/cgi-02.sh b/tests/cgi-02.sh
new file mode 100755
index 00000000..3c0d3f0f
--- /dev/null
+++ b/tests/cgi-02.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+CGI - SCRIPT_NAME (+pathinfo)
+GET /cgi.pl/foo HTTP/1.0
+
+Status: 200
+Content: /cgi.pl
+EOF
+
+run_test
diff --git a/tests/cgi-03.sh b/tests/cgi-03.sh
new file mode 100755
index 00000000..a9b81186
--- /dev/null
+++ b/tests/cgi-03.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+CGI + pathinfo
+GET /cgi-pathinfo.pl/foo HTTP/1.0
+
+Status: 200
+Content: /foo
+EOF
+
+run_test
diff --git a/tests/cleanup.sh b/tests/cleanup.sh
new file mode 100755
index 00000000..88e241f7
--- /dev/null
+++ b/tests/cleanup.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+tmpdir=/tmp/lighttpd
+
+if test x$srcdir = x; then
+ srcdir=.
+fi
+
+# create test-framework
+# rm -rf $tmpdir
+
+printf "%-40s" "cleaning up"
+
+exit 0
diff --git a/tests/compress-01.sh b/tests/compress-01.sh
new file mode 100755
index 00000000..4fcfc77c
--- /dev/null
+++ b/tests/compress-01.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Compression - deflate
+GET /index.html HTTP/1.0
+Accept-Encoding: deflate
+
+Status: 200
+MUST: Vary
+EOF
+
+run_test
diff --git a/tests/compress-02.sh b/tests/compress-02.sh
new file mode 100755
index 00000000..ae91a9bc
--- /dev/null
+++ b/tests/compress-02.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Compression - deflate
+GET /index.html HTTP/1.0
+Accept-Encoding: deflate
+
+Status: 200
+Content-Length: 1288
+MUST: Vary Content-Encoding
+EOF
+
+run_test
diff --git a/tests/compress-03.sh b/tests/compress-03.sh
new file mode 100755
index 00000000..830e02f9
--- /dev/null
+++ b/tests/compress-03.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Compression - gzip
+GET /index.html HTTP/1.0
+Accept-Encoding: gzip
+
+Status: 200
+MUST: Vary Content-Encoding
+EOF
+
+run_test
diff --git a/tests/compress-04.sh b/tests/compress-04.sh
new file mode 100755
index 00000000..c08f3975
--- /dev/null
+++ b/tests/compress-04.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Compression - gzip
+GET /index.txt HTTP/1.0
+Accept-Encoding: gzip, deflate
+Host: www.example.org
+
+Status: 200
+MIGHT: Content-Encoding Vary
+EOF
+
+run_test
diff --git a/tests/conformance.pl b/tests/conformance.pl
new file mode 100755
index 00000000..7f754975
--- /dev/null
+++ b/tests/conformance.pl
@@ -0,0 +1,164 @@
+#! /usr/bin/perl -w
+
+use strict;
+use IO::Socket;
+
+my $EOL = "\015\012";
+my $BLANK = $EOL x 2;
+
+
+my @f = <STDIN>;
+
+# drop first line
+my $headline = shift @f;
+chomp $headline;
+printf STDERR "%-40s", $headline." ";
+
+
+my $remote =
+ IO::Socket::INET->new(Proto => "tcp",
+ PeerAddr => "127.0.0.1",
+ PeerPort => $#ARGV == 0 ? "1025" : "2048")
+ or die "cannot connect to remote host";
+
+$remote->autoflush(1);
+
+my %y;
+my $m = 0;
+my $line = 0;
+my $method;
+foreach(@f) {
+ if (/^$/) {
+ $m = 1;
+ next;
+ }
+
+ chomp;
+ if ($m == 0) {
+ # header line
+ #
+ if ($line++ == 0) {
+ ($method = $_ ) =~ s/ .*//;
+ }
+ print $remote $_.$EOL;
+ } else {
+ my ($key, $value) = split /: /, $_;
+
+ $y{$key} = $value;
+ }
+}
+print $remote $EOL;
+
+my $ln = 0;
+my $error = 0;
+my $con_len = -1;
+my $body = "";
+$m = 0;
+
+my %header;
+while(<$remote>) {
+ $ln++;
+
+# print STDERR $_;
+
+ if ($ln == 1) {
+ if (/^HTTP/) {
+ my ($proto, $status, $text) = split / /, $_, 3;
+ if (defined $y{"Status"}) {
+ if ($status ne $y{"Status"}) {
+ $error = 1;
+ print STDERR "E: wrong Status code - ";
+ }
+ }
+ } elsif ($y{"Protocol"} eq "HTTP/0.9") {
+ # we expected HTTP/0.9 or Bad Protocol
+ $m = 1;
+ } else {
+ $error = 1;
+ print STDERR "E: broken something - ";
+ }
+ } elsif ($m == 0) {
+ # response header
+ my ($key, $value) = split /: /, $_;
+
+ if (not /^\r$/) {
+ ($header{$key} = $value) =~ s/\r\n$//;
+ }
+ }
+
+ # grep for content-length
+ if (/^Content-Length: ([0-9]+)\r$/) {
+ $con_len = $1;
+ }
+
+ if ($m == 1) {
+ $body .= $_;
+ }
+
+ if (/^\r$/) {
+ $m = 1;
+ }
+
+ print $_;
+
+ if ($m == 1 && (length($body) == $con_len)) {
+# print STDERR length($body)." - ".$con_len."\n";
+ last;
+ }
+}
+
+close $remote;
+
+if ($con_len != -1 && $method ne "HEAD" && $m == 1 && (length($body) != $con_len)) {
+ $error = 1;
+ print STDERR "E: wrong content-length - ";
+}
+
+# check the MUST header
+
+if (defined $y{"MUST"}) {
+ foreach (split / /, $y{"MUST"}) {
+ if (not defined $header{$_}) {
+ $error = 1;
+ print STDERR "E: MUST missing - ";
+ }
+ }
+}
+my $might = 0;
+if (defined $y{"MIGHT"}) {
+ foreach (split / /, $y{"MIGHT"}) {
+ if (not defined $header{$_}) {
+ $might = 1;
+ }
+ }
+}
+
+if (defined $y{"Content"}) {
+ if ($body ne $y{"Content"}) {
+ $error = 1;
+ print STDERR "E: Content doesn't match - ";
+ }
+}
+
+foreach (keys %y) {
+ next if /^MIGHT$/;
+ next if /^MUST$/;
+ next if /^Status$/;
+ next if /^Protocol$/;
+ next if /^Content$/;
+
+ if ((not defined $header{$_}) ||
+ ($header{$_} ne $y{$_})) {
+ $error = 1;
+ print STDERR "E: headerline missing - ";
+ }
+}
+
+if ($error) {
+ exit 1;
+} elsif ($might) {
+ exit 77;
+} else {
+ exit 0;
+}
+
diff --git a/tests/content-01.sh b/tests/content-01.sh
new file mode 100755
index 00000000..de4b6089
--- /dev/null
+++ b/tests/content-01.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+docroot=/tmp/lighttpd/servers/123.example.org/pages/
+reqfile=12345.txt
+test -d $docroot || exit 77
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Content-Type
+GET /$reqfile HTTP/1.0
+Host: 123.example.org
+
+Content-Type: text/plain
+EOF
+
+run_test_script
+
+if test x$exitcode = x0; then
+
+ if cat $NAME.out | sed '1,/^.$/d' | cmp - $docroot/$reqfile; then
+ a=a
+ else
+ exitcode=-1
+ fi
+fi
+
+run_test_exit
diff --git a/tests/content-02.sh b/tests/content-02.sh
new file mode 100755
index 00000000..6974e5f3
--- /dev/null
+++ b/tests/content-02.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+docroot=/tmp/lighttpd/servers/123.example.org/pages/
+reqfile=12345.html
+test -d $docroot || exit 77
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Content-Type
+GET /$reqfile HTTP/1.0
+Host: 123.example.org
+
+Content-Type: text/html
+EOF
+
+run_test_script
+
+if test x$exitcode = x0; then
+
+ if cat $NAME.out | sed '1,/^.$/d' | cmp - $docroot/$reqfile; then
+ a=a
+ else
+ exitcode=-1
+ fi
+fi
+
+run_test_exit
diff --git a/tests/content-03.sh b/tests/content-03.sh
new file mode 100755
index 00000000..3cfdbd39
--- /dev/null
+++ b/tests/content-03.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+docroot=/tmp/lighttpd/servers/123.example.org/pages/
+reqfile=dummyfile.bla
+test -d $docroot || exit 77
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Content-Type
+GET /$reqfile HTTP/1.0
+Host: 123.example.org
+
+Content-Type: application/octet-stream
+EOF
+
+run_test_script
+
+if test x$exitcode = x0; then
+
+ if cat $NAME.out | sed '1,/^.$/d' | cmp - $docroot/$reqfile; then
+ a=a
+ else
+ exitcode=-1
+ fi
+fi
+
+run_test_exit
diff --git a/tests/content-04.sh b/tests/content-04.sh
new file mode 100755
index 00000000..2c2e01e7
--- /dev/null
+++ b/tests/content-04.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "Getting PHP code"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+docroot=/tmp/lighttpd/servers/123.example.org/pages/
+reqfile=phpinfo.php
+test -d $docroot || exit 77
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Content-Type
+GET /$reqfile HTTP/1.0
+Host: 123.example.org
+
+EOF
+
+run_test_script
+
+if test x$exitcode = x0; then
+ # got the source of the php-file
+ if cat $NAME.out | sed '1,/^.$/d' | cmp - $docroot/$reqfile; then
+ exitcode=-1
+ fi > /dev/null
+fi
+
+run_test_exit
diff --git a/tests/content-length-01.sh b/tests/content-length-01.sh
new file mode 100755
index 00000000..1d3e4b88
--- /dev/null
+++ b/tests/content-length-01.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Content-Length, HTML
+GET /12345.html HTTP/1.0
+Host: 123.example.org
+
+Status: 200
+Content-Length: 6
+EOF
+
+run_test
diff --git a/tests/content-length-02.sh b/tests/content-length-02.sh
new file mode 100755
index 00000000..a271b224
--- /dev/null
+++ b/tests/content-length-02.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Content-Length, HTML
+GET /12345.txt HTTP/1.0
+Host: 123.example.org
+
+Status: 200
+Content-Length: 6
+EOF
+
+run_test
diff --git a/tests/content-length-03.sh b/tests/content-length-03.sh
new file mode 100755
index 00000000..547dc6ee
--- /dev/null
+++ b/tests/content-length-03.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Content-Length < 0
+POST /12345.txt HTTP/1.0
+Host: 123.example.org
+Content-Length: -473
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/content-length-04.sh b/tests/content-length-04.sh
new file mode 100755
index 00000000..3b57b12d
--- /dev/null
+++ b/tests/content-length-04.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Content-Length < 0
+POST /12345.txt HTTP/1.0
+Host: 123.example.org
+Content-Length: 2147483648
+
+Status: 413
+EOF
+
+run_test
diff --git a/tests/content-length-05.sh b/tests/content-length-05.sh
new file mode 100755
index 00000000..f528c78e
--- /dev/null
+++ b/tests/content-length-05.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Content-Length < 0
+POST /12345.txt HTTP/1.0
+Host: 123.example.org
+Content-Length:
+
+Status: 411
+EOF
+
+run_test
diff --git a/tests/continue-01.sh b/tests/continue-01.sh
new file mode 100755
index 00000000..26e090c1
--- /dev/null
+++ b/tests/continue-01.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Continue Handling
+GET / HTTP/1.1
+Connection: Close
+Expect: 100-continue
+
+Status: 417
+EOF
+
+run_test
diff --git a/tests/docroot/123/12345.html b/tests/docroot/123/12345.html
new file mode 100644
index 00000000..e56e15bb
--- /dev/null
+++ b/tests/docroot/123/12345.html
@@ -0,0 +1 @@
+12345
diff --git a/tests/docroot/123/12345.txt b/tests/docroot/123/12345.txt
new file mode 100644
index 00000000..e56e15bb
--- /dev/null
+++ b/tests/docroot/123/12345.txt
@@ -0,0 +1 @@
+12345
diff --git a/tests/docroot/123/Makefile.am b/tests/docroot/123/Makefile.am
new file mode 100644
index 00000000..64712d7a
--- /dev/null
+++ b/tests/docroot/123/Makefile.am
@@ -0,0 +1 @@
+EXTRA_DIST=12345.html 12345.txt dummyfile.bla phpinfo.php
diff --git a/tests/docroot/123/dummyfile.bla b/tests/docroot/123/dummyfile.bla
new file mode 100644
index 00000000..024416aa
--- /dev/null
+++ b/tests/docroot/123/dummyfile.bla
@@ -0,0 +1 @@
+ߤ¨*rð.üGC䳜‰nÑ$ê.ª!bgX¥8{©³[)ûx¸”žÕTª­¨ç¸jã+ ò~ Ä/V¼¼UŒúCI vn{ÀXKïFŽÝÍܸ¥íyÕ Â(bE¨¼çD‡>ŒÏÛ†[Q ²¦´ :±ô=¬H+Ç>{ó½fKÝÆaO€ûžod¹ šï$ÜÍ­ÎUïayø«Üç&QWÑ$¡§Œšóqìu~&o^Û5ˆ&….¶ë˜04gšt€.nä ¤Ò…O28%W @;8Ì&4 k9¸h/«Ú™Zø £™m°ÅÁ[ä´ß¿0õìL@úÖÐè'^øjfð–‹bJ ¼ðí±cAÖ/i‹>.§ÆÔPxüÛ–ÝiÐ2„HîÅʇḻ48ÿËÄ'ZkÎàÀþ¼&¡!?@Fü-o©j&€Âÿ}õ¹`1Í«!ϸ¤† \ No newline at end of file
diff --git a/tests/docroot/123/phpinfo.php b/tests/docroot/123/phpinfo.php
new file mode 100644
index 00000000..147cebcd
--- /dev/null
+++ b/tests/docroot/123/phpinfo.php
@@ -0,0 +1 @@
+<?php phpinfo(); ?>
diff --git a/tests/docroot/Makefile.am b/tests/docroot/Makefile.am
new file mode 100644
index 00000000..d14aa288
--- /dev/null
+++ b/tests/docroot/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS=123 www
diff --git a/tests/docroot/www/Makefile.am b/tests/docroot/www/Makefile.am
new file mode 100644
index 00000000..1ea8c469
--- /dev/null
+++ b/tests/docroot/www/Makefile.am
@@ -0,0 +1,4 @@
+EXTRA_DIST=cgi.php cgi.pl dummydir index.html index.txt phpinfo.php \
+ phpself.php redirect.php cgi-pathinfo.pl phphost.php \
+ nph-status.pl
+SUBDIRS=go indexfile
diff --git a/tests/docroot/www/cgi-pathinfo.pl b/tests/docroot/www/cgi-pathinfo.pl
new file mode 100644
index 00000000..af8d4dfb
--- /dev/null
+++ b/tests/docroot/www/cgi-pathinfo.pl
@@ -0,0 +1,7 @@
+#! /usr/bin/perl
+
+print "Content-Type: text/html\r\n\r\n";
+
+print $ENV{"PATH_INFO"};
+
+0;
diff --git a/tests/docroot/www/cgi.php b/tests/docroot/www/cgi.php
new file mode 100755
index 00000000..d92e52f9
--- /dev/null
+++ b/tests/docroot/www/cgi.php
@@ -0,0 +1,9 @@
+<?php
+
+#ob_start(/*"ob_gzhandler"*/);
+print "12345<br />\n";
+#phpinfo();
+#header("Content-Length: ".ob_get_length());
+#ob_end_flush();
+
+?>
diff --git a/tests/docroot/www/cgi.pl b/tests/docroot/www/cgi.pl
new file mode 100644
index 00000000..9695adf6
--- /dev/null
+++ b/tests/docroot/www/cgi.pl
@@ -0,0 +1,7 @@
+#! /usr/bin/perl
+
+print "Content-Type: text/html\r\n\r\n";
+
+print $ENV{"SCRIPT_NAME"};
+
+0;
diff --git a/tests/docroot/www/go/Makefile.am b/tests/docroot/www/go/Makefile.am
new file mode 100644
index 00000000..fe20c503
--- /dev/null
+++ b/tests/docroot/www/go/Makefile.am
@@ -0,0 +1 @@
+EXTRA_DIST=cgi.php
diff --git a/tests/docroot/www/go/cgi.php b/tests/docroot/www/go/cgi.php
new file mode 100755
index 00000000..d92e52f9
--- /dev/null
+++ b/tests/docroot/www/go/cgi.php
@@ -0,0 +1,9 @@
+<?php
+
+#ob_start(/*"ob_gzhandler"*/);
+print "12345<br />\n";
+#phpinfo();
+#header("Content-Length: ".ob_get_length());
+#ob_end_flush();
+
+?>
diff --git a/tests/docroot/www/index.html b/tests/docroot/www/index.html
new file mode 100644
index 00000000..3c149671
--- /dev/null
+++ b/tests/docroot/www/index.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML>
+<HEAD>
+<TITLE>Webserver testpage</TITLE>
+<META content="iso-8859-1" http-equiv="charset">
+<META name="author" content="Christian Hofmann, SuSE Linux AG">
+</HEAD>
+<BODY BGCOLOR="#ffffff" LINK="#669900" ALINK="#669900" VLINK="#fb8000" text=black marginwidth="0" marginheight="0" leftmargin="0" topmargin="0">
+<TABLE border="0" width="760" cellpadding="0" cellspacing="0">
+ <TR>
+ <TD rowspan="4" bgcolor="#669900" width="50">&nbsp;</TD>
+ <td valign=top height="70" width="29" bgcolor="#669900">&nbsp;</td>
+ <td valign="middle" height="70" width="152" align="center" bgcolor="#669900">&nbsp;</td>
+ <td valign=middle height="70" width="529" bgcolor="#669900">
+ <div align="center">
+ <font face="Courier New,Courier,mono" size="5" color="white"><B>+++ testinfo - webserver +++</b></font>
+ </div>
+ </td>
+ </tr>
+ <TR>
+ <td width="29" height="100" valign="middle" bgcolor="#669900">&nbsp;</td>
+ <td width="152" height="100" align="center" valign="bottom"><IMG src="gif/penguin.gif" width=90 height=76 hspace=0 vspace=0 border=0 alt=" "></td>
+ <td width="529" height="100" valign="middle">
+ <div align="center">
+ <font face="Courier New,Courier,mono" size="3"><I>This is only a test page for the webserver!</I></font><br>
+ <font face="Courier New,Courier,mono" size="-1">SuSE is not responsible for the contents of this domain!</font>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td valign=top align=left colspan="4">
+ <table border="0" cellpadding="0" cellspacing="0" width="710" vspace="0" hspace="0">
+ <TR>
+ <TD bgcolor="#669900" width="29" height="20">&nbsp;</TD>
+ <TD width="152" height="20">&nbsp;</TD>
+ <TD width="558" height="20" colspan="2">&nbsp;</TD>
+ </tr>
+ <TR>
+ <TD height="50" align=left width="181" colspan="2"><IMG src="gif/sysinfo_en.png" width="181" height="50" border="0" alt="system information"></TD>
+ <TD width="529" colspan="2" rowspan="2" valign=top>
+ <FONT face="helvetica, arial, sans-serif">
+ <B>Operating system:</B> [ SuSE Linux 8.0 (i386)
+ ]<BR>
+ <B>Host:</B> [ grisu.home.kneschke.de, Kernel: 2.4.18-4GB (i686)
+ ]
+ </FONT>
+ </TD>
+ </TR>
+ <TR>
+ <TD bgcolor="#669900" width="29" height="40">&nbsp;</TD>
+ <TD width="181" height="40">&nbsp;</TD>
+ </TR>
+
+ <TR>
+ <TD height="50" align=left width="181" colspan="2"><IMG src="gif/version_en.png" width="181" height="50" border="0" alt="webserver and modules"></TD>
+ <TD width="529" colspan="2" rowspan="2" valign=top>
+ <FONT face="helvetica,arial,sans-serif">
+ <B>Webserver version:</B><br>
+ [ lighttpd/0.1.0 (Unix) ]<br><br>
+
+ <B>Installed modules:</B><br>
+ <I>[ PHP module is not installed ]</I><br>
+ <I>[ Apache perl module (mod_perl) is not installed ]</I><br>
+ <I>[ Apache DAV module (mod_dav) is not installed ]</I><br>
+ <I>[ Apache Python module (mod_python) is not installed ]</I><br>
+
+ </FONT>
+ </TD>
+ </TR>
+ <TR>
+ <TD bgcolor="#669900" width="29" height="100">&nbsp;</TD>
+ <TD width="181" height="100">&nbsp;</TD>
+ </TR>
+ <TR>
+ <TD colspan="2" height="50" align=left width="181"><IMG src="gif/docu_en.png" width="181" height="50" border="0" alt="documentation"></TD>
+ <TD width="529" colspan="2" rowspan="2" valign=top>
+ <FONT face="helvetica,arial,sans-serif">
+<I>[ This host is not configured as server for the SuSE help system ]</I><br><br>
+<I>[ Apache manual is not installed ]</I><br>
+
+ <BR>
+ <A HREF="http://www.suse.de/">[ The SuSE website ]</A><BR>
+
+
+
+ </FONT>
+ </TD>
+
+ </TR>
+ <TR>
+ <TD bgcolor="#669900" width="29" height="90">&nbsp;</TD>
+ <TD width="181" height="90">&nbsp;</TD>
+ </Table>
+ </td>
+ </tr>
+ <tr>
+ <td width=29 bgcolor="#669900">&nbsp;</td>
+ <td valign=bottom align=right width="681" colspan="3"><A HREF="http://www.suse.de/en/"><IMG src="gif/powered_by_suse.gif" alt="powered by SuSE" width=100 height=40 hspace=5 vspace=5 border=0></A></td>
+ </tr>
+
+</TABLE>
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/docroot/www/index.txt b/tests/docroot/www/index.txt
new file mode 100644
index 00000000..3c149671
--- /dev/null
+++ b/tests/docroot/www/index.txt
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML>
+<HEAD>
+<TITLE>Webserver testpage</TITLE>
+<META content="iso-8859-1" http-equiv="charset">
+<META name="author" content="Christian Hofmann, SuSE Linux AG">
+</HEAD>
+<BODY BGCOLOR="#ffffff" LINK="#669900" ALINK="#669900" VLINK="#fb8000" text=black marginwidth="0" marginheight="0" leftmargin="0" topmargin="0">
+<TABLE border="0" width="760" cellpadding="0" cellspacing="0">
+ <TR>
+ <TD rowspan="4" bgcolor="#669900" width="50">&nbsp;</TD>
+ <td valign=top height="70" width="29" bgcolor="#669900">&nbsp;</td>
+ <td valign="middle" height="70" width="152" align="center" bgcolor="#669900">&nbsp;</td>
+ <td valign=middle height="70" width="529" bgcolor="#669900">
+ <div align="center">
+ <font face="Courier New,Courier,mono" size="5" color="white"><B>+++ testinfo - webserver +++</b></font>
+ </div>
+ </td>
+ </tr>
+ <TR>
+ <td width="29" height="100" valign="middle" bgcolor="#669900">&nbsp;</td>
+ <td width="152" height="100" align="center" valign="bottom"><IMG src="gif/penguin.gif" width=90 height=76 hspace=0 vspace=0 border=0 alt=" "></td>
+ <td width="529" height="100" valign="middle">
+ <div align="center">
+ <font face="Courier New,Courier,mono" size="3"><I>This is only a test page for the webserver!</I></font><br>
+ <font face="Courier New,Courier,mono" size="-1">SuSE is not responsible for the contents of this domain!</font>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td valign=top align=left colspan="4">
+ <table border="0" cellpadding="0" cellspacing="0" width="710" vspace="0" hspace="0">
+ <TR>
+ <TD bgcolor="#669900" width="29" height="20">&nbsp;</TD>
+ <TD width="152" height="20">&nbsp;</TD>
+ <TD width="558" height="20" colspan="2">&nbsp;</TD>
+ </tr>
+ <TR>
+ <TD height="50" align=left width="181" colspan="2"><IMG src="gif/sysinfo_en.png" width="181" height="50" border="0" alt="system information"></TD>
+ <TD width="529" colspan="2" rowspan="2" valign=top>
+ <FONT face="helvetica, arial, sans-serif">
+ <B>Operating system:</B> [ SuSE Linux 8.0 (i386)
+ ]<BR>
+ <B>Host:</B> [ grisu.home.kneschke.de, Kernel: 2.4.18-4GB (i686)
+ ]
+ </FONT>
+ </TD>
+ </TR>
+ <TR>
+ <TD bgcolor="#669900" width="29" height="40">&nbsp;</TD>
+ <TD width="181" height="40">&nbsp;</TD>
+ </TR>
+
+ <TR>
+ <TD height="50" align=left width="181" colspan="2"><IMG src="gif/version_en.png" width="181" height="50" border="0" alt="webserver and modules"></TD>
+ <TD width="529" colspan="2" rowspan="2" valign=top>
+ <FONT face="helvetica,arial,sans-serif">
+ <B>Webserver version:</B><br>
+ [ lighttpd/0.1.0 (Unix) ]<br><br>
+
+ <B>Installed modules:</B><br>
+ <I>[ PHP module is not installed ]</I><br>
+ <I>[ Apache perl module (mod_perl) is not installed ]</I><br>
+ <I>[ Apache DAV module (mod_dav) is not installed ]</I><br>
+ <I>[ Apache Python module (mod_python) is not installed ]</I><br>
+
+ </FONT>
+ </TD>
+ </TR>
+ <TR>
+ <TD bgcolor="#669900" width="29" height="100">&nbsp;</TD>
+ <TD width="181" height="100">&nbsp;</TD>
+ </TR>
+ <TR>
+ <TD colspan="2" height="50" align=left width="181"><IMG src="gif/docu_en.png" width="181" height="50" border="0" alt="documentation"></TD>
+ <TD width="529" colspan="2" rowspan="2" valign=top>
+ <FONT face="helvetica,arial,sans-serif">
+<I>[ This host is not configured as server for the SuSE help system ]</I><br><br>
+<I>[ Apache manual is not installed ]</I><br>
+
+ <BR>
+ <A HREF="http://www.suse.de/">[ The SuSE website ]</A><BR>
+
+
+
+ </FONT>
+ </TD>
+
+ </TR>
+ <TR>
+ <TD bgcolor="#669900" width="29" height="90">&nbsp;</TD>
+ <TD width="181" height="90">&nbsp;</TD>
+ </Table>
+ </td>
+ </tr>
+ <tr>
+ <td width=29 bgcolor="#669900">&nbsp;</td>
+ <td valign=bottom align=right width="681" colspan="3"><A HREF="http://www.suse.de/en/"><IMG src="gif/powered_by_suse.gif" alt="powered by SuSE" width=100 height=40 hspace=5 vspace=5 border=0></A></td>
+ </tr>
+
+</TABLE>
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/docroot/www/indexfile/Makefile.am b/tests/docroot/www/indexfile/Makefile.am
new file mode 100644
index 00000000..2487a42b
--- /dev/null
+++ b/tests/docroot/www/indexfile/Makefile.am
@@ -0,0 +1 @@
+EXTRA_DIST=index.php return-404.php
diff --git a/tests/docroot/www/indexfile/index.php b/tests/docroot/www/indexfile/index.php
new file mode 100644
index 00000000..e0c7d9ec
--- /dev/null
+++ b/tests/docroot/www/indexfile/index.php
@@ -0,0 +1 @@
+<?php print $_SERVER["PHP_SELF"]; ?>
diff --git a/tests/docroot/www/indexfile/return-404.php b/tests/docroot/www/indexfile/return-404.php
new file mode 100644
index 00000000..dd680cc5
--- /dev/null
+++ b/tests/docroot/www/indexfile/return-404.php
@@ -0,0 +1,5 @@
+<?php
+ header("Status: 404");
+
+ print $_SERVER["PHP_SELF"];
+?>
diff --git a/tests/docroot/www/nph-status.pl b/tests/docroot/www/nph-status.pl
new file mode 100755
index 00000000..602cdc70
--- /dev/null
+++ b/tests/docroot/www/nph-status.pl
@@ -0,0 +1,4 @@
+#!/usr/bin/perl
+
+print "HTTP/1.0 30 FooBar\r\n";
+print "\r\n";
diff --git a/tests/docroot/www/phphost.php b/tests/docroot/www/phphost.php
new file mode 100644
index 00000000..edf2f68e
--- /dev/null
+++ b/tests/docroot/www/phphost.php
@@ -0,0 +1,3 @@
+<?php
+ print $_SERVER["SERVER_NAME"];
+?>
diff --git a/tests/docroot/www/phpinfo.php b/tests/docroot/www/phpinfo.php
new file mode 100644
index 00000000..147cebcd
--- /dev/null
+++ b/tests/docroot/www/phpinfo.php
@@ -0,0 +1 @@
+<?php phpinfo(); ?>
diff --git a/tests/docroot/www/phpself.php b/tests/docroot/www/phpself.php
new file mode 100644
index 00000000..b78b9953
--- /dev/null
+++ b/tests/docroot/www/phpself.php
@@ -0,0 +1,3 @@
+<?php
+ print $_SERVER["PHP_SELF"];
+?>
diff --git a/tests/docroot/www/redirect.php b/tests/docroot/www/redirect.php
new file mode 100644
index 00000000..0489d22d
--- /dev/null
+++ b/tests/docroot/www/redirect.php
@@ -0,0 +1,4 @@
+<?php
+
+ header('Location: http://www.example.org:2048/');
+?>
diff --git a/tests/fastcgi-01.sh b/tests/fastcgi-01.sh
new file mode 100755
index 00000000..d134a160
--- /dev/null
+++ b/tests/fastcgi-01.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "FastCGI PHPinfo"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+FastCGI PHPinfo
+GET /phpinfo.php HTTP/1.0
+Host: www.example.org
+
+Status: 200
+EOF
+
+run_test
+
diff --git a/tests/fastcgi-02.sh b/tests/fastcgi-02.sh
new file mode 100755
index 00000000..79a0aacb
--- /dev/null
+++ b/tests/fastcgi-02.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "FastCGI - missing File"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+FastCGI - missing File
+GET /phpinfajdhdo.php HTTP/1.1
+Host: www.example.org
+
+Status: 404
+EOF
+
+run_test
diff --git a/tests/fastcgi-03.sh b/tests/fastcgi-03.sh
new file mode 100755
index 00000000..f18c2f87
--- /dev/null
+++ b/tests/fastcgi-03.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "index-file -> FastCGI"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+
+cat > $TMPFILE <<EOF
+index-file -> FastCGI
+GET /go/ HTTP/1.0
+Host: www.example.org
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/fastcgi-04.sh b/tests/fastcgi-04.sh
new file mode 100755
index 00000000..4b5eb738
--- /dev/null
+++ b/tests/fastcgi-04.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "Redirect in PHP"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+
+cat > $TMPFILE <<EOF
+Redirect in PHP
+GET /redirect.php HTTP/1.0
+Host: www.example.org
+Conntection: close
+
+Status: 302
+Location: http://www.example.org:2048/
+EOF
+
+run_test
diff --git a/tests/fastcgi-05.sh b/tests/fastcgi-05.sh
new file mode 100755
index 00000000..e0aecc7e
--- /dev/null
+++ b/tests/fastcgi-05.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "Redirect in PHP"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+
+cat > $TMPFILE <<EOF
+PHP_SELF
+GET /phpself.php HTTP/1.0
+Host: www.example.org
+Conntection: close
+
+Status: 200
+Content: /phpself.php
+EOF
+
+run_test
diff --git a/tests/fastcgi-06.sh b/tests/fastcgi-06.sh
new file mode 100755
index 00000000..bc54d924
--- /dev/null
+++ b/tests/fastcgi-06.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "Redirect in PHP"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+
+cat > $TMPFILE <<EOF
+PHP_SELF + PATH_INFO
+GET /phpself.php/foo HTTP/1.0
+Host: www.example.org
+Conntection: close
+
+Status: 200
+Content: /phpself.php
+EOF
+
+run_test
diff --git a/tests/fastcgi-07.sh b/tests/fastcgi-07.sh
new file mode 100755
index 00000000..ef148d24
--- /dev/null
+++ b/tests/fastcgi-07.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "Redirect in PHP"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+
+cat > $TMPFILE <<EOF
+SERVER_NAME + known host, simplevhost
+GET /phphost.php HTTP/1.0
+Host: www.example.org
+Conntection: close
+
+Status: 200
+Content: www.example.org
+EOF
+
+run_test
diff --git a/tests/fastcgi-08.sh b/tests/fastcgi-08.sh
new file mode 100755
index 00000000..b96b1120
--- /dev/null
+++ b/tests/fastcgi-08.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "Redirect in PHP"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+
+cat > $TMPFILE <<EOF
+SERVER_NAME + unknown host, default
+GET /phphost.php HTTP/1.0
+Host: xxx.example.org
+Conntection: close
+
+Status: 200
+Content: www.example.org
+EOF
+
+run_test
diff --git a/tests/fastcgi-09.sh b/tests/fastcgi-09.sh
new file mode 100755
index 00000000..401e2c07
--- /dev/null
+++ b/tests/fastcgi-09.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "Redirect in PHP"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+
+cat > $TMPFILE <<EOF
+SERVER_NAME + \$HTTP["Host"], default
+GET /phphost.php HTTP/1.0
+Host: vvv.example.org
+Conntection: close
+
+Status: 200
+Content: www.example.org
+EOF
+
+run_test
diff --git a/tests/fastcgi-10.conf b/tests/fastcgi-10.conf
new file mode 100644
index 00000000..f2b39db3
--- /dev/null
+++ b/tests/fastcgi-10.conf
@@ -0,0 +1,156 @@
+server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+server.pid-file = "/tmp/lighttpd/lighttpd.pid"
+
+## bind to port (default: 80)
+server.port = 2048
+
+# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c"
+
+## bind to localhost (default: all interfaces)
+server.bind = "localhost"
+server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log"
+server.name = "www.example.org"
+server.tag = "Apache 1.3.29"
+
+##
+## Format: <errorfile-prefix><status>.html
+## -> ..../status-404.html for 'File not found'
+#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-"
+
+server.dir-listing = "enable"
+
+#server.event-handler = "linux-sysepoll"
+#server.event-handler = "linux-rtsig"
+
+#server.modules.path = ""
+server.modules = (
+ "mod_rewrite",
+ "mod_access",
+ "mod_auth",
+# "mod_httptls",
+ "mod_status",
+ "mod_expire",
+# "mod_simple_vhost",
+ "mod_redirect",
+# "mod_evhost",
+# "mod_localizer",
+ "mod_fastcgi",
+ "mod_cgi",
+ "mod_compress",
+ "mod_accesslog" )
+
+server.indexfiles = ( "index.php", "index.html",
+ "index.htm", "default.htm" )
+
+#,-- only root can use these options
+#|
+#|# chroot() to directory (default: no chroot() )
+#| server.chroot /
+#|# change uid to <uid> (default: don't care)
+#| server.userid wwwrun
+#|# change uid to <uid> (default: don't care)
+#| server.groupid wwwrun
+#|
+#`--
+
+
+######################## MODULE CONFIG ############################
+
+
+accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log"
+
+mimetype.assign = ( ".png" => "image/png",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".gif" => "image/gif",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".pdf" => "application/pdf",
+ ".swf" => "application/x-shockwave-flash",
+ ".spl" => "application/futuresplash",
+ ".txt" => "text/plain",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".gz" => "application/x-gzip",
+ ".c" => "text/plain",
+ ".conf" => "text/plain" )
+
+compress.cache-dir = "/tmp/lighttpd/cache/compress/"
+compress.filetype = ("text/plain", "text/html")
+
+fastcgi.debug = 0
+fastcgi.server = ( ".php" => (
+ "grisu" => (
+ "host" => "192.168.0.2",
+ "port" => 1026
+ )
+# "ulf" => (
+# "host" => "192.168.2.41",
+# "docroot" => "/home/jan/servers/",
+# "port" => 1026
+# )
+ )
+ )
+
+
+cgi.assign = ( ".pl" => "/usr/bin/perl",
+ ".cgi" => "/usr/bin/perl",
+ ".py" => "/usr/bin/python" )
+
+
+
+ssl.engine = "disable"
+ssl.pemfile = "server.pem"
+
+auth.backend = "plain"
+auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user"
+auth.backend.plain.groupfile = "lighttpd.group"
+
+auth.backend.ldap.hostname = "localhost"
+auth.backend.ldap.base-dn = "dc=my-domain,dc=com"
+auth.backend.ldap.filter = "(uid=$)"
+
+auth.require = ( "/server-status" =>
+ (
+ "method" => "digest",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ ),
+ "/auth.php" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "user=jan"
+ ),
+ "/server-config" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ )
+ )
+
+url.access-deny = ( "~", ".inc")
+
+url.redirect = ( "^/redirect/$" => "http://localhost:2048/" )
+
+expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes")
+
+#cache.cache-dir = "/home/weigon/wwwroot/cache/"
+
+#### status module
+status.status-url = "/server-status"
+status.config-url = "/server-config"
+
+$HTTP["host"] == "vvv.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+}
+
+$HTTP["host"] == "zzz.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+ server.name = "zzz.example.org"
+}
+
diff --git a/tests/fastcgi-10.sh b/tests/fastcgi-10.sh
new file mode 100755
index 00000000..bd0e8dd0
--- /dev/null
+++ b/tests/fastcgi-10.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "Redirect in PHP"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+
+cat > $TMPFILE <<EOF
+SERVER_NAME + \$HTTP, servername
+GET /phphost.php HTTP/1.0
+Host: zzz.example.org
+Conntection: close
+
+Status: 200
+Content: zzz.example.org
+EOF
+
+run_test
diff --git a/tests/fastcgi-11.conf b/tests/fastcgi-11.conf
new file mode 100644
index 00000000..2b7f73ff
--- /dev/null
+++ b/tests/fastcgi-11.conf
@@ -0,0 +1,164 @@
+server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+server.pid-file = "/tmp/lighttpd/lighttpd.pid"
+
+debug.log-request-header = "enable"
+debug.log-response-header = "enable"
+debug.log-request-handling = "enable"
+
+## bind to port (default: 80)
+server.port = 2048
+
+# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c"
+
+## bind to localhost (default: all interfaces)
+server.bind = "localhost"
+server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log"
+server.name = "www.example.org"
+server.tag = "Apache 1.3.29"
+
+##
+## Format: <errorfile-prefix><status>.html
+## -> ..../status-404.html for 'File not found'
+#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-"
+
+server.dir-listing = "enable"
+
+#server.event-handler = "linux-sysepoll"
+#server.event-handler = "linux-rtsig"
+
+#server.modules.path = ""
+server.modules = (
+ "mod_rewrite",
+ "mod_access",
+ "mod_auth",
+# "mod_httptls",
+ "mod_status",
+ "mod_expire",
+# "mod_simple_vhost",
+ "mod_redirect",
+# "mod_evhost",
+# "mod_localizer",
+ "mod_fastcgi",
+ "mod_cgi",
+ "mod_compress",
+ "mod_accesslog" )
+
+server.indexfiles = ( "index.php", "index.html",
+ "index.htm", "default.htm" )
+
+#,-- only root can use these options
+#|
+#|# chroot() to directory (default: no chroot() )
+#| server.chroot /
+#|# change uid to <uid> (default: don't care)
+#| server.userid wwwrun
+#|# change uid to <uid> (default: don't care)
+#| server.groupid wwwrun
+#|
+#`--
+
+
+######################## MODULE CONFIG ############################
+
+
+accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log"
+
+mimetype.assign = ( ".png" => "image/png",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".gif" => "image/gif",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".pdf" => "application/pdf",
+ ".swf" => "application/x-shockwave-flash",
+ ".spl" => "application/futuresplash",
+ ".txt" => "text/plain",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".gz" => "application/x-gzip",
+ ".c" => "text/plain",
+ ".conf" => "text/plain" )
+
+compress.cache-dir = "/tmp/lighttpd/cache/compress/"
+compress.filetype = ("text/plain", "text/html")
+
+fastcgi.debug = 0
+fastcgi.server = ( "/" => (
+ "grisu" => (
+ "host" => "192.168.0.2",
+ "port" => 1027,
+ "bin-path" => "./fcgi-auth",
+ "mode" => "authorizer",
+ "docroot" => "/tmp/lighttpd/servers/www.example.org/pages/",
+
+ )
+# "ulf" => (
+# "host" => "192.168.2.41",
+# "docroot" => "/home/jan/servers/",
+# "port" => 1026
+# )
+ )
+ )
+
+
+cgi.assign = ( ".pl" => "/usr/bin/perl",
+ ".cgi" => "/usr/bin/perl",
+ ".py" => "/usr/bin/python" )
+
+
+
+ssl.engine = "disable"
+ssl.pemfile = "server.pem"
+
+auth.backend = "plain"
+auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user"
+auth.backend.plain.groupfile = "lighttpd.group"
+
+auth.backend.ldap.hostname = "localhost"
+auth.backend.ldap.base-dn = "dc=my-domain,dc=com"
+auth.backend.ldap.filter = "(uid=$)"
+
+auth.require = ( "/server-status" =>
+ (
+ "method" => "digest",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ ),
+ "/auth.php" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "user=jan"
+ ),
+ "/server-config" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ )
+ )
+
+url.access-deny = ( "~", ".inc")
+
+url.redirect = ( "^/redirect/$" => "http://localhost:2048/" )
+
+expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes")
+
+#cache.cache-dir = "/home/weigon/wwwroot/cache/"
+
+#### status module
+status.status-url = "/server-status"
+status.config-url = "/server-config"
+
+$HTTP["host"] == "vvv.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+}
+
+$HTTP["host"] == "zzz.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+ server.name = "zzz.example.org"
+}
+
diff --git a/tests/fastcgi-11.sh b/tests/fastcgi-11.sh
new file mode 100755
index 00000000..2042c1d3
--- /dev/null
+++ b/tests/fastcgi-11.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+
+cat > $TMPFILE <<EOF
+FastCGI-Auth - ok
+GET /index.html?ok HTTP/1.0
+Host: www.example.org
+Conntection: close
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/fastcgi-12.conf b/tests/fastcgi-12.conf
new file mode 100644
index 00000000..2b7f73ff
--- /dev/null
+++ b/tests/fastcgi-12.conf
@@ -0,0 +1,164 @@
+server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+server.pid-file = "/tmp/lighttpd/lighttpd.pid"
+
+debug.log-request-header = "enable"
+debug.log-response-header = "enable"
+debug.log-request-handling = "enable"
+
+## bind to port (default: 80)
+server.port = 2048
+
+# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c"
+
+## bind to localhost (default: all interfaces)
+server.bind = "localhost"
+server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log"
+server.name = "www.example.org"
+server.tag = "Apache 1.3.29"
+
+##
+## Format: <errorfile-prefix><status>.html
+## -> ..../status-404.html for 'File not found'
+#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-"
+
+server.dir-listing = "enable"
+
+#server.event-handler = "linux-sysepoll"
+#server.event-handler = "linux-rtsig"
+
+#server.modules.path = ""
+server.modules = (
+ "mod_rewrite",
+ "mod_access",
+ "mod_auth",
+# "mod_httptls",
+ "mod_status",
+ "mod_expire",
+# "mod_simple_vhost",
+ "mod_redirect",
+# "mod_evhost",
+# "mod_localizer",
+ "mod_fastcgi",
+ "mod_cgi",
+ "mod_compress",
+ "mod_accesslog" )
+
+server.indexfiles = ( "index.php", "index.html",
+ "index.htm", "default.htm" )
+
+#,-- only root can use these options
+#|
+#|# chroot() to directory (default: no chroot() )
+#| server.chroot /
+#|# change uid to <uid> (default: don't care)
+#| server.userid wwwrun
+#|# change uid to <uid> (default: don't care)
+#| server.groupid wwwrun
+#|
+#`--
+
+
+######################## MODULE CONFIG ############################
+
+
+accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log"
+
+mimetype.assign = ( ".png" => "image/png",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".gif" => "image/gif",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".pdf" => "application/pdf",
+ ".swf" => "application/x-shockwave-flash",
+ ".spl" => "application/futuresplash",
+ ".txt" => "text/plain",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".gz" => "application/x-gzip",
+ ".c" => "text/plain",
+ ".conf" => "text/plain" )
+
+compress.cache-dir = "/tmp/lighttpd/cache/compress/"
+compress.filetype = ("text/plain", "text/html")
+
+fastcgi.debug = 0
+fastcgi.server = ( "/" => (
+ "grisu" => (
+ "host" => "192.168.0.2",
+ "port" => 1027,
+ "bin-path" => "./fcgi-auth",
+ "mode" => "authorizer",
+ "docroot" => "/tmp/lighttpd/servers/www.example.org/pages/",
+
+ )
+# "ulf" => (
+# "host" => "192.168.2.41",
+# "docroot" => "/home/jan/servers/",
+# "port" => 1026
+# )
+ )
+ )
+
+
+cgi.assign = ( ".pl" => "/usr/bin/perl",
+ ".cgi" => "/usr/bin/perl",
+ ".py" => "/usr/bin/python" )
+
+
+
+ssl.engine = "disable"
+ssl.pemfile = "server.pem"
+
+auth.backend = "plain"
+auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user"
+auth.backend.plain.groupfile = "lighttpd.group"
+
+auth.backend.ldap.hostname = "localhost"
+auth.backend.ldap.base-dn = "dc=my-domain,dc=com"
+auth.backend.ldap.filter = "(uid=$)"
+
+auth.require = ( "/server-status" =>
+ (
+ "method" => "digest",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ ),
+ "/auth.php" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "user=jan"
+ ),
+ "/server-config" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ )
+ )
+
+url.access-deny = ( "~", ".inc")
+
+url.redirect = ( "^/redirect/$" => "http://localhost:2048/" )
+
+expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes")
+
+#cache.cache-dir = "/home/weigon/wwwroot/cache/"
+
+#### status module
+status.status-url = "/server-status"
+status.config-url = "/server-config"
+
+$HTTP["host"] == "vvv.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+}
+
+$HTTP["host"] == "zzz.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+ server.name = "zzz.example.org"
+}
+
diff --git a/tests/fastcgi-12.sh b/tests/fastcgi-12.sh
new file mode 100755
index 00000000..0eed8cd3
--- /dev/null
+++ b/tests/fastcgi-12.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+
+cat > $TMPFILE <<EOF
+FastCGI-Authorizer - 403
+GET /index.html?fail HTTP/1.0
+Host: www.example.org
+Conntection: close
+
+Status: 403
+EOF
+
+run_test
diff --git a/tests/fastcgi-13.conf b/tests/fastcgi-13.conf
new file mode 100644
index 00000000..cb12269a
--- /dev/null
+++ b/tests/fastcgi-13.conf
@@ -0,0 +1,156 @@
+server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+server.pid-file = "/tmp/lighttpd/lighttpd.pid"
+
+debug.log-request-header = "enable"
+debug.log-response-header = "enable"
+debug.log-request-handling = "enable"
+
+## bind to port (default: 80)
+server.port = 2048
+
+# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c"
+
+## bind to localhost (default: all interfaces)
+server.bind = "localhost"
+server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log"
+server.name = "www.example.org"
+server.tag = "Apache 1.3.29"
+
+##
+## Format: <errorfile-prefix><status>.html
+## -> ..../status-404.html for 'File not found'
+#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-"
+
+server.dir-listing = "enable"
+
+#server.event-handler = "linux-sysepoll"
+#server.event-handler = "linux-rtsig"
+
+#server.modules.path = ""
+server.modules = (
+ "mod_rewrite",
+ "mod_access",
+ "mod_auth",
+# "mod_httptls",
+ "mod_status",
+ "mod_expire",
+# "mod_simple_vhost",
+ "mod_redirect",
+# "mod_evhost",
+# "mod_localizer",
+ "mod_fastcgi",
+ "mod_cgi",
+ "mod_compress",
+ "mod_accesslog" )
+
+server.indexfiles = ( "index.php", "index.html",
+ "index.htm", "default.htm" )
+
+#,-- only root can use these options
+#|
+#|# chroot() to directory (default: no chroot() )
+#| server.chroot /
+#|# change uid to <uid> (default: don't care)
+#| server.userid wwwrun
+#|# change uid to <uid> (default: don't care)
+#| server.groupid wwwrun
+#|
+#`--
+
+
+######################## MODULE CONFIG ############################
+
+
+accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log"
+
+mimetype.assign = ( ".png" => "image/png",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".gif" => "image/gif",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".pdf" => "application/pdf",
+ ".swf" => "application/x-shockwave-flash",
+ ".spl" => "application/futuresplash",
+ ".txt" => "text/plain",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".gz" => "application/x-gzip",
+ ".c" => "text/plain",
+ ".conf" => "text/plain" )
+
+compress.cache-dir = "/tmp/lighttpd/cache/compress/"
+compress.filetype = ("text/plain", "text/html")
+
+fastcgi.debug = 0
+fastcgi.server = ( ".php" => (
+ "grisu" => (
+ "host" => "127.0.0.1",
+ "port" => 1048,
+ "bin-path" => "/home/weigon/Documents/php-4.3.10/sapi/cgi/php -c /usr/local/lib/php.ini",
+ )
+ )
+ )
+
+
+cgi.assign = ( ".pl" => "/usr/bin/perl",
+ ".cgi" => "/usr/bin/perl",
+ ".py" => "/usr/bin/python" )
+
+
+
+ssl.engine = "disable"
+ssl.pemfile = "server.pem"
+
+auth.backend = "plain"
+auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user"
+auth.backend.plain.groupfile = "lighttpd.group"
+
+auth.backend.ldap.hostname = "localhost"
+auth.backend.ldap.base-dn = "dc=my-domain,dc=com"
+auth.backend.ldap.filter = "(uid=$)"
+
+auth.require = ( "/server-status" =>
+ (
+ "method" => "digest",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ ),
+ "/auth.php" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "user=jan"
+ ),
+ "/server-config" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ )
+ )
+
+url.access-deny = ( "~", ".inc")
+
+url.redirect = ( "^/redirect/$" => "http://localhost:2048/" )
+
+expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes")
+
+#cache.cache-dir = "/home/weigon/wwwroot/cache/"
+
+#### status module
+status.status-url = "/server-status"
+status.config-url = "/server-config"
+
+$HTTP["host"] == "vvv.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+}
+
+$HTTP["host"] == "zzz.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+ server.name = "zzz.example.org"
+}
+
diff --git a/tests/fastcgi-13.sh b/tests/fastcgi-13.sh
new file mode 100755
index 00000000..75cbac92
--- /dev/null
+++ b/tests/fastcgi-13.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+if test -e /home/weigon/Documents/php-4.3.10/sapi/cgi/php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "FastCGI PHPinfo"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+
+cat > $TMPFILE <<EOF
+FastCGI + local-spawning
+GET /indexfile/index.php HTTP/1.0
+Host: www.example.org
+Conntection: close
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/fcgi-auth.c b/tests/fcgi-auth.c
new file mode 100644
index 00000000..ddecddc8
--- /dev/null
+++ b/tests/fcgi-auth.c
@@ -0,0 +1,26 @@
+#include <fcgi_stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+int main () {
+ char* p;
+
+ while (FCGI_Accept() >= 0) {
+ /* wait for fastcgi authorizer request */
+
+ printf("Content-type: text/html\r\n");
+
+ if (((p = getenv("QUERY_STRING")) == NULL) ||
+ strcmp(p, "ok") != 0) {
+ printf("Status: 403 Forbidden\r\n\r\n");
+ } else {
+ printf("\r\n");
+ /* default Status is 200 - allow access */
+ }
+
+ printf("foobar\r\n");
+ }
+
+ return 0;
+}
diff --git a/tests/head-01.sh b/tests/head-01.sh
new file mode 100755
index 00000000..510fb350
--- /dev/null
+++ b/tests/head-01.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+HEAD request should have no content
+HEAD /index.html HTTP/1.0
+
+Status: 200
+EOF
+
+run_test_script
+
+if test x$exitcode = x0; then
+ if test `cat $NAME.out | sed '1,/^.$/d' | wc -l` = 0; then
+ a=a
+ else
+ exitcode=-1
+ fi
+fi
+
+run_test_exit
diff --git a/tests/host-01.sh b/tests/host-01.sh
new file mode 100755
index 00000000..95b36176
--- /dev/null
+++ b/tests/host-01.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host set to weigon.dyndns.org
+GET / HTTP/1.0
+Host: weigon.dyndns.org
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/host-02.sh b/tests/host-02.sh
new file mode 100755
index 00000000..22ed7f7c
--- /dev/null
+++ b/tests/host-02.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host not set
+GET / HTTP/1.0
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/host-03.sh b/tests/host-03.sh
new file mode 100755
index 00000000..6ac03c32
--- /dev/null
+++ b/tests/host-03.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host not set
+GET / HTTP/1.0
+Host: grisu.home.kneschke.de
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/host-04.sh b/tests/host-04.sh
new file mode 100755
index 00000000..b8936c34
--- /dev/null
+++ b/tests/host-04.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host not set
+GET / HTTP/1.0
+Host: ../123.org/
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/host-05.sh b/tests/host-05.sh
new file mode 100755
index 00000000..d0999b8d
--- /dev/null
+++ b/tests/host-05.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+IPv6
+GET / HTTP/1.0
+Host: [::1]:80
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/http11-01.sh b/tests/http11-01.sh
new file mode 100755
index 00000000..592c6ba1
--- /dev/null
+++ b/tests/http11-01.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+valid HTTP/1.1 request
+GET / HTTP/1.1
+Host: weigon.dyndns.org
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/http11-02.sh b/tests/http11-02.sh
new file mode 100755
index 00000000..025fd2fe
--- /dev/null
+++ b/tests/http11-02.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+valid HTTP/1.1 request + req: Date
+GET / HTTP/1.1
+Host: weigon.dyndns.org
+
+Status: 200
+MUST: Date
+EOF
+
+run_test
diff --git a/tests/http11-03.sh b/tests/http11-03.sh
new file mode 100755
index 00000000..4162566e
--- /dev/null
+++ b/tests/http11-03.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+HTTP/1.0 + absoluteURI
+GET http://www.example.org/ HTTP/1.0
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/large-header-01.sh b/tests/large-header-01.sh
new file mode 100755
index 00000000..bd7744b5
--- /dev/null
+++ b/tests/large-header-01.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+large request header
+GET / HTTP/1.0
+Hsgfsdjf: asdfhdf
+hdhd: shdfhfdasd
+hfhr: jfghsdfg
+jfuuehdmn: sfdgjfdg
+jvcbzufdg: sgfdfg
+hrnvcnd: jfjdfg
+jfusfdngmd: gfjgfdusdfg
+nfj: jgfdjdfg
+jfue: jfdfdg
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/leak-01.sh b/tests/leak-01.sh
new file mode 100755
index 00000000..a7cad3d7
--- /dev/null
+++ b/tests/leak-01.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: leading dot
+GET / HTTP/1.0
+Host: .jsdh.sfdg.sdfg.
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/leak-02.sh b/tests/leak-02.sh
new file mode 100755
index 00000000..08142ca7
--- /dev/null
+++ b/tests/leak-02.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: good name
+GET / HTTP/1.0
+Host: jsdh.sfdg.sdfg
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/leak-03.sh b/tests/leak-03.sh
new file mode 100755
index 00000000..a54833f5
--- /dev/null
+++ b/tests/leak-03.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: two dots
+GET / HTTP/1.0
+Host: .jsdh..sfdg.sdfg
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/leak-04.sh b/tests/leak-04.sh
new file mode 100755
index 00000000..169ecb42
--- /dev/null
+++ b/tests/leak-04.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: trailing dot
+GET / HTTP/1.0
+Host: .jsdh.sfdg.sdfg.:aasd
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/leak-05.sh b/tests/leak-05.sh
new file mode 100755
index 00000000..22dd7091
--- /dev/null
+++ b/tests/leak-05.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: missing host
+GET / HTTP/1.0
+Host: :.jsdh.sfdg.sdfg.
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/leak-06.sh b/tests/leak-06.sh
new file mode 100755
index 00000000..5d9542c7
--- /dev/null
+++ b/tests/leak-06.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: broken host/port
+GET / HTTP/1.0
+Host: .jsdh.sfdg.:sdfg.
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/leak-07.sh b/tests/leak-07.sh
new file mode 100755
index 00000000..a4f97eea
--- /dev/null
+++ b/tests/leak-07.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: broken name/port
+GET / HTTP/1.0
+Host: ..jsdh..sfdg..:sdfg.
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/leak-08.sh b/tests/leak-08.sh
new file mode 100755
index 00000000..aa6d4637
--- /dev/null
+++ b/tests/leak-08.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: good name
+GET / HTTP/1.0
+Host: abc.de
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/leak-09.sh b/tests/leak-09.sh
new file mode 100755
index 00000000..5afd85fd
--- /dev/null
+++ b/tests/leak-09.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: good name
+GET / HTTP/1.0
+Host: a-b.de
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/leak-10.sh b/tests/leak-10.sh
new file mode 100755
index 00000000..694e99b9
--- /dev/null
+++ b/tests/leak-10.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: leading dash
+GET / HTTP/1.0
+Host: -ab.de
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/leak-11.sh b/tests/leak-11.sh
new file mode 100755
index 00000000..feef7ca3
--- /dev/null
+++ b/tests/leak-11.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: good name
+GET / HTTP/1.0
+Host: a-b.de124
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/leak-12.sh b/tests/leak-12.sh
new file mode 100755
index 00000000..df5012d9
--- /dev/null
+++ b/tests/leak-12.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: broken name
+GET / HTTP/1.0
+Host: .
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/leak-13.sh b/tests/leak-13.sh
new file mode 100755
index 00000000..65aa1560
--- /dev/null
+++ b/tests/leak-13.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: broken port
+GET / HTTP/1.0
+Host: abc.de:avs
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/leak-14.sh b/tests/leak-14.sh
new file mode 100755
index 00000000..7451e4d6
--- /dev/null
+++ b/tests/leak-14.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: good name
+GET / HTTP/1.0
+Host: abc.de:1234
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/leak-15.sh b/tests/leak-15.sh
new file mode 100755
index 00000000..f2691639
--- /dev/null
+++ b/tests/leak-15.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: good IP
+GET / HTTP/1.0
+Host: 192.168.2.10:1234
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/leak-16.sh b/tests/leak-16.sh
new file mode 100755
index 00000000..f6d589c8
--- /dev/null
+++ b/tests/leak-16.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: broken name/ip
+GET / HTTP/1.0
+Host: a192.168.2.10:1234
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/leak-17.sh b/tests/leak-17.sh
new file mode 100755
index 00000000..7884fb42
--- /dev/null
+++ b/tests/leak-17.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Host: broken IP
+GET / HTTP/1.0
+Host: 192.168.2:1234
+
+Status: 400
+EOF
+
+run_test
diff --git a/tests/lighttpd.conf b/tests/lighttpd.conf
new file mode 100644
index 00000000..4a91e72e
--- /dev/null
+++ b/tests/lighttpd.conf
@@ -0,0 +1,166 @@
+server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+server.pid-file = "/tmp/lighttpd/lighttpd.pid"
+
+## bind to port (default: 80)
+server.port = 2048
+
+# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c"
+
+## bind to localhost (default: all interfaces)
+server.bind = "localhost"
+server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log"
+server.name = "www.example.org"
+server.tag = "Apache 1.3.29"
+
+fastcgi.debug = 1
+
+##
+## Format: <errorfile-prefix><status>.html
+## -> ..../status-404.html for 'File not found'
+#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-"
+
+server.dir-listing = "enable"
+
+#server.event-handler = "linux-sysepoll"
+#server.event-handler = "linux-rtsig"
+
+#server.modules.path = ""
+server.modules = (
+ "mod_rewrite",
+ "mod_setenv",
+ "mod_access",
+ "mod_auth",
+# "mod_httptls",
+ "mod_status",
+ "mod_expire",
+ "mod_simple_vhost",
+ "mod_redirect",
+# "mod_evhost",
+# "mod_localizer",
+ "mod_fastcgi",
+ "mod_cgi",
+ "mod_compress",
+ "mod_userdir",
+ "mod_accesslog" )
+
+server.indexfiles = ( "index.php", "index.html",
+ "index.htm", "default.htm" )
+
+#,-- only root can use these options
+#|
+#|# chroot() to directory (default: no chroot() )
+#| server.chroot /
+#|# change uid to <uid> (default: don't care)
+#| server.userid wwwrun
+#|# change uid to <uid> (default: don't care)
+#| server.groupid wwwrun
+#|
+#`--
+
+
+######################## MODULE CONFIG ############################
+
+
+accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log"
+
+mimetype.assign = ( ".png" => "image/png",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".gif" => "image/gif",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".pdf" => "application/pdf",
+ ".swf" => "application/x-shockwave-flash",
+ ".spl" => "application/futuresplash",
+ ".txt" => "text/plain",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".gz" => "application/x-gzip",
+ ".c" => "text/plain",
+ ".conf" => "text/plain" )
+
+compress.cache-dir = "/tmp/lighttpd/cache/compress/"
+compress.filetype = ("text/plain", "text/html")
+
+setenv.add-environment = ( "TRAC_ENV" => "foo")
+setenv.add-request-header = ( "FOO" => "foo")
+setenv.add-response-header = ( "BAR" => "foo")
+
+fastcgi.debug = 0
+fastcgi.server = ( ".php" => (
+ "grisu" => (
+ "host" => "192.168.0.2",
+ "port" => 1026,
+# "mode" => "authorizer",
+# "docroot" => "/tmp/lighttpd/servers/www.example.org/pages/",
+ )
+ )
+ )
+
+
+cgi.assign = ( ".pl" => "/usr/bin/perl",
+ ".cgi" => "/usr/bin/perl",
+ ".py" => "/usr/bin/python" )
+
+userdir.include-user = ( "weigon" )
+userdir.path = "/"
+
+ssl.engine = "disable"
+ssl.pemfile = "server.pem"
+
+auth.backend = "plain"
+auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user"
+auth.backend.plain.groupfile = "lighttpd.group"
+
+auth.backend.ldap.hostname = "localhost"
+auth.backend.ldap.base-dn = "dc=my-domain,dc=com"
+auth.backend.ldap.filter = "(uid=$)"
+
+auth.require = ( "/server-status" =>
+ (
+ "method" => "digest",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ ),
+ "/auth.php" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "host=192.168.2.10")
+ "require" => "user=jan"
+ ),
+ "/server-config" =>
+ (
+ "method" => "basic",
+ "realm" => "download archiv",
+# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10")
+ "require" => "group=www|user=jan|host=192.168.2.10"
+ )
+ )
+
+url.access-deny = ( "~", ".inc")
+
+url.redirect = ( "^/redirect/$" => "http://localhost:2048/" )
+
+expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes")
+
+#cache.cache-dir = "/home/weigon/wwwroot/cache/"
+
+#### status module
+status.status-url = "/server-status"
+status.config-url = "/server-config"
+
+simple-vhost.document-root = "pages"
+simple-vhost.server-root = "/tmp/lighttpd/servers/"
+simple-vhost.default-host = "www.example.org"
+
+$HTTP["host"] == "vvv.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+}
+
+$HTTP["host"] == "zzz.example.org" {
+ server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/"
+ server.name = "zzz.example.org"
+}
+
diff --git a/tests/lighttpd.user b/tests/lighttpd.user
new file mode 100644
index 00000000..020aedc6
--- /dev/null
+++ b/tests/lighttpd.user
@@ -0,0 +1 @@
+jan:jan
diff --git a/tests/missing-01.sh b/tests/missing-01.sh
new file mode 100755
index 00000000..60518800
--- /dev/null
+++ b/tests/missing-01.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+missing file
+GET /cjhdhfdjgfdg HTTP/1.0
+
+Status: 404
+EOF
+
+run_test
diff --git a/tests/missing-02.sh b/tests/missing-02.sh
new file mode 100755
index 00000000..b3c61162
--- /dev/null
+++ b/tests/missing-02.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+missing file + get-vars
+GET /cjhdhfdjgfdg?jdfjh=dnfdh HTTP/1.0
+
+Status: 404
+EOF
+
+run_test
diff --git a/tests/pathinfo-01.sh b/tests/pathinfo-01.sh
new file mode 100755
index 00000000..17b7a50a
--- /dev/null
+++ b/tests/pathinfo-01.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "PathInfo"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+PathInfo
+GET /cgi.php/abc HTTP/1.0
+
+Status: 200
+EOF
+
+run_test
+
diff --git a/tests/pathinfo-02.sh b/tests/pathinfo-02.sh
new file mode 100755
index 00000000..08d7dde6
--- /dev/null
+++ b/tests/pathinfo-02.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+if pidof php > /dev/null; then
+ echo -n
+else
+ printf "%-40s" "PathInfo"
+ exit 77
+fi
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+PathInfo on a directory
+GET /www/abc/def HTTP/1.0
+
+Status: 404
+EOF
+
+run_test
+
diff --git a/tests/post-01.sh b/tests/post-01.sh
new file mode 100755
index 00000000..5f060091
--- /dev/null
+++ b/tests/post-01.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+POST without Content-Length
+POST / HTTP/1.0
+
+Status: 411
+EOF
+
+run_test
diff --git a/tests/post-02.sh b/tests/post-02.sh
new file mode 100755
index 00000000..3b2889a2
--- /dev/null
+++ b/tests/post-02.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+POST without Content-Length
+POST / HTTP/1.0
+Content-type: application/x-www-form-urlencoded
+Content-length: 0
+
+Status: 200
+EOF
+
+run_test
diff --git a/tests/prepare.sh b/tests/prepare.sh
new file mode 100755
index 00000000..faa7e220
--- /dev/null
+++ b/tests/prepare.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+tmpdir=/tmp/lighttpd
+
+if test x$srcdir = x; then
+ srcdir=.
+fi
+
+# create test-framework
+rm -rf $tmpdir
+mkdir -p $tmpdir/servers/www.example.org/pages/
+mkdir -p $tmpdir/servers/www.example.org/pages/dummydir/
+mkdir -p $tmpdir/servers/www.example.org/pages/go/
+mkdir -p $tmpdir/servers/www.example.org/pages/indexfile/
+mkdir -p $tmpdir/servers/123.example.org/pages/
+mkdir -p $tmpdir/logs/
+mkdir -p $tmpdir/cache/
+mkdir -p $tmpdir/cache/compress/
+
+# copy everything into the right places
+cp $srcdir/docroot/www/*.html \
+ $srcdir/docroot/www/*.php \
+ $srcdir/docroot/www/*.pl \
+ $srcdir/docroot/www/*.txt $tmpdir/servers/www.example.org/pages/
+cp $srcdir/docroot/www/go/*.php $tmpdir/servers/www.example.org/pages/go/
+cp $srcdir/docroot/www/indexfile/*.php $tmpdir/servers/www.example.org/pages/indexfile/
+cp $srcdir/docroot/123/*.txt \
+ $srcdir/docroot/123/*.html \
+ $srcdir/docroot/123/*.php \
+ $srcdir/docroot/123/*.bla $tmpdir/servers/123.example.org/pages/
+cp $srcdir/lighttpd.user $tmpdir/
+
+printf "%-40s" "preparing infrastructure"
+
+exit 0
diff --git a/tests/redirect-01.sh b/tests/redirect-01.sh
new file mode 100755
index 00000000..05f4c835
--- /dev/null
+++ b/tests/redirect-01.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+Internal Redirect
+GET /dummydir HTTP/1.0
+
+Status: 301
+Location: http://localhost:2048/dummydir/
+EOF
+
+run_test
diff --git a/tests/redirect-02.sh b/tests/redirect-02.sh
new file mode 100755
index 00000000..67b5c31b
--- /dev/null
+++ b/tests/redirect-02.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+redirect module test
+GET /redirect/ HTTP/1.0
+
+Status: 301
+Location: http://localhost:2048/
+EOF
+
+run_test
diff --git a/tests/redirect-03.sh b/tests/redirect-03.sh
new file mode 100755
index 00000000..96b0b2ea
--- /dev/null
+++ b/tests/redirect-03.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test x$srcdir = x && srcdir=.
+
+. $srcdir/testbase.sh
+
+prepare_test
+
+cat > $TMPFILE <<EOF
+redirect module test
+GET /dummydir?foo HTTP/1.0
+
+Status: 301
+Location: http://localhost:2048/dummydir/?foo
+EOF
+
+run_test
diff --git a/tests/testbase.sh b/tests/testbase.sh
new file mode 100644
index 00000000..b4fcfca7
--- /dev/null
+++ b/tests/testbase.sh
@@ -0,0 +1,63 @@
+exitcode=0
+lighttpdpid=0
+prepare_test () {
+ test -x $srcdir/conformance.pl || exit 77
+
+ NAME=`basename $0 | sed s/\.sh$//`
+ if which mktemp > /dev/null; then
+ TMPFILE=`mktemp /tmp/$NAME.XXXXXX` || exit 1;
+ else
+ TMPFILE=/tmp/$NAME.XXXXXX
+ fi
+
+ if test x$top_builddir != x; then
+ # not in stand-alone mode
+ if test -f /tmp/lighttpd/lighttpd.pid; then
+ kill `cat /tmp/lighttpd/lighttpd.pid`
+ rm -f /tmp/lighttpd/lighttpd.pid
+ fi
+
+ # start webserver
+ CONF=`echo $0 | sed s/\.sh$/.conf/`
+ #VALGRIND='valgrind --tool=memcheck --logfile=lighttpd'
+ VALGRIND=
+ if test -e $CONF; then
+ $VALGRIND $top_builddir/src/lighttpd -f $CONF
+ else
+ $VALGRIND $top_builddir/src/lighttpd -f $srcdir/lighttpd.conf
+ fi
+ test x$? = x0 || exit 1
+
+ # ps ax > $NAME.psax
+ fi
+}
+
+run_test_script () {
+ if test x$top_builddir = x; then
+ cat $TMPFILE | $srcdir/conformance.pl standalone > $NAME.out
+ else
+ cat $TMPFILE | $srcdir/conformance.pl > $NAME.out
+ fi
+
+ exitcode=$?
+}
+
+run_test_exit () {
+ if test x$top_builddir != x; then
+ # stop webserver
+ kill `cat /tmp/lighttpd/lighttpd.pid` || exit 1
+ rm -f /tmp/lighttpd/lighttpd.pid
+ fi
+
+ if test x$exitcode = x0; then
+ rm $NAME.out;
+ fi;
+ rm -f $TMPFILE
+
+ exit $exitcode;
+}
+
+run_test () {
+ run_test_script
+ run_test_exit
+}
diff --git a/tests/wrapper.sh b/tests/wrapper.sh
new file mode 100755
index 00000000..5b8e6064
--- /dev/null
+++ b/tests/wrapper.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+## get some parameters from the makefile
+
+export srcdir=$1
+export top_builddir=$2
+
+$3