diff --git a/Makefile.am b/Makefile.am
index 99dd380a3..9f55243c0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -31,6 +31,7 @@ SUBDIRS = \
@ncurses_dir@ \
@media_dir@ \
@jit_dir@ \
+ @httpd_dir@ \
comp \
app \
examples \
diff --git a/app/other/MakeWebSite/.settings b/app/other/MakeWebSite/.settings
index 770e39411..27208e851 100644
--- a/app/other/MakeWebSite/.settings
+++ b/app/other/MakeWebSite/.settings
@@ -39,7 +39,7 @@ SearchString=True
File[1]="gambas.sourceforge.net/menu.html:79.149"
File[2]=".src/MChangeLog.module:18.0"
Active=3
-File[3]=".src/MMain.module:15.31"
+File[3]=".src/MMain.module:16.31"
File[4]="gambas.sourceforge.net/style.css:427.0"
File[5]="gambas.sourceforge.net/style-rtl.css:431.0"
Count=5
diff --git a/app/other/MakeWebSite/.src/MMain.module b/app/other/MakeWebSite/.src/MMain.module
index 33f6c995f..c696e6ac9 100644
--- a/app/other/MakeWebSite/.src/MMain.module
+++ b/app/other/MakeWebSite/.src/MMain.module
@@ -14,7 +14,7 @@ Sub InitVar()
'DIM aDev AS String[] = ["92", "91", "90", "51"]
$cVar["OLD_VERSION"] = "2.24.0"
- $cVar["DEV_VERSION"] = "3.3.0"
+ $cVar["DEV_VERSION"] = "3.3.1"
InitAuthor
diff --git a/app/other/MakeWebSite/gambas.sourceforge.net/home.html b/app/other/MakeWebSite/gambas.sourceforge.net/home.html
index 15e054433..231e2da5e 100644
--- a/app/other/MakeWebSite/gambas.sourceforge.net/home.html
+++ b/app/other/MakeWebSite/gambas.sourceforge.net/home.html
@@ -56,10 +56,10 @@
diff --git a/app/src/gambas3/.project b/app/src/gambas3/.project
index 33905eaf3..1a107b251 100644
--- a/app/src/gambas3/.project
+++ b/app/src/gambas3/.project
@@ -2,6 +2,7 @@
# Compiled with Gambas 3.3.0
Title=Gambas 3
Startup=Project
+Profiling=1
Icon=img/logo/logo-ide.png
Version=3.3.0
VersionFile=1
diff --git a/comp/src/gb.report/.component b/comp/src/gb.report/.component
index dc7a398e6..33f23775d 100644
--- a/comp/src/gb.report/.component
+++ b/comp/src/gb.report/.component
@@ -1,6 +1,6 @@
[Component]
Key=gb.report
-Version=3.2.90
+Version=3.3.0
State=1
Authors=Fabien Bodard
Needs=Form,ImageIO
diff --git a/comp/src/gb.report/.project b/comp/src/gb.report/.project
index cb5992712..a92f50750 100644
--- a/comp/src/gb.report/.project
+++ b/comp/src/gb.report/.project
@@ -3,12 +3,13 @@
Title=Report designer
Startup=Report1
Icon=printer1.png
-Version=3.2.90
+Version=3.3.0
VersionFile=1
Component=gb.image
Component=gb.gui
Component=gb.form
Component=gb.db
+Component=gb.report
Description="Report engine for gambas"
Authors="Fabien Bodard"
Environment="GB_GUI=gb.gtk"
diff --git a/comp/src/gb.web/.info b/comp/src/gb.web/.info
index a7bbc53b9..51d483643 100644
--- a/comp/src/gb.web/.info
+++ b/comp/src/gb.web/.info
@@ -5,6 +5,10 @@ Host
R
s
+Port
+R
+s
+
Root
R
s
@@ -196,10 +200,6 @@ CookiePath
P
s
-_init
-M
-
-
_exit
M
diff --git a/comp/src/gb.web/.src/Application.module b/comp/src/gb.web/.src/Application.module
index 9b458b85c..97c2023cb 100644
--- a/comp/src/gb.web/.src/Application.module
+++ b/comp/src/gb.web/.src/Application.module
@@ -3,6 +3,7 @@
Export
Property Read Host As String
+Property Read Port As String
Property Read Root As String
Property Read Request As String
Property LogFile As String
@@ -22,9 +23,9 @@ Private Function Request_Read() As String
Dim sReq As String
- sReq = $sProtocol & "://" & CGI["HTTP_HOST"] &/ CGI["SCRIPT_NAME"] &/ CGI["PATH_INFO"]
+ sReq = CGI["SCRIPT_NAME"] &/ CGI["PATH_INFO"]
If CGI["QUERY_STRING"] Then sReq &= "?" & CGI["QUERY_STRING"]
- Return sReq
+ Return Main.GetAbsoluteURL(sReq)
End
@@ -65,3 +66,9 @@ Private Sub Protocol_Write(Value As String)
End
+
+Private Function Port_Read() As String
+
+ Return CGI["SERVER_PORT"]
+
+End
diff --git a/comp/src/gb.web/.src/CGI.module b/comp/src/gb.web/.src/CGI.module
index ff28656c6..2ed14a177 100644
--- a/comp/src/gb.web/.src/CGI.module
+++ b/comp/src/gb.web/.src/CGI.module
@@ -69,7 +69,7 @@ Public Sub _init()
sRoot = CGI["SCRIPT_NAME"]
If Right(sRoot) = "/" Then sRoot = Left$(sRoot, -1)
- If Not sRoot Then sRoot = "/"
+ If Not sRoot Then sRoot = "/."
CGI["SCRIPT_NAME"] = sRoot
End
diff --git a/comp/src/gb.web/.src/Main.module b/comp/src/gb.web/.src/Main.module
index a21e88681..bf0f40a10 100644
--- a/comp/src/gb.web/.src/Main.module
+++ b/comp/src/gb.web/.src/Main.module
@@ -58,6 +58,20 @@ Public Sub DecodeURL(sUrl As String, aField As String[], cVal As Collection)
'
End
+Public Sub GetAbsoluteURL(sPath As String) As String
+
+ Dim sReq As String
+ Dim sPort As String
+
+ sReq = Application.Protocol & "://" & Application.Host
+
+ sPort = Application.Port
+ If sPort And If sPort <> "80" Then sReq &= ":" & sPort
+
+ Return sReq &/ sPath
+
+End
+
Public Sub Main()
diff --git a/comp/src/gb.web/.src/Response.module b/comp/src/gb.web/.src/Response.module
index bdc369afe..e5c40f547 100644
--- a/comp/src/gb.web/.src/Response.module
+++ b/comp/src/gb.web/.src/Response.module
@@ -30,7 +30,7 @@ Public Sub Redirect(URL As String)
If URL Like "*://*" Then
AddHeader("Location", URL)
Else
- AddHeader("Location", Application.Protocol & "://" & CGI["HTTP_HOST"] &/ URL)
+ AddHeader("Location", Main.GetAbsoluteURL(URL))
Endif
Response.Begin
@@ -113,7 +113,7 @@ Public Sub End()
If ShouldCompress() Then
If Split(CGI["HTTP_ACCEPT_ENCODING"], ",").Exist("gzip*", gb.Like) Then
- If Stat(sFile).Size >= 128 Then
+ If Stat(sFile).Size >= 1024 Then
AddHeader("Content-Encoding", "gzip")
AddHeader("Vary", "Accept-Encoding")
Exec ["gzip", "-9", sFile] Wait
@@ -126,6 +126,7 @@ Public Sub End()
'hLog = Open "/tmp/response." & CStr(Application.Id) For Create
AddHeader("Content-Length", Lof($hFile))
Print $sHeader
+ Main.Log("Header = " & $sHeader)
$sHeader = ""
While Not Eof($hFile)
diff --git a/comp/src/gb.web/.src/Session.module b/comp/src/gb.web/.src/Session.module
index 8994fd548..82e4e4885 100644
--- a/comp/src/gb.web/.src/Session.module
+++ b/comp/src/gb.web/.src/Session.module
@@ -13,6 +13,7 @@ Private $hLock As File
Private $sPrefix As String
Private $bUnique As Boolean
Private $sCookiePath As String
+Private $bInit As Boolean
Property Id As String
Property Timeout As Float
@@ -261,11 +262,19 @@ Private Sub SelectSession()
End
-Public Sub _init()
+Private Sub Init()
+
+ If $bInit Then Return
+
+ $bInit = True
Main.AllowLog = Exist("/tmp/session.debug")
+ 'Main.Log("Session.Init")
+
+ 'Main.Log("HTTP_COOKIE = " & CGI["HTTP_COOKIE"] & " / " & Env["HTTP_COOKIE"])
$sId = Request.Cookies["SESSION"]
+ Main.Log("Cookie = " & $sId)
'$sId = "9E2496B3AB6DDED93ABE6F0CF6E071B3@"
If Not $sId Then Return
@@ -278,17 +287,21 @@ End
Public Sub _exit()
+ Main.Log("Session._exit")
+
SaveSession
End
Private Sub GetCookiePath() As String
- If $sCookiePath Then
- Return $sCookiePath
- Else
- Return CGI["SCRIPT_NAME"]
- Endif
+ Dim sPath As String
+
+ If $sCookiePath Then Return $sCookiePath
+
+ sPath = CGI["SCRIPT_NAME"]
+ If sPath = "/." Then sPath = "/"
+ Return sPath
End
@@ -311,12 +324,14 @@ End
Public Sub _get(Key As String) As Variant
+ Init
If $cVal Then Return $cVal[Key]
End
Public Sub _put(Value As Variant, Key As String)
+ Init
If Not $cVal Then CreateSession
$cVal[Key] = Value
$bModify = True
@@ -326,6 +341,7 @@ End
Private Function Id_Read() As String
+ Init
Return $sId
End
@@ -333,6 +349,7 @@ End
Private Sub Id_Write(Value As String)
+ Init
Abandon
$sId = Value
SelectSession
@@ -341,24 +358,28 @@ End
Private Function Timeout_Read() As Float
+ Init
Return Int($eTimeout * 86400 + 0.5)
End
Private Sub Timeout_Write(Value As Float)
+ Init
$eTimeout = Value / 86400
End
Public Sub Save()
+ Init
SaveSession
End
Public Sub Load()
+ Init
LoadSession
End
@@ -384,6 +405,7 @@ End
Private Sub Unique_Write(Value As Boolean)
+ Init
$bUnique = Value
CheckUnique
@@ -397,6 +419,7 @@ End
Private Sub Modified_Write(Value As Boolean)
+ Init
$bModify = Value
End
diff --git a/configure.ac b/configure.ac
index 6ad7d0a5c..3c167294a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -40,6 +40,7 @@ GB_CONFIG_SUBDIRS(gsl, gb.gsl)
GB_CONFIG_SUBDIRS(ncurses, gb.ncurses)
GB_CONFIG_SUBDIRS(media, gb.media)
GB_CONFIG_SUBDIRS(jit, gb.jit)
+GB_CONFIG_SUBDIRS(httpd, gb.httpd)
AC_CONFIG_SUBDIRS(comp)
AC_CONFIG_SUBDIRS(app)
diff --git a/examples/examples/Control/Embedder/.project b/examples/examples/Control/Embedder/.project
index fbd4e7be8..b57bb7b29 100644
--- a/examples/examples/Control/Embedder/.project
+++ b/examples/examples/Control/Embedder/.project
@@ -1,14 +1,15 @@
# Gambas Project File 3.0
-# Compiled with Gambas 3.0.0
+# Compiled with Gambas 3.3.0
Title=Embedder
Startup=FMain
Icon=embedder.png
-Version=3.0.0
+Version=3.3.0
VersionFile=1
Component=gb.image
Component=gb.gui
Component=gb.form
Component=gb.desktop
+Environment="GB_GUI=gb.gtk"
TabSize=2
Translate=1
Language=fr
@@ -16,3 +17,4 @@ Maintainer=benoit
Vendor=Princeton
Address=benoit@localhost
License=General Public Licence
+Packager=1
diff --git a/examples/examples/Control/Embedder/.settings b/examples/examples/Control/Embedder/.settings
index 06f179f15..320a9bec8 100644
--- a/examples/examples/Control/Embedder/.settings
+++ b/examples/examples/Control/Embedder/.settings
@@ -12,9 +12,9 @@ SearchComment=False
SearchString=True
[OpenFile]
+Active=1
File[1]=".src/FMain.form"
-Active=2
-File[2]=".src/FMain.class:15.30"
+File[2]=".src/FMain.class:10.2"
Count=2
[Watches]
diff --git a/examples/examples/Control/Embedder/.src/FMain.class b/examples/examples/Control/Embedder/.src/FMain.class
index 85e857f16..4fc5b9dde 100644
--- a/examples/examples/Control/Embedder/.src/FMain.class
+++ b/examples/examples/Control/Embedder/.src/FMain.class
@@ -6,7 +6,9 @@ Public Sub btnEmbed_Click()
Dim aHandle As Integer[]
Dim iHandle As Integer
- sTitle = txtTitle.Text
+ sTitle = Trim(txtTitle.Text)
+ If Not sTitle Then Return
+
If Left(sTitle, 2) = "0x" Then
iHandle = Val("&" & Mid$(sTitle, 3))
Else If Left(sTitle) = "&" Then
diff --git a/examples/examples/Control/Embedder/.src/FMain.form b/examples/examples/Control/Embedder/.src/FMain.form
index b47e6b1fd..40465f3de 100644
--- a/examples/examples/Control/Embedder/.src/FMain.form
+++ b/examples/examples/Control/Embedder/.src/FMain.form
@@ -1,15 +1,15 @@
# Gambas Form File 3.0
{ Form Form
- MoveScaled(32.6667,25,84,60)
+ MoveScaled(32.7143,25,84,60)
Text = ("Desktop application embedder")
Icon = Picture["embedder.png"]
Arrangement = Arrange.Vertical
{ Panel1 Panel
- MoveScaled(0,0,82,5)
+ MoveScaled(0,0,82,6)
Arrangement = Arrange.Horizontal
- Spacing = 8
- Padding = 4
+ Spacing = True
+ Margin = True
{ Label1 Label
MoveScaled(2,1,16,4)
Font = Font["Bold"]
@@ -20,7 +20,6 @@
MoveScaled(19,1,17,4)
ToolTip = ("Enter there the title of the window you want to embed.")
Expand = True
- Text = ("")
}
{ btnEmbed Button
MoveScaled(37,1,14,4)
@@ -37,7 +36,6 @@
{ lblID Label
MoveScaled(69,1,11,4)
Visible = False
- Text = ("")
Alignment = Align.Center
Border = Border.Sunken
}
diff --git a/examples/examples/Drawing/Fractal/.project b/examples/examples/Drawing/Fractal/.project
index d0c9f8c86..714ca00d7 100644
--- a/examples/examples/Drawing/Fractal/.project
+++ b/examples/examples/Drawing/Fractal/.project
@@ -1,9 +1,9 @@
# Gambas Project File 3.0
-# Compiled with Gambas 3.2.0
+# Compiled with Gambas 3.3.0
Title=Fractal
Startup=FFractal
Icon=icon.png
-Version=3.2.0
+Version=3.3.0
VersionFile=1
Component=gb.image
Component=gb.gui
diff --git a/examples/examples/Drawing/Fractal/.startup b/examples/examples/Drawing/Fractal/.startup
index f45ca569a..2eb9fab6a 100644
--- a/examples/examples/Drawing/Fractal/.startup
+++ b/examples/examples/Drawing/Fractal/.startup
@@ -2,7 +2,7 @@ FFractal
Fractal
0
0
-3.2.0
+3.3.0
gb.image
gb.gui
diff --git a/gb.httpd/AUTHORS b/gb.httpd/AUTHORS
new file mode 100644
index 000000000..e69de29bb
diff --git a/gb.httpd/COPYING b/gb.httpd/COPYING
new file mode 120000
index 000000000..012065c85
--- /dev/null
+++ b/gb.httpd/COPYING
@@ -0,0 +1 @@
+../COPYING
\ No newline at end of file
diff --git a/gb.httpd/ChangeLog b/gb.httpd/ChangeLog
new file mode 100644
index 000000000..e69de29bb
diff --git a/gb.httpd/INSTALL b/gb.httpd/INSTALL
new file mode 120000
index 000000000..99d491b4f
--- /dev/null
+++ b/gb.httpd/INSTALL
@@ -0,0 +1 @@
+../INSTALL
\ No newline at end of file
diff --git a/gb.httpd/Makefile.am b/gb.httpd/Makefile.am
new file mode 100644
index 000000000..4cac8ec72
--- /dev/null
+++ b/gb.httpd/Makefile.am
@@ -0,0 +1,3 @@
+ACLOCAL_AMFLAGS = -I m4 --install
+SUBDIRS = @HTTPD_DIR@
+EXTRA_DIST = reconf gambas.h gb*.h
diff --git a/gb.httpd/NEWS b/gb.httpd/NEWS
new file mode 100644
index 000000000..e69de29bb
diff --git a/gb.httpd/README b/gb.httpd/README
new file mode 100644
index 000000000..e69de29bb
diff --git a/gb.httpd/acinclude.m4 b/gb.httpd/acinclude.m4
new file mode 120000
index 000000000..d84c32a31
--- /dev/null
+++ b/gb.httpd/acinclude.m4
@@ -0,0 +1 @@
+../acinclude.m4
\ No newline at end of file
diff --git a/gb.httpd/component.am b/gb.httpd/component.am
new file mode 120000
index 000000000..2f0eee34f
--- /dev/null
+++ b/gb.httpd/component.am
@@ -0,0 +1 @@
+../component.am
\ No newline at end of file
diff --git a/gb.httpd/configure.ac b/gb.httpd/configure.ac
new file mode 100644
index 000000000..036c27214
--- /dev/null
+++ b/gb.httpd/configure.ac
@@ -0,0 +1,151 @@
+dnl ---- configure.ac for gb.httpd
+
+AC_INIT(configure.ac)
+AC_CONFIG_MACRO_DIR([m4])
+dnl AC_CANONICAL_SYSTEM
+dnl AC_PROG_CC
+
+GB_INIT(gb.httpd)
+AC_PROG_LIBTOOL
+
+V_CCOPT="-O"
+if test "$GCC" = yes ; then
+ AC_MSG_CHECKING(gcc version)
+ AC_CACHE_VAL(ac_cv_lbl_gcc_vers,
+ ac_cv_lbl_gcc_vers=`$CC -dumpversion 2>&1 | \
+ sed -e 's/\..*//'`)
+ AC_MSG_RESULT($ac_cv_lbl_gcc_vers)
+ if test "$ac_cv_lbl_gcc_vers" -gt 1 ; then
+ V_CCOPT="-O2"
+ fi
+fi
+if test -f .devel ; then
+ V_CCOPT="-g $V_CCOPT -Wall -Wmissing-prototypes -Wstrict-prototypes"
+fi
+
+dnl
+dnl maybe this should be a loop
+dnl
+AC_MSG_CHECKING(how to link static binaries)
+AC_CACHE_VAL(ac_cv_lbl_static_flag,
+ ac_cv_lbl_static_flag=unknown
+ echo 'main() {}' > conftest.c
+ if test "$GCC" != yes ; then
+ trial_flag="-Bstatic"
+ test=`$CC $trial_flag -o conftest conftest.c 2>&1`
+ if test -z "$test" ; then
+ ac_cv_lbl_static_flag="$trial_flag"
+ fi
+ rm -f conftest
+ fi
+ if test "$ac_cv_lbl_static_flag" = unknown ; then
+ trial_flag="-static"
+ test=`$CC $trial_flag -o conftest conftest.c 2>&1`
+ if test -z "$test" ; then
+ ac_cv_lbl_static_flag="$trial_flag"
+ fi
+ rm -f conftest
+ fi
+ rm conftest.c)
+AC_MSG_RESULT($ac_cv_lbl_static_flag)
+if test "$ac_cv_lbl_static_flag" != unknown ; then
+ V_STATICFLAG="$ac_cv_lbl_static_flag"
+fi
+
+AC_MSG_CHECKING(for __progname)
+AC_CACHE_VAL(ac_cv_extern__progname,
+ AC_TRY_LINK([],
+ [extern char *__progname;
+ puts(__progname)],
+ ac_cv_extern__progname=yes,
+ ac_cv_extern__progname=no))
+if test $ac_cv_extern__progname = yes ; then
+ AC_DEFINE([HAVE__PROGNAME], [], [have __progname])
+ AC_MSG_RESULT(yes)
+else
+ AC_MSG_RESULT(no)
+fi
+
+AC_CHECK_HEADERS(fcntl.h grp.h memory.h paths.h poll.h sys/poll.h sys/devpoll.h sys/event.h osreldate.h)
+AC_HEADER_TIME
+AC_HEADER_DIRENT
+
+d="/usr/local/v6/lib"
+AC_MSG_CHECKING(for $d)
+if test -d $d; then
+ AC_MSG_RESULT(yes (Adding -L$d to LDFLAGS))
+ LDFLAGS="$LDFLAGS -L$d"
+else
+ AC_MSG_RESULT(no)
+fi
+
+dnl
+dnl Most operating systems have gethostbyname() in the default searched
+dnl libraries (i.e. libc):
+dnl
+V_NETLIBS=""
+AC_CHECK_FUNC(gethostbyname, ,
+ # Some OSes (eg. Solaris) place it in libnsl:
+ GB_AC_LBL_CHECK_LIB(nsl, gethostbyname,
+ V_NETLIBS="-lnsl $V_NETLIBS",
+ # Some strange OSes (SINIX) have it in libsocket:
+ GB_AC_LBL_CHECK_LIB(socket, gethostbyname,
+ V_NETLIBS="-lsocket $V_NETLIBS",
+ # Unfortunately libsocket sometimes depends on libnsl.
+ # AC_CHECK_LIB's API is essentially broken so the
+ # following ugliness is necessary:
+ GB_AC_LBL_CHECK_LIB(socket, gethostbyname,
+ V_NETLIBS="-lsocket -lnsl $V_NETLIBS",
+ AC_CHECK_LIB(resolv, gethostbyname,
+ V_NETLIBS="-lresolv $V_NETLIBS"),
+ -lnsl))))
+AC_CHECK_FUNC(socket, ,
+ AC_CHECK_LIB(socket, socket,
+ V_NETLIBS="-lsocket $V_NETLIBS",
+ GB_AC_LBL_CHECK_LIB(socket, socket,
+ V_NETLIBS="-lsocket -lnsl $V_NETLIBS", , -lnsl)))
+
+AC_CHECK_LIB(inet6, main)
+
+AC_CHECK_FUNC(crypt, , AC_CHECK_LIB(crypt, crypt))
+AC_CHECK_FUNC(hstrerror, ,
+ AC_CHECK_LIB(resolv, hstrerror, V_NETLIBS="-lresolv $V_NETLIBS"))
+
+AC_REPLACE_FUNCS(strerror)
+AC_CHECK_FUNCS(waitpid vsnprintf daemon setsid setlogin getaddrinfo getnameinfo gai_strerror kqueue atoll)
+AC_FUNC_MMAP
+
+case "$target_os" in
+solaris*)
+ dnl Solaris's select() is a bad wrapper routine.
+ AC_CHECK_FUNCS(poll)
+ ;;
+*)
+ AC_CHECK_FUNCS(select poll)
+ ;;
+esac
+
+GB_AC_ACME_TM_GMTOFF
+GB_AC_ACME_INT64T
+GB_AC_ACME_SOCKLENT
+
+AC_PROG_MAKE_SET
+AC_PROG_INSTALL
+
+AC_SUBST(DEFS)
+AC_SUBST(V_CCOPT)
+AC_SUBST(V_STATICFLAG)
+AC_SUBST(V_NETLIBS)
+
+GB_COMPONENT(
+ httpd,
+ HTTPD,
+ gb.httpd,
+ [src],
+ [],
+ [],
+ [],
+ [])
+
+AC_OUTPUT( Makefile src/Makefile )
+GB_PRINT_MESSAGES
diff --git a/gb.httpd/gambas.h b/gb.httpd/gambas.h
new file mode 120000
index 000000000..03677ecd0
--- /dev/null
+++ b/gb.httpd/gambas.h
@@ -0,0 +1 @@
+../main/share/gambas.h
\ No newline at end of file
diff --git a/gb.httpd/gb_common.h b/gb.httpd/gb_common.h
new file mode 120000
index 000000000..707d79da6
--- /dev/null
+++ b/gb.httpd/gb_common.h
@@ -0,0 +1 @@
+../main/share/gb_common.h
\ No newline at end of file
diff --git a/gb.httpd/m4 b/gb.httpd/m4
new file mode 120000
index 000000000..7d49a2a4b
--- /dev/null
+++ b/gb.httpd/m4
@@ -0,0 +1 @@
+../m4
\ No newline at end of file
diff --git a/gb.httpd/missing b/gb.httpd/missing
new file mode 120000
index 000000000..f3ade9ba1
--- /dev/null
+++ b/gb.httpd/missing
@@ -0,0 +1 @@
+../missing
\ No newline at end of file
diff --git a/gb.httpd/reconf b/gb.httpd/reconf
new file mode 120000
index 000000000..48a376da6
--- /dev/null
+++ b/gb.httpd/reconf
@@ -0,0 +1 @@
+../reconf
\ No newline at end of file
diff --git a/gb.httpd/src/Makefile.am b/gb.httpd/src/Makefile.am
new file mode 100644
index 000000000..772180410
--- /dev/null
+++ b/gb.httpd/src/Makefile.am
@@ -0,0 +1,21 @@
+COMPONENT = gb.httpd
+include $(top_srcdir)/component.am
+
+gblib_LTLIBRARIES = gb.httpd.la
+
+gb_httpd_la_LIBADD = @HTTPD_LIB@
+gb_httpd_la_LDFLAGS = -module @LD_FLAGS@ @HTTPD_LDFLAGS@
+gb_httpd_la_CPPFLAGS = @HTTPD_INC@
+
+gb_httpd_la_SOURCES = main.c main.h \
+ fdwatch.h fdwatch.c \
+ libhttpd.h libhttpd.c \
+ match.h match.c \
+ mime_encodings.h \
+ mime_types.h \
+ mmc.h mmc.c \
+ tdate_parse.h tdate_parse.c \
+ thttpd.h thttpd.c \
+ timers.h timers.c \
+ version.h
+
diff --git a/gb.httpd/src/fdwatch.c b/gb.httpd/src/fdwatch.c
new file mode 100644
index 000000000..129d9af3b
--- /dev/null
+++ b/gb.httpd/src/fdwatch.c
@@ -0,0 +1,840 @@
+/* fdwatch.c - fd watcher routines, either select() or poll()
+**
+** Copyright 1999,2000 by Jef Poskanzer
.
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifdef HAVE_POLL_H
+#include
+#else /* HAVE_POLL_H */
+#ifdef HAVE_SYS_POLL_H
+#include
+#endif /* HAVE_SYS_POLL_H */
+#endif /* HAVE_POLL_H */
+
+#ifdef HAVE_SYS_DEVPOLL_H
+#include
+#ifndef HAVE_DEVPOLL
+#define HAVE_DEVPOLL
+#endif /* !HAVE_DEVPOLL */
+#endif /* HAVE_SYS_DEVPOLL_H */
+
+#ifdef HAVE_SYS_EVENT_H
+#include
+#endif /* HAVE_SYS_EVENT_H */
+
+#include "fdwatch.h"
+
+#ifdef HAVE_SELECT
+#ifndef FD_SET
+#define NFDBITS 32
+#define FD_SETSIZE 32
+#define FD_SET(n, p) ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
+#define FD_CLR(n, p) ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
+#define FD_ISSET(n, p) ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
+#define FD_ZERO(p) bzero((char*)(p), sizeof(*(p)))
+#endif /* !FD_SET */
+#endif /* HAVE_SELECT */
+
+static int nfiles;
+static long nwatches;
+static int *fd_rw;
+static void **fd_data;
+static int nreturned, next_ridx;
+
+#ifdef HAVE_KQUEUE
+
+#define WHICH "kevent"
+#define INIT( nfiles ) kqueue_init( nfiles )
+#define ADD_FD( fd, rw ) kqueue_add_fd( fd, rw )
+#define DEL_FD( fd ) kqueue_del_fd( fd )
+#define WATCH( timeout_msecs ) kqueue_watch( timeout_msecs )
+#define CHECK_FD( fd ) kqueue_check_fd( fd )
+#define GET_FD( ridx ) kqueue_get_fd( ridx )
+
+static int kqueue_init (int nfiles);
+static void kqueue_add_fd (int fd, int rw);
+static void kqueue_del_fd (int fd);
+static int kqueue_watch (long timeout_msecs);
+static int kqueue_check_fd (int fd);
+static int kqueue_get_fd (int ridx);
+
+#else /* HAVE_KQUEUE */
+#ifdef HAVE_DEVPOLL
+
+#define WHICH "devpoll"
+#define INIT( nfiles ) devpoll_init( nfiles )
+#define ADD_FD( fd, rw ) devpoll_add_fd( fd, rw )
+#define DEL_FD( fd ) devpoll_del_fd( fd )
+#define WATCH( timeout_msecs ) devpoll_watch( timeout_msecs )
+#define CHECK_FD( fd ) devpoll_check_fd( fd )
+#define GET_FD( ridx ) devpoll_get_fd( ridx )
+
+static int devpoll_init (int nfiles);
+static void devpoll_add_fd (int fd, int rw);
+static void devpoll_del_fd (int fd);
+static int devpoll_watch (long timeout_msecs);
+static int devpoll_check_fd (int fd);
+static int devpoll_get_fd (int ridx);
+
+#else /* HAVE_DEVPOLL */
+#ifdef HAVE_POLL
+
+#define WHICH "poll"
+#define INIT( nfiles ) poll_init( nfiles )
+#define ADD_FD( fd, rw ) poll_add_fd( fd, rw )
+#define DEL_FD( fd ) poll_del_fd( fd )
+#define WATCH( timeout_msecs ) poll_watch( timeout_msecs )
+#define CHECK_FD( fd ) poll_check_fd( fd )
+#define GET_FD( ridx ) poll_get_fd( ridx )
+
+static int poll_init (int nfiles);
+static void poll_add_fd (int fd, int rw);
+static void poll_del_fd (int fd);
+static int poll_watch (long timeout_msecs);
+static int poll_check_fd (int fd);
+static int poll_get_fd (int ridx);
+
+#else /* HAVE_POLL */
+#ifdef HAVE_SELECT
+
+#define WHICH "select"
+#define INIT( nfiles ) select_init( nfiles )
+#define ADD_FD( fd, rw ) select_add_fd( fd, rw )
+#define DEL_FD( fd ) select_del_fd( fd )
+#define WATCH( timeout_msecs ) select_watch( timeout_msecs )
+#define CHECK_FD( fd ) select_check_fd( fd )
+#define GET_FD( ridx ) select_get_fd( ridx )
+
+static int select_init (int nfiles);
+static void select_add_fd (int fd, int rw);
+static void select_del_fd (int fd);
+static int select_watch (long timeout_msecs);
+static int select_check_fd (int fd);
+static int select_get_fd (int ridx);
+
+#endif /* HAVE_SELECT */
+#endif /* HAVE_POLL */
+#endif /* HAVE_DEVPOLL */
+#endif /* HAVE_KQUEUE */
+
+
+/* Routines. */
+
+/* Figure out how many file descriptors the system allows, and
+** initialize the fdwatch data structures. Returns -1 on failure.
+*/
+int fdwatch_get_nfiles (void)
+{
+ int i;
+#ifdef RLIMIT_NOFILE
+ struct rlimit rl;
+#endif /* RLIMIT_NOFILE */
+
+ /* Figure out how many fd's we can have. */
+ nfiles = getdtablesize ();
+#ifdef RLIMIT_NOFILE
+ /* If we have getrlimit(), use that, and attempt to raise the limit. */
+ if (getrlimit (RLIMIT_NOFILE, &rl) == 0)
+ {
+ nfiles = rl.rlim_cur;
+ if (rl.rlim_max == RLIM_INFINITY)
+ rl.rlim_cur = 8192; /* arbitrary */
+ else if (rl.rlim_max > rl.rlim_cur)
+ rl.rlim_cur = rl.rlim_max;
+ if (setrlimit (RLIMIT_NOFILE, &rl) == 0)
+ nfiles = rl.rlim_cur;
+ }
+#endif /* RLIMIT_NOFILE */
+
+#if defined(HAVE_SELECT) && ! ( defined(HAVE_POLL) || defined(HAVE_DEVPOLL) || defined(HAVE_KQUEUE) )
+ /* If we use select(), then we must limit ourselves to FD_SETSIZE. */
+ nfiles = MIN (nfiles, FD_SETSIZE);
+#endif /* HAVE_SELECT && ! ( HAVE_POLL || HAVE_DEVPOLL || HAVE_KQUEUE ) */
+
+ /* Initialize the fdwatch data structures. */
+ nwatches = 0;
+ fd_rw = (int *) malloc (sizeof (int) * nfiles);
+ fd_data = (void **) malloc (sizeof (void *) * nfiles);
+ if (fd_rw == (int *) 0 || fd_data == (void **) 0)
+ return -1;
+ for (i = 0; i < nfiles; ++i)
+ fd_rw[i] = -1;
+ if (INIT (nfiles) == -1)
+ return -1;
+
+ return nfiles;
+}
+
+
+/* Add a descriptor to the watch list. rw is either FDW_READ or FDW_WRITE. */
+void fdwatch_add_fd (int fd, void *client_data, int rw)
+{
+ if (fd < 0 || fd >= nfiles || fd_rw[fd] != -1)
+ {
+ syslog (LOG_ERR, "bad fd (%d) passed to fdwatch_add_fd!", fd);
+ return;
+ }
+ ADD_FD (fd, rw);
+ fd_rw[fd] = rw;
+ fd_data[fd] = client_data;
+}
+
+
+/* Remove a descriptor from the watch list. */
+void fdwatch_del_fd (int fd)
+{
+ if (fd < 0 || fd >= nfiles || fd_rw[fd] == -1)
+ {
+ syslog (LOG_ERR, "bad fd (%d) passed to fdwatch_del_fd!", fd);
+ return;
+ }
+ DEL_FD (fd);
+ fd_rw[fd] = -1;
+ fd_data[fd] = (void *) 0;
+}
+
+/* Do the watch. Return value is the number of descriptors that are ready,
+** or 0 if the timeout expired, or -1 on errors. A timeout of INFTIM means
+** wait indefinitely.
+*/
+int fdwatch (long timeout_msecs)
+{
+ ++nwatches;
+ nreturned = WATCH (timeout_msecs);
+ next_ridx = 0;
+ return nreturned;
+}
+
+
+/* Check if a descriptor was ready. */
+int fdwatch_check_fd (int fd)
+{
+ if (fd < 0 || fd >= nfiles || fd_rw[fd] == -1)
+ {
+ syslog (LOG_ERR, "bad fd (%d) passed to fdwatch_check_fd!", fd);
+ return 0;
+ }
+ return CHECK_FD (fd);
+}
+
+
+void *fdwatch_get_next_client_data (void)
+{
+ int fd;
+
+ if (next_ridx >= nreturned)
+ return (void *) -1;
+ fd = GET_FD (next_ridx++);
+ if (fd < 0 || fd >= nfiles)
+ return (void *) 0;
+ return fd_data[fd];
+}
+
+
+/* Generate debugging statistics syslog message. */
+void fdwatch_logstats (long secs)
+{
+ if (secs > 0)
+ syslog (LOG_INFO, " fdwatch - %ld %ss (%g/sec)",
+ nwatches, WHICH, (float) nwatches / secs);
+ nwatches = 0;
+}
+
+
+#ifdef HAVE_KQUEUE
+
+static int maxkqevents;
+static struct kevent *kqevents;
+static int nkqevents;
+static struct kevent *kqrevents;
+static int *kqrfdidx;
+static int kq;
+
+
+static int kqueue_init (int nfiles)
+{
+ kq = kqueue ();
+ if (kq == -1)
+ return -1;
+ maxkqevents = nfiles * 2;
+ kqevents = (struct kevent *) malloc (sizeof (struct kevent) * maxkqevents);
+ kqrevents = (struct kevent *) malloc (sizeof (struct kevent) * nfiles);
+ kqrfdidx = (int *) malloc (sizeof (int) * nfiles);
+ if (kqevents == (struct kevent *) 0 || kqrevents == (struct kevent *) 0 ||
+ kqrfdidx == (int *) 0)
+ return -1;
+ (void) memset (kqevents, 0, sizeof (struct kevent) * maxkqevents);
+ (void) memset (kqrfdidx, 0, sizeof (int) * nfiles);
+ return 0;
+}
+
+
+static void kqueue_add_fd (int fd, int rw)
+{
+ if (nkqevents >= maxkqevents)
+ {
+ syslog (LOG_ERR, "too many kqevents in kqueue_add_fd!");
+ return;
+ }
+ kqevents[nkqevents].ident = fd;
+ kqevents[nkqevents].flags = EV_ADD;
+ switch (rw)
+ {
+ case FDW_READ:
+ kqevents[nkqevents].filter = EVFILT_READ;
+ break;
+ case FDW_WRITE:
+ kqevents[nkqevents].filter = EVFILT_WRITE;
+ break;
+ default:
+ break;
+ }
+ ++nkqevents;
+}
+
+
+static void kqueue_del_fd (int fd)
+{
+ if (nkqevents >= maxkqevents)
+ {
+ syslog (LOG_ERR, "too many kqevents in kqueue_del_fd!");
+ return;
+ }
+ kqevents[nkqevents].ident = fd;
+ kqevents[nkqevents].flags = EV_DELETE;
+ switch (fd_rw[fd])
+ {
+ case FDW_READ:
+ kqevents[nkqevents].filter = EVFILT_READ;
+ break;
+ case FDW_WRITE:
+ kqevents[nkqevents].filter = EVFILT_WRITE;
+ break;
+ }
+ ++nkqevents;
+}
+
+
+static int kqueue_watch (long timeout_msecs)
+{
+ int i, r;
+
+ if (timeout_msecs == INFTIM)
+ r =
+ kevent (kq, kqevents, nkqevents, kqrevents, nfiles,
+ (struct timespec *) 0);
+ else
+ {
+ struct timespec ts;
+ ts.tv_sec = timeout_msecs / 1000L;
+ ts.tv_nsec = (timeout_msecs % 1000L) * 1000000L;
+ r = kevent (kq, kqevents, nkqevents, kqrevents, nfiles, &ts);
+ }
+ nkqevents = 0;
+ if (r == -1)
+ return -1;
+
+ for (i = 0; i < r; ++i)
+ kqrfdidx[kqrevents[i].ident] = i;
+
+ return r;
+}
+
+
+static int kqueue_check_fd (int fd)
+{
+ int ridx = kqrfdidx[fd];
+
+ if (ridx < 0 || ridx >= nfiles)
+ {
+ syslog (LOG_ERR, "bad ridx (%d) in kqueue_check_fd!", ridx);
+ return 0;
+ }
+ if (ridx >= nreturned)
+ return 0;
+ if (kqrevents[ridx].ident != fd)
+ return 0;
+ if (kqrevents[ridx].flags & EV_ERROR)
+ return 0;
+ switch (fd_rw[fd])
+ {
+ case FDW_READ:
+ return kqrevents[ridx].filter == EVFILT_READ;
+ case FDW_WRITE:
+ return kqrevents[ridx].filter == EVFILT_WRITE;
+ default:
+ return 0;
+ }
+}
+
+
+static int kqueue_get_fd (int ridx)
+{
+ if (ridx < 0 || ridx >= nfiles)
+ {
+ syslog (LOG_ERR, "bad ridx (%d) in kqueue_get_fd!", ridx);
+ return -1;
+ }
+ return kqrevents[ridx].ident;
+}
+
+#else /* HAVE_KQUEUE */
+
+
+#ifdef HAVE_DEVPOLL
+
+static int maxdpevents;
+static struct pollfd *dpevents;
+static int ndpevents;
+static struct pollfd *dprevents;
+static int *dp_rfdidx;
+static int dp;
+
+
+static int devpoll_init (int nfiles)
+{
+ dp = open ("/dev/poll", O_RDWR);
+ if (dp == -1)
+ return -1;
+ (void) fcntl (dp, F_SETFD, 1);
+ maxdpevents = nfiles * 2;
+ dpevents = (struct pollfd *) malloc (sizeof (struct pollfd) * maxdpevents);
+ dprevents = (struct pollfd *) malloc (sizeof (struct pollfd) * nfiles);
+ dp_rfdidx = (int *) malloc (sizeof (int) * nfiles);
+ if (dpevents == (struct pollfd *) 0 || dprevents == (struct pollfd *) 0 ||
+ dp_rfdidx == (int *) 0)
+ return -1;
+ (void) memset (dp_rfdidx, 0, sizeof (int) * nfiles);
+ return 0;
+}
+
+
+static void devpoll_add_fd (int fd, int rw)
+{
+ if (ndpevents >= maxdpevents)
+ {
+ syslog (LOG_ERR, "too many fds in devpoll_add_fd!");
+ return;
+ }
+ dpevents[ndpevents].fd = fd;
+ switch (rw)
+ {
+ case FDW_READ:
+ dpevents[ndpevents].events = POLLIN;
+ break;
+ case FDW_WRITE:
+ dpevents[ndpevents].events = POLLOUT;
+ break;
+ default:
+ break;
+ }
+ ++ndpevents;
+}
+
+
+static void devpoll_del_fd (int fd)
+{
+ if (ndpevents >= maxdpevents)
+ {
+ syslog (LOG_ERR, "too many fds in devpoll_del_fd!");
+ return;
+ }
+ dpevents[ndpevents].fd = fd;
+ dpevents[ndpevents].events = POLLREMOVE;
+ ++ndpevents;
+}
+
+
+static int devpoll_watch (long timeout_msecs)
+{
+ int i, r;
+ struct dvpoll dvp;
+
+ r = sizeof (struct pollfd) * ndpevents;
+ if (r > 0 && write (dp, dpevents, r) != r)
+ return -1;
+
+ ndpevents = 0;
+ dvp.dp_fds = dprevents;
+ dvp.dp_nfds = nfiles;
+ dvp.dp_timeout = (int) timeout_msecs;
+
+ r = ioctl (dp, DP_POLL, &dvp);
+ if (r == -1)
+ return -1;
+
+ for (i = 0; i < r; ++i)
+ dp_rfdidx[dprevents[i].fd] = i;
+
+ return r;
+}
+
+
+static int devpoll_check_fd (int fd)
+{
+ int ridx = dp_rfdidx[fd];
+
+ if (ridx < 0 || ridx >= nfiles)
+ {
+ syslog (LOG_ERR, "bad ridx (%d) in devpoll_check_fd!", ridx);
+ return 0;
+ }
+ if (ridx >= nreturned)
+ return 0;
+ if (dprevents[ridx].fd != fd)
+ return 0;
+ if (dprevents[ridx].revents & POLLERR)
+ return 0;
+ switch (fd_rw[fd])
+ {
+ case FDW_READ:
+ return dprevents[ridx].revents & (POLLIN | POLLHUP | POLLNVAL);
+ case FDW_WRITE:
+ return dprevents[ridx].revents & (POLLOUT | POLLHUP | POLLNVAL);
+ default:
+ return 0;
+ }
+}
+
+
+static int devpoll_get_fd (int ridx)
+{
+ if (ridx < 0 || ridx >= nfiles)
+ {
+ syslog (LOG_ERR, "bad ridx (%d) in devpoll_get_fd!", ridx);
+ return -1;
+ }
+ return dprevents[ridx].fd;
+}
+
+
+#else /* HAVE_DEVPOLL */
+
+
+#ifdef HAVE_POLL
+
+static struct pollfd *pollfds;
+static int npoll_fds;
+static int *poll_fdidx;
+static int *poll_rfdidx;
+
+
+static int poll_init (int nfiles)
+{
+ int i;
+
+ pollfds = (struct pollfd *) malloc (sizeof (struct pollfd) * nfiles);
+ poll_fdidx = (int *) malloc (sizeof (int) * nfiles);
+ poll_rfdidx = (int *) malloc (sizeof (int) * nfiles);
+ if (pollfds == (struct pollfd *) 0 || poll_fdidx == (int *) 0 ||
+ poll_rfdidx == (int *) 0)
+ return -1;
+ for (i = 0; i < nfiles; ++i)
+ pollfds[i].fd = poll_fdidx[i] = -1;
+ return 0;
+}
+
+
+static void poll_add_fd (int fd, int rw)
+{
+ if (npoll_fds >= nfiles)
+ {
+ syslog (LOG_ERR, "too many fds in poll_add_fd!");
+ return;
+ }
+ pollfds[npoll_fds].fd = fd;
+ switch (rw)
+ {
+ case FDW_READ:
+ pollfds[npoll_fds].events = POLLIN;
+ break;
+ case FDW_WRITE:
+ pollfds[npoll_fds].events = POLLOUT;
+ break;
+ default:
+ break;
+ }
+ poll_fdidx[fd] = npoll_fds;
+ ++npoll_fds;
+}
+
+
+static void poll_del_fd (int fd)
+{
+ int idx = poll_fdidx[fd];
+
+ if (idx < 0 || idx >= nfiles)
+ {
+ syslog (LOG_ERR, "bad idx (%d) in poll_del_fd!", idx);
+ return;
+ }
+ --npoll_fds;
+ pollfds[idx] = pollfds[npoll_fds];
+ poll_fdidx[pollfds[idx].fd] = idx;
+ pollfds[npoll_fds].fd = -1;
+ poll_fdidx[fd] = -1;
+}
+
+
+static int poll_watch (long timeout_msecs)
+{
+ int r, ridx, i;
+
+ r = poll (pollfds, npoll_fds, (int) timeout_msecs);
+ if (r <= 0)
+ return r;
+
+ ridx = 0;
+ for (i = 0; i < npoll_fds; ++i)
+ if (pollfds[i].revents &
+ (POLLIN | POLLOUT | POLLERR | POLLHUP | POLLNVAL))
+ {
+ poll_rfdidx[ridx++] = pollfds[i].fd;
+ if (ridx == r)
+ break;
+ }
+
+ return ridx; /* should be equal to r */
+}
+
+
+static int poll_check_fd (int fd)
+{
+ int fdidx = poll_fdidx[fd];
+
+ if (fdidx < 0 || fdidx >= nfiles)
+ {
+ syslog (LOG_ERR, "bad fdidx (%d) in poll_check_fd!", fdidx);
+ return 0;
+ }
+ if (pollfds[fdidx].revents & POLLERR)
+ return 0;
+ switch (fd_rw[fd])
+ {
+ case FDW_READ:
+ return pollfds[fdidx].revents & (POLLIN | POLLHUP | POLLNVAL);
+ case FDW_WRITE:
+ return pollfds[fdidx].revents & (POLLOUT | POLLHUP | POLLNVAL);
+ default:
+ return 0;
+ }
+}
+
+
+static int poll_get_fd (int ridx)
+{
+ if (ridx < 0 || ridx >= nfiles)
+ {
+ syslog (LOG_ERR, "bad ridx (%d) in poll_get_fd!", ridx);
+ return -1;
+ }
+ return poll_rfdidx[ridx];
+}
+
+#else /* HAVE_POLL */
+
+
+#ifdef HAVE_SELECT
+
+static fd_set master_rfdset;
+static fd_set master_wfdset;
+static fd_set working_rfdset;
+static fd_set working_wfdset;
+static int *select_fds;
+static int *select_fdidx;
+static int *select_rfdidx;
+static int nselect_fds;
+static int maxfd;
+static int maxfd_changed;
+
+
+static int select_init (int nfiles)
+{
+ int i;
+
+ FD_ZERO (&master_rfdset);
+ FD_ZERO (&master_wfdset);
+ select_fds = (int *) malloc (sizeof (int) * nfiles);
+ select_fdidx = (int *) malloc (sizeof (int) * nfiles);
+ select_rfdidx = (int *) malloc (sizeof (int) * nfiles);
+ if (select_fds == (int *) 0 || select_fdidx == (int *) 0 ||
+ select_rfdidx == (int *) 0)
+ return -1;
+ nselect_fds = 0;
+ maxfd = -1;
+ maxfd_changed = 0;
+ for (i = 0; i < nfiles; ++i)
+ select_fds[i] = select_fdidx[i] = -1;
+ return 0;
+}
+
+
+static void select_add_fd (int fd, int rw)
+{
+ if (nselect_fds >= nfiles)
+ {
+ syslog (LOG_ERR, "too many fds in select_add_fd!");
+ return;
+ }
+ select_fds[nselect_fds] = fd;
+ switch (rw)
+ {
+ case FDW_READ:
+ FD_SET (fd, &master_rfdset);
+ break;
+ case FDW_WRITE:
+ FD_SET (fd, &master_wfdset);
+ break;
+ default:
+ break;
+ }
+ if (fd > maxfd)
+ maxfd = fd;
+ select_fdidx[fd] = nselect_fds;
+ ++nselect_fds;
+}
+
+
+static void select_del_fd (int fd)
+{
+ int idx = select_fdidx[fd];
+
+ if (idx < 0 || idx >= nfiles)
+ {
+ syslog (LOG_ERR, "bad idx (%d) in select_del_fd!", idx);
+ return;
+ }
+
+ --nselect_fds;
+ select_fds[idx] = select_fds[nselect_fds];
+ select_fdidx[select_fds[idx]] = idx;
+ select_fds[nselect_fds] = -1;
+ select_fdidx[fd] = -1;
+
+ FD_CLR (fd, &master_rfdset);
+ FD_CLR (fd, &master_wfdset);
+
+ if (fd >= maxfd)
+ maxfd_changed = 1;
+}
+
+
+static int select_get_maxfd (void)
+{
+ if (maxfd_changed)
+ {
+ int i;
+ maxfd = -1;
+ for (i = 0; i < nselect_fds; ++i)
+ if (select_fds[i] > maxfd)
+ maxfd = select_fds[i];
+ maxfd_changed = 0;
+ }
+ return maxfd;
+}
+
+
+static int select_watch (long timeout_msecs)
+{
+ int mfd;
+ int r, idx, ridx;
+
+ working_rfdset = master_rfdset;
+ working_wfdset = master_wfdset;
+ mfd = select_get_maxfd ();
+ if (timeout_msecs == INFTIM)
+ r = select (mfd + 1, &working_rfdset, &working_wfdset, (fd_set *) 0,
+ (struct timeval *) 0);
+ else
+ {
+ struct timeval timeout;
+ timeout.tv_sec = timeout_msecs / 1000L;
+ timeout.tv_usec = (timeout_msecs % 1000L) * 1000L;
+ r =
+ select (mfd + 1, &working_rfdset, &working_wfdset, (fd_set *) 0,
+ &timeout);
+ }
+ if (r <= 0)
+ return r;
+
+ ridx = 0;
+ for (idx = 0; idx < nselect_fds; ++idx)
+ if (select_check_fd (select_fds[idx]))
+ {
+ select_rfdidx[ridx++] = select_fds[idx];
+ if (ridx == r)
+ break;
+ }
+
+ return ridx; /* should be equal to r */
+}
+
+
+static int select_check_fd (int fd)
+{
+ switch (fd_rw[fd])
+ {
+ case FDW_READ:
+ return FD_ISSET (fd, &working_rfdset);
+ case FDW_WRITE:
+ return FD_ISSET (fd, &working_wfdset);
+ default:
+ return 0;
+ }
+}
+
+
+static int select_get_fd (int ridx)
+{
+ if (ridx < 0 || ridx >= nfiles)
+ {
+ syslog (LOG_ERR, "bad ridx (%d) in select_get_fd!", ridx);
+ return -1;
+ }
+ return select_rfdidx[ridx];
+}
+
+#endif /* HAVE_SELECT */
+
+#endif /* HAVE_POLL */
+
+#endif /* HAVE_DEVPOLL */
+
+#endif /* HAVE_KQUEUE */
diff --git a/gb.httpd/src/fdwatch.h b/gb.httpd/src/fdwatch.h
new file mode 100644
index 000000000..5588b739e
--- /dev/null
+++ b/gb.httpd/src/fdwatch.h
@@ -0,0 +1,85 @@
+/* fdwatch.h - header file for fdwatch package
+**
+** This package abstracts the use of the select()/poll()/kqueue()
+** system calls. The basic function of these calls is to watch a set
+** of file descriptors for activity. select() originated in the BSD world,
+** while poll() came from SysV land, and their interfaces are somewhat
+** different. fdwatch lets you write your code to a single interface,
+** with the portability differences hidden inside the package.
+**
+** Usage is fairly simple. Call fdwatch_get_nfiles() to initialize
+** the package and find out how many fine descriptors are available.
+** Then each time through your main loop, call fdwatch_clear(), then
+** fdwatch_add_fd() for each of the descriptors you want to watch,
+** then call fdwatch() to actually perform the watch. After it returns
+** you can check which descriptors are ready via fdwatch_check_fd().
+**
+** If your descriptor set hasn't changed from the last time through
+** the loop, you can skip calling fdwatch_clear() and fdwatch_add_fd()
+** to save a little CPU time.
+**
+**
+** Copyright © 1999 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+#ifndef _FDWATCH_H_
+#define _FDWATCH_H_
+
+#define FDW_READ 0
+#define FDW_WRITE 1
+
+#ifndef INFTIM
+#define INFTIM -1
+#endif /* INFTIM */
+
+/* Figure out how many file descriptors the system allows, and
+** initialize the fdwatch data structures. Returns -1 on failure.
+*/
+extern int fdwatch_get_nfiles (void);
+
+/* Add a descriptor to the watch list. rw is either FDW_READ or FDW_WRITE. */
+extern void fdwatch_add_fd (int fd, void *client_data, int rw);
+
+/* Delete a descriptor from the watch list. */
+extern void fdwatch_del_fd (int fd);
+
+/* Do the watch. Return value is the number of descriptors that are ready,
+** or 0 if the timeout expired, or -1 on errors. A timeout of INFTIM means
+** wait indefinitely.
+*/
+extern int fdwatch (long timeout_msecs);
+
+/* Check if a descriptor was ready. */
+extern int fdwatch_check_fd (int fd);
+
+/* Get the client data for the next returned event. Returns -1 when there
+** are no more events.
+*/
+extern void *fdwatch_get_next_client_data (void);
+
+/* Generate debugging statistics syslog message. */
+extern void fdwatch_logstats (long secs);
+
+#endif /* _FDWATCH_H_ */
diff --git a/gb.httpd/src/gb.httpd.component b/gb.httpd/src/gb.httpd.component
new file mode 100644
index 000000000..10e636c3f
--- /dev/null
+++ b/gb.httpd/src/gb.httpd.component
@@ -0,0 +1,3 @@
+[Component]
+Author=
+Alpha=1
diff --git a/gb.httpd/src/libhttpd.c b/gb.httpd/src/libhttpd.c
new file mode 100644
index 000000000..e8be20416
--- /dev/null
+++ b/gb.httpd/src/libhttpd.c
@@ -0,0 +1,4308 @@
+/* libhttpd.c - HTTP protocol library
+**
+** Copyright � 1995,1998,1999,2000,2001 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+#include "main.h"
+#include "thttpd.h"
+#include "version.h"
+
+#ifdef SHOW_SERVER_VERSION
+#define EXPOSED_SERVER_SOFTWARE SERVER_SOFTWARE
+#else /* SHOW_SERVER_VERSION */
+#define EXPOSED_SERVER_SOFTWARE "thttpd"
+#endif /* SHOW_SERVER_VERSION */
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#ifdef HAVE_MEMORY_H
+#include
+#endif /* HAVE_MEMORY_H */
+#include
+#include
+#include
+#include
+#include
+//#include
+#include
+#include
+
+#ifdef HAVE_OSRELDATE_H
+#include
+#endif /* HAVE_OSRELDATE_H */
+
+#ifdef HAVE_DIRENT_H
+#include
+#define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+#define dirent direct
+#define NAMLEN(dirent) (dirent)->d_namlen
+#ifdef HAVE_SYS_NDIR_H
+#include
+#endif
+#ifdef HAVE_SYS_DIR_H
+#include
+#endif
+#ifdef HAVE_NDIR_H
+#include
+#endif
+#endif
+
+extern char *crypt (const char *key, const char *setting);
+
+#include "libhttpd.h"
+#include "mmc.h"
+#include "timers.h"
+#include "match.h"
+#include "tdate_parse.h"
+
+#ifndef STDIN_FILENO
+#define STDIN_FILENO 0
+#endif
+#ifndef STDOUT_FILENO
+#define STDOUT_FILENO 1
+#endif
+#ifndef STDERR_FILENO
+#define STDERR_FILENO 2
+#endif
+
+#ifndef SHUT_WR
+#define SHUT_WR 1
+#endif
+
+#ifndef HAVE_INT64T
+typedef long long int64_t;
+#endif
+
+#ifndef HAVE_SOCKLENT
+typedef int socklen_t;
+#endif
+
+#ifdef __CYGWIN__
+#define timezone _timezone
+#endif
+
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+extern char **environ;
+
+/* Forwards. */
+static void check_options (void);
+static void free_httpd_server (httpd_server * hs);
+static int initialize_listen_socket (httpd_sockaddr * saP);
+static void add_response (httpd_conn * hc, char *str);
+static void send_mime (httpd_conn * hc, int status, char *title,
+ char *encodings, char *extraheads, char *type,
+ off_t length, time_t mod);
+static void send_response (httpd_conn * hc, int status, char *title,
+ char *extraheads, char *form, char *arg);
+static void send_response_tail (httpd_conn * hc);
+static void defang (char *str, char *dfstr, int dfsize);
+#ifdef ERR_DIR
+static int send_err_file (httpd_conn * hc, int status, char *title,
+ char *extraheads, char *filename);
+#endif /* ERR_DIR */
+#ifdef AUTH_FILE
+static void send_authenticate (httpd_conn * hc, char *realm);
+static int b64_decode (const char *str, unsigned char *space, int size);
+//static int auth_check (httpd_conn * hc, char *dirname);
+//static int auth_check2 (httpd_conn * hc, char *dirname);
+#endif /* AUTH_FILE */
+//static void send_dirredirect (httpd_conn * hc);
+static int hexit (char c);
+static void strdecode (char *to, char *from);
+#ifdef GENERATE_INDEXES
+static void strencode (char *to, int tosize, char *from);
+#endif /* GENERATE_INDEXES */
+#ifdef TILDE_MAP_1
+static int tilde_map_1 (httpd_conn * hc);
+#endif /* TILDE_MAP_1 */
+#ifdef TILDE_MAP_2
+static int tilde_map_2 (httpd_conn * hc);
+#endif /* TILDE_MAP_2 */
+static int vhost_map (httpd_conn * hc);
+//static char *expand_symlinks (char *path, char **restP, int no_symlink_check, int tildemapped);
+static char *bufgets (httpd_conn * hc);
+static void de_dotdot (char *file);
+static void init_mime (void);
+//static void figure_mime (httpd_conn * hc);
+#ifdef CGI_TIMELIMIT
+static void cgi_kill2 (ClientData client_data, struct timeval *nowP);
+static void cgi_kill (ClientData client_data, struct timeval *nowP);
+#endif /* CGI_TIMELIMIT */
+#ifdef GENERATE_INDEXES
+static int ls (httpd_conn * hc);
+#endif /* GENERATE_INDEXES */
+static char *build_env (char *fmt, char *arg);
+#ifdef SERVER_NAME_LIST
+static char *hostname_map (char *hostname);
+#endif /* SERVER_NAME_LIST */
+static char **make_envp (httpd_conn * hc);
+static char **make_argp (httpd_conn * hc);
+static void cgi_interpose_input (httpd_conn * hc, int wfd);
+static void post_post_garbage_hack (httpd_conn * hc);
+static void cgi_interpose_output (httpd_conn * hc, int rfd);
+static void cgi_child (httpd_conn * hc);
+static int cgi (httpd_conn * hc);
+static int really_start_request (httpd_conn * hc, struct timeval *nowP);
+static void make_log_entry (httpd_conn * hc, struct timeval *nowP);
+static int check_referer (httpd_conn * hc);
+static int really_check_referer (httpd_conn * hc);
+static int sockaddr_check (httpd_sockaddr * saP);
+static size_t sockaddr_len (httpd_sockaddr * saP);
+static int my_snprintf (char *str, size_t size, const char *format, ...);
+#ifndef HAVE_ATOLL
+static long long atoll (const char *str);
+#endif /* HAVE_ATOLL */
+
+
+/* This global keeps track of whether we are in the main process or a
+** sub-process. The reason is that httpd_write_response() can get called
+** in either context; when it is called from the main process it must use
+** non-blocking I/O to avoid stalling the server, but when it is called
+** from a sub-process it wants to use blocking I/O so that the whole
+** response definitely gets written. So, it checks this variable. A bit
+** of a hack but it seems to do the right thing.
+*/
+static int sub_process = 0;
+
+
+static void check_options (void)
+{
+#if defined(TILDE_MAP_1) && defined(TILDE_MAP_2)
+ syslog (LOG_CRIT, "both TILDE_MAP_1 and TILDE_MAP_2 are defined");
+ exit (1);
+#endif /* both */
+}
+
+
+static void free_httpd_server (httpd_server * hs)
+{
+ if (hs->binding_hostname != (char *) 0)
+ free ((void *) hs->binding_hostname);
+ if (hs->cwd != (char *) 0)
+ free ((void *) hs->cwd);
+ if (hs->cgi_pattern != (char *) 0)
+ free ((void *) hs->cgi_pattern);
+ if (hs->charset != (char *) 0)
+ free ((void *) hs->charset);
+ if (hs->p3p != (char *) 0)
+ free ((void *) hs->p3p);
+ if (hs->url_pattern != (char *) 0)
+ free ((void *) hs->url_pattern);
+ if (hs->local_pattern != (char *) 0)
+ free ((void *) hs->local_pattern);
+ free ((void *) hs);
+}
+
+
+httpd_server *httpd_initialize (char *hostname, httpd_sockaddr * sa4P,
+ httpd_sockaddr * sa6P, unsigned short port,
+ char *cgi_pattern, int cgi_limit,
+ int cgi_timelimit, char *charset, char *p3p,
+ int max_age, char *cwd, int no_log,
+ FILE * logfp, int no_symlink_check, int vhost,
+ int global_passwd, char *url_pattern,
+ char *local_pattern, int no_empty_referers)
+{
+ httpd_server *hs;
+ static char ghnbuf[256];
+ char *cp;
+
+ check_options ();
+
+ hs = NEW (httpd_server, 1);
+ if (hs == (httpd_server *) 0)
+ {
+ syslog (LOG_CRIT, "out of memory allocating an httpd_server");
+ return (httpd_server *) 0;
+ }
+
+ if (hostname != (char *) 0)
+ {
+ hs->binding_hostname = strdup (hostname);
+ if (hs->binding_hostname == (char *) 0)
+ {
+ syslog (LOG_CRIT, "out of memory copying hostname");
+ return (httpd_server *) 0;
+ }
+ hs->server_hostname = hs->binding_hostname;
+ }
+ else
+ {
+ hs->binding_hostname = (char *) 0;
+ hs->server_hostname = (char *) 0;
+ if (gethostname (ghnbuf, sizeof (ghnbuf)) < 0)
+ ghnbuf[0] = '\0';
+#ifdef SERVER_NAME_LIST
+ if (ghnbuf[0] != '\0')
+ hs->server_hostname = hostname_map (ghnbuf);
+#endif /* SERVER_NAME_LIST */
+ if (hs->server_hostname == (char *) 0)
+ {
+#ifdef SERVER_NAME
+ hs->server_hostname = SERVER_NAME;
+#else /* SERVER_NAME */
+ if (ghnbuf[0] != '\0')
+ hs->server_hostname = ghnbuf;
+#endif /* SERVER_NAME */
+ }
+ }
+
+ hs->port = port;
+ if (cgi_pattern == (char *) 0)
+ hs->cgi_pattern = (char *) 0;
+ else
+ {
+ /* Nuke any leading slashes. */
+ if (cgi_pattern[0] == '/')
+ ++cgi_pattern;
+ hs->cgi_pattern = strdup (cgi_pattern);
+ if (hs->cgi_pattern == (char *) 0)
+ {
+ syslog (LOG_CRIT, "out of memory copying cgi_pattern");
+ return (httpd_server *) 0;
+ }
+ /* Nuke any leading slashes in the cgi pattern. */
+ while ((cp = strstr (hs->cgi_pattern, "|/")) != (char *) 0)
+ (void) strcpy (cp + 1, cp + 2);
+ }
+ hs->cgi_limit = cgi_limit;
+ hs->cgi_timelimit = cgi_timelimit;
+ hs->cgi_count = 0;
+ hs->charset = strdup (charset);
+ hs->p3p = strdup (p3p);
+ hs->max_age = max_age;
+ hs->cwd = strdup (cwd);
+ if (hs->cwd == (char *) 0)
+ {
+ syslog (LOG_CRIT, "out of memory copying cwd");
+ return (httpd_server *) 0;
+ }
+ if (url_pattern == (char *) 0)
+ hs->url_pattern = (char *) 0;
+ else
+ {
+ hs->url_pattern = strdup (url_pattern);
+ if (hs->url_pattern == (char *) 0)
+ {
+ syslog (LOG_CRIT, "out of memory copying url_pattern");
+ return (httpd_server *) 0;
+ }
+ }
+ if (local_pattern == (char *) 0)
+ hs->local_pattern = (char *) 0;
+ else
+ {
+ hs->local_pattern = strdup (local_pattern);
+ if (hs->local_pattern == (char *) 0)
+ {
+ syslog (LOG_CRIT, "out of memory copying local_pattern");
+ return (httpd_server *) 0;
+ }
+ }
+ hs->no_log = no_log;
+ hs->logfp = (FILE *) 0;
+ httpd_set_logfp (hs, logfp);
+ hs->no_symlink_check = no_symlink_check;
+ hs->vhost = vhost;
+ hs->global_passwd = global_passwd;
+ hs->no_empty_referers = no_empty_referers;
+
+ /* Initialize listen sockets. Try v6 first because of a Linux peculiarity;
+ ** like some other systems, it has magical v6 sockets that also listen for
+ ** v4, but in Linux if you bind a v4 socket first then the v6 bind fails.
+ */
+ if (sa6P == (httpd_sockaddr *) 0)
+ hs->listen6_fd = -1;
+ else
+ hs->listen6_fd = initialize_listen_socket (sa6P);
+ if (sa4P == (httpd_sockaddr *) 0)
+ hs->listen4_fd = -1;
+ else
+ hs->listen4_fd = initialize_listen_socket (sa4P);
+ /* If we didn't get any valid sockets, fail. */
+ if (hs->listen4_fd == -1 && hs->listen6_fd == -1)
+ {
+ free_httpd_server (hs);
+ return (httpd_server *) 0;
+ }
+
+ init_mime ();
+
+ /* Done initializing. */
+ if (hs->binding_hostname == (char *) 0)
+ syslog (LOG_NOTICE, "%.80s starting on port %d", SERVER_SOFTWARE,
+ (int) hs->port);
+ else
+ syslog (LOG_NOTICE, "%.80s starting on %.80s, port %d", SERVER_SOFTWARE,
+ httpd_ntoa (hs->listen4_fd != -1 ? sa4P : sa6P), (int) hs->port);
+ return hs;
+}
+
+
+static int initialize_listen_socket (httpd_sockaddr * saP)
+{
+ int listen_fd;
+ int on, flags;
+
+ /* Check sockaddr. */
+ if (!sockaddr_check (saP))
+ {
+ syslog (LOG_CRIT, "unknown sockaddr family on listen socket");
+ return -1;
+ }
+
+ /* Create socket. */
+ listen_fd = socket (saP->sa.sa_family, SOCK_STREAM, 0);
+ if (listen_fd < 0)
+ {
+ syslog (LOG_CRIT, "socket %.80s - %m", httpd_ntoa (saP));
+ return -1;
+ }
+ (void) fcntl (listen_fd, F_SETFD, 1);
+
+ /* Allow reuse of local addresses. */
+ on = 1;
+ if (setsockopt (listen_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
+ sizeof (on)) < 0)
+ syslog (LOG_CRIT, "setsockopt SO_REUSEADDR - %m");
+
+ /* Bind to it. */
+ if (bind (listen_fd, &saP->sa, sockaddr_len (saP)) < 0)
+ {
+ syslog (LOG_CRIT, "bind %.80s - %m", httpd_ntoa (saP));
+ (void) close (listen_fd);
+ return -1;
+ }
+
+ /* Set the listen file descriptor to no-delay / non-blocking mode. */
+ flags = fcntl (listen_fd, F_GETFL, 0);
+ if (flags == -1)
+ {
+ syslog (LOG_CRIT, "fcntl F_GETFL - %m");
+ (void) close (listen_fd);
+ return -1;
+ }
+ if (fcntl (listen_fd, F_SETFL, flags | O_NDELAY) < 0)
+ {
+ syslog (LOG_CRIT, "fcntl O_NDELAY - %m");
+ (void) close (listen_fd);
+ return -1;
+ }
+
+ /* Start a listen going. */
+ if (listen (listen_fd, LISTEN_BACKLOG) < 0)
+ {
+ syslog (LOG_CRIT, "listen - %m");
+ (void) close (listen_fd);
+ return -1;
+ }
+
+ /* Use accept filtering, if available. */
+#ifdef SO_ACCEPTFILTER
+ {
+#if ( __FreeBSD_version >= 411000 )
+#define ACCEPT_FILTER_NAME "httpready"
+#else
+#define ACCEPT_FILTER_NAME "dataready"
+#endif
+ struct accept_filter_arg af;
+ (void) bzero (&af, sizeof (af));
+ (void) strcpy (af.af_name, ACCEPT_FILTER_NAME);
+ (void) setsockopt (listen_fd, SOL_SOCKET, SO_ACCEPTFILTER, (char *) &af,
+ sizeof (af));
+ }
+#endif /* SO_ACCEPTFILTER */
+
+ return listen_fd;
+}
+
+
+void httpd_set_logfp (httpd_server * hs, FILE * logfp)
+{
+ if (hs->logfp != (FILE *) 0)
+ (void) fclose (hs->logfp);
+ hs->logfp = logfp;
+}
+
+
+void httpd_terminate (httpd_server * hs)
+{
+ httpd_unlisten (hs);
+ if (hs->logfp != (FILE *) 0)
+ (void) fclose (hs->logfp);
+ free_httpd_server (hs);
+}
+
+
+void httpd_unlisten (httpd_server * hs)
+{
+ if (hs->listen4_fd != -1)
+ {
+ (void) close (hs->listen4_fd);
+ hs->listen4_fd = -1;
+ }
+ if (hs->listen6_fd != -1)
+ {
+ (void) close (hs->listen6_fd);
+ hs->listen6_fd = -1;
+ }
+}
+
+
+/* Conditional macro to allow two alternate forms for use in the built-in
+** error pages. If EXPLICIT_ERROR_PAGES is defined, the second and more
+** explicit error form is used; otherwise, the first and more generic
+** form is used.
+*/
+#ifdef EXPLICIT_ERROR_PAGES
+#define ERROR_FORM(a,b) b
+#else /* EXPLICIT_ERROR_PAGES */
+#define ERROR_FORM(a,b) a
+#endif /* EXPLICIT_ERROR_PAGES */
+
+
+static char *ok200title = "OK";
+static char *ok206title = "Partial Content";
+
+static char *err302title = "Found";
+//static char *err302form = "The actual URL is '%.80s'.\n";
+
+static char *err304title = "Not Modified";
+
+char *httpd_err400title = "Bad Request";
+char *httpd_err400form =
+ "Your request has bad syntax or is inherently impossible to satisfy.\n";
+
+#ifdef AUTH_FILE
+static char *err401title = "Unauthorized";
+static char *err401form = "Authorization required for the URL '%.80s'.\n";
+#endif /* AUTH_FILE */
+
+static char *err403title = "Forbidden";
+#ifndef EXPLICIT_ERROR_PAGES
+static char *err403form =
+ "You do not have permission to get URL '%.80s' from this server.\n";
+#endif /* !EXPLICIT_ERROR_PAGES */
+
+static char *err404title = "Not Found";
+//static char *err404form = "The requested URL '%.80s' was not found on this server.\n";
+
+char *httpd_err408title = "Request Timeout";
+char *httpd_err408form =
+ "No request appeared within a reasonable time period.\n";
+
+static char *err500title = "Internal Error";
+static char *err500form =
+ "There was an unusual problem serving the requested URL '%.80s'.\n";
+
+static char *err501title = "Not Implemented";
+static char *err501form =
+ "The requested method '%.80s' is not implemented by this server.\n";
+
+char *httpd_err503title = "Service Temporarily Overloaded";
+char *httpd_err503form =
+ "The requested URL '%.80s' is temporarily overloaded. Please try again later.\n";
+
+
+/* Append a string to the buffer waiting to be sent as response. */
+static void add_response (httpd_conn * hc, char *str)
+{
+ size_t len;
+
+ len = strlen (str);
+ httpd_realloc_str (&hc->response, &hc->maxresponse, hc->responselen + len);
+ (void) memmove (&(hc->response[hc->responselen]), str, len);
+ hc->responselen += len;
+}
+
+/* Send the buffered response. */
+void httpd_write_response (httpd_conn * hc)
+{
+ /* If we are in a sub-process, turn off no-delay mode. */
+ if (sub_process)
+ httpd_clear_ndelay (hc->conn_fd);
+ /* Send the response, if necessary. */
+ if (hc->responselen > 0)
+ {
+ (void) httpd_write_fully (hc->conn_fd, hc->response, hc->responselen);
+ hc->responselen = 0;
+ }
+}
+
+
+/* Set no-delay / non-blocking mode on a socket. */
+void httpd_set_ndelay (int fd)
+{
+ int flags, newflags;
+
+ flags = fcntl (fd, F_GETFL, 0);
+ if (flags != -1)
+ {
+ newflags = flags | (int) O_NDELAY;
+ if (newflags != flags)
+ (void) fcntl (fd, F_SETFL, newflags);
+ }
+}
+
+
+/* Clear no-delay / non-blocking mode on a socket. */
+void httpd_clear_ndelay (int fd)
+{
+ int flags, newflags;
+
+ flags = fcntl (fd, F_GETFL, 0);
+ if (flags != -1)
+ {
+ newflags = flags & ~(int) O_NDELAY;
+ if (newflags != flags)
+ (void) fcntl (fd, F_SETFL, newflags);
+ }
+}
+
+
+static void
+send_mime (httpd_conn * hc, int status, char *title, char *encodings,
+ char *extraheads, char *type, off_t length, time_t mod)
+{
+ time_t now, expires;
+ const char *rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT";
+ char nowbuf[100];
+ char modbuf[100];
+ char expbuf[100];
+ char fixed_type[500];
+ char buf[1000];
+ int partial_content;
+ int s100;
+
+ hc->status = status;
+ hc->bytes_to_send = length;
+ if (hc->mime_flag)
+ {
+ if (status == 200 && hc->got_range &&
+ (hc->last_byte_index >= hc->first_byte_index) &&
+ ((hc->last_byte_index != length - 1) ||
+ (hc->first_byte_index != 0)) &&
+ (hc->range_if == (time_t) - 1 || hc->range_if == hc->sb.st_mtime))
+ {
+ partial_content = 1;
+ hc->status = status = 206;
+ title = ok206title;
+ }
+ else
+ {
+ partial_content = 0;
+ hc->got_range = 0;
+ }
+
+ now = time ((time_t *) 0);
+ if (mod == (time_t) 0)
+ mod = now;
+ (void) strftime (nowbuf, sizeof (nowbuf), rfc1123fmt, gmtime (&now));
+ (void) strftime (modbuf, sizeof (modbuf), rfc1123fmt, gmtime (&mod));
+ (void) my_snprintf (fixed_type, sizeof (fixed_type), type,
+ hc->hs->charset);
+ (void) my_snprintf (buf, sizeof (buf),
+ "%.20s %d %s\015\012Server: %s\015\012Content-Type: %s\015\012Date: %s\015\012Last-Modified: %s\015\012Accept-Ranges: bytes\015\012Connection: close\015\012",
+ hc->protocol, status, title, EXPOSED_SERVER_SOFTWARE,
+ fixed_type, nowbuf, modbuf);
+ add_response (hc, buf);
+ s100 = status / 100;
+ if (s100 != 2 && s100 != 3)
+ {
+ (void) my_snprintf (buf, sizeof (buf),
+ "Cache-Control: no-cache,no-store\015\012");
+ add_response (hc, buf);
+ }
+ if (encodings[0] != '\0')
+ {
+ (void) my_snprintf (buf, sizeof (buf),
+ "Content-Encoding: %s\015\012", encodings);
+ add_response (hc, buf);
+ }
+ if (partial_content)
+ {
+ (void) my_snprintf (buf, sizeof (buf),
+ "Content-Range: bytes %lld-%lld/%lld\015\012Content-Length: %lld\015\012",
+ (int64_t) hc->first_byte_index,
+ (int64_t) hc->last_byte_index, (int64_t) length,
+ (int64_t) (hc->last_byte_index -
+ hc->first_byte_index + 1));
+ add_response (hc, buf);
+ }
+ else if (length >= 0)
+ {
+ (void) my_snprintf (buf, sizeof (buf),
+ "Content-Length: %lld\015\012", (int64_t) length);
+ add_response (hc, buf);
+ }
+ if (hc->hs->p3p[0] != '\0')
+ {
+ (void) my_snprintf (buf, sizeof (buf), "P3P: %s\015\012", hc->hs->p3p);
+ add_response (hc, buf);
+ }
+ if (hc->hs->max_age >= 0)
+ {
+ expires = now + hc->hs->max_age;
+ (void) strftime (expbuf, sizeof (expbuf), rfc1123fmt,
+ gmtime (&expires));
+ (void) my_snprintf (buf, sizeof (buf),
+ "Cache-Control: max-age=%d\015\012Expires: %s\015\012",
+ hc->hs->max_age, expbuf);
+ add_response (hc, buf);
+ }
+ if (extraheads[0] != '\0')
+ add_response (hc, extraheads);
+ add_response (hc, "\015\012");
+ }
+}
+
+
+static int str_alloc_count = 0;
+static size_t str_alloc_size = 0;
+
+void httpd_realloc_str (char **strP, size_t * maxsizeP, size_t size)
+{
+ if (*maxsizeP == 0)
+ {
+ *maxsizeP = MAX (200, size + 100);
+ *strP = NEW (char, *maxsizeP + 1);
+ ++str_alloc_count;
+ str_alloc_size += *maxsizeP;
+ }
+ else if (size > *maxsizeP)
+ {
+ str_alloc_size -= *maxsizeP;
+ *maxsizeP = MAX (*maxsizeP * 2, size * 5 / 4);
+ *strP = RENEW (*strP, char, *maxsizeP + 1);
+ str_alloc_size += *maxsizeP;
+ }
+ else
+ return;
+ if (*strP == (char *) 0)
+ {
+ syslog (LOG_ERR, "out of memory reallocating a string to %d bytes",
+ *maxsizeP);
+ exit (1);
+ }
+}
+
+
+static void
+send_response (httpd_conn * hc, int status, char *title, char *extraheads,
+ char *form, char *arg)
+{
+ char defanged_arg[1000], buf[2000];
+
+ send_mime (hc, status, title, "", extraheads, "text/html; charset=%s",
+ (off_t) - 1, (time_t) 0);
+ (void) my_snprintf (buf, sizeof (buf), "\
+\n\
+%d %s\n\
+\n\
+%d %s
\n", status, title, status, title);
+ add_response (hc, buf);
+ defang (arg, defanged_arg, sizeof (defanged_arg));
+ (void) my_snprintf (buf, sizeof (buf), form, defanged_arg);
+ add_response (hc, buf);
+ if (match ("**MSIE**", hc->useragent))
+ {
+ int n;
+ add_response (hc, "\n");
+ }
+ send_response_tail (hc);
+}
+
+
+static void send_response_tail (httpd_conn * hc)
+{
+ char buf[1000];
+
+ (void) my_snprintf (buf, sizeof (buf), "\
+
\n\
+%s\n\
+\n\
+\n", SERVER_ADDRESS, EXPOSED_SERVER_SOFTWARE);
+ add_response (hc, buf);
+}
+
+
+static void defang (char *str, char *dfstr, int dfsize)
+{
+ char *cp1;
+ char *cp2;
+
+ for (cp1 = str, cp2 = dfstr;
+ *cp1 != '\0' && cp2 - dfstr < dfsize - 5; ++cp1, ++cp2)
+ {
+ switch (*cp1)
+ {
+ case '<':
+ *cp2++ = '&';
+ *cp2++ = 'l';
+ *cp2++ = 't';
+ *cp2 = ';';
+ break;
+ case '>':
+ *cp2++ = '&';
+ *cp2++ = 'g';
+ *cp2++ = 't';
+ *cp2 = ';';
+ break;
+ default:
+ *cp2 = *cp1;
+ break;
+ }
+ }
+ *cp2 = '\0';
+}
+
+
+void
+httpd_send_err (httpd_conn * hc, int status, char *title, char *extraheads,
+ char *form, char *arg)
+{
+#ifdef ERR_DIR
+
+ char filename[1000];
+
+ /* Try virtual host error page. */
+ if (hc->hs->vhost && hc->hostdir[0] != '\0')
+ {
+ (void) my_snprintf (filename, sizeof (filename),
+ "%s/%s/err%d.html", hc->hostdir, ERR_DIR, status);
+ if (send_err_file (hc, status, title, extraheads, filename))
+ return;
+ }
+
+ /* Try server-wide error page. */
+ (void) my_snprintf (filename, sizeof (filename),
+ "%s/err%d.html", ERR_DIR, status);
+ if (send_err_file (hc, status, title, extraheads, filename))
+ return;
+
+ /* Fall back on built-in error page. */
+ send_response (hc, status, title, extraheads, form, arg);
+
+#else /* ERR_DIR */
+
+ send_response (hc, status, title, extraheads, form, arg);
+
+#endif /* ERR_DIR */
+}
+
+
+#ifdef ERR_DIR
+static int
+send_err_file (httpd_conn * hc, int status, char *title, char *extraheads,
+ char *filename)
+{
+ FILE *fp;
+ char buf[1000];
+ size_t r;
+
+ fp = fopen (filename, "r");
+ if (fp == (FILE *) 0)
+ return 0;
+ send_mime (hc, status, title, "", extraheads, "text/html; charset=%s",
+ (off_t) - 1, (time_t) 0);
+ for (;;)
+ {
+ r = fread (buf, 1, sizeof (buf) - 1, fp);
+ if (r == 0)
+ break;
+ buf[r] = '\0';
+ add_response (hc, buf);
+ }
+ (void) fclose (fp);
+
+#ifdef ERR_APPEND_SERVER_INFO
+ send_response_tail (hc);
+#endif /* ERR_APPEND_SERVER_INFO */
+
+ return 1;
+}
+#endif /* ERR_DIR */
+
+
+#ifdef AUTH_FILE
+
+static void send_authenticate (httpd_conn * hc, char *realm)
+{
+ static char *header;
+ static size_t maxheader = 0;
+ static char headstr[] = "WWW-Authenticate: Basic realm=\"";
+
+ httpd_realloc_str (&header, &maxheader,
+ sizeof (headstr) + strlen (realm) + 3);
+ (void) my_snprintf (header, maxheader, "%s%s\"\015\012", headstr, realm);
+ httpd_send_err (hc, 401, err401title, header, err401form, hc->encodedurl);
+ /* If the request was a POST then there might still be data to be read,
+ ** so we need to do a lingering close.
+ */
+ if (hc->method == METHOD_POST)
+ hc->should_linger = 1;
+}
+
+
+/* Base-64 decoding. This represents binary data as printable ASCII
+** characters. Three 8-bit binary bytes are turned into four 6-bit
+** values, like so:
+**
+** [11111111] [22222222] [33333333]
+**
+** [111111] [112222] [222233] [333333]
+**
+** Then the 6-bit values are represented using the characters "A-Za-z0-9+/".
+*/
+
+static int b64_decode_table[256] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00-0F */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10-1F */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 20-2F */
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 30-3F */
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 40-4F */
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 50-5F */
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 60-6F */
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 70-7F */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80-8F */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90-9F */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* A0-AF */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* B0-BF */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* C0-CF */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* D0-DF */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* E0-EF */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* F0-FF */
+};
+
+/* Do base-64 decoding on a string. Ignore any non-base64 bytes.
+** Return the actual number of bytes generated. The decoded size will
+** be at most 3/4 the size of the encoded, and may be smaller if there
+** are padding characters (blanks, newlines).
+*/
+static int b64_decode (const char *str, unsigned char *space, int size)
+{
+ const char *cp;
+ int space_idx, phase;
+ int d, prev_d = 0;
+ unsigned char c;
+
+ space_idx = 0;
+ phase = 0;
+ for (cp = str; *cp != '\0'; ++cp)
+ {
+ d = b64_decode_table[(int) *cp];
+ if (d != -1)
+ {
+ switch (phase)
+ {
+ case 0:
+ ++phase;
+ break;
+ case 1:
+ c = ((prev_d << 2) | ((d & 0x30) >> 4));
+ if (space_idx < size)
+ space[space_idx++] = c;
+ ++phase;
+ break;
+ case 2:
+ c = (((prev_d & 0xf) << 4) | ((d & 0x3c) >> 2));
+ if (space_idx < size)
+ space[space_idx++] = c;
+ ++phase;
+ break;
+ case 3:
+ c = (((prev_d & 0x03) << 6) | d);
+ if (space_idx < size)
+ space[space_idx++] = c;
+ phase = 0;
+ break;
+ }
+ prev_d = d;
+ }
+ }
+ return space_idx;
+}
+
+/* Returns -1 == unauthorized, 0 == no auth file, 1 = authorized. */
+static int auth_check (httpd_conn * hc, char *dirname)
+{
+ if (hc->hs->global_passwd)
+ {
+ char *topdir;
+ if (hc->hs->vhost && hc->hostdir[0] != '\0')
+ topdir = hc->hostdir;
+ else
+ topdir = ".";
+ switch (auth_check2 (hc, topdir))
+ {
+ case -1:
+ return -1;
+ case 1:
+ return 1;
+ }
+ }
+ return auth_check2 (hc, dirname);
+}
+
+/* Returns -1 == unauthorized, 0 == no auth file, 1 = authorized. */
+static int auth_check2 (httpd_conn * hc, char *dirname)
+{
+ static char *authpath;
+ static size_t maxauthpath = 0;
+ struct stat sb;
+ char authinfo[500];
+ char *authpass;
+ char *colon;
+ int l;
+ FILE *fp;
+ char line[500];
+ char *cryp;
+ static char *prevauthpath;
+ static size_t maxprevauthpath = 0;
+ static time_t prevmtime;
+ static char *prevuser;
+ static size_t maxprevuser = 0;
+ static char *prevcryp;
+ static size_t maxprevcryp = 0;
+
+ /* Construct auth filename. */
+ httpd_realloc_str (&authpath, &maxauthpath,
+ strlen (dirname) + 1 + sizeof (AUTH_FILE));
+ (void) my_snprintf (authpath, maxauthpath, "%s/%s", dirname, AUTH_FILE);
+
+ /* Does this directory have an auth file? */
+ if (stat (authpath, &sb) < 0)
+ /* Nope, let the request go through. */
+ return 0;
+
+ /* Does this request contain basic authorization info? */
+ if (hc->authorization[0] == '\0' ||
+ strncmp (hc->authorization, "Basic ", 6) != 0)
+ {
+ /* Nope, return a 401 Unauthorized. */
+ send_authenticate (hc, dirname);
+ return -1;
+ }
+
+ /* Decode it. */
+ l = b64_decode (&(hc->authorization[6]), (unsigned char *) authinfo,
+ sizeof (authinfo) - 1);
+ authinfo[l] = '\0';
+ /* Split into user and password. */
+ authpass = strchr (authinfo, ':');
+ if (authpass == (char *) 0)
+ {
+ /* No colon? Bogus auth info. */
+ send_authenticate (hc, dirname);
+ return -1;
+ }
+ *authpass++ = '\0';
+ /* If there are more fields, cut them off. */
+ colon = strchr (authpass, ':');
+ if (colon != (char *) 0)
+ *colon = '\0';
+
+ /* See if we have a cached entry and can use it. */
+ if (maxprevauthpath != 0 &&
+ strcmp (authpath, prevauthpath) == 0 &&
+ sb.st_mtime == prevmtime && strcmp (authinfo, prevuser) == 0)
+ {
+ /* Yes. Check against the cached encrypted password. */
+ if (strcmp (crypt (authpass, prevcryp), prevcryp) == 0)
+ {
+ /* Ok! */
+ httpd_realloc_str (&hc->remoteuser, &hc->maxremoteuser,
+ strlen (authinfo));
+ (void) strcpy (hc->remoteuser, authinfo);
+ return 1;
+ }
+ else
+ {
+ /* No. */
+ send_authenticate (hc, dirname);
+ return -1;
+ }
+ }
+
+ /* Open the password file. */
+ fp = fopen (authpath, "r");
+ if (fp == (FILE *) 0)
+ {
+ /* The file exists but we can't open it? Disallow access. */
+ syslog (LOG_ERR, "%.80s auth file %.80s could not be opened - %m",
+ httpd_ntoa (&hc->client_addr), authpath);
+ httpd_send_err (hc, 403, err403title, "",
+ ERROR_FORM (err403form,
+ "The requested URL '%.80s' is protected by an authentication file, but the authentication file cannot be opened.\n"),
+ hc->encodedurl);
+ return -1;
+ }
+
+ /* Read it. */
+ while (fgets (line, sizeof (line), fp) != (char *) 0)
+ {
+ /* Nuke newline. */
+ l = strlen (line);
+ if (line[l - 1] == '\n')
+ line[l - 1] = '\0';
+ /* Split into user and encrypted password. */
+ cryp = strchr (line, ':');
+ if (cryp == (char *) 0)
+ continue;
+ *cryp++ = '\0';
+ /* Is this the right user? */
+ if (strcmp (line, authinfo) == 0)
+ {
+ /* Yes. */
+ (void) fclose (fp);
+ /* So is the password right? */
+ if (strcmp (crypt (authpass, cryp), cryp) == 0)
+ {
+ /* Ok! */
+ httpd_realloc_str (&hc->remoteuser, &hc->maxremoteuser,
+ strlen (line));
+ (void) strcpy (hc->remoteuser, line);
+ /* And cache this user's info for next time. */
+ httpd_realloc_str (&prevauthpath, &maxprevauthpath,
+ strlen (authpath));
+ (void) strcpy (prevauthpath, authpath);
+ prevmtime = sb.st_mtime;
+ httpd_realloc_str (&prevuser, &maxprevuser, strlen (authinfo));
+ (void) strcpy (prevuser, authinfo);
+ httpd_realloc_str (&prevcryp, &maxprevcryp, strlen (cryp));
+ (void) strcpy (prevcryp, cryp);
+ return 1;
+ }
+ else
+ {
+ /* No. */
+ send_authenticate (hc, dirname);
+ return -1;
+ }
+ }
+ }
+
+ /* Didn't find that user. Access denied. */
+ (void) fclose (fp);
+ send_authenticate (hc, dirname);
+ return -1;
+}
+
+#endif /* AUTH_FILE */
+
+#if 0
+static void send_dirredirect (httpd_conn * hc)
+{
+ static char *location;
+ static char *header;
+ static size_t maxlocation = 0, maxheader = 0;
+ static char headstr[] = "Location: ";
+
+ if (hc->query[0] != '\0')
+ {
+ char *cp = strchr (hc->encodedurl, '?');
+ if (cp != (char *) 0) /* should always find it */
+ *cp = '\0';
+ httpd_realloc_str (&location, &maxlocation,
+ strlen (hc->encodedurl) + 2 + strlen (hc->query));
+ (void) my_snprintf (location, maxlocation,
+ "%s/?%s", hc->encodedurl, hc->query);
+ }
+ else
+ {
+ httpd_realloc_str (&location, &maxlocation, strlen (hc->encodedurl) + 1);
+ (void) my_snprintf (location, maxlocation, "%s/", hc->encodedurl);
+ }
+ httpd_realloc_str (&header, &maxheader,
+ sizeof (headstr) + strlen (location));
+ (void) my_snprintf (header, maxheader, "%s%s\015\012", headstr, location);
+ send_response (hc, 302, err302title, header, err302form, location);
+}
+#endif
+
+char *httpd_method_str (int method)
+{
+ switch (method)
+ {
+ case METHOD_GET:
+ return "GET";
+ case METHOD_HEAD:
+ return "HEAD";
+ case METHOD_POST:
+ return "POST";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+
+static int hexit (char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return 0; /* shouldn't happen, we're guarded by isxdigit() */
+}
+
+
+/* Copies and decodes a string. It's ok for from and to to be the
+** same string.
+*/
+static void strdecode (char *to, char *from)
+{
+ for (; *from != '\0'; ++to, ++from)
+ {
+ if (from[0] == '%' && isxdigit (from[1]) && isxdigit (from[2]))
+ {
+ *to = hexit (from[1]) * 16 + hexit (from[2]);
+ from += 2;
+ }
+ else
+ *to = *from;
+ }
+ *to = '\0';
+}
+
+
+#ifdef GENERATE_INDEXES
+/* Copies and encodes a string. */
+static void strencode (char *to, int tosize, char *from)
+{
+ int tolen;
+
+ for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from)
+ {
+ if (isalnum (*from) || strchr ("/_.-~", *from) != (char *) 0)
+ {
+ *to = *from;
+ ++to;
+ ++tolen;
+ }
+ else
+ {
+ (void) sprintf (to, "%%%02x", (int) *from & 0xff);
+ to += 3;
+ tolen += 3;
+ }
+ }
+ *to = '\0';
+}
+#endif /* GENERATE_INDEXES */
+
+
+#ifdef TILDE_MAP_1
+/* Map a ~username/whatever URL into /username. */
+static int tilde_map_1 (httpd_conn * hc)
+{
+ static char *temp;
+ static size_t maxtemp = 0;
+ int len;
+ static char *prefix = TILDE_MAP_1;
+
+ len = strlen (hc->expnfilename) - 1;
+ httpd_realloc_str (&temp, &maxtemp, len);
+ (void) strcpy (temp, &hc->expnfilename[1]);
+ httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename,
+ strlen (prefix) + 1 + len);
+ (void) strcpy (hc->expnfilename, prefix);
+ if (prefix[0] != '\0')
+ (void) strcat (hc->expnfilename, "/");
+ (void) strcat (hc->expnfilename, temp);
+ return 1;
+}
+#endif /* TILDE_MAP_1 */
+
+#ifdef TILDE_MAP_2
+/* Map a ~username/whatever URL into /. */
+static int tilde_map_2 (httpd_conn * hc)
+{
+ static char *temp;
+ static size_t maxtemp = 0;
+ static char *postfix = TILDE_MAP_2;
+ char *cp;
+ struct passwd *pw;
+ char *alt;
+ char *rest;
+
+ /* Get the username. */
+ httpd_realloc_str (&temp, &maxtemp, strlen (hc->expnfilename) - 1);
+ (void) strcpy (temp, &hc->expnfilename[1]);
+ cp = strchr (temp, '/');
+ if (cp != (char *) 0)
+ *cp++ = '\0';
+ else
+ cp = "";
+
+ /* Get the passwd entry. */
+ pw = getpwnam (temp);
+ if (pw == (struct passwd *) 0)
+ return 0;
+
+ /* Set up altdir. */
+ httpd_realloc_str (&hc->altdir, &hc->maxaltdir,
+ strlen (pw->pw_dir) + 1 + strlen (postfix));
+ (void) strcpy (hc->altdir, pw->pw_dir);
+ if (postfix[0] != '\0')
+ {
+ (void) strcat (hc->altdir, "/");
+ (void) strcat (hc->altdir, postfix);
+ }
+ alt = expand_symlinks (hc->altdir, &rest, 0, 1);
+ if (rest[0] != '\0')
+ return 0;
+ httpd_realloc_str (&hc->altdir, &hc->maxaltdir, strlen (alt));
+ (void) strcpy (hc->altdir, alt);
+
+ /* And the filename becomes altdir plus the post-~ part of the original. */
+ httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename,
+ strlen (hc->altdir) + 1 + strlen (cp));
+ (void) my_snprintf (hc->expnfilename, hc->maxexpnfilename,
+ "%s/%s", hc->altdir, cp);
+
+ /* For this type of tilde mapping, we want to defeat vhost mapping. */
+ hc->tildemapped = 1;
+
+ return 1;
+}
+#endif /* TILDE_MAP_2 */
+
+
+/* Virtual host mapping. */
+static int vhost_map (httpd_conn * hc)
+{
+ httpd_sockaddr sa;
+ socklen_t sz;
+ static char *tempfilename;
+ static size_t maxtempfilename = 0;
+ char *cp1;
+ int len;
+#ifdef VHOST_DIRLEVELS
+ int i;
+ char *cp2;
+#endif /* VHOST_DIRLEVELS */
+
+ /* Figure out the virtual hostname. */
+ if (hc->reqhost[0] != '\0')
+ hc->hostname = hc->reqhost;
+ else if (hc->hdrhost[0] != '\0')
+ hc->hostname = hc->hdrhost;
+ else
+ {
+ sz = sizeof (sa);
+ if (getsockname (hc->conn_fd, &sa.sa, &sz) < 0)
+ {
+ syslog (LOG_ERR, "getsockname - %m");
+ return 0;
+ }
+ hc->hostname = httpd_ntoa (&sa);
+ }
+ /* Pound it to lower case. */
+ for (cp1 = hc->hostname; *cp1 != '\0'; ++cp1)
+ if (isupper (*cp1))
+ *cp1 = tolower (*cp1);
+
+ if (hc->tildemapped)
+ return 1;
+
+ /* Figure out the host directory. */
+#ifdef VHOST_DIRLEVELS
+ httpd_realloc_str (&hc->hostdir, &hc->maxhostdir,
+ strlen (hc->hostname) + 2 * VHOST_DIRLEVELS);
+ if (strncmp (hc->hostname, "www.", 4) == 0)
+ cp1 = &hc->hostname[4];
+ else
+ cp1 = hc->hostname;
+ for (cp2 = hc->hostdir, i = 0; i < VHOST_DIRLEVELS; ++i)
+ {
+ /* Skip dots in the hostname. If we don't, then we get vhost
+ ** directories in higher level of filestructure if dot gets
+ ** involved into path construction. It's `while' used here instead
+ ** of `if' for it's possible to have a hostname formed with two
+ ** dots at the end of it.
+ */
+ while (*cp1 == '.')
+ ++cp1;
+ /* Copy a character from the hostname, or '_' if we ran out. */
+ if (*cp1 != '\0')
+ *cp2++ = *cp1++;
+ else
+ *cp2++ = '_';
+ /* Copy a slash. */
+ *cp2++ = '/';
+ }
+ (void) strcpy (cp2, hc->hostname);
+#else /* VHOST_DIRLEVELS */
+ httpd_realloc_str (&hc->hostdir, &hc->maxhostdir, strlen (hc->hostname));
+ (void) strcpy (hc->hostdir, hc->hostname);
+#endif /* VHOST_DIRLEVELS */
+
+ /* Prepend hostdir to the filename. */
+ len = strlen (hc->expnfilename);
+ httpd_realloc_str (&tempfilename, &maxtempfilename, len);
+ (void) strcpy (tempfilename, hc->expnfilename);
+ httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename,
+ strlen (hc->hostdir) + 1 + len);
+ (void) strcpy (hc->expnfilename, hc->hostdir);
+ (void) strcat (hc->expnfilename, "/");
+ (void) strcat (hc->expnfilename, tempfilename);
+ return 1;
+}
+
+
+#if 0
+/* Expands all symlinks in the given filename, eliding ..'s and leading /'s.
+** Returns the expanded path (pointer to static string), or (char*) 0 on
+** errors. Also returns, in the string pointed to by restP, any trailing
+** parts of the path that don't exist.
+**
+** This is a fairly nice little routine. It handles any size filenames
+** without excessive mallocs.
+*/
+static char *expand_symlinks (char *path, char **restP, int no_symlink_check,
+ int tildemapped)
+{
+ static char *checked;
+ static char *rest;
+ char link[5000];
+ static size_t maxchecked = 0, maxrest = 0;
+ size_t checkedlen, restlen, linklen, prevcheckedlen, prevrestlen;
+ int nlinks, i;
+ char *r;
+ char *cp1;
+ char *cp2;
+
+ if (no_symlink_check)
+ {
+ /* If we are chrooted, we can actually skip the symlink-expansion,
+ ** since it's impossible to get out of the tree. However, we still
+ ** need to do the pathinfo check, and the existing symlink expansion
+ ** code is a pretty reasonable way to do this. So, what we do is
+ ** a single stat() of the whole filename - if it exists, then we
+ ** return it as is with nothing in restP. If it doesn't exist, we
+ ** fall through to the existing code.
+ **
+ ** One side-effect of this is that users can't symlink to central
+ ** approved CGIs any more. The workaround is to use the central
+ ** URL for the CGI instead of a local symlinked one.
+ */
+ struct stat sb;
+ if (stat (path, &sb) != -1)
+ {
+ checkedlen = strlen (path);
+ httpd_realloc_str (&checked, &maxchecked, checkedlen);
+ (void) strcpy (checked, path);
+ /* Trim trailing slashes. */
+ while (checked[checkedlen - 1] == '/')
+ {
+ checked[checkedlen - 1] = '\0';
+ --checkedlen;
+ }
+ httpd_realloc_str (&rest, &maxrest, 0);
+ rest[0] = '\0';
+ *restP = rest;
+ return checked;
+ }
+ }
+
+ /* Start out with nothing in checked and the whole filename in rest. */
+ httpd_realloc_str (&checked, &maxchecked, 1);
+ checked[0] = '\0';
+ checkedlen = 0;
+ restlen = strlen (path);
+ httpd_realloc_str (&rest, &maxrest, restlen);
+ (void) strcpy (rest, path);
+ if (rest[restlen - 1] == '/')
+ rest[--restlen] = '\0'; /* trim trailing slash */
+ if (!tildemapped)
+ /* Remove any leading slashes. */
+ while (rest[0] == '/')
+ {
+ (void) strcpy (rest, &(rest[1]));
+ --restlen;
+ }
+ r = rest;
+ nlinks = 0;
+
+ /* While there are still components to check... */
+ while (restlen > 0)
+ {
+ /* Save current checkedlen in case we get a symlink. Save current
+ ** restlen in case we get a non-existant component.
+ */
+ prevcheckedlen = checkedlen;
+ prevrestlen = restlen;
+
+ /* Grab one component from r and transfer it to checked. */
+ cp1 = strchr (r, '/');
+ if (cp1 != (char *) 0)
+ {
+ i = cp1 - r;
+ if (i == 0)
+ {
+ /* Special case for absolute paths. */
+ httpd_realloc_str (&checked, &maxchecked, checkedlen + 1);
+ (void) strncpy (&checked[checkedlen], r, 1);
+ checkedlen += 1;
+ }
+ else if (strncmp (r, "..", MAX (i, 2)) == 0)
+ {
+ /* Ignore ..'s that go above the start of the path. */
+ if (checkedlen != 0)
+ {
+ cp2 = strrchr (checked, '/');
+ if (cp2 == (char *) 0)
+ checkedlen = 0;
+ else if (cp2 == checked)
+ checkedlen = 1;
+ else
+ checkedlen = cp2 - checked;
+ }
+ }
+ else
+ {
+ httpd_realloc_str (&checked, &maxchecked, checkedlen + 1 + i);
+ if (checkedlen > 0 && checked[checkedlen - 1] != '/')
+ checked[checkedlen++] = '/';
+ (void) strncpy (&checked[checkedlen], r, i);
+ checkedlen += i;
+ }
+ checked[checkedlen] = '\0';
+ r += i + 1;
+ restlen -= i + 1;
+ }
+ else
+ {
+ /* No slashes remaining, r is all one component. */
+ if (strcmp (r, "..") == 0)
+ {
+ /* Ignore ..'s that go above the start of the path. */
+ if (checkedlen != 0)
+ {
+ cp2 = strrchr (checked, '/');
+ if (cp2 == (char *) 0)
+ checkedlen = 0;
+ else if (cp2 == checked)
+ checkedlen = 1;
+ else
+ checkedlen = cp2 - checked;
+ checked[checkedlen] = '\0';
+ }
+ }
+ else
+ {
+ httpd_realloc_str (&checked, &maxchecked, checkedlen + 1 + restlen);
+ if (checkedlen > 0 && checked[checkedlen - 1] != '/')
+ checked[checkedlen++] = '/';
+ (void) strcpy (&checked[checkedlen], r);
+ checkedlen += restlen;
+ }
+ r += restlen;
+ restlen = 0;
+ }
+
+ /* Try reading the current filename as a symlink */
+ if (checked[0] == '\0')
+ continue;
+ linklen = readlink (checked, link, sizeof (link) - 1);
+ if (linklen == -1)
+ {
+ if (errno == EINVAL)
+ continue; /* not a symlink */
+ if (errno == EACCES || errno == ENOENT || errno == ENOTDIR)
+ {
+ /* That last component was bogus. Restore and return. */
+ *restP = r - (prevrestlen - restlen);
+ if (prevcheckedlen == 0)
+ (void) strcpy (checked, ".");
+ else
+ checked[prevcheckedlen] = '\0';
+ return checked;
+ }
+ syslog (LOG_ERR, "readlink %.80s - %m", checked);
+ return (char *) 0;
+ }
+ ++nlinks;
+ if (nlinks > MAX_LINKS)
+ {
+ syslog (LOG_ERR, "too many symlinks in %.80s", path);
+ return (char *) 0;
+ }
+ link[linklen] = '\0';
+ if (link[linklen - 1] == '/')
+ link[--linklen] = '\0'; /* trim trailing slash */
+
+ /* Insert the link contents in front of the rest of the filename. */
+ if (restlen != 0)
+ {
+ (void) strcpy (rest, r);
+ httpd_realloc_str (&rest, &maxrest, restlen + linklen + 1);
+ for (i = restlen; i >= 0; --i)
+ rest[i + linklen + 1] = rest[i];
+ (void) strcpy (rest, link);
+ rest[linklen] = '/';
+ restlen += linklen + 1;
+ r = rest;
+ }
+ else
+ {
+ /* There's nothing left in the filename, so the link contents
+ ** becomes the rest.
+ */
+ httpd_realloc_str (&rest, &maxrest, linklen);
+ (void) strcpy (rest, link);
+ restlen = linklen;
+ r = rest;
+ }
+
+ if (rest[0] == '/')
+ {
+ /* There must have been an absolute symlink - zero out checked. */
+ checked[0] = '\0';
+ checkedlen = 0;
+ }
+ else
+ {
+ /* Re-check this component. */
+ checkedlen = prevcheckedlen;
+ checked[checkedlen] = '\0';
+ }
+ }
+
+ /* Ok. */
+ *restP = r;
+ if (checked[0] == '\0')
+ (void) strcpy (checked, ".");
+ return checked;
+}
+#endif
+
+int httpd_get_conn (httpd_server * hs, int listen_fd, httpd_conn * hc)
+{
+ httpd_sockaddr sa;
+ socklen_t sz;
+
+ if (!hc->initialized)
+ {
+ hc->read_size = 0;
+ httpd_realloc_str (&hc->read_buf, &hc->read_size, 500);
+ hc->maxdecodedurl =
+ hc->maxorigfilename = hc->maxexpnfilename = hc->maxencodings =
+ hc->maxpathinfo = hc->maxquery = hc->maxaccept =
+ hc->maxaccepte = hc->maxreqhost = hc->maxhostdir =
+ hc->maxremoteuser = hc->maxresponse = 0;
+#ifdef TILDE_MAP_2
+ hc->maxaltdir = 0;
+#endif /* TILDE_MAP_2 */
+ httpd_realloc_str (&hc->decodedurl, &hc->maxdecodedurl, 1);
+ httpd_realloc_str (&hc->origfilename, &hc->maxorigfilename, 1);
+ httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename, 0);
+ httpd_realloc_str (&hc->encodings, &hc->maxencodings, 0);
+ httpd_realloc_str (&hc->pathinfo, &hc->maxpathinfo, 0);
+ httpd_realloc_str (&hc->query, &hc->maxquery, 0);
+ httpd_realloc_str (&hc->accept, &hc->maxaccept, 0);
+ httpd_realloc_str (&hc->accepte, &hc->maxaccepte, 0);
+ httpd_realloc_str (&hc->reqhost, &hc->maxreqhost, 0);
+ httpd_realloc_str (&hc->hostdir, &hc->maxhostdir, 0);
+ httpd_realloc_str (&hc->remoteuser, &hc->maxremoteuser, 0);
+ httpd_realloc_str (&hc->response, &hc->maxresponse, 0);
+#ifdef TILDE_MAP_2
+ httpd_realloc_str (&hc->altdir, &hc->maxaltdir, 0);
+#endif /* TILDE_MAP_2 */
+ hc->initialized = 1;
+ }
+
+ /* Accept the new connection. */
+ sz = sizeof (sa);
+ hc->conn_fd = accept (listen_fd, &sa.sa, &sz);
+ if (hc->conn_fd < 0)
+ {
+ if (errno == EWOULDBLOCK)
+ return GC_NO_MORE;
+ syslog (LOG_ERR, "accept - %m");
+ return GC_FAIL;
+ }
+ if (!sockaddr_check (&sa))
+ {
+ syslog (LOG_ERR, "unknown sockaddr family");
+ close (hc->conn_fd);
+ hc->conn_fd = -1;
+ return GC_FAIL;
+ }
+ (void) fcntl (hc->conn_fd, F_SETFD, 1);
+ hc->hs = hs;
+ (void) memset (&hc->client_addr, 0, sizeof (hc->client_addr));
+ (void) memmove (&hc->client_addr, &sa, sockaddr_len (&sa));
+ hc->read_idx = 0;
+ hc->checked_idx = 0;
+ hc->checked_state = CHST_FIRSTWORD;
+ hc->method = METHOD_UNKNOWN;
+ hc->status = 0;
+ hc->bytes_to_send = 0;
+ hc->bytes_sent = 0;
+ hc->encodedurl = "";
+ hc->decodedurl[0] = '\0';
+ hc->protocol = "UNKNOWN";
+ hc->origfilename[0] = '\0';
+ hc->expnfilename[0] = '\0';
+ hc->encodings[0] = '\0';
+ hc->pathinfo[0] = '\0';
+ hc->query[0] = '\0';
+ hc->referer = "";
+ hc->useragent = "";
+ hc->accept[0] = '\0';
+ hc->accepte[0] = '\0';
+ hc->acceptl = "";
+ hc->cookie = "";
+ hc->contenttype = "";
+#ifdef X_CGI_HEADER
+ hc->xcgi = "";
+#endif
+ hc->reqhost[0] = '\0';
+ hc->hdrhost = "";
+ hc->hostdir[0] = '\0';
+ hc->authorization = "";
+ hc->remoteuser[0] = '\0';
+ hc->response[0] = '\0';
+#ifdef TILDE_MAP_2
+ hc->altdir[0] = '\0';
+#endif /* TILDE_MAP_2 */
+ hc->responselen = 0;
+ hc->if_modified_since = (time_t) - 1;
+ hc->range_if = (time_t) - 1;
+ hc->contentlength = -1;
+ hc->type = "";
+ hc->hostname = (char *) 0;
+ hc->mime_flag = 1;
+ hc->one_one = 0;
+ hc->got_range = 0;
+ hc->tildemapped = 0;
+ hc->first_byte_index = 0;
+ hc->last_byte_index = -1;
+ hc->keep_alive = 0;
+ hc->should_linger = 0;
+ hc->file_address = (char *) 0;
+ return GC_OK;
+}
+
+
+/* Checks hc->read_buf to see whether a complete request has been read so far;
+** either the first line has two words (an HTTP/0.9 request), or the first
+** line has three words and there's a blank line present.
+**
+** hc->read_idx is how much has been read in; hc->checked_idx is how much we
+** have checked so far; and hc->checked_state is the current state of the
+** finite state machine.
+*/
+int httpd_got_request (httpd_conn * hc)
+{
+ char c;
+
+ for (; hc->checked_idx < hc->read_idx; ++hc->checked_idx)
+ {
+ c = hc->read_buf[hc->checked_idx];
+ switch (hc->checked_state)
+ {
+ case CHST_FIRSTWORD:
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ hc->checked_state = CHST_FIRSTWS;
+ break;
+ case '\012':
+ case '\015':
+ hc->checked_state = CHST_BOGUS;
+ return GR_BAD_REQUEST;
+ }
+ break;
+ case CHST_FIRSTWS:
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ break;
+ case '\012':
+ case '\015':
+ hc->checked_state = CHST_BOGUS;
+ return GR_BAD_REQUEST;
+ default:
+ hc->checked_state = CHST_SECONDWORD;
+ break;
+ }
+ break;
+ case CHST_SECONDWORD:
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ hc->checked_state = CHST_SECONDWS;
+ break;
+ case '\012':
+ case '\015':
+ /* The first line has only two words - an HTTP/0.9 request. */
+ return GR_GOT_REQUEST;
+ }
+ break;
+ case CHST_SECONDWS:
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ break;
+ case '\012':
+ case '\015':
+ hc->checked_state = CHST_BOGUS;
+ return GR_BAD_REQUEST;
+ default:
+ hc->checked_state = CHST_THIRDWORD;
+ break;
+ }
+ break;
+ case CHST_THIRDWORD:
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ hc->checked_state = CHST_THIRDWS;
+ break;
+ case '\012':
+ hc->checked_state = CHST_LF;
+ break;
+ case '\015':
+ hc->checked_state = CHST_CR;
+ break;
+ }
+ break;
+ case CHST_THIRDWS:
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ break;
+ case '\012':
+ hc->checked_state = CHST_LF;
+ break;
+ case '\015':
+ hc->checked_state = CHST_CR;
+ break;
+ default:
+ hc->checked_state = CHST_BOGUS;
+ return GR_BAD_REQUEST;
+ }
+ break;
+ case CHST_LINE:
+ switch (c)
+ {
+ case '\012':
+ hc->checked_state = CHST_LF;
+ break;
+ case '\015':
+ hc->checked_state = CHST_CR;
+ break;
+ }
+ break;
+ case CHST_LF:
+ switch (c)
+ {
+ case '\012':
+ /* Two newlines in a row - a blank line - end of request. */
+ return GR_GOT_REQUEST;
+ case '\015':
+ hc->checked_state = CHST_CR;
+ break;
+ default:
+ hc->checked_state = CHST_LINE;
+ break;
+ }
+ break;
+ case CHST_CR:
+ switch (c)
+ {
+ case '\012':
+ hc->checked_state = CHST_CRLF;
+ break;
+ case '\015':
+ /* Two returns in a row - end of request. */
+ return GR_GOT_REQUEST;
+ default:
+ hc->checked_state = CHST_LINE;
+ break;
+ }
+ break;
+ case CHST_CRLF:
+ switch (c)
+ {
+ case '\012':
+ /* Two newlines in a row - end of request. */
+ return GR_GOT_REQUEST;
+ case '\015':
+ hc->checked_state = CHST_CRLFCR;
+ break;
+ default:
+ hc->checked_state = CHST_LINE;
+ break;
+ }
+ break;
+ case CHST_CRLFCR:
+ switch (c)
+ {
+ case '\012':
+ case '\015':
+ /* Two CRLFs or two CRs in a row - end of request. */
+ return GR_GOT_REQUEST;
+ default:
+ hc->checked_state = CHST_LINE;
+ break;
+ }
+ break;
+ case CHST_BOGUS:
+ return GR_BAD_REQUEST;
+ }
+ }
+ return GR_NO_REQUEST;
+}
+
+
+int httpd_parse_request (httpd_conn * hc)
+{
+ char *buf;
+ char *method_str;
+ char *url;
+ char *protocol;
+ char *reqhost;
+ char *eol;
+ char *cp;
+ char *pi;
+
+ hc->checked_idx = 0; /* reset */
+ method_str = bufgets (hc);
+ url = strpbrk (method_str, " \t\012\015");
+ if (url == (char *) 0)
+ {
+ httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, "");
+ return -1;
+ }
+ *url++ = '\0';
+ url += strspn (url, " \t\012\015");
+ protocol = strpbrk (url, " \t\012\015");
+ if (protocol == (char *) 0)
+ {
+ protocol = "HTTP/0.9";
+ hc->mime_flag = 0;
+ }
+ else
+ {
+ *protocol++ = '\0';
+ protocol += strspn (protocol, " \t\012\015");
+ if (*protocol != '\0')
+ {
+ eol = strpbrk (protocol, " \t\012\015");
+ if (eol != (char *) 0)
+ *eol = '\0';
+ if (strcasecmp (protocol, "HTTP/1.0") != 0)
+ hc->one_one = 1;
+ }
+ }
+ hc->protocol = protocol;
+
+ /* Check for HTTP/1.1 absolute URL. */
+ if (strncasecmp (url, "http://", 7) == 0)
+ {
+ if (!hc->one_one)
+ {
+ httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, "");
+ return -1;
+ }
+ reqhost = url + 7;
+ url = strchr (reqhost, '/');
+ if (url == (char *) 0)
+ {
+ httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, "");
+ return -1;
+ }
+ *url = '\0';
+ if (strchr (reqhost, '/') != (char *) 0 || reqhost[0] == '.')
+ {
+ httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, "");
+ return -1;
+ }
+ httpd_realloc_str (&hc->reqhost, &hc->maxreqhost, strlen (reqhost));
+ (void) strcpy (hc->reqhost, reqhost);
+ *url = '/';
+ }
+
+ if (*url != '/')
+ {
+ httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, "");
+ return -1;
+ }
+
+ if (strcasecmp (method_str, httpd_method_str (METHOD_GET)) == 0)
+ hc->method = METHOD_GET;
+ else if (strcasecmp (method_str, httpd_method_str (METHOD_HEAD)) == 0)
+ hc->method = METHOD_HEAD;
+ else if (strcasecmp (method_str, httpd_method_str (METHOD_POST)) == 0)
+ hc->method = METHOD_POST;
+ else
+ {
+ httpd_send_err (hc, 501, err501title, "", err501form, method_str);
+ return -1;
+ }
+
+ hc->encodedurl = url;
+ httpd_realloc_str (&hc->decodedurl, &hc->maxdecodedurl,
+ strlen (hc->encodedurl));
+ strdecode (hc->decodedurl, hc->encodedurl);
+
+ httpd_realloc_str (&hc->origfilename, &hc->maxorigfilename,
+ strlen (hc->decodedurl));
+
+ pi = &hc->decodedurl[1];
+ while (*pi == '/')
+ pi++;
+
+ (void) strcpy (hc->origfilename, pi);
+
+ /* Special case for top-level URL. */
+ //if ( hc->origfilename[0] == '\0' )
+ // (void) strcpy( hc->origfilename, "." );
+
+ /* Extract query string from encoded URL. */
+ cp = strchr (hc->encodedurl, '?');
+ if (cp != (char *) 0)
+ {
+ ++cp;
+ httpd_realloc_str (&hc->query, &hc->maxquery, strlen (cp));
+ (void) strcpy (hc->query, cp);
+ /* Remove query from (decoded) origfilename. */
+ cp = strchr (hc->origfilename, '?');
+ if (cp != (char *) 0)
+ *cp = '\0';
+ }
+
+ de_dotdot (hc->origfilename);
+
+ if (hc->origfilename[0] == '/' ||
+ (hc->origfilename[0] == '.' && hc->origfilename[1] == '.' &&
+ (hc->origfilename[2] == '\0' || hc->origfilename[2] == '/')))
+ {
+ httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, "");
+ return -1;
+ }
+
+ if (hc->mime_flag)
+ {
+ /* Read the MIME headers. */
+ while ((buf = bufgets (hc)) != (char *) 0)
+ {
+ if (buf[0] == '\0')
+ break;
+ if (strncasecmp (buf, "Referer:", 8) == 0)
+ {
+ cp = &buf[8];
+ cp += strspn (cp, " \t");
+ hc->referer = cp;
+ }
+ else if (strncasecmp (buf, "User-Agent:", 11) == 0)
+ {
+ cp = &buf[11];
+ cp += strspn (cp, " \t");
+ hc->useragent = cp;
+ }
+ else if (strncasecmp (buf, "Host:", 5) == 0)
+ {
+ cp = &buf[5];
+ cp += strspn (cp, " \t");
+ hc->hdrhost = cp;
+ cp = strchr (hc->hdrhost, ':');
+ if (cp != (char *) 0)
+ *cp = '\0';
+ if (strchr (hc->hdrhost, '/') != (char *) 0 || hc->hdrhost[0] == '.')
+ {
+ httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form,
+ "");
+ return -1;
+ }
+ }
+ else if (strncasecmp (buf, "Accept:", 7) == 0)
+ {
+ cp = &buf[7];
+ cp += strspn (cp, " \t");
+ if (hc->accept[0] != '\0')
+ {
+ if (strlen (hc->accept) > 5000)
+ {
+ syslog (LOG_ERR, "%.80s way too much Accept: data",
+ httpd_ntoa (&hc->client_addr));
+ continue;
+ }
+ httpd_realloc_str (&hc->accept, &hc->maxaccept,
+ strlen (hc->accept) + 2 + strlen (cp));
+ (void) strcat (hc->accept, ", ");
+ }
+ else
+ httpd_realloc_str (&hc->accept, &hc->maxaccept, strlen (cp));
+
+ (void) strcat (hc->accept, cp);
+ }
+ else if (strncasecmp (buf, "Accept-Encoding:", 16) == 0)
+ {
+ cp = &buf[16];
+ cp += strspn (cp, " \t");
+ if (hc->accepte[0] != '\0')
+ {
+ if (strlen (hc->accepte) > 5000)
+ {
+ syslog (LOG_ERR, "%.80s way too much Accept-Encoding: data",
+ httpd_ntoa (&hc->client_addr));
+ continue;
+ }
+ httpd_realloc_str (&hc->accepte, &hc->maxaccepte,
+ strlen (hc->accepte) + 2 + strlen (cp));
+ (void) strcat (hc->accepte, ", ");
+ }
+ else
+ httpd_realloc_str (&hc->accepte, &hc->maxaccepte, strlen (cp));
+
+ (void) strcpy (hc->accepte, cp);
+ }
+ else if (strncasecmp (buf, "Accept-Language:", 16) == 0)
+ {
+ cp = &buf[16];
+ cp += strspn (cp, " \t");
+ hc->acceptl = cp;
+ }
+ else if (strncasecmp (buf, "If-Modified-Since:", 18) == 0)
+ {
+ cp = &buf[18];
+ hc->if_modified_since = tdate_parse (cp);
+ if (hc->if_modified_since == (time_t) - 1)
+ syslog (LOG_DEBUG, "unparsable time: %.80s", cp);
+ }
+ else if (strncasecmp (buf, "Cookie:", 7) == 0)
+ {
+ cp = &buf[7];
+ cp += strspn (cp, " \t");
+ hc->cookie = cp;
+ }
+ else if (strncasecmp (buf, "Range:", 6) == 0)
+ {
+ /* Only support %d- and %d-%d, not %d-%d,%d-%d or -%d. */
+ if (strchr (buf, ',') == (char *) 0)
+ {
+ char *cp_dash;
+ cp = strpbrk (buf, "=");
+ if (cp != (char *) 0)
+ {
+ cp_dash = strchr (cp + 1, '-');
+ if (cp_dash != (char *) 0 && cp_dash != cp + 1)
+ {
+ *cp_dash = '\0';
+ hc->got_range = 1;
+ hc->first_byte_index = atoll (cp + 1);
+ if (hc->first_byte_index < 0)
+ hc->first_byte_index = 0;
+ if (isdigit ((int) cp_dash[1]))
+ {
+ hc->last_byte_index = atoll (cp_dash + 1);
+ if (hc->last_byte_index < 0)
+ hc->last_byte_index = -1;
+ }
+ }
+ }
+ }
+ }
+ else if (strncasecmp (buf, "Range-If:", 9) == 0 ||
+ strncasecmp (buf, "If-Range:", 9) == 0)
+ {
+ cp = &buf[9];
+ hc->range_if = tdate_parse (cp);
+ if (hc->range_if == (time_t) - 1)
+ syslog (LOG_DEBUG, "unparsable time: %.80s", cp);
+ }
+ else if (strncasecmp (buf, "Content-Type:", 13) == 0)
+ {
+ cp = &buf[13];
+ cp += strspn (cp, " \t");
+ hc->contenttype = cp;
+ }
+ else if (strncasecmp (buf, "Content-Length:", 15) == 0)
+ {
+ cp = &buf[15];
+ hc->contentlength = atol (cp);
+ }
+ else if (strncasecmp (buf, "Authorization:", 14) == 0)
+ {
+ cp = &buf[14];
+ cp += strspn (cp, " \t");
+ hc->authorization = cp;
+ }
+ else if (strncasecmp (buf, "Connection:", 11) == 0)
+ {
+ cp = &buf[11];
+ cp += strspn (cp, " \t");
+ if (strcasecmp (cp, "keep-alive") == 0)
+ hc->keep_alive = 1;
+ }
+#ifdef X_CGI_HEADER
+ else if (strncasecmp (buf, "X-Cgi:", 6) == 0)
+ {
+ cp = &buf[6];
+ cp += strspn (cp, " \t");
+ hc->xcgi = cp;
+ }
+#endif
+#ifdef LOG_UNKNOWN_HEADERS
+ else if (strncasecmp (buf, "Accept-Charset:", 15) == 0 ||
+ strncasecmp (buf, "Accept-Language:", 16) == 0 ||
+ strncasecmp (buf, "Agent:", 6) == 0 ||
+ strncasecmp (buf, "Cache-Control:", 14) == 0 ||
+ strncasecmp (buf, "Cache-Info:", 11) == 0 ||
+ strncasecmp (buf, "Charge-To:", 10) == 0 ||
+ strncasecmp (buf, "Client-IP:", 10) == 0 ||
+ strncasecmp (buf, "Date:", 5) == 0 ||
+ strncasecmp (buf, "Extension:", 10) == 0 ||
+ strncasecmp (buf, "Forwarded:", 10) == 0 ||
+ strncasecmp (buf, "From:", 5) == 0 ||
+ strncasecmp (buf, "HTTP-Version:", 13) == 0 ||
+ strncasecmp (buf, "Max-Forwards:", 13) == 0 ||
+ strncasecmp (buf, "Message-Id:", 11) == 0 ||
+ strncasecmp (buf, "MIME-Version:", 13) == 0 ||
+ strncasecmp (buf, "Negotiate:", 10) == 0 ||
+ strncasecmp (buf, "Pragma:", 7) == 0 ||
+ strncasecmp (buf, "Proxy-Agent:", 12) == 0 ||
+ strncasecmp (buf, "Proxy-Connection:", 17) == 0 ||
+ strncasecmp (buf, "Security-Scheme:", 16) == 0 ||
+ strncasecmp (buf, "Session-Id:", 11) == 0 ||
+ strncasecmp (buf, "UA-Color:", 9) == 0 ||
+ strncasecmp (buf, "UA-CPU:", 7) == 0 ||
+ strncasecmp (buf, "UA-Disp:", 8) == 0 ||
+ strncasecmp (buf, "UA-OS:", 6) == 0 ||
+ strncasecmp (buf, "UA-Pixels:", 10) == 0 ||
+ strncasecmp (buf, "User:", 5) == 0 ||
+ strncasecmp (buf, "Via:", 4) == 0 ||
+ strncasecmp (buf, "X-", 2) == 0)
+ ; /* ignore */
+ else
+ syslog (LOG_DEBUG, "unknown request header: %.80s", buf);
+#endif /* LOG_UNKNOWN_HEADERS */
+ }
+ }
+
+ if (hc->one_one)
+ {
+ /* Check that HTTP/1.1 requests specify a host, as required. */
+ if (hc->reqhost[0] == '\0' && hc->hdrhost[0] == '\0')
+ {
+ httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, "");
+ return -1;
+ }
+
+ /* If the client wants to do keep-alives, it might also be doing
+ ** pipelining. There's no way for us to tell. Since we don't
+ ** implement keep-alives yet, if we close such a connection there
+ ** might be unread pipelined requests waiting. So, we have to
+ ** do a lingering close.
+ */
+ if (hc->keep_alive)
+ hc->should_linger = 1;
+ }
+
+ /* Ok, the request has been parsed. Now we resolve stuff that
+ ** may require the entire request.
+ */
+
+ /* Copy original filename to expanded filename. */
+ httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename,
+ strlen (hc->origfilename));
+ (void) strcpy (hc->expnfilename, hc->origfilename);
+
+ /* Tilde mapping. */
+ if (hc->expnfilename[0] == '~')
+ {
+#ifdef TILDE_MAP_1
+ if (!tilde_map_1 (hc))
+ {
+ httpd_send_err (hc, 404, err404title, "", err404form, hc->encodedurl);
+ return -1;
+ }
+#endif /* TILDE_MAP_1 */
+#ifdef TILDE_MAP_2
+ if (!tilde_map_2 (hc))
+ {
+ httpd_send_err (hc, 404, err404title, "", err404form, hc->encodedurl);
+ return -1;
+ }
+#endif /* TILDE_MAP_2 */
+ }
+
+ /* Virtual host mapping. */
+ if (hc->hs->vhost)
+ if (!vhost_map (hc))
+ {
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ return -1;
+ }
+
+#if 0
+ /* Expand all symbolic links in the filename. This also gives us
+ ** any trailing non-existing components, for pathinfo.
+ */
+ cp =
+ expand_symlinks (hc->expnfilename, &pi, hc->hs->no_symlink_check,
+ hc->tildemapped);
+
+ //fprintf(stderr, "expnfilename: %s -> cp = %s pi = %s\n", hc->expnfilename, cp, pi);
+
+ if (cp == (char *) 0)
+ {
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ return -1;
+ }
+ httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename, strlen (cp));
+ (void) strcpy (hc->expnfilename, cp);
+ httpd_realloc_str (&hc->pathinfo, &hc->maxpathinfo, strlen (pi));
+ (void) strcpy (hc->pathinfo, pi);
+
+ /* Remove pathinfo stuff from the original filename too. */
+ if (hc->pathinfo[0] != '\0')
+ {
+ int i;
+ i = strlen (hc->origfilename) - strlen (hc->pathinfo);
+ if (i > 0 && strcmp (&hc->origfilename[i], hc->pathinfo) == 0)
+ hc->origfilename[i - 1] = '\0';
+ }
+
+ /* If the expanded filename is an absolute path, check that it's still
+ ** within the current directory or the alternate directory.
+ */
+ if (hc->expnfilename[0] == '/')
+ {
+ if (strncmp (hc->expnfilename, hc->hs->cwd, strlen (hc->hs->cwd)) == 0)
+ {
+ /* Elide the current directory. */
+ (void) strcpy (hc->expnfilename,
+ &hc->expnfilename[strlen (hc->hs->cwd)]);
+ }
+#ifdef TILDE_MAP_2
+ else if (hc->altdir[0] != '\0' &&
+ (strncmp (hc->expnfilename, hc->altdir,
+ strlen (hc->altdir)) == 0 &&
+ (hc->expnfilename[strlen (hc->altdir)] == '\0' ||
+ hc->expnfilename[strlen (hc->altdir)] == '/')))
+ {
+ }
+#endif /* TILDE_MAP_2 */
+ else
+ {
+ syslog (LOG_NOTICE, "%.80s URL \"%.80s\" goes outside the web tree",
+ httpd_ntoa (&hc->client_addr), hc->encodedurl);
+ httpd_send_err (hc, 403, err403title, "",
+ ERROR_FORM (err403form,
+ "The requested URL '%.80s' resolves to a file outside the permitted web server directory tree.\n"),
+ hc->encodedurl);
+ return -1;
+ }
+ }
+#endif
+
+ return 0;
+}
+
+
+static char *bufgets (httpd_conn * hc)
+{
+ int i;
+ char c;
+
+ for (i = hc->checked_idx; hc->checked_idx < hc->read_idx; ++hc->checked_idx)
+ {
+ c = hc->read_buf[hc->checked_idx];
+ if (c == '\012' || c == '\015')
+ {
+ hc->read_buf[hc->checked_idx] = '\0';
+ ++hc->checked_idx;
+ if (c == '\015' && hc->checked_idx < hc->read_idx &&
+ hc->read_buf[hc->checked_idx] == '\012')
+ {
+ hc->read_buf[hc->checked_idx] = '\0';
+ ++hc->checked_idx;
+ }
+ return &(hc->read_buf[i]);
+ }
+ }
+ return (char *) 0;
+}
+
+
+static void de_dotdot (char *file)
+{
+ char *cp;
+ char *cp2;
+ int l;
+
+ /* Collapse any multiple / sequences. */
+ while ((cp = strstr (file, "//")) != (char *) 0)
+ {
+ for (cp2 = cp + 2; *cp2 == '/'; ++cp2)
+ continue;
+ (void) strcpy (cp + 1, cp2);
+ }
+
+ /* Remove leading ./ and any /./ sequences. */
+ while (strncmp (file, "./", 2) == 0)
+ (void) strcpy (file, file + 2);
+ while ((cp = strstr (file, "/./")) != (char *) 0)
+ (void) strcpy (cp, cp + 2);
+
+ /* Alternate between removing leading ../ and removing xxx/../ */
+ for (;;)
+ {
+ while (strncmp (file, "../", 3) == 0)
+ (void) strcpy (file, file + 3);
+ cp = strstr (file, "/../");
+ if (cp == (char *) 0)
+ break;
+ for (cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2)
+ continue;
+ (void) strcpy (cp2 + 1, cp + 4);
+ }
+
+ /* Also elide any xxx/.. at the end. */
+ while ((l = strlen (file)) > 3 && strcmp ((cp = file + l - 3), "/..") == 0)
+ {
+ for (cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2)
+ continue;
+ if (cp2 < file)
+ break;
+ *cp2 = '\0';
+ }
+}
+
+
+void httpd_close_conn (httpd_conn * hc, struct timeval *nowP)
+{
+ make_log_entry (hc, nowP);
+
+ if (hc->file_address != (char *) 0)
+ {
+ mmc_unmap (hc->file_address, &(hc->sb), nowP);
+ hc->file_address = (char *) 0;
+ }
+ if (hc->conn_fd >= 0)
+ {
+ (void) close (hc->conn_fd);
+ hc->conn_fd = -1;
+ }
+}
+
+void httpd_destroy_conn (httpd_conn * hc)
+{
+ if (hc->initialized)
+ {
+ free ((void *) hc->read_buf);
+ free ((void *) hc->decodedurl);
+ free ((void *) hc->origfilename);
+ free ((void *) hc->expnfilename);
+ free ((void *) hc->encodings);
+ free ((void *) hc->pathinfo);
+ free ((void *) hc->query);
+ free ((void *) hc->accept);
+ free ((void *) hc->accepte);
+ free ((void *) hc->reqhost);
+ free ((void *) hc->hostdir);
+ free ((void *) hc->remoteuser);
+ free ((void *) hc->response);
+#ifdef TILDE_MAP_2
+ free ((void *) hc->altdir);
+#endif /* TILDE_MAP_2 */
+ hc->initialized = 0;
+ }
+}
+
+
+struct mime_entry
+{
+ char *ext;
+ size_t ext_len;
+ char *val;
+ size_t val_len;
+};
+static struct mime_entry enc_tab[] = {
+#include "mime_encodings.h"
+};
+
+static const int n_enc_tab = sizeof (enc_tab) / sizeof (*enc_tab);
+static struct mime_entry typ_tab[] = {
+#include "mime_types.h"
+};
+
+static const int n_typ_tab = sizeof (typ_tab) / sizeof (*typ_tab);
+
+
+/* qsort comparison routine - declared old-style on purpose, for portability. */
+static int ext_compare (a, b)
+ struct mime_entry *a;
+ struct mime_entry *b;
+{
+ return strcmp (a->ext, b->ext);
+}
+
+
+static void init_mime (void)
+{
+ int i;
+
+ /* Sort the tables so we can do binary search. */
+ qsort (enc_tab, n_enc_tab, sizeof (*enc_tab), ext_compare);
+ qsort (typ_tab, n_typ_tab, sizeof (*typ_tab), ext_compare);
+
+ /* Fill in the lengths. */
+ for (i = 0; i < n_enc_tab; ++i)
+ {
+ enc_tab[i].ext_len = strlen (enc_tab[i].ext);
+ enc_tab[i].val_len = strlen (enc_tab[i].val);
+ }
+ for (i = 0; i < n_typ_tab; ++i)
+ {
+ typ_tab[i].ext_len = strlen (typ_tab[i].ext);
+ typ_tab[i].val_len = strlen (typ_tab[i].val);
+ }
+
+}
+
+#if 0
+/* Figure out MIME encodings and type based on the filename. Multiple
+** encodings are separated by commas, and are listed in the order in
+** which they were applied to the file.
+*/
+static void figure_mime (httpd_conn * hc)
+{
+ char *prev_dot;
+ char *dot;
+ char *ext;
+ int me_indexes[100], n_me_indexes;
+ size_t ext_len, encodings_len;
+ int i, top, bot, mid;
+ int r;
+ char *default_type = "text/plain; charset=%s";
+
+ /* Peel off encoding extensions until there aren't any more. */
+ n_me_indexes = 0;
+ for (prev_dot = &hc->expnfilename[strlen (hc->expnfilename)];;
+ prev_dot = dot)
+ {
+ for (dot = prev_dot - 1; dot >= hc->expnfilename && *dot != '.'; --dot)
+ ;
+ if (dot < hc->expnfilename)
+ {
+ /* No dot found. No more encoding extensions, and no type
+ ** extension either.
+ */
+ hc->type = default_type;
+ goto done;
+ }
+ ext = dot + 1;
+ ext_len = prev_dot - ext;
+ /* Search the encodings table. Linear search is fine here, there
+ ** are only a few entries.
+ */
+ for (i = 0; i < n_enc_tab; ++i)
+ {
+ if (ext_len == enc_tab[i].ext_len
+ && strncasecmp (ext, enc_tab[i].ext, ext_len) == 0)
+ {
+ if (n_me_indexes < sizeof (me_indexes) / sizeof (*me_indexes))
+ {
+ me_indexes[n_me_indexes] = i;
+ ++n_me_indexes;
+ }
+ goto next;
+ }
+ }
+ /* No encoding extension found. Break and look for a type extension. */
+ break;
+
+ next:;
+ }
+
+ /* Binary search for a matching type extension. */
+ top = n_typ_tab - 1;
+ bot = 0;
+ while (top >= bot)
+ {
+ mid = (top + bot) / 2;
+ r = strncasecmp (ext, typ_tab[mid].ext, ext_len);
+ if (r < 0)
+ top = mid - 1;
+ else if (r > 0)
+ bot = mid + 1;
+ else if (ext_len < typ_tab[mid].ext_len)
+ top = mid - 1;
+ else if (ext_len > typ_tab[mid].ext_len)
+ bot = mid + 1;
+ else
+ {
+ hc->type = typ_tab[mid].val;
+ goto done;
+ }
+ }
+ hc->type = default_type;
+
+done:
+
+ /* The last thing we do is actually generate the mime-encoding header. */
+ hc->encodings[0] = '\0';
+ encodings_len = 0;
+ for (i = n_me_indexes - 1; i >= 0; --i)
+ {
+ httpd_realloc_str (&hc->encodings, &hc->maxencodings,
+ encodings_len + enc_tab[me_indexes[i]].val_len + 1);
+ if (hc->encodings[0] != '\0')
+ {
+ (void) strcpy (&hc->encodings[encodings_len], ",");
+ ++encodings_len;
+ }
+ (void) strcpy (&hc->encodings[encodings_len], enc_tab[me_indexes[i]].val);
+ encodings_len += enc_tab[me_indexes[i]].val_len;
+ }
+
+}
+#endif
+
+#ifdef CGI_TIMELIMIT
+static void cgi_kill2 (ClientData client_data, struct timeval *nowP)
+{
+ pid_t pid;
+
+ pid = (pid_t) client_data.i;
+ if (kill (pid, SIGKILL) == 0)
+ syslog (LOG_ERR, "hard-killed CGI process %d", pid);
+}
+
+static void cgi_kill (ClientData client_data, struct timeval *nowP)
+{
+ pid_t pid;
+
+ pid = (pid_t) client_data.i;
+ if (kill (pid, SIGINT) == 0)
+ {
+ syslog (LOG_ERR, "killed CGI process %d", pid);
+ /* In case this isn't enough, schedule an uncatchable kill. */
+ if (tmr_create (nowP, cgi_kill2, client_data, 5 * 1000L, 0) ==
+ (Timer *) 0)
+ {
+ syslog (LOG_CRIT, "tmr_create(cgi_kill2) failed");
+ exit (1);
+ }
+ }
+}
+#endif /* CGI_TIMELIMIT */
+
+
+#ifdef GENERATE_INDEXES
+
+/* qsort comparison routine - declared old-style on purpose, for portability. */
+static int name_compare (a, b)
+ char **a;
+ char **b;
+{
+ return strcmp (*a, *b);
+}
+
+#if 0
+static int ls (httpd_conn * hc)
+{
+ DIR *dirp;
+ struct dirent *de;
+ int namlen;
+ static int maxnames = 0;
+ int nnames;
+ static char *names;
+ static char **nameptrs;
+ static char *name;
+ static size_t maxname = 0;
+ static char *rname;
+ static size_t maxrname = 0;
+ static char *encrname;
+ static size_t maxencrname = 0;
+ FILE *fp;
+ int i, r;
+ struct stat sb;
+ struct stat lsb;
+ char modestr[20];
+ char *linkprefix;
+ char link[MAXPATHLEN + 1];
+ int linklen;
+ char *fileclass;
+ time_t now;
+ char *timestr;
+ ClientData client_data;
+
+ dirp = opendir (hc->expnfilename);
+ if (dirp == (DIR *) 0)
+ {
+ syslog (LOG_ERR, "opendir %.80s - %m", hc->expnfilename);
+ httpd_send_err (hc, 404, err404title, "", err404form, hc->encodedurl);
+ return -1;
+ }
+
+ if (hc->method == METHOD_HEAD)
+ {
+ closedir (dirp);
+ send_mime (hc, 200, ok200title, "", "", "text/html; charset=%s",
+ (off_t) - 1, hc->sb.st_mtime);
+ }
+ else if (hc->method == METHOD_GET)
+ {
+ if (hc->hs->cgi_limit != 0 && hc->hs->cgi_count >= hc->hs->cgi_limit)
+ {
+ closedir (dirp);
+ httpd_send_err (hc, 503, httpd_err503title, "", httpd_err503form,
+ hc->encodedurl);
+ return -1;
+ }
+ ++hc->hs->cgi_count;
+ r = fork ();
+ if (r < 0)
+ {
+ syslog (LOG_ERR, "fork - %m");
+ closedir (dirp);
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ return -1;
+ }
+ if (r == 0)
+ {
+ /* Child process. */
+ sub_process = 1;
+ httpd_unlisten (hc->hs);
+ send_mime (hc, 200, ok200title, "", "", "text/html; charset=%s",
+ (off_t) - 1, hc->sb.st_mtime);
+ httpd_write_response (hc);
+
+#ifdef CGI_NICE
+ /* Set priority. */
+ (void) nice (CGI_NICE);
+#endif /* CGI_NICE */
+
+ /* Open a stdio stream so that we can use fprintf, which is more
+ ** efficient than a bunch of separate write()s. We don't have
+ ** to worry about double closes or file descriptor leaks cause
+ ** we're in a subprocess.
+ */
+ fp = fdopen (hc->conn_fd, "w");
+ if (fp == (FILE *) 0)
+ {
+ syslog (LOG_ERR, "fdopen - %m");
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ httpd_write_response (hc);
+ closedir (dirp);
+ exit (1);
+ }
+
+ (void) fprintf (fp, "\
+\n\
+Index of %.80s\n\
+\n\
+Index of %.80s
\n\
+\n\
+mode links bytes last-changed name\n\
+
", hc->encodedurl, hc->encodedurl);
+
+ /* Read in names. */
+ nnames = 0;
+ while ((de = readdir (dirp)) != 0) /* dirent or direct */
+ {
+ if (nnames >= maxnames)
+ {
+ if (maxnames == 0)
+ {
+ maxnames = 100;
+ names = NEW (char, maxnames * (MAXPATHLEN + 1));
+ nameptrs = NEW (char *, maxnames);
+ }
+ else
+ {
+ maxnames *= 2;
+ names = RENEW (names, char, maxnames * (MAXPATHLEN + 1));
+ nameptrs = RENEW (nameptrs, char *, maxnames);
+ }
+ if (names == (char *) 0 || nameptrs == (char **) 0)
+ {
+ syslog (LOG_ERR, "out of memory reallocating directory names");
+ exit (1);
+ }
+ for (i = 0; i < maxnames; ++i)
+ nameptrs[i] = &names[i * (MAXPATHLEN + 1)];
+ }
+ namlen = NAMLEN (de);
+ (void) strncpy (nameptrs[nnames], de->d_name, namlen);
+ nameptrs[nnames][namlen] = '\0';
+ ++nnames;
+ }
+ closedir (dirp);
+
+ /* Sort the names. */
+ qsort (nameptrs, nnames, sizeof (*nameptrs), name_compare);
+
+ /* Generate output. */
+ for (i = 0; i < nnames; ++i)
+ {
+ httpd_realloc_str (&name, &maxname,
+ strlen (hc->expnfilename) + 1 +
+ strlen (nameptrs[i]));
+ httpd_realloc_str (&rname, &maxrname,
+ strlen (hc->origfilename) + 1 +
+ strlen (nameptrs[i]));
+ if (hc->expnfilename[0] == '\0'
+ || strcmp (hc->expnfilename, ".") == 0)
+ {
+ (void) strcpy (name, nameptrs[i]);
+ (void) strcpy (rname, nameptrs[i]);
+ }
+ else
+ {
+ (void) my_snprintf (name, maxname,
+ "%s/%s", hc->expnfilename, nameptrs[i]);
+ if (strcmp (hc->origfilename, ".") == 0)
+ (void) my_snprintf (rname, maxrname, "%s", nameptrs[i]);
+ else
+ (void) my_snprintf (rname, maxrname,
+ "%s%s", hc->origfilename, nameptrs[i]);
+ }
+ httpd_realloc_str (&encrname, &maxencrname, 3 * strlen (rname) + 1);
+ strencode (encrname, maxencrname, rname);
+
+ if (stat (name, &sb) < 0 || lstat (name, &lsb) < 0)
+ continue;
+
+ linkprefix = "";
+ link[0] = '\0';
+ /* Break down mode word. First the file type. */
+ switch (lsb.st_mode & S_IFMT)
+ {
+ case S_IFIFO:
+ modestr[0] = 'p';
+ break;
+ case S_IFCHR:
+ modestr[0] = 'c';
+ break;
+ case S_IFDIR:
+ modestr[0] = 'd';
+ break;
+ case S_IFBLK:
+ modestr[0] = 'b';
+ break;
+ case S_IFREG:
+ modestr[0] = '-';
+ break;
+ case S_IFSOCK:
+ modestr[0] = 's';
+ break;
+ case S_IFLNK:
+ modestr[0] = 'l';
+ linklen = readlink (name, link, sizeof (link) - 1);
+ if (linklen != -1)
+ {
+ link[linklen] = '\0';
+ linkprefix = " -> ";
+ }
+ break;
+ default:
+ modestr[0] = '?';
+ break;
+ }
+ /* Now the world permissions. Owner and group permissions
+ ** are not of interest to web clients.
+ */
+ modestr[1] = (lsb.st_mode & S_IROTH) ? 'r' : '-';
+ modestr[2] = (lsb.st_mode & S_IWOTH) ? 'w' : '-';
+ modestr[3] = (lsb.st_mode & S_IXOTH) ? 'x' : '-';
+ modestr[4] = '\0';
+
+ /* We also leave out the owner and group name, they are
+ ** also not of interest to web clients. Plus if we're
+ ** running under chroot(), they would require a copy
+ ** of /etc/passwd and /etc/group, which we want to avoid.
+ */
+
+ /* Get time string. */
+ now = time ((time_t *) 0);
+ timestr = ctime (&lsb.st_mtime);
+ timestr[0] = timestr[4];
+ timestr[1] = timestr[5];
+ timestr[2] = timestr[6];
+ timestr[3] = ' ';
+ timestr[4] = timestr[8];
+ timestr[5] = timestr[9];
+ timestr[6] = ' ';
+ if (now - lsb.st_mtime > 60 * 60 * 24 * 182) /* 1/2 year */
+ {
+ timestr[7] = ' ';
+ timestr[8] = timestr[20];
+ timestr[9] = timestr[21];
+ timestr[10] = timestr[22];
+ timestr[11] = timestr[23];
+ }
+ else
+ {
+ timestr[7] = timestr[11];
+ timestr[8] = timestr[12];
+ timestr[9] = ':';
+ timestr[10] = timestr[14];
+ timestr[11] = timestr[15];
+ }
+ timestr[12] = '\0';
+
+ /* The ls -F file class. */
+ switch (sb.st_mode & S_IFMT)
+ {
+ case S_IFDIR:
+ fileclass = "/";
+ break;
+ case S_IFSOCK:
+ fileclass = "=";
+ break;
+ case S_IFLNK:
+ fileclass = "@";
+ break;
+ default:
+ fileclass = (sb.st_mode & S_IXOTH) ? "*" : "";
+ break;
+ }
+
+ /* And print. */
+ (void) fprintf (fp,
+ "%s %3ld %10ld %s %.80s%s%s%s\n",
+ modestr, (long) lsb.st_nlink, (int64_t) lsb.st_size,
+ timestr, encrname, S_ISDIR (sb.st_mode) ? "/" : "",
+ nameptrs[i], linkprefix, link, fileclass);
+ }
+
+ (void) fprintf (fp, "
\n\n");
+ (void) fclose (fp);
+ exit (0);
+ }
+
+ /* Parent process. */
+ closedir (dirp);
+ syslog (LOG_INFO, "spawned indexing process %d for directory '%.200s'", r,
+ hc->expnfilename);
+#ifdef CGI_TIMELIMIT
+ /* Schedule a kill for the child process, in case it runs too long */
+ if (hc->hs->cgi_timelimit)
+ {
+ client_data.i = r;
+ if (tmr_create
+ ((struct timeval *) 0, cgi_kill, client_data,
+ hc->hs->cgi_timelimit * 1000L, 0) == (Timer *) 0)
+ {
+ syslog (LOG_CRIT, "tmr_create(cgi_kill ls) failed");
+ exit (1);
+ }
+ }
+#endif /* CGI_TIMELIMIT */
+ hc->status = 200;
+ hc->bytes_sent = CGI_BYTECOUNT;
+ hc->should_linger = 0;
+ }
+ else
+ {
+ closedir (dirp);
+ httpd_send_err (hc, 501, err501title, "", err501form,
+ httpd_method_str (hc->method));
+ return -1;
+ }
+
+ return 0;
+}
+#endif
+
+#endif /* GENERATE_INDEXES */
+
+
+static char *build_env (char *fmt, char *arg)
+{
+ char *cp;
+ size_t size;
+ static char *buf;
+ static size_t maxbuf = 0;
+
+ size = strlen (fmt) + strlen (arg);
+ if (size > maxbuf)
+ httpd_realloc_str (&buf, &maxbuf, size);
+
+ (void) my_snprintf (buf, maxbuf, fmt, arg);
+ cp = strdup (buf);
+ if (cp == (char *) 0)
+ {
+ syslog (LOG_ERR, "out of memory copying environment variable");
+ exit (1);
+ }
+ //fprintf(stderr, "build_env: %s\n", cp);
+ return cp;
+}
+
+
+#ifdef SERVER_NAME_LIST
+static char *hostname_map (char *hostname)
+{
+ int len, n;
+ static char *list[] = { SERVER_NAME_LIST };
+
+ len = strlen (hostname);
+ for (n = sizeof (list) / sizeof (*list) - 1; n >= 0; --n)
+ if (strncasecmp (hostname, list[n], len) == 0)
+ if (list[n][len] == '/') /* check in case of a substring match */
+ return &list[n][len + 1];
+ return (char *) 0;
+}
+#endif /* SERVER_NAME_LIST */
+
+
+/* Set up environment variables. Be real careful here to avoid
+** letting malicious clients overrun a buffer. We don't have
+** to worry about freeing stuff since we're a sub-process.
+*/
+static char **make_envp (httpd_conn * hc)
+{
+ static char *envp[50];
+ int envn;
+ char *cp;
+ char buf[256];
+
+ envn = 0;
+ envp[envn++] = build_env ("PATH=%s", CGI_PATH);
+#ifdef CGI_LD_LIBRARY_PATH
+ envp[envn++] = build_env ("LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH);
+#endif /* CGI_LD_LIBRARY_PATH */
+ envp[envn++] = build_env ("SERVER_SOFTWARE=%s", SERVER_SOFTWARE);
+ /* If vhosting, use that server-name here. */
+ if (hc->hs->vhost && hc->hostname != (char *) 0)
+ cp = hc->hostname;
+ else
+ cp = hc->hs->server_hostname;
+ if (cp != (char *) 0)
+ envp[envn++] = build_env ("SERVER_NAME=%s", cp);
+ envp[envn++] = "GATEWAY_INTERFACE=CGI/1.1";
+ envp[envn++] = build_env ("SERVER_PROTOCOL=%s", hc->protocol);
+ (void) my_snprintf (buf, sizeof (buf), "%d", (int) hc->hs->port);
+ envp[envn++] = build_env ("SERVER_PORT=%s", buf);
+ envp[envn++] =
+ build_env ("REQUEST_METHOD=%s", httpd_method_str (hc->method));
+
+ /*if ( hc->pathinfo[0] != '\0' )
+ {
+ char* cp2;
+ size_t l;
+ envp[envn++] = build_env( "PATH_INFO=/%s", hc->pathinfo );
+ l = strlen( hc->hs->cwd ) + strlen( hc->pathinfo ) + 1;
+ cp2 = NEW( char, l );
+ if ( cp2 != (char*) 0 )
+ {
+ (void) my_snprintf( cp2, l, "%s%s", hc->hs->cwd, hc->pathinfo );
+ envp[envn++] = build_env( "PATH_TRANSLATED=%s", cp2 );
+ }
+ } */
+
+ //envp[envn++] = build_env("SCRIPT_NAME=/%s", strcmp( hc->origfilename, "." ) == 0 ? "" : hc->origfilename );
+
+ envp[envn++] = build_env ("PATH_INFO=/%s", hc->expnfilename);
+ //envp[envn++] = build_env( "SCRIPT_NAME=%s", "");
+
+ if (hc->query[0] != '\0')
+ envp[envn++] = build_env ("QUERY_STRING=%s", hc->query);
+ envp[envn++] = build_env ("REMOTE_ADDR=%s", httpd_ntoa (&hc->client_addr));
+ if (hc->referer[0] != '\0')
+ envp[envn++] = build_env ("HTTP_REFERER=%s", hc->referer);
+ if (hc->useragent[0] != '\0')
+ envp[envn++] = build_env ("HTTP_USER_AGENT=%s", hc->useragent);
+ if (hc->accept[0] != '\0')
+ envp[envn++] = build_env ("HTTP_ACCEPT=%s", hc->accept);
+ if (hc->accepte[0] != '\0')
+ envp[envn++] = build_env ("HTTP_ACCEPT_ENCODING=%s", hc->accepte);
+ if (hc->acceptl[0] != '\0')
+ envp[envn++] = build_env ("HTTP_ACCEPT_LANGUAGE=%s", hc->acceptl);
+ if (hc->cookie[0] != '\0')
+ envp[envn++] = build_env ("HTTP_COOKIE=%s", hc->cookie);
+ if (hc->contenttype[0] != '\0')
+ envp[envn++] = build_env ("CONTENT_TYPE=%s", hc->contenttype);
+ if (hc->hdrhost[0] != '\0')
+ envp[envn++] = build_env ("HTTP_HOST=%s", hc->hdrhost);
+
+ if (hc->contentlength != -1)
+ {
+ (void) my_snprintf (buf, sizeof (buf), "%lu",
+ (unsigned long) hc->contentlength);
+ envp[envn++] = build_env ("CONTENT_LENGTH=%s", buf);
+ }
+ if (hc->remoteuser[0] != '\0')
+ envp[envn++] = build_env ("REMOTE_USER=%s", hc->remoteuser);
+ if (hc->authorization[0] != '\0')
+ envp[envn++] = build_env ("AUTH_TYPE=%s", "Basic");
+
+ /* We only support Basic auth at the moment. */
+ if (getenv ("TZ") != (char *) 0)
+ envp[envn++] = build_env ("TZ=%s", getenv ("TZ"));
+
+ //fprintf(stderr, "make_envp #2.4: %d %p\n", getpid(), hc->hs->cgi_pattern);
+ //envp[envn++] = build_env( "CGI_PATTERN=%s", hc->hs->cgi_pattern );
+
+#ifdef X_CGI_HEADER
+ //if (hc->xcgi[0])
+ envp[envn++] = build_env ("X_CGI=%s", hc->xcgi);
+ if (hc->if_modified_since != (time_t) - 1)
+ {
+ my_snprintf (buf, sizeof (buf), "%lld",
+ (int64_t) (hc->if_modified_since));
+ envp[envn++] = build_env ("HTTP_IF_MODIFIED_SINCE=%s", buf);
+ }
+#endif
+
+ envp[envn] = (char *) 0;
+ return envp;
+}
+
+
+/* Set up argument vector. Again, we don't have to worry about freeing stuff
+** since we're a sub-process. This gets done after make_envp() because we
+** scribble on hc->query.
+*/
+static char **make_argp (httpd_conn * hc)
+{
+ char **argp;
+ int argn;
+ char *cp1;
+ char *cp2;
+
+ /* By allocating an arg slot for every character in the query, plus
+ ** one for the filename and one for the NULL, we are guaranteed to
+ ** have enough. We could actually use strlen/2.
+ */
+ argp = NEW (char *, strlen (hc->query) + 2);
+ if (argp == (char **) 0)
+ return (char **) 0;
+
+ argp[0] = strrchr (hc->expnfilename, '/');
+ if (argp[0] != (char *) 0)
+ ++argp[0];
+ else
+ argp[0] = hc->expnfilename;
+
+ argn = 1;
+ /* According to the CGI spec at http://hoohoo.ncsa.uiuc.edu/cgi/cl.html,
+ ** "The server should search the query information for a non-encoded =
+ ** character to determine if the command line is to be used, if it finds
+ ** one, the command line is not to be used."
+ */
+ if (strchr (hc->query, '=') == (char *) 0)
+ {
+ for (cp1 = cp2 = hc->query; *cp2 != '\0'; ++cp2)
+ {
+ if (*cp2 == '+')
+ {
+ *cp2 = '\0';
+ strdecode (cp1, cp1);
+ argp[argn++] = cp1;
+ cp1 = cp2 + 1;
+ }
+ }
+ if (cp2 != cp1)
+ {
+ strdecode (cp1, cp1);
+ argp[argn++] = cp1;
+ }
+ }
+
+ argp[argn] = (char *) 0;
+ return argp;
+}
+
+
+/* This routine is used only for POST requests. It reads the data
+** from the request and sends it to the child process. The only reason
+** we need to do it this way instead of just letting the child read
+** directly is that we have already read part of the data into our
+** buffer.
+*/
+static void cgi_interpose_input (httpd_conn * hc, int wfd)
+{
+ size_t c;
+ ssize_t r;
+ char buf[1024];
+
+ c = hc->read_idx - hc->checked_idx;
+ if (c > 0)
+ {
+ if (httpd_write_fully (wfd, &(hc->read_buf[hc->checked_idx]), c) != c)
+ return;
+ }
+ while (c < hc->contentlength)
+ {
+ r = read (hc->conn_fd, buf, MIN (sizeof (buf), hc->contentlength - c));
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ {
+ sleep (1);
+ continue;
+ }
+ if (r <= 0)
+ return;
+ if (httpd_write_fully (wfd, buf, r) != r)
+ return;
+ c += r;
+ }
+ post_post_garbage_hack (hc);
+}
+
+
+/* Special hack to deal with broken browsers that send a LF or CRLF
+** after POST data, causing TCP resets - we just read and discard up
+** to 2 bytes. Unfortunately this doesn't fix the problem for CGIs
+** which avoid the interposer process due to their POST data being
+** short. Creating an interposer process for all POST CGIs is
+** unacceptably expensive. The eventual fix will come when interposing
+** gets integrated into the main loop as a tasklet instead of a process.
+*/
+static void post_post_garbage_hack (httpd_conn * hc)
+{
+ char buf[2];
+
+ /* If we are in a sub-process, turn on no-delay mode in case we
+ ** previously cleared it.
+ */
+ if (sub_process)
+ httpd_set_ndelay (hc->conn_fd);
+ /* And read up to 2 bytes. */
+ (void) read (hc->conn_fd, buf, sizeof (buf));
+}
+
+
+/* This routine is used for parsed-header CGIs. The idea here is that the
+** CGI can return special headers such as "Status:" and "Location:" which
+** change the return status of the response. Since the return status has to
+** be the very first line written out, we have to accumulate all the headers
+** and check for the special ones before writing the status. Then we write
+** out the saved headers and proceed to echo the rest of the response.
+*/
+static void cgi_interpose_output (httpd_conn * hc, int rfd)
+{
+ int r;
+ char buf[1024];
+ size_t headers_size, headers_len;
+ char *headers;
+ char *br;
+ int status;
+ char *title;
+ char *cp;
+
+ /* Make sure the connection is in blocking mode. It should already
+ ** be blocking, but we might as well be sure.
+ */
+ httpd_clear_ndelay (hc->conn_fd);
+
+ /* Slurp in all headers. */
+ headers_size = 0;
+ httpd_realloc_str (&headers, &headers_size, 500);
+ headers_len = 0;
+ for (;;)
+ {
+ r = read (rfd, buf, sizeof (buf));
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ {
+ sleep (1);
+ continue;
+ }
+ if (r <= 0)
+ {
+ br = &(headers[headers_len]);
+ break;
+ }
+ httpd_realloc_str (&headers, &headers_size, headers_len + r);
+ (void) memmove (&(headers[headers_len]), buf, r);
+ headers_len += r;
+ headers[headers_len] = '\0';
+ if ((br = strstr (headers, "\015\012\015\012")) != (char *) 0 ||
+ (br = strstr (headers, "\012\012")) != (char *) 0)
+ break;
+ }
+
+ /* If there were no headers, bail. */
+ if (headers[0] == '\0')
+ return;
+
+ /* Figure out the status. Look for a Status: or Location: header;
+ ** else if there's an HTTP header line, get it from there; else
+ ** default to 200.
+ */
+ status = 200;
+ if (strncmp (headers, "HTTP/", 5) == 0)
+ {
+ cp = headers;
+ cp += strcspn (cp, " \t");
+ status = atoi (cp);
+ }
+ if ((cp = strstr (headers, "Status:")) != (char *) 0 &&
+ cp < br && (cp == headers || *(cp - 1) == '\012'))
+ {
+ cp += 7;
+ cp += strspn (cp, " \t");
+ status = atoi (cp);
+ }
+ if ((cp = strstr (headers, "Location:")) != (char *) 0 &&
+ cp < br && (cp == headers || *(cp - 1) == '\012'))
+ status = 302;
+
+ /* Write the status line. */
+ switch (status)
+ {
+ case 200:
+ title = ok200title;
+ break;
+ case 302:
+ title = err302title;
+ break;
+ case 304:
+ title = err304title;
+ break;
+ case 400:
+ title = httpd_err400title;
+ break;
+#ifdef AUTH_FILE
+ case 401:
+ title = err401title;
+ break;
+#endif /* AUTH_FILE */
+ case 403:
+ title = err403title;
+ break;
+ case 404:
+ title = err404title;
+ break;
+ case 408:
+ title = httpd_err408title;
+ break;
+ case 500:
+ title = err500title;
+ break;
+ case 501:
+ title = err501title;
+ break;
+ case 503:
+ title = httpd_err503title;
+ break;
+ default:
+ title = "Something";
+ break;
+ }
+ (void) my_snprintf (buf, sizeof (buf), "HTTP/1.0 %d %s\015\012", status,
+ title);
+ (void) httpd_write_fully (hc->conn_fd, buf, strlen (buf));
+
+ /* Write the saved headers. */
+ (void) httpd_write_fully (hc->conn_fd, headers, headers_len);
+
+ /* Echo the rest of the output. */
+ for (;;)
+ {
+ r = read (rfd, buf, sizeof (buf));
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ {
+ sleep (1);
+ continue;
+ }
+ if (r <= 0)
+ break;
+ if (httpd_write_fully (hc->conn_fd, buf, r) != r)
+ break;
+ }
+ shutdown (hc->conn_fd, SHUT_WR);
+}
+
+
+/* CGI child process. */
+static void cgi_child (httpd_conn * hc)
+{
+ int r;
+ char **argp;
+ char **envp;
+ //char* binary;
+ //char* directory;
+
+ /* Unset close-on-exec flag for this socket. This actually shouldn't
+ ** be necessary, according to POSIX a dup()'d file descriptor does
+ ** *not* inherit the close-on-exec flag, its flag is always clear.
+ ** However, Linux messes this up and does copy the flag to the
+ ** dup()'d descriptor, so we have to clear it. This could be
+ ** ifdeffed for Linux only.
+ */
+ (void) fcntl (hc->conn_fd, F_SETFD, 0);
+
+ /* Close the syslog descriptor so that the CGI program can't
+ ** mess with it. All other open descriptors should be either
+ ** the listen socket(s), sockets from accept(), or the file-logging
+ ** fd, and all of those are set to close-on-exec, so we don't
+ ** have to close anything else.
+ */
+ closelog ();
+
+ /* If the socket happens to be using one of the stdin/stdout/stderr
+ ** descriptors, move it to another descriptor so that the dup2 calls
+ ** below don't screw things up. We arbitrarily pick fd 3 - if there
+ ** was already something on it, we clobber it, but that doesn't matter
+ ** since at this point the only fd of interest is the connection.
+ ** All others will be closed on exec.
+ */
+ if (hc->conn_fd == STDIN_FILENO || hc->conn_fd == STDOUT_FILENO
+ || hc->conn_fd == STDERR_FILENO)
+ {
+ int newfd = dup2 (hc->conn_fd, STDERR_FILENO + 1);
+ if (newfd >= 0)
+ hc->conn_fd = newfd;
+ /* If the dup2 fails, shrug. We'll just take our chances.
+ ** Shouldn't happen though.
+ */
+ }
+
+ /* Make the environment vector. */
+ envp = make_envp (hc);
+
+ /* Make the argument vector. */
+ argp = make_argp (hc);
+
+ /* Set up stdin. For POSTs we may have to set up a pipe from an
+ ** interposer process, depending on if we've read some of the data
+ ** into our buffer.
+ */
+ if (hc->method == METHOD_POST && hc->read_idx > hc->checked_idx)
+ {
+ int p[2];
+
+ if (pipe (p) < 0)
+ {
+ syslog (LOG_ERR, "pipe - %m");
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ httpd_write_response (hc);
+ exit (1);
+ }
+
+ r = fork ();
+
+ if (r < 0)
+ {
+ syslog (LOG_ERR, "fork - %m");
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ httpd_write_response (hc);
+ exit (1);
+ }
+
+ if (r == 0)
+ {
+ /* Interposer process. */
+ sub_process = 1;
+ (void) close (p[0]);
+ cgi_interpose_input (hc, p[1]);
+ exit (0);
+ }
+
+ /* Need to schedule a kill for process r; but in the main process! */
+ (void) close (p[1]);
+ if (p[0] != STDIN_FILENO)
+ {
+ (void) dup2 (p[0], STDIN_FILENO);
+ (void) close (p[0]);
+ }
+ }
+ else
+ {
+ /* Otherwise, the request socket is stdin. */
+ if (hc->conn_fd != STDIN_FILENO)
+ (void) dup2 (hc->conn_fd, STDIN_FILENO);
+ }
+
+ /* Set up stdout/stderr. If we're doing CGI header parsing,
+ ** we need an output interposer too.
+ */
+ if (strncmp (argp[0], "nph-", 4) != 0 && hc->mime_flag)
+ {
+ int p[2];
+
+ if (pipe (p) < 0)
+ {
+ syslog (LOG_ERR, "pipe - %m");
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ httpd_write_response (hc);
+ exit (1);
+ }
+ r = fork ();
+ if (r < 0)
+ {
+ syslog (LOG_ERR, "fork - %m");
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ httpd_write_response (hc);
+ exit (1);
+ }
+ if (r == 0)
+ {
+ /* Interposer process. */
+ sub_process = 1;
+ (void) close (p[1]);
+ cgi_interpose_output (hc, p[0]);
+ exit (0);
+ }
+ /* Need to schedule a kill for process r; but in the main process! */
+ (void) close (p[0]);
+ if (p[1] != STDOUT_FILENO)
+ (void) dup2 (p[1], STDOUT_FILENO);
+ if (p[1] != STDERR_FILENO)
+ (void) dup2 (p[1], STDERR_FILENO);
+ if (p[1] != STDOUT_FILENO && p[1] != STDERR_FILENO)
+ (void) close (p[1]);
+ }
+ else
+ {
+ /* Otherwise, the request socket is stdout/stderr. */
+ if (hc->conn_fd != STDOUT_FILENO)
+ (void) dup2 (hc->conn_fd, STDOUT_FILENO);
+ if (hc->conn_fd != STDERR_FILENO)
+ (void) dup2 (hc->conn_fd, STDERR_FILENO);
+ }
+
+ /* At this point we would like to set close-on-exec again for hc->conn_fd
+ ** (see previous comments on Linux's broken behavior re: close-on-exec
+ ** and dup.) Unfortunately there seems to be another Linux problem, or
+ ** perhaps a different aspect of the same problem - if we do this
+ ** close-on-exec in Linux, the socket stays open but stderr gets
+ ** closed - the last fd duped from the socket. What a mess. So we'll
+ ** just leave the socket as is, which under other OSs means an extra
+ ** file descriptor gets passed to the child process. Since the child
+ ** probably already has that file open via stdin stdout and/or stderr,
+ ** this is not a problem.
+ */
+ /* (void) fcntl( hc->conn_fd, F_SETFD, 1 ); */
+
+#ifdef CGI_NICE
+ /* Set priority. */
+ (void) nice (CGI_NICE);
+#endif /* CGI_NICE */
+
+#if 0
+ /* Split the program into directory and binary, so we can chdir()
+ ** to the program's own directory. This isn't in the CGI 1.1
+ ** spec, but it's what other HTTP servers do.
+ */
+ directory = strdup (hc->expnfilename);
+ if (directory == (char *) 0)
+ binary = hc->expnfilename; /* ignore errors */
+ else
+ {
+ binary = strrchr (directory, '/');
+ if (binary == (char *) 0)
+ binary = hc->expnfilename;
+ else
+ {
+ *binary++ = '\0';
+ (void) chdir (directory); /* ignore errors */
+ }
+ }
+#endif
+
+ // Reset signal handlers
+
+ (void) signal (SIGTERM, SIG_DFL);
+ (void) signal (SIGINT, SIG_DFL);
+ (void) signal (SIGCHLD, SIG_DFL);
+ (void) signal (SIGPIPE, SIG_DFL);
+ (void) signal (SIGHUP, SIG_DFL);
+ (void) signal (SIGUSR1, SIG_DFL);
+ (void) signal (SIGUSR2, SIG_DFL);
+ (void) signal (SIGALRM, SIG_DFL);
+
+#if 0
+ /* Run the program. */
+ (void) execve (binary, argp, envp);
+
+ /* Something went wrong. */
+ syslog (LOG_ERR, "execve %.80s - %m", hc->expnfilename);
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ httpd_write_response (hc);
+ exit (1);
+#endif
+
+ environ = envp;
+ run_cgi ();
+
+}
+
+
+static int cgi (httpd_conn * hc)
+{
+ int r;
+ ClientData client_data;
+
+ if (hc->method == METHOD_GET || hc->method == METHOD_POST)
+ {
+ if (hc->hs->cgi_limit != 0 && hc->hs->cgi_count >= hc->hs->cgi_limit)
+ {
+ httpd_send_err (hc, 503, httpd_err503title, "", httpd_err503form,
+ hc->encodedurl);
+ return -1;
+ }
+
+ ++hc->hs->cgi_count;
+ httpd_clear_ndelay (hc->conn_fd);
+ r = fork ();
+ if (r < 0)
+ {
+ syslog (LOG_ERR, "fork - %m");
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ return -1;
+ }
+
+ if (r == 0)
+ {
+ /* Child process. */
+ sub_process = 1;
+ httpd_unlisten (hc->hs);
+ cgi_child (hc);
+ }
+ else
+ {
+ /* Parent process. */
+ syslog (LOG_INFO, "spawned CGI process %d for path '%.200s'", r,
+ hc->expnfilename);
+#ifdef CGI_TIMELIMIT
+ /* Schedule a kill for the child process, in case it runs too long */
+ if (hc->hs->cgi_timelimit)
+ {
+ client_data.i = r;
+ if (tmr_create
+ ((struct timeval *) 0, cgi_kill, client_data,
+ hc->hs->cgi_timelimit * 1000L, 0) == (Timer *) 0)
+ {
+ syslog (LOG_CRIT, "tmr_create(cgi_kill child) failed");
+ exit (1);
+ }
+ }
+#endif /* CGI_TIMELIMIT */
+ hc->status = 200;
+ hc->bytes_sent = CGI_BYTECOUNT;
+ hc->should_linger = 0;
+ }
+ }
+ else
+ {
+ httpd_send_err (hc, 501, err501title, "", err501form,
+ httpd_method_str (hc->method));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int really_start_request (httpd_conn * hc, struct timeval *nowP)
+{
+ //static char *indexname;
+ //static size_t maxindexname = 0;
+ //static const char *index_names[] = { INDEX_NAMES };
+ //int i;
+#ifdef AUTH_FILE
+ //static char *dirname;
+ //static size_t maxdirname = 0;
+#endif /* AUTH_FILE */
+ //size_t expnlen, indxlen;
+ //char *cp;
+ //char *pi;
+
+ //expnlen = strlen (hc->expnfilename);
+
+ if (hc->method != METHOD_GET && hc->method != METHOD_HEAD &&
+ hc->method != METHOD_POST)
+ {
+ httpd_send_err (hc, 501, err501title, "", err501form,
+ httpd_method_str (hc->method));
+ return -1;
+ }
+
+ //--------------------------------------------------------------
+
+ /* Referer check. */
+ if (!check_referer (hc))
+ return -1;
+
+ /* Is it world-executable and in the CGI area? */
+ //if ( hc->hs->cgi_pattern != (char*) 0 && ( hc->sb.st_mode & S_IXOTH ) && match( hc->hs->cgi_pattern, hc->expnfilename ) )
+ return cgi (hc);
+
+ //--------------------------------------------------------------
+
+#if 0
+ /* Stat the file. */
+ if (stat (hc->expnfilename, &hc->sb) < 0)
+ {
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ return -1;
+ }
+
+ /* Is it world-readable or world-executable? We check explicitly instead
+ ** of just trying to open it, so that no one ever gets surprised by
+ ** a file that's not set world-readable and yet somehow is
+ ** readable by the HTTP server and therefore the *whole* world.
+ */
+ if (!(hc->sb.st_mode & (S_IROTH | S_IXOTH)))
+ {
+ syslog (LOG_INFO,
+ "%.80s URL \"%.80s\" resolves to a non world-readable file",
+ httpd_ntoa (&hc->client_addr), hc->encodedurl);
+ httpd_send_err (hc, 403, err403title, "",
+ ERROR_FORM (err403form,
+ "The requested URL '%.80s' resolves to a file that is not world-readable.\n"),
+ hc->encodedurl);
+ return -1;
+ }
+
+ /* Is it a directory? */
+ if (S_ISDIR (hc->sb.st_mode))
+ {
+ /* If there's pathinfo, it's just a non-existent file. */
+ if (hc->pathinfo[0] != '\0')
+ {
+ httpd_send_err (hc, 404, err404title, "", err404form, hc->encodedurl);
+ return -1;
+ }
+
+ /* Special handling for directory URLs that don't end in a slash.
+ ** We send back an explicit redirect with the slash, because
+ ** otherwise many clients can't build relative URLs properly.
+ */
+ if (strcmp (hc->origfilename, "") != 0 &&
+ strcmp (hc->origfilename, ".") != 0 &&
+ hc->origfilename[strlen (hc->origfilename) - 1] != '/')
+ {
+ send_dirredirect (hc);
+ return -1;
+ }
+
+ /* Check for an index file. */
+ for (i = 0; i < sizeof (index_names) / sizeof (char *); ++i)
+ {
+ httpd_realloc_str (&indexname, &maxindexname,
+ expnlen + 1 + strlen (index_names[i]));
+ (void) strcpy (indexname, hc->expnfilename);
+ indxlen = strlen (indexname);
+ if (indxlen == 0 || indexname[indxlen - 1] != '/')
+ (void) strcat (indexname, "/");
+ if (strcmp (indexname, "./") == 0)
+ indexname[0] = '\0';
+ (void) strcat (indexname, index_names[i]);
+ if (stat (indexname, &hc->sb) >= 0)
+ goto got_one;
+ }
+
+ /* Nope, no index file, so it's an actual directory request. */
+#ifdef GENERATE_INDEXES
+ /* Directories must be readable for indexing. */
+ if (!(hc->sb.st_mode & S_IROTH))
+ {
+ syslog (LOG_INFO,
+ "%.80s URL \"%.80s\" tried to index a directory with indexing disabled",
+ httpd_ntoa (&hc->client_addr), hc->encodedurl);
+ httpd_send_err (hc, 403, err403title, "",
+ ERROR_FORM (err403form,
+ "The requested URL '%.80s' resolves to a directory that has indexing disabled.\n"),
+ hc->encodedurl);
+ return -1;
+ }
+#ifdef AUTH_FILE
+ /* Check authorization for this directory. */
+ if (auth_check (hc, hc->expnfilename) == -1)
+ return -1;
+#endif /* AUTH_FILE */
+ /* Referer check. */
+ if (!check_referer (hc))
+ return -1;
+ /* Ok, generate an index. */
+ return ls (hc);
+#else /* GENERATE_INDEXES */
+ syslog (LOG_INFO, "%.80s URL \"%.80s\" tried to index a directory",
+ httpd_ntoa (&hc->client_addr), hc->encodedurl);
+ httpd_send_err (hc, 403, err403title, "",
+ ERROR_FORM (err403form,
+ "The requested URL '%.80s' is a directory, and directory indexing is disabled on this server.\n"),
+ hc->encodedurl);
+ return -1;
+#endif /* GENERATE_INDEXES */
+
+ got_one:;
+ /* Got an index file. Expand symlinks again. More pathinfo means
+ ** something went wrong.
+ */
+ cp =
+ expand_symlinks (indexname, &pi, hc->hs->no_symlink_check,
+ hc->tildemapped);
+ if (cp == (char *) 0 || pi[0] != '\0')
+ {
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ return -1;
+ }
+ expnlen = strlen (cp);
+ httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename, expnlen);
+ (void) strcpy (hc->expnfilename, cp);
+
+ /* Now, is the index version world-readable or world-executable? */
+ if (!(hc->sb.st_mode & (S_IROTH | S_IXOTH)))
+ {
+ syslog (LOG_INFO,
+ "%.80s URL \"%.80s\" resolves to a non-world-readable index file",
+ httpd_ntoa (&hc->client_addr), hc->encodedurl);
+ httpd_send_err (hc, 403, err403title, "",
+ ERROR_FORM (err403form,
+ "The requested URL '%.80s' resolves to an index file that is not world-readable.\n"),
+ hc->encodedurl);
+ return -1;
+ }
+ }
+
+#ifdef AUTH_FILE
+ /* Check authorization for this directory. */
+ httpd_realloc_str (&dirname, &maxdirname, expnlen);
+ (void) strcpy (dirname, hc->expnfilename);
+ cp = strrchr (dirname, '/');
+ if (cp == (char *) 0)
+ (void) strcpy (dirname, ".");
+ else
+ *cp = '\0';
+ if (auth_check (hc, dirname) == -1)
+ return -1;
+
+ /* Check if the filename is the AUTH_FILE itself - that's verboten. */
+ if (expnlen == sizeof (AUTH_FILE) - 1)
+ {
+ if (strcmp (hc->expnfilename, AUTH_FILE) == 0)
+ {
+ syslog (LOG_NOTICE,
+ "%.80s URL \"%.80s\" tried to retrieve an auth file",
+ httpd_ntoa (&hc->client_addr), hc->encodedurl);
+ httpd_send_err (hc, 403, err403title, "",
+ ERROR_FORM (err403form,
+ "The requested URL '%.80s' is an authorization file, retrieving it is not permitted.\n"),
+ hc->encodedurl);
+ return -1;
+ }
+ }
+ else if (expnlen >= sizeof (AUTH_FILE) &&
+ strcmp (&(hc->expnfilename[expnlen - sizeof (AUTH_FILE) + 1]),
+ AUTH_FILE) == 0
+ && hc->expnfilename[expnlen - sizeof (AUTH_FILE)] == '/')
+ {
+ syslog (LOG_NOTICE,
+ "%.80s URL \"%.80s\" tried to retrieve an auth file",
+ httpd_ntoa (&hc->client_addr), hc->encodedurl);
+ httpd_send_err (hc, 403, err403title, "",
+ ERROR_FORM (err403form,
+ "The requested URL '%.80s' is an authorization file, retrieving it is not permitted.\n"),
+ hc->encodedurl);
+ return -1;
+ }
+#endif /* AUTH_FILE */
+
+ /* Referer check. */
+ if (!check_referer (hc))
+ return -1;
+
+ /* Is it world-executable and in the CGI area? */
+ if (hc->hs->cgi_pattern != (char *) 0 &&
+ (hc->sb.st_mode & S_IXOTH) &&
+ match (hc->hs->cgi_pattern, hc->expnfilename))
+ return cgi (hc);
+
+ /* It's not CGI. If it's executable or there's pathinfo, someone's
+ ** trying to either serve or run a non-CGI file as CGI. Either case
+ ** is prohibited.
+ */
+ if (hc->sb.st_mode & S_IXOTH)
+ {
+ syslog (LOG_NOTICE, "%.80s URL \"%.80s\" is executable but isn't CGI",
+ httpd_ntoa (&hc->client_addr), hc->encodedurl);
+ httpd_send_err (hc, 403, err403title, "",
+ ERROR_FORM (err403form,
+ "The requested URL '%.80s' resolves to a file which is marked executable but is not a CGI file; retrieving it is forbidden.\n"),
+ hc->encodedurl);
+ return -1;
+ }
+ if (hc->pathinfo[0] != '\0')
+ {
+ syslog (LOG_INFO, "%.80s URL \"%.80s\" has pathinfo but isn't CGI",
+ httpd_ntoa (&hc->client_addr), hc->encodedurl);
+ httpd_send_err (hc, 403, err403title, "",
+ ERROR_FORM (err403form,
+ "The requested URL '%.80s' resolves to a file plus CGI-style pathinfo, but the file is not a valid CGI file.\n"),
+ hc->encodedurl);
+ return -1;
+ }
+
+ /* Fill in last_byte_index, if necessary. */
+ if (hc->got_range &&
+ (hc->last_byte_index == -1 || hc->last_byte_index >= hc->sb.st_size))
+ hc->last_byte_index = hc->sb.st_size - 1;
+
+ figure_mime (hc);
+
+ if (hc->method == METHOD_HEAD)
+ {
+ send_mime (hc, 200, ok200title, hc->encodings, "", hc->type,
+ hc->sb.st_size, hc->sb.st_mtime);
+ }
+ else if (hc->if_modified_since != (time_t) - 1 &&
+ hc->if_modified_since >= hc->sb.st_mtime)
+ {
+ send_mime (hc, 304, err304title, hc->encodings, "", hc->type, (off_t) - 1,
+ hc->sb.st_mtime);
+ }
+ else
+ {
+ hc->file_address = mmc_map (hc->expnfilename, &(hc->sb), nowP);
+ if (hc->file_address == (char *) 0)
+ {
+ httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl);
+ return -1;
+ }
+ send_mime (hc, 200, ok200title, hc->encodings, "", hc->type,
+ hc->sb.st_size, hc->sb.st_mtime);
+ }
+
+ return 0;
+#endif
+}
+
+
+int httpd_start_request (httpd_conn * hc, struct timeval *nowP)
+{
+ int r;
+
+ /* Really start the request. */
+ r = really_start_request (hc, nowP);
+
+ /* And return the status. */
+ return r;
+}
+
+
+static void make_log_entry (httpd_conn * hc, struct timeval *nowP)
+{
+ char *ru;
+ char url[305];
+ char bytes[40];
+
+ if (hc->hs->no_log)
+ return;
+
+ /* This is straight CERN Combined Log Format - the only tweak
+ ** being that if we're using syslog() we leave out the date, because
+ ** syslogd puts it in. The included syslogtocern script turns the
+ ** results into true CERN format.
+ */
+
+ /* Format remote user. */
+ if (hc->remoteuser[0] != '\0')
+ ru = hc->remoteuser;
+ else
+ ru = "-";
+ /* If we're vhosting, prepend the hostname to the url. This is
+ ** a little weird, perhaps writing separate log files for
+ ** each vhost would make more sense.
+ */
+ if (hc->hs->vhost && !hc->tildemapped)
+ (void) my_snprintf (url, sizeof (url),
+ "/%.100s%.200s",
+ hc->hostname ==
+ (char *) 0 ? hc->hs->server_hostname : hc->hostname,
+ hc->encodedurl);
+ else
+ (void) my_snprintf (url, sizeof (url), "%.200s", hc->encodedurl);
+ /* Format the bytes. */
+ if (hc->bytes_sent >= 0)
+ (void) my_snprintf (bytes, sizeof (bytes), "%lld",
+ (int64_t) hc->bytes_sent);
+ else
+ (void) strcpy (bytes, "-");
+
+ /* Logfile or syslog? */
+ if (hc->hs->logfp != (FILE *) 0)
+ {
+ time_t now;
+ struct tm *t;
+ const char *cernfmt_nozone = "%d/%b/%Y:%H:%M:%S";
+ char date_nozone[100];
+ int zone;
+ char sign;
+ char date[100];
+
+ /* Get the current time, if necessary. */
+ if (nowP != (struct timeval *) 0)
+ now = nowP->tv_sec;
+ else
+ now = time ((time_t *) 0);
+ /* Format the time, forcing a numeric timezone (some log analyzers
+ ** are stoooopid about this).
+ */
+ t = localtime (&now);
+ (void) strftime (date_nozone, sizeof (date_nozone), cernfmt_nozone, t);
+#ifdef HAVE_TM_GMTOFF
+ zone = t->tm_gmtoff / 60L;
+#else
+ zone = -timezone / 60L;
+ /* Probably have to add something about daylight time here. */
+#endif
+ if (zone >= 0)
+ sign = '+';
+ else
+ {
+ sign = '-';
+ zone = -zone;
+ }
+ zone = (zone / 60) * 100 + zone % 60;
+ (void) my_snprintf (date, sizeof (date),
+ "%s %c%04d", date_nozone, sign, zone);
+ /* And write the log entry. */
+ (void) fprintf (hc->hs->logfp,
+ "%.80s - %.80s [%s] \"%.80s %.300s %.80s\" %d %s \"%.200s\" \"%.200s\"\n",
+ httpd_ntoa (&hc->client_addr), ru, date,
+ httpd_method_str (hc->method), url, hc->protocol,
+ hc->status, bytes, hc->referer, hc->useragent);
+#ifdef FLUSH_LOG_EVERY_TIME
+ (void) fflush (hc->hs->logfp);
+#endif
+ }
+ else
+ syslog (LOG_INFO,
+ "%.80s - %.80s \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"",
+ httpd_ntoa (&hc->client_addr), ru,
+ httpd_method_str (hc->method), url, hc->protocol,
+ hc->status, bytes, hc->referer, hc->useragent);
+}
+
+
+/* Returns 1 if ok to serve the url, 0 if not. */
+static int check_referer (httpd_conn * hc)
+{
+ int r;
+ char *cp;
+
+ /* Are we doing referer checking at all? */
+ if (hc->hs->url_pattern == (char *) 0)
+ return 1;
+
+ r = really_check_referer (hc);
+
+ if (!r)
+ {
+ if (hc->hs->vhost && hc->hostname != (char *) 0)
+ cp = hc->hostname;
+ else
+ cp = hc->hs->server_hostname;
+ if (cp == (char *) 0)
+ cp = "";
+ syslog (LOG_INFO, "%.80s non-local referer \"%.80s%.80s\" \"%.80s\"",
+ httpd_ntoa (&hc->client_addr), cp, hc->encodedurl, hc->referer);
+ httpd_send_err (hc, 403, err403title, "",
+ ERROR_FORM (err403form,
+ "You must supply a local referer to get URL '%.80s' from this server.\n"),
+ hc->encodedurl);
+ }
+ return r;
+}
+
+
+/* Returns 1 if ok to serve the url, 0 if not. */
+static int really_check_referer (httpd_conn * hc)
+{
+ httpd_server *hs;
+ char *cp1;
+ char *cp2;
+ char *cp3;
+ static char *refhost = (char *) 0;
+ static size_t refhost_size = 0;
+ char *lp;
+
+ hs = hc->hs;
+
+ /* Check for an empty referer. */
+ if (hc->referer == (char *) 0 || hc->referer[0] == '\0' ||
+ (cp1 = strstr (hc->referer, "//")) == (char *) 0)
+ {
+ /* Disallow if we require a referer and the url matches. */
+ if (hs->no_empty_referers && match (hs->url_pattern, hc->origfilename))
+ return 0;
+ /* Otherwise ok. */
+ return 1;
+ }
+
+ /* Extract referer host. */
+ cp1 += 2;
+ for (cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2)
+ continue;
+ httpd_realloc_str (&refhost, &refhost_size, cp2 - cp1);
+ for (cp3 = refhost; cp1 < cp2; ++cp1, ++cp3)
+ if (isupper (*cp1))
+ *cp3 = tolower (*cp1);
+ else
+ *cp3 = *cp1;
+ *cp3 = '\0';
+
+ /* Local pattern? */
+ if (hs->local_pattern != (char *) 0)
+ lp = hs->local_pattern;
+ else
+ {
+ /* No local pattern. What's our hostname? */
+ if (!hs->vhost)
+ {
+ /* Not vhosting, use the server name. */
+ lp = hs->server_hostname;
+ if (lp == (char *) 0)
+ /* Couldn't figure out local hostname - give up. */
+ return 1;
+ }
+ else
+ {
+ /* We are vhosting, use the hostname on this connection. */
+ lp = hc->hostname;
+ if (lp == (char *) 0)
+ /* Oops, no hostname. Maybe it's an old browser that
+ ** doesn't send a Host: header. We could figure out
+ ** the default hostname for this IP address, but it's
+ ** not worth it for the few requests like this.
+ */
+ return 1;
+ }
+ }
+
+ /* If the referer host doesn't match the local host pattern, and
+ ** the filename does match the url pattern, it's an illegal reference.
+ */
+ if (!match (lp, refhost) && match (hs->url_pattern, hc->origfilename))
+ return 0;
+ /* Otherwise ok. */
+ return 1;
+}
+
+
+char *httpd_ntoa (httpd_sockaddr * saP)
+{
+#ifdef USE_IPV6
+ static char str[200];
+
+ if (getnameinfo
+ (&saP->sa, sockaddr_len (saP), str, sizeof (str), 0, 0,
+ NI_NUMERICHOST) != 0)
+ {
+ str[0] = '?';
+ str[1] = '\0';
+ }
+ else if (IN6_IS_ADDR_V4MAPPED (&saP->sa_in6.sin6_addr)
+ && strncmp (str, "::ffff:", 7) == 0)
+ /* Elide IPv6ish prefix for IPv4 addresses. */
+ (void) strcpy (str, &str[7]);
+
+ return str;
+
+#else /* USE_IPV6 */
+
+ return inet_ntoa (saP->sa_in.sin_addr);
+
+#endif /* USE_IPV6 */
+}
+
+
+static int sockaddr_check (httpd_sockaddr * saP)
+{
+ switch (saP->sa.sa_family)
+ {
+ case AF_INET:
+ return 1;
+#ifdef USE_IPV6
+ case AF_INET6:
+ return 1;
+#endif /* USE_IPV6 */
+ default:
+ return 0;
+ }
+}
+
+
+static size_t sockaddr_len (httpd_sockaddr * saP)
+{
+ switch (saP->sa.sa_family)
+ {
+ case AF_INET:
+ return sizeof (struct sockaddr_in);
+#ifdef USE_IPV6
+ case AF_INET6:
+ return sizeof (struct sockaddr_in6);
+#endif /* USE_IPV6 */
+ default:
+ return 0; /* shouldn't happen */
+ }
+}
+
+
+/* Some systems don't have snprintf(), so we make our own that uses
+** either vsnprintf() or vsprintf(). If your system doesn't have
+** vsnprintf(), it is probably vulnerable to buffer overruns.
+** Upgrade!
+*/
+static int my_snprintf (char *str, size_t size, const char *format, ...)
+{
+ va_list ap;
+ int r;
+
+ va_start (ap, format);
+#ifdef HAVE_VSNPRINTF
+ r = vsnprintf (str, size, format, ap);
+#else /* HAVE_VSNPRINTF */
+ r = vsprintf (str, format, ap);
+#endif /* HAVE_VSNPRINTF */
+ va_end (ap);
+ return r;
+}
+
+
+#ifndef HAVE_ATOLL
+static long long atoll (const char *str)
+{
+ long long value;
+ long long sign;
+
+ while (isspace (*str))
+ ++str;
+ switch (*str)
+ {
+ case '-':
+ sign = -1;
+ ++str;
+ break;
+ case '+':
+ sign = 1;
+ ++str;
+ break;
+ default:
+ sign = 1;
+ break;
+ }
+ value = 0;
+ while (isdigit (*str))
+ {
+ value = value * 10 + (*str - '0');
+ ++str;
+ }
+ return sign * value;
+}
+#endif /* HAVE_ATOLL */
+
+
+/* Read the requested buffer completely, accounting for interruptions. */
+int httpd_read_fully (int fd, void *buf, size_t nbytes)
+{
+ int nread;
+
+ nread = 0;
+ while (nread < nbytes)
+ {
+ int r;
+
+ r = read (fd, (char *) buf + nread, nbytes - nread);
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ {
+ sleep (1);
+ continue;
+ }
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+ nread += r;
+ }
+
+ return nread;
+}
+
+
+/* Write the requested buffer completely, accounting for interruptions. */
+int httpd_write_fully (int fd, const void *buf, size_t nbytes)
+{
+ int nwritten;
+
+ nwritten = 0;
+ while (nwritten < nbytes)
+ {
+ int r;
+
+ r = write (fd, (char *) buf + nwritten, nbytes - nwritten);
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ {
+ sleep (1);
+ continue;
+ }
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+ nwritten += r;
+ }
+
+ return nwritten;
+}
+
+
+/* Generate debugging statistics syslog message. */
+void httpd_logstats (long secs)
+{
+ if (str_alloc_count > 0)
+ syslog (LOG_INFO,
+ " libhttpd - %d strings allocated, %lu bytes (%g bytes/str)",
+ str_alloc_count, (unsigned long) str_alloc_size,
+ (float) str_alloc_size / str_alloc_count);
+}
diff --git a/gb.httpd/src/libhttpd.h b/gb.httpd/src/libhttpd.h
new file mode 100644
index 000000000..ea722b61b
--- /dev/null
+++ b/gb.httpd/src/libhttpd.h
@@ -0,0 +1,294 @@
+/* libhttpd.h - defines for libhttpd
+**
+** Copyright � 1995,1998,1999,2000,2001 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+#ifndef _LIBHTTPD_H_
+#define _LIBHTTPD_H_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED)
+#define USE_IPV6
+#endif
+
+
+/* A few convenient defines. */
+
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+#define NEW(t,n) ((t*) malloc( sizeof(t) * (n) ))
+#define RENEW(o,t,n) ((t*) realloc( (void*) o, sizeof(t) * (n) ))
+
+
+/* The httpd structs. */
+
+/* A multi-family sockaddr. */
+typedef union
+{
+ struct sockaddr sa;
+ struct sockaddr_in sa_in;
+#ifdef USE_IPV6
+ struct sockaddr_in6 sa_in6;
+ struct sockaddr_storage sa_stor;
+#endif /* USE_IPV6 */
+} httpd_sockaddr;
+
+/* A server. */
+typedef struct
+{
+ char *binding_hostname;
+ char *server_hostname;
+ unsigned short port;
+ char *cgi_pattern;
+ int cgi_limit, cgi_timelimit, cgi_count;
+ char *charset;
+ char *p3p;
+ int max_age;
+ char *cwd;
+ int listen4_fd, listen6_fd;
+ int no_log;
+ FILE *logfp;
+ int no_symlink_check;
+ int vhost;
+ int global_passwd;
+ char *url_pattern;
+ char *local_pattern;
+ int no_empty_referers;
+} httpd_server;
+
+/* A connection. */
+typedef struct
+{
+ int initialized;
+ httpd_server *hs;
+ httpd_sockaddr client_addr;
+ char *read_buf;
+ size_t read_size, read_idx, checked_idx;
+ int checked_state;
+ int method;
+ int status;
+ off_t bytes_to_send;
+ off_t bytes_sent;
+ char *encodedurl;
+ char *decodedurl;
+ char *protocol;
+ char *origfilename;
+ char *expnfilename;
+ char *encodings;
+ char *pathinfo;
+ char *query;
+ char *referer;
+ char *useragent;
+ char *accept;
+ char *accepte;
+ char *acceptl;
+ char *cookie;
+ char *contenttype;
+#ifdef X_CGI_HEADER
+ char *xcgi;
+#endif
+ char *reqhost;
+ char *hdrhost;
+ char *hostdir;
+ char *authorization;
+ char *remoteuser;
+ char *response;
+ size_t maxdecodedurl, maxorigfilename, maxexpnfilename, maxencodings,
+ maxpathinfo, maxquery, maxaccept, maxaccepte, maxreqhost, maxhostdir,
+ maxremoteuser, maxresponse;
+#ifdef TILDE_MAP_2
+ char *altdir;
+ size_t maxaltdir;
+#endif /* TILDE_MAP_2 */
+ size_t responselen;
+ time_t if_modified_since, range_if;
+ size_t contentlength;
+ char *type; /* not malloc()ed */
+ char *hostname; /* not malloc()ed */
+ int mime_flag;
+ int one_one; /* HTTP/1.1 or better */
+ int got_range;
+ int tildemapped; /* this connection got tilde-mapped */
+ off_t first_byte_index, last_byte_index;
+ int keep_alive;
+ int should_linger;
+ struct stat sb;
+ int conn_fd;
+ char *file_address;
+} httpd_conn;
+
+/* Methods. */
+#define METHOD_UNKNOWN 0
+#define METHOD_GET 1
+#define METHOD_HEAD 2
+#define METHOD_POST 3
+
+/* States for checked_state. */
+#define CHST_FIRSTWORD 0
+#define CHST_FIRSTWS 1
+#define CHST_SECONDWORD 2
+#define CHST_SECONDWS 3
+#define CHST_THIRDWORD 4
+#define CHST_THIRDWS 5
+#define CHST_LINE 6
+#define CHST_LF 7
+#define CHST_CR 8
+#define CHST_CRLF 9
+#define CHST_CRLFCR 10
+#define CHST_BOGUS 11
+
+
+/* Initializes. Does the socket(), bind(), and listen(). Returns an
+** httpd_server* which includes a socket fd that you can select() on.
+** Return (httpd_server*) 0 on error.
+*/
+extern httpd_server *httpd_initialize (char *hostname, httpd_sockaddr * sa4P,
+ httpd_sockaddr * sa6P,
+ unsigned short port, char *cgi_pattern,
+ int cgi_limit, int cgi_timelimit,
+ char *charset, char *p3p, int max_age,
+ char *cwd, int no_log, FILE * logfp,
+ int no_symlink_check, int vhost,
+ int global_passwd, char *url_pattern,
+ char *local_pattern,
+ int no_empty_referers);
+
+/* Change the log file. */
+extern void httpd_set_logfp (httpd_server * hs, FILE * logfp);
+
+/* Call to unlisten/close socket(s) listening for new connections. */
+extern void httpd_unlisten (httpd_server * hs);
+
+/* Call to shut down. */
+extern void httpd_terminate (httpd_server * hs);
+
+
+/* When a listen fd is ready to read, call this. It does the accept() and
+** returns an httpd_conn* which includes the fd to read the request from and
+** write the response to. Returns an indication of whether the accept()
+** failed, succeeded, or if there were no more connections to accept.
+**
+** In order to minimize malloc()s, the caller passes in the httpd_conn.
+** The caller is also responsible for setting initialized to zero before the
+** first call using each different httpd_conn.
+*/
+extern int httpd_get_conn (httpd_server * hs, int listen_fd, httpd_conn * hc);
+#define GC_FAIL 0
+#define GC_OK 1
+#define GC_NO_MORE 2
+
+/* Checks whether the data in hc->read_buf constitutes a complete request
+** yet. The caller reads data into hc->read_buf[hc->read_idx] and advances
+** hc->read_idx. This routine checks what has been read so far, using
+** hc->checked_idx and hc->checked_state to keep track, and returns an
+** indication of whether there is no complete request yet, there is a
+** complete request, or there won't be a valid request due to a syntax error.
+*/
+extern int httpd_got_request (httpd_conn * hc);
+#define GR_NO_REQUEST 0
+#define GR_GOT_REQUEST 1
+#define GR_BAD_REQUEST 2
+
+/* Parses the request in hc->read_buf. Fills in lots of fields in hc,
+** like the URL and the various headers.
+**
+** Returns -1 on error.
+*/
+extern int httpd_parse_request (httpd_conn * hc);
+
+/* Starts sending data back to the client. In some cases (directories,
+** CGI programs), finishes sending by itself - in those cases, hc->file_fd
+** is <0. If there is more data to be sent, then hc->file_fd is a file
+** descriptor for the file to send. If you don't have a current timeval
+** handy just pass in 0.
+**
+** Returns -1 on error.
+*/
+extern int httpd_start_request (httpd_conn * hc, struct timeval *nowP);
+
+/* Actually sends any buffered response text. */
+extern void httpd_write_response (httpd_conn * hc);
+
+/* Call this to close down a connection and free the data. A fine point,
+** if you fork() with a connection open you should still call this in the
+** parent process - the connection will stay open in the child.
+** If you don't have a current timeval handy just pass in 0.
+*/
+extern void httpd_close_conn (httpd_conn * hc, struct timeval *nowP);
+
+/* Call this to de-initialize a connection struct and *really* free the
+** mallocced strings.
+*/
+extern void httpd_destroy_conn (httpd_conn * hc);
+
+
+/* Send an error message back to the client. */
+extern void httpd_send_err (httpd_conn * hc, int status, char *title,
+ char *extraheads, char *form, char *arg);
+
+/* Some error messages. */
+extern char *httpd_err400title;
+extern char *httpd_err400form;
+extern char *httpd_err408title;
+extern char *httpd_err408form;
+extern char *httpd_err503title;
+extern char *httpd_err503form;
+
+/* Generate a string representation of a method number. */
+extern char *httpd_method_str (int method);
+
+/* Reallocate a string. */
+extern void httpd_realloc_str (char **strP, size_t * maxsizeP, size_t size);
+
+/* Format a network socket to a string representation. */
+extern char *httpd_ntoa (httpd_sockaddr * saP);
+
+/* Set NDELAY mode on a socket. */
+extern void httpd_set_ndelay (int fd);
+
+/* Clear NDELAY mode on a socket. */
+extern void httpd_clear_ndelay (int fd);
+
+/* Read the requested buffer completely, accounting for interruptions. */
+extern int httpd_read_fully (int fd, void *buf, size_t nbytes);
+
+/* Write the requested buffer completely, accounting for interruptions. */
+extern int httpd_write_fully (int fd, const void *buf, size_t nbytes);
+
+/* Generate debugging statistics syslog message. */
+extern void httpd_logstats (long secs);
+
+#endif /* _LIBHTTPD_H_ */
diff --git a/gb.httpd/src/main.c b/gb.httpd/src/main.c
new file mode 100644
index 000000000..72cf98d62
--- /dev/null
+++ b/gb.httpd/src/main.c
@@ -0,0 +1,71 @@
+/***************************************************************************
+
+ main.c
+
+ gb.httpd component
+
+ (c) 2012 Benoît Minisini
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 1, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ MA 02110-1301, USA.
+
+***************************************************************************/
+
+#define __MAIN_C
+
+#include "gb_common.h"
+
+#include
+
+#include "main.h"
+
+int thttpd_main (int argc, char **argv);
+
+const GB_INTERFACE *GB_PTR EXPORT;
+
+static jmp_buf _setjmp_env;
+
+void syslog (int priority, const char *format, ...)
+{
+ va_list args;
+
+ va_start (args, format);
+
+ fprintf (stderr, "gb.httpd: ");
+ vfprintf (stderr, format, args);
+ putc ('\n', stderr);
+}
+
+void run_cgi (void)
+{
+ longjmp (_setjmp_env, 1);
+}
+
+void EXPORT GB_MAIN (int argc, char **argv)
+{
+ if (setjmp (_setjmp_env) == 0)
+ thttpd_main (argc, argv);
+ else
+ GB.System.HasForked ();
+}
+
+int EXPORT GB_INIT ()
+{
+ return 0;
+}
+
+void EXPORT GB_EXIT ()
+{
+}
diff --git a/gb.httpd/src/main.h b/gb.httpd/src/main.h
new file mode 100644
index 000000000..defbd9c9b
--- /dev/null
+++ b/gb.httpd/src/main.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+
+ main.h
+
+ gb.httpd component
+
+ (c) 2012 Benoît Minisini
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 1, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ MA 02110-1301, USA.
+
+***************************************************************************/
+
+#ifndef __MAIN_H
+#define __MAIN_H
+
+#include "gambas.h"
+
+#ifndef __MAIN_C
+extern const GB_INTERFACE *GB_PTR;
+#endif
+
+#define GB (*GB_PTR)
+
+#define LOG_EMERG 0 /* system is unusable */
+#define LOG_ALERT 1 /* action must be taken immediately */
+#define LOG_CRIT 2 /* critical conditions */
+#define LOG_ERR 3 /* error conditions */
+#define LOG_WARNING 4 /* warning conditions */
+#define LOG_NOTICE 5 /* normal but significant condition */
+#define LOG_INFO 6 /* informational */
+#define LOG_DEBUG 7 /* debug-level messages */
+
+void syslog (int priority, const char *format, ...);
+#define closelog()
+
+void run_cgi ();
+
+#endif /* __MAIN_H */
diff --git a/gb.httpd/src/match.c b/gb.httpd/src/match.c
new file mode 100644
index 000000000..47ee7563d
--- /dev/null
+++ b/gb.httpd/src/match.c
@@ -0,0 +1,87 @@
+/* match.c - simple shell-style filename matcher
+**
+** Only does ? * and **, and multiple patterns separated by |. Returns 1 or 0.
+**
+** Copyright © 1995,2000 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+
+#include
+
+#include "match.h"
+
+static int match_one (const char *pattern, int patternlen,
+ const char *string);
+
+int match (const char *pattern, const char *string)
+{
+ const char *or;
+
+ for (;;)
+ {
+ or = strchr (pattern, '|');
+ if (or == (char *) 0)
+ return match_one (pattern, strlen (pattern), string);
+ if (match_one (pattern, or - pattern, string))
+ return 1;
+ pattern = or + 1;
+ }
+}
+
+
+static int match_one (const char *pattern, int patternlen, const char *string)
+{
+ const char *p;
+
+ for (p = pattern; p - pattern < patternlen; ++p, ++string)
+ {
+ if (*p == '?' && *string != '\0')
+ continue;
+ if (*p == '*')
+ {
+ int i, pl;
+ ++p;
+ if (*p == '*')
+ {
+ /* Double-wildcard matches anything. */
+ ++p;
+ i = strlen (string);
+ }
+ else
+ /* Single-wildcard matches anything but slash. */
+ i = strcspn (string, "/");
+ pl = patternlen - (p - pattern);
+ for (; i >= 0; --i)
+ if (match_one (p, pl, &(string[i])))
+ return 1;
+ return 0;
+ }
+ if (*p != *string)
+ return 0;
+ }
+ if (*string == '\0')
+ return 1;
+ return 0;
+}
diff --git a/gb.httpd/src/match.h b/gb.httpd/src/match.h
new file mode 100644
index 000000000..a0d7569ef
--- /dev/null
+++ b/gb.httpd/src/match.h
@@ -0,0 +1,36 @@
+/* match.h - simple shell-style filename patcher
+**
+** Copyright © 1995 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+#ifndef _MATCH_H_
+#define _MATCH_H_
+
+/* Simple shell-style filename pattern matcher. Only does ? * and **, and
+** multiple patterns separated by |. Returns 1 or 0.
+*/
+extern int match (const char *pattern, const char *string);
+
+#endif /* _MATCH_H_ */
diff --git a/gb.httpd/src/mime_encodings.h b/gb.httpd/src/mime_encodings.h
new file mode 100644
index 000000000..b1130e7e0
--- /dev/null
+++ b/gb.httpd/src/mime_encodings.h
@@ -0,0 +1,8 @@
+{
+"Z", 0, "compress", 0},
+
+{
+"gz", 0, "gzip", 0},
+
+{
+"uu", 0, "x-uuencode", 0},
diff --git a/gb.httpd/src/mime_types.h b/gb.httpd/src/mime_types.h
new file mode 100644
index 000000000..e049fc4ca
--- /dev/null
+++ b/gb.httpd/src/mime_types.h
@@ -0,0 +1,569 @@
+{
+"a", 0, "application/octet-stream", 0},
+
+{
+"aab", 0, "application/x-authorware-bin", 0},
+
+{
+"aam", 0, "application/x-authorware-map", 0},
+
+{
+"aas", 0, "application/x-authorware-seg", 0},
+
+{
+"ai", 0, "application/postscript", 0},
+
+{
+"aif", 0, "audio/x-aiff", 0},
+
+{
+"aifc", 0, "audio/x-aiff", 0},
+
+{
+"aiff", 0, "audio/x-aiff", 0},
+
+{
+"asc", 0, "text/plain", 0},
+
+{
+"asf", 0, "video/x-ms-asf", 0},
+
+{
+"asx", 0, "video/x-ms-asf", 0},
+
+{
+"au", 0, "audio/basic", 0},
+
+{
+"avi", 0, "video/x-msvideo", 0},
+
+{
+"bcpio", 0, "application/x-bcpio", 0},
+
+{
+"bin", 0, "application/octet-stream", 0},
+
+{
+"bmp", 0, "image/bmp", 0},
+
+{
+"cdf", 0, "application/x-netcdf", 0},
+
+{
+"class", 0, "application/x-java-vm", 0},
+
+{
+"cpio", 0, "application/x-cpio", 0},
+
+{
+"cpt", 0, "application/mac-compactpro", 0},
+
+{
+"crl", 0, "application/x-pkcs7-crl", 0},
+
+{
+"crt", 0, "application/x-x509-ca-cert", 0},
+
+{
+"csh", 0, "application/x-csh", 0},
+
+{
+"css", 0, "text/css", 0},
+
+{
+"dcr", 0, "application/x-director", 0},
+
+{
+"dir", 0, "application/x-director", 0},
+
+{
+"djv", 0, "image/vnd.djvu", 0},
+
+{
+"djvu", 0, "image/vnd.djvu", 0},
+
+{
+"dll", 0, "application/octet-stream", 0},
+
+{
+"dms", 0, "application/octet-stream", 0},
+
+{
+"doc", 0, "application/msword", 0},
+
+{
+"dtd", 0, "text/xml", 0},
+
+{
+"dump", 0, "application/octet-stream", 0},
+
+{
+"dvi", 0, "application/x-dvi", 0},
+
+{
+"dxr", 0, "application/x-director", 0},
+
+{
+"eps", 0, "application/postscript", 0},
+
+{
+"etx", 0, "text/x-setext", 0},
+
+{
+"exe", 0, "application/octet-stream", 0},
+
+{
+"ez", 0, "application/andrew-inset", 0},
+
+{
+"fgd", 0, "application/x-director", 0},
+
+{
+"fh", 0, "image/x-freehand", 0},
+
+{
+"fh4", 0, "image/x-freehand", 0},
+
+{
+"fh5", 0, "image/x-freehand", 0},
+
+{
+"fh7", 0, "image/x-freehand", 0},
+
+{
+"fhc", 0, "image/x-freehand", 0},
+
+{
+"gif", 0, "image/gif", 0},
+
+{
+"gtar", 0, "application/x-gtar", 0},
+
+{
+"hdf", 0, "application/x-hdf", 0},
+
+{
+"hqx", 0, "application/mac-binhex40", 0},
+
+{
+"htm", 0, "text/html; charset=%s", 0},
+
+{
+"html", 0, "text/html; charset=%s", 0},
+
+{
+"ice", 0, "x-conference/x-cooltalk", 0},
+
+{
+"ief", 0, "image/ief", 0},
+
+{
+"iges", 0, "model/iges", 0},
+
+{
+"igs", 0, "model/iges", 0},
+
+{
+"iv", 0, "application/x-inventor", 0},
+
+{
+"jar", 0, "application/x-java-archive", 0},
+
+{
+"jfif", 0, "image/jpeg", 0},
+
+{
+"jpe", 0, "image/jpeg", 0},
+
+{
+"jpeg", 0, "image/jpeg", 0},
+
+{
+"jpg", 0, "image/jpeg", 0},
+
+{
+"js", 0, "application/x-javascript", 0},
+
+{
+"kar", 0, "audio/midi", 0},
+
+{
+"latex", 0, "application/x-latex", 0},
+
+{
+"lha", 0, "application/octet-stream", 0},
+
+{
+"lzh", 0, "application/octet-stream", 0},
+
+{
+"m3u", 0, "audio/x-mpegurl", 0},
+
+{
+"man", 0, "application/x-troff-man", 0},
+
+{
+"mathml", 0, "application/mathml+xml", 0},
+
+{
+"me", 0, "application/x-troff-me", 0},
+
+{
+"mesh", 0, "model/mesh", 0},
+
+{
+"mid", 0, "audio/midi", 0},
+
+{
+"midi", 0, "audio/midi", 0},
+
+{
+"mif", 0, "application/vnd.mif", 0},
+
+{
+"mime", 0, "message/rfc822", 0},
+
+{
+"mml", 0, "application/mathml+xml", 0},
+
+{
+"mov", 0, "video/quicktime", 0},
+
+{
+"movie", 0, "video/x-sgi-movie", 0},
+
+{
+"mp2", 0, "audio/mpeg", 0},
+
+{
+"mp3", 0, "audio/mpeg", 0},
+
+{
+"mp4", 0, "video/mp4", 0},
+
+{
+"mpe", 0, "video/mpeg", 0},
+
+{
+"mpeg", 0, "video/mpeg", 0},
+
+{
+"mpg", 0, "video/mpeg", 0},
+
+{
+"mpga", 0, "audio/mpeg", 0},
+
+{
+"ms", 0, "application/x-troff-ms", 0},
+
+{
+"msh", 0, "model/mesh", 0},
+
+{
+"mv", 0, "video/x-sgi-movie", 0},
+
+{
+"mxu", 0, "video/vnd.mpegurl", 0},
+
+{
+"nc", 0, "application/x-netcdf", 0},
+
+{
+"o", 0, "application/octet-stream", 0},
+
+{
+"oda", 0, "application/oda", 0},
+
+{
+"ogg", 0, "application/x-ogg", 0},
+
+{
+"pac", 0, "application/x-ns-proxy-autoconfig", 0},
+
+{
+"pbm", 0, "image/x-portable-bitmap", 0},
+
+{
+"pdb", 0, "chemical/x-pdb", 0},
+
+{
+"pdf", 0, "application/pdf", 0},
+
+{
+"pgm", 0, "image/x-portable-graymap", 0},
+
+{
+"pgn", 0, "application/x-chess-pgn", 0},
+
+{
+"png", 0, "image/png", 0},
+
+{
+"pnm", 0, "image/x-portable-anymap", 0},
+
+{
+"ppm", 0, "image/x-portable-pixmap", 0},
+
+{
+"ppt", 0, "application/vnd.ms-powerpoint", 0},
+
+{
+"ps", 0, "application/postscript", 0},
+
+{
+"qt", 0, "video/quicktime", 0},
+
+{
+"ra", 0, "audio/x-realaudio", 0},
+
+{
+"ram", 0, "audio/x-pn-realaudio", 0},
+
+{
+"ras", 0, "image/x-cmu-raster", 0},
+
+{
+"rdf", 0, "application/rdf+xml", 0},
+
+{
+"rgb", 0, "image/x-rgb", 0},
+
+{
+"rm", 0, "audio/x-pn-realaudio", 0},
+
+{
+"roff", 0, "application/x-troff", 0},
+
+{
+"rpm", 0, "audio/x-pn-realaudio-plugin", 0},
+
+{
+"rss", 0, "application/rss+xml", 0},
+
+{
+"rtf", 0, "text/rtf", 0},
+
+{
+"rtx", 0, "text/richtext", 0},
+
+{
+"sgm", 0, "text/sgml", 0},
+
+{
+"sgml", 0, "text/sgml", 0},
+
+{
+"sh", 0, "application/x-sh", 0},
+
+{
+"shar", 0, "application/x-shar", 0},
+
+{
+"silo", 0, "model/mesh", 0},
+
+{
+"sit", 0, "application/x-stuffit", 0},
+
+{
+"skd", 0, "application/x-koan", 0},
+
+{
+"skm", 0, "application/x-koan", 0},
+
+{
+"skp", 0, "application/x-koan", 0},
+
+{
+"skt", 0, "application/x-koan", 0},
+
+{
+"smi", 0, "application/smil", 0},
+
+{
+"smil", 0, "application/smil", 0},
+
+{
+"snd", 0, "audio/basic", 0},
+
+{
+"so", 0, "application/octet-stream", 0},
+
+{
+"spl", 0, "application/x-futuresplash", 0},
+
+{
+"src", 0, "application/x-wais-source", 0},
+
+{
+"stc", 0, "application/vnd.sun.xml.calc.template", 0},
+
+{
+"std", 0, "application/vnd.sun.xml.draw.template", 0},
+
+{
+"sti", 0, "application/vnd.sun.xml.impress.template", 0},
+
+{
+"stw", 0, "application/vnd.sun.xml.writer.template", 0},
+
+{
+"sv4cpio", 0, "application/x-sv4cpio", 0},
+
+{
+"sv4crc", 0, "application/x-sv4crc", 0},
+
+{
+"svg", 0, "image/svg+xml", 0},
+
+{
+"svgz", 0, "image/svg+xml", 0},
+
+{
+"swf", 0, "application/x-shockwave-flash", 0},
+
+{
+"sxc", 0, "application/vnd.sun.xml.calc", 0},
+
+{
+"sxd", 0, "application/vnd.sun.xml.draw", 0},
+
+{
+"sxg", 0, "application/vnd.sun.xml.writer.global", 0},
+
+{
+"sxi", 0, "application/vnd.sun.xml.impress", 0},
+
+{
+"sxm", 0, "application/vnd.sun.xml.math", 0},
+
+{
+"sxw", 0, "application/vnd.sun.xml.writer", 0},
+
+{
+"t", 0, "application/x-troff", 0},
+
+{
+"tar", 0, "application/x-tar", 0},
+
+{
+"tcl", 0, "application/x-tcl", 0},
+
+{
+"tex", 0, "application/x-tex", 0},
+
+{
+"texi", 0, "application/x-texinfo", 0},
+
+{
+"texinfo", 0, "application/x-texinfo", 0},
+
+{
+"tif", 0, "image/tiff", 0},
+
+{
+"tiff", 0, "image/tiff", 0},
+
+{
+"tr", 0, "application/x-troff", 0},
+
+{
+"tsp", 0, "application/dsptype", 0},
+
+{
+"tsv", 0, "text/tab-separated-values", 0},
+
+{
+"txt", 0, "text/plain; charset=%s", 0},
+
+{
+"ustar", 0, "application/x-ustar", 0},
+
+{
+"vcd", 0, "application/x-cdlink", 0},
+
+{
+"vrml", 0, "model/vrml", 0},
+
+{
+"vx", 0, "video/x-rad-screenplay", 0},
+
+{
+"wav", 0, "audio/x-wav", 0},
+
+{
+"wax", 0, "audio/x-ms-wax", 0},
+
+{
+"wbmp", 0, "image/vnd.wap.wbmp", 0},
+
+{
+"wbxml", 0, "application/vnd.wap.wbxml", 0},
+
+{
+"wm", 0, "video/x-ms-wm", 0},
+
+{
+"wma", 0, "audio/x-ms-wma", 0},
+
+{
+"wmd", 0, "application/x-ms-wmd", 0},
+
+{
+"wml", 0, "text/vnd.wap.wml", 0},
+
+{
+"wmlc", 0, "application/vnd.wap.wmlc", 0},
+
+{
+"wmls", 0, "text/vnd.wap.wmlscript", 0},
+
+{
+"wmlsc", 0, "application/vnd.wap.wmlscriptc", 0},
+
+{
+"wmv", 0, "video/x-ms-wmv", 0},
+
+{
+"wmx", 0, "video/x-ms-wmx", 0},
+
+{
+"wmz", 0, "application/x-ms-wmz", 0},
+
+{
+"wrl", 0, "model/vrml", 0},
+
+{
+"wsrc", 0, "application/x-wais-source", 0},
+
+{
+"wvx", 0, "video/x-ms-wvx", 0},
+
+{
+"xbm", 0, "image/x-xbitmap", 0},
+
+{
+"xht", 0, "application/xhtml+xml", 0},
+
+{
+"xhtml", 0, "application/xhtml+xml", 0},
+
+{
+"xls", 0, "application/vnd.ms-excel", 0},
+
+{
+"xml", 0, "text/xml", 0},
+
+{
+"xpm", 0, "image/x-xpixmap", 0},
+
+{
+"xsl", 0, "text/xml", 0},
+
+{
+"xwd", 0, "image/x-xwindowdump", 0},
+
+{
+"xyz", 0, "chemical/x-xyz", 0},
+
+{
+"zip", 0, "application/zip", 0},
diff --git a/gb.httpd/src/mmc.c b/gb.httpd/src/mmc.c
new file mode 100644
index 000000000..113108776
--- /dev/null
+++ b/gb.httpd/src/mmc.c
@@ -0,0 +1,521 @@
+/* mmc.c - mmap cache
+**
+** Copyright 1998,2001 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+#include "thttpd.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef HAVE_MMAP
+#include
+#endif /* HAVE_MMAP */
+
+#include "mmc.h"
+#include "libhttpd.h"
+
+#ifndef HAVE_INT64T
+typedef long long int64_t;
+#endif
+
+
+/* Defines. */
+#ifndef DEFAULT_EXPIRE_AGE
+#define DEFAULT_EXPIRE_AGE 600
+#endif
+#ifndef DESIRED_FREE_COUNT
+#define DESIRED_FREE_COUNT 100
+#endif
+#ifndef DESIRED_MAX_MAPPED_FILES
+#define DESIRED_MAX_MAPPED_FILES 2000
+#endif
+#ifndef DESIRED_MAX_MAPPED_BYTES
+#define DESIRED_MAX_MAPPED_BYTES 1000000000
+#endif
+#ifndef INITIAL_HASH_SIZE
+#define INITIAL_HASH_SIZE (1 << 10)
+#endif
+
+#ifndef MAX
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#endif
+#ifndef MIN
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#endif
+
+
+/* The Map struct. */
+typedef struct MapStruct
+{
+ ino_t ino;
+ dev_t dev;
+ off_t size;
+ time_t ctime;
+ int refcount;
+ time_t reftime;
+ void *addr;
+ unsigned int hash;
+ int hash_idx;
+ struct MapStruct *next;
+} Map;
+
+
+/* Globals. */
+static Map *maps = (Map *) 0;
+static Map *free_maps = (Map *) 0;
+static int alloc_count = 0, map_count = 0, free_count = 0;
+static Map **hash_table = (Map **) 0;
+static int hash_size;
+static unsigned int hash_mask;
+static time_t expire_age = DEFAULT_EXPIRE_AGE;
+static off_t mapped_bytes = 0;
+
+
+
+/* Forwards. */
+static void panic (void);
+static void really_unmap (Map ** mm);
+static int check_hash_size (void);
+static int add_hash (Map * m);
+static Map *find_hash (ino_t ino, dev_t dev, off_t size, time_t ctime);
+static unsigned int hash (ino_t ino, dev_t dev, off_t size, time_t ctime);
+
+
+void *mmc_map (char *filename, struct stat *sbP, struct timeval *nowP)
+{
+ time_t now;
+ struct stat sb;
+ Map *m;
+ int fd;
+
+ /* Stat the file, if necessary. */
+ if (sbP != (struct stat *) 0)
+ sb = *sbP;
+ else
+ {
+ if (stat (filename, &sb) != 0)
+ {
+ syslog (LOG_ERR, "stat - %m");
+ return (void *) 0;
+ }
+ }
+
+ /* Get the current time, if necessary. */
+ if (nowP != (struct timeval *) 0)
+ now = nowP->tv_sec;
+ else
+ now = time ((time_t *) 0);
+
+ /* See if we have it mapped already, via the hash table. */
+ if (check_hash_size () < 0)
+ {
+ syslog (LOG_ERR, "check_hash_size() failure");
+ return (void *) 0;
+ }
+ m = find_hash (sb.st_ino, sb.st_dev, sb.st_size, sb.st_ctime);
+ if (m != (Map *) 0)
+ {
+ /* Yep. Just return the existing map */
+ ++m->refcount;
+ m->reftime = now;
+ return m->addr;
+ }
+
+ /* Open the file. */
+ fd = open (filename, O_RDONLY);
+ if (fd < 0)
+ {
+ syslog (LOG_ERR, "open - %m");
+ return (void *) 0;
+ }
+
+ /* Find a free Map entry or make a new one. */
+ if (free_maps != (Map *) 0)
+ {
+ m = free_maps;
+ free_maps = m->next;
+ --free_count;
+ }
+ else
+ {
+ m = (Map *) malloc (sizeof (Map));
+ if (m == (Map *) 0)
+ {
+ (void) close (fd);
+ syslog (LOG_ERR, "out of memory allocating a Map");
+ return (void *) 0;
+ }
+ ++alloc_count;
+ }
+
+ /* Fill in the Map entry. */
+ m->ino = sb.st_ino;
+ m->dev = sb.st_dev;
+ m->size = sb.st_size;
+ m->ctime = sb.st_ctime;
+ m->refcount = 1;
+ m->reftime = now;
+
+ /* Avoid doing anything for zero-length files; some systems don't like
+ ** to mmap them, other systems dislike mallocing zero bytes.
+ */
+ if (m->size == 0)
+ m->addr = (void *) 1; /* arbitrary non-NULL address */
+ else
+ {
+ size_t size_size = (size_t) m->size; /* loses on files >2GB */
+#ifdef HAVE_MMAP
+ /* Map the file into memory. */
+ m->addr = mmap (0, size_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (m->addr == (void *) -1 && errno == ENOMEM)
+ {
+ /* Ooo, out of address space. Free all unreferenced maps
+ ** and try again.
+ */
+ panic ();
+ m->addr = mmap (0, size_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ }
+ if (m->addr == (void *) -1)
+ {
+ syslog (LOG_ERR, "mmap - %m");
+ (void) close (fd);
+ free ((void *) m);
+ --alloc_count;
+ return (void *) 0;
+ }
+#else /* HAVE_MMAP */
+ /* Read the file into memory. */
+ m->addr = (void *) malloc (size_size);
+ if (m->addr == (void *) 0)
+ {
+ /* Ooo, out of memory. Free all unreferenced maps
+ ** and try again.
+ */
+ panic ();
+ m->addr = (void *) malloc (size_size);
+ }
+ if (m->addr == (void *) 0)
+ {
+ syslog (LOG_ERR, "out of memory storing a file");
+ (void) close (fd);
+ free ((void *) m);
+ --alloc_count;
+ return (void *) 0;
+ }
+ if (httpd_read_fully (fd, m->addr, size_size) != size_size)
+ {
+ syslog (LOG_ERR, "read - %m");
+ (void) close (fd);
+ free ((void *) m);
+ --alloc_count;
+ return (void *) 0;
+ }
+#endif /* HAVE_MMAP */
+ }
+ (void) close (fd);
+
+ /* Put the Map into the hash table. */
+ if (add_hash (m) < 0)
+ {
+ syslog (LOG_ERR, "add_hash() failure");
+ free ((void *) m);
+ --alloc_count;
+ return (void *) 0;
+ }
+
+ /* Put the Map on the active list. */
+ m->next = maps;
+ maps = m;
+ ++map_count;
+
+ /* Update the total byte count. */
+ mapped_bytes += m->size;
+
+ /* And return the address. */
+ return m->addr;
+}
+
+
+void mmc_unmap (void *addr, struct stat *sbP, struct timeval *nowP)
+{
+ Map *m = (Map *) 0;
+
+ /* Find the Map entry for this address. First try a hash. */
+ if (sbP != (struct stat *) 0)
+ {
+ m = find_hash (sbP->st_ino, sbP->st_dev, sbP->st_size, sbP->st_ctime);
+ if (m != (Map *) 0 && m->addr != addr)
+ m = (Map *) 0;
+ }
+ /* If that didn't work, try a full search. */
+ if (m == (Map *) 0)
+ for (m = maps; m != (Map *) 0; m = m->next)
+ if (m->addr == addr)
+ break;
+ if (m == (Map *) 0)
+ syslog (LOG_ERR, "mmc_unmap failed to find entry!");
+ else if (m->refcount <= 0)
+ syslog (LOG_ERR, "mmc_unmap found zero or negative refcount!");
+ else
+ {
+ --m->refcount;
+ if (nowP != (struct timeval *) 0)
+ m->reftime = nowP->tv_sec;
+ else
+ m->reftime = time ((time_t *) 0);
+ }
+}
+
+
+void mmc_cleanup (struct timeval *nowP)
+{
+ time_t now;
+ Map **mm;
+ Map *m;
+
+ /* Get the current time, if necessary. */
+ if (nowP != (struct timeval *) 0)
+ now = nowP->tv_sec;
+ else
+ now = time ((time_t *) 0);
+
+ /* Really unmap any unreferenced entries older than the age limit. */
+ for (mm = &maps; *mm != (Map *) 0;)
+ {
+ m = *mm;
+ if (m->refcount == 0 && now - m->reftime >= expire_age)
+ really_unmap (mm);
+ else
+ mm = &(*mm)->next;
+ }
+
+ /* Adjust the age limit if there are too many bytes mapped, or
+ ** too many or too few files mapped.
+ */
+ if (mapped_bytes > DESIRED_MAX_MAPPED_BYTES)
+ expire_age = MAX ((expire_age * 2) / 3, DEFAULT_EXPIRE_AGE / 10);
+ else if (map_count > DESIRED_MAX_MAPPED_FILES)
+ expire_age = MAX ((expire_age * 2) / 3, DEFAULT_EXPIRE_AGE / 10);
+ else if (map_count < DESIRED_MAX_MAPPED_FILES / 2)
+ expire_age = MIN ((expire_age * 5) / 4, DEFAULT_EXPIRE_AGE * 3);
+
+ /* Really free excess blocks on the free list. */
+ while (free_count > DESIRED_FREE_COUNT)
+ {
+ m = free_maps;
+ free_maps = m->next;
+ --free_count;
+ free ((void *) m);
+ --alloc_count;
+ }
+}
+
+
+static void panic (void)
+{
+ Map **mm;
+ Map *m;
+
+ syslog (LOG_ERR, "mmc panic - freeing all unreferenced maps");
+
+ /* Really unmap all unreferenced entries. */
+ for (mm = &maps; *mm != (Map *) 0;)
+ {
+ m = *mm;
+ if (m->refcount == 0)
+ really_unmap (mm);
+ else
+ mm = &(*mm)->next;
+ }
+}
+
+
+static void really_unmap (Map ** mm)
+{
+ Map *m;
+
+ m = *mm;
+ if (m->size != 0)
+ {
+#ifdef HAVE_MMAP
+ if (munmap (m->addr, m->size) < 0)
+ syslog (LOG_ERR, "munmap - %m");
+#else /* HAVE_MMAP */
+ free ((void *) m->addr);
+#endif /* HAVE_MMAP */
+ }
+ /* Update the total byte count. */
+ mapped_bytes -= m->size;
+ /* And move the Map to the free list. */
+ *mm = m->next;
+ --map_count;
+ m->next = free_maps;
+ free_maps = m;
+ ++free_count;
+ /* This will sometimes break hash chains, but that's harmless; the
+ ** unmapping code that searches the hash table knows to keep searching.
+ */
+ hash_table[m->hash_idx] = (Map *) 0;
+}
+
+
+void mmc_destroy (void)
+{
+ Map *m;
+
+ while (maps != (Map *) 0)
+ really_unmap (&maps);
+ while (free_maps != (Map *) 0)
+ {
+ m = free_maps;
+ free_maps = m->next;
+ --free_count;
+ free ((void *) m);
+ --alloc_count;
+ }
+}
+
+
+/* Make sure the hash table is big enough. */
+static int check_hash_size (void)
+{
+ int i;
+ Map *m;
+
+ /* Are we just starting out? */
+ if (hash_table == (Map **) 0)
+ {
+ hash_size = INITIAL_HASH_SIZE;
+ hash_mask = hash_size - 1;
+ }
+ /* Is it at least three times bigger than the number of entries? */
+ else if (hash_size >= map_count * 3)
+ return 0;
+ else
+ {
+ /* No, got to expand. */
+ free ((void *) hash_table);
+ /* Double the hash size until it's big enough. */
+ do
+ {
+ hash_size = hash_size << 1;
+ }
+ while (hash_size < map_count * 6);
+ hash_mask = hash_size - 1;
+ }
+ /* Make the new table. */
+ hash_table = (Map **) malloc (hash_size * sizeof (Map *));
+ if (hash_table == (Map **) 0)
+ return -1;
+ /* Clear it. */
+ for (i = 0; i < hash_size; ++i)
+ hash_table[i] = (Map *) 0;
+ /* And rehash all entries. */
+ for (m = maps; m != (Map *) 0; m = m->next)
+ if (add_hash (m) < 0)
+ return -1;
+ return 0;
+}
+
+
+static int add_hash (Map * m)
+{
+ unsigned int h, he, i;
+
+ h = hash (m->ino, m->dev, m->size, m->ctime);
+ he = (h + hash_size - 1) & hash_mask;
+ for (i = h;; i = (i + 1) & hash_mask)
+ {
+ if (hash_table[i] == (Map *) 0)
+ {
+ hash_table[i] = m;
+ m->hash = h;
+ m->hash_idx = i;
+ return 0;
+ }
+ if (i == he)
+ break;
+ }
+ return -1;
+}
+
+
+static Map *find_hash (ino_t ino, dev_t dev, off_t size, time_t ctime)
+{
+ unsigned int h, he, i;
+ Map *m;
+
+ h = hash (ino, dev, size, ctime);
+ he = (h + hash_size - 1) & hash_mask;
+ for (i = h;; i = (i + 1) & hash_mask)
+ {
+ m = hash_table[i];
+ if (m == (Map *) 0)
+ break;
+ if (m->hash == h && m->ino == ino && m->dev == dev &&
+ m->size == size && m->ctime == ctime)
+ return m;
+ if (i == he)
+ break;
+ }
+ return (Map *) 0;
+}
+
+
+static unsigned int hash (ino_t ino, dev_t dev, off_t size, time_t ctime)
+{
+ unsigned int h = 177573;
+
+ h ^= ino;
+ h += h << 5;
+ h ^= dev;
+ h += h << 5;
+ h ^= size;
+ h += h << 5;
+ h ^= ctime;
+
+ return h & hash_mask;
+}
+
+
+/* Generate debugging statistics syslog message. */
+void mmc_logstats (long secs)
+{
+ syslog (LOG_INFO,
+ " map cache - %d allocated, %d active (%ld bytes), %d free; hash size: %d; expire age: %ld",
+ alloc_count, map_count, (int64_t) mapped_bytes, free_count,
+ hash_size, expire_age);
+ if (map_count + free_count != alloc_count)
+ syslog (LOG_ERR, "map counts don't add up!");
+}
diff --git a/gb.httpd/src/mmc.h b/gb.httpd/src/mmc.h
new file mode 100644
index 000000000..6fa7087a1
--- /dev/null
+++ b/gb.httpd/src/mmc.h
@@ -0,0 +1,55 @@
+/* mmc.h - header file for mmap cache package
+**
+** Copyright © 1998 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+#ifndef _MMC_H_
+#define _MMC_H_
+
+/* Returns an mmap()ed area for the given file, or (void*) 0 on errors.
+** If you have a stat buffer on the file, pass it in, otherwise pass 0.
+** Same for the current time.
+*/
+extern void *mmc_map (char *filename, struct stat *sbP, struct timeval *nowP);
+
+/* Done with an mmap()ed area that was returned by mmc_map().
+** If you have a stat buffer on the file, pass it in, otherwise pass 0.
+** Same for the current time.
+*/
+extern void mmc_unmap (void *addr, struct stat *sbP, struct timeval *nowP);
+
+/* Clean up the mmc package, freeing any unused storage.
+** This should be called periodically, say every five minutes.
+** If you have the current time, pass it in, otherwise pass 0.
+*/
+extern void mmc_cleanup (struct timeval *nowP);
+
+/* Free all storage, usually in preparation for exitting. */
+extern void mmc_destroy (void);
+
+/* Generate debugging statistics syslog message. */
+extern void mmc_logstats (long secs);
+
+#endif /* _MMC_H_ */
diff --git a/gb.httpd/src/strerror.c b/gb.httpd/src/strerror.c
new file mode 100644
index 000000000..26e0e7936
--- /dev/null
+++ b/gb.httpd/src/strerror.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 1988 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the University of California, Berkeley. The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)strerror.c 5.1 (Berkeley) 4/9/89";
+#endif /* LIBC_SCCS and not lint */
+
+#include
+
+#include
+
+char *strerror (errnum)
+ int errnum;
+{
+ extern int sys_nerr;
+ extern char *sys_errlist[];
+ static char ebuf[20];
+
+ if ((unsigned int) errnum < sys_nerr)
+ return (sys_errlist[errnum]);
+ (void) sprintf (ebuf, "Unknown error: %d", errnum);
+ return (ebuf);
+}
diff --git a/gb.httpd/src/tdate_parse.c b/gb.httpd/src/tdate_parse.c
new file mode 100644
index 000000000..212e3d6eb
--- /dev/null
+++ b/gb.httpd/src/tdate_parse.c
@@ -0,0 +1,313 @@
+/* tdate_parse - parse string dates into internal form, stripped-down version
+**
+** Copyright © 1995 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+/* This is a stripped-down version of date_parse.c, available at
+** http://www.acme.com/software/date_parse/
+*/
+
+#include
+
+#include
+#ifdef HAVE_MEMORY_H
+#include
+#endif
+#include
+#include
+#include
+#include
+
+#include "tdate_parse.h"
+
+
+struct strlong
+{
+ char *s;
+ long l;
+};
+
+
+static void pound_case (char *str)
+{
+ for (; *str != '\0'; ++str)
+ {
+ if (isupper ((int) *str))
+ *str = tolower ((int) *str);
+ }
+}
+
+static int strlong_compare (v1, v2)
+ char *v1;
+ char *v2;
+{
+ return strcmp (((struct strlong *) v1)->s, ((struct strlong *) v2)->s);
+}
+
+
+static int strlong_search (char *str, struct strlong *tab, int n, long *lP)
+{
+ int i, h, l, r;
+
+ l = 0;
+ h = n - 1;
+ for (;;)
+ {
+ i = (h + l) / 2;
+ r = strcmp (str, tab[i].s);
+ if (r < 0)
+ h = i - 1;
+ else if (r > 0)
+ l = i + 1;
+ else
+ {
+ *lP = tab[i].l;
+ return 1;
+ }
+ if (h < l)
+ return 0;
+ }
+}
+
+
+static int scan_wday (char *str_wday, long *tm_wdayP)
+{
+ static struct strlong wday_tab[] = {
+ {"sun", 0}, {"sunday", 0},
+ {"mon", 1}, {"monday", 1},
+ {"tue", 2}, {"tuesday", 2},
+ {"wed", 3}, {"wednesday", 3},
+ {"thu", 4}, {"thursday", 4},
+ {"fri", 5}, {"friday", 5},
+ {"sat", 6}, {"saturday", 6},
+ };
+ static int sorted = 0;
+
+ if (!sorted)
+ {
+ (void) qsort (wday_tab, sizeof (wday_tab) / sizeof (struct strlong),
+ sizeof (struct strlong), strlong_compare);
+ sorted = 1;
+ }
+ pound_case (str_wday);
+ return strlong_search (str_wday, wday_tab,
+ sizeof (wday_tab) / sizeof (struct strlong),
+ tm_wdayP);
+}
+
+
+static int scan_mon (char *str_mon, long *tm_monP)
+{
+ static struct strlong mon_tab[] = {
+ {"jan", 0}, {"january", 0},
+ {"feb", 1}, {"february", 1},
+ {"mar", 2}, {"march", 2},
+ {"apr", 3}, {"april", 3},
+ {"may", 4},
+ {"jun", 5}, {"june", 5},
+ {"jul", 6}, {"july", 6},
+ {"aug", 7}, {"august", 7},
+ {"sep", 8}, {"september", 8},
+ {"oct", 9}, {"october", 9},
+ {"nov", 10}, {"november", 10},
+ {"dec", 11}, {"december", 11},
+ };
+ static int sorted = 0;
+
+ if (!sorted)
+ {
+ (void) qsort (mon_tab, sizeof (mon_tab) / sizeof (struct strlong),
+ sizeof (struct strlong), strlong_compare);
+ sorted = 1;
+ }
+ pound_case (str_mon);
+ return strlong_search (str_mon, mon_tab,
+ sizeof (mon_tab) / sizeof (struct strlong), tm_monP);
+}
+
+
+static int is_leap (int year)
+{
+ return year % 400 ? (year % 100 ? (year % 4 ? 0 : 1) : 0) : 1;
+}
+
+
+/* Basically the same as mktime(). */
+static time_t tm_to_time (struct tm *tmP)
+{
+ time_t t;
+ static int monthtab[12] = {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+ };
+
+ /* Years since epoch, converted to days. */
+ t = (tmP->tm_year - 70) * 365;
+ /* Leap days for previous years. */
+ t += (tmP->tm_year - 69) / 4;
+ /* Days for the beginning of this month. */
+ t += monthtab[tmP->tm_mon];
+ /* Leap day for this year. */
+ if (tmP->tm_mon >= 2 && is_leap (tmP->tm_year + 1900))
+ ++t;
+ /* Days since the beginning of this month. */
+ t += tmP->tm_mday - 1; /* 1-based field */
+ /* Hours, minutes, and seconds. */
+ t = t * 24 + tmP->tm_hour;
+ t = t * 60 + tmP->tm_min;
+ t = t * 60 + tmP->tm_sec;
+
+ return t;
+}
+
+
+time_t tdate_parse (char *str)
+{
+ struct tm tm;
+ char *cp;
+ char str_mon[500], str_wday[500];
+ int tm_sec, tm_min, tm_hour, tm_mday, tm_year;
+ long tm_mon, tm_wday;
+ time_t t;
+
+ /* Initialize. */
+ (void) memset ((char *) &tm, 0, sizeof (struct tm));
+
+ /* Skip initial whitespace ourselves - sscanf is clumsy at this. */
+ for (cp = str; *cp == ' ' || *cp == '\t'; ++cp)
+ continue;
+
+ /* And do the sscanfs. WARNING: you can add more formats here,
+ ** but be careful! You can easily screw up the parsing of existing
+ ** formats when you add new ones. The order is important.
+ */
+
+ /* DD-mth-YY HH:MM:SS GMT */
+ if (sscanf (cp, "%d-%400[a-zA-Z]-%d %d:%d:%d GMT",
+ &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
+ &tm_sec) == 6 && scan_mon (str_mon, &tm_mon))
+ {
+ tm.tm_mday = tm_mday;
+ tm.tm_mon = tm_mon;
+ tm.tm_year = tm_year;
+ tm.tm_hour = tm_hour;
+ tm.tm_min = tm_min;
+ tm.tm_sec = tm_sec;
+ }
+
+ /* DD mth YY HH:MM:SS GMT */
+ else if (sscanf (cp, "%d %400[a-zA-Z] %d %d:%d:%d GMT",
+ &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
+ &tm_sec) == 6 && scan_mon (str_mon, &tm_mon))
+ {
+ tm.tm_mday = tm_mday;
+ tm.tm_mon = tm_mon;
+ tm.tm_year = tm_year;
+ tm.tm_hour = tm_hour;
+ tm.tm_min = tm_min;
+ tm.tm_sec = tm_sec;
+ }
+
+ /* HH:MM:SS GMT DD-mth-YY */
+ else if (sscanf (cp, "%d:%d:%d GMT %d-%400[a-zA-Z]-%d",
+ &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon,
+ &tm_year) == 6 && scan_mon (str_mon, &tm_mon))
+ {
+ tm.tm_hour = tm_hour;
+ tm.tm_min = tm_min;
+ tm.tm_sec = tm_sec;
+ tm.tm_mday = tm_mday;
+ tm.tm_mon = tm_mon;
+ tm.tm_year = tm_year;
+ }
+
+ /* HH:MM:SS GMT DD mth YY */
+ else if (sscanf (cp, "%d:%d:%d GMT %d %400[a-zA-Z] %d",
+ &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon,
+ &tm_year) == 6 && scan_mon (str_mon, &tm_mon))
+ {
+ tm.tm_hour = tm_hour;
+ tm.tm_min = tm_min;
+ tm.tm_sec = tm_sec;
+ tm.tm_mday = tm_mday;
+ tm.tm_mon = tm_mon;
+ tm.tm_year = tm_year;
+ }
+
+ /* wdy, DD-mth-YY HH:MM:SS GMT */
+ else if (sscanf (cp, "%400[a-zA-Z], %d-%400[a-zA-Z]-%d %d:%d:%d GMT",
+ str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
+ &tm_sec) == 7 &&
+ scan_wday (str_wday, &tm_wday) && scan_mon (str_mon, &tm_mon))
+ {
+ tm.tm_wday = tm_wday;
+ tm.tm_mday = tm_mday;
+ tm.tm_mon = tm_mon;
+ tm.tm_year = tm_year;
+ tm.tm_hour = tm_hour;
+ tm.tm_min = tm_min;
+ tm.tm_sec = tm_sec;
+ }
+
+ /* wdy, DD mth YY HH:MM:SS GMT */
+ else if (sscanf (cp, "%400[a-zA-Z], %d %400[a-zA-Z] %d %d:%d:%d GMT",
+ str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
+ &tm_sec) == 7 &&
+ scan_wday (str_wday, &tm_wday) && scan_mon (str_mon, &tm_mon))
+ {
+ tm.tm_wday = tm_wday;
+ tm.tm_mday = tm_mday;
+ tm.tm_mon = tm_mon;
+ tm.tm_year = tm_year;
+ tm.tm_hour = tm_hour;
+ tm.tm_min = tm_min;
+ tm.tm_sec = tm_sec;
+ }
+
+ /* wdy mth DD HH:MM:SS GMT YY */
+ else if (sscanf (cp, "%400[a-zA-Z] %400[a-zA-Z] %d %d:%d:%d GMT %d",
+ str_wday, str_mon, &tm_mday, &tm_hour, &tm_min, &tm_sec,
+ &tm_year) == 7 &&
+ scan_wday (str_wday, &tm_wday) && scan_mon (str_mon, &tm_mon))
+ {
+ tm.tm_wday = tm_wday;
+ tm.tm_mon = tm_mon;
+ tm.tm_mday = tm_mday;
+ tm.tm_hour = tm_hour;
+ tm.tm_min = tm_min;
+ tm.tm_sec = tm_sec;
+ tm.tm_year = tm_year;
+ }
+ else
+ return (time_t) - 1;
+
+ if (tm.tm_year > 1900)
+ tm.tm_year -= 1900;
+ else if (tm.tm_year < 70)
+ tm.tm_year += 100;
+
+ t = tm_to_time (&tm);
+
+ return t;
+}
diff --git a/gb.httpd/src/tdate_parse.h b/gb.httpd/src/tdate_parse.h
new file mode 100644
index 000000000..58811ca5b
--- /dev/null
+++ b/gb.httpd/src/tdate_parse.h
@@ -0,0 +1,33 @@
+/* tdate_parse.h - parse string dates into internal form, stripped-down version
+**
+** Copyright © 1995 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+#ifndef _TDATE_PARSE_H_
+#define _TDATE_PARSE_H_
+
+extern time_t tdate_parse (char *str);
+
+#endif /* _TDATE_PARSE_H_ */
diff --git a/gb.httpd/src/thttpd.c b/gb.httpd/src/thttpd.c
new file mode 100644
index 000000000..29afd90b3
--- /dev/null
+++ b/gb.httpd/src/thttpd.c
@@ -0,0 +1,2235 @@
+/* thttpd.c - tiny/turbo/throttling HTTP server
+**
+** Copyright (c) 1995,1998,1999,2000,2001 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+#include "main.h"
+#include "thttpd.h"
+#include "version.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#ifdef HAVE_FCNTL_H
+#include
+#endif
+#include
+#ifdef HAVE_GRP_H
+#include
+#endif
+#include
+#include
+#include
+#include
+//#include
+#ifdef TIME_WITH_SYS_TIME
+#include
+#endif
+#include
+
+#include "fdwatch.h"
+#include "libhttpd.h"
+#include "mmc.h"
+#include "timers.h"
+#include "match.h"
+
+#ifndef SHUT_WR
+#define SHUT_WR 1
+#endif
+
+#ifndef HAVE_INT64T
+typedef long long int64_t;
+#endif
+
+
+static char *argv0;
+static int debug;
+static unsigned short port;
+static char *dir;
+static char *data_dir;
+static int do_chroot, no_log, no_symlink_check, do_vhost, do_global_passwd;
+static char *cgi_pattern;
+static int cgi_limit;
+static int cgi_timelimit;
+static char *url_pattern;
+static int no_empty_referers;
+static char *local_pattern;
+static char *logfile;
+static char *throttlefile;
+static char *hostname;
+static char *pidfile;
+static char *user;
+static char *charset;
+static char *p3p;
+static int max_age;
+
+
+typedef struct
+{
+ char *pattern;
+ long max_limit, min_limit;
+ long rate;
+ off_t bytes_since_avg;
+ int num_sending;
+} throttletab;
+static throttletab *throttles;
+static int numthrottles, maxthrottles;
+
+#define THROTTLE_NOLIMIT -1
+
+
+typedef struct
+{
+ int conn_state;
+ int next_free_connect;
+ httpd_conn *hc;
+ int tnums[MAXTHROTTLENUMS]; /* throttle indexes */
+ int numtnums;
+ long max_limit, min_limit;
+ time_t started_at, active_at;
+ Timer *wakeup_timer;
+ Timer *linger_timer;
+ long wouldblock_delay;
+ off_t bytes;
+ off_t end_byte_index;
+ off_t next_byte_index;
+} connecttab;
+static connecttab *connects;
+static int num_connects, max_connects, first_free_connect;
+static int httpd_conn_count;
+
+/* The connection states. */
+#define CNST_FREE 0
+#define CNST_READING 1
+#define CNST_SENDING 2
+#define CNST_PAUSING 3
+#define CNST_LINGERING 4
+
+
+static httpd_server *hs = (httpd_server *) 0;
+int terminate = 0;
+time_t start_time, stats_time;
+long stats_connections;
+off_t stats_bytes;
+int stats_simultaneous;
+
+static volatile int got_hup, got_usr1, watchdog_flag;
+
+
+/* Forwards. */
+static void parse_args (int argc, char **argv);
+/*static void usage (void);
+static void read_config (char *filename);
+static void value_required (char *name, char *value);
+static void no_value_required (char *name, char *value);*/
+static char *e_strdup (char *oldstr);
+static void lookup_hostname (httpd_sockaddr * sa4P, size_t sa4_len,
+ int *gotv4P, httpd_sockaddr * sa6P,
+ size_t sa6_len, int *gotv6P);
+static void read_throttlefile (char *throttlefile);
+static void shut_down (void);
+static int handle_newconnect (struct timeval *tvP, int listen_fd);
+static void handle_read (connecttab * c, struct timeval *tvP);
+static void handle_send (connecttab * c, struct timeval *tvP);
+static void handle_linger (connecttab * c, struct timeval *tvP);
+static int check_throttles (connecttab * c);
+static void clear_throttles (connecttab * c, struct timeval *tvP);
+static void update_throttles (ClientData client_data, struct timeval *nowP);
+static void finish_connection (connecttab * c, struct timeval *tvP);
+static void clear_connection (connecttab * c, struct timeval *tvP);
+static void really_clear_connection (connecttab * c, struct timeval *tvP);
+static void idle (ClientData client_data, struct timeval *nowP);
+static void wakeup_connection (ClientData client_data, struct timeval *nowP);
+static void linger_clear_connection (ClientData client_data,
+ struct timeval *nowP);
+static void occasional (ClientData client_data, struct timeval *nowP);
+#ifdef STATS_TIME
+static void show_stats (ClientData client_data, struct timeval *nowP);
+#endif /* STATS_TIME */
+static void logstats (struct timeval *nowP);
+static void thttpd_logstats (long secs);
+
+
+/* SIGTERM and SIGINT say to exit immediately. */
+static void handle_term (int sig)
+{
+ /* Don't need to set up the handler again, since it's a one-shot. */
+
+ shut_down ();
+ syslog (LOG_NOTICE, "exiting due to signal %d", sig);
+ closelog ();
+ exit (1);
+}
+
+
+/* SIGCHLD - a chile process exitted, so we need to reap the zombie */
+static void handle_chld (int sig)
+{
+ const int oerrno = errno;
+ pid_t pid;
+ int status;
+
+#ifndef HAVE_SIGSET
+ /* Set up handler again. */
+ (void) signal (SIGCHLD, handle_chld);
+#endif /* ! HAVE_SIGSET */
+
+ /* Reap defunct children until there aren't any more. */
+ for (;;)
+ {
+#ifdef HAVE_WAITPID
+ pid = waitpid ((pid_t) - 1, &status, WNOHANG);
+#else /* HAVE_WAITPID */
+ pid = wait3 (&status, WNOHANG, (struct rusage *) 0);
+#endif /* HAVE_WAITPID */
+ if ((int) pid == 0) /* none left */
+ break;
+ if ((int) pid < 0)
+ {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ /* ECHILD shouldn't happen with the WNOHANG option,
+ ** but with some kernels it does anyway. Ignore it.
+ */
+ if (errno != ECHILD)
+ syslog (LOG_ERR, "child wait - %m");
+ break;
+ }
+ /* Decrement the CGI count. Note that this is not accurate, since
+ ** each CGI can involve two or even three child processes.
+ ** Decrementing for each child means that when there is heavy CGI
+ ** activity, the count will be lower than it should be, and therefore
+ ** more CGIs will be allowed than should be.
+ */
+ if (hs != (httpd_server *) 0)
+ {
+ --hs->cgi_count;
+ if (hs->cgi_count < 0)
+ hs->cgi_count = 0;
+ }
+ }
+
+ /* Restore previous errno. */
+ errno = oerrno;
+}
+
+
+/* SIGHUP says to re-open the log file. */
+static void handle_hup (int sig)
+{
+ const int oerrno = errno;
+
+#ifndef HAVE_SIGSET
+ /* Set up handler again. */
+ (void) signal (SIGHUP, handle_hup);
+#endif /* ! HAVE_SIGSET */
+
+ /* Just set a flag that we got the signal. */
+ got_hup = 1;
+
+ /* Restore previous errno. */
+ errno = oerrno;
+}
+
+
+/* SIGUSR1 says to exit as soon as all current connections are done. */
+static void handle_usr1 (int sig)
+{
+ /* Don't need to set up the handler again, since it's a one-shot. */
+
+ if (num_connects == 0)
+ {
+ /* If there are no active connections we want to exit immediately
+ ** here. Not only is it faster, but without any connections the
+ ** main loop won't wake up until the next new connection.
+ */
+ shut_down ();
+ syslog (LOG_NOTICE, "exiting");
+ closelog ();
+ exit (0);
+ }
+
+ /* Otherwise, just set a flag that we got the signal. */
+ got_usr1 = 1;
+
+ /* Don't need to restore old errno, since we didn't do any syscalls. */
+}
+
+
+/* SIGUSR2 says to generate the stats syslogs immediately. */
+static void handle_usr2 (int sig)
+{
+ const int oerrno = errno;
+
+#ifndef HAVE_SIGSET
+ /* Set up handler again. */
+ (void) signal (SIGUSR2, handle_usr2);
+#endif /* ! HAVE_SIGSET */
+
+ logstats ((struct timeval *) 0);
+
+ /* Restore previous errno. */
+ errno = oerrno;
+}
+
+
+/* SIGALRM is used as a watchdog. */
+static void handle_alrm (int sig)
+{
+ const int oerrno = errno;
+
+ /* If nothing has been happening */
+ if (!watchdog_flag)
+ {
+ /* Try changing dirs to someplace we can write. */
+ (void) chdir ("/tmp");
+ /* Dump core. */
+ abort ();
+ }
+ watchdog_flag = 0;
+
+#ifndef HAVE_SIGSET
+ /* Set up handler again. */
+ (void) signal (SIGALRM, handle_alrm);
+#endif /* ! HAVE_SIGSET */
+ /* Set up alarm again. */
+ (void) alarm (OCCASIONAL_TIME * 3);
+
+ /* Restore previous errno. */
+ errno = oerrno;
+}
+
+
+static void re_open_logfile (void)
+{
+ FILE *logfp;
+
+ if (no_log || hs == (httpd_server *) 0)
+ return;
+
+ /* Re-open the log file. */
+ if (logfile != (char *) 0 && strcmp (logfile, "-") != 0)
+ {
+ syslog (LOG_NOTICE, "re-opening logfile");
+ logfp = fopen (logfile, "a");
+ if (logfp == (FILE *) 0)
+ {
+ syslog (LOG_CRIT, "re-opening %.80s - %m", logfile);
+ return;
+ }
+ (void) fcntl (fileno (logfp), F_SETFD, 1);
+ httpd_set_logfp (hs, logfp);
+ }
+}
+
+
+int thttpd_main (int argc, char **argv)
+{
+ char *cp;
+ struct passwd *pwd;
+ uid_t uid = 32767;
+ gid_t gid = 32767;
+ char cwd[MAXPATHLEN + 1];
+ FILE *logfp;
+ int num_ready;
+ int cnum;
+ connecttab *c;
+ httpd_conn *hc;
+ httpd_sockaddr sa4;
+ httpd_sockaddr sa6;
+ int gotv4, gotv6;
+ struct timeval tv;
+
+ argv0 = argv[0];
+
+ cp = strrchr (argv0, '/');
+ if (cp != (char *) 0)
+ ++cp;
+ else
+ cp = argv0;
+
+ //openlog( cp, LOG_NDELAY|LOG_PID, LOG_FACILITY );
+
+ /* Handle command-line arguments. */
+ parse_args (argc, argv);
+ debug = 1;
+ do_chroot = 0;
+
+ /* Read zone info now, in case we chroot(). */
+ tzset ();
+
+ /* Look up hostname now, in case we chroot(). */
+ lookup_hostname (&sa4, sizeof (sa4), &gotv4, &sa6, sizeof (sa6), &gotv6);
+ if (!(gotv4 || gotv6))
+ {
+ syslog (LOG_ERR, "can't find any valid address");
+ (void) fprintf (stderr, "%s: can't find any valid address\n", argv0);
+ exit (1);
+ }
+
+ /* Throttle file. */
+ numthrottles = 0;
+ maxthrottles = 0;
+ throttles = (throttletab *) 0;
+ if (throttlefile != (char *) 0)
+ read_throttlefile (throttlefile);
+
+ /* If we're root and we're going to become another user, get the uid/gid
+ ** now.
+ */
+ if (getuid () == 0)
+ {
+ pwd = getpwnam (user);
+ if (pwd == (struct passwd *) 0)
+ {
+ syslog (LOG_CRIT, "unknown user - '%.80s'", user);
+ (void) fprintf (stderr, "%s: unknown user - '%s'\n", argv0, user);
+ exit (1);
+ }
+ uid = pwd->pw_uid;
+ gid = pwd->pw_gid;
+ }
+
+ /* Log file. */
+#if 0
+ if (logfile != (char *) 0)
+ {
+ if (strcmp (logfile, "/dev/null") == 0)
+ {
+ no_log = 1;
+ logfp = (FILE *) 0;
+ }
+ else if (strcmp (logfile, "-") == 0)
+ logfp = stdout;
+ else
+ {
+ logfp = fopen (logfile, "a");
+ if (logfp == (FILE *) 0)
+ {
+ syslog (LOG_CRIT, "%.80s - %m", logfile);
+ perror (logfile);
+ exit (1);
+ }
+ if (logfile[0] != '/')
+ {
+ syslog (LOG_WARNING,
+ "logfile is not an absolute path, you may not be able to re-open it");
+ (void) fprintf (stderr,
+ "%s: logfile is not an absolute path, you may not be able to re-open it\n",
+ argv0);
+ }
+ (void) fcntl (fileno (logfp), F_SETFD, 1);
+ if (getuid () == 0)
+ {
+ /* If we are root then we chown the log file to the user we'll
+ ** be switching to.
+ */
+ if (fchown (fileno (logfp), uid, gid) < 0)
+ {
+ syslog (LOG_WARNING, "fchown logfile - %m");
+ perror ("fchown logfile");
+ }
+ }
+ }
+ }
+ else
+#endif
+ logfp = (FILE *) 0;
+
+#if 0
+ /* Switch directories if requested. */
+ if (dir != (char *) 0)
+ {
+ if (chdir (dir) < 0)
+ {
+ syslog (LOG_CRIT, "chdir - %m");
+ perror ("chdir");
+ exit (1);
+ }
+ }
+#ifdef USE_USER_DIR
+ else if (getuid () == 0)
+ {
+ /* No explicit directory was specified, we're root, and the
+ ** USE_USER_DIR option is set - switch to the specified user's
+ ** home dir.
+ */
+ if (chdir (pwd->pw_dir) < 0)
+ {
+ syslog (LOG_CRIT, "chdir - %m");
+ perror ("chdir");
+ exit (1);
+ }
+ }
+#endif /* USE_USER_DIR */
+#endif
+
+ /* Get current directory. */
+ (void) getcwd (cwd, sizeof (cwd) - 1);
+ if (cwd[strlen (cwd) - 1] != '/')
+ (void) strcat (cwd, "/");
+
+ if (0) //!debug)
+ {
+ /* We're not going to use stdin stdout or stderr from here on, so close
+ ** them to save file descriptors.
+ */
+ (void) fclose (stdin);
+ if (logfp != stdout)
+ (void) fclose (stdout);
+ (void) fclose (stderr);
+
+ /* Daemonize - make ourselves a subprocess. */
+#ifdef HAVE_DAEMON
+ if (daemon (1, 1) < 0)
+ {
+ syslog (LOG_CRIT, "daemon - %m");
+ exit (1);
+ }
+#else /* HAVE_DAEMON */
+ switch (fork ())
+ {
+ case 0:
+ break;
+ case -1:
+ syslog (LOG_CRIT, "fork - %m");
+ exit (1);
+ default:
+ exit (0);
+ }
+#ifdef HAVE_SETSID
+ (void) setsid ();
+#endif /* HAVE_SETSID */
+#endif /* HAVE_DAEMON */
+ }
+ else
+ {
+ /* Even if we don't daemonize, we still want to disown our parent
+ ** process.
+ */
+#ifdef HAVE_SETSID
+ (void) setsid ();
+#endif /* HAVE_SETSID */
+ }
+
+#if 0
+ if (pidfile != (char *) 0)
+ {
+ /* Write the PID file. */
+ FILE *pidfp = fopen (pidfile, "w");
+ if (pidfp == (FILE *) 0)
+ {
+ syslog (LOG_CRIT, "%.80s - %m", pidfile);
+ exit (1);
+ }
+ (void) fprintf (pidfp, "%d\n", (int) getpid ());
+ (void) fclose (pidfp);
+ }
+#endif
+
+ /* Initialize the fdwatch package. Have to do this before chroot,
+ ** if /dev/poll is used.
+ */
+ max_connects = fdwatch_get_nfiles ();
+ if (max_connects < 0)
+ {
+ syslog (LOG_CRIT, "fdwatch initialization failure");
+ exit (1);
+ }
+ max_connects -= SPARE_FDS;
+
+ /* Chroot if requested. */
+ if (0) //do_chroot)
+ {
+ if (chroot (cwd) < 0)
+ {
+ syslog (LOG_CRIT, "chroot - %m");
+ perror ("chroot");
+ exit (1);
+ }
+ /* If we're logging and the logfile's pathname begins with the
+ ** chroot tree's pathname, then elide the chroot pathname so
+ ** that the logfile pathname still works from inside the chroot
+ ** tree.
+ */
+ if (logfile != (char *) 0 && strcmp (logfile, "-") != 0)
+ {
+ if (strncmp (logfile, cwd, strlen (cwd)) == 0)
+ {
+ (void) strcpy (logfile, &logfile[strlen (cwd) - 1]);
+ /* (We already guaranteed that cwd ends with a slash, so leaving
+ ** that slash in logfile makes it an absolute pathname within
+ ** the chroot tree.)
+ */
+ }
+ else
+ {
+ syslog (LOG_WARNING,
+ "logfile is not within the chroot tree, you will not be able to re-open it");
+ (void) fprintf (stderr,
+ "%s: logfile is not within the chroot tree, you will not be able to re-open it\n",
+ argv0);
+ }
+ }
+ (void) strcpy (cwd, "/");
+ /* Always chdir to / after a chroot. */
+ if (chdir (cwd) < 0)
+ {
+ syslog (LOG_CRIT, "chroot chdir - %m");
+ perror ("chroot chdir");
+ exit (1);
+ }
+ }
+
+ /* Switch directories again if requested. */
+#if 0
+ if (data_dir != (char *) 0)
+ {
+ if (chdir (data_dir) < 0)
+ {
+ syslog (LOG_CRIT, "data_dir chdir - %m");
+ perror ("data_dir chdir");
+ exit (1);
+ }
+ }
+#endif
+
+ /* Set up to catch signals. */
+#ifdef HAVE_SIGSET
+ (void) sigset (SIGTERM, handle_term);
+ (void) sigset (SIGINT, handle_term);
+ (void) sigset (SIGCHLD, handle_chld);
+ (void) sigset (SIGPIPE, SIG_IGN); /* get EPIPE instead */
+ (void) sigset (SIGHUP, handle_hup);
+ (void) sigset (SIGUSR1, handle_usr1);
+ (void) sigset (SIGUSR2, handle_usr2);
+ (void) sigset (SIGALRM, handle_alrm);
+#else /* HAVE_SIGSET */
+ (void) signal (SIGTERM, handle_term);
+ (void) signal (SIGINT, handle_term);
+ (void) signal (SIGCHLD, handle_chld);
+ (void) signal (SIGPIPE, SIG_IGN); /* get EPIPE instead */
+ (void) signal (SIGHUP, handle_hup);
+ (void) signal (SIGUSR1, handle_usr1);
+ (void) signal (SIGUSR2, handle_usr2);
+ (void) signal (SIGALRM, handle_alrm);
+#endif /* HAVE_SIGSET */
+ got_hup = 0;
+ got_usr1 = 0;
+ watchdog_flag = 0;
+ (void) alarm (OCCASIONAL_TIME * 3);
+
+ /* Initialize the timer package. */
+ tmr_init ();
+
+ /* Initialize the HTTP layer. Got to do this before giving up root,
+ ** so that we can bind to a privileged port.
+ */
+ hs = httpd_initialize (hostname,
+ gotv4 ? &sa4 : (httpd_sockaddr *) 0,
+ gotv6 ? &sa6 : (httpd_sockaddr *) 0, port,
+ cgi_pattern, cgi_limit, cgi_timelimit, charset, p3p,
+ max_age, cwd, no_log, logfp, no_symlink_check,
+ do_vhost, do_global_passwd, url_pattern,
+ local_pattern, no_empty_referers);
+ if (hs == (httpd_server *) 0)
+ exit (1);
+
+ /* Set up the occasional timer. */
+ if (tmr_create
+ ((struct timeval *) 0, occasional, JunkClientData,
+ OCCASIONAL_TIME * 1000L, 1) == (Timer *) 0)
+ {
+ syslog (LOG_CRIT, "tmr_create(occasional) failed");
+ exit (1);
+ }
+ /* Set up the idle timer. */
+ if (tmr_create ((struct timeval *) 0, idle, JunkClientData, 5 * 1000L, 1) ==
+ (Timer *) 0)
+ {
+ syslog (LOG_CRIT, "tmr_create(idle) failed");
+ exit (1);
+ }
+ if (numthrottles > 0)
+ {
+ /* Set up the throttles timer. */
+ if (tmr_create
+ ((struct timeval *) 0, update_throttles, JunkClientData,
+ THROTTLE_TIME * 1000L, 1) == (Timer *) 0)
+ {
+ syslog (LOG_CRIT, "tmr_create(update_throttles) failed");
+ exit (1);
+ }
+ }
+#ifdef STATS_TIME
+ /* Set up the stats timer. */
+ if (tmr_create
+ ((struct timeval *) 0, show_stats, JunkClientData, STATS_TIME * 1000L,
+ 1) == (Timer *) 0)
+ {
+ syslog (LOG_CRIT, "tmr_create(show_stats) failed");
+ exit (1);
+ }
+#endif /* STATS_TIME */
+ start_time = stats_time = time ((time_t *) 0);
+ stats_connections = 0;
+ stats_bytes = 0;
+ stats_simultaneous = 0;
+
+ /* If we're root, try to become someone else. */
+ if (getuid () == 0)
+ {
+ /* Set aux groups to null. */
+ if (setgroups (0, (const gid_t *) 0) < 0)
+ {
+ syslog (LOG_CRIT, "setgroups - %m");
+ exit (1);
+ }
+ /* Set primary group. */
+ if (setgid (gid) < 0)
+ {
+ syslog (LOG_CRIT, "setgid - %m");
+ exit (1);
+ }
+ /* Try setting aux groups correctly - not critical if this fails. */
+ if (initgroups (user, gid) < 0)
+ syslog (LOG_WARNING, "initgroups - %m");
+#ifdef HAVE_SETLOGIN
+ /* Set login name. */
+ (void) setlogin (user);
+#endif /* HAVE_SETLOGIN */
+ /* Set uid. */
+ if (setuid (uid) < 0)
+ {
+ syslog (LOG_CRIT, "setuid - %m");
+ exit (1);
+ }
+ /* Check for unnecessary security exposure. */
+ if (!do_chroot)
+ syslog (LOG_WARNING,
+ "started as root without requesting chroot(), warning only");
+ }
+
+ /* Initialize our connections table. */
+ connects = NEW (connecttab, max_connects);
+ if (connects == (connecttab *) 0)
+ {
+ syslog (LOG_CRIT, "out of memory allocating a connecttab");
+ exit (1);
+ }
+ for (cnum = 0; cnum < max_connects; ++cnum)
+ {
+ connects[cnum].conn_state = CNST_FREE;
+ connects[cnum].next_free_connect = cnum + 1;
+ connects[cnum].hc = (httpd_conn *) 0;
+ }
+ connects[max_connects - 1].next_free_connect = -1; /* end of link list */
+ first_free_connect = 0;
+ num_connects = 0;
+ httpd_conn_count = 0;
+
+ if (hs != (httpd_server *) 0)
+ {
+ if (hs->listen4_fd != -1)
+ fdwatch_add_fd (hs->listen4_fd, (void *) 0, FDW_READ);
+ if (hs->listen6_fd != -1)
+ fdwatch_add_fd (hs->listen6_fd, (void *) 0, FDW_READ);
+ }
+
+ /* Main loop. */
+ (void) gettimeofday (&tv, (struct timezone *) 0);
+ while ((!terminate) || num_connects > 0)
+ {
+ /* Do we need to re-open the log file? */
+ if (got_hup)
+ {
+ re_open_logfile ();
+ got_hup = 0;
+ }
+
+ /* Do the fd watch. */
+ num_ready = fdwatch (tmr_mstimeout (&tv));
+ if (num_ready < 0)
+ {
+ if (errno == EINTR || errno == EAGAIN)
+ continue; /* try again */
+ syslog (LOG_ERR, "fdwatch - %m");
+ exit (1);
+ }
+ (void) gettimeofday (&tv, (struct timezone *) 0);
+
+ if (num_ready == 0)
+ {
+ /* No fd's are ready - run the timers. */
+ tmr_run (&tv);
+ continue;
+ }
+
+ /* Is it a new connection? */
+ if (hs != (httpd_server *) 0 && hs->listen6_fd != -1 &&
+ fdwatch_check_fd (hs->listen6_fd))
+ {
+ if (handle_newconnect (&tv, hs->listen6_fd))
+ /* Go around the loop and do another fdwatch, rather than
+ ** dropping through and processing existing connections.
+ ** New connections always get priority.
+ */
+ continue;
+ }
+ if (hs != (httpd_server *) 0 && hs->listen4_fd != -1 &&
+ fdwatch_check_fd (hs->listen4_fd))
+ {
+ if (handle_newconnect (&tv, hs->listen4_fd))
+ /* Go around the loop and do another fdwatch, rather than
+ ** dropping through and processing existing connections.
+ ** New connections always get priority.
+ */
+ continue;
+ }
+
+ /* Find the connections that need servicing. */
+ while ((c =
+ (connecttab *) fdwatch_get_next_client_data ()) !=
+ (connecttab *) - 1)
+ {
+ if (c == (connecttab *) 0)
+ continue;
+ hc = c->hc;
+ if (!fdwatch_check_fd (hc->conn_fd))
+ /* Something went wrong. */
+ clear_connection (c, &tv);
+ else
+ switch (c->conn_state)
+ {
+ case CNST_READING:
+ handle_read (c, &tv);
+ break;
+ case CNST_SENDING:
+ handle_send (c, &tv);
+ break;
+ case CNST_LINGERING:
+ handle_linger (c, &tv);
+ break;
+ }
+ }
+ tmr_run (&tv);
+
+ if (got_usr1 && !terminate)
+ {
+ terminate = 1;
+ if (hs != (httpd_server *) 0)
+ {
+ if (hs->listen4_fd != -1)
+ fdwatch_del_fd (hs->listen4_fd);
+ if (hs->listen6_fd != -1)
+ fdwatch_del_fd (hs->listen6_fd);
+ httpd_unlisten (hs);
+ }
+ }
+ }
+
+ /* The main loop terminated. */
+ shut_down ();
+ syslog (LOG_NOTICE, "exiting");
+ //closelog();
+ exit (0);
+}
+
+
+static void parse_args (int argc, char **argv)
+{
+ char *env;
+ int val;
+
+ debug = 0;
+ port = DEFAULT_PORT;
+ dir = (char *) 0;
+ data_dir = (char *) 0;
+#ifdef ALWAYS_CHROOT
+ do_chroot = 1;
+#else /* ALWAYS_CHROOT */
+ do_chroot = 0;
+#endif /* ALWAYS_CHROOT */
+ no_log = 0;
+ no_symlink_check = do_chroot;
+#ifdef ALWAYS_VHOST
+ do_vhost = 1;
+#else /* ALWAYS_VHOST */
+ do_vhost = 0;
+#endif /* ALWAYS_VHOST */
+#ifdef ALWAYS_GLOBAL_PASSWD
+ do_global_passwd = 1;
+#else /* ALWAYS_GLOBAL_PASSWD */
+ do_global_passwd = 0;
+#endif /* ALWAYS_GLOBAL_PASSWD */
+#ifdef CGI_PATTERN
+ cgi_pattern = CGI_PATTERN;
+#else /* CGI_PATTERN */
+ cgi_pattern = (char *) 0;
+#endif /* CGI_PATTERN */
+#ifdef CGI_LIMIT
+ cgi_limit = CGI_LIMIT;
+#else /* CGI_LIMIT */
+ cgi_limit = 0;
+#endif /* CGI_LIMIT */
+#ifdef CGI_TIMELIMIT
+ cgi_timelimit = CGI_TIMELIMIT;
+#else /* CGI_LIMIT */
+ cgi_timelimit = 0;
+#endif /* CGI_LIMIT */
+ url_pattern = (char *) 0;
+ no_empty_referers = 0;
+ local_pattern = (char *) 0;
+ throttlefile = (char *) 0;
+ hostname = (char *) 0;
+ logfile = (char *) 0;
+ pidfile = (char *) 0;
+ user = DEFAULT_USER;
+ charset = DEFAULT_CHARSET;
+ p3p = "";
+ max_age = -1;
+
+ env = getenv("GB_HTTPD_PORT");
+ if (env && *env)
+ {
+ port = (unsigned short)atoi(env);
+ if (port == 0)
+ port = 80;
+ }
+
+ env = getenv("GB_HTTPD_TIMEOUT");
+ if (env && *env)
+ {
+ val = atoi(env);
+ if (val == 0)
+ {
+ if (env[0] == '0' && env[1] == 0)
+ cgi_timelimit = 0;
+ }
+ else
+ cgi_timelimit = val;
+ }
+
+#if 0
+ argn = 1;
+ while (argn < argc && argv[argn][0] == '-')
+ {
+ if (strcmp (argv[argn], "-V") == 0)
+ {
+ (void) printf ("%s\n", SERVER_SOFTWARE);
+ exit (0);
+ }
+ else if (strcmp (argv[argn], "-C") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ read_config (argv[argn]);
+ }
+ else if (strcmp (argv[argn], "-p") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ port = (unsigned short) atoi (argv[argn]);
+ }
+ else if (strcmp (argv[argn], "-d") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ dir = argv[argn];
+ }
+ else if (strcmp (argv[argn], "-r") == 0)
+ {
+ do_chroot = 1;
+ no_symlink_check = 1;
+ }
+ else if (strcmp (argv[argn], "-nor") == 0)
+ {
+ do_chroot = 0;
+ no_symlink_check = 0;
+ }
+ else if (strcmp (argv[argn], "-dd") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ data_dir = argv[argn];
+ }
+ else if (strcmp (argv[argn], "-s") == 0)
+ no_symlink_check = 0;
+ else if (strcmp (argv[argn], "-nos") == 0)
+ no_symlink_check = 1;
+ else if (strcmp (argv[argn], "-u") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ user = argv[argn];
+ }
+ else if (strcmp (argv[argn], "-c") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ cgi_pattern = argv[argn];
+ }
+ else if (strcmp (argv[argn], "-t") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ throttlefile = argv[argn];
+ }
+ else if (strcmp (argv[argn], "-h") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ hostname = argv[argn];
+ }
+ else if (strcmp (argv[argn], "-l") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ logfile = argv[argn];
+ }
+ else if (strcmp (argv[argn], "-v") == 0)
+ do_vhost = 1;
+ else if (strcmp (argv[argn], "-nov") == 0)
+ do_vhost = 0;
+ else if (strcmp (argv[argn], "-g") == 0)
+ do_global_passwd = 1;
+ else if (strcmp (argv[argn], "-nog") == 0)
+ do_global_passwd = 0;
+ else if (strcmp (argv[argn], "-i") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ pidfile = argv[argn];
+ }
+ else if (strcmp (argv[argn], "-T") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ charset = argv[argn];
+ }
+ else if (strcmp (argv[argn], "-P") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ p3p = argv[argn];
+ }
+ else if (strcmp (argv[argn], "-M") == 0 && argn + 1 < argc)
+ {
+ ++argn;
+ max_age = atoi (argv[argn]);
+ }
+ else if (strcmp (argv[argn], "-D") == 0)
+ debug = 1;
+ else
+ usage ();
+ ++argn;
+ }
+ if (argn != argc)
+ usage ();
+#endif
+}
+
+#if 0
+static void usage (void)
+{
+ (void) fprintf (stderr,
+ "usage: %s [-C configfile] [-p port] [-d dir] [-r|-nor] [-dd data_dir] [-s|-nos] [-v|-nov] [-g|-nog] [-u user] [-c cgipat] [-t throttles] [-h host] [-l logfile] [-i pidfile] [-T charset] [-P P3P] [-M maxage] [-V] [-D]\n",
+ argv0);
+ exit (1);
+}
+#endif
+
+#if 0
+static void read_config (char *filename)
+{
+ FILE *fp;
+ char line[10000];
+ char *cp;
+ char *cp2;
+ char *name;
+ char *value;
+
+ fp = fopen (filename, "r");
+ if (fp == (FILE *) 0)
+ {
+ perror (filename);
+ exit (1);
+ }
+
+ while (fgets (line, sizeof (line), fp) != (char *) 0)
+ {
+ /* Trim comments. */
+ if ((cp = strchr (line, '#')) != (char *) 0)
+ *cp = '\0';
+
+ /* Skip leading whitespace. */
+ cp = line;
+ cp += strspn (cp, " \t\n\r");
+
+ /* Split line into words. */
+ while (*cp != '\0')
+ {
+ /* Find next whitespace. */
+ cp2 = cp + strcspn (cp, " \t\n\r");
+ /* Insert EOS and advance next-word pointer. */
+ while (*cp2 == ' ' || *cp2 == '\t' || *cp2 == '\n' || *cp2 == '\r')
+ *cp2++ = '\0';
+ /* Split into name and value. */
+ name = cp;
+ value = strchr (name, '=');
+ if (value != (char *) 0)
+ *value++ = '\0';
+ /* Interpret. */
+ if (strcasecmp (name, "debug") == 0)
+ {
+ no_value_required (name, value);
+ debug = 1;
+ }
+ else if (strcasecmp (name, "port") == 0)
+ {
+ value_required (name, value);
+ port = (unsigned short) atoi (value);
+ }
+ else if (strcasecmp (name, "dir") == 0)
+ {
+ value_required (name, value);
+ dir = e_strdup (value);
+ }
+ else if (strcasecmp (name, "chroot") == 0)
+ {
+ no_value_required (name, value);
+ do_chroot = 1;
+ no_symlink_check = 1;
+ }
+ else if (strcasecmp (name, "nochroot") == 0)
+ {
+ no_value_required (name, value);
+ do_chroot = 0;
+ no_symlink_check = 0;
+ }
+ else if (strcasecmp (name, "data_dir") == 0)
+ {
+ value_required (name, value);
+ data_dir = e_strdup (value);
+ }
+ else if (strcasecmp (name, "symlink") == 0)
+ {
+ no_value_required (name, value);
+ no_symlink_check = 0;
+ }
+ else if (strcasecmp (name, "nosymlink") == 0)
+ {
+ no_value_required (name, value);
+ no_symlink_check = 1;
+ }
+ else if (strcasecmp (name, "symlinks") == 0)
+ {
+ no_value_required (name, value);
+ no_symlink_check = 0;
+ }
+ else if (strcasecmp (name, "nosymlinks") == 0)
+ {
+ no_value_required (name, value);
+ no_symlink_check = 1;
+ }
+ else if (strcasecmp (name, "user") == 0)
+ {
+ value_required (name, value);
+ user = e_strdup (value);
+ }
+ else if (strcasecmp (name, "cgipat") == 0)
+ {
+ value_required (name, value);
+ cgi_pattern = e_strdup (value);
+ }
+ else if (strcasecmp (name, "cgilimit") == 0)
+ {
+ value_required (name, value);
+ cgi_limit = atoi (value);
+ }
+ else if (strcasecmp (name, "cgitimelimit") == 0)
+ {
+ value_required (name, value);
+ cgi_timelimit = atoi (value);
+ }
+ else if (strcasecmp (name, "urlpat") == 0)
+ {
+ value_required (name, value);
+ url_pattern = e_strdup (value);
+ }
+ else if (strcasecmp (name, "noemptyreferers") == 0)
+ {
+ no_value_required (name, value);
+ no_empty_referers = 1;
+ }
+ else if (strcasecmp (name, "localpat") == 0)
+ {
+ value_required (name, value);
+ local_pattern = e_strdup (value);
+ }
+ else if (strcasecmp (name, "throttles") == 0)
+ {
+ value_required (name, value);
+ throttlefile = e_strdup (value);
+ }
+ else if (strcasecmp (name, "host") == 0)
+ {
+ value_required (name, value);
+ hostname = e_strdup (value);
+ }
+ else if (strcasecmp (name, "logfile") == 0)
+ {
+ value_required (name, value);
+ logfile = e_strdup (value);
+ }
+ else if (strcasecmp (name, "vhost") == 0)
+ {
+ no_value_required (name, value);
+ do_vhost = 1;
+ }
+ else if (strcasecmp (name, "novhost") == 0)
+ {
+ no_value_required (name, value);
+ do_vhost = 0;
+ }
+ else if (strcasecmp (name, "globalpasswd") == 0)
+ {
+ no_value_required (name, value);
+ do_global_passwd = 1;
+ }
+ else if (strcasecmp (name, "noglobalpasswd") == 0)
+ {
+ no_value_required (name, value);
+ do_global_passwd = 0;
+ }
+ else if (strcasecmp (name, "pidfile") == 0)
+ {
+ value_required (name, value);
+ pidfile = e_strdup (value);
+ }
+ else if (strcasecmp (name, "charset") == 0)
+ {
+ value_required (name, value);
+ charset = e_strdup (value);
+ }
+ else if (strcasecmp (name, "p3p") == 0)
+ {
+ value_required (name, value);
+ p3p = e_strdup (value);
+ }
+ else if (strcasecmp (name, "max_age") == 0)
+ {
+ value_required (name, value);
+ max_age = atoi (value);
+ }
+ else
+ {
+ (void) fprintf (stderr, "%s: unknown config option '%s'\n",
+ argv0, name);
+ exit (1);
+ }
+
+ /* Advance to next word. */
+ cp = cp2;
+ cp += strspn (cp, " \t\n\r");
+ }
+ }
+
+ (void) fclose (fp);
+}
+
+static void value_required (char *name, char *value)
+{
+ if (value == (char *) 0)
+ {
+ (void) fprintf (stderr, "%s: value required for %s option\n", argv0,
+ name);
+ exit (1);
+ }
+}
+
+
+static void no_value_required (char *name, char *value)
+{
+ if (value != (char *) 0)
+ {
+ (void) fprintf (stderr, "%s: no value required for %s option\n",
+ argv0, name);
+ exit (1);
+ }
+}
+#endif
+
+
+static char *e_strdup (char *oldstr)
+{
+ char *newstr;
+
+ newstr = strdup (oldstr);
+ if (newstr == (char *) 0)
+ {
+ syslog (LOG_CRIT, "out of memory copying a string");
+ (void) fprintf (stderr, "%s: out of memory copying a string\n", argv0);
+ exit (1);
+ }
+ return newstr;
+}
+
+
+static void
+lookup_hostname (httpd_sockaddr * sa4P, size_t sa4_len, int *gotv4P,
+ httpd_sockaddr * sa6P, size_t sa6_len, int *gotv6P)
+{
+#ifdef USE_IPV6
+
+ struct addrinfo hints;
+ char portstr[10];
+ int gaierr;
+ struct addrinfo *ai;
+ struct addrinfo *ai2;
+ struct addrinfo *aiv6;
+ struct addrinfo *aiv4;
+
+ (void) memset (&hints, 0, sizeof (hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_socktype = SOCK_STREAM;
+ (void) snprintf (portstr, sizeof (portstr), "%d", (int) port);
+ if ((gaierr = getaddrinfo (hostname, portstr, &hints, &ai)) != 0)
+ {
+ syslog (LOG_CRIT, "getaddrinfo %.80s - %.80s",
+ hostname, gai_strerror (gaierr));
+ (void) fprintf (stderr, "%s: getaddrinfo %s - %s\n",
+ argv0, hostname, gai_strerror (gaierr));
+ exit (1);
+ }
+
+ /* Find the first IPv6 and IPv4 entries. */
+ aiv6 = (struct addrinfo *) 0;
+ aiv4 = (struct addrinfo *) 0;
+ for (ai2 = ai; ai2 != (struct addrinfo *) 0; ai2 = ai2->ai_next)
+ {
+ switch (ai2->ai_family)
+ {
+ case AF_INET6:
+ if (aiv6 == (struct addrinfo *) 0)
+ aiv6 = ai2;
+ break;
+ case AF_INET:
+ if (aiv4 == (struct addrinfo *) 0)
+ aiv4 = ai2;
+ break;
+ }
+ }
+
+ if (aiv6 == (struct addrinfo *) 0)
+ *gotv6P = 0;
+ else
+ {
+ if (sa6_len < aiv6->ai_addrlen)
+ {
+ syslog (LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)",
+ hostname, (unsigned long) sa6_len,
+ (unsigned long) aiv6->ai_addrlen);
+ exit (1);
+ }
+ (void) memset (sa6P, 0, sa6_len);
+ (void) memmove (sa6P, aiv6->ai_addr, aiv6->ai_addrlen);
+ *gotv6P = 1;
+ }
+
+ if (aiv4 == (struct addrinfo *) 0)
+ *gotv4P = 0;
+ else
+ {
+ if (sa4_len < aiv4->ai_addrlen)
+ {
+ syslog (LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)",
+ hostname, (unsigned long) sa4_len,
+ (unsigned long) aiv4->ai_addrlen);
+ exit (1);
+ }
+ (void) memset (sa4P, 0, sa4_len);
+ (void) memmove (sa4P, aiv4->ai_addr, aiv4->ai_addrlen);
+ *gotv4P = 1;
+ }
+
+ freeaddrinfo (ai);
+
+#else /* USE_IPV6 */
+
+ struct hostent *he;
+
+ *gotv6P = 0;
+
+ (void) memset (sa4P, 0, sa4_len);
+ sa4P->sa.sa_family = AF_INET;
+ if (hostname == (char *) 0)
+ sa4P->sa_in.sin_addr.s_addr = htonl (INADDR_ANY);
+ else
+ {
+ sa4P->sa_in.sin_addr.s_addr = inet_addr (hostname);
+ if ((int) sa4P->sa_in.sin_addr.s_addr == -1)
+ {
+ he = gethostbyname (hostname);
+ if (he == (struct hostent *) 0)
+ {
+#ifdef HAVE_HSTRERROR
+ syslog (LOG_CRIT, "gethostbyname %.80s - %.80s",
+ hostname, hstrerror (h_errno));
+ (void) fprintf (stderr, "%s: gethostbyname %s - %s\n",
+ argv0, hostname, hstrerror (h_errno));
+#else /* HAVE_HSTRERROR */
+ syslog (LOG_CRIT, "gethostbyname %.80s failed", hostname);
+ (void) fprintf (stderr, "%s: gethostbyname %s failed\n", argv0,
+ hostname);
+#endif /* HAVE_HSTRERROR */
+ exit (1);
+ }
+ if (he->h_addrtype != AF_INET)
+ {
+ syslog (LOG_CRIT, "%.80s - non-IP network address", hostname);
+ (void) fprintf (stderr, "%s: %s - non-IP network address\n",
+ argv0, hostname);
+ exit (1);
+ }
+ (void) memmove (&sa4P->sa_in.sin_addr.s_addr, he->h_addr, he->h_length);
+ }
+ }
+ sa4P->sa_in.sin_port = htons (port);
+ *gotv4P = 1;
+
+#endif /* USE_IPV6 */
+}
+
+
+static void read_throttlefile (char *throttlefile)
+{
+ FILE *fp;
+ char buf[5000];
+ char *cp;
+ int len;
+ char pattern[5000];
+ long max_limit, min_limit;
+ struct timeval tv;
+
+ fp = fopen (throttlefile, "r");
+ if (fp == (FILE *) 0)
+ {
+ syslog (LOG_CRIT, "%.80s - %m", throttlefile);
+ perror (throttlefile);
+ exit (1);
+ }
+
+ (void) gettimeofday (&tv, (struct timezone *) 0);
+
+ while (fgets (buf, sizeof (buf), fp) != (char *) 0)
+ {
+ /* Nuke comments. */
+ cp = strchr (buf, '#');
+ if (cp != (char *) 0)
+ *cp = '\0';
+
+ /* Nuke trailing whitespace. */
+ len = strlen (buf);
+ while (len > 0 &&
+ (buf[len - 1] == ' ' || buf[len - 1] == '\t' ||
+ buf[len - 1] == '\n' || buf[len - 1] == '\r'))
+ buf[--len] = '\0';
+
+ /* Ignore empty lines. */
+ if (len == 0)
+ continue;
+
+ /* Parse line. */
+ if (sscanf
+ (buf, " %4900[^ \t] %ld-%ld", pattern, &min_limit, &max_limit) == 3)
+ {
+ }
+ else if (sscanf (buf, " %4900[^ \t] %ld", pattern, &max_limit) == 2)
+ min_limit = 0;
+ else
+ {
+ syslog (LOG_CRIT,
+ "unparsable line in %.80s - %.80s", throttlefile, buf);
+ (void) fprintf (stderr,
+ "%s: unparsable line in %.80s - %.80s\n",
+ argv0, throttlefile, buf);
+ continue;
+ }
+
+ /* Nuke any leading slashes in pattern. */
+ if (pattern[0] == '/')
+ (void) strcpy (pattern, &pattern[1]);
+ while ((cp = strstr (pattern, "|/")) != (char *) 0)
+ (void) strcpy (cp + 1, cp + 2);
+
+ /* Check for room in throttles. */
+ if (numthrottles >= maxthrottles)
+ {
+ if (maxthrottles == 0)
+ {
+ maxthrottles = 100; /* arbitrary */
+ throttles = NEW (throttletab, maxthrottles);
+ }
+ else
+ {
+ maxthrottles *= 2;
+ throttles = RENEW (throttles, throttletab, maxthrottles);
+ }
+ if (throttles == (throttletab *) 0)
+ {
+ syslog (LOG_CRIT, "out of memory allocating a throttletab");
+ (void) fprintf (stderr,
+ "%s: out of memory allocating a throttletab\n",
+ argv0);
+ exit (1);
+ }
+ }
+
+ /* Add to table. */
+ throttles[numthrottles].pattern = e_strdup (pattern);
+ throttles[numthrottles].max_limit = max_limit;
+ throttles[numthrottles].min_limit = min_limit;
+ throttles[numthrottles].rate = 0;
+ throttles[numthrottles].bytes_since_avg = 0;
+ throttles[numthrottles].num_sending = 0;
+
+ ++numthrottles;
+ }
+ (void) fclose (fp);
+}
+
+
+static void shut_down (void)
+{
+ int cnum;
+ struct timeval tv;
+
+ (void) gettimeofday (&tv, (struct timezone *) 0);
+ logstats (&tv);
+ for (cnum = 0; cnum < max_connects; ++cnum)
+ {
+ if (connects[cnum].conn_state != CNST_FREE)
+ httpd_close_conn (connects[cnum].hc, &tv);
+ if (connects[cnum].hc != (httpd_conn *) 0)
+ {
+ httpd_destroy_conn (connects[cnum].hc);
+ free ((void *) connects[cnum].hc);
+ --httpd_conn_count;
+ connects[cnum].hc = (httpd_conn *) 0;
+ }
+ }
+ if (hs != (httpd_server *) 0)
+ {
+ httpd_server *ths = hs;
+ hs = (httpd_server *) 0;
+ if (ths->listen4_fd != -1)
+ fdwatch_del_fd (ths->listen4_fd);
+ if (ths->listen6_fd != -1)
+ fdwatch_del_fd (ths->listen6_fd);
+ httpd_terminate (ths);
+ }
+ mmc_destroy ();
+ tmr_destroy ();
+ free ((void *) connects);
+ if (throttles != (throttletab *) 0)
+ free ((void *) throttles);
+}
+
+
+static int handle_newconnect (struct timeval *tvP, int listen_fd)
+{
+ connecttab *c;
+ //ClientData client_data;
+
+ /* This loops until the accept() fails, trying to start new
+ ** connections as fast as possible so we don't overrun the
+ ** listen queue.
+ */
+ for (;;)
+ {
+ /* Is there room in the connection table? */
+ if (num_connects >= max_connects)
+ {
+ /* Out of connection slots. Run the timers, then the
+ ** existing connections, and maybe we'll free up a slot
+ ** by the time we get back here.
+ */
+ syslog (LOG_WARNING, "too many connections!");
+ tmr_run (tvP);
+ return 0;
+ }
+ /* Get the first free connection entry off the free list. */
+ if (first_free_connect == -1
+ || connects[first_free_connect].conn_state != CNST_FREE)
+ {
+ syslog (LOG_CRIT, "the connects free list is messed up");
+ exit (1);
+ }
+ c = &connects[first_free_connect];
+ /* Make the httpd_conn if necessary. */
+ if (c->hc == (httpd_conn *) 0)
+ {
+ c->hc = NEW (httpd_conn, 1);
+ if (c->hc == (httpd_conn *) 0)
+ {
+ syslog (LOG_CRIT, "out of memory allocating an httpd_conn");
+ exit (1);
+ }
+ c->hc->initialized = 0;
+ ++httpd_conn_count;
+ }
+
+ /* Get the connection. */
+ switch (httpd_get_conn (hs, listen_fd, c->hc))
+ {
+ /* Some error happened. Run the timers, then the
+ ** existing connections. Maybe the error will clear.
+ */
+ case GC_FAIL:
+ tmr_run (tvP);
+ return 0;
+
+ /* No more connections to accept for now. */
+ case GC_NO_MORE:
+ return 1;
+ }
+ c->conn_state = CNST_READING;
+ /* Pop it off the free list. */
+ first_free_connect = c->next_free_connect;
+ c->next_free_connect = -1;
+ ++num_connects;
+ //client_data.p = c;
+ c->active_at = tvP->tv_sec;
+ c->wakeup_timer = (Timer *) 0;
+ c->linger_timer = (Timer *) 0;
+ c->next_byte_index = 0;
+ c->numtnums = 0;
+
+ /* Set the connection file descriptor to no-delay mode. */
+ httpd_set_ndelay (c->hc->conn_fd);
+
+ fdwatch_add_fd (c->hc->conn_fd, c, FDW_READ);
+
+ ++stats_connections;
+ if (num_connects > stats_simultaneous)
+ stats_simultaneous = num_connects;
+ }
+}
+
+
+static void handle_read (connecttab * c, struct timeval *tvP)
+{
+ int sz;
+ //ClientData client_data;
+ httpd_conn *hc = c->hc;
+
+ /* Is there room in our buffer to read more bytes? */
+ if (hc->read_idx >= hc->read_size)
+ {
+ if (hc->read_size > 5000)
+ {
+ httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, "");
+ finish_connection (c, tvP);
+ return;
+ }
+ httpd_realloc_str (&hc->read_buf, &hc->read_size, hc->read_size + 1000);
+ }
+
+ /* Read some more bytes. */
+ sz = read (hc->conn_fd, &(hc->read_buf[hc->read_idx]),
+ hc->read_size - hc->read_idx);
+ if (sz == 0)
+ {
+ httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, "");
+ finish_connection (c, tvP);
+ return;
+ }
+ if (sz < 0)
+ {
+ /* Ignore EINTR and EAGAIN. Also ignore EWOULDBLOCK. At first glance
+ ** you would think that connections returned by fdwatch as readable
+ ** should never give an EWOULDBLOCK; however, this apparently can
+ ** happen if a packet gets garbled.
+ */
+ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
+ return;
+ httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, "");
+ finish_connection (c, tvP);
+ return;
+ }
+ hc->read_idx += sz;
+ c->active_at = tvP->tv_sec;
+
+ /* Do we have a complete request yet? */
+ switch (httpd_got_request (hc))
+ {
+ case GR_NO_REQUEST:
+ return;
+ case GR_BAD_REQUEST:
+ httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, "");
+ finish_connection (c, tvP);
+ return;
+ }
+
+ /* Yes. Try parsing and resolving it. */
+ if (httpd_parse_request (hc) < 0)
+ {
+ finish_connection (c, tvP);
+ return;
+ }
+
+ /* Check the throttle table */
+ if (!check_throttles (c))
+ {
+ httpd_send_err (hc, 503, httpd_err503title, "", httpd_err503form,
+ hc->encodedurl);
+ finish_connection (c, tvP);
+ return;
+ }
+
+ /* Start the connection going. */
+ if (httpd_start_request (hc, tvP) < 0)
+ {
+ /* Something went wrong. Close down the connection. */
+ finish_connection (c, tvP);
+ return;
+ }
+
+ /* Fill in end_byte_index. */
+ if (hc->got_range)
+ {
+ c->next_byte_index = hc->first_byte_index;
+ c->end_byte_index = hc->last_byte_index + 1;
+ }
+ else if (hc->bytes_to_send < 0)
+ c->end_byte_index = 0;
+ else
+ c->end_byte_index = hc->bytes_to_send;
+
+ /* Check if it's already handled. */
+ if (hc->file_address == (char *) 0)
+ {
+ /* No file address means someone else is handling it. */
+ int tind;
+ for (tind = 0; tind < c->numtnums; ++tind)
+ throttles[c->tnums[tind]].bytes_since_avg += hc->bytes_sent;
+ c->next_byte_index = hc->bytes_sent;
+ finish_connection (c, tvP);
+ return;
+ }
+ if (c->next_byte_index >= c->end_byte_index)
+ {
+ /* There's nothing to send. */
+ finish_connection (c, tvP);
+ return;
+ }
+
+ /* Cool, we have a valid connection and a file to send to it. */
+ c->conn_state = CNST_SENDING;
+ c->started_at = tvP->tv_sec;
+ c->wouldblock_delay = 0;
+ //client_data.p = c;
+
+ fdwatch_del_fd (hc->conn_fd);
+ fdwatch_add_fd (hc->conn_fd, c, FDW_WRITE);
+}
+
+
+static void handle_send (connecttab * c, struct timeval *tvP)
+{
+ size_t max_bytes;
+ int sz, coast;
+ ClientData client_data;
+ time_t elapsed;
+ httpd_conn *hc = c->hc;
+ int tind;
+
+ if (c->max_limit == THROTTLE_NOLIMIT)
+ max_bytes = 1000000000L;
+ else
+ max_bytes = c->max_limit / 4; /* send at most 1/4 seconds worth */
+
+ /* Do we need to write the headers first? */
+ if (hc->responselen == 0)
+ {
+ /* No, just write the file. */
+ sz = write (hc->conn_fd, &(hc->file_address[c->next_byte_index]),
+ MIN (c->end_byte_index - c->next_byte_index, max_bytes));
+ }
+ else
+ {
+ /* Yes. We'll combine headers and file into a single writev(),
+ ** hoping that this generates a single packet.
+ */
+ struct iovec iv[2];
+
+ iv[0].iov_base = hc->response;
+ iv[0].iov_len = hc->responselen;
+ iv[1].iov_base = &(hc->file_address[c->next_byte_index]);
+ iv[1].iov_len = MIN (c->end_byte_index - c->next_byte_index, max_bytes);
+ sz = writev (hc->conn_fd, iv, 2);
+ }
+
+ if (sz < 0 && errno == EINTR)
+ return;
+
+ if (sz == 0 || (sz < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)))
+ {
+ /* This shouldn't happen, but some kernels, e.g.
+ ** SunOS 4.1.x, are broken and select() says that
+ ** O_NDELAY sockets are always writable even when
+ ** they're actually not.
+ **
+ ** Current workaround is to block sending on this
+ ** socket for a brief adaptively-tuned period.
+ ** Fortunately we already have all the necessary
+ ** blocking code, for use with throttling.
+ */
+ c->wouldblock_delay += MIN_WOULDBLOCK_DELAY;
+ c->conn_state = CNST_PAUSING;
+ fdwatch_del_fd (hc->conn_fd);
+ client_data.p = c;
+ if (c->wakeup_timer != (Timer *) 0)
+ syslog (LOG_ERR, "replacing non-null wakeup_timer!");
+ c->wakeup_timer =
+ tmr_create (tvP, wakeup_connection, client_data, c->wouldblock_delay,
+ 0);
+ if (c->wakeup_timer == (Timer *) 0)
+ {
+ syslog (LOG_CRIT, "tmr_create(wakeup_connection) failed");
+ exit (1);
+ }
+ return;
+ }
+
+ if (sz < 0)
+ {
+ /* Something went wrong, close this connection.
+ **
+ ** If it's just an EPIPE, don't bother logging, that
+ ** just means the client hung up on us.
+ **
+ ** On some systems, write() occasionally gives an EINVAL.
+ ** Dunno why, something to do with the socket going
+ ** bad. Anyway, we don't log those either.
+ **
+ ** And ECONNRESET isn't interesting either.
+ */
+ if (errno != EPIPE && errno != EINVAL && errno != ECONNRESET)
+ syslog (LOG_ERR, "write - %m sending %.80s", hc->encodedurl);
+ clear_connection (c, tvP);
+ return;
+ }
+
+ /* Ok, we wrote something. */
+ c->active_at = tvP->tv_sec;
+ /* Was this a headers + file writev()? */
+ if (hc->responselen > 0)
+ {
+ /* Yes; did we write only part of the headers? */
+ if (sz < hc->responselen)
+ {
+ /* Yes; move the unwritten part to the front of the buffer. */
+ int newlen = hc->responselen - sz;
+ (void) memmove (hc->response, &(hc->response[sz]), newlen);
+ hc->responselen = newlen;
+ sz = 0;
+ }
+ else
+ {
+ /* Nope, we wrote the full headers, so adjust accordingly. */
+ sz -= hc->responselen;
+ hc->responselen = 0;
+ }
+ }
+ /* And update how much of the file we wrote. */
+ c->next_byte_index += sz;
+ c->hc->bytes_sent += sz;
+ for (tind = 0; tind < c->numtnums; ++tind)
+ throttles[c->tnums[tind]].bytes_since_avg += sz;
+
+ /* Are we done? */
+ if (c->next_byte_index >= c->end_byte_index)
+ {
+ /* This connection is finished! */
+ finish_connection (c, tvP);
+ return;
+ }
+
+ /* Tune the (blockheaded) wouldblock delay. */
+ if (c->wouldblock_delay > MIN_WOULDBLOCK_DELAY)
+ c->wouldblock_delay -= MIN_WOULDBLOCK_DELAY;
+
+ /* If we're throttling, check if we're sending too fast. */
+ if (c->max_limit != THROTTLE_NOLIMIT)
+ {
+ elapsed = tvP->tv_sec - c->started_at;
+ if (elapsed == 0)
+ elapsed = 1; /* count at least one second */
+ if (c->hc->bytes_sent / elapsed > c->max_limit)
+ {
+ c->conn_state = CNST_PAUSING;
+ fdwatch_del_fd (hc->conn_fd);
+ /* How long should we wait to get back on schedule? If less
+ ** than a second (integer math rounding), use 1/2 second.
+ */
+ coast = c->hc->bytes_sent / c->max_limit - elapsed;
+ client_data.p = c;
+ if (c->wakeup_timer != (Timer *) 0)
+ syslog (LOG_ERR, "replacing non-null wakeup_timer!");
+ c->wakeup_timer = tmr_create (tvP, wakeup_connection, client_data,
+ coast > 0 ? (coast * 1000L) : 500L, 0);
+ if (c->wakeup_timer == (Timer *) 0)
+ {
+ syslog (LOG_CRIT, "tmr_create(wakeup_connection) failed");
+ exit (1);
+ }
+ }
+ }
+ /* (No check on min_limit here, that only controls connection startups.) */
+}
+
+
+static void handle_linger (connecttab * c, struct timeval *tvP)
+{
+ char buf[4096];
+ int r;
+
+ /* In lingering-close mode we just read and ignore bytes. An error
+ ** or EOF ends things, otherwise we go until a timeout.
+ */
+ r = read (c->hc->conn_fd, buf, sizeof (buf));
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ return;
+ if (r <= 0)
+ really_clear_connection (c, tvP);
+}
+
+
+static int check_throttles (connecttab * c)
+{
+ int tnum;
+ long l;
+
+ c->numtnums = 0;
+ c->max_limit = c->min_limit = THROTTLE_NOLIMIT;
+ for (tnum = 0; tnum < numthrottles && c->numtnums < MAXTHROTTLENUMS; ++tnum)
+ if (match (throttles[tnum].pattern, c->hc->expnfilename))
+ {
+ /* If we're way over the limit, don't even start. */
+ if (throttles[tnum].rate > throttles[tnum].max_limit * 2)
+ return 0;
+ /* Also don't start if we're under the minimum. */
+ if (throttles[tnum].rate < throttles[tnum].min_limit)
+ return 0;
+ if (throttles[tnum].num_sending < 0)
+ {
+ syslog (LOG_ERR,
+ "throttle sending count was negative - shouldn't happen!");
+ throttles[tnum].num_sending = 0;
+ }
+ c->tnums[c->numtnums++] = tnum;
+ ++throttles[tnum].num_sending;
+ l = throttles[tnum].max_limit / throttles[tnum].num_sending;
+ if (c->max_limit == THROTTLE_NOLIMIT)
+ c->max_limit = l;
+ else
+ c->max_limit = MIN (c->max_limit, l);
+ l = throttles[tnum].min_limit;
+ if (c->min_limit == THROTTLE_NOLIMIT)
+ c->min_limit = l;
+ else
+ c->min_limit = MAX (c->min_limit, l);
+ }
+ return 1;
+}
+
+
+static void clear_throttles (connecttab * c, struct timeval *tvP)
+{
+ int tind;
+
+ for (tind = 0; tind < c->numtnums; ++tind)
+ --throttles[c->tnums[tind]].num_sending;
+}
+
+
+static void update_throttles (ClientData client_data, struct timeval *nowP)
+{
+ int tnum, tind;
+ int cnum;
+ connecttab *c;
+ long l;
+
+ /* Update the average sending rate for each throttle. This is only used
+ ** when new connections start up.
+ */
+ for (tnum = 0; tnum < numthrottles; ++tnum)
+ {
+ throttles[tnum].rate =
+ (2 * throttles[tnum].rate +
+ throttles[tnum].bytes_since_avg / THROTTLE_TIME) / 3;
+ throttles[tnum].bytes_since_avg = 0;
+ /* Log a warning message if necessary. */
+ if (throttles[tnum].rate > throttles[tnum].max_limit
+ && throttles[tnum].num_sending != 0)
+ {
+ if (throttles[tnum].rate > throttles[tnum].max_limit * 2)
+ syslog (LOG_NOTICE,
+ "throttle #%d '%.80s' rate %ld greatly exceeding limit %ld; %d sending",
+ tnum, throttles[tnum].pattern, throttles[tnum].rate,
+ throttles[tnum].max_limit, throttles[tnum].num_sending);
+ else
+ syslog (LOG_INFO,
+ "throttle #%d '%.80s' rate %ld exceeding limit %ld; %d sending",
+ tnum, throttles[tnum].pattern, throttles[tnum].rate,
+ throttles[tnum].max_limit, throttles[tnum].num_sending);
+ }
+ if (throttles[tnum].rate < throttles[tnum].min_limit
+ && throttles[tnum].num_sending != 0)
+ {
+ syslog (LOG_NOTICE,
+ "throttle #%d '%.80s' rate %ld lower than minimum %ld; %d sending",
+ tnum, throttles[tnum].pattern, throttles[tnum].rate,
+ throttles[tnum].min_limit, throttles[tnum].num_sending);
+ }
+ }
+
+ /* Now update the sending rate on all the currently-sending connections,
+ ** redistributing it evenly.
+ */
+ for (cnum = 0; cnum < max_connects; ++cnum)
+ {
+ c = &connects[cnum];
+ if (c->conn_state == CNST_SENDING || c->conn_state == CNST_PAUSING)
+ {
+ c->max_limit = THROTTLE_NOLIMIT;
+ for (tind = 0; tind < c->numtnums; ++tind)
+ {
+ tnum = c->tnums[tind];
+ l = throttles[tnum].max_limit / throttles[tnum].num_sending;
+ if (c->max_limit == THROTTLE_NOLIMIT)
+ c->max_limit = l;
+ else
+ c->max_limit = MIN (c->max_limit, l);
+ }
+ }
+ }
+}
+
+
+static void finish_connection (connecttab * c, struct timeval *tvP)
+{
+ /* If we haven't actually sent the buffered response yet, do so now. */
+ httpd_write_response (c->hc);
+
+ /* And clear. */
+ clear_connection (c, tvP);
+}
+
+
+static void clear_connection (connecttab * c, struct timeval *tvP)
+{
+ ClientData client_data;
+
+ if (c->wakeup_timer != (Timer *) 0)
+ {
+ tmr_cancel (c->wakeup_timer);
+ c->wakeup_timer = 0;
+ }
+
+ /* This is our version of Apache's lingering_close() routine, which is
+ ** their version of the often-broken SO_LINGER socket option. For why
+ ** this is necessary, see http://www.apache.org/docs/misc/fin_wait_2.html
+ ** What we do is delay the actual closing for a few seconds, while reading
+ ** any bytes that come over the connection. However, we don't want to do
+ ** this unless it's necessary, because it ties up a connection slot and
+ ** file descriptor which means our maximum connection-handling rate
+ ** is lower. So, elsewhere we set a flag when we detect the few
+ ** circumstances that make a lingering close necessary. If the flag
+ ** isn't set we do the real close now.
+ */
+ if (c->conn_state == CNST_LINGERING)
+ {
+ /* If we were already lingering, shut down for real. */
+ tmr_cancel (c->linger_timer);
+ c->linger_timer = (Timer *) 0;
+ c->hc->should_linger = 0;
+ }
+ if (c->hc->should_linger)
+ {
+ if (c->conn_state != CNST_PAUSING)
+ fdwatch_del_fd (c->hc->conn_fd);
+ c->conn_state = CNST_LINGERING;
+ shutdown (c->hc->conn_fd, SHUT_WR);
+ fdwatch_add_fd (c->hc->conn_fd, c, FDW_READ);
+ client_data.p = c;
+ if (c->linger_timer != (Timer *) 0)
+ syslog (LOG_ERR, "replacing non-null linger_timer!");
+ c->linger_timer =
+ tmr_create (tvP, linger_clear_connection, client_data, LINGER_TIME, 0);
+ if (c->linger_timer == (Timer *) 0)
+ {
+ syslog (LOG_CRIT, "tmr_create(linger_clear_connection) failed");
+ exit (1);
+ }
+ }
+ else
+ really_clear_connection (c, tvP);
+}
+
+
+static void really_clear_connection (connecttab * c, struct timeval *tvP)
+{
+ stats_bytes += c->hc->bytes_sent;
+ if (c->conn_state != CNST_PAUSING)
+ fdwatch_del_fd (c->hc->conn_fd);
+ httpd_close_conn (c->hc, tvP);
+ clear_throttles (c, tvP);
+ if (c->linger_timer != (Timer *) 0)
+ {
+ tmr_cancel (c->linger_timer);
+ c->linger_timer = 0;
+ }
+ c->conn_state = CNST_FREE;
+ c->next_free_connect = first_free_connect;
+ first_free_connect = c - connects; /* division by sizeof is implied */
+ --num_connects;
+}
+
+
+static void idle (ClientData client_data, struct timeval *nowP)
+{
+ int cnum;
+ connecttab *c;
+
+ for (cnum = 0; cnum < max_connects; ++cnum)
+ {
+ c = &connects[cnum];
+ switch (c->conn_state)
+ {
+ case CNST_READING:
+ if (nowP->tv_sec - c->active_at >= IDLE_READ_TIMELIMIT)
+ {
+ syslog (LOG_INFO,
+ "%.80s connection timed out reading",
+ httpd_ntoa (&c->hc->client_addr));
+ httpd_send_err (c->hc, 408, httpd_err408title, "",
+ httpd_err408form, "");
+ finish_connection (c, nowP);
+ }
+ break;
+ case CNST_SENDING:
+ case CNST_PAUSING:
+ if (nowP->tv_sec - c->active_at >= IDLE_SEND_TIMELIMIT)
+ {
+ syslog (LOG_INFO,
+ "%.80s connection timed out sending",
+ httpd_ntoa (&c->hc->client_addr));
+ clear_connection (c, nowP);
+ }
+ break;
+ }
+ }
+}
+
+
+static void wakeup_connection (ClientData client_data, struct timeval *nowP)
+{
+ connecttab *c;
+
+ c = (connecttab *) client_data.p;
+ c->wakeup_timer = (Timer *) 0;
+ if (c->conn_state == CNST_PAUSING)
+ {
+ c->conn_state = CNST_SENDING;
+ fdwatch_add_fd (c->hc->conn_fd, c, FDW_WRITE);
+ }
+}
+
+static void
+linger_clear_connection (ClientData client_data, struct timeval *nowP)
+{
+ connecttab *c;
+
+ c = (connecttab *) client_data.p;
+ c->linger_timer = (Timer *) 0;
+ really_clear_connection (c, nowP);
+}
+
+
+static void occasional (ClientData client_data, struct timeval *nowP)
+{
+ mmc_cleanup (nowP);
+ tmr_cleanup ();
+ watchdog_flag = 1; /* let the watchdog know that we are alive */
+}
+
+
+#ifdef STATS_TIME
+static void show_stats (ClientData client_data, struct timeval *nowP)
+{
+ logstats (nowP);
+}
+#endif /* STATS_TIME */
+
+
+/* Generate debugging statistics syslog messages for all packages. */
+static void logstats (struct timeval *nowP)
+{
+ struct timeval tv;
+ time_t now;
+ long up_secs, stats_secs;
+
+ if (nowP == (struct timeval *) 0)
+ {
+ (void) gettimeofday (&tv, (struct timezone *) 0);
+ nowP = &tv;
+ }
+ now = nowP->tv_sec;
+ up_secs = now - start_time;
+ stats_secs = now - stats_time;
+ if (stats_secs == 0)
+ stats_secs = 1; /* fudge */
+ stats_time = now;
+ syslog (LOG_INFO,
+ "up %ld seconds, stats for %ld seconds:", up_secs, stats_secs);
+
+ thttpd_logstats (stats_secs);
+ httpd_logstats (stats_secs);
+ mmc_logstats (stats_secs);
+ fdwatch_logstats (stats_secs);
+ tmr_logstats (stats_secs);
+}
+
+
+/* Generate debugging statistics syslog message. */
+static void thttpd_logstats (long secs)
+{
+ if (secs > 0)
+ syslog (LOG_INFO,
+ " thttpd - %ld connections (%g/sec), %d max simultaneous, %lld bytes (%g/sec), %d httpd_conns allocated",
+ stats_connections, (float) stats_connections / secs,
+ stats_simultaneous, (int64_t) stats_bytes,
+ (float) stats_bytes / secs, httpd_conn_count);
+ stats_connections = 0;
+ stats_bytes = 0;
+ stats_simultaneous = 0;
+}
diff --git a/gb.httpd/src/thttpd.h b/gb.httpd/src/thttpd.h
new file mode 100644
index 000000000..008501c94
--- /dev/null
+++ b/gb.httpd/src/thttpd.h
@@ -0,0 +1,403 @@
+/* config.h - configuration defines for thttpd and libhttpd
+**
+** Copyright � 1995,1998,1999,2000,2001 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+#ifndef _THTTPD_H_
+#define _THTTPD_H_
+
+#include "config.h"
+
+/* The following configuration settings are sorted in order of decreasing
+** likelihood that you'd want to change them - most likely first, least
+** likely last.
+**
+** In case you're not familiar with the convention, "#ifdef notdef"
+** is a Berkeleyism used to indicate temporarily disabled code.
+** The idea here is that you re-enable it by just moving it outside
+** of the ifdef.
+*/
+
+/* CONFIGURE: CGI programs must match this pattern to get executed. It's
+** a simple shell-style wildcard pattern, with * meaning any string not
+** containing a slash, ** meaning any string at all, and ? meaning any
+** single character; or multiple such patterns separated by |. The
+** patterns get checked against the filename part of the incoming URL.
+**
+** Restricting CGI programs to a single directory lets the site administrator
+** review them for security holes, and is strongly recommended. If there
+** are individual users that you trust, you can enable their directories too.
+**
+** You can also specify a CGI pattern on the command line, with the -c flag.
+** Such a pattern overrides this compiled-in default.
+**
+** If no CGI pattern is specified, neither here nor on the command line,
+** then CGI programs cannot be run at all. If you want to disable CGI
+** as a security measure that's how you do it, just don't define any
+** pattern here and don't run with the -c flag.
+*/
+#ifdef notdef
+/* Some sample patterns. Allow programs only in one central directory: */
+#define CGI_PATTERN "/cgi-bin/*"
+/* Allow programs in a central directory, or anywhere in a trusted
+** user's tree: */
+#define CGI_PATTERN "/cgi-bin/*|/jef/**"
+/* Allow any program ending with a .cgi: */
+#define CGI_PATTERN "**.cgi"
+/* When virtual hosting, enable the central directory on every host: */
+#define CGI_PATTERN "/*/cgi-bin/*"
+#endif
+
+/* CONFIGURE: How many seconds to allow CGI programs to run before killing
+** them. This is in case someone writes a CGI program that goes into an
+** infinite loop, or does a massive database lookup that would take hours,
+** or whatever. If you don't want any limit, comment this out, but that's
+** probably a really bad idea.
+*/
+#define CGI_TIMELIMIT 30
+
+/* CONFIGURE: Maximum number of simultaneous CGI programs allowed.
+** If this many are already running, then attempts to run more will
+** return an HTTP 503 error. If this is not defined then there's
+** no limit (and you'd better have a lot of memory). This can also be
+** set in the runtime config file.
+*/
+#ifdef notdef
+#define CGI_LIMIT 50
+#endif
+
+/* CONFIGURE: How many seconds to allow for reading the initial request
+** on a new connection.
+*/
+#define IDLE_READ_TIMELIMIT 60
+
+/* CONFIGURE: How many seconds before an idle connection gets closed.
+*/
+#define IDLE_SEND_TIMELIMIT 300
+
+/* CONFIGURE: The syslog facility to use. Using this you can set up your
+** syslog.conf so that all thttpd messages go into a separate file. Note
+** that even if you use the -l command line flag to send logging to a
+** file, errors still get sent via syslog.
+*/
+#define LOG_FACILITY LOG_DAEMON
+
+/* CONFIGURE: Tilde mapping. Many URLs use ~username to indicate a
+** user's home directory. thttpd provides two options for mapping
+** this construct to an actual filename.
+**
+** 1) Map ~username to /username. This is the recommended choice.
+** Each user gets a subdirectory in the main chrootable web tree, and
+** the tilde construct points there. The prefix could be something
+** like "users", or it could be empty. See also the makeweb program
+** for letting users create their own web subdirectories.
+**
+** 2) Map ~username to /. The postfix would be
+** the name of a subdirectory off of the user's actual home dir, something
+** like "public_html". This is what Apache and other servers do. The problem
+** is, you can't do this and chroot() at the same time, so it's inherently
+** a security hole. This is strongly dis-recommended, but it's here because
+** some people really want it. Use at your own risk.
+**
+** You can also leave both options undefined, and thttpd will not do
+** anything special about tildes. Enabling both options is an error.
+*/
+#ifdef notdef
+#define TILDE_MAP_1 "users"
+#define TILDE_MAP_2 "public_html"
+#endif
+
+/* CONFIGURE: The file to use for authentication. If this is defined then
+** thttpd checks for this file in the local directory before every fetch.
+** If the file exists then authentication is done, otherwise the fetch
+** proceeds as usual.
+**
+** If you undefine this then thttpd will not implement authentication
+** at all and will not check for auth files, which saves a bit of CPU time.
+*/
+//#define AUTH_FILE ".htpasswd"
+
+/* CONFIGURE: The default character set name to use with text MIME types.
+** This gets substituted into the MIME types where they have a "%s".
+**
+** You can override this in the config file with the "charset" setting,
+** or on the command like with the -T flag.
+*/
+#define DEFAULT_CHARSET "iso-8859-1"
+
+
+/* Most people won't want to change anything below here. */
+
+/* CONFIGURE: This controls the SERVER_NAME environment variable that gets
+** passed to CGI programs. By default thttpd does a gethostname(), which
+** gives the host's canonical name. If you want to always use some other name
+** you can define it here.
+**
+** Alternately, if you want to run the same thttpd binary on multiple
+** machines, and want to build in alternate names for some or all of
+** them, you can define a list of canonical name to altername name
+** mappings. thttpd seatches the list and when it finds a match on
+** the canonical name, that alternate name gets used. If no match
+** is found, the canonical name gets used.
+**
+** If both SERVER_NAME and SERVER_NAME_LIST are defined here, thttpd searches
+** the list as above, and if no match is found then SERVER_NAME gets used.
+**
+** In any case, if thttpd is started with the -h flag, that name always
+** gets used.
+*/
+#ifdef notdef
+#define SERVER_NAME "your.hostname.here"
+#define SERVER_NAME_LIST \
+ "canonical.name.here/alternate.name.here", \
+ "canonical.name.two/alternate.name.two"
+#endif
+
+/* CONFIGURE: Undefine this if you want thttpd to hide its specific version
+** when returning into to browsers. Instead it'll just say "thttpd" with
+** no version.
+*/
+#define SHOW_SERVER_VERSION
+
+/* CONFIGURE: Define this if you want to always chroot(), without having
+** to give the -r command line flag. Some people like this as a security
+** measure, to prevent inadvertant exposure by accidentally running without -r.
+** You can still disable it at runtime with the -nor flag.
+*/
+#ifdef notdef
+#define ALWAYS_CHROOT
+#endif
+
+/* CONFIGURE: Define this if you want to always do virtual hosting, without
+** having to give the -v command line flag. You can still disable it at
+** runtime with the -nov flag.
+*/
+#ifdef notdef
+#define ALWAYS_VHOST
+#endif
+
+/* CONFIGURE: If you're using the vhost feature and you have a LOT of
+** virtual hostnames (like, hundreds or thousands), you will want to
+** enable this feature. It avoids a problem with most Unix filesystems,
+** where if there are a whole lot of items in a directory then name lookup
+** becomes very slow. This feature makes thttpd use subdirectories
+** based on the first characters of each hostname. You can set it to use
+** from one to three characters. If the hostname starts with "www.", that
+** part is skipped over. Dots are also skipped over, and if the name isn't
+** long enough then "_"s are used. Here are some examples of how hostnames
+** would get turned into directory paths, for each different setting:
+** 1: www.acme.com -> a/www.acme.com
+** 1: foobar.acme.com -> f/foobar.acme.com
+** 2: www.acme.com -> a/c/www.acme.com
+** 2: foobar.acme.com -> f/o/foobar.acme.com
+** 3: www.acme.com -> a/c/m/www.acme.com
+** 3: foobar.acme.com -> f/o/o/foobar.acme.com
+** 3: m.tv -> m/t/v/m.tv
+** 4: m.tv -> m/t/v/_/m.tv
+** Note that if you compile this setting in but then forget to set up
+** the corresponding subdirectories, the only error indication you'll
+** get is a "404 Not Found" when you try to visit a site. So be careful.
+*/
+#ifdef notdef
+#define VHOST_DIRLEVELS 1
+#define VHOST_DIRLEVELS 2
+#define VHOST_DIRLEVELS 3
+#endif
+
+/* CONFIGURE: Define this if you want to always use a global passwd file,
+** without having to give the -P command line flag. You can still disable
+** it at runtime with the -noP flag.
+*/
+#ifdef notdef
+#define ALWAYS_GLOBAL_PASSWD
+#endif
+
+/* CONFIGURE: When started as root, the default username to switch to after
+** initializing. If this user (or the one specified by the -u flag) does
+** not exist, the program will refuse to run.
+*/
+#define DEFAULT_USER "nobody"
+
+/* CONFIGURE: When started as root, the program can automatically chdir()
+** to the home directory of the user specified by -u or DEFAULT_USER.
+** An explicit -d still overrides this.
+*/
+#ifdef notdef
+#define USE_USER_DIR
+#endif
+
+/* CONFIGURE: If this is defined, some of the built-in error pages will
+** have more explicit information about exactly what the problem is.
+** Some sysadmins don't like this, for security reasons.
+*/
+#define EXPLICIT_ERROR_PAGES
+
+/* CONFIGURE: Subdirectory for custom error pages. The error filenames are
+** $WEBDIR/$ERR_DIR/err%d.html - if virtual hosting is enabled then
+** $WEBDIR/hostname/$ERR_DIR/err%d.html is searched first. This allows
+** different custom error pages for each virtual hosting web server. If
+** no custom page for a given error can be found, the built-in error page
+** is generated. If ERR_DIR is not defined at all, only the built-in error
+** pages will be generated.
+*/
+#define ERR_DIR "errors"
+
+/* CONFIGURE: Define this if you want a standard HTML tail containing
+** $SERVER_SOFTWARE and $SERVER_ADDRESS to be appended to the custom error
+** pages. (It is always appended to the built-in error pages.)
+*/
+#define ERR_APPEND_SERVER_INFO
+
+/* CONFIGURE: nice(2) value to use for CGI programs. If this is undefined,
+** CGI programs run at normal priority.
+*/
+#define CGI_NICE 10
+
+/* CONFIGURE: $PATH to use for CGI programs.
+*/
+#define CGI_PATH "/usr/local/bin:/usr/ucb:/bin:/usr/bin"
+
+/* CONFIGURE: If defined, $LD_LIBRARY_PATH to use for CGI programs.
+*/
+#ifdef notdef
+#define CGI_LD_LIBRARY_PATH "/usr/local/lib:/usr/lib"
+#endif
+
+/* CONFIGURE: How often to run the occasional cleanup job.
+*/
+#define OCCASIONAL_TIME 120
+
+/* CONFIGURE: Seconds between stats syslogs. If this is undefined then
+** no stats are accumulated and no stats syslogs are done.
+*/
+//#define STATS_TIME 3600
+
+/* CONFIGURE: The mmap cache tries to keep the total number of mapped
+** files below this number, so you don't run out of kernel file descriptors.
+** If you have reconfigured your kernel to have more descriptors, you can
+** raise this and thttpd will keep more maps cached. However it's not
+** a hard limit, thttpd will go over it if you really are accessing
+** a whole lot of files.
+*/
+#define DESIRED_MAX_MAPPED_FILES 1000
+
+/* CONFIGURE: The mmap cache also tries to keep the total mapped bytes
+** below this number, so you don't run out of address space. Again
+** it's not a hard limit, thttpd will go over it if you really are
+** accessing a bunch of large files.
+*/
+#define DESIRED_MAX_MAPPED_BYTES 1000000000
+
+/* CONFIGURE: Minimum and maximum intervals between child-process reaping,
+** in seconds.
+*/
+#define MIN_REAP_TIME 30
+#define MAX_REAP_TIME 900
+
+
+/* You almost certainly don't want to change anything below here. */
+
+/* CONFIGURE: When throttling CGI programs, we don't know how many bytes
+** they send back to the client because it would be inefficient to
+** interpose a counter. CGI programs are much more expensive than
+** regular files to serve, so we set an arbitrary and high byte count
+** that gets applied to all CGI programs for throttling purposes.
+*/
+#define CGI_BYTECOUNT 25000
+
+/* CONFIGURE: The default port to listen on. 80 is the standard HTTP port.
+*/
+#define DEFAULT_PORT 80
+
+/* CONFIGURE: A list of index filenames to check. The files are searched
+** for in this order.
+*/
+#define INDEX_NAMES "index.html", "index.htm", "index.xhtml", "index.xht", "Default.htm", "index.cgi"
+
+/* CONFIGURE: If this is defined then thttpd will automatically generate
+** index pages for directories that don't have an explicit index file.
+** If you want to disable this behavior site-wide, perhaps for security
+** reasons, just undefine this. Note that you can disable indexing of
+** individual directories by merely doing a "chmod 711" on them - the
+** standard Unix file permission to allow file access but disable "ls".
+*/
+//#define GENERATE_INDEXES
+
+/* CONFIGURE: Whether to log unknown request headers. Most sites will not
+** want to log them, which will save them a bit of CPU time.
+*/
+#ifdef notdef
+#define LOG_UNKNOWN_HEADERS
+#endif
+
+/* CONFIGURE: Whether to fflush() the log file after each request. If
+** this is turned off there's a slight savings in CPU cycles.
+*/
+//#define FLUSH_LOG_EVERY_TIME
+
+/* CONFIGURE: Time between updates of the throttle table's rolling averages. */
+#define THROTTLE_TIME 2
+
+/* CONFIGURE: The listen() backlog queue length. The 1024 doesn't actually
+** get used, the kernel uses its maximum allowed value. This is a config
+** parameter only in case there's some OS where asking for too high a queue
+** length causes an error. Note that on many systems the maximum length is
+** way too small - see http://www.acme.com/software/thttpd/notes.html
+*/
+#define LISTEN_BACKLOG 1024
+
+/* CONFIGURE: Maximum number of throttle patterns that any single URL can
+** be included in. This has nothing to do with the number of throttle
+** patterns that you can define, which is unlimited.
+*/
+#define MAXTHROTTLENUMS 10
+
+/* CONFIGURE: Number of file descriptors to reserve for uses other than
+** connections. Currently this is 10, representing one for the listen fd,
+** one for dup()ing at connection startup time, one for reading the file,
+** one for syslog, and possibly one for the regular log file, which is
+** five, plus a factor of two for who knows what.
+*/
+#define SPARE_FDS 10
+
+/* CONFIGURE: How many milliseconds to leave a connection open while doing a
+** lingering close.
+*/
+#define LINGER_TIME 500
+
+/* CONFIGURE: Maximum number of symbolic links to follow before
+** assuming there's a loop.
+*/
+#define MAX_LINKS 32
+
+/* CONFIGURE: You don't even want to know.
+*/
+#define MIN_WOULDBLOCK_DELAY 100L
+
+/* CONFIGURE: Pass the X-Cgi header to the CGI script
+*/
+#define X_CGI_HEADER
+
+#endif /* _THTTPD_H_ */
diff --git a/gb.httpd/src/timers.c b/gb.httpd/src/timers.c
new file mode 100644
index 000000000..f6e91671a
--- /dev/null
+++ b/gb.httpd/src/timers.c
@@ -0,0 +1,334 @@
+/* timers.c - simple timer routines
+**
+** Copyright © 1995,1998,2000 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+#include
+
+#include
+#include
+//#include
+
+#include "main.h"
+#include "timers.h"
+
+
+#define HASH_SIZE 67
+static Timer *timers[HASH_SIZE];
+static Timer *free_timers;
+static int alloc_count, active_count, free_count;
+
+ClientData JunkClientData;
+
+
+
+static unsigned int hash (Timer * t)
+{
+ /* We can hash on the trigger time, even though it can change over
+ ** the life of a timer via either the periodic bit or the tmr_reset()
+ ** call. This is because both of those guys call l_resort(), which
+ ** recomputes the hash and moves the timer to the appropriate list.
+ */
+ return ((unsigned int) t->time.tv_sec ^
+ (unsigned int) t->time.tv_usec) % HASH_SIZE;
+}
+
+
+static void l_add (Timer * t)
+{
+ int h = t->hash;
+ register Timer *t2;
+ register Timer *t2prev;
+
+ t2 = timers[h];
+ if (t2 == (Timer *) 0)
+ {
+ /* The list is empty. */
+ timers[h] = t;
+ t->prev = t->next = (Timer *) 0;
+ }
+ else
+ {
+ if (t->time.tv_sec < t2->time.tv_sec ||
+ (t->time.tv_sec == t2->time.tv_sec &&
+ t->time.tv_usec <= t2->time.tv_usec))
+ {
+ /* The new timer goes at the head of the list. */
+ timers[h] = t;
+ t->prev = (Timer *) 0;
+ t->next = t2;
+ t2->prev = t;
+ }
+ else
+ {
+ /* Walk the list to find the insertion point. */
+ for (t2prev = t2, t2 = t2->next; t2 != (Timer *) 0;
+ t2prev = t2, t2 = t2->next)
+ {
+ if (t->time.tv_sec < t2->time.tv_sec ||
+ (t->time.tv_sec == t2->time.tv_sec &&
+ t->time.tv_usec <= t2->time.tv_usec))
+ {
+ /* Found it. */
+ t2prev->next = t;
+ t->prev = t2prev;
+ t->next = t2;
+ t2->prev = t;
+ return;
+ }
+ }
+ /* Oops, got to the end of the list. Add to tail. */
+ t2prev->next = t;
+ t->prev = t2prev;
+ t->next = (Timer *) 0;
+ }
+ }
+}
+
+
+static void l_remove (Timer * t)
+{
+ int h = t->hash;
+
+ if (t->prev == (Timer *) 0)
+ timers[h] = t->next;
+ else
+ t->prev->next = t->next;
+ if (t->next != (Timer *) 0)
+ t->next->prev = t->prev;
+}
+
+
+static void l_resort (Timer * t)
+{
+ /* Remove the timer from its old list. */
+ l_remove (t);
+ /* Recompute the hash. */
+ t->hash = hash (t);
+ /* And add it back in to its new list, sorted correctly. */
+ l_add (t);
+}
+
+
+void tmr_init (void)
+{
+ int h;
+
+ for (h = 0; h < HASH_SIZE; ++h)
+ timers[h] = (Timer *) 0;
+ free_timers = (Timer *) 0;
+ alloc_count = active_count = free_count = 0;
+}
+
+
+Timer *tmr_create (struct timeval *nowP, TimerProc * timer_proc,
+ ClientData client_data, long msecs, int periodic)
+{
+ Timer *t;
+
+ if (free_timers != (Timer *) 0)
+ {
+ t = free_timers;
+ free_timers = t->next;
+ --free_count;
+ }
+ else
+ {
+ t = (Timer *) malloc (sizeof (Timer));
+ if (t == (Timer *) 0)
+ return (Timer *) 0;
+ ++alloc_count;
+ }
+
+ t->timer_proc = timer_proc;
+ t->client_data = client_data;
+ t->msecs = msecs;
+ t->periodic = periodic;
+ if (nowP != (struct timeval *) 0)
+ t->time = *nowP;
+ else
+ (void) gettimeofday (&t->time, (struct timezone *) 0);
+ t->time.tv_sec += msecs / 1000L;
+ t->time.tv_usec += (msecs % 1000L) * 1000L;
+ if (t->time.tv_usec >= 1000000L)
+ {
+ t->time.tv_sec += t->time.tv_usec / 1000000L;
+ t->time.tv_usec %= 1000000L;
+ }
+ t->hash = hash (t);
+ /* Add the new timer to the proper active list. */
+ l_add (t);
+ ++active_count;
+
+ return t;
+}
+
+
+struct timeval *tmr_timeout (struct timeval *nowP)
+{
+ long msecs;
+ static struct timeval timeout;
+
+ msecs = tmr_mstimeout (nowP);
+ if (msecs == INFTIM)
+ return (struct timeval *) 0;
+ timeout.tv_sec = msecs / 1000L;
+ timeout.tv_usec = (msecs % 1000L) * 1000L;
+ return &timeout;
+}
+
+
+long tmr_mstimeout (struct timeval *nowP)
+{
+ int h;
+ int gotone;
+ long msecs, m;
+ register Timer *t;
+
+ gotone = 0;
+ msecs = 0; /* make lint happy */
+ /* Since the lists are sorted, we only need to look at the
+ ** first timer on each one.
+ */
+ for (h = 0; h < HASH_SIZE; ++h)
+ {
+ t = timers[h];
+ if (t != (Timer *) 0)
+ {
+ m = (t->time.tv_sec - nowP->tv_sec) * 1000L +
+ (t->time.tv_usec - nowP->tv_usec) / 1000L;
+ if (!gotone)
+ {
+ msecs = m;
+ gotone = 1;
+ }
+ else if (m < msecs)
+ msecs = m;
+ }
+ }
+ if (!gotone)
+ return INFTIM;
+ if (msecs <= 0)
+ msecs = 0;
+ return msecs;
+}
+
+
+void tmr_run (struct timeval *nowP)
+{
+ int h;
+ Timer *t;
+ Timer *next;
+
+ for (h = 0; h < HASH_SIZE; ++h)
+ for (t = timers[h]; t != (Timer *) 0; t = next)
+ {
+ next = t->next;
+ /* Since the lists are sorted, as soon as we find a timer
+ ** that isn't ready yet, we can go on to the next list.
+ */
+ if (t->time.tv_sec > nowP->tv_sec ||
+ (t->time.tv_sec == nowP->tv_sec && t->time.tv_usec > nowP->tv_usec))
+ break;
+ (t->timer_proc) (t->client_data, nowP);
+ if (t->periodic)
+ {
+ /* Reschedule. */
+ t->time.tv_sec += t->msecs / 1000L;
+ t->time.tv_usec += (t->msecs % 1000L) * 1000L;
+ if (t->time.tv_usec >= 1000000L)
+ {
+ t->time.tv_sec += t->time.tv_usec / 1000000L;
+ t->time.tv_usec %= 1000000L;
+ }
+ l_resort (t);
+ }
+ else
+ tmr_cancel (t);
+ }
+}
+
+
+void tmr_reset (struct timeval *nowP, Timer * t)
+{
+ t->time = *nowP;
+ t->time.tv_sec += t->msecs / 1000L;
+ t->time.tv_usec += (t->msecs % 1000L) * 1000L;
+ if (t->time.tv_usec >= 1000000L)
+ {
+ t->time.tv_sec += t->time.tv_usec / 1000000L;
+ t->time.tv_usec %= 1000000L;
+ }
+ l_resort (t);
+}
+
+
+void tmr_cancel (Timer * t)
+{
+ /* Remove it from its active list. */
+ l_remove (t);
+ --active_count;
+ /* And put it on the free list. */
+ t->next = free_timers;
+ free_timers = t;
+ ++free_count;
+ t->prev = (Timer *) 0;
+}
+
+
+void tmr_cleanup (void)
+{
+ Timer *t;
+
+ while (free_timers != (Timer *) 0)
+ {
+ t = free_timers;
+ free_timers = t->next;
+ --free_count;
+ free ((void *) t);
+ --alloc_count;
+ }
+}
+
+
+void tmr_destroy (void)
+{
+ int h;
+
+ for (h = 0; h < HASH_SIZE; ++h)
+ while (timers[h] != (Timer *) 0)
+ tmr_cancel (timers[h]);
+ tmr_cleanup ();
+}
+
+
+/* Generate debugging statistics syslog message. */
+void tmr_logstats (long secs)
+{
+ syslog (LOG_INFO, " timers - %d allocated, %d active, %d free",
+ alloc_count, active_count, free_count);
+ if (active_count + free_count != alloc_count)
+ syslog (LOG_ERR, "timer counts don't add up!");
+}
diff --git a/gb.httpd/src/timers.h b/gb.httpd/src/timers.h
new file mode 100644
index 000000000..867484b84
--- /dev/null
+++ b/gb.httpd/src/timers.h
@@ -0,0 +1,110 @@
+/* timers.h - header file for timers package
+**
+** Copyright © 1995,1998,1999,2000 by Jef Poskanzer .
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. 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.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+*/
+
+#ifndef _TIMERS_H_
+#define _TIMERS_H_
+
+#include
+
+#ifndef INFTIM
+#define INFTIM -1
+#endif /* INFTIM */
+
+/* ClientData is a random value that tags along with a timer. The client
+** can use it for whatever, and it gets passed to the callback when the
+** timer triggers.
+*/
+typedef union
+{
+ void *p;
+ int i;
+ long l;
+} ClientData;
+
+extern ClientData JunkClientData; /* for use when you don't care */
+
+/* The TimerProc gets called when the timer expires. It gets passed
+** the ClientData associated with the timer, and a timeval in case
+** it wants to schedule another timer.
+*/
+typedef void TimerProc (ClientData client_data, struct timeval *nowP);
+
+/* The Timer struct. */
+typedef struct TimerStruct
+{
+ TimerProc *timer_proc;
+ ClientData client_data;
+ long msecs;
+ int periodic;
+ struct timeval time;
+ struct TimerStruct *prev;
+ struct TimerStruct *next;
+ int hash;
+} Timer;
+
+/* Initialize the timer package. */
+extern void tmr_init (void);
+
+/* Set up a timer, either periodic or one-shot. Returns (Timer*) 0 on errors. */
+extern Timer *tmr_create (struct timeval *nowP, TimerProc * timer_proc,
+ ClientData client_data, long msecs, int periodic);
+
+/* Returns a timeout indicating how long until the next timer triggers. You
+** can just put the call to this routine right in your select(). Returns
+** (struct timeval*) 0 if no timers are pending.
+*/
+extern struct timeval *tmr_timeout (struct timeval *nowP);
+
+/* Returns a timeout in milliseconds indicating how long until the next timer
+** triggers. You can just put the call to this routine right in your poll().
+** Returns INFTIM (-1) if no timers are pending.
+*/
+extern long tmr_mstimeout (struct timeval *nowP);
+
+/* Run the list of timers. Your main program needs to call this every so often,
+** or as indicated by tmr_timeout().
+*/
+extern void tmr_run (struct timeval *nowP);
+
+/* Reset the clock on a timer, to current time plus the original timeout. */
+extern void tmr_reset (struct timeval *nowP, Timer * timer);
+
+/* Deschedule a timer. Note that non-periodic timers are automatically
+** descheduled when they run, so you don't have to call this on them.
+*/
+extern void tmr_cancel (Timer * timer);
+
+/* Clean up the timers package, freeing any unused storage. */
+extern void tmr_cleanup (void);
+
+/* Cancel all timers and free storage, usually in preparation for exitting. */
+extern void tmr_destroy (void);
+
+/* Generate debugging statistics syslog message. */
+extern void tmr_logstats (long secs);
+
+#endif /* _TIMERS_H_ */
diff --git a/gb.httpd/src/version.h b/gb.httpd/src/version.h
new file mode 100644
index 000000000..5ad5212b8
--- /dev/null
+++ b/gb.httpd/src/version.h
@@ -0,0 +1,9 @@
+/* version.h - version defines for thttpd and libhttpd */
+
+#ifndef _VERSION_H_
+#define _VERSION_H_
+
+#define SERVER_SOFTWARE "thttpd/2.25b.patch2 " __DATE__
+#define SERVER_ADDRESS "http://www.acme.com/software/thttpd/"
+
+#endif /* _VERSION_H_ */
diff --git a/m4/gbhttpd.m4 b/m4/gbhttpd.m4
new file mode 100644
index 000000000..fea63a5a6
--- /dev/null
+++ b/m4/gbhttpd.m4
@@ -0,0 +1,188 @@
+dnl
+dnl Improved version of AC_CHECK_LIB
+dnl
+dnl Thanks to John Hawkinson (jhawk@mit.edu)
+dnl
+dnl usage:
+dnl
+dnl GB_AC_LBL_CHECK_LIB(LIBRARY, FUNCTION [, ACTION-IF-FOUND [,
+dnl ACTION-IF-NOT-FOUND [, OTHER-LIBRARIES]]])
+dnl
+dnl results:
+dnl
+dnl LIBS
+dnl
+
+define(GB_AC_LBL_CHECK_LIB,
+[AC_MSG_CHECKING([for $2 in -l$1])
+dnl Use a cache variable name containing both the library and function name,
+dnl because the test really is for library $1 defining function $2, not
+dnl just for library $1. Separate tests with the same $1 and different $2's
+dnl may have different results.
+ac_lib_var=`echo $1['_']$2['_']$5 | sed 'y%./+- %__p__%'`
+AC_CACHE_VAL(ac_cv_lbl_lib_$ac_lib_var,
+[ac_save_LIBS="$LIBS"
+LIBS="-l$1 $5 $LIBS"
+AC_TRY_LINK(dnl
+ifelse([$2], [main], , dnl Avoid conflicting decl of main.
+[/* Override any gcc2 internal prototype to avoid an error. */
+]ifelse(_AC_LANG_CURRENT, CPLUSPLUS, [#ifdef __cplusplus
+extern "C"
+#endif
+])dnl
+[/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $2();
+]),
+ [$2()],
+ eval "ac_cv_lbl_lib_$ac_lib_var=yes",
+ eval "ac_cv_lbl_lib_$ac_lib_var=no")
+LIBS="$ac_save_LIBS"
+])dnl
+if eval "test \"`echo '$ac_cv_lbl_lib_'$ac_lib_var`\" = yes"; then
+ AC_MSG_RESULT(yes)
+ ifelse([$3], ,
+[changequote(, )dnl
+ ac_tr_lib=HAVE_LIB`echo $1 | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+ -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+changequote([, ])dnl
+ AC_DEFINE_UNQUOTED($ac_tr_lib, [], [])
+ LIBS="-l$1 $LIBS"
+], [$3])
+else
+ AC_MSG_RESULT(no)
+ifelse([$4], , , [$4
+])dnl
+fi
+])
+
+dnl
+dnl GB_AC_LBL_LIBRARY_NET
+dnl
+dnl This test is for network applications that need socket() and
+dnl gethostbyname() -ish functions. Under Solaris, those applications
+dnl need to link with "-lsocket -lnsl". Under IRIX, they need to link
+dnl with "-lnsl" but should *not* link with "-lsocket" because
+dnl libsocket.a breaks a number of things (for instance:
+dnl gethostbyname() under IRIX 5.2, and snoop sockets under most
+dnl versions of IRIX).
+dnl
+dnl Unfortunately, many application developers are not aware of this,
+dnl and mistakenly write tests that cause -lsocket to be used under
+dnl IRIX. It is also easy to write tests that cause -lnsl to be used
+dnl under operating systems where neither are necessary (or useful),
+dnl such as SunOS 4.1.4, which uses -lnsl for TLI.
+dnl
+dnl This test exists so that every application developer does not test
+dnl this in a different, and subtly broken fashion.
+
+dnl It has been argued that this test should be broken up into two
+dnl seperate tests, one for the resolver libraries, and one for the
+dnl libraries necessary for using Sockets API. Unfortunately, the two
+dnl are carefully intertwined and allowing the autoconf user to use
+dnl them independantly potentially results in unfortunate ordering
+dnl dependancies -- as such, such component macros would have to
+dnl carefully use indirection and be aware if the other components were
+dnl executed. Since other autoconf macros do not go to this trouble,
+dnl and almost no applications use sockets without the resolver, this
+dnl complexity has not been implemented.
+dnl
+dnl The check for libresolv is in case you are attempting to link
+dnl statically and happen to have a libresolv.a lying around (and no
+dnl libnsl.a).
+dnl
+AC_DEFUN([GB_AC_LBL_LIBRARY_NET], [
+ # Most operating systems have gethostbyname() in the default searched
+ # libraries (i.e. libc):
+ AC_CHECK_FUNC(gethostbyname, ,
+ # Some OSes (eg. Solaris) place it in libnsl:
+ GB_AC_LBL_CHECK_LIB(nsl, gethostbyname, ,
+ # Some strange OSes (SINIX) have it in libsocket:
+ GB_AC_LBL_CHECK_LIB(socket, gethostbyname, ,
+ # Unfortunately libsocket sometimes depends on libnsl.
+ # AC_CHECK_LIB's API is essentially broken so the
+ # following ugliness is necessary:
+ GB_AC_LBL_CHECK_LIB(socket, gethostbyname,
+ LIBS="-lsocket -lnsl $LIBS",
+ AC_CHECK_LIB(resolv, gethostbyname),
+ -lnsl))))
+ AC_CHECK_FUNC(socket, , AC_CHECK_LIB(socket, socket, ,
+ GB_AC_LBL_CHECK_LIB(socket, socket, LIBS="-lsocket -lnsl $LIBS", ,
+ -lnsl)))
+ # DLPI needs putmsg under HPUX so test for -lstr while we're at it
+ AC_CHECK_LIB(str, putmsg)
+ ])
+
+dnl
+dnl Checks to see if struct tm has the BSD tm_gmtoff member
+dnl
+dnl usage:
+dnl
+dnl GB_AC_ACME_TM_GMTOFF
+dnl
+dnl results:
+dnl
+dnl HAVE_TM_GMTOFF (defined)
+dnl
+AC_DEFUN([GB_AC_ACME_TM_GMTOFF],
+ [AC_MSG_CHECKING(if struct tm has tm_gmtoff member)
+ AC_CACHE_VAL(ac_cv_acme_tm_has_tm_gmtoff,
+ AC_TRY_COMPILE([
+# include
+# include ],
+ [u_int i = sizeof(((struct tm *)0)->tm_gmtoff)],
+ ac_cv_acme_tm_has_tm_gmtoff=yes,
+ ac_cv_acme_tm_has_tm_gmtoff=no))
+ AC_MSG_RESULT($ac_cv_acme_tm_has_tm_gmtoff)
+ if test $ac_cv_acme_tm_has_tm_gmtoff = yes ; then
+ AC_DEFINE([HAVE_TM_GMTOFF], [], [if struct tm has the BSD tm_gmtoff member])
+ fi])
+
+dnl
+dnl Checks to see if int64_t exists
+dnl
+dnl usage:
+dnl
+dnl GB_AC_ACME_INT64T
+dnl
+dnl results:
+dnl
+dnl HAVE_INT64T (defined)
+dnl
+AC_DEFUN([GB_AC_ACME_INT64T],
+ [AC_MSG_CHECKING(if int64_t exists)
+ AC_CACHE_VAL(ac_cv_acme_int64_t,
+ AC_TRY_COMPILE([
+# include ],
+ [int64_t i64],
+ ac_cv_acme_int64_t=yes,
+ ac_cv_acme_int64_t=no))
+ AC_MSG_RESULT($ac_cv_acme_int64_t)
+ if test $ac_cv_acme_int64_t = yes ; then
+ AC_DEFINE([HAVE_INT64T], [], [if int64_t exists])
+ fi])
+
+dnl
+dnl Checks to see if socklen_t exists
+dnl
+dnl usage:
+dnl
+dnl GB_AC_ACME_SOCKLENT
+dnl
+dnl results:
+dnl
+dnl HAVE_SOCKLENT (defined)
+dnl
+AC_DEFUN([GB_AC_ACME_SOCKLENT],
+ [AC_MSG_CHECKING(if socklen_t exists)
+ AC_CACHE_VAL(ac_cv_acme_socklen_t,
+ AC_TRY_COMPILE([
+# include
+# include ],
+ [socklen_t slen],
+ ac_cv_acme_socklen_t=yes,
+ ac_cv_acme_socklen_t=no))
+ AC_MSG_RESULT($ac_cv_acme_socklen_t)
+ if test $ac_cv_acme_socklen_t = yes ; then
+ AC_DEFINE([HAVE_SOCKLENT], [], [if socklen_t exists])
+ fi])
diff --git a/main/gbx/gbx.c b/main/gbx/gbx.c
index eb9980fad..0491f6ba8 100644
--- a/main/gbx/gbx.c
+++ b/main/gbx/gbx.c
@@ -66,6 +66,7 @@ FILE *log_file;
static bool _welcome = FALSE;
static bool _quit_after_main = FALSE;
+static bool _run_httpd = FALSE;
static void NORETURN my_exit(int ret)
{
@@ -89,10 +90,11 @@ static void NORETURN fatal(const char *msg, ...)
my_exit(1);
}
-static void init(const char *file)
+static void init(const char *file, int argc, char **argv)
{
COMPONENT_init();
FILE_init();
+
EXEC_init();
CLASS_init();
CFILE_init();
@@ -112,6 +114,11 @@ static void init(const char *file)
else
fatal("no project file in '%s'.", file);
}
+
+ if (_run_httpd)
+ COMPONENT_exec("gb.httpd", argc, argv);
+
+ PROJECT_load_finish();
}
else
STACK_init();
@@ -127,7 +134,7 @@ static void init(const char *file)
static void main_exit(bool silent)
{
- // If the stack has not been initialized because the project could not be started, do it know
+ // If the stack has not been initialized because the project could not be started, do it now
if (!SP)
STACK_init();
@@ -207,17 +214,6 @@ int main(int argc, char *argv[])
if (setrlimit(RLIMIT_CORE, &rl))
perror(strerror(errno));*/
- /*VALUE c, s, a, b, r;
- double v = (argc - 2) / 2.0;
- c._float.value = cos(v);
- s._float.value = __builtin_sin(v);
- a._float.value = c._float.value * c._float.value;
- b._float.value = s._float.value * s._float.value;
- r._float.value = a._float.value + b._float.value;
-
- fprintf(stderr, "%.24g %.24g / %.24g + %.24g = %.24g %d\n", c._float.value, s._float.value, a._float.value, b._float.value, r._float.value, r._float.value == 1.0);
- */
-
MEMORY_init();
COMMON_init();
//STRING_init();
@@ -245,18 +241,19 @@ int main(int argc, char *argv[])
}
printf(
"Options:\n"
- " -g enter debugging mode\n"
- " -p activate profiling and debugging mode\n"
- " -k do not unload shared libraries\n"
+ " -g enter debugging mode\n"
+ " -p activate profiling and debugging mode\n"
+ " -k do not unload shared libraries\n"
+ " -H --httpd run through an embedded http server\n"
);
if (!EXEC_arch)
{
- printf(" -e evaluate an expression\n");
+ printf(" -e evaluate an expression\n");
}
printf(
- " -V --version display version\n"
- " -L --license display license\n"
- " -h --help display this help\n"
+ " -V --version display version\n"
+ " -L --license display license\n"
+ " -h --help display this help\n"
"\n"
);
@@ -284,7 +281,7 @@ int main(int argc, char *argv[])
TRY
{
- init(NULL);
+ init(NULL, argc, argv);
EVAL_string(argv[2]);
}
CATCH
@@ -323,6 +320,10 @@ int main(int argc, char *argv[])
{
_quit_after_main = TRUE;
}
+ else if (is_long_option(argv[i], 'H', "httpd"))
+ {
+ _run_httpd = TRUE;
+ }
else if (is_option(argv[i], '-'))
{
i++;
@@ -359,7 +360,7 @@ int main(int argc, char *argv[])
TRY
{
- init(file);
+ init(file, argc, argv);
if (!EXEC_arch)
argv[0] = PROJECT_name;
diff --git a/main/gbx/gbx_archive.c b/main/gbx/gbx_archive.c
index 73833444d..982c150a1 100644
--- a/main/gbx/gbx_archive.c
+++ b/main/gbx/gbx_archive.c
@@ -75,7 +75,7 @@ ARCHIVE *ARCHIVE_create(const char *name, const char *path)
return arch;
}
-static void load_exported_class(ARCHIVE *arch)
+void ARCHIVE_load_exported_class(ARCHIVE *arch)
{
/*STREAM stream;*/
char *buffer;
@@ -84,11 +84,19 @@ static void load_exported_class(ARCHIVE *arch)
CLASS *class;
CLASS **exported = NULL;
int i;
+ COMPONENT *current;
+ if (arch->exported_classes_loaded)
+ return;
+
+ current = COMPONENT_current;
+
+ COMPONENT_current = (COMPONENT *)arch->current_component;
+
/* COMPONENT_current is set => it will look in the archive */
#if DEBUG_COMP
- fprintf(stderr, "load_exported_class: %s (component: %s)\n", arch->name, COMPONENT_current ? COMPONENT_current->name : "?");
+ fprintf(stderr, "load_exported_class: %s (component: %s)\n", arch->name, COMPONENT_current ? COMPONENT_current->name : "?");
#endif
if (!FILE_exist(".list"))
@@ -139,6 +147,10 @@ static void load_exported_class(ARCHIVE *arch)
ARRAY_delete(&exported);
FREE(&buffer, "load_exported_class");
+
+ arch->exported_classes_loaded = TRUE;
+
+ COMPONENT_current = current;
}
#if 0
@@ -161,14 +173,6 @@ static void load_component(char *name)
FREE(&buffer, "load_dependencies");
}*/
-static void load_archive(ARCHIVE *arch, const char *path) //, bool dep)
-{
- arch->arch = ARCH_open(path);
- load_exported_class(arch);
- //if (dep)
- // load_dependencies(arch);
-}
-
static char *exist_library(const char *dir, const char *name)
{
char *path;
@@ -180,7 +184,7 @@ static char *exist_library(const char *dir, const char *name)
return NULL;
}
-void ARCHIVE_load(ARCHIVE *arch) //, bool dep)
+void ARCHIVE_load(ARCHIVE *arch, bool load_exp)
{
char *path;
@@ -204,7 +208,11 @@ void ARCHIVE_load(ARCHIVE *arch) //, bool dep)
sprintf(path, ARCH_PATTERN, COMPONENT_path, arch->name);
}
- load_archive(arch, path); //, dep);
+ arch->arch = ARCH_open(path);
+ arch->current_component = COMPONENT_current;
+
+ if (load_exp)
+ ARCHIVE_load_exported_class(arch); //, dep);
}
@@ -221,7 +229,7 @@ void ARCHIVE_create_main(const char *path)
void ARCHIVE_load_main()
{
- load_exported_class(ARCHIVE_main);
+ ARCHIVE_load_exported_class(ARCHIVE_main);
}
diff --git a/main/gbx/gbx_archive.h b/main/gbx/gbx_archive.h
index 026f773de..70f733e7f 100644
--- a/main/gbx/gbx_archive.h
+++ b/main/gbx/gbx_archive.h
@@ -39,7 +39,9 @@ typedef
char *domain;
TABLE *classes;
const char *path;
+ void *current_component;
unsigned translation_loaded : 1;
+ unsigned exported_classes_loaded : 1;
}
ARCHIVE;
@@ -65,7 +67,8 @@ void ARCHIVE_load_main(void);
ARCHIVE *ARCHIVE_create(const char *name, const char *path);
void ARCHIVE_delete(ARCHIVE *arch);
-void ARCHIVE_load(ARCHIVE *arch);
+void ARCHIVE_load(ARCHIVE *arch, bool load_exp);
+void ARCHIVE_load_exported_class(ARCHIVE *arch);
bool ARCHIVE_get(ARCHIVE *arch, const char **ppath, ARCHIVE_FIND *find);
diff --git a/main/gbx/gbx_component.c b/main/gbx/gbx_component.c
index e7936873e..2df6465be 100644
--- a/main/gbx/gbx_component.c
+++ b/main/gbx/gbx_component.c
@@ -65,6 +65,7 @@ char *COMPONENT_path;
static COMPONENT *_component_list = NULL;
+static bool _load_all = FALSE;
void COMPONENT_init(void)
{
@@ -123,11 +124,26 @@ void COMPONENT_load_all(void)
COMPONENT_create("gb.debug");
}
+ _load_all = TRUE;
+
LIST_for_each(comp, _component_list)
{
comp->preload = TRUE;
COMPONENT_load(comp);
}
+
+ _load_all = FALSE;
+}
+
+void COMPONENT_load_all_finish(void)
+{
+ COMPONENT *comp;
+
+ LIST_for_each(comp, _component_list)
+ {
+ if (comp->preload && comp->archive)
+ ARCHIVE_load_exported_class(comp->archive);
+ }
}
@@ -265,7 +281,7 @@ void COMPONENT_load(COMPONENT *comp)
}
if (comp->archive)
- ARCHIVE_load(comp->archive);
+ ARCHIVE_load(comp->archive, !_load_all);
comp->loading = FALSE;
comp->loaded = TRUE;
@@ -341,3 +357,16 @@ bool COMPONENT_get_info(const char *key, void **value)
return TRUE;
}
+
+void COMPONENT_exec(const char *name, int argc, char **argv)
+{
+ COMPONENT *comp;
+
+ comp = COMPONENT_create(name);
+
+ COMPONENT_load(comp);
+
+ if (comp->library)
+ LIBRARY_exec(comp->library, argc, argv);
+}
+
diff --git a/main/gbx/gbx_component.h b/main/gbx/gbx_component.h
index 2b86f2be7..7d0e662ea 100644
--- a/main/gbx/gbx_component.h
+++ b/main/gbx/gbx_component.h
@@ -67,9 +67,12 @@ COMPONENT *COMPONENT_find(const char *name);
bool COMPONENT_exist(const char *name);
void COMPONENT_load(COMPONENT *comp);
-void COMPONENT_load_all(void);
void COMPONENT_unload(COMPONENT *comp);
+void COMPONENT_load_all(void);
+void COMPONENT_load_all_finish(void);
+
+
COMPONENT *COMPONENT_next(COMPONENT *comp);
void COMPONENT_translation_must_be_reloaded(void);
@@ -80,4 +83,6 @@ void COMPONENT_signal(int signal, void *param);
bool COMPONENT_get_info(const char *key, void **value);
+void COMPONENT_exec(const char *name, int argc, char **argv);
+
#endif
diff --git a/main/gbx/gbx_library.c b/main/gbx/gbx_library.c
index eb00af46b..fc8df1373 100644
--- a/main/gbx/gbx_library.c
+++ b/main/gbx/gbx_library.c
@@ -274,6 +274,15 @@ int LIBRARY_load(LIBRARY *lib)
return order;
}
+void LIBRARY_exec(LIBRARY *lib, int argc, char **argv)
+{
+ void (*func)();
+
+ func = get_symbol(lib, LIB_MAIN, FALSE);
+ if (func)
+ (*func)(argc, argv);
+}
+
void LIBRARY_declare(GB_DESC **desc)
{
diff --git a/main/gbx/gbx_library.h b/main/gbx/gbx_library.h
index aaec49203..f02bbe7e2 100644
--- a/main/gbx/gbx_library.h
+++ b/main/gbx/gbx_library.h
@@ -71,4 +71,6 @@ void LIBRARY_declare(GB_DESC **desc);
bool LIBRARY_get_interface_by_name(const char *name, int version, void *iface);
void LIBRARY_get_interface(LIBRARY *lib, int version, void *iface);
+void LIBRARY_exec(LIBRARY *lib, int argc, char **argv);
+
#endif
diff --git a/main/gbx/gbx_project.c b/main/gbx/gbx_project.c
index 467ccd5ac..2f83f2c7b 100644
--- a/main/gbx/gbx_project.c
+++ b/main/gbx/gbx_project.c
@@ -461,17 +461,23 @@ bool PROJECT_load()
if (len < 0)
return TRUE;
- /* Loads all component */
+ // Loads all component
COMPONENT_load_all();
- /* Loads main archive */
- ARCHIVE_load_main();
-
- /* Startup class */
- PROJECT_class = CLASS_find(PROJECT_startup);
return FALSE;
}
+void PROJECT_load_finish(void)
+{
+ // Load exported class of components written in Gambas
+ COMPONENT_load_all_finish();
+
+ // Loads main archive
+ ARCHIVE_load_main();
+
+ // Startup class
+ PROJECT_class = CLASS_find(PROJECT_startup);
+}
void PROJECT_exit(void)
{
diff --git a/main/gbx/gbx_project.h b/main/gbx/gbx_project.h
index d34ad7082..d332d9ca2 100644
--- a/main/gbx/gbx_project.h
+++ b/main/gbx/gbx_project.h
@@ -52,6 +52,7 @@ EXTERN char *PROJECT_user_home;
void PROJECT_init(const char *file);
bool PROJECT_load(void);
+void PROJECT_load_finish(void);
void PROJECT_exit(void);
char *PROJECT_get_home(void);
void PROJECT_analyze_startup(char *addr, int len, PROJECT_COMPONENT_CALLBACK cb);
diff --git a/main/share/gb_component.h b/main/share/gb_component.h
index e98af53bf..c7d162524 100644
--- a/main/share/gb_component.h
+++ b/main/share/gb_component.h
@@ -43,6 +43,7 @@ typedef
#define LIB_NEED "GB_NEED"
#define LIB_GAMBAS "GB"
#define LIB_GAMBAS_PTR "GB_PTR"
+#define LIB_MAIN "GB_MAIN"
#ifdef DONT_USE_LTDL
#if defined(OS_MACOSX)