summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSascha Volkenandt <sascha (at) akv-soft (dot) de>2007-01-02 19:18:27 +0000
committerSascha Volkenandt <sascha (at) akv-soft (dot) de>2007-01-02 19:18:27 +0000
commit48c46dfdd986ad4a7a0692d05992f7882bef6a88 (patch)
tree88a3a88a7ab43632850569cba3ab48a1924d9e52
downloadvdr-plugin-live-48c46dfdd986ad4a7a0692d05992f7882bef6a88.tar.gz
vdr-plugin-live-48c46dfdd986ad4a7a0692d05992f7882bef6a88.tar.bz2
- initial checkin
-rw-r--r--COPYING340
-rw-r--r--HISTORY6
-rw-r--r--Makefile125
-rw-r--r--README11
-rw-r--r--channels.ecpp22
-rw-r--r--httpd/Makefile33
-rw-r--r--httpd/dispatcher.cpp109
-rw-r--r--httpd/job.cpp240
-rw-r--r--httpd/listener.cpp167
-rw-r--r--httpd/poller.cpp190
-rw-r--r--httpd/regex.cpp175
-rw-r--r--httpd/tnt/dispatcher.h121
-rw-r--r--httpd/tnt/gcryptinit.h37
-rw-r--r--httpd/tnt/gnutls.h146
-rw-r--r--httpd/tnt/job.h201
-rw-r--r--httpd/tnt/listener.h78
-rw-r--r--httpd/tnt/openssl.h121
-rw-r--r--httpd/tnt/poller.h61
-rw-r--r--httpd/tnt/regex.h79
-rw-r--r--httpd/tnt/ssl.h52
-rw-r--r--httpd/tnt/tntnet.h99
-rw-r--r--httpd/tnt/worker.h91
-rw-r--r--httpd/tntnet.cpp832
-rw-r--r--httpd/worker.cpp365
-rw-r--r--live.cpp86
-rw-r--r--setup.cpp33
-rw-r--r--setup.h26
-rw-r--r--thread.cpp40
-rw-r--r--thread.h22
-rw-r--r--tntconfig.cpp65
-rw-r--r--tntconfig.h28
31 files changed, 4001 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..f90922e
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 2 of the License, 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
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/HISTORY b/HISTORY
new file mode 100644
index 0000000..f9f21b7
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,6 @@
+VDR Plugin 'httpd' Revision History
+-----------------------------------
+
+2007-01-01: Version 0.0.1
+
+- Initial revision.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b69c1df
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,125 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+# $Id: Makefile,v 1.1 2007/01/02 19:18:27 lordjaxom Exp $
+
+# The official name of this plugin.
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
+# IPORTANT: the presence of this macro is important for the Make.config
+# file. So it must be defined, even if it is not used here!
+#
+PLUGIN = live
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).cpp | awk '{ print $$6 }' | sed -e 's/[";]//g')
+
+### The C++ compiler and options:
+
+CXX ?= g++
+CXXFLAGS ?= -fPIC -g -O2 -Wall -Woverloaded-virtual
+
+ECPPC ?= /usr/local/bin/ecppc
+CXXFLAGS += `tntnet-config --cxxflags`
+
+LDFLAGS += `tntnet-config --libs`
+
+### The directory environment:
+
+VDRDIR = ../../..
+LIBDIR = ../../lib
+TMPDIR = /tmp
+
+### Allow user defined options to overwrite defaults:
+
+-include $(VDRDIR)/Make.config
+
+### The version number of VDR's plugin API (taken from VDR's "config.h"):
+
+APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h)
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### Includes and Defines (add further entries here):
+
+INCLUDES += -I$(VDRDIR)/include -Ihttpd
+
+DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+
+SUBDIRS = httpd
+
+LIBS += httpd/libhttpd.a
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o thread.o tntconfig.o setup.o
+
+WEBS = channels.o
+
+### Implicit rules:
+
+%.o: %.cpp
+ $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $<
+
+%.cpp: %.ecpp
+ $(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_CPP) $<
+
+%.cpp: %.gif
+ $(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_GIF) -b $<
+
+%.cpp: %.jpg
+ $(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_JPG) -b $<
+
+%.cpp: %.css
+ $(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_CSS) -b $<
+
+%.cpp: %.js
+ $(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_JS) -b $<
+
+# Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+ @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@
+
+-include $(DEPFILE)
+
+### Targets:
+
+.PHONY: all dist clean SUBDIRS
+
+SUBDIRS:
+ @for dir in $(SUBDIRS); do \
+ make -C $$dir CXX="$(CXX)" CXXFLAGS="$(CXXFLAGS)" lib$$dir.a ; \
+ done
+
+all: libvdr-$(PLUGIN).so libtnt-$(PLUGIN).so
+
+libvdr-$(PLUGIN).so: $(OBJS) SUBDIRS
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) $(LIBS) -o $@
+ @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION)
+
+libtnt-$(PLUGIN).so: $(WEBS)
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -o $@ $^
+ @cp --remove-destination $@ $(LIBDIR)/$@
+
+dist: clean $(WEBS:%.o=%.cpp)
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @mkdir $(TMPDIR)/$(ARCHIVE)
+ @cp -a * $(TMPDIR)/$(ARCHIVE)
+ @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+ @-rm -f $(OBJS) $(WEBS) $(DEPFILE) *.so *.tgz core* *~
+ @for dir in $(SUBDIRS); do \
+ make -C $$dir clean ; \
+ done
+
+
diff --git a/README b/README
new file mode 100644
index 0000000..4e04b83
--- /dev/null
+++ b/README
@@ -0,0 +1,11 @@
+This is a "plugin" for the Video Disk Recorder (VDR).
+
+Written by: Your Name <email@host.dom>
+
+Project's homepage: URL
+
+Latest version available at: URL
+
+See the file COPYING for license information.
+
+Description:
diff --git a/channels.ecpp b/channels.ecpp
new file mode 100644
index 0000000..6458f5b
--- /dev/null
+++ b/channels.ecpp
@@ -0,0 +1,22 @@
+<%pre>
+#include <vdr/channels.h>
+</%pre>
+<html>
+ <head>
+ <title>ecpp-application testproject</title>
+ </head>
+ <body>
+<{
+
+ for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
+ if (!channel->GroupSep() && *channel->Name()) {
+}>
+ <$ channel->Name() $>
+<{
+ }
+ }
+
+
+}>
+ </body>
+</html>
diff --git a/httpd/Makefile b/httpd/Makefile
new file mode 100644
index 0000000..ffa7f0b
--- /dev/null
+++ b/httpd/Makefile
@@ -0,0 +1,33 @@
+CXX ?= g++
+AR ?= ar
+
+CXXFLAGS ?= -O2 -Woverloaded-virtual -Wall -fPIC
+
+INCLUDES += -I.
+
+OBJS = dispatcher.o job.o regex.o worker.o \
+ listener.o poller.o tntnet.o
+
+### Implicit rules:
+
+%.o: %.cpp
+ $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
+
+# Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+ @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@
+
+-include $(DEPFILE)
+
+### Targets:
+
+all: libhttpd.a
+
+libhttpd.a: $(OBJS)
+ $(AR) r $@ $^
+
+clean:
+ rm -f *.o core* libproctools.a proctest $(DEPFILE)
diff --git a/httpd/dispatcher.cpp b/httpd/dispatcher.cpp
new file mode 100644
index 0000000..8620e76
--- /dev/null
+++ b/httpd/dispatcher.cpp
@@ -0,0 +1,109 @@
+/* dispatcher.cpp
+ * Copyright (C) 2003-2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "tnt/dispatcher.h"
+#include <tnt/httperror.h>
+#include <functional>
+#include <iterator>
+#include <algorithm>
+#include <cxxtools/log.h>
+
+log_define("tntnet.dispatcher")
+
+namespace tnt
+{
+
+void Dispatcher::addUrlMapEntry(const std::string& url, const CompidentType& ci)
+{
+ cxxtools::WrLock lock(rwlock);
+
+ urlmap.push_back(urlmap_type::value_type(regex(url), ci));
+}
+
+Compident Dispatcher::mapComp(const std::string& compUrl) const
+{
+ urlmap_type::const_iterator pos = urlmap.begin();
+ return mapCompNext(compUrl, pos);
+}
+
+namespace {
+ class regmatch_formatter : public std::unary_function<const std::string&, std::string>
+ {
+ public:
+ regex_smatch what;
+ std::string operator() (const std::string& s) const
+ { return what.format(s); }
+ };
+}
+
+Dispatcher::urlMapCacheType::size_type Dispatcher::maxUrlMapCache = 8192;
+
+Dispatcher::CompidentType Dispatcher::mapCompNext(const std::string& compUrl,
+ Dispatcher::urlmap_type::const_iterator& pos) const
+{
+ // check cache
+ urlMapCacheType::key_type cacheKey = urlMapCacheType::key_type(compUrl, pos);
+ urlMapCacheType::const_iterator um = urlMapCache.find(cacheKey);
+ if (um != urlMapCache.end())
+ return um->second;
+
+ // no cache hit
+ regmatch_formatter formatter;
+
+ for (; pos != urlmap.end(); ++pos)
+ {
+ if (pos->first.match(compUrl, formatter.what))
+ {
+ const CompidentType& src = pos->second;
+
+ CompidentType ci;
+ ci.libname = formatter(src.libname);
+ ci.compname = formatter(src.compname);
+ if (src.hasPathInfo())
+ ci.setPathInfo(formatter(src.getPathInfo()));
+ std::transform(src.getArgs().begin(), src.getArgs().end(),
+ std::back_inserter(ci.getArgsRef()), formatter);
+
+ // clear cache after maxUrlMapCache distict requests
+ if (urlMapCache.size() >= maxUrlMapCache)
+ {
+ log_warn("clear url-map-cache");
+ urlMapCache.clear();
+ }
+
+ urlMapCache.insert(urlMapCacheType::value_type(cacheKey, ci));
+
+ return ci;
+ }
+ }
+
+ throw NotFoundException(compUrl);
+}
+
+Dispatcher::CompidentType Dispatcher::PosType::getNext()
+{
+ if (first)
+ first = false;
+ else
+ ++pos;
+
+ return dis.mapCompNext(url, pos);
+}
+
+}
diff --git a/httpd/job.cpp b/httpd/job.cpp
new file mode 100644
index 0000000..49ecba6
--- /dev/null
+++ b/httpd/job.cpp
@@ -0,0 +1,240 @@
+/* job.cpp
+ * Copyright (C) 2003-2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "tnt/job.h"
+#include <tnt/httpreply.h>
+#include <cxxtools/log.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+log_define("tntnet.job")
+
+namespace tnt
+{
+ unsigned Job::socket_read_timeout = 200;
+ unsigned Job::socket_write_timeout = 10000;
+ unsigned Job::keepalive_max = 1000;
+ unsigned Job::socket_buffer_size = 16384;
+
+ Job::~Job()
+ { }
+
+ void Job::clear()
+ {
+ parser.reset();
+ request.clear();
+ touch();
+ }
+
+ int Job::msecToTimeout(time_t currentTime) const
+ {
+ return (lastAccessTime - currentTime + 1) * 1000
+ + getKeepAliveTimeout()
+ - getSocketReadTimeout();
+ }
+
+ unsigned Job::getKeepAliveTimeout()
+ {
+ return HttpReply::getKeepAliveTimeout();
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ // Tcpjob
+ //
+ void Tcpjob::accept(const cxxtools::net::Server& listener)
+ {
+ log_debug("accept");
+ socket.accept(listener);
+
+ struct sockaddr_storage s = socket.getSockAddr();
+ struct sockaddr_storage sockaddr;
+ memcpy(&sockaddr, &s, sizeof(sockaddr));
+
+ char buffer[INET6_ADDRSTRLEN];
+ log_debug("connection accepted from "
+ << inet_ntop(AF_INET6, &(socket.getPeeraddr()), buffer, sizeof(buffer)));
+
+ getRequest().setPeerAddr(socket.getPeeraddr());
+ getRequest().setServerAddr(sockaddr);
+ getRequest().setSsl(false);
+ }
+
+ std::iostream& Tcpjob::getStream()
+ {
+ return socket;
+ }
+
+ int Tcpjob::getFd() const
+ {
+ return socket.getFd();
+ }
+
+ void Tcpjob::setRead()
+ {
+ socket.setTimeout(getSocketReadTimeout());
+ }
+
+ void Tcpjob::setWrite()
+ {
+ socket.setTimeout(getSocketWriteTimeout());
+ }
+
+#ifdef USE_SSL
+ ////////////////////////////////////////////////////////////////////////
+ // SslTcpjob
+ //
+ void SslTcpjob::accept(const SslServer& listener)
+ {
+ log_debug("accept (ssl)");
+ socket.accept(listener);
+ log_debug("connection accepted (ssl)");
+
+ struct sockaddr_storage s = socket.getSockAddr();
+ struct sockaddr_storage sockaddr;
+ memcpy(&sockaddr, &s, sizeof(sockaddr));
+
+ getRequest().setPeerAddr(socket.getPeeraddr());
+ getRequest().setServerAddr(sockaddr);
+ getRequest().setSsl(true);
+
+ setRead();
+ }
+
+ std::iostream& SslTcpjob::getStream()
+ {
+ return socket;
+ }
+
+ int SslTcpjob::getFd() const
+ {
+ return socket.getFd();
+ }
+
+ void SslTcpjob::setRead()
+ {
+ socket.setTimeout(getSocketReadTimeout());
+ }
+
+ void SslTcpjob::setWrite()
+ {
+ socket.setTimeout(getSocketWriteTimeout());
+ }
+
+#endif // USE_SSL
+
+#ifdef USE_GNUTLS
+ ////////////////////////////////////////////////////////////////////////
+ // GnuTlsTcpjob
+ //
+ void GnuTlsTcpjob::accept(const GnuTlsServer& listener)
+ {
+ log_debug("accept (ssl)");
+ socket.accept(listener);
+ log_debug("connection accepted (ssl)");
+
+ struct sockaddr_storage s = socket.getSockAddr();
+ struct sockaddr_storage sockaddr;
+ memcpy(&sockaddr, &s, sizeof(sockaddr));
+
+ getRequest().setPeerAddr(socket.getPeeraddr());
+ getRequest().setServerAddr(sockaddr);
+ getRequest().setSsl(true);
+
+ setRead();
+ }
+
+ std::iostream& GnuTlsTcpjob::getStream()
+ {
+ return socket;
+ }
+
+ int GnuTlsTcpjob::getFd() const
+ {
+ return socket.getFd();
+ }
+
+ void GnuTlsTcpjob::setRead()
+ {
+ socket.setTimeout(getSocketReadTimeout());
+ }
+
+ void GnuTlsTcpjob::setWrite()
+ {
+ socket.setTimeout(getSocketWriteTimeout());
+ }
+
+#endif // USE_GNUTLS
+
+ //////////////////////////////////////////////////////////////////////
+ // Jobqueue
+ //
+ void Jobqueue::put(JobPtr j)
+ {
+ log_debug("Jobqueue::put");
+ j->touch();
+
+ cxxtools::MutexLock lock(mutex);
+
+ if (capacity > 0)
+ {
+ while (jobs.size() >= capacity)
+ {
+ log_warn("Jobqueue full");
+ notFull.wait(lock);
+ }
+ }
+
+ jobs.push_back(j);
+
+ if (waitThreads == 0)
+ {
+ log_info("no waiting threads left");
+ noWaitThreads.signal();
+ }
+
+ notEmpty.signal();
+ }
+
+ Jobqueue::JobPtr Jobqueue::get()
+ {
+ // wait, until a job is available
+ ++waitThreads;
+
+ cxxtools::MutexLock lock(mutex);
+ while (jobs.empty())
+ notEmpty.wait(lock);
+
+ --waitThreads;
+
+ log_debug("Jobqueue: fetch job " << waitThreads << " waiting threads left");
+
+ // take next job (queue is locked)
+ JobPtr j = jobs.front();
+ jobs.pop_front();
+
+ // if there are more jobs, wake onther thread
+ if (!jobs.empty())
+ notEmpty.signal();
+ notFull.signal();
+
+ return j;
+ }
+
+}
diff --git a/httpd/listener.cpp b/httpd/listener.cpp
new file mode 100644
index 0000000..57c334d
--- /dev/null
+++ b/httpd/listener.cpp
@@ -0,0 +1,167 @@
+/* listener.cpp
+ * Copyright (C) 2003 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "tnt/listener.h"
+#include "tnt/tntnet.h"
+#include <cxxtools/log.h>
+#include <errno.h>
+#include <unistd.h>
+
+#ifdef WITH_GNUTLS
+# include "tnt/gnutls.h"
+#endif
+
+#ifdef WITH_OPENSSL
+# include "tnt/openssl.h"
+#endif
+
+log_define("tntnet.listener")
+
+static void doListenRetry(cxxtools::net::Server& server,
+ const char* ipaddr, unsigned short int port)
+{
+ for (unsigned n = 1; true; ++n)
+ {
+ try
+ {
+ log_debug("listen " << ipaddr << ':' << port);
+ server.listen(ipaddr, port, tnt::Listener::getBacklog());
+ return;
+ }
+ catch (const cxxtools::net::Exception& e)
+ {
+ log_debug("cxxtools::net::Exception caught: errno=" << e.getErrno() << " msg=" << e.what());
+ if (e.getErrno() != EADDRINUSE || n > tnt::Listener::getListenRetry())
+ {
+ log_debug("rethrow exception");
+ throw;
+ }
+ log_warn("address " << ipaddr << ':' << port << " in use - retry; n = " << n);
+ ::sleep(1);
+ }
+ }
+}
+
+namespace tnt
+{
+ void ListenerBase::doStop()
+ {
+ log_warn("stop listener " << ipaddr << ':' << port);
+ try
+ {
+ // connect once to wake up listener, so it will check stop-flag
+ cxxtools::net::Stream(ipaddr, port);
+ }
+ catch (const std::exception& e)
+ {
+ log_warn("error waking up listener: " << e.what() << " try 127.0.0.1");
+ cxxtools::net::Stream("127.0.0.1", port);
+ }
+ }
+
+ int Listener::backlog = 16;
+ unsigned Listener::listenRetry = 5;
+
+ Listener::Listener(const std::string& ipaddr, unsigned short int port, Jobqueue& q)
+ : ListenerBase(ipaddr, port),
+ queue(q)
+ {
+ log_info("listen ip=" << ipaddr << " port=" << port);
+ doListenRetry(server, ipaddr.c_str(), port);
+ }
+
+ void Listener::run()
+ {
+ // accept-loop
+ log_debug("enter accept-loop");
+ while (!Tntnet::shouldStop())
+ {
+ try
+ {
+ Tcpjob* j = new Tcpjob;
+ Jobqueue::JobPtr p(j);
+ j->accept(server);
+ log_debug("connection accepted");
+
+ if (Tntnet::shouldStop())
+ break;
+
+ queue.put(p);
+ }
+ catch (const std::exception& e)
+ {
+ log_error("error in accept-loop: " << e.what());
+ }
+ }
+
+ log_debug("stop listener");
+ }
+
+#ifdef WITH_GNUTLS
+#define USE_SSL
+
+#endif
+
+#ifdef WITH_OPENSSL
+#define USE_SSL
+
+#endif
+
+#ifdef USE_SSL
+ Ssllistener::Ssllistener(const char* certificateFile,
+ const char* keyFile,
+ const std::string& ipaddr, unsigned short int port,
+ Jobqueue& q)
+ : ListenerBase(ipaddr, port),
+ server(certificateFile, keyFile),
+ queue(q)
+ {
+ log_info("listen ip=" << ipaddr << " port=" << port << " (ssl)");
+ doListenRetry(server, ipaddr.c_str(), port);
+ }
+
+ void Ssllistener::run()
+ {
+ // accept-loop
+ log_debug("enter accept-loop (ssl)");
+ while (!Tntnet::shouldStop())
+ {
+ try
+ {
+ SslTcpjob* j = new SslTcpjob;
+ Jobqueue::JobPtr p(j);
+ j->accept(server);
+
+ if (Tntnet::shouldStop())
+ break;
+
+ queue.put(p);
+ }
+ catch (const std::exception& e)
+ {
+ log_error("error in ssl-accept-loop: " << e.what());
+ }
+ }
+
+ log_debug("stop ssl-listener");
+ }
+
+#endif // USE_SSL
+
+}
diff --git a/httpd/poller.cpp b/httpd/poller.cpp
new file mode 100644
index 0000000..c604f55
--- /dev/null
+++ b/httpd/poller.cpp
@@ -0,0 +1,190 @@
+/* poller.cpp
+ * Copyright (C) 2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "tnt/poller.h"
+#include "tnt/tntnet.h"
+#include <cxxtools/log.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+log_define("tntnet.poller")
+
+namespace tnt
+{
+ Poller::Poller(Jobqueue& q)
+ : queue(q),
+ poll_timeout(-1)
+ {
+ pipe(notify_pipe);
+ fcntl(notify_pipe[0], F_SETFL, O_NONBLOCK);
+
+ pollfds.reserve(16);
+ pollfds[0].fd = notify_pipe[0];
+ pollfds[0].events = POLLIN;
+ pollfds[0].revents = 0;
+ }
+
+ void Poller::append_new_jobs()
+ {
+ cxxtools::MutexLock lock(mutex);
+ if (!new_jobs.empty())
+ {
+ // append new jobs to current
+ log_debug("add " << new_jobs.size() << " new jobs to poll-list");
+
+ pollfds.reserve(current_jobs.size() + new_jobs.size() + 1);
+
+ time_t currentTime;
+ time(&currentTime);
+ for (jobs_type::iterator it = new_jobs.begin();
+ it != new_jobs.end(); ++it)
+ {
+ append(*it);
+ int msec;
+ if (poll_timeout < 0)
+ poll_timeout = (*it)->msecToTimeout(currentTime);
+ else if ((msec = (*it)->msecToTimeout(currentTime)) < poll_timeout)
+ poll_timeout = msec;
+ }
+
+ new_jobs.clear();
+ }
+ }
+
+ void Poller::append(Jobqueue::JobPtr& job)
+ {
+ current_jobs.push_back(job);
+
+ pollfd& p = *(pollfds.data() + current_jobs.size());
+ p.fd = job->getFd();
+ p.events = POLLIN;
+ }
+
+ void Poller::run()
+ {
+ while (!Tntnet::shouldStop())
+ {
+ append_new_jobs();
+
+ try
+ {
+ log_debug("poll timeout=" << poll_timeout);
+ ::poll(pollfds.data(), current_jobs.size() + 1, poll_timeout);
+ if (Tntnet::shouldStop())
+ {
+ log_warn("stop poller");
+ break;
+ }
+
+ poll_timeout = -1;
+
+ if (pollfds[0].revents != 0)
+ {
+ log_debug("read notify-pipe");
+ char ch;
+ ::read(notify_pipe[0], &ch, 1);
+ pollfds[0].revents = 0;
+ }
+
+ dispatch();
+ }
+ catch (const std::exception& e)
+ {
+ log_error("error in poll-loop: " << e.what());
+ }
+ }
+ }
+
+ void Poller::doStop()
+ {
+ log_debug("notify stop");
+ char ch = 'A';
+ ::write(notify_pipe[1], &ch, 1);
+ }
+
+ void Poller::dispatch()
+ {
+ log_debug("dispatch " << current_jobs.size() << " jobs");
+
+ time_t currentTime;
+ time(&currentTime);
+ for (unsigned i = 0; i < current_jobs.size(); )
+ {
+ if (pollfds[i + 1].revents & POLLIN)
+ {
+ log_debug("job found " << pollfds[i + 1].fd);
+
+ // put job into work-queue
+ queue.put(current_jobs[i]);
+ remove(i);
+ }
+ else if (pollfds[i + 1].revents != 0)
+ {
+ log_debug("pollevent " << std::hex << pollfds[i + 1].revents << " on fd " << pollfds[i + 1].fd);
+ remove(i);
+ }
+ else
+ {
+ // check timeout
+ int msec = current_jobs[i]->msecToTimeout(currentTime);
+ if (msec <= 0)
+ {
+ log_debug("keep-alive-timeout reached");
+ remove(i);
+ }
+ else if (poll_timeout < 0 || msec < poll_timeout)
+ poll_timeout = msec;
+
+ ++i;
+ }
+ }
+ }
+
+ void Poller::remove(jobs_type::size_type n)
+ {
+ // replace job with last job in poller-list
+ jobs_type::size_type last = current_jobs.size() - 1;
+
+ if (n != last)
+ {
+ pollfds[n + 1] = pollfds[last + 1];
+ current_jobs[n] = current_jobs[last];
+ }
+
+ current_jobs.pop_back();
+ }
+
+ void Poller::addIdleJob(Jobqueue::JobPtr job)
+ {
+ log_debug("addIdleJob " << job->getFd());
+
+ {
+ cxxtools::MutexLock lock(mutex);
+ new_jobs.push_back(job);
+ }
+
+ log_debug("notify " << job->getFd());
+
+ char ch = 'A';
+ ::write(notify_pipe[1], &ch, 1);
+
+ log_debug("addIdleJob ready");
+ }
+
+}
diff --git a/httpd/regex.cpp b/httpd/regex.cpp
new file mode 100644
index 0000000..a9f52fc
--- /dev/null
+++ b/httpd/regex.cpp
@@ -0,0 +1,175 @@
+/* regex.cpp
+ * Copyright (C) 2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "tnt/regex.h"
+#include <stdexcept>
+#include <locale>
+
+namespace tnt
+{
+ static inline bool isdigit(char ch)
+ {
+ return ch >= '0' && ch <= '9';
+ }
+
+ unsigned regex_smatch::size() const
+ {
+ unsigned n;
+ for (n = 0; n < 10 && matchbuf[n].rm_so >= 0; ++n)
+ ;
+
+ return n;
+ }
+
+ std::string regex_smatch::get(unsigned n) const
+ {
+ return str.substr(matchbuf[n].rm_so, matchbuf[n].rm_eo - matchbuf[n].rm_so);
+ }
+
+ std::string regex_smatch::format(const std::string& s) const
+ {
+ enum state_type
+ {
+ state_0,
+ state_esc,
+ state_var0,
+ state_var1,
+ state_1
+ } state;
+
+ state = state_0;
+ std::string ret;
+
+ for (std::string::const_iterator it = s.begin(); it != s.end(); ++it)
+ {
+ char ch = *it;
+
+ switch (state)
+ {
+ case state_0:
+ if (ch == '$')
+ state = state_var0;
+ else if (ch == '\\')
+ {
+ ret = std::string(s.begin(), it);
+ state = state_esc;
+ }
+ break;
+
+ case state_esc:
+ ret += ch;
+ state = state_1;
+ break;
+
+ case state_var0:
+ if (isdigit(ch))
+ {
+ ret = std::string(s.begin(), it - 1);
+ regoff_t s = matchbuf[ch - '0'].rm_so;
+ regoff_t e = matchbuf[ch - '0'].rm_eo;
+ if (s >= 0 && e >= 0)
+ ret.append(str, s, e-s);
+ state = state_1;
+ }
+ else
+ state = state_0;
+ break;
+
+ case state_1:
+ if (ch == '$')
+ state = state_var1;
+ else if (state == '\\')
+ state = state_esc;
+ else
+ ret += ch;
+ break;
+
+ case state_var1:
+ if (isdigit(ch))
+ {
+ unsigned s = matchbuf[ch - '0'].rm_so;
+ unsigned e = matchbuf[ch - '0'].rm_eo;
+ if (s >= 0 && e >= 0)
+ ret.append(str, s, e-s);
+ state = state_1;
+ }
+ else if (ch == '$')
+ ret += '$';
+ else
+ {
+ ret += '$';
+ ret += ch;
+ }
+ break;
+ }
+ }
+
+ switch (state)
+ {
+ case state_0:
+ case state_var0:
+ return s;
+
+ case state_esc:
+ return ret + '\\';
+
+ case state_var1:
+ return ret + '$';
+
+ case state_1:
+ return ret;
+ }
+
+ return ret;
+ }
+
+ void regex::checkerr(int ret) const
+ {
+ if (ret != 0)
+ {
+ char errbuf[256];
+ regerror(ret, &expr, errbuf, sizeof(errbuf));
+ throw std::runtime_error(errbuf);
+ }
+ }
+
+ bool regex::match(const std::string& str_, int eflags) const
+ {
+ regex_smatch smatch;
+ return match(str_, smatch, eflags);
+ }
+
+ bool regex::match(const std::string& str_, regex_smatch& smatch, int eflags) const
+ {
+ smatch.str = str_;
+ int ret = regexec(&expr, str_.c_str(),
+ sizeof(smatch.matchbuf) / sizeof(regmatch_t), smatch.matchbuf, eflags);
+
+ if (ret ==REG_NOMATCH)
+ return false;
+
+ checkerr(ret);
+ return true;
+ }
+
+ void regex::free()
+ {
+ regfree(&expr);
+ }
+}
diff --git a/httpd/tnt/dispatcher.h b/httpd/tnt/dispatcher.h
new file mode 100644
index 0000000..218efcb
--- /dev/null
+++ b/httpd/tnt/dispatcher.h
@@ -0,0 +1,121 @@
+/* tnt/dispatcher.h
+ * Copyright (C) 2003-2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef TNT_DISPATCHER_H
+#define TNT_DISPATCHER_H
+
+#include <cxxtools/thread.h>
+#include <tnt/urlmapper.h>
+#include <vector>
+#include <map>
+#include "tnt/regex.h"
+
+namespace tnt
+{
+ // Dispatcher - one per host
+ class Dispatcher : public Urlmapper
+ {
+ public:
+ class CompidentType : public Compident
+ {
+ public:
+ typedef std::vector<std::string> args_type;
+
+ private:
+ std::string pathinfo;
+ args_type args;
+ bool pathinfo_set;
+
+ public:
+ CompidentType()
+ : pathinfo_set(false)
+ { }
+
+ explicit CompidentType(const std::string& ident)
+ : Compident(ident),
+ pathinfo_set(false)
+ { }
+
+ bool hasPathInfo() const
+ { return pathinfo_set; }
+ void setPathInfo(const std::string& p)
+ { pathinfo = p; pathinfo_set = true; }
+ void setArgs(const args_type& a)
+ { args = a; }
+ const std::string& getPathInfo() const
+ { return pathinfo; }
+ const args_type& getArgs() const
+ { return args; }
+ args_type& getArgsRef()
+ { return args; }
+ };
+
+ private:
+ typedef std::vector<std::pair<regex, CompidentType> > urlmap_type;
+ urlmap_type urlmap; // map url to soname/compname
+ mutable cxxtools::RWLock rwlock;
+
+ typedef std::map<std::pair<std::string, urlmap_type::const_iterator>,
+ CompidentType> urlMapCacheType;
+ mutable urlMapCacheType urlMapCache;
+ static urlMapCacheType::size_type maxUrlMapCache;
+
+ // don't make this public - it's not threadsafe:
+ CompidentType mapCompNext(const std::string& compUrl,
+ urlmap_type::const_iterator& pos) const;
+
+ public:
+ virtual ~Dispatcher() { }
+
+ void addUrlMapEntry(const std::string& url, const CompidentType& ci);
+
+ Compident mapComp(const std::string& compUrl) const;
+
+ static urlMapCacheType::size_type getMaxUrlMapCache()
+ { return maxUrlMapCache; }
+ static void setMaxUrlMapCache(urlMapCacheType::size_type s)
+ { maxUrlMapCache = s; }
+
+ friend class PosType;
+
+ class PosType
+ {
+ const Dispatcher& dis;
+ cxxtools::RdLock lock;
+ urlmap_type::const_iterator pos;
+ std::string url;
+ bool first;
+
+ public:
+ PosType(const Dispatcher& d, const std::string& u)
+ : dis(d),
+ lock(dis.rwlock),
+ pos(dis.urlmap.begin()),
+ url(u),
+ first(true)
+ { }
+
+ CompidentType getNext();
+ };
+ };
+
+}
+
+#endif // TNT_DISPATCHER_H
+
diff --git a/httpd/tnt/gcryptinit.h b/httpd/tnt/gcryptinit.h
new file mode 100644
index 0000000..3b6ee33
--- /dev/null
+++ b/httpd/tnt/gcryptinit.h
@@ -0,0 +1,37 @@
+/* tnt/gcryptinit.h
+ * Copyright (C) 2003-2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef TNT_GCRYPTINIT_H
+#define TNT_GCRYPTINIT_H
+
+#include <gcrypt.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+gcry_error_t gcrypt_init();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TNT_GCRYPTINIT_H
+
diff --git a/httpd/tnt/gnutls.h b/httpd/tnt/gnutls.h
new file mode 100644
index 0000000..8d8811c
--- /dev/null
+++ b/httpd/tnt/gnutls.h
@@ -0,0 +1,146 @@
+/* tnt/gnutls.h
+ * Copyright (C) 2006 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef TNT_GNUTLS_H
+#define TNT_GNUTLS_H
+
+#include <cxxtools/tcpstream.h>
+#include <gnutls/gnutls.h>
+
+namespace tnt
+{
+ class GnuTlsException : public std::runtime_error
+ {
+ unsigned long code;
+ static std::string formatMessage(const std::string& function, int code_);
+
+ public:
+ GnuTlsException(const std::string& function, int code_)
+ : std::runtime_error(formatMessage(function, code_)),
+ code(code_)
+ { }
+
+ unsigned long getCode() const
+ { return code; }
+ };
+
+ class GnuTlsInit
+ {
+ static unsigned initCount;
+ static gnutls_dh_params dhParams;
+
+ public:
+ GnuTlsInit();
+ ~GnuTlsInit();
+
+ gnutls_dh_params getDhParams() const { return dhParams; }
+ };
+
+ class GnuTlsX509Cred
+ {
+ gnutls_certificate_credentials x509_cred;
+ GnuTlsInit init;
+
+ public:
+ GnuTlsX509Cred(const char* certificateFile, const char* privateKeyFile);
+ ~GnuTlsX509Cred();
+
+ operator gnutls_certificate_credentials() const { return x509_cred; }
+ };
+
+ class GnuTlsServer : public cxxtools::net::Server
+ {
+ GnuTlsX509Cred cred;
+
+ public:
+ GnuTlsServer(const char* certificateFile, const char* privateKeyFile);
+
+ gnutls_certificate_credentials getCred() const { return cred; }
+ };
+
+ class GnuTlsStream : public cxxtools::net::Stream
+ {
+ gnutls_session session;
+
+ public:
+ GnuTlsStream()
+ : session(0)
+ { }
+
+ explicit GnuTlsStream(int fd)
+ : cxxtools::net::Stream(fd)
+ { }
+
+ explicit GnuTlsStream(const GnuTlsServer& server)
+ { accept(server); }
+
+ ~GnuTlsStream();
+
+ void accept(const GnuTlsServer& server);
+ int sslRead(char* buffer, int bufsize) const;
+ int sslWrite(const char* buffer, int bufsize) const;
+ void shutdown() const;
+ };
+
+ class GnuTls_streambuf : public std::streambuf
+ {
+ GnuTlsStream& m_stream;
+ char_type* m_buffer;
+ unsigned m_bufsize;
+
+ public:
+ explicit GnuTls_streambuf(GnuTlsStream& stream, unsigned bufsize = 256, int timeout = -1);
+ ~GnuTls_streambuf()
+ { delete[] m_buffer; }
+
+ void setTimeout(int t) { m_stream.setTimeout(t); }
+ int getTimeout() const { return m_stream.getTimeout(); }
+
+ /// overload std::streambuf
+ int_type overflow(int_type c);
+ /// overload std::streambuf
+ int_type underflow();
+ /// overload std::streambuf
+ int sync();
+ };
+
+ class GnuTls_iostream : public GnuTlsStream, public std::iostream
+ {
+ GnuTls_streambuf m_buffer;
+
+ public:
+ explicit GnuTls_iostream(unsigned bufsize = 256, int timeout = -1)
+ : GnuTlsStream(-1),
+ std::iostream(&m_buffer),
+ m_buffer(*this, bufsize, timeout)
+ { }
+
+ explicit GnuTls_iostream(const GnuTlsServer& server, unsigned bufsize = 256, int timeout = -1)
+ : GnuTlsStream(server),
+ std::iostream(&m_buffer),
+ m_buffer(*this, bufsize, timeout)
+ { }
+
+ void setTimeout(int timeout) { m_buffer.setTimeout(timeout); }
+ int getTimeout() const { return m_buffer.getTimeout(); }
+ };
+}
+
+#endif // TNT_GNUTLS_H
+
diff --git a/httpd/tnt/job.h b/httpd/tnt/job.h
new file mode 100644
index 0000000..5e839fd
--- /dev/null
+++ b/httpd/tnt/job.h
@@ -0,0 +1,201 @@
+/* tnt/job.h
+ * Copyright (C) 2003-2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef TNT_JOB_H
+#define TNT_JOB_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <deque>
+#include <cxxtools/thread.h>
+#include <cxxtools/tcpstream.h>
+#include <tnt/httprequest.h>
+#include <tnt/httpparser.h>
+#include <tnt/pointer.h>
+#include <time.h>
+#include "tnt/ssl.h"
+
+/**
+// in tntnet (mainthread):
+Jobqueue queue;
+void mainloop()
+{
+ while (1)
+ {
+ Jobqueue::JobPtr j = new Tcpjob();
+ j->accept(poller.get());
+ queue.put(j);
+ }
+}
+
+// in server (workerthread):
+void Server::run()
+{
+ while (1)
+ {
+ Jobqueue::JobPtr j = queue.get();
+ std::iostream& socket = j->getStream();
+ processRequest(socket);
+ }
+}
+*/
+
+namespace tnt
+{
+ /** Job - one per request */
+ class Job
+ {
+ unsigned keepAliveCounter;
+
+ HttpRequest request;
+ HttpMessage::Parser parser;
+ time_t lastAccessTime;
+
+ unsigned refs;
+
+ static unsigned socket_read_timeout;
+ static unsigned socket_write_timeout;
+ static unsigned keepalive_max;
+ static unsigned socket_buffer_size;
+
+ public:
+ Job()
+ : keepAliveCounter(keepalive_max),
+ parser(request),
+ lastAccessTime(0),
+ refs(0)
+ { }
+
+ protected:
+ virtual ~Job();
+
+ public:
+ unsigned addRef() { return ++refs; }
+ unsigned release()
+ {
+ if (--refs == 0)
+ {
+ delete this;
+ return 0;
+ }
+ else
+ return refs;
+ }
+
+ virtual std::iostream& getStream() = 0;
+ virtual int getFd() const = 0;
+ virtual void setRead() = 0;
+ virtual void setWrite() = 0;
+
+ HttpRequest& getRequest() { return request; }
+ HttpMessage::Parser& getParser() { return parser; }
+
+ unsigned decrementKeepAliveCounter()
+ { return keepAliveCounter > 0 ? --keepAliveCounter : 0; }
+ void clear();
+ void touch() { time(&lastAccessTime); }
+ int msecToTimeout(time_t currentTime) const;
+
+ static void setSocketReadTimeout(unsigned ms) { socket_read_timeout = ms; }
+ static void setSocketWriteTimeout(unsigned ms) { socket_write_timeout = ms; }
+ static void setKeepAliveMax(unsigned n) { keepalive_max = n; }
+ static void setSocketBufferSize(unsigned b) { socket_buffer_size = b; }
+
+ static unsigned getSocketReadTimeout() { return socket_read_timeout; }
+ static unsigned getSocketWriteTimeout() { return socket_write_timeout; }
+ static unsigned getKeepAliveTimeout();
+ static unsigned getKeepAliveMax() { return keepalive_max; }
+ static unsigned getSocketBufferSize() { return socket_buffer_size; }
+ };
+
+ class Tcpjob : public Job
+ {
+ cxxtools::net::iostream socket;
+
+ public:
+ Tcpjob()
+ : socket(getSocketBufferSize(), getSocketReadTimeout())
+ { }
+
+ void accept(const cxxtools::net::Server& listener);
+
+ std::iostream& getStream();
+ int getFd() const;
+ void setRead();
+ void setWrite();
+ };
+
+#ifdef USE_SSL
+ class SslTcpjob : public Job
+ {
+ ssl_iostream socket;
+
+ public:
+ SslTcpjob()
+ : socket(getSocketBufferSize(), getSocketReadTimeout())
+ { }
+
+ void accept(const SslServer& listener);
+
+ std::iostream& getStream();
+ int getFd() const;
+ void setRead();
+ void setWrite();
+ };
+#endif // USE_SSL
+
+ /** Jobqueue - one per process */
+ class Jobqueue
+ {
+ public:
+ typedef Pointer<Job> JobPtr;
+
+ cxxtools::Condition noWaitThreads;
+
+ private:
+ std::deque<JobPtr> jobs;
+ cxxtools::Mutex mutex;
+ cxxtools::Condition notEmpty;
+ cxxtools::Condition notFull;
+ unsigned waitThreads;
+ unsigned capacity;
+
+ public:
+ explicit Jobqueue(unsigned capacity_)
+ : waitThreads(0),
+ capacity(capacity_)
+ { }
+
+ void put(JobPtr j);
+ JobPtr get();
+
+ void setCapacity(unsigned c)
+ { capacity = c; }
+ unsigned getCapacity() const
+ { return capacity; }
+ unsigned getWaitThreadCount() const
+ { return waitThreads; }
+ bool empty() const
+ { return jobs.empty(); }
+ };
+}
+
+#endif // TNT_JOB_H
+
diff --git a/httpd/tnt/listener.h b/httpd/tnt/listener.h
new file mode 100644
index 0000000..4991763
--- /dev/null
+++ b/httpd/tnt/listener.h
@@ -0,0 +1,78 @@
+/* tnt/listener.h
+ * Copyright (C) 2003 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef TNT_LISTENER_H
+#define TNT_LISTENER_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <cxxtools/thread.h>
+#include "tnt/job.h"
+
+namespace tnt
+{
+ class ListenerBase : public cxxtools::AttachedThread
+ {
+ std::string ipaddr;
+ unsigned short int port;
+
+ public:
+ ListenerBase(const std::string& ipaddr_, unsigned short int port_)
+ : ipaddr(ipaddr_),
+ port(port_)
+ { }
+
+ void doStop();
+ };
+
+ class Listener : public ListenerBase
+ {
+ cxxtools::net::Server server;
+ Jobqueue& queue;
+ static int backlog;
+ static unsigned listenRetry;
+
+ public:
+ Listener(const std::string& ipaddr, unsigned short int port, Jobqueue& q);
+ virtual void run();
+
+ static void setBacklog(int backlog_) { backlog = backlog_; }
+ static int getBacklog() { return backlog; }
+ static void setListenRetry(unsigned listenRetry_) { listenRetry = listenRetry_; }
+ static unsigned getListenRetry() { return listenRetry; }
+ };
+
+#ifdef USE_SSL
+ class Ssllistener : public ListenerBase
+ {
+ SslServer server;
+ Jobqueue& queue;
+
+ public:
+ Ssllistener(const char* certificateFile, const char* keyFile,
+ const std::string& ipaddr, unsigned short int port, Jobqueue& q);
+ virtual void run();
+ };
+#endif // USE_SSL
+
+}
+
+#endif // TNT_LISTENER_H
+
diff --git a/httpd/tnt/openssl.h b/httpd/tnt/openssl.h
new file mode 100644
index 0000000..863782f
--- /dev/null
+++ b/httpd/tnt/openssl.h
@@ -0,0 +1,121 @@
+/* tnt/openssl.h
+ * Copyright (C) 2003 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef TNT_OPENSSL_H
+#define TNT_OPENSSL_H
+
+#include <cxxtools/tcpstream.h>
+#include <openssl/ssl.h>
+
+namespace tnt
+{
+ class OpensslException : public std::runtime_error
+ {
+ unsigned long code;
+
+ public:
+ OpensslException(const std::string& what, unsigned long code_)
+ : std::runtime_error(what),
+ code(code_)
+ { }
+
+ unsigned long getCode() const
+ { return code; }
+ };
+
+ class OpensslServer : public cxxtools::net::Server
+ {
+ SSL_CTX* ctx;
+ void installCertificates(const char* certificateFile, const char* privateKeyFile);
+
+ public:
+ OpensslServer(const char* certificateFile);
+ OpensslServer(const char* certificateFile, const char* privateKeyFile);
+ ~OpensslServer();
+
+ SSL_CTX* getSslContext() const { return ctx; }
+ };
+
+ class OpensslStream : public cxxtools::net::Stream
+ {
+ SSL* ssl;
+
+ public:
+ OpensslStream();
+
+ explicit OpensslStream(int fd)
+ : cxxtools::net::Stream(fd)
+ { }
+
+ explicit OpensslStream(const OpensslServer& server);
+ ~OpensslStream();
+
+ void accept(const OpensslServer& server);
+
+ int sslRead(char* buffer, int bufsize) const;
+ int sslWrite(const char* buffer, int bufsize) const;
+ void shutdown() const;
+ };
+
+ class openssl_streambuf : public std::streambuf
+ {
+ OpensslStream& m_stream;
+ char_type* m_buffer;
+ unsigned m_bufsize;
+
+ public:
+ explicit openssl_streambuf(OpensslStream& stream, unsigned bufsize = 256, int timeout = -1);
+ ~openssl_streambuf()
+ { delete[] m_buffer; }
+
+ void setTimeout(int t) { m_stream.setTimeout(t); }
+ int getTimeout() const { return m_stream.getTimeout(); }
+
+ /// overload std::streambuf
+ int_type overflow(int_type c);
+ /// overload std::streambuf
+ int_type underflow();
+ /// overload std::streambuf
+ int sync();
+ };
+
+ class openssl_iostream : public OpensslStream, public std::iostream
+ {
+ openssl_streambuf m_buffer;
+
+ public:
+ explicit openssl_iostream(unsigned bufsize = 256, int timeout = -1)
+ : OpensslStream(-1),
+ std::iostream(&m_buffer),
+ m_buffer(*this, bufsize, timeout)
+ { }
+
+ explicit openssl_iostream(const OpensslServer& server, unsigned bufsize = 256, int timeout = -1)
+ : OpensslStream(server),
+ std::iostream(&m_buffer),
+ m_buffer(*this, bufsize, timeout)
+ { }
+
+ void setTimeout(int timeout) { m_buffer.setTimeout(timeout); }
+ int getTimeout() const { return m_buffer.getTimeout(); }
+ };
+}
+
+#endif // TNT_OPENSSL_H
+
diff --git a/httpd/tnt/poller.h b/httpd/tnt/poller.h
new file mode 100644
index 0000000..d9b12e9
--- /dev/null
+++ b/httpd/tnt/poller.h
@@ -0,0 +1,61 @@
+/* tnt/poller.h
+ * Copyright (C) 2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef TNT_POLLER_H
+#define TNT_POLLER_H
+
+#include "tnt/job.h"
+#include <cxxtools/dynbuffer.h>
+#include <cxxtools/thread.h>
+#include <sys/poll.h>
+
+namespace tnt
+{
+ class Poller : public cxxtools::AttachedThread
+ {
+ Jobqueue& queue;
+ int notify_pipe[2];
+
+ typedef std::deque<Jobqueue::JobPtr> jobs_type;
+
+ jobs_type current_jobs;
+ cxxtools::Dynbuffer<pollfd> pollfds;
+
+ jobs_type new_jobs;
+
+ cxxtools::Mutex mutex;
+ int poll_timeout;
+
+ void append_new_jobs();
+ void append(Jobqueue::JobPtr& job);
+ void dispatch();
+ void remove(jobs_type::size_type n);
+
+ public:
+ Poller(Jobqueue& q);
+
+ virtual void run();
+ void doStop();
+ void addIdleJob(Jobqueue::JobPtr job);
+ };
+
+}
+
+#endif // TNT_POLLER_H
+
diff --git a/httpd/tnt/regex.h b/httpd/tnt/regex.h
new file mode 100644
index 0000000..b2a30e8
--- /dev/null
+++ b/httpd/tnt/regex.h
@@ -0,0 +1,79 @@
+/* tnt/regex.h
+ * Copyright (C) 2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef TNT_REGEX_H
+#define TNT_REGEX_H
+
+#include <string>
+#include <sys/types.h>
+#include <regex.h>
+
+namespace tnt
+{
+ /// collects matches in a regex
+ class regex_smatch
+ {
+ friend class regex;
+
+ std::string str;
+ regmatch_t matchbuf[10];
+
+ public:
+ unsigned size() const;
+ std::string get(unsigned n) const;
+ std::string format(const std::string& s) const;
+ };
+
+ /// regex(3)-wrapper.
+ /// Warning: incomplete, but sufficient for tntnet.
+ /// Regular expression is not automatically freed. Tntnet needs to
+ /// put regex into a stl-container, so it needs to be copyable.
+ /// For this class to be complete, the regex_t needs to be
+ /// reference-counted. This is unneeded for tntnet, because the regex is
+ /// never freed anyway.
+ class regex
+ {
+ regex_t expr;
+
+ void checkerr(int ret) const;
+
+ public:
+ explicit regex(const char* ex, int cflags = REG_EXTENDED)
+ {
+ checkerr(::regcomp(&expr, ex, cflags));
+ }
+
+ explicit regex(const std::string& ex, int cflags = REG_EXTENDED)
+ {
+ checkerr(::regcomp(&expr, ex.c_str(), cflags));
+ }
+
+ bool match(const std::string& str_, regex_smatch& smatch, int eflags = 0) const;
+ bool match(const std::string& str_, int eflags = 0) const;
+
+ void free();
+
+ private:
+ friend class value_type;
+ };
+
+}
+
+#endif // TNT_REGEX_H
+
diff --git a/httpd/tnt/ssl.h b/httpd/tnt/ssl.h
new file mode 100644
index 0000000..6660d2f
--- /dev/null
+++ b/httpd/tnt/ssl.h
@@ -0,0 +1,52 @@
+/* tnt/ssl.h
+ * Copyright (C) 2006 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef TNT_SSL_H
+#define TNT_SSL_H
+
+#ifdef WITH_GNUTLS
+# include "tnt/gnutls.h"
+# define USE_SSL
+#endif
+
+#ifdef WITH_OPENSSL
+# include "tnt/openssl.h"
+# define USE_SSL
+#endif
+
+namespace tnt
+{
+#ifdef WITH_GNUTLS
+
+ typedef GnuTlsServer SslServer;
+ typedef GnuTls_iostream ssl_iostream;
+
+#endif
+
+#ifdef WITH_OPENSSL
+
+ typedef OpensslServer SslServer;
+ typedef openssl_iostream ssl_iostream;
+
+#endif
+
+}
+
+#endif // TNT_SSL_H
+
diff --git a/httpd/tnt/tntnet.h b/httpd/tnt/tntnet.h
new file mode 100644
index 0000000..a83784e
--- /dev/null
+++ b/httpd/tnt/tntnet.h
@@ -0,0 +1,99 @@
+/* tnt/tntnet.h
+ * Copyright (C) 2003-2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef TNT_TNTNET_H
+#define TNT_TNTNET_H
+
+#include <cxxtools/arg.h>
+#include "tnt/tntconfig.h"
+#include "tnt/job.h"
+#include "tnt/poller.h"
+#include "tnt/dispatcher.h"
+#include <tnt/scopemanager.h>
+#include <set>
+
+namespace tnt
+{
+ class ListenerBase;
+
+ class Tntnet
+ {
+ std::string configFile;
+ Tntconfig config;
+ cxxtools::Arg<const char*> propertyfilename;
+ cxxtools::Arg<bool> debug;
+ bool isDaemon;
+
+ unsigned minthreads;
+ unsigned maxthreads;
+ unsigned long threadstartdelay;
+
+ Jobqueue queue;
+
+ static bool stop;
+ static int ret;
+ typedef std::set<ListenerBase*> listeners_type;
+ listeners_type listeners;
+
+ Poller pollerthread;
+ Dispatcher d_dispatcher;
+
+ static std::string pidFileName;
+
+ ScopeManager scopemanager;
+
+ // helper methods
+ void setUser() const;
+ void setGroup() const;
+ void setDir(const char* def) const;
+ int mkDaemon() const; // returns pipe
+ void closeStdHandles() const;
+
+ // noncopyable
+ Tntnet(const Tntnet&);
+ Tntnet& operator= (const Tntnet&);
+
+ void initLogging();
+ void writePidfile(int pid);
+ void monitorProcess(int workerPid);
+ void initWorkerProcess();
+ void workerProcess(int filedes = -1);
+
+ void timerTask();
+ void loadConfiguration();
+
+ public:
+ Tntnet(int& argc, char* argv[]);
+ int run();
+
+ static void shutdown();
+ static void restart();
+ static bool shouldStop() { return stop; }
+
+ Jobqueue& getQueue() { return queue; }
+ Poller& getPoller() { return pollerthread; }
+ const Dispatcher& getDispatcher() const { return d_dispatcher; }
+ const Tntconfig& getConfig() const { return config; }
+ ScopeManager& getScopemanager() { return scopemanager; }
+ };
+
+}
+
+#endif // TNT_TNTNET_H
+
diff --git a/httpd/tnt/worker.h b/httpd/tnt/worker.h
new file mode 100644
index 0000000..be42841
--- /dev/null
+++ b/httpd/tnt/worker.h
@@ -0,0 +1,91 @@
+/* tnt/worker.h
+ * Copyright (C) 2003-2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef TNT_WORKER_H
+#define TNT_WORKER_H
+
+#include <string>
+#include <cxxtools/tcpstream.h>
+#include <cxxtools/thread.h>
+#include <cxxtools/pool.h>
+#include <tnt/comploader.h>
+#include <tnt/tntnet.h>
+#include <tnt/scope.h>
+
+namespace tnt
+{
+ class HttpRequest;
+ class HttpReply;
+
+ class Worker : public cxxtools::DetachedThread
+ {
+ static cxxtools::Mutex mutex;
+ static unsigned nextThreadNumber;
+
+ Tntnet& application;
+
+ typedef cxxtools::Pool<Comploader> ComploaderPoolType;
+ static ComploaderPoolType comploaderPool;
+
+ ComploaderPoolType::objectptr_type comploaderObject;
+ Comploader& comploader;
+
+ Scope threadScope;
+
+ pthread_t threadId;
+ const char* state;
+ time_t lastWaitTime;
+
+ typedef std::set<Worker*> workers_type;
+ static workers_type workers;
+
+ static unsigned maxRequestTime;
+ static unsigned minThreads;
+ static bool enableCompression;
+
+ bool processRequest(HttpRequest& request, std::iostream& socket,
+ unsigned keepAliveCount);
+ void healthCheck(time_t currentTime);
+
+ ~Worker();
+
+ public:
+ Worker(Tntnet& app);
+
+ virtual void run();
+
+ void dispatch(HttpRequest& request, HttpReply& reply);
+
+ static void timer();
+
+ /// Sets a hard limit for request-time.
+ /// When the time is exceeded, this process exits.
+ static void setMaxRequestTime(unsigned sec) { maxRequestTime = sec; }
+ static unsigned getMaxRequestTime() { return maxRequestTime; }
+
+ static workers_type::size_type getCountThreads();
+ static void setMinThreads(unsigned n) { minThreads = n; }
+
+ static void setEnableCompression(bool sw = true) { enableCompression = sw; }
+ static unsigned getEnableCompression() { return enableCompression; }
+ };
+}
+
+#endif // TNT_WORKER_H
+
diff --git a/httpd/tntnet.cpp b/httpd/tntnet.cpp
new file mode 100644
index 0000000..ebaad62
--- /dev/null
+++ b/httpd/tntnet.cpp
@@ -0,0 +1,832 @@
+/* tntnet.cpp
+ * Copyright (C) 2003-2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "tnt/worker.h"
+#include "tnt/tntnet.h"
+#include "tnt/listener.h"
+#include "tnt/http.h"
+#include "tnt/httpreply.h"
+#include "tnt/sessionscope.h"
+
+#include <cxxtools/tcpstream.h>
+#include <cxxtools/log.h>
+#include <cxxtools/loginit.h>
+
+#include <iostream>
+#include <fstream>
+#include <vector>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <errno.h>
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifndef TNTNET_CONF
+# define TNTNET_CONF "/etc/tntnet.conf"
+#endif
+
+#ifndef TNTNET_PID
+# define TNTNET_PID "/var/run/tntnet.pid"
+#endif
+
+log_define("tntnet.tntnet")
+
+#define log_error_master(expr) \
+ do { \
+ std::cout << "ERROR: " << expr << std::endl; \
+ } while(false)
+
+#define log_warn_master(expr) \
+ do { \
+ std::cout << "WARN: " << expr << std::endl; \
+ } while(false)
+
+#define log_info_master(expr) \
+ do { \
+ std::cout << "INFO: " << expr << std::endl; \
+ } while(false)
+
+#define log_debug_master(expr) \
+ do { \
+ std::cout << "DEBUG: " << expr << std::endl; \
+ } while(false)
+
+namespace
+{
+ void sigEnd(int)
+ {
+ tnt::Tntnet::shutdown();
+ }
+
+ void sigReload(int)
+ {
+ // stopping child with 111 signals monitor-process to restart child
+ tnt::Tntnet::restart();
+ }
+
+ void configureDispatcher(tnt::Dispatcher& dis, const tnt::Tntconfig& config)
+ {
+ typedef tnt::Dispatcher::CompidentType CompidentType;
+
+ const tnt::Tntconfig::config_entries_type& params = config.getConfigValues();
+
+ tnt::Tntconfig::config_entries_type::const_iterator vi;
+ for (vi = params.begin(); vi != params.end(); ++vi)
+ {
+ const tnt::Tntconfig::config_entry_type& v = *vi;
+ const tnt::Tntconfig::params_type& args = v.params;
+ if (v.key == "MapUrl")
+ {
+ if (args.size() < 2)
+ {
+ std::ostringstream msg;
+ msg << "invalid number of parameters (" << args.size() << ") in MapUrl";
+ throw std::runtime_error(msg.str());
+ }
+
+ std::string url = args[0];
+
+ CompidentType ci = CompidentType(args[1]);
+ if (args.size() > 2)
+ {
+ ci.setPathInfo(args[2]);
+ if (args.size() > 3)
+ ci.setArgs(CompidentType::args_type(args.begin() + 3, args.end()));
+ }
+
+ dis.addUrlMapEntry(url, ci);
+ }
+ }
+ }
+
+ bool checkChildSuccess(int fd)
+ {
+ log_debug("checkChildSuccess");
+
+ char buffer;
+ int ret = ::read(fd, &buffer, 1);
+ if (ret < 0)
+ throw std::runtime_error(
+ std::string("error in read: ") + strerror(errno));
+ close(fd);
+ return ret > 0;
+ }
+
+ void signalParentSuccess(int fd)
+ {
+ log_debug("signalParentSuccess");
+
+ ssize_t s = write(fd, "1", 1);
+ if (s < 0)
+ throw std::runtime_error(
+ std::string("error in write(): ") + strerror(errno));
+ close(fd);
+ }
+
+}
+
+namespace tnt
+{
+ ////////////////////////////////////////////////////////////////////////
+ // Tntnet
+ //
+ bool Tntnet::stop = false;
+ int Tntnet::ret = 0;
+ std::string Tntnet::pidFileName;
+
+ Tntnet::Tntnet(int& argc, char* argv[])
+ : propertyfilename(argc, argv, 'P'),
+ debug(argc, argv, 'd'),
+ queue(1000),
+ pollerthread(queue)
+ {
+ // check for argument -c
+ cxxtools::Arg<const char*> conf(argc, argv, 'c');
+ if (conf.isSet())
+ configFile = conf;
+ else
+ {
+ // read 1st parameter from argument-list
+ cxxtools::Arg<const char*> conf(argc, argv);
+ if (conf.isSet())
+ configFile = conf;
+ else
+ {
+ // check environment-variable TNTNET_CONF
+ const char* tntnetConf = ::getenv("TNTNET_CONF");
+ if (tntnetConf)
+ configFile = tntnetConf;
+ else
+ configFile = TNTNET_CONF; // take default
+ }
+ }
+ }
+
+ void Tntnet::setGroup() const
+ {
+ Tntconfig::params_type group = config.getConfigValue("Group");
+ if (group.size() >= 1)
+ {
+ struct group * gr = getgrnam(group.begin()->c_str());
+ if (gr == 0)
+ throw std::runtime_error("unknown group " + *group.begin());
+
+ log_debug("change group to " << *group.begin() << '(' << gr->gr_gid << ')');
+
+ int ret = setgid(gr->gr_gid);
+ if (ret != 0)
+ {
+ std::ostringstream msg;
+ msg << "cannot change group to " << *group.begin()
+ << '(' << gr->gr_gid << "): " << strerror(errno);
+ throw std::runtime_error(msg.str());
+ }
+ }
+ }
+
+ void Tntnet::setDir(const char* def) const
+ {
+ std::string dir = config.getValue("Dir", def);
+
+ if (!dir.empty())
+ {
+ log_debug("chdir(" << dir << ')');
+ if (chdir(dir.c_str()) == -1)
+ {
+ throw std::runtime_error(
+ std::string("error in chdir(): ")
+ + strerror(errno));
+ }
+ }
+
+ std::string chrootdir = config.getValue("Chroot");
+ if (!chrootdir.empty() && chroot(chrootdir.c_str()) == -1)
+ throw std::runtime_error(
+ std::string("error in chroot(): ")
+ + strerror(errno));
+ }
+
+ void Tntnet::setUser() const
+ {
+ Tntconfig::params_type user = config.getConfigValue("User");
+ if (user.size() >= 1)
+ {
+ struct passwd * pw = getpwnam(user.begin()->c_str());
+ if (pw == 0)
+ throw std::runtime_error("unknown user " + *user.begin());
+
+ log_debug("change user to " << *user.begin() << '(' << pw->pw_uid << ')');
+
+ int ret = setuid(pw->pw_uid);
+ if (ret != 0)
+ {
+ std::ostringstream msg;
+ msg << "cannot change user to " << *user.begin()
+ << '(' << pw->pw_uid << "): " << strerror(errno);
+ throw std::runtime_error(msg.str());
+ }
+ }
+ }
+
+ int Tntnet::mkDaemon() const
+ {
+ log_info("start daemon-mode");
+
+ int filedes[2];
+
+ if (pipe(filedes) != 0)
+ throw std::runtime_error(
+ std::string("error in pipe(int[2]): ") + strerror(errno));
+
+ int pid = fork();
+ if (pid > 0)
+ {
+ // parent
+
+ close(filedes[1]); // close write-fd
+
+ // exit with error, when nothing read
+ ::exit (checkChildSuccess(filedes[0]) ? 0 : 1);
+ }
+ else if (pid < 0)
+ throw std::runtime_error(
+ std::string("error in fork(): ") + strerror(errno));
+
+ // child
+
+ close(filedes[0]); // close read-fd
+
+ // setsid
+ if (setsid() == -1)
+ throw std::runtime_error(
+ std::string("error in setsid(): ")
+ + strerror(errno));
+
+ // return write-fd
+ return filedes[1];
+ }
+
+ void Tntnet::closeStdHandles() const
+ {
+ // close stdin, stdout and stderr
+ bool noclosestd = config.getBoolValue("NoCloseStdout", false);
+ if (noclosestd)
+ {
+ log_debug("not closing stdout");
+ return;
+ }
+
+ if (freopen("/dev/null", "r", stdin) == 0)
+ throw std::runtime_error(
+ std::string("unable to replace stdin with /dev/null: ")
+ + strerror(errno));
+
+ if (freopen("/dev/null", "w", stdout) == 0)
+ throw std::runtime_error(
+ std::string("unable to replace stdout with /dev/null: ")
+ + strerror(errno));
+
+ if (freopen("/dev/null", "w", stderr) == 0)
+ throw std::runtime_error(
+ std::string("unable to replace stderr with /dev/null: ")
+ + strerror(errno));
+ }
+
+ int Tntnet::run()
+ {
+ loadConfiguration();
+
+ if (debug)
+ {
+ log_init_debug();
+ log_warn("Debugmode");
+ isDaemon = false;
+ }
+ else
+ {
+ isDaemon = config.getBoolValue("Daemon", false);
+ }
+
+ if (isDaemon)
+ {
+ int filedes = mkDaemon();
+
+ setDir("");
+
+ bool nomonitor = config.getBoolValue("NoMonitor", false);
+ if (nomonitor)
+ {
+ log_debug("start worker-process without monitor");
+ writePidfile(getpid());
+ initWorkerProcess();
+
+ // change group and user
+ setGroup();
+ setUser();
+
+ initLogging();
+ workerProcess(filedes);
+ }
+ else
+ {
+ initWorkerProcess();
+ do
+ {
+ int filedes_monitor[2];
+
+ if (pipe(filedes_monitor) != 0)
+ throw std::runtime_error(
+ std::string("error in pipe(int[2]): ") + strerror(errno));
+
+ // fork workerprocess
+ int pid = fork();
+ if (pid < 0)
+ throw std::runtime_error(
+ std::string("error in forking workerprocess: ")
+ + strerror(errno));
+
+ if (pid == 0)
+ {
+ // workerprocess
+
+ close(filedes_monitor[0]); // close read-fd
+
+ // change group and user
+ setGroup();
+ setUser();
+
+ initLogging();
+ workerProcess(filedes_monitor[1]);
+ return ret;
+ }
+ else
+ {
+ close(filedes_monitor[1]); // close write-fd
+
+ // write child-pid
+ writePidfile(pid);
+
+ // wait for worker to signal success
+ if (!checkChildSuccess(filedes_monitor[0]))
+ ::exit(1);
+ if (filedes >= 0)
+ {
+ signalParentSuccess(filedes);
+ filedes = -1;
+ }
+
+ monitorProcess(pid);
+ if (!stop)
+ sleep(1);
+ }
+
+ } while (!stop);
+ }
+ }
+ else
+ {
+ log_info("no daemon-mode");
+ initLogging();
+ initWorkerProcess();
+ workerProcess();
+ }
+
+ return 0;
+ }
+
+ void Tntnet::initLogging()
+ {
+ if (debug)
+ return; // logging already initialized
+
+ std::string pf;
+ if (propertyfilename.isSet())
+ pf = propertyfilename.getValue();
+ else
+ pf = config.getValue("PropertyFile");
+
+ if (pf.empty())
+ log_init();
+ else
+ {
+ struct stat properties_stat;
+ if (stat(pf.c_str(), &properties_stat) != 0)
+ throw std::runtime_error("propertyfile " + pf + " not found");
+
+ log_init(pf.c_str());
+ }
+ }
+
+ void Tntnet::writePidfile(int pid)
+ {
+ pidFileName = config.getValue("PidFile", TNTNET_PID);
+
+ log_debug("pidfile=" << pidFileName);
+
+ if (!pidFileName.empty())
+ {
+ if (pidFileName[0] != '/')
+ {
+ // prepend current working-directory to pidfilename if not absolute
+ std::vector<char> buf(256);
+ const char* cwd;
+ while (true)
+ {
+ cwd = ::getcwd(&buf[0], buf.size());
+ if (cwd)
+ break;
+ else if (errno == ERANGE)
+ buf.resize(buf.size() * 2);
+ else
+ throw std::runtime_error(
+ std::string("error in getcwd: ") + strerror(errno));
+ }
+ pidFileName = std::string(cwd) + '/' + pidFileName;
+ log_debug("pidfile=" << pidFileName);
+ }
+
+ std::ofstream pidfile(pidFileName.c_str());
+ if (!pidfile)
+ throw std::runtime_error("unable to open pid-file " + pidFileName);
+ pidfile << pid;
+ }
+ }
+
+ void Tntnet::monitorProcess(int workerPid)
+ {
+ setDir("");
+
+ // close stdin, stdout and stderr
+ closeStdHandles();
+
+ int status;
+ waitpid(workerPid, &status, 0);
+
+ if (WIFSIGNALED(status))
+ {
+ // SIGTERM means normal exit
+ if (WTERMSIG(status) == SIGTERM)
+ {
+ log_info_master("child terminated normally");
+ stop = true;
+ }
+ else
+ {
+ log_warn_master("child terminated with signal "
+ << WTERMSIG(status) << " - restart child");
+ }
+ }
+ else if (WEXITSTATUS(status) == 111)
+ {
+ log_info_master("child requested restart");
+ }
+ else
+ {
+ log_info_master("child exited with exitcode " << WEXITSTATUS(status));
+ stop = true;
+ }
+
+ if (unlink(pidFileName.c_str()) != 0)
+ log_error_master("failed to remove pidfile \"" << pidFileName << "\" error " << errno);
+ }
+
+ void Tntnet::initWorkerProcess()
+ {
+ log_debug("init workerprocess");
+
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGABRT, SIG_IGN);
+ signal(SIGTERM, sigEnd);
+ signal(SIGHUP, sigReload);
+
+ configureDispatcher(d_dispatcher, config);
+
+ // create listener-threads
+ Tntconfig::config_entries_type configListen;
+ config.getConfigValues("Listen", configListen);
+
+ if (configListen.empty())
+ {
+ log_warn("no listeners defined - using 0.0.0.0:80");
+ ListenerBase* s = new tnt::Listener("0.0.0.0", 80, queue);
+ listeners.insert(s);
+ }
+ else
+ {
+ for (Tntconfig::config_entries_type::const_iterator it = configListen.begin();
+ it != configListen.end(); ++it)
+ {
+ if (it->params.empty())
+ throw std::runtime_error("empty Listen-entry");
+
+ unsigned short int port = 80;
+ if (it->params.size() >= 2)
+ {
+ std::istringstream p(it->params[1]);
+ p >> port;
+ if (!p)
+ {
+ std::ostringstream msg;
+ msg << "invalid port " << it->params[1];
+ throw std::runtime_error(msg.str());
+ }
+ }
+
+ std::string ip(it->params[0]);
+ log_debug("create listener ip=" << ip << " port=" << port);
+ ListenerBase* s = new tnt::Listener(ip, port, queue);
+ listeners.insert(s);
+ }
+ }
+
+#ifdef USE_SSL
+ // create ssl-listener-threads
+ std::string defaultCertificateFile = config.getValue("SslCertificate");
+ std::string defaultCertificateKey = config.getValue("SslKey");
+ configListen.clear();
+ config.getConfigValues("SslListen", configListen);
+
+ for (Tntconfig::config_entries_type::const_iterator it = configListen.begin();
+ it != configListen.end(); ++it)
+ {
+ if (it->params.empty())
+ throw std::runtime_error("empty SslListen-entry");
+
+ unsigned short int port = 443;
+ if (it->params.size() >= 2)
+ {
+ std::istringstream p(it->params[1]);
+ p >> port;
+ if (!p)
+ {
+ std::ostringstream msg;
+ msg << "invalid port " << it->params[1];
+ throw std::runtime_error(msg.str());
+ }
+ }
+
+ std::string certificateFile =
+ it->params.size() >= 3 ? it->params[2]
+ : defaultCertificateFile;
+ std::string certificateKey =
+ it->params.size() >= 4 ? it->params[3] :
+ it->params.size() >= 3 ? it->params[2] : defaultCertificateKey;
+
+ if (certificateFile.empty())
+ throw std::runtime_error("Ssl-certificate not configured");
+
+ std::string ip(it->params[0]);
+ log_debug("create ssl-listener ip=" << ip << " port=" << port);
+ ListenerBase* s = new Ssllistener(certificateFile.c_str(),
+ certificateKey.c_str(), ip, port, queue);
+ listeners.insert(s);
+ }
+#endif // USE_SSL
+
+ // configure worker (static)
+ Comploader::configure(config);
+
+ // configure http
+ HttpMessage::setMaxRequestSize(
+ config.getValue("MaxRequestSize", HttpMessage::getMaxRequestSize()));
+ Job::setSocketReadTimeout(
+ config.getValue("SocketReadTimeout", Job::getSocketReadTimeout()));
+ Job::setSocketWriteTimeout(
+ config.getValue("SocketWriteTimeout", Job::getSocketWriteTimeout()));
+ Job::setKeepAliveMax(
+ config.getValue("KeepAliveMax", Job::getKeepAliveMax()));
+ Job::setSocketBufferSize(
+ config.getValue("BufferSize", Job::getSocketBufferSize()));
+ HttpReply::setMinCompressSize(
+ config.getValue("MinCompressSize", HttpReply::getMinCompressSize()));
+ HttpReply::setKeepAliveTimeout(
+ config.getValue("KeepAliveTimeout", HttpReply::getKeepAliveTimeout()));
+ HttpReply::setDefaultContentType(
+ config.getValue("DefaultContentType", HttpReply::getDefaultContentType()));
+
+ log_debug("listeners.size()=" << listeners.size());
+ }
+
+ void Tntnet::workerProcess(int filedes)
+ {
+ log_debug("worker-process");
+
+ // reload configuration
+ config = Tntconfig();
+ loadConfiguration();
+
+ // initialize worker-process
+ minthreads = config.getValue<unsigned>("MinThreads", 5);
+ maxthreads = config.getValue<unsigned>("MaxThreads", 100);
+ threadstartdelay = config.getValue<unsigned>("ThreadStartDelay", 10);
+ Worker::setMinThreads(minthreads);
+ Worker::setMaxRequestTime(config.getValue<unsigned>("MaxRequestTime", Worker::getMaxRequestTime()));
+ Worker::setEnableCompression(config.getBoolValue("EnableCompression", Worker::getEnableCompression()));
+ queue.setCapacity(config.getValue<unsigned>("QueueSize", queue.getCapacity()));
+ Sessionscope::setDefaultTimeout(config.getValue<unsigned>("SessionTimeout", Sessionscope::getDefaultTimeout()));
+ Listener::setBacklog(config.getValue<int>("ListenBacklog", Listener::getBacklog()));
+ Listener::setListenRetry(config.getValue<int>("ListenRetry", Listener::getListenRetry()));
+ Dispatcher::setMaxUrlMapCache(config.getValue<unsigned>("MaxUrlMapCache", Dispatcher::getMaxUrlMapCache()));
+
+ Tntconfig::config_entries_type configSetEnv;
+ config.getConfigValues("SetEnv", configSetEnv);
+ for (Tntconfig::config_entries_type::const_iterator it = configSetEnv.begin();
+ it != configSetEnv.end(); ++it)
+ {
+ if (it->params.size() >= 2)
+ {
+#ifdef HAVE_SETENV
+ log_debug("setenv " << it->params[0] << "=\"" << it->params[1] << '"');
+ ::setenv(it->params[0].c_str(), it->params[1].c_str(), 1);
+#else
+ std::string name = it->params[0];
+ std::string value = it->params[1];
+
+ char* env = new char[name.size() + value.size() + 2];
+ name.copy(env, name.size());
+ env[name.size()] = '=';
+ value.copy(env + name.size() + 1, value.size());
+ env[name.size() + value.size() + 1] = '\0';
+
+ log_debug("putenv(" << env);
+ ::putenv(env);
+#endif
+ }
+ }
+
+ // create worker-threads
+ log_info("create " << minthreads << " worker threads");
+ for (unsigned i = 0; i < minthreads; ++i)
+ {
+ log_debug("create worker " << i);
+ Worker* s = new Worker(*this);
+ s->create();
+ }
+
+ // create poller-thread
+ log_debug("start poller thread");
+ pollerthread.create();
+
+ // launch listener-threads
+ log_info("create " << listeners.size() << " listener threads");
+ for (listeners_type::iterator it = listeners.begin();
+ it != listeners.end(); ++it)
+ (*it)->create();
+
+ log_debug("start timer thread");
+ cxxtools::MethodThread<Tntnet, cxxtools::AttachedThread> timerThread(*this, &Tntnet::timerTask);
+ timerThread.create();
+
+ if (filedes >= 0)
+ {
+ signalParentSuccess(filedes);
+ closeStdHandles();
+ }
+
+ // mainloop
+ cxxtools::Mutex mutex;
+ while (!stop)
+ {
+ {
+ cxxtools::MutexLock lock(mutex);
+ queue.noWaitThreads.wait(lock);
+ }
+
+ if (stop)
+ break;
+
+ if (Worker::getCountThreads() < maxthreads)
+ {
+ log_info("create workerthread");
+ Worker* s = new Worker(*this);
+ s->create();
+ }
+ else
+ log_warn("max worker-threadcount " << maxthreads << " reached");
+
+ if (threadstartdelay > 0)
+ usleep(threadstartdelay);
+ }
+
+ log_warn("stopping Tntnet");
+
+ // join-loop
+ while (!listeners.empty())
+ {
+ listeners_type::value_type s = *listeners.begin();
+ log_debug("remove listener from listener-list");
+ listeners.erase(s);
+
+ log_debug("request listener to stop");
+ s->doStop();
+
+ log_debug("join listener-thread");
+ s->join();
+ delete s;
+
+ log_debug("listener stopped");
+ }
+
+ log_info("listeners stopped");
+ }
+
+ void Tntnet::timerTask()
+ {
+ log_debug("timer thread");
+
+ while (!stop)
+ {
+ sleep(1);
+
+ log_debug("check sessiontimeout");
+ getScopemanager().checkSessionTimeout();
+
+ log_debug("worker-timer");
+ Worker::timer();
+ }
+
+ log_warn("stopping Tntnet");
+
+ if (!pidFileName.empty())
+ unlink(pidFileName.c_str());
+
+ queue.noWaitThreads.signal();
+ Worker::setMinThreads(0);
+ pollerthread.doStop();
+ }
+
+ void Tntnet::loadConfiguration()
+ {
+ config = Tntconfig();
+ config.load(configFile.c_str());
+ }
+
+ void Tntnet::shutdown()
+ {
+ stop = true;
+ }
+
+ void Tntnet::restart()
+ {
+ // stopping child with 111 signals monitor-process to restart child
+ stop = true;
+ ret = 111;
+ }
+
+}
+
+////////////////////////////////////////////////////////////////////////
+// main
+//
+#ifdef HAVE_TNTNET_MAIN
+int main(int argc, char* argv[])
+{
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGABRT, SIG_IGN);
+ signal(SIGTERM, sigEnd);
+ std::ios::sync_with_stdio(false);
+
+ try
+ {
+ tnt::Tntnet app(argc, argv);
+ if (argc != 1)
+ {
+ std::cout << PACKAGE_STRING "\n\n" <<
+ "usage: " << argv[0] << " {options}\n\n"
+ " -c file configurationfile (default: " TNTNET_CONF ")\n"
+ " -d enable all debug output (ignoring properties-file)\n";
+ return -1;
+ }
+
+ return app.run();
+ }
+ catch(const std::exception& e)
+ {
+ log_fatal(e.what());
+ std::cerr << e.what() << std::endl;
+ return -1;
+ }
+}
+#endif
diff --git a/httpd/worker.cpp b/httpd/worker.cpp
new file mode 100644
index 0000000..fc8a271
--- /dev/null
+++ b/httpd/worker.cpp
@@ -0,0 +1,365 @@
+/* worker.cpp
+ * Copyright (C) 2003-2005 Tommi Maekitalo
+ *
+ * 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
+ * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "tnt/worker.h"
+#include "tnt/dispatcher.h"
+#include "tnt/job.h"
+#include <tnt/httprequest.h>
+#include <tnt/httpreply.h>
+#include <tnt/httperror.h>
+#include <tnt/http.h>
+#include <tnt/poller.h>
+#include <cxxtools/log.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <locale>
+#include <errno.h>
+
+log_define("tntnet.worker")
+
+namespace
+{
+ static const char stateStarting[] = "0 starting";
+ static const char stateWaitingForJob[] = "1 waiting for job";
+ static const char stateParsing[] = "2 parsing request";
+ static const char statePostParsing[] = "3 post parsing";
+ static const char stateDispatch[] = "4 dispatch";
+ static const char stateProcessingRequest[] = "5 processing request";
+ static const char stateFlush[] = "6 flush";
+ static const char stateSendReply[] = "7 send reply";
+ static const char stateSendError[] = "8 send error";
+ static const char stateStopping[] = "9 stopping";
+}
+
+namespace tnt
+{
+ cxxtools::Mutex Worker::mutex;
+ unsigned Worker::nextThreadNumber = 0;
+ Worker::workers_type Worker::workers;
+ unsigned Worker::maxRequestTime = 600;
+ unsigned Worker::minThreads = 5;
+ bool Worker::enableCompression = true;
+
+ Worker::ComploaderPoolType Worker::comploaderPool;
+
+ Worker::Worker(Tntnet& app)
+ : application(app),
+ comploaderObject(comploaderPool.get()),
+ comploader(comploaderObject),
+ threadId(0),
+ state(stateStarting),
+ lastWaitTime(0)
+ {
+ log_debug("initialize thread " << threadId);
+
+ cxxtools::MutexLock lock(mutex);
+ workers.insert(this);
+ }
+
+ Worker::~Worker()
+ {
+ cxxtools::MutexLock lock(mutex);
+ workers.erase(this);
+ comploader.cleanup();
+
+ log_debug("delete worker " << threadId << " - " << workers.size() << " threads left - " << application.getQueue().getWaitThreadCount() << " waiting threads");
+ }
+
+ void Worker::run()
+ {
+ threadId = pthread_self();
+ Jobqueue& queue = application.getQueue();
+ log_debug("start thread " << threadId);
+ while (queue.getWaitThreadCount() < minThreads)
+ {
+ log_debug("waiting for job");
+ state = stateWaitingForJob;
+ Jobqueue::JobPtr j = queue.get();
+ if (Tntnet::shouldStop())
+ {
+ log_warn("stop worker");
+ break;
+ }
+ log_debug("got job - fd=" << j->getFd());
+
+ std::iostream& socket = j->getStream();
+
+ try
+ {
+ bool keepAlive;
+ do
+ {
+ time(&lastWaitTime);
+
+ log_debug("read request");
+
+ keepAlive = false;
+ state = stateParsing;
+ j->getParser().parse(socket);
+ state = statePostParsing;
+
+ if (socket.eof())
+ log_debug("eof");
+ else if (j->getParser().failed())
+ {
+ state = stateSendError;
+ log_warn("bad request");
+ socket << "HTTP/1.0 500 bad request\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n"
+ "<html><body><h1>Error</h1><p>bad request</p></body></html>"
+ << std::endl;
+ }
+ else if (socket.fail())
+ log_error("socket failed");
+ else
+ {
+ j->getRequest().doPostParse();
+
+ j->setWrite();
+ keepAlive = processRequest(j->getRequest(), socket,
+ j->decrementKeepAliveCounter());
+
+ if (keepAlive)
+ {
+ j->setRead();
+ j->clear();
+
+ // if there is something to do and no threads waiting, we take
+ // the next job just to improve resposiveness.
+ if (queue.getWaitThreadCount() == 0
+ && !queue.empty())
+ {
+ application.getPoller().addIdleJob(j);
+ keepAlive = false;
+ }
+ else
+ {
+ struct pollfd fd;
+ fd.fd = j->getFd();
+ fd.events = POLLIN;
+ if (::poll(&fd, 1, Job::getSocketReadTimeout()) == 0)
+ {
+ application.getPoller().addIdleJob(j);
+ keepAlive = false;
+ }
+ }
+ }
+ }
+ } while (keepAlive);
+ }
+ catch (const cxxtools::net::Timeout& e)
+ {
+ log_debug("timeout - put job in poller");
+ application.getPoller().addIdleJob(j);
+ }
+ catch (const cxxtools::net::Exception& e)
+ {
+ if (e.getErrno() != ENOENT)
+ log_warn("unexpected exception: " << e.what());
+ }
+ catch (const std::exception& e)
+ {
+ log_warn("unexpected exception: " << e.what());
+ }
+ }
+
+ time(&lastWaitTime);
+
+ log_debug("end worker-thread " << threadId);
+
+ state = stateStopping;
+ }
+
+ bool Worker::processRequest(HttpRequest& request, std::iostream& socket,
+ unsigned keepAliveCount)
+ {
+ // log message
+ log_info("process request: " << request.getMethod() << ' ' << request.getQuery()
+ << " from client " << request.getPeerIp() << " user-Agent \"" << request.getUserAgent()
+ << '"');
+
+ // create reply-object
+ HttpReply reply(socket);
+ reply.setVersion(request.getMajorVersion(), request.getMinorVersion());
+ reply.setMethod(request.getMethod());
+
+ std::locale loc = request.getLocale();
+ reply.out().imbue(loc);
+ reply.sout().imbue(loc);
+
+ if (request.keepAlive())
+ reply.setKeepAliveCounter(keepAliveCount);
+
+ if (enableCompression)
+ reply.setAcceptEncoding(request.getEncoding());
+
+ // process request
+ try
+ {
+ try
+ {
+ dispatch(request, reply);
+
+ if (!request.keepAlive() || !reply.keepAlive())
+ keepAliveCount = 0;
+
+ if (keepAliveCount > 0)
+ log_debug("keep alive");
+ else
+ {
+ log_debug("no keep alive request/reply="
+ << request.keepAlive() << '/' << reply.keepAlive());
+ }
+ }
+ catch (const HttpError& e)
+ {
+ throw;
+ }
+ catch (const std::exception& e)
+ {
+ throw HttpError(HTTP_INTERNAL_SERVER_ERROR, e.what());
+ }
+ }
+ catch (const HttpError& e)
+ {
+ state = stateSendError;
+ log_warn("http-Error: " << e.what());
+ HttpReply reply(socket);
+ reply.setVersion(request.getMajorVersion(), request.getMinorVersion());
+ if (request.keepAlive())
+ reply.setKeepAliveCounter(keepAliveCount);
+ else
+ keepAliveCount = 0;
+ reply.out() << "<html><body><h1>Error</h1><p>"
+ << e.what() << "</p></body></html>" << std::endl;
+ reply.sendReply(e.getErrcode(), e.getErrmsg());
+ }
+
+ return keepAliveCount > 0;
+ }
+
+ void Worker::dispatch(HttpRequest& request, HttpReply& reply)
+ {
+ state = stateDispatch;
+ const std::string& url = request.getUrl();
+
+ log_debug("dispatch " << request.getQuery());
+
+ if (!HttpRequest::checkUrl(url))
+ throw HttpError(HTTP_BAD_REQUEST, "illegal url");
+
+ request.setThreadScope(threadScope);
+
+ Dispatcher::PosType pos(application.getDispatcher(), request.getUrl());
+ while (true)
+ {
+ state = stateDispatch;
+
+ // pos.getNext() throws NotFoundException at end
+ Dispatcher::CompidentType ci = pos.getNext();
+ try
+ {
+ log_debug("load component " << ci);
+ Component& comp = comploader.fetchComp(ci, application.getDispatcher());
+ request.setPathInfo(ci.hasPathInfo() ? ci.getPathInfo() : url);
+ request.setArgs(ci.getArgs());
+
+ application.getScopemanager().preCall(request, ci.libname);
+
+ log_debug("call component " << ci << " path " << request.getPathInfo());
+ state = stateProcessingRequest;
+ unsigned http_return = comp(request, reply, request.getQueryParams());
+ if (http_return != DECLINED)
+ {
+ if (reply.isDirectMode())
+ {
+ log_info("request ready, returncode " << http_return);
+ state = stateFlush;
+ reply.out().flush();
+ }
+ else
+ {
+ log_info("request ready, returncode " << http_return << " - ContentSize: " << reply.getContentSize());
+
+ application.getScopemanager().postCall(request, reply, ci.libname);
+
+ state = stateSendReply;
+ reply.sendReply(http_return);
+ }
+
+ if (reply.out())
+ log_debug("reply sent");
+ else
+ log_warn("stream error");
+
+ return;
+ }
+ else
+ log_debug("component " << ci << " returned DECLINED");
+ }
+ catch (const cxxtools::dl::DlopenError& e)
+ {
+ log_warn("DlopenError catched - libname " << e.getLibname());
+ }
+ catch (const cxxtools::dl::SymbolNotFound& e)
+ {
+ log_warn("SymbolNotFound catched - symbol " << e.getSymbol());
+ }
+ }
+
+ throw NotFoundException(request.getUrl());
+ }
+
+ void Worker::timer()
+ {
+ time_t currentTime;
+ time(&currentTime);
+
+ cxxtools::MutexLock lock(mutex);
+ for (workers_type::iterator it = workers.begin();
+ it != workers.end(); ++it)
+ {
+ (*it)->healthCheck(currentTime);
+ }
+ }
+
+ void Worker::healthCheck(time_t currentTime)
+ {
+ if (state == stateProcessingRequest
+ && lastWaitTime != 0
+ && maxRequestTime > 0)
+ {
+ if (static_cast<unsigned>(currentTime - lastWaitTime) > maxRequestTime)
+ {
+ log_fatal("requesttime " << maxRequestTime << " seconds in thread "
+ << threadId << " exceeded - exit process");
+ log_info("current state: " << state);
+ exit(111);
+ }
+ }
+ }
+
+ Worker::workers_type::size_type Worker::getCountThreads()
+ {
+ cxxtools::MutexLock lock(mutex);
+ return workers.size();
+ }
+}
diff --git a/live.cpp b/live.cpp
new file mode 100644
index 0000000..da7c300
--- /dev/null
+++ b/live.cpp
@@ -0,0 +1,86 @@
+/*
+ * httpd.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id: live.cpp,v 1.1 2007/01/02 19:18:27 lordjaxom Exp $
+ */
+
+#include <memory>
+#include <vdr/plugin.h>
+#include "setup.h"
+#include "thread.h"
+
+namespace vdrlive {
+
+using namespace std;
+
+static const char *VERSION = "0.0.1";
+static const char *DESCRIPTION = "Enter description for 'httpd' plugin";
+
+class Plugin : public cPlugin {
+public:
+ Plugin(void);
+ virtual const char *Version(void) { return VERSION; }
+ virtual const char *Description(void) { return DESCRIPTION; }
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual bool Start(void);
+ virtual void Stop(void);
+ virtual void MainThreadHook(void);
+ virtual cString Active(void);
+ virtual cMenuSetupPage *SetupMenu(void);
+ virtual bool SetupParse(const char *Name, const char *Value);
+
+private:
+ auto_ptr< ServerThread > m_thread;
+};
+
+Plugin::Plugin(void)
+{
+}
+
+const char *Plugin::CommandLineHelp(void)
+{
+ return "-L DIR --lib=DIR libtnt-live.so will be searched in DIR\n";
+}
+
+bool Plugin::ProcessArgs(int argc, char *argv[])
+{
+ return Setup::Get().Parse( argc, argv );
+}
+
+bool Plugin::Start(void)
+{
+ // XXX error handling
+ m_thread.reset( new ServerThread );
+ m_thread->Start();
+ return true;
+}
+
+void Plugin::Stop(void)
+{
+}
+
+void Plugin::MainThreadHook(void)
+{
+}
+
+cString Plugin::Active(void)
+{
+ return NULL;
+}
+
+cMenuSetupPage *Plugin::SetupMenu(void)
+{
+ return NULL;
+}
+
+bool Plugin::SetupParse(const char *Name, const char *Value)
+{
+ return true;
+}
+
+} // namespace vdrlive
+
+VDRPLUGINCREATOR(vdrlive::Plugin); // Don't touch this!
diff --git a/setup.cpp b/setup.cpp
new file mode 100644
index 0000000..ab518dc
--- /dev/null
+++ b/setup.cpp
@@ -0,0 +1,33 @@
+#include <getopt.h>
+#include "setup.h"
+
+namespace vdrlive {
+
+Setup::Setup()
+{
+}
+
+bool Setup::Parse( int argc, char* argv[] )
+{
+ static struct option opts[] = {
+ { "lib", required_argument, NULL, 'L' },
+ { 0 }
+ };
+
+ int optchar, optind = 0;
+ while ( ( optchar = getopt_long( argc, argv, "L:", opts, &optind ) ) != -1 ) {
+ switch ( optchar ) {
+ case 'L': m_libraryPath = optarg; break;
+ default: return false;
+ }
+ }
+ return true;
+}
+
+Setup& Setup::Get()
+{
+ static Setup instance;
+ return instance;
+}
+
+} // namespace vdrlive
diff --git a/setup.h b/setup.h
new file mode 100644
index 0000000..d3a6027
--- /dev/null
+++ b/setup.h
@@ -0,0 +1,26 @@
+#ifndef VDR_LIVE_SETUP_H
+#define VDR_LIVE_SETUP_H
+
+#include <string>
+
+namespace vdrlive {
+
+class Setup
+{
+public:
+ static Setup& Get();
+
+ std::string const& GetLibraryPath() const { return m_libraryPath; }
+
+ bool Parse( int argc, char* argv[] );
+
+private:
+ Setup();
+ Setup( Setup const& );
+
+ std::string m_libraryPath;
+};
+
+} // namespace vdrlive
+
+#endif // VDR_LIVE_SETUP_H
diff --git a/thread.cpp b/thread.cpp
new file mode 100644
index 0000000..df6580c
--- /dev/null
+++ b/thread.cpp
@@ -0,0 +1,40 @@
+#include <cstdlib>
+#include <iostream>
+#include <stdexcept>
+#include <tnt/tntnet.h>
+#include <vdr/plugin.h>
+#include "thread.h"
+#include "tntconfig.h"
+
+namespace vdrlive {
+
+using namespace std;
+using namespace tnt;
+
+ServerThread::ServerThread():
+ m_configPath( 0 )
+{
+}
+
+ServerThread::~ServerThread()
+{
+ free( m_configPath );
+}
+
+void ServerThread::Action()
+{
+ try {
+ m_configPath = strdup( TntConfig::Get().GetConfigPath().c_str() );
+
+ char* argv[] = { "tntnet", "-c", m_configPath };
+ int argc = sizeof( argv ) / sizeof( argv[0] );
+ Tntnet app( argc, argv );
+ app.run();
+ } catch ( exception const& ex ) {
+ // XXX move initial error handling to live.cpp
+ esyslog( "ERROR: live httpd server crashed: %s", ex.what() );
+ cerr << "HTTPD FATAL ERROR: " << ex.what() << endl;
+ }
+}
+
+} // namespace vdrlive
diff --git a/thread.h b/thread.h
new file mode 100644
index 0000000..d0a3bca
--- /dev/null
+++ b/thread.h
@@ -0,0 +1,22 @@
+#ifndef VDR_LIVE_THREAD_H
+#define VDR_LIVE_THREAD_H
+
+#include <vdr/thread.h>
+
+namespace vdrlive {
+
+class ServerThread : public cThread {
+public:
+ ServerThread();
+ virtual ~ServerThread();
+
+protected:
+ virtual void Action();
+
+private:
+ char* m_configPath;
+};
+
+} // namespace vdrlive
+
+#endif // VDR_LIVE_THREAD_H
diff --git a/tntconfig.cpp b/tntconfig.cpp
new file mode 100644
index 0000000..78e3f45
--- /dev/null
+++ b/tntconfig.cpp
@@ -0,0 +1,65 @@
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+#include <sstream>
+#include <stdexcept>
+#include <vdr/plugin.h>
+#include "setup.h"
+#include "tntconfig.h"
+
+namespace vdrlive {
+
+using namespace std;
+
+TntConfig::TntConfig()
+{
+ WriteConfig();
+}
+
+void TntConfig::WriteConfig()
+{
+ WriteProperties();
+
+ ostringstream builder;
+ builder << cPlugin::ConfigDirectory( PLUGIN_NAME_I18N ) << "/httpd.config";
+ m_configPath = builder.str();
+
+ ofstream file( m_configPath.c_str(), ios::out | ios::trunc );
+ if ( !file ) {
+ ostringstream builder;
+ builder << "Can't open " << m_configPath << " for writing: " << strerror( errno );
+ throw runtime_error( builder.str() );
+ }
+
+ // XXX modularize
+ file << "MapUrl /([^.]+)(\\..+)? $1@libtnt-live" << std::endl;
+ file << "Listen 0.0.0.0 8001" << std::endl;
+ file << "PropertyFile " << m_propertiesPath << std::endl;
+ file << "CompPath " << Setup::Get().GetLibraryPath() << "/" << std::endl;
+}
+
+void TntConfig::WriteProperties()
+{
+ ostringstream builder;
+ builder << cPlugin::ConfigDirectory( PLUGIN_NAME_I18N ) << "/httpd.properties";
+ m_propertiesPath = builder.str();
+
+ ofstream file( m_propertiesPath.c_str(), ios::out | ios::trunc );
+ if ( !file ) {
+ ostringstream builder;
+ builder << "Can't open " << m_propertiesPath << " for writing: " << strerror( errno );
+ throw runtime_error( builder.str() );
+ }
+
+ // XXX modularize
+ file << "rootLogger=INFO" << std::endl;
+ file << "logger.tntnet=INFO" << std::endl;
+}
+
+TntConfig const& TntConfig::Get()
+{
+ static TntConfig instance;
+ return instance;
+}
+
+} // namespace vdrlive
diff --git a/tntconfig.h b/tntconfig.h
new file mode 100644
index 0000000..ba31f97
--- /dev/null
+++ b/tntconfig.h
@@ -0,0 +1,28 @@
+#ifndef VDR_LIVE_TNTCONFIG_H
+#define VDR_LIVE_TNTCONFIG_H
+
+#include <string>
+
+namespace vdrlive {
+
+class TntConfig
+{
+public:
+ static TntConfig const& Get();
+
+ std::string const& GetConfigPath() const { return m_configPath; }
+
+private:
+ std::string m_propertiesPath;
+ std::string m_configPath;
+
+ TntConfig();
+ TntConfig( TntConfig const& );
+
+ void WriteProperties();
+ void WriteConfig();
+};
+
+} // namespace vdrlive
+
+#endif // VDR_LIVE_TNTCONFIG_H